/*
 * $Header: /home/cvspublic/jakarta-slide/proposals/tamino/src/store/org/apache/slide/store/tamino/datastore/schema/XSchema.java,v 1.3 2004/07/30 06:51:56 ozeigermann Exp $
 * $Revision: 1.3 $
 * $Date: 2004/07/30 06:51:56 $
 *
 * ====================================================================
 *
 * Copyright 1999-2004 The Apache Software Foundation.
 *
 * Licensed 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.slide.store.tamino.datastore.schema;

// import list
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.slide.content.NodeProperty.NamespaceCache;
import org.apache.slide.store.tamino.common.XGlobals;
import org.jdom.Element;
import org.jdom.Namespace;

/**
 * An XSchema abstracts from TSD2, TSD3 and TSD4 schema.
 *
 * @author martin.wallmer@softwareag.com
 *
 * @version $Revision: 1.3 $ *
 */
public class XSchema {

    /**
     ** The namespace with the {@link XGlobals#TAMINO_NAMESPACE_PREFIX TAMINO_NAMESPACE_PREFIX}
     ** and {@link XGlobals#TAMINO_NAMESPACE_URI TAMINO_NAMESPACE_URI}.
     **/
    public static final Namespace TAMINO_NAMESPACE = NamespaceCache.getNamespace(XGlobals.TAMINO_NAMESPACE_PREFIX,
                                                                                 XGlobals.TAMINO_NAMESPACE_URI);

    /**
     ** The namespace with the {@link XGlobals#TAMINO_TSD_2_NAMESPACE_PREFIX TAMINO_TSD_2_NAMESPACE_PREFIX}
     ** and {@link XGlobals#TAMINO_TSD_2_NAMESPACE_URI TAMINO_TSD_2_NAMESPACE_URI}.
     **/
    public static final Namespace TAMINO_TSD_2_NAMESPACE = NamespaceCache.getNamespace(XGlobals.TAMINO_TSD_2_NAMESPACE_PREFIX,
                                                                                       XGlobals.TAMINO_TSD_2_NAMESPACE_URI);

    /**
     ** The namespace with the {@link XGlobals#TAMINO_TSD_3_NAMESPACE_PREFIX TAMINO_TSD_3_NAMESPACE_PREFIX}
     ** and {@link XGlobals#TAMINO_TSD_3_NAMESPACE_URI TAMINO_TSD_3_NAMESPACE_URI}.
     **/
    public static final Namespace TAMINO_TSD_3_NAMESPACE = NamespaceCache.getNamespace(XGlobals.TAMINO_TSD_3_NAMESPACE_PREFIX,
                                                                                       XGlobals.TAMINO_TSD_3_NAMESPACE_URI);

    /**
     ** The namespace with the {@link XGlobals#XML_SCHEMA_NAMESPACE_PREFIX XML_SCHEMA_NAMESPACE_PREFIX}
     ** and {@link XGlobals#XML_SCHEMA_NAMESPACE_URI XML_SCHEMA_NAMESPACE_URI}.
     **/
    public static final Namespace XML_SCHEMA_NAMESPACE = NamespaceCache.getNamespace(XGlobals.XML_SCHEMA_NAMESPACE_PREFIX,
                                                                                     XGlobals.XML_SCHEMA_NAMESPACE_URI);

    /**
     ** The message of the XBadSchemaException thrown by {@link #newInstance newInstance()}
     ** if the parameter <code>schemaRootElement</code> is null.
     **/
    public static final String SCHEMA_ROOT_ELEMENT_MUST_NOT_BE_NULL = "The parameter 'schemaRootElement' must not be <null>";

    /**
     ** The message of the XBadSchemaException thrown by {@link #newInstance newInstance()}
     ** if an expected element was missing.
     **/
    public static final String EXPECTED_ELEMENT_MISSING_PREFIX = "Expected element ";

    /**
     ** The message of the XBadSchemaException if the version is not supported.
     **/
    public static final String VERSION_NOT_SUPPORTED_PREFIX = "Version not supported: ";


    //--

    private final XSchemaType type;
    
    /**
     ** The Set containing the XDoctypes of this schema.
     **/
    private Set doctypeSet = null;
    
    private String targetNamespaceUri = null;
    
    /**
     ** Creates an XSchema from the given (TSD 3 or 4) schema root element.
     **
     ** @param      tsdSchemaRootElement   the schema document to create
     **                                    the XSchema from.
     **
     ** @throws     XBadSchemaException  if the schema is not a valid TSD 3 schema.
     **/
    protected XSchema(XSchemaType type, Element tsdSchemaRootElement) throws XBadSchemaException {
        this.type = type;
        parseSchema(tsdSchemaRootElement);
    }

    public XSchemaType getType() {
        return type;
    }
    
    /**
     ** Parses the the schema document to create the XSchema from.
     **
     ** @pre        true
     ** @post       true
     **
     ** @param      tsdSchemaRootElement  the schema document to create the XSchema from.
     **
     ** @throws     XBadSchemaException  if the schema is not a valid TSD schema.
     **/
    private void parseSchema(Element tsdSchemaRootElement) throws XBadSchemaException {
        if (tsdSchemaRootElement == null) {
            throw new XBadSchemaException(SCHEMA_ROOT_ELEMENT_MUST_NOT_BE_NULL);
        }
        targetNamespaceUri = tsdSchemaRootElement.getAttributeValue (XGlobals.TARGET_NAMESPACE);
        if ( ! (XGlobals.SCHEMA.equals(tsdSchemaRootElement.getName()) &&
                    XGlobals.XML_SCHEMA_NAMESPACE_URI.equals(tsdSchemaRootElement.getNamespaceURI())) ) {
            throw new XBadSchemaException(EXPECTED_ELEMENT_MISSING_PREFIX + XGlobals.SCHEMA);
        }
        
        Element annotationElement = tsdSchemaRootElement.getChild (XGlobals.TAMINO_ANNOTATION, XML_SCHEMA_NAMESPACE);
        if (annotationElement == null) {
            throw new XBadSchemaException(EXPECTED_ELEMENT_MISSING_PREFIX + XGlobals.TAMINO_ANNOTATION);
        }
        Element appinfoElement = annotationElement.getChild (XGlobals.TAMINO_APPINFO, XML_SCHEMA_NAMESPACE);
        if (appinfoElement == null) {
            throw new XBadSchemaException(EXPECTED_ELEMENT_MISSING_PREFIX + XGlobals.TAMINO_APPINFO);
        }
        Element schemaInfoElement = appinfoElement.getChild (XGlobals.TAMINO_SCHEMA_INFO, TAMINO_TSD_3_NAMESPACE);
        if (schemaInfoElement == null) {
            throw new XBadSchemaException(EXPECTED_ELEMENT_MISSING_PREFIX + XGlobals.TAMINO_SCHEMA_INFO);
        }
        
        List doctypeElementList = schemaInfoElement.getChildren(XGlobals.TAMINO_DOCTYPE, TAMINO_TSD_3_NAMESPACE);
        doctypeSet = new HashSet(doctypeElementList.size());
        Iterator iterator = doctypeElementList.iterator();
        while (iterator.hasNext()) {
            Element doctypeElement = (Element)iterator.next();
            XDoctype docType = createXDoctype(doctypeElement);
            doctypeSet.add (docType);
        }
    }

    /**
     ** Creates an XDoctype for the given <code>&lt;doctype&gt;</code> element.
     ** Called by {@link #parseSchema parseSchema()}.
     **
     ** @pre        true
     ** @post       true
     **
     ** @param      doctypeElement  the <code>&lt;doctype&gt;</code> element.
     **
     ** @return     the XDoctype for the given <code>&lt;doctype&gt;</code> element.
     **
     ** @throws     XBadSchemaException  if the schema is bad.
     **/
    protected XDoctype createXDoctype(Element doctypeElement) throws XBadSchemaException {
        String prefixedName = doctypeElement.getAttributeValue (XGlobals.TAMINO_NAME);
        XDoctype docType = new XDoctype (prefixedName, targetNamespaceUri, false);
        return docType;
    }
    
    
    
    /**
     * Method getDoctypes
     *
     * @return   a Set of XDoctype objects that are contained in this schema
     */
    public Set getDoctypes() {
        return doctypeSet;
    }

    /**
     * factory method to create schemas. <code>schemaRootElement</code> may contain
     * TSD2, TSD3 or TSD4. Dependant of this a concrete XSchema is instantiated.
     *
     * @param    schemaRootElement      a  Document
     *
     * @return   a XSchema
     *
     * @throws   XBadSchemaException  if parsing the schema document failed for any reason.
     */
    public static XSchema newInstance(Element schemaRootElement) throws XBadSchemaException {
        if (schemaRootElement == null) {
            throw new XBadSchemaException(SCHEMA_ROOT_ELEMENT_MUST_NOT_BE_NULL);
        }

        return new XSchema(getSchemaTypeForTSD3AndHigher(schemaRootElement), schemaRootElement);
    }


    /**
     ** Determines the XSchemaType for the schema given by <code>schemaRootElement</code>.
     **
     ** @pre        true
     ** @post       true
     **
     ** @param      schemaRootElement  the root element of the schema document.
     **
     ** @return     the appropriate XSchemaType for the give schema.
     **
     ** @throws     XBadSchemaException  if the schema type could not be determined
     **                                  for any reason.
     **/
    private static XSchemaType getSchemaTypeForTSD3AndHigher(Element schemaRootElement) throws XBadSchemaException {

        if (schemaRootElement == null) {
            throw new XBadSchemaException(SCHEMA_ROOT_ELEMENT_MUST_NOT_BE_NULL);
        }
        if ( ! (XGlobals.SCHEMA.equals(schemaRootElement.getName()) &&
                    XGlobals.XML_SCHEMA_NAMESPACE_URI.equals(schemaRootElement.getNamespaceURI())) ) {
            throw new XBadSchemaException(EXPECTED_ELEMENT_MISSING_PREFIX + XGlobals.SCHEMA);
        }

        Element annotationElement = schemaRootElement.getChild (XGlobals.TAMINO_ANNOTATION, XML_SCHEMA_NAMESPACE);
        if (annotationElement == null) {
            throw new XBadSchemaException(EXPECTED_ELEMENT_MISSING_PREFIX + XGlobals.TAMINO_ANNOTATION);
        }
        Element appinfoElement = annotationElement.getChild (XGlobals.TAMINO_APPINFO, XML_SCHEMA_NAMESPACE);
        if (appinfoElement == null) {
            throw new XBadSchemaException(EXPECTED_ELEMENT_MISSING_PREFIX + XGlobals.TAMINO_APPINFO);
        }
        Element schemaInfoElement = appinfoElement.getChild (XGlobals.TAMINO_SCHEMA_INFO, TAMINO_TSD_3_NAMESPACE);
        if (schemaInfoElement == null) {
            throw new XBadSchemaException(EXPECTED_ELEMENT_MISSING_PREFIX + XGlobals.TAMINO_SCHEMA_INFO);
        }
        Element adminInfoElement = schemaInfoElement.getChild (XGlobals.TAMINO_ADMIN_INFO, TAMINO_TSD_3_NAMESPACE);
        if (adminInfoElement == null) {
            throw new XBadSchemaException(EXPECTED_ELEMENT_MISSING_PREFIX + XGlobals.TAMINO_ADMIN_INFO);
        }
        Element versionElement = adminInfoElement.getChild (XGlobals.TAMINO_VERSION, TAMINO_TSD_3_NAMESPACE);
        if (versionElement == null) {
            throw new XBadSchemaException(EXPECTED_ELEMENT_MISSING_PREFIX + XGlobals.TAMINO_VERSION);
        }

        XSchemaType schemaType = XSchemaType.lookup(versionElement.getText());
        if (schemaType == null) {
            throw new XBadSchemaException(VERSION_NOT_SUPPORTED_PREFIX + versionElement.getText());
        }

        return schemaType;
    }
}


