/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Xerces" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation and was
 * originally based on software copyright (c) 2001, International
 * Business Machines, Inc., http://www.apache.org.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package org.apache.xerces.impl.xs.traversers;

import java.util.Vector;

import org.apache.xerces.impl.dv.InvalidDatatypeFacetException;
import org.apache.xerces.impl.dv.SchemaDVFactory;
import org.apache.xerces.impl.dv.XSSimpleType;
import org.apache.xerces.impl.dv.xs.SchemaDVFactoryImpl;
import org.apache.xerces.impl.xs.SchemaGrammar;
import org.apache.xerces.impl.xs.SchemaSymbols;
import org.apache.xerces.impl.xs.XSAnnotationImpl;
import org.apache.xerces.xs.XSConstants;
import org.apache.xerces.xs.XSObjectList;
import org.apache.xerces.xs.XSTypeDefinition;
import org.apache.xerces.impl.xs.util.XSObjectListImpl;
import org.apache.xerces.impl.xs.util.XInt;
import org.apache.xerces.util.DOMUtil;
import org.apache.xerces.xni.QName;
import org.w3c.dom.Element;

/**
 * The simple type definition schema component traverser.
 *
 * <simpleType
 *   final = (#all | (list | union | restriction))
 *   id = ID
 *   name = NCName
 *   {any attributes with non-schema namespace . . .}>
 *   Content: (annotation?, (restriction | list | union))
 * </simpleType>
 *
 * <restriction
 *   base = QName
 *   id = ID
 *   {any attributes with non-schema namespace . . .}>
 *   Content: (annotation?, (simpleType?, (minExclusive | minInclusive | maxExclusive | maxInclusive | totalDigits | fractionDigits | length | minLength | maxLength | enumeration | whiteSpace | pattern)*))
 * </restriction>
 *
 * <list
 *   id = ID
 *   itemType = QName
 *   {any attributes with non-schema namespace . . .}>
 *   Content: (annotation?, (simpleType?))
 * </list>
 *
 * <union
 *   id = ID
 *   memberTypes = List of QName
 *   {any attributes with non-schema namespace . . .}>
 *   Content: (annotation?, (simpleType*))
 * </union>
 *
 * @author Elena Litani, IBM
 * @author Neeraj Bajaj, Sun Microsystems, Inc.
 * @author Sandy Gao, IBM
 * 
 * @version $Id: XSDSimpleTypeTraverser.java,v 1.26 2003/11/11 20:15:00 sandygao Exp $
 */
class XSDSimpleTypeTraverser extends XSDAbstractTraverser {

    // the factory used to query/create simple types
    private final SchemaDVFactory schemaFactory = SchemaDVFactory.getInstance();

    // whether the type being parsed is a S4S built-in type.
    private boolean fIsBuiltIn = false;

    XSDSimpleTypeTraverser (XSDHandler handler,
                            XSAttributeChecker gAttrCheck) {
        super(handler, gAttrCheck);
        if (schemaFactory instanceof SchemaDVFactoryImpl) {
            ((SchemaDVFactoryImpl)schemaFactory).setDeclPool(handler.fDeclPool);
        }
    }

    //return qualified name of simpleType or empty string if error occured
    XSSimpleType traverseGlobal(Element elmNode,
                                XSDocumentInfo schemaDoc,
                                SchemaGrammar grammar) {

        // General Attribute Checking
        Object[] attrValues = fAttrChecker.checkAttributes(elmNode, true, schemaDoc);
        String nameAtt = (String)attrValues[XSAttributeChecker.ATTIDX_NAME];
        XSSimpleType type = traverseSimpleTypeDecl(elmNode, attrValues, schemaDoc, grammar);
        fAttrChecker.returnAttrArray(attrValues, schemaDoc);

        // if it's a global type without a name, return null
        if (nameAtt == null) {
            reportSchemaError("s4s-att-must-appear", new Object[]{SchemaSymbols.ELT_SIMPLETYPE, SchemaSymbols.ATT_NAME}, elmNode);
            type = null;
        }

        // don't add global components without name to the grammar
        if (type != null) {
            grammar.addGlobalTypeDecl(type);
        }

        return type;
    }

    XSSimpleType traverseLocal(Element elmNode,
                               XSDocumentInfo schemaDoc,
                               SchemaGrammar grammar) {

        // General Attribute Checking
        Object[] attrValues = fAttrChecker.checkAttributes(elmNode, false, schemaDoc);
        XSSimpleType type = traverseSimpleTypeDecl (elmNode, attrValues, schemaDoc, grammar);
        fAttrChecker.returnAttrArray(attrValues, schemaDoc);

        return type;
    }

    private XSSimpleType traverseSimpleTypeDecl(Element simpleTypeDecl,
                                                Object[] attrValues,
                                                XSDocumentInfo schemaDoc,
                                                SchemaGrammar grammar) {

        // get name and final values
        String name = (String)attrValues[XSAttributeChecker.ATTIDX_NAME];
        XInt finalAttr = (XInt)attrValues[XSAttributeChecker.ATTIDX_FINAL];
        int finalProperty = finalAttr == null ? schemaDoc.fFinalDefault : finalAttr.intValue();

        // annotation?,(list|restriction|union)
        Element child = DOMUtil.getFirstChildElement(simpleTypeDecl);
        XSAnnotationImpl [] annotations = null;
        if (child != null) {
            // traverse annotation if any
            if (DOMUtil.getLocalName(child).equals(SchemaSymbols.ELT_ANNOTATION)) {
                XSAnnotationImpl annotation = traverseAnnotationDecl(child, attrValues, false, schemaDoc);
                if (annotation != null)
                    annotations = new XSAnnotationImpl [] {annotation};
                child = DOMUtil.getNextSiblingElement(child);
            }
        }

        // (list|restriction|union)
        if (child == null) {
            reportSchemaError("s4s-elt-must-match.2", new Object[]{SchemaSymbols.ELT_SIMPLETYPE, "(annotation?, (restriction | list | union))"}, simpleTypeDecl);
            return errorType(name, schemaDoc.fTargetNamespace, XSConstants.DERIVATION_RESTRICTION);
        }

        // derivation type: restriction/list/union
        String varietyProperty = DOMUtil.getLocalName(child);
        short refType = XSConstants.DERIVATION_RESTRICTION;
        boolean restriction = false, list = false, union = false;
        if (varietyProperty.equals(SchemaSymbols.ELT_RESTRICTION)) {
            refType = XSConstants.DERIVATION_RESTRICTION;
            restriction = true;
        }
        else if (varietyProperty.equals(SchemaSymbols.ELT_LIST)) {
            refType = XSConstants.DERIVATION_LIST;
            list = true;
        }
        else if (varietyProperty.equals(SchemaSymbols.ELT_UNION)) {
            refType = XSConstants.DERIVATION_UNION;
            union = true;
        }
        else {
            reportSchemaError("s4s-elt-must-match.1", new Object[]{SchemaSymbols.ELT_SIMPLETYPE, "(annotation?, (restriction | list | union))", varietyProperty}, simpleTypeDecl);
            return errorType(name, schemaDoc.fTargetNamespace, XSConstants.DERIVATION_RESTRICTION);
        }

        // nothing should follow this element
        Element nextChild = DOMUtil.getNextSiblingElement(child);
        if (nextChild != null) {
            reportSchemaError("s4s-elt-must-match.1", new Object[]{SchemaSymbols.ELT_SIMPLETYPE, "(annotation?, (restriction | list | union))", DOMUtil.getLocalName(nextChild)}, nextChild);
        }
        
        // General Attribute Checking: get base/item/member types
        Object[] contentAttrs = fAttrChecker.checkAttributes(child, false, schemaDoc);
        QName baseTypeName = (QName)contentAttrs[restriction ?
                                                 XSAttributeChecker.ATTIDX_BASE :
                                                 XSAttributeChecker.ATTIDX_ITEMTYPE];
        Vector memberTypes = (Vector)contentAttrs[XSAttributeChecker.ATTIDX_MEMBERTYPES];

        //content = {annotation?,simpleType?...}
        Element content = DOMUtil.getFirstChildElement(child);

        //check content (annotation?, ...)
        if (content != null) {
            // traverse annotation if any
            if (DOMUtil.getLocalName(content).equals(SchemaSymbols.ELT_ANNOTATION)) {
                XSAnnotationImpl annotation = traverseAnnotationDecl(content, contentAttrs, false, schemaDoc);
                if(annotation != null ) {
                    if(annotations == null) 
                        annotations = new XSAnnotationImpl [] {annotation};
                    else {
                        XSAnnotationImpl [] tempArray = new XSAnnotationImpl[2];
                        tempArray[0] = annotations[0];
                        annotations = tempArray;
                        annotations[1] = annotation;
                    }
                }
                content = DOMUtil.getNextSiblingElement(content);
            }
        }

        // get base type from "base" attribute
        XSSimpleType baseValidator = null;
        if ((restriction || list) && baseTypeName != null) {
            baseValidator = findDTValidator(child, name, baseTypeName, refType, schemaDoc);
            // if its the built-in type, return null from here
            if (baseValidator == null && fIsBuiltIn) {
                fIsBuiltIn = false;
                return null;
            }
        }

        // get types from "memberTypes" attribute
        Vector dTValidators = null;
        XSSimpleType dv = null;
        XSObjectList dvs;
        if (union && memberTypes != null && memberTypes.size() > 0) {
            int size = memberTypes.size();
            dTValidators = new Vector(size, 2);
            // for each qname in the list
            for (int i = 0; i < size; i++) {
                // get the type decl
                dv = findDTValidator(child, name, (QName)memberTypes.elementAt(i),
                                     XSConstants.DERIVATION_UNION, schemaDoc);
                if (dv != null) {
                    // if it's a union, expand it
                    if (dv.getVariety() == XSSimpleType.VARIETY_UNION) {
                        dvs = dv.getMemberTypes();
                        for (int j = 0; j < dvs.getLength(); j++)
                            dTValidators.addElement(dvs.item(j));
                    } else {
                        dTValidators.addElement(dv);
                    }
                }
            }
        }

        // when there is an error finding the base type of a restriction
        // we use anySimpleType as the base, then we should skip the facets,
        // because anySimpleType doesn't recognize any facet.
        boolean skipFacets = false;
        
        // check if there is a child "simpleType"
        if (content != null && DOMUtil.getLocalName(content).equals(SchemaSymbols.ELT_SIMPLETYPE)) {
            if (restriction || list) {
                // it's an error for both "base" and "simpleType" to appear
                if (baseTypeName != null) {
                    reportSchemaError(list ? "src-simple-type.3.a" : "src-simple-type.2.a", null, content);
                }
                else {
                    // traver this child to get the base type
                    baseValidator = traverseLocal(content, schemaDoc, grammar);
                }
                // get the next element
                content = DOMUtil.getNextSiblingElement(content);
            }
            else if (union) {
                if (dTValidators == null) {
                    dTValidators = new Vector(2, 2);
                }
                do {
                    // traver this child to get the member type
                    dv = traverseLocal(content, schemaDoc, grammar);
                    if (dv != null) {
                        // if it's a union, expand it
                        if (dv.getVariety() == XSSimpleType.VARIETY_UNION) {
                            dvs = dv.getMemberTypes();
                            for (int j = 0; j < dvs.getLength(); j++)
                                dTValidators.addElement(dvs.item(j));
                        } else {
                            dTValidators.addElement(dv);
                        }
                    }
                    // get the next element
                    content = DOMUtil.getNextSiblingElement(content);
                } while (content != null && DOMUtil.getLocalName(content).equals(SchemaSymbols.ELT_SIMPLETYPE));
            }
        }
        else if ((restriction || list) && baseTypeName == null) {
            // it's an error if neither "base" nor "simpleType" appears
			reportSchemaError(list ? "src-simple-type.3.b" : "src-simple-type.2.b", null, child);
            // base can't be found, skip the facets.
            skipFacets = true;
            baseValidator = SchemaGrammar.fAnySimpleType;
        }
        else if (union && (memberTypes == null || memberTypes.size() == 0)) {
            // it's an error if "memberTypes" is empty and no "simpleType" appears
            reportSchemaError("src-union-memberTypes-or-simpleTypes", null, child);
            dTValidators = new Vector(1);
            dTValidators.addElement(SchemaGrammar.fAnySimpleType);
        }

        // error finding "base" or error traversing "simpleType".
        // don't need to report an error, since some error has been reported.
        if ((restriction || list) && baseValidator == null) {
            baseValidator = SchemaGrammar.fAnySimpleType;
        }
        // error finding "memberTypes" or error traversing "simpleType".
        // don't need to report an error, since some error has been reported.
        if (union && (dTValidators == null || dTValidators.size() == 0)) {
            dTValidators = new Vector(1);
            dTValidators.addElement(SchemaGrammar.fAnySimpleType);
        }

        // item type of list types can't have list content
		if (list && isListDatatype(baseValidator)) {
			reportSchemaError("cos-st-restricts.2.1", new Object[]{name, baseValidator.getName()}, child);
		}
        
        // create the simple type based on the "base" type
        XSSimpleType newDecl = null;
        if (restriction) {
            newDecl = schemaFactory.createTypeRestriction(name, schemaDoc.fTargetNamespace, (short)finalProperty, baseValidator, 
                annotations == null? null : new XSObjectListImpl(annotations, annotations.length));
        }
        else if (list) {
            newDecl = schemaFactory.createTypeList(name, schemaDoc.fTargetNamespace, (short)finalProperty, baseValidator,
                annotations == null? null : new XSObjectListImpl(annotations, annotations.length));
        }
        else if (union) {
            XSSimpleType[] memberDecls = new XSSimpleType[dTValidators.size()];
            for (int i = 0; i < dTValidators.size(); i++)
                memberDecls[i] = (XSSimpleType)dTValidators.elementAt(i);
            newDecl = schemaFactory.createTypeUnion(name, schemaDoc.fTargetNamespace, (short)finalProperty, memberDecls,
                annotations == null? null : new XSObjectListImpl(annotations, annotations.length));
        }

        // now traverse facets, if it's derived by restriction
        if (restriction && content != null) {
            FacetInfo fi = traverseFacets(content, baseValidator, schemaDoc);
            content = fi.nodeAfterFacets;

            if (!skipFacets) {
                try {
                    fValidationState.setNamespaceSupport(schemaDoc.fNamespaceSupport);
                    newDecl.applyFacets(fi.facetdata, fi.fPresentFacets, fi.fFixedFacets, fValidationState);
                } catch (InvalidDatatypeFacetException ex) {
                    reportSchemaError(ex.getKey(), ex.getArgs(), child);
                }
            }
        }

        // now element should appear after this point
        if (content != null) {
            if (restriction) {
                reportSchemaError("s4s-elt-must-match.1", new Object[]{SchemaSymbols.ELT_RESTRICTION, "(annotation?, (simpleType?, (minExclusive | minInclusive | maxExclusive | maxInclusive | totalDigits | fractionDigits | length | minLength | maxLength | enumeration | whiteSpace | pattern)*))", DOMUtil.getLocalName(content)}, content);
            }
            else if (list) {
                reportSchemaError("s4s-elt-must-match.1", new Object[]{SchemaSymbols.ELT_LIST, "(annotation?, (simpleType?))", DOMUtil.getLocalName(content)}, content);
            }
            else if (union) {
                reportSchemaError("s4s-elt-must-match.1", new Object[]{SchemaSymbols.ELT_UNION, "(annotation?, (simpleType*))", DOMUtil.getLocalName(content)}, content);
            }
        }
        
        fAttrChecker.returnAttrArray(contentAttrs, schemaDoc);

        // return the new type
        return newDecl;
    }

    //@param: elm - top element
    //@param: baseTypeStr - type (base/itemType/memberTypes)
    //@param: baseRefContext:  whether the caller is using this type as a base for restriction, union or list
    //return XSSimpleType available for the baseTypeStr, null if not found or disallowed.
    // also throws an error if the base type won't allow itself to be used in this context.
    // REVISIT: can this code be re-used?
    private XSSimpleType findDTValidator(Element elm, String refName,
                                         QName baseTypeStr, short baseRefContext,
                                         XSDocumentInfo schemaDoc) {
        if (baseTypeStr == null)
            return null;

        XSTypeDefinition baseType = (XSTypeDefinition)fSchemaHandler.getGlobalDecl(schemaDoc, XSDHandler.TYPEDECL_TYPE, baseTypeStr, elm);
        if (baseType != null) {
            // if it's a complex type, or if its restriction of anySimpleType
            if (baseType.getTypeCategory() != XSTypeDefinition.SIMPLE_TYPE ||
                baseType == SchemaGrammar.fAnySimpleType &&
                baseRefContext == XSConstants.DERIVATION_RESTRICTION) {
                // if the base type is anySimpleType and the current type is
                // a S4S built-in type, return null. (not an error).
                if (baseType == SchemaGrammar.fAnySimpleType &&
                    checkBuiltIn(refName, schemaDoc.fTargetNamespace)) {
                    return null;
                }
                reportSchemaError("cos-st-restricts.1.1", new Object[]{baseTypeStr.rawname, refName}, elm);
                return SchemaGrammar.fAnySimpleType;
            }
            if ((baseType.getFinal() & baseRefContext) != 0) {
				if (baseRefContext == XSConstants.DERIVATION_RESTRICTION) {
                    reportSchemaError("st-props-correct.3", new Object[]{refName, baseTypeStr.rawname}, elm);
                }
                else if (baseRefContext == XSConstants.DERIVATION_LIST) {
                    reportSchemaError("cos-st-restricts.2.3.1.1", new Object[]{baseTypeStr.rawname, refName}, elm);
                }
                else if (baseRefContext == XSConstants.DERIVATION_UNION) {
                    reportSchemaError("cos-st-restricts.3.3.1.1", new Object[]{baseTypeStr.rawname, refName}, elm);
                }
            }
        }

        return (XSSimpleType)baseType;
    }

    // check whethe the type denoted by the name and namespace is a S4S
    // built-in type. update fIsBuiltIn at the same time.
    private final boolean checkBuiltIn(String name, String namespace) {
        if (namespace != SchemaSymbols.URI_SCHEMAFORSCHEMA)
            return false;
        if (SchemaGrammar.SG_SchemaNS.getGlobalTypeDecl(name) != null)
            fIsBuiltIn = true;
        return fIsBuiltIn;
    }

    // find if a datatype validator is a list or has list datatype member.
    private boolean isListDatatype(XSSimpleType validator) {
        if (validator.getVariety() == XSSimpleType.VARIETY_LIST)
            return true;

        if (validator.getVariety() == XSSimpleType.VARIETY_UNION) {
            XSObjectList temp = validator.getMemberTypes();
            for (int i = 0; i < temp.getLength(); i++) {
                if (((XSSimpleType)temp.item(i)).getVariety() == XSSimpleType.VARIETY_LIST) {
                    return true;
                }
            }
        }

        return false;
    }//isListDatatype(XSSimpleTypeDecl):boolean

    private XSSimpleType errorType(String name, String namespace, short refType) {
        switch (refType) {
        case XSConstants.DERIVATION_RESTRICTION:
            return schemaFactory.createTypeRestriction(name, namespace, (short)0,
                                                       SchemaGrammar.fAnySimpleType, null);
        case XSConstants.DERIVATION_LIST:
            return schemaFactory.createTypeList(name, namespace, (short)0,
                                                SchemaGrammar.fAnySimpleType, null);
        case XSConstants.DERIVATION_UNION:
            return schemaFactory.createTypeUnion(name, namespace, (short)0,
                                                 new XSSimpleType[]{SchemaGrammar.fAnySimpleType}, null);
        }
        
        return null;
    }
    
}//class XSDSimpleTypeTraverser
