/*   Copyright 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.xmlbeans.impl.schema;

import org.apache.xmlbeans.impl.values.*;
import org.apache.xmlbeans.impl.regex.RegularExpression;
import org.apache.xmlbeans.impl.common.QNameHelper;
import org.apache.xmlbeans.impl.config.ExtensionHolder;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.SchemaGlobalElement;
import org.apache.xmlbeans.SchemaComponent;
import org.apache.xmlbeans.SchemaField;
import org.apache.xmlbeans.SchemaType;
import org.apache.xmlbeans.SchemaParticle;
import org.apache.xmlbeans.SchemaGlobalAttribute;
import org.apache.xmlbeans.SchemaAttributeModel;
import org.apache.xmlbeans.SchemaTypeElementSequencer;
import org.apache.xmlbeans.SchemaTypeSystem;
import org.apache.xmlbeans.SchemaProperty;
import org.apache.xmlbeans.QNameSet;
import org.apache.xmlbeans.SchemaLocalAttribute;
import org.apache.xmlbeans.SchemaLocalElement;
import org.apache.xmlbeans.XmlAnySimpleType;
import org.apache.xmlbeans.StringEnumAbstractBase;
import org.apache.xmlbeans.SchemaStringEnumEntry;
import org.apache.xmlbeans.SchemaTypeLoader;

import java.lang.reflect.Constructor;
import java.util.*;
import java.math.BigInteger;

import javax.xml.namespace.QName;

public final class SchemaTypeImpl implements SchemaType, TypeStoreUserFactory
{
    // global types have names
    private QName _name;

    // compilation support
    private int _resolvePhase;
    private static final int UNRESOLVED = 0;
    private static final int RESOLVING_SGS = 1; // For document types only
    private static final int RESOLVED_SGS = 2;  // For document types only
    private static final int RESOLVING = 3;
    private static final int RESOLVED = 4;
    private static final int JAVAIZING = 5;
    private static final int JAVAIZED = 6;

    // anonymous type support
    private SchemaType.Ref _outerSchemaTypeRef;
    private volatile SchemaComponent.Ref _containerFieldRef;
    private volatile SchemaField _containerField;
    private volatile int _containerFieldCode;
    private volatile int _containerFieldIndex;
    private SchemaType.Ref[] _anonymousTyperefs;
    private boolean _isDocumentType;
    private boolean _isAttributeType;
    // private boolean _skippedAnonymousType;

    // compiletime java type support
    private boolean _isCompiled;
    private String _shortJavaName;
    private String _fullJavaName;
    private String _shortJavaImplName;
    private String _fullJavaImplName;
    private ExtensionHolder _extensionHolder;

    // runtime java type support: volatile because they're cached
    private volatile Class _javaClass;
    private volatile Class _javaEnumClass;
    private volatile Class _javaImplClass;
    private volatile Constructor _javaImplConstructor;
    private volatile Constructor _javaImplConstructor2;
    private volatile boolean _implNotAvailable;

    private final Object[] _ctrArgs = new Object[] { this };

    // reflective support
    private SchemaTypeSystem _typeSystem;
    private String _filename;

    // complex content support
    private SchemaParticle _contentModel;
    private volatile SchemaLocalElement[] _localElts; // lazily computed
    private volatile Map _eltToIndexMap; // lazily computed
    private volatile Map _attrToIndexMap; // lazily computed
    private Map _propertyModelByElementName;
    private Map _propertyModelByAttributeName;
    private boolean _hasAllContent;
    private boolean _orderSensitive;
    private QNameSet _typedWildcardElements;
    private QNameSet _typedWildcardAttributes;
    private boolean _hasWildcardElements;
    private boolean _hasWildcardAttributes;
    // set of valid QNames that can be substituted for a property
    private Set _validSubstitutions = Collections.EMPTY_SET;

    // simple content support
    private int _complexTypeVariety;
    private SchemaAttributeModel _attributeModel;

    // simple type support
    private int _builtinTypeCode;

    private int _simpleTypeVariety;
    private boolean _isSimpleType;

    private SchemaType.Ref _baseTyperef; // via restriction or extension
    private int _baseDepth; // how many inheritance steps to AnyType
    private int _derivationType;

    // facets
    private XmlValueRef[] _facetArray;
    private boolean[] _fixedFacetArray;

    // fundamental facets
    private int _ordered;
    private boolean _isFinite;
    private boolean _isBounded;
    private boolean _isNumeric;

    private boolean _abs;
    private boolean _finalExt;
    private boolean _finalRest;
    private boolean _finalList;
    private boolean _finalUnion;
    private boolean _blockExt;
    private boolean _blockRest;

    // whitespace facet
    private int _whiteSpaceRule;

    // regex patterns
    private boolean _hasPatterns; // also takes into account base classes
    private org.apache.xmlbeans.impl.regex.RegularExpression[] _patterns;

    // enumerated values
    private XmlValueRef[] _enumerationValues;
    private SchemaType.Ref _baseEnumTyperef;
    private boolean _stringEnumEnsured;
    private volatile Map _lookupStringEnum;
    private volatile List _listOfStringEnum;
    private volatile Map _lookupStringEnumEntry;
    private SchemaStringEnumEntry[] _stringEnumEntries;

    // for lists only
    private SchemaType.Ref _listItemTyperef;

    // for unions only
    private boolean _isUnionOfLists;
    private SchemaType.Ref[] _unionMemberTyperefs;
    private int _anonymousUnionMemberOrdinal;
    private volatile SchemaType[] _unionConstituentTypes;
    private volatile SchemaType[] _unionSubTypes;
    private volatile SchemaType _unionCommonBaseType;

    // for atomic types only
    private SchemaType.Ref _primitiveTypeRef;

    // for decimal restrictions only
    private int _decimalSize;

    // lazy loading support
    private volatile boolean _unloaded;

    // for document types only - only valid during compilation
    private QName _sg;
    private List _sgMembers = new ArrayList();

    public boolean isUnloaded()
    {
        return _unloaded;
    }

    public void finishLoading()
    {
        _unloaded = false;
    }



    SchemaTypeImpl(SchemaTypeSystem typeSystem)
    {
        _typeSystem = typeSystem;
    }

    SchemaTypeImpl(SchemaTypeSystem typeSystem, boolean unloaded)
    {
        _typeSystem = typeSystem;
        _unloaded = unloaded;
        if (unloaded)
            finishQuick();
    }

    public boolean isSGResolved()
        { return _resolvePhase >= RESOLVED_SGS; }

    public boolean isSGResolving()
        { return _resolvePhase >= RESOLVING_SGS; }

    public boolean isResolved()
        { return _resolvePhase >= RESOLVED; }

    public boolean isResolving()
        { return _resolvePhase == RESOLVING; }

    public boolean isUnjavaized()
        { return _resolvePhase < JAVAIZED; }

    public boolean isJavaized()
        { return _resolvePhase == JAVAIZED; }

    public void startResolvingSGs()
    {
        if (_resolvePhase != UNRESOLVED)
            throw new IllegalStateException();
        _resolvePhase = RESOLVING_SGS;
    }

    public void finishResolvingSGs()
    {
        if (_resolvePhase != RESOLVING_SGS)
            throw new IllegalStateException();
        _resolvePhase = RESOLVED_SGS;
    }

    public void startResolving()
    {
        if ( (_isDocumentType && _resolvePhase != RESOLVED_SGS) ||
             (!_isDocumentType && _resolvePhase != UNRESOLVED))
            throw new IllegalStateException();
        _resolvePhase = RESOLVING;
    }

    public void finishResolving()
    {
        if (_resolvePhase != RESOLVING)
            throw new IllegalStateException();
        _resolvePhase = RESOLVED;
    }

    public void startJavaizing()
    {
        if (_resolvePhase != RESOLVED)
            throw new IllegalStateException();
        _resolvePhase = JAVAIZING;
    }

    public void finishJavaizing()
    {
        if (_resolvePhase != JAVAIZING)
            throw new IllegalStateException();
        _resolvePhase = JAVAIZED;
    }

    private void finishQuick()
    {
        _resolvePhase = JAVAIZED;
    }

    private void assertUnresolved()
    {
        if (_resolvePhase != UNRESOLVED && !_unloaded)
            throw new IllegalStateException();
    }

    private void assertSGResolving()
    {
        if (_resolvePhase != RESOLVING_SGS && !_unloaded)
            throw new IllegalStateException();
    }

    private void assertSGResolved()
    {
        if (_resolvePhase != RESOLVED_SGS && !_unloaded)
            throw new IllegalStateException();
    }

    private void assertResolving()
    {
        if (_resolvePhase != RESOLVING && !_unloaded)
            throw new IllegalStateException();
    }

    private void assertResolved()
    {
        if (_resolvePhase != RESOLVED && !_unloaded)
            throw new IllegalStateException();
    }

    private void assertJavaizing()
    {
        if (_resolvePhase != JAVAIZING && !_unloaded)
            throw new IllegalStateException();
    }

    public QName getName()
        { return _name; }

    public void setName(QName name)
        { assertUnresolved(); _name = name; }

    public String getSourceName()
    {
        if (_filename != null)
            return _filename;
        if (getOuterType() != null)
            return getOuterType().getSourceName();

        SchemaField field = getContainerField();
        if (field != null)
        {
            if (field instanceof SchemaGlobalElement)
                return ((SchemaGlobalElement)field).getSourceName();
            if (field instanceof SchemaGlobalAttribute)
                return ((SchemaGlobalAttribute)field).getSourceName();
        }
        return null;
    }

    public void setFilename(String filename)
        { assertUnresolved(); _filename = filename; }

    public int getComponentType()
        { return SchemaComponent.TYPE; }

    public boolean isAnonymousType()
        { return _name == null; }

    public boolean isDocumentType()
        { return _isDocumentType; }

    public boolean isAttributeType()
        { return _isAttributeType; }

    public QName getDocumentElementName()
    {
        if (_isDocumentType)
        {
            SchemaParticle sp = getContentModel();
            if (sp != null)
                return sp.getName();
        }

        return null;
    }

    public QName getAttributeTypeAttributeName()
    {
        if (_isAttributeType)
        {
            SchemaAttributeModel sam = getAttributeModel();
            if (sam != null)
            {
                SchemaLocalAttribute[] slaArray = sam.getAttributes();
                if (slaArray != null && slaArray.length > 0)
                {
                    SchemaLocalAttribute sla = slaArray[0];
                    return sla.getName();
                }
            }
        }

        return null;
    }

    public void setDocumentType(boolean isDocument)
        { assertUnresolved(); _isDocumentType = isDocument; }

    public void setAttributeType(boolean isAttribute)
        { assertUnresolved(); _isAttributeType = isAttribute; }

    public int getContentType()
        { return _complexTypeVariety; }

    public void setComplexTypeVariety(int complexTypeVariety)
        { assertResolving(); _complexTypeVariety = complexTypeVariety; }

    public SchemaTypeElementSequencer getElementSequencer()
    {
        if (_complexTypeVariety == NOT_COMPLEX_TYPE)
            return new SequencerImpl(null);

        return new SequencerImpl(new SchemaTypeVisitorImpl(_contentModel));
    }

    /** Set the abstract and final flags for a complex type */
    void setAbstractFinal(
        boolean abs, boolean finalExt, boolean finalRest, boolean finalList, boolean finalUnion)
    {
        assertResolving();
        _abs = abs;
        _finalExt = finalExt; _finalRest = finalRest;
        _finalList = finalList; _finalUnion = finalUnion;
    }

    /** Set the final flags for a simple type */
    void setSimpleFinal(boolean finalRest, boolean finalList, boolean finalUnion)
    {
        assertResolving(); _finalRest = finalRest; _finalList = finalList ; _finalUnion = finalUnion;
    }

    void setBlock(boolean blockExt, boolean blockRest)
    {
        assertResolving(); _blockExt = blockExt ; _blockRest = blockRest;
    }

    public boolean blockRestriction()
    { return _blockRest; }

    public boolean blockExtension()
    { return _blockExt; }

    public boolean isAbstract()
    { return _abs; }

    public boolean finalExtension()
    { return _finalExt; }

    public boolean finalRestriction()
    { return _finalRest; }

    public boolean finalList()
    { return _finalList; }

    public boolean finalUnion()
    { return _finalUnion; }

    public synchronized SchemaField getContainerField()
    {
        if (_containerFieldCode != -1)
        {
            SchemaType outer = getOuterType();
            if (_containerFieldCode == 0)
                _containerField = _containerFieldRef == null ? null : (SchemaField)_containerFieldRef.getComponent();
            else if (_containerFieldCode == 1)
                _containerField = outer.getAttributeModel().getAttributes()[_containerFieldIndex];
            else
                _containerField = ((SchemaTypeImpl)outer).getLocalElementByIndex(_containerFieldIndex);
            _containerFieldCode = -1;
        }
        return _containerField;
    }

    public void setContainerField(SchemaField field)
    {
        assertUnresolved();
        _containerField = field;
        _containerFieldCode = -1;
    }

    public void setContainerFieldRef(SchemaComponent.Ref ref)
    {
        assertUnresolved();
        _containerFieldRef = ref;
        _containerFieldCode = 0;
    }

    public void setContainerFieldIndex(short code, int index)
    {
        assertUnresolved();
        _containerFieldCode = code;
        _containerFieldIndex = index;
    }

    public SchemaType getOuterType()
        { return _outerSchemaTypeRef == null ? null : _outerSchemaTypeRef.get(); }

    public void setOuterSchemaTypeRef(SchemaType.Ref typeref)
        { assertUnresolved(); _outerSchemaTypeRef = typeref; }

    public boolean isCompiled()
        { return _isCompiled; }

    public void setCompiled(boolean f)
        { assertJavaizing(); _isCompiled = f; }

    public boolean isSkippedAnonymousType()
        { return _outerSchemaTypeRef.get().getBaseType() == this; }

    public String getShortJavaName()
        { return _shortJavaName; }

    public void setShortJavaName(String name)
    {
        assertResolved();
        _shortJavaName = name;
        SchemaType outer = _outerSchemaTypeRef.get();
        while (outer.getFullJavaName() == null)
            outer = outer.getOuterType();

        _fullJavaName = outer.getFullJavaName() + "$" + _shortJavaName;
    }

    public String getFullJavaName()
        { return _fullJavaName; }

    public void setFullJavaName(String name)
    {
        assertResolved();
        _fullJavaName = name;
        int index = Math.max(_fullJavaName.lastIndexOf('$'),
                             _fullJavaName.lastIndexOf('.')) + 1;
        _shortJavaName = _fullJavaName.substring(index);
    }

    public void setShortJavaImplName(String name)
    {
        assertResolved();
        _shortJavaImplName = name;
        SchemaType outer = _outerSchemaTypeRef.get();
        while (outer.getFullJavaImplName() == null)
            outer = outer.getOuterType();

        _fullJavaImplName = outer.getFullJavaImplName() + "$" + _shortJavaImplName;
    }

    public void setFullJavaImplName(String name)
    {
        assertResolved();
        _fullJavaImplName = name;
        int index = Math.max(_fullJavaImplName.lastIndexOf('$'),
                             _fullJavaImplName.lastIndexOf('.')) + 1;
        _shortJavaImplName = _fullJavaImplName.substring(index);
    }

    public void setExtensionHolder(ExtensionHolder extHolder)
    {
        assertResolved();
        _extensionHolder = extHolder;
    }

    public ExtensionHolder getExtensionHolder()
    {
        return _extensionHolder;
    }

    public String getFullJavaImplName() { return _fullJavaImplName;}
    public String getShortJavaImplName() { return _shortJavaImplName;}

    public SchemaTypeSystem getTypeSystem()
        { return _typeSystem; }

    public SchemaParticle getContentModel()
        { return _contentModel; }

    private static void buildEltList(List eltList, SchemaParticle contentModel)
    {
        if (contentModel == null)
            return;

        switch (contentModel.getParticleType())
        {
            case SchemaParticle.ELEMENT:
                eltList.add(contentModel);
                return;
            case SchemaParticle.ALL:
            case SchemaParticle.CHOICE:
            case SchemaParticle.SEQUENCE:
                for (int i = 0; i < contentModel.countOfParticleChild(); i++)
                    buildEltList(eltList, contentModel.getParticleChild(i));
                return;
            default:
                return;
        }
    }

    private void buildLocalElts()
    {
        List eltList = new ArrayList();
        buildEltList(eltList, _contentModel);
        _localElts = (SchemaLocalElement[])eltList.toArray(new SchemaLocalElement[eltList.size()]);
    }

    public SchemaLocalElement getLocalElementByIndex(int i)
    {
        SchemaLocalElement[] elts = _localElts;
        if (elts == null)
        {
            buildLocalElts();
            elts = _localElts;
        }
        return elts[i];
    }

    public int getIndexForLocalElement(SchemaLocalElement elt)
    {
        Map localEltMap = _eltToIndexMap;
        if (localEltMap == null)
        {
            if (_localElts == null)
                buildLocalElts();
            localEltMap = new HashMap();
            for (int i = 0; i < _localElts.length; i++)
            {
                localEltMap.put(_localElts[i], new Integer(i));
            }
            _eltToIndexMap = localEltMap;
        }
        return ((Integer)localEltMap.get(elt)).intValue();
    }

    public int getIndexForLocalAttribute(SchemaLocalAttribute attr)
    {
        Map localAttrMap = _attrToIndexMap;
        if (localAttrMap == null)
        {
            localAttrMap = new HashMap();
            SchemaLocalAttribute[] attrs = this._attributeModel.getAttributes();
            for (int i = 0; i < attrs.length; i++)
            {
                localAttrMap.put(attrs[i], new Integer(i));
            }
            _attrToIndexMap = localAttrMap;
        }
        return ((Integer)localAttrMap.get(attr)).intValue();
    }

    public SchemaAttributeModel getAttributeModel()
        { return _attributeModel; }

    public SchemaProperty[] getProperties()
    {
        if (_propertyModelByElementName == null)
            return getAttributeProperties();

        if (_propertyModelByAttributeName == null)
            return getElementProperties();

        List list = new ArrayList();
        list.addAll(_propertyModelByElementName.values());
        list.addAll(_propertyModelByAttributeName.values());
        if (list.size()==0)
            return NO_PROPERTIES;
        return (SchemaProperty[])list.toArray(new SchemaProperty[list.size()]);
    }

    private static final SchemaProperty[] NO_PROPERTIES = new SchemaProperty[0];

    public SchemaProperty[] getDerivedProperties()
    {
        SchemaType baseType = getBaseType();
        if (baseType == null)
            return getProperties();

        List results = new ArrayList();

        if (_propertyModelByElementName != null)
            results.addAll(_propertyModelByElementName.values());

        if (_propertyModelByAttributeName != null)
            results.addAll(_propertyModelByAttributeName.values());

        for (Iterator it = results.iterator() ; it.hasNext() ; )
        {
            SchemaProperty prop = (SchemaProperty)it.next();
            SchemaProperty baseProp = prop.isAttribute() ?
                baseType.getAttributeProperty(prop.getName()) :
                baseType.getElementProperty(prop.getName());


            // Remove the derived property from the results if it is
            // A) present in the base type and
            // B) all the details are the same (cardinality, nillability)

            if (baseProp != null)
            {
                if ( eq(prop.getMinOccurs(), baseProp.getMinOccurs()) &&
                     eq(prop.getMaxOccurs(), baseProp.getMaxOccurs()) &&
                     prop.hasNillable() == baseProp.hasNillable())
                {
                    it.remove();
                }

            }

        }

        if (results.size() == 0)
            return NO_PROPERTIES;
        return (SchemaProperty[])results.toArray(new SchemaProperty[results.size()]);
    }

    private static boolean eq(BigInteger a, BigInteger b)
    {
        if (a == null && b == null)
            return true;
        if (a== null || b == null)
            return false;
        return a.equals(b);
    }

    public SchemaProperty[] getElementProperties()
    {
        if (_propertyModelByElementName == null)
            return NO_PROPERTIES;

        return (SchemaProperty[])
                _propertyModelByElementName.values().toArray(new SchemaProperty[_propertyModelByElementName.size()]);
    }

    public SchemaProperty[] getAttributeProperties()
    {
        if (_propertyModelByAttributeName == null)
            return NO_PROPERTIES;

        return (SchemaProperty[])
                _propertyModelByAttributeName.values().toArray(new SchemaProperty[_propertyModelByAttributeName.size()]);
    }

    public SchemaProperty getElementProperty(QName eltName)
        { return _propertyModelByElementName == null ? null : (SchemaProperty)_propertyModelByElementName.get(eltName); }

    public SchemaProperty getAttributeProperty(QName attrName)
        { return _propertyModelByAttributeName == null ? null : (SchemaProperty)_propertyModelByAttributeName.get(attrName); }

    public boolean hasAllContent()
        { return _hasAllContent; }

    public boolean isOrderSensitive()
        { return _orderSensitive; }

    public void setOrderSensitive(boolean sensitive)
        { assertJavaizing(); _orderSensitive = sensitive; }

    public void setContentModel(
            SchemaParticle contentModel,
            SchemaAttributeModel attrModel,
            Map propertyModelByElementName,
            Map propertyModelByAttributeName,
            boolean isAll)
    {
        assertResolving();
        _contentModel = contentModel;
        _attributeModel = attrModel;
        _propertyModelByElementName = propertyModelByElementName;
        _propertyModelByAttributeName = propertyModelByAttributeName;
        _hasAllContent = isAll;


        // Add entries for each element property for substitution group members
        if (_propertyModelByElementName != null)
        {
            _validSubstitutions = new LinkedHashSet();
            Collection eltProps = _propertyModelByElementName.values();
            for (Iterator it = eltProps.iterator() ; it.hasNext() ; )
            {
                SchemaProperty prop = (SchemaProperty)it.next();
                QName[] names = prop.acceptedNames();
                for (int i = 0 ; i < names.length ; i++)
                {
                    if (! _propertyModelByElementName.containsKey(names[i]))
                        _validSubstitutions.add(names[i]);
                }
            }
        }
    }

    private boolean containsElements()
    {
        return getContentType() == ELEMENT_CONTENT ||
               getContentType() == MIXED_CONTENT;
    }

    public boolean hasAttributeWildcards()
    {
        return _hasWildcardAttributes;
    }

    public boolean hasElementWildcards()
    {
        return _hasWildcardElements;
    }

    public boolean isValidSubstitution(QName name)
    {
        return _validSubstitutions.contains(name);
    }

    public SchemaType getElementType(QName eltName, QName xsiType, SchemaTypeLoader wildcardTypeLoader)
    {
        if (isSimpleType() || !containsElements() || isNoType())
            return BuiltinSchemaTypeSystem.ST_NO_TYPE;

        SchemaType type;
        SchemaProperty prop = (SchemaProperty)_propertyModelByElementName.get(eltName);
        if (prop != null)
        {
            type = prop.getType();
        }
        else
        {
            if (!_typedWildcardElements.contains(eltName) || wildcardTypeLoader == null)
                return BuiltinSchemaTypeSystem.ST_NO_TYPE;

            SchemaGlobalElement elt = wildcardTypeLoader.findElement(eltName);
            if (elt == null)
                return BuiltinSchemaTypeSystem.ST_NO_TYPE;
            // According to http://www.w3.org/TR/xmlschema-1/#key-lva,
            // the line above should return ST_ANY_TYPE.
            type = elt.getType();
        }

        if (xsiType != null && wildcardTypeLoader != null)
        {
            SchemaType itype = wildcardTypeLoader.findType(xsiType);

            // NOTE: a previous version of XMLBeans used ST_NO_TYPE if the
            // xsiType was not derived from 'type', but this results in a
            // ClassCastException.  Instead we ignore xsi:type if it's not
            // found or a derived type.
            if (itype != null && type.isAssignableFrom(itype)) {
                return itype;
            }
        }

        return type;
    }

    public SchemaType getAttributeType(QName attrName, SchemaTypeLoader wildcardTypeLoader)
    {
        if (isSimpleType() || isNoType())
            return BuiltinSchemaTypeSystem.ST_NO_TYPE;

        if (isURType())
            return BuiltinSchemaTypeSystem.ST_ANY_SIMPLE;

        SchemaProperty prop = (SchemaProperty)_propertyModelByAttributeName.get(attrName);
        if (prop != null)
            return prop.getType();

        if (!_typedWildcardAttributes.contains(attrName) || wildcardTypeLoader == null)
            return BuiltinSchemaTypeSystem.ST_NO_TYPE;

        SchemaGlobalAttribute attr = wildcardTypeLoader.findAttribute(attrName);
        if (attr == null)
            return BuiltinSchemaTypeSystem.ST_NO_TYPE;
        return attr.getType();
    }

    public XmlObject createElementType(QName eltName, QName xsiType, SchemaTypeLoader wildcardTypeLoader)
    {
        SchemaType type = null;
        SchemaProperty prop = null;
        if (isSimpleType() || !containsElements() || isNoType())
        {
            type = BuiltinSchemaTypeSystem.ST_NO_TYPE;
        }
        else
        {
            prop = (SchemaProperty)_propertyModelByElementName.get(eltName);
            if (prop != null)
            {
                type = prop.getType();
            }
            else if (!_typedWildcardElements.contains(eltName) &&
                     !_validSubstitutions.contains(eltName))
            {
                type = BuiltinSchemaTypeSystem.ST_NO_TYPE;
            }
            else
            {
                SchemaGlobalElement elt = wildcardTypeLoader.findElement(eltName);
                if (elt != null)
                    type = elt.getType();
                else
                    type = BuiltinSchemaTypeSystem.ST_NO_TYPE;
            }

            if (xsiType != null)
            {
                SchemaType itype = wildcardTypeLoader.findType(xsiType);

                // NOTE: a previous version of XMLBeans used ST_NO_TYPE if the
                // xsiType was not derived from 'type', but this results in a
                // ClassCastException.  Instead we ignore xsi:type if it's not
                // found or a derived type.
                if (itype != null && type.isAssignableFrom(itype)) {
                    type = itype;
                }
            }
        }

        if (type != null)
            return ((SchemaTypeImpl)type).createUnattachedNode(prop);
        return null;
    }

    public XmlObject createAttributeType(QName attrName, SchemaTypeLoader wildcardTypeLoader)
    {
        SchemaTypeImpl type = null;
        SchemaProperty prop = null;
        if (isSimpleType() || isNoType())
        {
            type = BuiltinSchemaTypeSystem.ST_NO_TYPE;
        }
        else if (isURType())
        {
            type = BuiltinSchemaTypeSystem.ST_ANY_SIMPLE;
        }
        else
        {
            prop = (SchemaProperty)_propertyModelByAttributeName.get(attrName);
            if (prop != null)
            {
                type = (SchemaTypeImpl)prop.getType();
            }
            else if (!_typedWildcardAttributes.contains(attrName))
            {
                type = BuiltinSchemaTypeSystem.ST_NO_TYPE;
            }
            else
            {
                SchemaGlobalAttribute attr = wildcardTypeLoader.findAttribute(attrName);
                if (attr != null)
                    type = (SchemaTypeImpl)attr.getType();
                else
                    type = BuiltinSchemaTypeSystem.ST_NO_TYPE;
            }
        }

        if (type != null)
            return type.createUnattachedNode(prop);

        return null;
    }

    public void setWildcardSummary(QNameSet elementSet, boolean haswcElt, QNameSet attributeSet, boolean haswcAtt)
    {
        assertResolving(); _typedWildcardElements = elementSet; _hasWildcardElements = haswcElt; _typedWildcardAttributes = attributeSet; _hasWildcardAttributes = haswcAtt;
    }

    public SchemaType[] getAnonymousTypes()
    {
        SchemaType[] result = new SchemaType[_anonymousTyperefs.length];
        for (int i = 0; i < result.length; i++)
            result[i] = _anonymousTyperefs[i].get();
        return result;
    }

    public void setAnonymousTypeRefs(SchemaType.Ref[] anonymousTyperefs)
    {
        _anonymousTyperefs = anonymousTyperefs;
    }


    private static SchemaType[] staCopy(SchemaType[] a)
    {
        if (a == null)
            return null;

        SchemaType[] result = new SchemaType[a.length];
        System.arraycopy(a, 0, result, 0, a.length);
        return result;
    }

    private static boolean[] boaCopy(boolean[] a)
    {
        if (a == null)
            return null;

        boolean[] result = new boolean[a.length];
        System.arraycopy(a, 0, result, 0, a.length);
        return result;
    }



    public void setSimpleTypeVariety(int variety)
        { assertResolving(); _simpleTypeVariety = variety; }

    public int getSimpleVariety()
        { return _simpleTypeVariety; }

    public boolean isURType()
        { return _builtinTypeCode == BTC_ANY_TYPE || _builtinTypeCode == BTC_ANY_SIMPLE; }

    public boolean isNoType()
        { return this == BuiltinSchemaTypeSystem.ST_NO_TYPE; }

    public boolean isSimpleType()
        { return _isSimpleType; }

    public void setSimpleType(boolean f)
        { assertUnresolved(); _isSimpleType = f; }

    public boolean isUnionOfLists()
        { return _isUnionOfLists; }

    public void setUnionOfLists(boolean f)
        { assertResolving(); _isUnionOfLists = f; }

    public SchemaType getPrimitiveType()
        { return _primitiveTypeRef == null ? null : _primitiveTypeRef.get(); }

    public void setPrimitiveTypeRef(SchemaType.Ref typeref)
        { assertResolving(); _primitiveTypeRef = typeref; }

    public int getDecimalSize()
        { return _decimalSize; }

    public void setDecimalSize(int bits)
        { assertResolving(); _decimalSize = bits; }

    public SchemaType getBaseType()
        { return _baseTyperef == null ? null : _baseTyperef.get(); }

    public void setBaseTypeRef(SchemaType.Ref typeref)
        { assertResolving(); _baseTyperef = typeref; }

    public int getBaseDepth()
        { return _baseDepth; }

    public void setBaseDepth(int depth)
        { assertResolving(); _baseDepth = depth; }

    public int getDerivationType()
        { return _derivationType; }

    public void setDerivationType(int type)
        { assertResolving(); _derivationType = type; }

    public SchemaType getListItemType()
        { return _listItemTyperef == null ? null : _listItemTyperef.get(); }

    public void setListItemTypeRef(SchemaType.Ref typeref)
        { assertResolving(); _listItemTyperef = typeref; }

    public SchemaType[] getUnionMemberTypes()
    {
        SchemaType[] result = new SchemaType[_unionMemberTyperefs ==null ? 0 : _unionMemberTyperefs.length];
        for (int i = 0; i < result.length; i++)
            result[i] = _unionMemberTyperefs[i].get();
        return result;
    }

    public void setUnionMemberTypeRefs(SchemaType.Ref[] typerefs)
        { assertResolving(); _unionMemberTyperefs = typerefs; }

    public int getAnonymousUnionMemberOrdinal()
        { return _anonymousUnionMemberOrdinal; }

    public void setAnonymousUnionMemberOrdinal(int i)
        { assertUnresolved(); _anonymousUnionMemberOrdinal = i; }

    public synchronized SchemaType[] getUnionConstituentTypes()
    {
        if (_unionCommonBaseType == null)
            computeFlatUnionModel();
        return staCopy(_unionConstituentTypes);
    }

    private void setUnionConstituentTypes(SchemaType[] types)
        { _unionConstituentTypes = types; }

    public synchronized SchemaType[] getUnionSubTypes()
    {
        if (_unionCommonBaseType == null)
            computeFlatUnionModel();
        return staCopy(_unionSubTypes);
    }

    private void setUnionSubTypes(SchemaType[] types)
        { _unionSubTypes = types; }

    public synchronized SchemaType getUnionCommonBaseType()
    {
        if (_unionCommonBaseType == null)
            computeFlatUnionModel();
        return _unionCommonBaseType;
    }

    private void setUnionCommonBaseType(SchemaType type)
        { _unionCommonBaseType = type; }

    private void computeFlatUnionModel()
    {
        Set constituentMemberTypes = new LinkedHashSet();
        Set allSubTypes = new LinkedHashSet();
        SchemaType commonBaseType = null;

        allSubTypes.add(this);

        for (int i = 0; i < _unionMemberTyperefs.length; i++)
        {
            SchemaTypeImpl mImpl = (SchemaTypeImpl)_unionMemberTyperefs[i].get();

            switch (mImpl.getSimpleVariety())
            {
                case SchemaType.LIST:
                    constituentMemberTypes.add(mImpl);
                    allSubTypes.add(mImpl);
                    commonBaseType = mImpl.getCommonBaseType(commonBaseType);
                    break;
                case SchemaType.UNION:
                    constituentMemberTypes.addAll(Arrays.asList(mImpl.getUnionConstituentTypes()));
                    allSubTypes.addAll(Arrays.asList(mImpl.getUnionSubTypes()));
                    SchemaType otherCommonBaseType = mImpl.getUnionCommonBaseType();
                    if (otherCommonBaseType != null)
                        commonBaseType = otherCommonBaseType.getCommonBaseType(commonBaseType);
                    break;
                case SchemaType.ATOMIC:
                    constituentMemberTypes.add(mImpl);
                    allSubTypes.add(mImpl);
                    commonBaseType = mImpl.getCommonBaseType(commonBaseType);
                    break;
                default:
                    assert(false);
            }
        }

        setUnionConstituentTypes((SchemaType[])
                constituentMemberTypes.toArray(StscState.EMPTY_ST_ARRAY));
        setUnionSubTypes((SchemaType[])
                allSubTypes.toArray(StscState.EMPTY_ST_ARRAY));
        setUnionCommonBaseType(commonBaseType);
    }

    public QName getSubstitutionGroup()
        { return _sg; }

    public void setSubstitutionGroup(QName sg)
        { assertSGResolving(); _sg = sg; }

    public void addSubstitutionGroupMember(QName member)
        { assertSGResolved(); _sgMembers.add(member); }

    public QName[] getSubstitutionGroupMembers()
    {
        QName[] result = new QName[_sgMembers.size()];
        return (QName[])_sgMembers.toArray(result);
    }

    public int getWhiteSpaceRule()
        { return _whiteSpaceRule; }

    public void setWhiteSpaceRule(int ws)
        { assertResolving(); _whiteSpaceRule = ws; }

    public XmlAnySimpleType getFacet(int facetCode)
    {
        if (_facetArray == null)
            return null;
        XmlValueRef ref = _facetArray[facetCode];
        if (ref == null)
            return null;
        return ref.get();
    }

    public boolean isFacetFixed(int facetCode)
    {
        return _fixedFacetArray[facetCode];
    }

    public XmlAnySimpleType[] getBasicFacets()
    {
        XmlAnySimpleType[] result = new XmlAnySimpleType[SchemaType.LAST_FACET + 1];
        for (int i = 0; i <= SchemaType.LAST_FACET; i++)
        {
            result[i] = getFacet(i);
        }
        return result;
    }

    public boolean[] getFixedFacets()
    {
        return boaCopy(_fixedFacetArray);
    }

    public void setBasicFacets(XmlValueRef[] values, boolean[] fixed)
    {
        assertResolving();
        _facetArray = values;
        _fixedFacetArray = fixed;
    }

    public int ordered()
        { return _ordered; }

    public void setOrdered(int ordering)
        { assertResolving(); _ordered = ordering; }

    public boolean isBounded()
        { return _isBounded; }

    public void setBounded(boolean f)
        { assertResolving(); _isBounded = f; }

    public boolean isFinite()
        { return _isFinite; }

    public void setFinite(boolean f)
        { assertResolving(); _isFinite = f; }

    public boolean isNumeric()
        { return _isNumeric; }

    public void setNumeric(boolean f)
        { assertResolving(); _isNumeric = f; }


    public boolean hasPatternFacet()
        { return _hasPatterns; }

    public void setPatternFacet(boolean hasPatterns)
        { assertResolving(); _hasPatterns = hasPatterns; }

    public boolean matchPatternFacet(String s)
    {
        if (!_hasPatterns)
            return true;

        if (_patterns != null && _patterns.length > 0)
        {
            int i;
            for (i = 0; i < _patterns.length; i++)
            {
                if (_patterns[i].matches(s))
                    break;
            }
            if (i >= _patterns.length)
                return false;
        }

        return getBaseType().matchPatternFacet(s);
    }

    public String[] getPatterns()
    {
        if (_patterns == null)
            return new String[0];
        String[] patterns = new String[_patterns.length];
        for (int i=0; i< _patterns.length; i++)
            patterns[i] = _patterns[i].getPattern();
        return patterns;
    }

    public org.apache.xmlbeans.impl.regex.RegularExpression[] getPatternExpressions()
    {
        if (_patterns == null)
            return new org.apache.xmlbeans.impl.regex.RegularExpression[0];
        org.apache.xmlbeans.impl.regex.RegularExpression[] result = new org.apache.xmlbeans.impl.regex.RegularExpression[_patterns.length];
        System.arraycopy(_patterns, 0, result, 0, _patterns.length);
        return result;
    }

    public void setPatterns(org.apache.xmlbeans.impl.regex.RegularExpression[] list)
        { assertResolving(); _patterns = list; }

    public XmlAnySimpleType[] getEnumerationValues()
    {
        if (_enumerationValues == null)
            return null;
        XmlAnySimpleType[] result = new XmlAnySimpleType[_enumerationValues.length];
        for (int i = 0; i < result.length; i++)
        {
            XmlValueRef ref = _enumerationValues[i];
            result[i] = (ref == null ? null : ref.get());

        }
        return result;
    }

    public void setEnumerationValues(XmlValueRef[] a)
        { assertResolving(); _enumerationValues = a; }

    public StringEnumAbstractBase enumForString(String s)
    {
        ensureStringEnumInfo();
        if (_lookupStringEnum == null)
            return null;
        return (StringEnumAbstractBase)_lookupStringEnum.get(s);
    }

    public StringEnumAbstractBase enumForInt(int i)
    {
        ensureStringEnumInfo();
        if (_listOfStringEnum == null || i < 0 || i >= _listOfStringEnum.size())
            return null;
        return (StringEnumAbstractBase)_listOfStringEnum.get(i);
    }

    public SchemaStringEnumEntry enumEntryForString(String s)
    {
        ensureStringEnumInfo();
        if (_lookupStringEnumEntry == null)
            return null;
        return (SchemaStringEnumEntry)_lookupStringEnumEntry.get(s);
    }

    public SchemaType getBaseEnumType()
    {
        return _baseEnumTyperef == null ? null : _baseEnumTyperef.get();
    }

    public void setBaseEnumTypeRef(SchemaType.Ref baseEnumTyperef)
    {
        _baseEnumTyperef = baseEnumTyperef;
    }

    public SchemaStringEnumEntry[] getStringEnumEntries()
    {
        if (_stringEnumEntries == null)
            return null;
        SchemaStringEnumEntry[] result = new SchemaStringEnumEntry[_stringEnumEntries.length];
        System.arraycopy(_stringEnumEntries, 0, result, 0, result.length);
        return result;
    }

    public void setStringEnumEntries(SchemaStringEnumEntry sEnums[])
    {
        assertJavaizing();
        _stringEnumEntries = sEnums;
    }

    private void ensureStringEnumInfo()
    {
        if (_stringEnumEnsured)
            return;

        SchemaStringEnumEntry[] sEnums = _stringEnumEntries;
        if (sEnums == null)
        {
            _stringEnumEnsured = true;
            return;
        }

        Map lookupStringEnum = new HashMap(sEnums.length);
        List listOfStringEnum = new ArrayList(sEnums.length + 1);
        Map lookupStringEnumEntry = new HashMap(sEnums.length);

        for (int i = 0; i < sEnums.length; i++)
        {
            lookupStringEnumEntry.put(sEnums[i].getString(), sEnums[i]);
        }

        Class jc = _baseEnumTyperef.get().getEnumJavaClass();
        if (jc != null)
        {
            try
            {
                StringEnumAbstractBase.Table table = (StringEnumAbstractBase.Table)jc.getField("table").get(null);
                for (int i = 0; i < sEnums.length; i++)
                {
                    int j = sEnums[i].getIntValue();
                    StringEnumAbstractBase enumVal = table.forInt(j);
                    lookupStringEnum.put(sEnums[i].getString(), enumVal);
                    while (listOfStringEnum.size() <= j)
                        listOfStringEnum.add(null);
                    listOfStringEnum.set(j, enumVal);
                }
            }
            catch (Exception e)
            {
                System.err.println("Something wrong: could not locate enum table for " + jc);
                jc = null;
                lookupStringEnum.clear();
                listOfStringEnum.clear();
            }
        }

        if (jc == null)
        {
            for (int i = 0; i < sEnums.length; i++)
            {
                int j = sEnums[i].getIntValue();
                String s = sEnums[i].getString();
                StringEnumAbstractBase enumVal = new StringEnumValue(s, j);
                lookupStringEnum.put(s, enumVal);
                while (listOfStringEnum.size() <= j)
                    listOfStringEnum.add(null);
                listOfStringEnum.set(j, enumVal);
            }
        }

        synchronized (this)
        {
            if (!_stringEnumEnsured)
            {
                _lookupStringEnum = lookupStringEnum;
                _listOfStringEnum = listOfStringEnum;
                _lookupStringEnumEntry = lookupStringEnumEntry;
            }
        }
        // HACKHACK: two syncrhonized blocks force a memory barrier:
        // BUGBUG: this behavior is likely to change in future VMs
        synchronized (this)
        {
            _stringEnumEnsured = true;
        }
    }

    public boolean hasStringEnumValues()
    {
        return _stringEnumEntries != null;
    }

    public void copyEnumerationValues(SchemaTypeImpl baseImpl)
    {
        assertResolving();
        _enumerationValues = baseImpl._enumerationValues;
        _baseEnumTyperef = baseImpl._baseEnumTyperef;
    }

    public int getBuiltinTypeCode()
        { return _builtinTypeCode; } // special: set up pre-init

    public void setBuiltinTypeCode(int builtinTypeCode)
        { assertResolving(); _builtinTypeCode = builtinTypeCode; }

    public Class getJavaClass()
    {
        // This field is declared volatile and Class is immutable so this is allowed.
        if (_javaClass == null && getFullJavaName() != null)
        {
            try
                { _javaClass = Class.forName(getFullJavaName(), false, _typeSystem.getClassLoader()); }
            catch (ClassNotFoundException e)
            {
                System.err.println("Could not find class name " + getFullJavaName());
                System.err.println("Searched in classloader " + _typeSystem.getClassLoader());
                e.printStackTrace(System.err);
                _javaClass = null;
            }
        }

        return _javaClass;
    }

    public Class getJavaImplClass() {
        if (_implNotAvailable)
            return null;

        if (_javaImplClass == null)
        {
            try {
                if (getFullJavaImplName() != null)
                    _javaImplClass = Class.forName(getFullJavaImplName(), false, _typeSystem.getClassLoader());
                else
                    _implNotAvailable = true;
            }
            catch (ClassNotFoundException e) {
                _implNotAvailable = true;
            }
        }

        return _javaImplClass;
    }

    public Constructor getJavaImplConstructor()
    {
        if (_javaImplConstructor == null && !_implNotAvailable)
        {
            final Class impl = getJavaImplClass();
            if (impl == null) return null;
            try
            {
                _javaImplConstructor = impl.getConstructor(new Class[] { SchemaType.class });
            }
            catch (NoSuchMethodException e)
            {
                e.printStackTrace();
            }
        }

        return _javaImplConstructor;

    }

    public Constructor getJavaImplConstructor2()
    {
        if (_javaImplConstructor2 == null && !_implNotAvailable)
        {
            final Class impl = getJavaImplClass();
            if (impl == null) return null;
            try
            {
                _javaImplConstructor2 = impl.getConstructor(new Class[] { SchemaType.class, boolean.class });
            }
            catch (NoSuchMethodException e)
            {
                e.printStackTrace();
            }
        }

        return _javaImplConstructor2;

    }

    public Class getEnumJavaClass()
    {
        // This field is declared volatile and Class is immutable so this is allowed.
        if (_javaEnumClass == null)
        {
            try
                { _javaEnumClass = Class.forName(getBaseEnumType().getFullJavaName() + "$Enum", false, _typeSystem.getClassLoader()); }
            catch (ClassNotFoundException e)
                { _javaEnumClass = null; }
        }

        return _javaEnumClass;
    }

    public void setJavaClass(Class javaClass)
    {
        assertResolved();
        _javaClass = javaClass;
        setFullJavaName(javaClass.getName());
    }

    public boolean isPrimitiveType()
    {
        return (getBuiltinTypeCode() >= BTC_FIRST_PRIMITIVE &&
                getBuiltinTypeCode() <= BTC_LAST_PRIMITIVE);
    }

    public boolean isBuiltinType()
    {
        return getBuiltinTypeCode() != 0;
    }

    public XmlObject createUnwrappedNode()
    {
        // Todo: attach a new xml store!
        XmlObject result = createUnattachedNode(null);
        return result;
    }

    /**
     * TypeStoreUserFactory implementation
     */
    public TypeStoreUser createTypeStoreUser()
    {
        return (TypeStoreUser)createUnattachedNode(null);
    }


    public XmlAnySimpleType newValidatingValue(Object obj)
    {
        return newValue(obj, true);
    }
    /**
     * Creates an immutable simple value.
     */
    public XmlAnySimpleType newValue(Object obj)
    {
        return newValue(obj, false);
    }

    public XmlAnySimpleType newValue(Object obj, boolean validateOnSet)
    {
        if (!isSimpleType() && getContentType() != SchemaType.SIMPLE_CONTENT)
            throw new XmlValueOutOfRangeException(); // values must be simple

        XmlObjectBase result = (XmlObjectBase)createUnattachedNode(null);
        if (validateOnSet)
            result.setValidateOnSet();

        // In the case of tree copy, need to call a specla setter to avoid
        // set(XmlObject)
        if (obj instanceof XmlObject)
            result.set_newValue((XmlObject)obj);
        else
            result.objectSet(obj);

        result.check_dated();
        result.setImmutable();

        return (XmlAnySimpleType) result;
    }

    /**
     * Creates an instance of this type.
     */
    private XmlObject createUnattachedNode(SchemaProperty prop)
    {
        XmlObject result = null;

        if (!isBuiltinType() && !isNoType())
        {
            // System.out.println("Attempting to load impl class: " + getFullJavaImplName());
            Constructor ctr = getJavaImplConstructor();
            if (ctr != null)
            {
                try
                {
                    // System.out.println("Succeeded!");
                    return (XmlObject)ctr.newInstance(_ctrArgs);
                }
                catch (Exception e)
                {
                    System.out.println("Exception trying to instantiate impl class.");
                    e.printStackTrace();
                }
            }
        }
        else
        {
            result = createBuiltinInstance();
        }

        // if no result, we must be a restriction of some compiled type
        for (SchemaType sType = this; result == null; sType = sType.getBaseType())
            result = ((SchemaTypeImpl)sType).createUnattachedSubclass(this);

        ((XmlObjectBase)result).init_flags(prop);
        return result;
    }

    private XmlObject createUnattachedSubclass(SchemaType sType)
    {
        if (!isBuiltinType() && !isNoType())
        {
            // System.out.println("Attempting to load impl class: " + getFullJavaImplName());
            Constructor ctr = getJavaImplConstructor2();
            if (ctr != null)
            {
                try
                {
                    // System.out.println("Succeeded!");
                    return (XmlObject)ctr.newInstance(new Object[] { sType, sType.isSimpleType() ? Boolean.FALSE : Boolean.TRUE });
                }
                catch (Exception e)
                {
                    System.out.println("Exception trying to instantiate impl class.");
                    e.printStackTrace();
                }
            }
            return null;
        }
        else
        {
            return createBuiltinSubclass(sType);
        }
    }

    private XmlObject createBuiltinInstance()
    {
        switch (getBuiltinTypeCode())
        {
            case BTC_NOT_BUILTIN:
                return new XmlAnyTypeImpl(BuiltinSchemaTypeSystem.ST_NO_TYPE);
            case BTC_ANY_TYPE:
                return new XmlAnyTypeImpl();
            case BTC_ANY_SIMPLE:
                return new XmlAnySimpleTypeImpl();
            case BTC_BOOLEAN:
                return new XmlBooleanImpl();
            case BTC_BASE_64_BINARY:
                return new XmlBase64BinaryImpl();
            case BTC_HEX_BINARY:
                return new XmlHexBinaryImpl();
            case BTC_ANY_URI:
                return new XmlAnyUriImpl();
            case BTC_QNAME:
                return new XmlQNameImpl();
            case BTC_NOTATION:
                return new XmlNotationImpl();
            case BTC_FLOAT:
                return new XmlFloatImpl();
            case BTC_DOUBLE:
                return new XmlDoubleImpl();
            case BTC_DECIMAL:
                return new XmlDecimalImpl();
            case BTC_STRING:
                return new XmlStringImpl();
            case BTC_DURATION:
                return new XmlDurationImpl();
            case BTC_DATE_TIME:
                return new XmlDateTimeImpl();
            case BTC_TIME:
                return new XmlTimeImpl();
            case BTC_DATE:
                return new XmlDateImpl();
            case BTC_G_YEAR_MONTH:
                return new XmlGYearMonthImpl();
            case BTC_G_YEAR:
                return new XmlGYearImpl();
            case BTC_G_MONTH_DAY:
                return new XmlGMonthDayImpl();
            case BTC_G_DAY:
                return new XmlGDayImpl();
            case BTC_G_MONTH:
                return new XmlGMonthImpl();
            case BTC_INTEGER:
                return new XmlIntegerImpl();
            case BTC_LONG:
                return new XmlLongImpl();
            case BTC_INT:
                return new XmlIntImpl();
            case BTC_SHORT:
                return new XmlShortImpl();
            case BTC_BYTE:
                return new XmlByteImpl();
            case BTC_NON_POSITIVE_INTEGER:
                return new XmlNonPositiveIntegerImpl();
            case BTC_NEGATIVE_INTEGER:
                return new XmlNegativeIntegerImpl();
            case BTC_NON_NEGATIVE_INTEGER:
                return new XmlNonNegativeIntegerImpl();
            case BTC_POSITIVE_INTEGER:
                return new XmlPositiveIntegerImpl();
            case BTC_UNSIGNED_LONG:
                return new XmlUnsignedLongImpl();
            case BTC_UNSIGNED_INT:
                return new XmlUnsignedIntImpl();
            case BTC_UNSIGNED_SHORT:
                return new XmlUnsignedShortImpl();
            case BTC_UNSIGNED_BYTE:
                return new XmlUnsignedByteImpl();
            case BTC_NORMALIZED_STRING:
                return new XmlNormalizedStringImpl();
            case BTC_TOKEN:
                return new XmlTokenImpl();
            case BTC_NAME:
                return new XmlNameImpl();
            case BTC_NCNAME:
                return new XmlNCNameImpl();
            case BTC_LANGUAGE:
                return new XmlLanguageImpl();
            case BTC_ID:
                return new XmlIdImpl();
            case BTC_IDREF:
                return new XmlIdRefImpl();
            case BTC_IDREFS:
                return new XmlIdRefsImpl();
            case BTC_ENTITY:
                return new XmlEntityImpl();
            case BTC_ENTITIES:
                return new XmlEntitiesImpl();
            case BTC_NMTOKEN:
                return new XmlNmTokenImpl();
            case BTC_NMTOKENS:
                return new XmlNmTokensImpl();
            default:
                throw new IllegalStateException("Unrecognized builtin type: " + getBuiltinTypeCode());
        }
    }

    private XmlObject createBuiltinSubclass(SchemaType sType)
    {
        boolean complex = !sType.isSimpleType();
        switch (getBuiltinTypeCode())
        {
            case BTC_NOT_BUILTIN:
                return new XmlAnyTypeImpl(BuiltinSchemaTypeSystem.ST_NO_TYPE);
            case BTC_ANY_TYPE:
            case BTC_ANY_SIMPLE:
                switch (sType.getSimpleVariety())
                {
                    case NOT_SIMPLE:
                        return new XmlComplexContentImpl(sType);
                    case ATOMIC:
                        return new XmlAnySimpleTypeRestriction(sType, complex);
                    case LIST:
                        return new XmlListImpl(sType, complex);
                    case UNION:
                        return new XmlUnionImpl(sType, complex);
                    default:
                        throw new IllegalStateException();
                }
            case BTC_BOOLEAN:
                return new XmlBooleanRestriction(sType, complex);
            case BTC_BASE_64_BINARY:
                return new XmlBase64BinaryRestriction(sType, complex);
            case BTC_HEX_BINARY:
                return new XmlHexBinaryRestriction(sType, complex);
            case BTC_ANY_URI:
                return new XmlAnyUriRestriction(sType, complex);
            case BTC_QNAME:
                return new XmlQNameRestriction(sType, complex);
            case BTC_NOTATION:
                return new XmlNotationRestriction(sType, complex);
            case BTC_FLOAT:
                return new XmlFloatRestriction(sType, complex);
            case BTC_DOUBLE:
                return new XmlDoubleRestriction(sType, complex);
            case BTC_DECIMAL:
                return new XmlDecimalRestriction(sType, complex);
            case BTC_STRING:
                if (sType.hasStringEnumValues())
                    return new XmlStringEnumeration(sType, complex);
                else
                    return new XmlStringRestriction(sType, complex);
            case BTC_DURATION:
                return new XmlDurationImpl(sType, complex);
            case BTC_DATE_TIME:
                return new XmlDateTimeImpl(sType, complex);
            case BTC_TIME:
                return new XmlTimeImpl(sType, complex);
            case BTC_DATE:
                return new XmlDateImpl(sType, complex);
            case BTC_G_YEAR_MONTH:
                return new XmlGYearMonthImpl(sType, complex);
            case BTC_G_YEAR:
                return new XmlGYearImpl(sType, complex);
            case BTC_G_MONTH_DAY:
                return new XmlGMonthDayImpl(sType, complex);
            case BTC_G_DAY:
                return new XmlGDayImpl(sType, complex);
            case BTC_G_MONTH:
                return new XmlGMonthImpl(sType, complex);
            case BTC_INTEGER:
                return new XmlIntegerRestriction(sType, complex);
            case BTC_LONG:
                return new XmlLongRestriction(sType, complex);
            case BTC_INT:
                return new XmlIntRestriction(sType, complex);
            case BTC_SHORT:
                return new XmlShortImpl(sType, complex);
            case BTC_BYTE:
                return new XmlByteImpl(sType, complex);
            case BTC_NON_POSITIVE_INTEGER:
                return new XmlNonPositiveIntegerImpl(sType, complex);
            case BTC_NEGATIVE_INTEGER:
                return new XmlNegativeIntegerImpl(sType, complex);
            case BTC_NON_NEGATIVE_INTEGER:
                return new XmlNonNegativeIntegerImpl(sType, complex);
            case BTC_POSITIVE_INTEGER:
                return new XmlPositiveIntegerImpl(sType, complex);
            case BTC_UNSIGNED_LONG:
                return new XmlUnsignedLongImpl(sType, complex);
            case BTC_UNSIGNED_INT:
                return new XmlUnsignedIntImpl(sType, complex);
            case BTC_UNSIGNED_SHORT:
                return new XmlUnsignedShortImpl(sType, complex);
            case BTC_UNSIGNED_BYTE:
                return new XmlUnsignedByteImpl(sType, complex);
            case BTC_NORMALIZED_STRING:
                return new XmlNormalizedStringImpl(sType, complex);
            case BTC_TOKEN:
                return new XmlTokenImpl(sType, complex);
            case BTC_NAME:
                return new XmlNameImpl(sType, complex);
            case BTC_NCNAME:
                return new XmlNCNameImpl(sType, complex);
            case BTC_LANGUAGE:
                return new XmlLanguageImpl(sType, complex);
            case BTC_ID:
                return new XmlIdImpl(sType, complex);
            case BTC_IDREF:
                return new XmlIdRefImpl(sType, complex);
            case BTC_IDREFS:
                return new XmlIdRefsImpl(sType, complex);
            case BTC_ENTITY:
                return new XmlEntityImpl(sType, complex);
            case BTC_ENTITIES:
                return new XmlEntitiesImpl(sType, complex);
            case BTC_NMTOKEN:
                return new XmlNmTokenImpl(sType, complex);
            case BTC_NMTOKENS:
                return new XmlNmTokensImpl(sType, complex);
            default:
                throw new IllegalStateException("Unrecognized builtin type: " + getBuiltinTypeCode());
        }
    }

    public SchemaType getCommonBaseType(SchemaType type)
    {
        // null type is treated as the no-type
        if (this == BuiltinSchemaTypeSystem.ST_ANY_TYPE || type == null || type.isNoType())
            return this;

        // any type is the universal base type; noType is the universal derived type
        if (type == BuiltinSchemaTypeSystem.ST_ANY_TYPE || isNoType())
            return type;

        // the regular case:
        SchemaTypeImpl sImpl1 = (SchemaTypeImpl)type;
        while (sImpl1.getBaseDepth() > getBaseDepth())
            sImpl1 = (SchemaTypeImpl)sImpl1.getBaseType();
        SchemaTypeImpl sImpl2 = this;
        while (sImpl2.getBaseDepth() > sImpl1.getBaseDepth())
            sImpl2 = (SchemaTypeImpl)sImpl2.getBaseType();
        for (;;)
        {
            if (sImpl1.equals(sImpl2))
                break;
            sImpl1 = (SchemaTypeImpl)sImpl1.getBaseType();
            sImpl2 = (SchemaTypeImpl)sImpl2.getBaseType();
            assert(sImpl1 != null && sImpl2 != null); // must meet at anyType
        }
        return sImpl1;
    }

    public boolean isAssignableFrom(SchemaType type)
    {
        if (type == null || type.isNoType())
            return true;

        if (isNoType())
            return false;

        int depth = ((SchemaTypeImpl)type).getBaseDepth() - getBaseDepth();
        if (depth < 0)
            return false;
        while (depth > 0)
        {
            type = type.getBaseType();
            depth -= 1;
        }
        return (type.equals(this));
    }


    public String toString()
    {
        if (getName() != null)
            return "T=" + QNameHelper.pretty(getName());

        if (isDocumentType())
            return "D=" + QNameHelper.pretty(getDocumentElementName());

        if (isAttributeType())
            return "R=" + QNameHelper.pretty(getAttributeTypeAttributeName());

        String prefix;

        if (getContainerField() != null)
        {
            prefix = (getContainerField().getName().getNamespaceURI().length() > 0 ?
                            (getContainerField().isAttribute() ? "Q=" : "E=") :
                            (getContainerField().isAttribute() ? "A=" : "U="))
                    + getContainerField().getName().getLocalPart();
            if (getOuterType() == null)
                return prefix + "@" + getContainerField().getName().getNamespaceURI();
        }
        else if (isNoType())
            return "N=";
        else if (getOuterType() == null)
            return "noouter";
        else if (getOuterType().getBaseType() == this)
            prefix = "B=";
        else if (getOuterType().getSimpleVariety() == SchemaType.LIST)
            prefix = "I=";
        else if (getOuterType().getSimpleVariety() == SchemaType.UNION)
            prefix = "M=" + getAnonymousUnionMemberOrdinal();
        else
            prefix = "strange=";

        return prefix + "|" + getOuterType().toString();
    }

    private XmlObject _parseObject;
    private String _parseTNS;
    private boolean _chameleon;
    private boolean _redefinition;

    public void setParseContext(XmlObject parseObject, String targetNamespace, boolean chameleon, boolean redefinition)
    {
        _parseObject = parseObject;
        _parseTNS = targetNamespace;
        _chameleon = chameleon;
        _redefinition = redefinition;
    }

    public XmlObject getParseObject()
        { return _parseObject; }

    public String getTargetNamespace()
        { return _parseTNS; }

    public boolean isChameleon ( )
        { return _chameleon; }

    public String getChameleonNamespace()
        { return _chameleon ? _parseTNS : null; }

    public boolean isRedefinition()
        { return _redefinition; }

    private SchemaType.Ref _selfref = new SchemaType.Ref(this);

    public SchemaType.Ref getRef()
        { return _selfref; }

    public SchemaComponent.Ref getComponentRef()
        { return getRef(); }

    /**
     * Gives access to the internals of element validation
     */
    private static class SequencerImpl implements SchemaTypeElementSequencer
    {
        private SchemaTypeVisitorImpl _visitor;

        private SequencerImpl(SchemaTypeVisitorImpl visitor)
        {
            _visitor = visitor;
        }

        public boolean next(QName elementName)
        {
            if (_visitor == null)
                return false;

            return _visitor.visit(elementName);
        }

        public boolean peek(QName elementName)
        {
            if (_visitor == null)
                return false;

            return _visitor.testValid(elementName);
        }
    }
}
