/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *  
 *    http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License. 
 *  
 */
package org.apache.directory.studio.templateeditor.model.parser;


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.eclipse.osgi.util.NLS;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.apache.directory.studio.templateeditor.model.AbstractTemplate;
import org.apache.directory.studio.templateeditor.model.ExtensionPointTemplate;
import org.apache.directory.studio.templateeditor.model.FileTemplate;
import org.apache.directory.studio.templateeditor.model.Template;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateCheckbox;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateComposite;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateDate;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateFileChooser;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateForm;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateImage;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateLabel;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateLink;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateListbox;
import org.apache.directory.studio.templateeditor.model.widgets.TemplatePassword;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateRadioButtons;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateSection;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateSpinner;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateTable;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateTextField;
import org.apache.directory.studio.templateeditor.model.widgets.TemplateWidget;
import org.apache.directory.studio.templateeditor.model.widgets.ValueItem;
import org.apache.directory.studio.templateeditor.model.widgets.WidgetAlignment;
import org.apache.directory.studio.templateeditor.view.preferences.PreferencesFileTemplate;


/**
 * This class is used to read/write the 'connections.xml' file.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public class TemplateIO
{
    /** The logger */
    private static final Logger LOG = LoggerFactory.getLogger( TemplateIO.class );

    private static final String THE_FILE_DOES_NOT_SEEM_TO_BE_A_VALID_TEMPLATE_FILE = Messages
        .getString( "TemplateIO.FileIsNotAValidTemplateFile" ); //$NON-NLS-1$

    // XML Elements
    private static final String ATTRIBUTE_ATTRIBUTETYPE = "attributeType"; //$NON-NLS-1$
    private static final String ATTRIBUTE_CHARACTERSLIMIT = "charactersLimit"; //$NON-NLS-1$
    private static final String ATTRIBUTE_DESCRIPTION = "description"; //$NON-NLS-1$
    private static final String ATTRIBUTE_DIGITS = "digits"; //$NON-NLS-1$
    private static final String ATTRIBUTE_DOLLAR_SIGN_IS_NEW_LINE = "dollarSignIsNewLine"; //$NON-NLS-1$
    private static final String ATTRIBUTE_EXTENSIONS = "extensions"; //$NON-NLS-1$
    private static final String ATTRIBUTE_EQUALCOLUMNS = "equalColumns"; //$NON-NLS-1$
    private static final String ATTRIBUTE_ENABLED = "enabled"; //$NON-NLS-1$
    private static final String ATTRIBUTE_EXPANDABLE = "expandable"; //$NON-NLS-1$
    private static final String ATTRIBUTE_EXPANDED = "expanded"; //$NON-NLS-1$
    private static final String ATTRIBUTE_FORMAT = "format"; //$NON-NLS-1$
    private static final String ATTRIBUTE_GRAB_EXCESS_HORIZONTAL_SPACE = "grabExcessHorizontalSpace"; //$NON-NLS-1$
    private static final String ATTRIBUTE_GRAB_EXCESS_VERTICAL_SPACE = "grabExcessVerticalSpace"; //$NON-NLS-1$
    private static final String ATTRIBUTE_HIDDEN = "hidden"; //$NON-NLS-1$
    private static final String ATTRIBUTE_HEIGHT = "height"; //$NON-NLS-1$
    private static final String ATTRIBUTE_HORIZONTAL_ALIGNMENT = "horizontalAlignment"; //$NON-NLS-1$
    private static final String ATTRIBUTE_HORIZONTAL_SPAN = "horizontalSpan"; //$NON-NLS-1$
    private static final String ATTRIBUTE_ID = "id"; //$NON-NLS-1$
    private static final String ATTRIBUTE_IMAGE_HEIGHT = "imageHeight"; //$NON-NLS-1$
    private static final String ATTRIBUTE_IMAGE_WIDTH = "imageWidth"; //$NON-NLS-1$
    private static final String ATTRIBUTE_INCREMENT = "increment"; //$NON-NLS-1$
    private static final String ATTRIBUTE_LABEL = "label"; //$NON-NLS-1$
    private static final String ATTRIBUTE_MAXIMUM = "maximum"; //$NON-NLS-1$
    private static final String ATTRIBUTE_MINIMUM = "minimum"; //$NON-NLS-1$
    private static final String ATTRIBUTE_MULTIPLESELECTION = "multipleSelection"; //$NON-NLS-1$
    private static final String ATTRIBUTE_NUMBEROFCOLUMNS = "numberOfColumns"; //$NON-NLS-1$
    private static final String ATTRIBUTE_NUMBEROFROWS = "numberOfRows"; //$NON-NLS-1$
    private static final String ATTRIBUTE_PAGEINCREMENT = "pageIncrement"; //$NON-NLS-1$
    private static final String ATTRIBUTE_SHOWADDBUTTON = "showAddButton"; //$NON-NLS-1$
    private static final String ATTRIBUTE_SHOWBROWSEBUTTON = "showBrowseButton"; //$NON-NLS-1$
    private static final String ATTRIBUTE_SHOWCLEARBUTTON = "showClearButton"; //$NON-NLS-1$
    private static final String ATTRIBUTE_SHOWDELETEBUTTON = "showDeleteButton"; //$NON-NLS-1$
    private static final String ATTRIBUTE_SHOWEDITBUTTON = "showEditButton"; //$NON-NLS-1$
    private static final String ATTRIBUTE_SHOWICON = "showIcon"; //$NON-NLS-1$
    private static final String ATTRIBUTE_SHOWSAVEASBUTTON = "showSaveAsButton"; //$NON-NLS-1$
    private static final String ATTRIBUTE_SHOWSHOWPASSWORDCHECKBOX = "showShowPasswordCheckbox"; //$NON-NLS-1$
    private static final String ATTRIBUTE_TITLE = "title"; //$NON-NLS-1$
    private static final String ATTRIBUTE_VALUE = "value"; //$NON-NLS-1$
    private static final String ATTRIBUTE_VERTICAL_ALIGNMENT = "verticalAlignment"; //$NON-NLS-1$
    private static final String ATTRIBUTE_VERTICAL_SPAN = "verticalSpan"; //$NON-NLS-1$
    private static final String ATTRIBUTE_WIDTH = "width"; //$NON-NLS-1$
    private static final String ELEMENT_AUXILIARIES = "auxiliaries"; //$NON-NLS-1$
    private static final String ELEMENT_AUXILIARY = "auxiliary"; //$NON-NLS-1$
    private static final String ELEMENT_BUTTON = "button"; //$NON-NLS-1$
    private static final String ELEMENT_BUTTONS = "buttons"; //$NON-NLS-1$
    private static final String ELEMENT_CHECKBOX = "checkbox"; //$NON-NLS-1$
    private static final String ELEMENT_CHECKEDVALUE = "checkedValue"; //$NON-NLS-1$
    private static final String ELEMENT_COMPOSITE = "composite"; //$NON-NLS-1$
    private static final String ELEMENT_DATA = "data"; //$NON-NLS-1$
    private static final String ELEMENT_DATE = "date"; //$NON-NLS-1$
    private static final String ELEMENT_FILECHOOSER = "fileChooser"; //$NON-NLS-1$
    private static final String ELEMENT_FORM = "form"; //$NON-NLS-1$
    private static final String ELEMENT_ICON = "icon"; //$NON-NLS-1$
    private static final String ELEMENT_IMAGE = "image"; //$NON-NLS-1$
    private static final String ELEMENT_ITEM = "item"; //$NON-NLS-1$
    private static final String ELEMENT_ITEMS = "items"; //$NON-NLS-1$
    private static final String ELEMENT_LABEL = "label"; //$NON-NLS-1$
    private static final String ELEMENT_LINK = "link"; //$NON-NLS-1$
    private static final String ELEMENT_LISTBOX = "listbox"; //$NON-NLS-1$
    private static final String ELEMENT_OBJECTCLASSES = "objectClasses"; //$NON-NLS-1$
    private static final String ELEMENT_PASSWORD = "password"; //$NON-NLS-1$
    private static final String ELEMENT_RADIOBUTTONS = "radiobuttons"; //$NON-NLS-1$
    private static final String ELEMENT_SECTION = "section"; //$NON-NLS-1$
    private static final String ELEMENT_SPINNER = "spinner"; //$NON-NLS-1$
    private static final String ELEMENT_STRUCTURAL = "structural"; //$NON-NLS-1$
    private static final String ELEMENT_TABLE = "table"; //$NON-NLS-1$
    private static final String ELEMENT_TEMPLATE = "template"; //$NON-NLS-1$
    private static final String ELEMENT_TEXTFIELD = "textfield"; //$NON-NLS-1$
    private static final String ELEMENT_VALUE = "value"; //$NON-NLS-1$
    private static final String ELEMENT_UNCHECKEDVALUE = "uncheckedValue"; //$NON-NLS-1$
    private static final String VALUE_BEGINNING = "beginning"; //$NON-NLS-1$
    private static final String VALUE_CENTER = "center"; //$NON-NLS-1$
    private static final String VALUE_END = "end"; //$NON-NLS-1$
    private static final String VALUE_FALSE = "false"; //$NON-NLS-1$
    private static final String VALUE_FILL = "fill"; //$NON-NLS-1$
    private static final String VALUE_NONE = "none"; //$NON-NLS-1$
    private static final String VALUE_TRUE = "true"; //$NON-NLS-1$


    /**
     * Reads the input stream as a file template
     *
     * @param is
     *      the input stream
     * @return
     *      the template
     * @throws TemplateIOException 
     *      if an error occurs when converting the document
     */
    public static FileTemplate readAsFileTemplate( InputStream is ) throws TemplateIOException
    {
        // Creating the FileTemplate
        FileTemplate template = new FileTemplate();

        // Reading the template
        readTemplate( is, template );

        // Returning the template
        return template;
    }


    /**
     * Reads the input stream as a preferences file template
     *
     * @param is
     *      the input stream
     * @return
     *      the template
     * @throws TemplateIOException 
     *      if an error occurs when converting the document
     */
    public static PreferencesFileTemplate readAsPreferencesFileTemplate( InputStream is ) throws TemplateIOException
    {
        // Creating the PreferencesFileTemplate
        PreferencesFileTemplate template = new PreferencesFileTemplate();

        // Reading the template
        readTemplate( is, template );

        // Returning the template
        return template;
    }


    /**
     * Reads the input stream as a file template
     *
     * @param is
     *      the input stream
     * @return
     *      the template
     * @throws TemplateIOException 
     *      if an error occurs when converting the document
     */
    public static ExtensionPointTemplate readAsExtensionPointTemplate( InputStream is ) throws TemplateIOException
    {
        // Creating the FileTemplate
        ExtensionPointTemplate template = new ExtensionPointTemplate();

        // Reading the template
        readTemplate( is, template );

        // Returning the template
        return template;
    }


    /**
     * Reads the input stream as a file template
    *
    * @param is
    *      the input stream
    * @return
    *      the template
    * @throws TemplateIOException 
    *      if an error occurs when converting the document
    */
    public static void readTemplate( InputStream is, Template template ) throws TemplateIOException
    {
        // Getting the document
        Document document = getDocument( is );

        // Reading the template.
        readTemplate( document.getRootElement(), template );
    }


    /**
     * Gets the document associated with the input stream.
     *
     * @param stream
     *      the input stream
     * @return
     * @throws TemplateIOException
     */
    private static Document getDocument( InputStream is ) throws TemplateIOException
    {
        try
        {
            return ( new SAXReader() ).read( is );
        }
        catch ( DocumentException e )
        {
            throw new TemplateIOException( e.getMessage() );
        }
    }


    /**
     * Reads the template.
     *
     * @param rootElement
     *      the root element
     * @param template
     *      the template
     */
    private static void readTemplate( Element rootElement, Template template ) throws TemplateIOException
    {
        LOG.debug( "Reading the template" ); //$NON-NLS-1$

        // Verifying the root 'template' element
        if ( ( rootElement == null ) || ( !rootElement.getName().equalsIgnoreCase( ELEMENT_TEMPLATE ) ) )
        {
            LOG.error( "Unable to find element: '" + ELEMENT_TEMPLATE + "'." ); //$NON-NLS-1$ //$NON-NLS-2$
            throw new TemplateIOException( THE_FILE_DOES_NOT_SEEM_TO_BE_A_VALID_TEMPLATE_FILE + "\n" //$NON-NLS-1$
                + NLS.bind( Messages.getString( "TemplateIO.UnableToFindElement" ), ELEMENT_TEMPLATE ) ); //$NON-NLS-1$
        }

        // Reading the ID
        Attribute idAttribute = rootElement.attribute( ATTRIBUTE_ID );
        if ( ( idAttribute != null ) && ( idAttribute.getText() != null ) )
        {
            // Verifying if the ID is valid
            if ( AbstractTemplate.isValidId( idAttribute.getText() ) )
            {
                LOG.debug( "ID='" + idAttribute.getText() + "'" ); //$NON-NLS-1$ //$NON-NLS-2$
                template.setId( idAttribute.getText() );
            }
            else
            {
                LOG.error( "Invalid ID attribute: '" + idAttribute.getText() + "'." ); //$NON-NLS-1$ //$NON-NLS-2$
                throw new TemplateIOException( THE_FILE_DOES_NOT_SEEM_TO_BE_A_VALID_TEMPLATE_FILE + "\n" //$NON-NLS-1$
                    + NLS.bind( Messages.getString( "TemplateIO.InvalidIdAttribute" ), idAttribute.getText() ) ); //$NON-NLS-1$
            }
        }
        else
        {
            LOG.error( "Unable to find attribute or attribute empty: '" + ATTRIBUTE_ID + "'." ); //$NON-NLS-1$ //$NON-NLS-2$
            throw new TemplateIOException( THE_FILE_DOES_NOT_SEEM_TO_BE_A_VALID_TEMPLATE_FILE + "\n" //$NON-NLS-1$
                + NLS.bind( Messages.getString( "TemplateIO.AttributeNotFoundOrEmpty" ), ATTRIBUTE_ID ) ); //$NON-NLS-1$
        }

        // Reading the title
        Attribute titleAttribute = rootElement.attribute( ATTRIBUTE_TITLE );
        if ( ( titleAttribute != null ) && ( titleAttribute.getText() != null ) )
        {
            LOG.debug( "Title='" + titleAttribute.getText() + "'" ); //$NON-NLS-1$ //$NON-NLS-2$
            template.setTitle( titleAttribute.getText() );
        }
        else
        {

            LOG.error( "Unable to find attribute or attribute empty: '" + ATTRIBUTE_TITLE + "'." ); //$NON-NLS-1$ //$NON-NLS-2$
            throw new TemplateIOException( THE_FILE_DOES_NOT_SEEM_TO_BE_A_VALID_TEMPLATE_FILE + "\n" //$NON-NLS-1$
                + NLS.bind( Messages.getString( "TemplateIO.AttributeNotFoundOrEmpty" ), ATTRIBUTE_TITLE ) ); //$NON-NLS-1$
        }

        // Reading the object classes
        readObjectClasses( rootElement, template );

        // Reading the form
        readForm( rootElement, template );
    }


    /**
     * Reads the object classes for the template.
     *
     * @param element
     *      the element
     * @param template
     *      the template
     * @throws TemplateIOException
     */
    private static void readObjectClasses( Element element, Template template ) throws TemplateIOException
    {
        LOG.debug( "Reading the template's object classes" ); //$NON-NLS-1$

        // Reading the 'objectClasses' element
        Element objectClassesElement = element.element( ELEMENT_OBJECTCLASSES );
        if ( objectClassesElement == null )
        {
            LOG.error( "Unable to find element: '" + ELEMENT_OBJECTCLASSES + "'." ); //$NON-NLS-1$ //$NON-NLS-2$
            throw new TemplateIOException( THE_FILE_DOES_NOT_SEEM_TO_BE_A_VALID_TEMPLATE_FILE + "\n" //$NON-NLS-1$
                + NLS.bind( Messages.getString( "TemplateIO.UnableToFindElement" ), ELEMENT_OBJECTCLASSES ) ); //$NON-NLS-1$
        }

        // Reading the 'structural' element
        Element structuralElement = objectClassesElement.element( ELEMENT_STRUCTURAL );
        if ( structuralElement != null )
        {
            String structuralObjectClassText = structuralElement.getText();
            if ( ( structuralObjectClassText != null ) && ( !structuralObjectClassText.equals( "" ) ) ) //$NON-NLS-1$
            {
                template.setStructuralObjectClass( structuralObjectClassText );
            }
        }
        else
        {
            LOG.error( "Unable to find any: '" + ELEMENT_STRUCTURAL + "' element." ); //$NON-NLS-1$ //$NON-NLS-2$
            throw new TemplateIOException( THE_FILE_DOES_NOT_SEEM_TO_BE_A_VALID_TEMPLATE_FILE + "\n" //$NON-NLS-1$
                + NLS.bind( Messages.getString( "TemplateIO.UnableToFindAnyElement" ), ELEMENT_STRUCTURAL ) ); //$NON-NLS-1$
        }

        // Reading the 'auxiliaries' element
        Element auxliariesElement = objectClassesElement.element( ELEMENT_AUXILIARIES );
        if ( auxliariesElement != null )
        {
            // Reading the auxiliaries object classes
            for ( Iterator<?> i = auxliariesElement.elementIterator( ELEMENT_AUXILIARY ); i.hasNext(); )
            {
                Element auxliaryObjectClassElement = ( Element ) i.next();
                String auxliaryObjectClassText = auxliaryObjectClassElement.getText();
                if ( ( auxliaryObjectClassText != null ) && ( !auxliaryObjectClassText.equals( "" ) ) ) //$NON-NLS-1$
                {
                    template.addAuxiliaryObjectClass( auxliaryObjectClassText );
                }
            }
        }
    }


    /**
     * Reads the form for the template.
     *
     * @param element
     *      the element
     * @param template
     *      the template
     * @throws TemplateIOException
     */
    private static void readForm( Element element, Template template ) throws TemplateIOException
    {
        LOG.debug( "Reading the template's form" ); //$NON-NLS-1$

        // Reading the 'form' element
        Element formElement = element.element( ELEMENT_FORM );
        if ( formElement == null )
        {
            LOG.error( "Unable to find element: '" + ELEMENT_FORM + "'." ); //$NON-NLS-1$//$NON-NLS-2$
            throw new TemplateIOException( THE_FILE_DOES_NOT_SEEM_TO_BE_A_VALID_TEMPLATE_FILE + "\n" //$NON-NLS-1$
                + NLS.bind( Messages.getString( "TemplateIO.UnableToFindElement" ), ELEMENT_FORM ) ); //$NON-NLS-1$
        }

        // Creating the form and setting it to the template
        TemplateForm form = new TemplateForm();
        template.setForm( form );

        // Reading the child elements
        for ( Iterator<?> i = formElement.elementIterator(); i.hasNext(); )
        {
            Element childElement = ( Element ) i.next();

            // Getting the name of the element
            String elementName = childElement.getName();
            if ( elementName.equalsIgnoreCase( ELEMENT_COMPOSITE ) )
            {
                readComposite( childElement, form );
            }
            else if ( elementName.equalsIgnoreCase( ELEMENT_SECTION ) )
            {
                readSection( childElement, form );
            }
            else
            {
                throw new TemplateIOException( THE_FILE_DOES_NOT_SEEM_TO_BE_A_VALID_TEMPLATE_FILE + "\n" //$NON-NLS-1$
                    + NLS.bind( Messages.getString( "TemplateIO.ElementNotAllowedAtThisLevel" ), //$NON-NLS-1$
                        new String[]
                            { elementName, ELEMENT_SECTION, ELEMENT_COMPOSITE } ) );
            }

        }

        // Verifying if we've found at least one section
        if ( form.getChildren().size() == 0 )
        {
            throw new TemplateIOException( THE_FILE_DOES_NOT_SEEM_TO_BE_A_VALID_TEMPLATE_FILE + "\n" //$NON-NLS-1$
                + NLS.bind( Messages.getString( "TemplateIO.UnableToFindAnyXOrYElement" ), new String[] //$NON-NLS-1$
                    { ELEMENT_SECTION, ELEMENT_COMPOSITE } ) );
        }
    }


    /**
     * Reads a widget.
     *
     * @param element
     *      the element
     * @param template
     *      the template
     * @throws TemplateIOException
     */
    private static void readWidget( Element element, TemplateWidget parent ) throws TemplateIOException
    {
        // Getting the name of the element
        String elementName = element.getName();

        // Switching on the various widgets we support
        if ( elementName.equalsIgnoreCase( ELEMENT_CHECKBOX ) )
        {
            readCheckbox( element, parent );
        }
        else if ( elementName.equalsIgnoreCase( ELEMENT_COMPOSITE ) )
        {
            readComposite( element, parent );
        }
        else if ( elementName.equalsIgnoreCase( ELEMENT_DATE ) )
        {
            readDate( element, parent );
        }
        else if ( elementName.equalsIgnoreCase( ELEMENT_FILECHOOSER ) )
        {
            readFileChooser( element, parent );
        }
        else if ( elementName.equalsIgnoreCase( ELEMENT_IMAGE ) )
        {
            readImage( element, parent );
        }
        else if ( elementName.equalsIgnoreCase( ELEMENT_LABEL ) )
        {
            readLabel( element, parent );
        }
        else if ( elementName.equalsIgnoreCase( ELEMENT_LINK ) )
        {
            readLink( element, parent );
        }
        else if ( elementName.equalsIgnoreCase( ELEMENT_LISTBOX ) )
        {
            readListbox( element, parent );
        }
        else if ( elementName.equalsIgnoreCase( ELEMENT_PASSWORD ) )
        {
            readPassword( element, parent );
        }
        else if ( elementName.equalsIgnoreCase( ELEMENT_RADIOBUTTONS ) )
        {
            readRadioButtons( element, parent );
        }
        else if ( elementName.equalsIgnoreCase( ELEMENT_SECTION ) )
        {
            readSection( element, parent );
        }
        else if ( elementName.equalsIgnoreCase( ELEMENT_SPINNER ) )
        {
            readSpinner( element, parent );
        }
        else if ( elementName.equalsIgnoreCase( ELEMENT_TABLE ) )
        {
            readTable( element, parent );
        }
        else if ( elementName.equalsIgnoreCase( ELEMENT_TEXTFIELD ) )
        {
            readTextfield( element, parent );
        }
        // We could not find a widget associated with this name.
        else
        {
            throw new TemplateIOException( THE_FILE_DOES_NOT_SEEM_TO_BE_A_VALID_TEMPLATE_FILE + "\n" //$NON-NLS-1$
                + NLS.bind( Messages.getString( "TemplateIO.UnknownWidget" ), elementName ) ); //$NON-NLS-1$
        }
    }


    /**
     * Reads the widget common properties like 'attributeType' and all position related properties.
     *
     * @param element
     *      the element to read from
     * @param widget
     *      the associated widget
     * @param throwExceptionIfMissingAttributeType
     *      <code>true</code> if the method should throw an exception if the 'attributeType' value is missing,
     *      <code>false</code> if not
     * @param widgetElementName
     *      the name of the XML element of the widget (for debug purposes)
     * @throws TemplateIOException
     *      if the mandatory 'attributeType' value is missing
     */
    private static void readWidgetCommonProperties( Element element, TemplateWidget widget,
        boolean throwExceptionIfMissingAttributeType, String widgetElementName ) throws TemplateIOException
    {
        // Reading the 'attributeType' attribute
        boolean foundAttributeTypeAttribute = readAttributeTypeAttribute( element, widget );
        // If the 'attributeType' attribute does not exist, we throw an 
        // exception
        if ( throwExceptionIfMissingAttributeType && !foundAttributeTypeAttribute )
        {
            throw new TemplateIOException( THE_FILE_DOES_NOT_SEEM_TO_BE_A_VALID_TEMPLATE_FILE + "\n" //$NON-NLS-1$
                + NLS.bind( Messages.getString( "TemplateIO.UnableToFindMandatoryAttribute" ), new String[] //$NON-NLS-1$
                    { ATTRIBUTE_ATTRIBUTETYPE, widgetElementName } ) );
        }

        // Reading the 'horizontalAlignment' attribute
        Attribute horizontalAlignmentAttribute = element.attribute( ATTRIBUTE_HORIZONTAL_ALIGNMENT );
        if ( ( horizontalAlignmentAttribute != null ) && ( horizontalAlignmentAttribute.getText() != null ) )
        {
            widget.setHorizontalAlignment( readWidgetAlignmentValue( horizontalAlignmentAttribute.getText() ) );
        }

        // Reading the 'verticalAlignment' attribute
        Attribute verticalAlignmentAttribute = element.attribute( ATTRIBUTE_VERTICAL_ALIGNMENT );
        if ( ( verticalAlignmentAttribute != null ) && ( verticalAlignmentAttribute.getText() != null ) )
        {
            widget.setVerticalAlignment( readWidgetAlignmentValue( verticalAlignmentAttribute.getText() ) );
        }

        // Reading the 'grabExcessHorizontalSpace' attribute
        Attribute grabExcessHorizontalSpaceAttribute = element.attribute( ATTRIBUTE_GRAB_EXCESS_HORIZONTAL_SPACE );
        if ( ( grabExcessHorizontalSpaceAttribute != null ) && ( grabExcessHorizontalSpaceAttribute.getText() != null ) )
        {
            widget.setGrabExcessHorizontalSpace( readBoolean( grabExcessHorizontalSpaceAttribute.getText() ) );
        }

        // Reading the 'grabExcessVerticalSpace' attribute
        Attribute grabExcessVerticalSpaceAttribute = element.attribute( ATTRIBUTE_GRAB_EXCESS_VERTICAL_SPACE );
        if ( ( grabExcessVerticalSpaceAttribute != null ) && ( grabExcessVerticalSpaceAttribute.getText() != null ) )
        {
            widget.setGrabExcessVerticalSpace( readBoolean( grabExcessVerticalSpaceAttribute.getText() ) );
        }

        // Reading the 'horizontalSpan' attribute
        Attribute horizontalSpanAttribute = element.attribute( ATTRIBUTE_HORIZONTAL_SPAN );
        if ( ( horizontalSpanAttribute != null ) && ( horizontalSpanAttribute.getText() != null ) )
        {
            widget.setHorizontalSpan( readInteger( horizontalSpanAttribute.getText() ) );
        }

        // Reading the 'verticalSpan' attribute
        Attribute verticalSpanAttribute = element.attribute( ATTRIBUTE_VERTICAL_SPAN );
        if ( ( verticalSpanAttribute != null ) && ( verticalSpanAttribute.getText() != null ) )
        {
            widget.setVerticalSpan( readInteger( verticalSpanAttribute.getText() ) );
        }

        // Reading the 'width' attribute
        Attribute widthAttribute = element.attribute( ATTRIBUTE_WIDTH );
        if ( ( widthAttribute != null ) && ( widthAttribute.getText() != null ) )
        {
            widget.setImageWidth( readInteger( widthAttribute.getText() ) );
        }

        // Reading the 'height' attribute
        Attribute heightAttribute = element.attribute( ATTRIBUTE_HEIGHT );
        if ( ( heightAttribute != null ) && ( heightAttribute.getText() != null ) )
        {
            widget.setImageHeight( readInteger( heightAttribute.getText() ) );
        }
    }


    /**
     * Reads a widget alignment value.
     *
     * @param text
     *      the widget alignment value
     */
    private static WidgetAlignment readWidgetAlignmentValue( String text ) throws TemplateIOException
    {
        if ( text.equalsIgnoreCase( VALUE_NONE ) )
        {
            return WidgetAlignment.NONE;
        }
        else if ( text.equalsIgnoreCase( VALUE_BEGINNING ) )
        {
            return WidgetAlignment.BEGINNING;
        }
        else if ( text.equalsIgnoreCase( VALUE_CENTER ) )
        {
            return WidgetAlignment.CENTER;
        }
        else if ( text.equalsIgnoreCase( VALUE_END ) )
        {
            return WidgetAlignment.END;
        }
        else if ( text.equalsIgnoreCase( VALUE_FILL ) )
        {
            return WidgetAlignment.FILL;
        }
        else
        {
            String message = NLS.bind( Messages.getString( "TemplateIO.UnableToConvertStringToWidgetAlignmentValue" ), //$NON-NLS-1$
                new String[]
                    { VALUE_NONE, VALUE_BEGINNING, VALUE_CENTER, VALUE_END, VALUE_FILL, text } );
            LOG.error( message );
            throw new TemplateIOException( THE_FILE_DOES_NOT_SEEM_TO_BE_A_VALID_TEMPLATE_FILE + "\n" + message ); //$NON-NLS-1$
        }
    }


    /**
     * Reads the attribute type attribute.
     *
     * @param element
     *      the element to read from
     * @param widget
     *      the associated widget
     * @return
     *      <code>true</code> if the attribute type attribute has been found,
     *      <code>false</code> if not
     */
    private static boolean readAttributeTypeAttribute( Element element, TemplateWidget widget )
    {
        // Reading the 'attributeType' attribute
        Attribute attributeTypeAttribute = element.attribute( ATTRIBUTE_ATTRIBUTETYPE );
        if ( ( attributeTypeAttribute != null ) && ( attributeTypeAttribute.getText() != null ) )
        {
            widget.setAttributeType( attributeTypeAttribute.getText() );
            return true;
        }

        return false;
    }


    /**
     * Reads a checkbox widget.
     *
     * @param element
     *      the element
     * @param template
     *      the template
     * @throws TemplateIOException
     */
    private static void readCheckbox( Element element, TemplateWidget parent ) throws TemplateIOException
    {
        LOG.debug( "Reading a template checkbox" ); //$NON-NLS-1$

        // Creating the checkbox
        TemplateCheckbox templateCheckbox = new TemplateCheckbox( parent );

        // Reading the widget's common properties
        readWidgetCommonProperties( element, templateCheckbox, true, ELEMENT_CHECKBOX );

        // Reading the 'label' attribute
        Attribute labelAttribute = element.attribute( ATTRIBUTE_LABEL );
        if ( ( labelAttribute != null ) && ( labelAttribute.getText() != null ) )
        {
            templateCheckbox.setLabel( labelAttribute.getText() );
        }

        // Reading the 'enabled' attribute
        Attribute enabledAttribute = element.attribute( ATTRIBUTE_ENABLED );
        if ( ( enabledAttribute != null ) && ( enabledAttribute.getText() != null ) )
        {
            templateCheckbox.setEnabled( readBoolean( enabledAttribute.getText() ) );
        }

        // Reading the 'checkedValue' element
        Element checkedValueElement = element.element( ELEMENT_CHECKEDVALUE );
        if ( checkedValueElement != null )
        {
            templateCheckbox.setCheckedValue( checkedValueElement.getText() );
        }

        // Reading the 'uncheckedValue' element
        Element uncheckedValueElement = element.element( ELEMENT_UNCHECKEDVALUE );
        if ( uncheckedValueElement != null )
        {
            templateCheckbox.setUncheckedValue( uncheckedValueElement.getText() );
        }
    }


    /**
     * Reads a composite widget.
     *
     * @param element
     *      the element
     * @param template
     *      the template
     * @throws TemplateIOException
     */
    private static void readComposite( Element element, TemplateWidget parent ) throws TemplateIOException
    {
        LOG.debug( "Reading a template composite" ); //$NON-NLS-1$

        // Creating the composite
        TemplateComposite templateComposite = new TemplateComposite( parent );

        // Reading the widget's common properties
        readWidgetCommonProperties( element, templateComposite, false, ELEMENT_COMPOSITE );

        // Reading the 'numberOfColumns' attribute
        Attribute numberOfColumnsAttribute = element.attribute( ATTRIBUTE_NUMBEROFCOLUMNS );
        if ( ( numberOfColumnsAttribute != null ) && ( numberOfColumnsAttribute.getText() != null ) )
        {
