/*   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.*;
import org.apache.xmlbeans.impl.common.QNameHelper;
import org.apache.xmlbeans.impl.regex.ParseException;
import org.apache.xmlbeans.impl.regex.RegularExpression;
import org.apache.xmlbeans.impl.values.XmlValueOutOfRangeException;
import org.apache.xmlbeans.impl.xb.xsdschema.*;
import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument.Schema;

import javax.xml.namespace.QName;
import java.math.BigInteger;
import java.util.*;

public class StscSimpleTypeResolver {

    /**************************************************************************
     * SIMPLE TYPE RESOLUTION HERE
     *
     * Simple types can be declared as lists, unions, or restrictions.
     * These three cases are treated separately in resolveListType,
     * resolveUnionType, and resolveSimpleRestrictionType.
     *
     * The intricate work with facets is done in the restriction case,
     * using method called resolveFacets (the union and list cases have
     * trivial facet rules). Then all simple types call resolveProperties
     * in the end to have their "fundamental facets" resolved.
     */

    public static void resolveSimpleType(SchemaTypeImpl sImpl) {
        SimpleType parseSt = (SimpleType) sImpl.getParseObject();

        assert sImpl.isSimpleType();

        Schema schema = StscComplexTypeResolver.getSchema(parseSt);

        // Verify: have list, union, or restriction, but not more than one
        int count =
            (parseSt.isSetList() ? 1 : 0) +
            (parseSt.isSetUnion() ? 1 : 0) +
            (parseSt.isSetRestriction() ? 1 : 0);
        if (count > 1) {
            StscState.get().error(
                "A simple type must define either a list, a union, or a restriction: more than one found.",
                XmlErrorCodes.MALFORMED_SIMPLE_TYPE_DEFN,
                parseSt);
            // recovery: treat it as the first of list, union, restr
        } else if (count < 1) {
            StscState.get().error("A simple type must define either a list, a union, or a restriction: none was found.",
                XmlErrorCodes.MALFORMED_SIMPLE_TYPE_DEFN,
                parseSt);
            // recovery: treat it as restriction of anySimpleType
            resolveErrorSimpleType(sImpl);
            return;
        }

        // Set final flags
        boolean finalRest = false;
        boolean finalList = false;
        boolean finalUnion = false;

        Object finalValue = null;
        if (parseSt.isSetFinal()) {
            finalValue = parseSt.getFinal();
        }
        // Inspect the finalDefault attribute on the schema
        else if (schema != null && schema.isSetFinalDefault()) {
            finalValue = schema.getFinalDefault();
        }

        if (finalValue != null) {
            if (finalValue instanceof String) {
                if ("#all".equals(finalValue)) {
                    finalRest = finalList = finalUnion = true;
                }
            } else if (finalValue instanceof List) {
                @SuppressWarnings("unchecked")
                List<String> lFinalValue = (List<String>) finalValue;
                if (lFinalValue.contains("restriction")) {
                    finalRest = true;
                }

                if (lFinalValue.contains("list")) {
                    finalList = true;
                }

                if (lFinalValue.contains("union")) {
                    finalUnion = true;
                }
            }
        }

        sImpl.setSimpleFinal(finalRest, finalList, finalUnion);

        List<SchemaType> anonTypes = new ArrayList<>();

        if (parseSt.getList() != null) {
            resolveListType(sImpl, parseSt.getList(), anonTypes);
        } else if (parseSt.getUnion() != null) {
            resolveUnionType(sImpl, parseSt.getUnion(), anonTypes);
        } else if (parseSt.getRestriction() != null) {
            resolveSimpleRestrictionType(sImpl, parseSt.getRestriction(), anonTypes);
        }

        sImpl.setAnonymousTypeRefs(makeRefArray(anonTypes));
    }

    private static SchemaType.Ref[] makeRefArray(List<? extends SchemaType> typeList) {
        return typeList.stream().map(SchemaType::getRef).toArray(SchemaType.Ref[]::new);
    }

    static void resolveErrorSimpleType(SchemaTypeImpl sImpl) {
        sImpl.setSimpleTypeVariety(SchemaType.ATOMIC);
        sImpl.setBaseTypeRef(BuiltinSchemaTypeSystem.ST_ANY_SIMPLE.getRef());
        sImpl.setBaseDepth(BuiltinSchemaTypeSystem.ST_ANY_SIMPLE.getBaseDepth() + 1);
        sImpl.setPrimitiveTypeRef(BuiltinSchemaTypeSystem.ST_ANY_SIMPLE.getRef());
    }

    static void resolveListType(SchemaTypeImpl sImpl, org.apache.xmlbeans.impl.xb.xsdschema.ListDocument.List parseList, List<SchemaType> anonTypes) {
        StscState state = StscState.get();

        sImpl.setSimpleTypeVariety(SchemaType.LIST);
        sImpl.setBaseTypeRef(BuiltinSchemaTypeSystem.ST_ANY_SIMPLE.getRef());
        sImpl.setBaseDepth(BuiltinSchemaTypeSystem.ST_ANY_SIMPLE.getBaseDepth() + 1);
        sImpl.setDerivationType(SchemaType.DT_RESTRICTION);

        if (sImpl.isRedefinition()) {
            state.error(XmlErrorCodes.SCHEMA_REDEFINE$EXTEND_OR_RESTRICT,
                new Object[]{"list"}, parseList);
            // recovery: oh well.
        }

        QName itemName = parseList.getItemType();
        LocalSimpleType parseInner = parseList.getSimpleType();

        if (itemName != null && parseInner != null) {
            state.error(XmlErrorCodes.SCHEMA_SIMPLE_TYPE$LIST_HAS_BOTH_ITEM_OR_SIMPLE_TYPE, null, parseList);
            // recovery: ignore the inner simple type.
            parseInner = null;
        }

        SchemaTypeImpl itemImpl;
        XmlObject errorLoc;

        if (itemName != null) {
            itemImpl = state.findGlobalType(itemName, sImpl.getChameleonNamespace(), sImpl.getTargetNamespace());
            errorLoc = parseList.xgetItemType();
            if (itemImpl == null) {
                state.notFoundError(itemName, SchemaType.TYPE, parseList.xgetItemType(), true);
                // recovery: treat it as a list of anySimpleType
                itemImpl = BuiltinSchemaTypeSystem.ST_ANY_SIMPLE;
            }
        } else if (parseInner != null) {
            itemImpl = StscTranslator.translateAnonymousSimpleType(parseInner,
                sImpl.getTargetNamespace(), sImpl.getChameleonNamespace() != null,
                sImpl.getElemFormDefault(), sImpl.getAttFormDefault(), anonTypes, sImpl);
            errorLoc = parseInner;
        } else {
            state.error(XmlErrorCodes.SCHEMA_SIMPLE_TYPE$LIST_HAS_NEITHER_ITEM_OR_SIMPLE_TYPE, null, parseList);
            // recovery: treat it as an extension of anySimpleType
            resolveErrorSimpleType(sImpl);
            return;
        }

        // Verify final restrictions
        if (itemImpl.finalList()) {
            state.error(XmlErrorCodes.SIMPLE_TYPE_PROPERTIES$LIST_FINAL, null, parseList);
        }

        // Recursion...
        StscResolver.resolveType(itemImpl);

        if (!itemImpl.isSimpleType()) {
            state.error(XmlErrorCodes.SIMPLE_TYPE_RESTRICTION$LIST_ITEM_NOT_SIMPLE, null, errorLoc);
            // recovery: treat the item type as anySimpleType
            sImpl = BuiltinSchemaTypeSystem.ST_ANY_SIMPLE;
        }

        switch (itemImpl.getSimpleVariety()) {
            case SchemaType.LIST:
                state.error(XmlErrorCodes.SIMPLE_TYPE_RESTRICTION$LIST_ITEM_IS_LIST, null, errorLoc);
                // recovery: treat the list as an anySimpleType
                resolveErrorSimpleType(sImpl);
                return;
            case SchemaType.UNION:
                if (itemImpl.isUnionOfLists()) {
                    state.error(XmlErrorCodes.SIMPLE_TYPE_RESTRICTION$LIST_ITEM_IS_UNION_OF_LIST, null, errorLoc);
                    resolveErrorSimpleType(sImpl);
                    return;
                }
                // fallthrough: nonlist unions are just like atomic items
            case SchemaType.ATOMIC:
                sImpl.setListItemTypeRef(itemImpl.getRef());
                // Check that the item type is not a plan NOTATION
                if (sImpl.getBuiltinTypeCode() == SchemaType.BTC_NOTATION) {
                    state.recover(XmlErrorCodes.DATATYPE_ENUM_NOTATION, null, errorLoc);
                }
                break;
            default:
                assert (false);
                sImpl.setListItemTypeRef(BuiltinSchemaTypeSystem.ST_ANY_SIMPLE.getRef());
        }

        // now deal with facets
        sImpl.setBasicFacets(StscState.FACETS_LIST, StscState.FIXED_FACETS_LIST);
        sImpl.setWhiteSpaceRule(SchemaType.WS_COLLAPSE);

        // now compute our intrinsic properties
        resolveFundamentalFacets(sImpl);
    }


    static void resolveUnionType(SchemaTypeImpl sImpl, UnionDocument.Union parseUnion, List<SchemaType> anonTypes) {
        sImpl.setSimpleTypeVariety(SchemaType.UNION);
        sImpl.setBaseTypeRef(BuiltinSchemaTypeSystem.ST_ANY_SIMPLE.getRef());
        sImpl.setBaseDepth(BuiltinSchemaTypeSystem.ST_ANY_SIMPLE.getBaseDepth() + 1);
        sImpl.setDerivationType(SchemaType.DT_RESTRICTION);

        StscState state = StscState.get();

        if (sImpl.isRedefinition()) {
            state.error(XmlErrorCodes.SCHEMA_REDEFINE$EXTEND_OR_RESTRICT,
                new Object[]{"union"}, parseUnion);
            // recovery: oh well.
        }

        @SuppressWarnings("unchecked")
        List<QName> memberTypes = (List<QName>) parseUnion.getMemberTypes();
        SimpleType[] simpleTypes = parseUnion.getSimpleTypeArray();

        List<SchemaTypeImpl> memberImplList = new ArrayList<>();

        if (simpleTypes.length == 0 && (memberTypes == null || memberTypes.size() == 0)) {
            state.error(XmlErrorCodes.SCHEMA_SIMPLE_TYPE$UNION_HAS_MEMBER_TYPES_OR_SIMPLE_TYPES, null, parseUnion);
            // recovery: oh well, zero member types is fine.
        }

        if (memberTypes != null) {
            for (QName mName : memberTypes) {
                SchemaTypeImpl memberImpl = state.findGlobalType(mName, sImpl.getChameleonNamespace(), sImpl.getTargetNamespace());
                if (memberImpl == null)
                // recovery: skip member
                {
                    state.notFoundError(mName, SchemaType.TYPE, parseUnion.xgetMemberTypes(), true);
                } else {
                    memberImplList.add(memberImpl);
                }
            }
        }

        for (int i = 0; i < simpleTypes.length; i++) {
            // BUGBUG: see if non<simpleType> children can leak through
            SchemaTypeImpl mImpl = StscTranslator.translateAnonymousSimpleType(simpleTypes[i],
                sImpl.getTargetNamespace(), sImpl.getChameleonNamespace() != null,
                sImpl.getElemFormDefault(), sImpl.getAttFormDefault(), anonTypes, sImpl);
            memberImplList.add(mImpl);
            mImpl.setAnonymousUnionMemberOrdinal(i + 1);
        }

        // Recurse and resolve all member types
        for (Iterator<SchemaTypeImpl> mImpls = memberImplList.iterator(); mImpls.hasNext(); ) {
            SchemaTypeImpl mImpl = mImpls.next();
            if (!StscResolver.resolveType(mImpl)) {
                // KHK: review
                String memberName = "";
                XmlObject errorLoc;
                if (Objects.equals(mImpl.getOuterType(), sImpl)) {
                    errorLoc = mImpl.getParseObject();
                } else {
                    memberName = QNameHelper.pretty(mImpl.getName()) + " ";
                    errorLoc = parseUnion.xgetMemberTypes();
                }

                state.error(XmlErrorCodes.SCHEMA_SIMPLE_TYPE$CYCLIC_UNION, new Object[]{memberName}, errorLoc);

                // recovery: ignore the errant union member
                mImpls.remove();
            }
        }

        // Now verify members
        boolean isUnionOfLists = false;

        for (Iterator<SchemaTypeImpl> mImpls = memberImplList.iterator(); mImpls.hasNext(); ) {
            SchemaTypeImpl mImpl = mImpls.next();

            if (!mImpl.isSimpleType()) {
                // KHK: review
                String memberName = "";
                XmlObject errorLoc;
                if (mImpl.getOuterType() != null && mImpl.getOuterType().equals(sImpl)) {
                    errorLoc = mImpl.getParseObject();
                } else {
                    memberName = QNameHelper.pretty(mImpl.getName()) + " ";
                    errorLoc = parseUnion.xgetMemberTypes();
                }

                state.error(XmlErrorCodes.SIMPLE_TYPE_RESTRICTION$UNION_MEMBER_NOT_SIMPLE, new Object[]{memberName}, errorLoc);

                // recovery: ignore the errant union member
                mImpls.remove();
                continue;
            }

            if (mImpl.getSimpleVariety() == SchemaType.LIST ||
                mImpl.getSimpleVariety() == SchemaType.UNION && mImpl.isUnionOfLists()) {
                isUnionOfLists = true;
            }
        }

        // Verify any final restrictions
        for (SchemaTypeImpl schemaType : memberImplList) {
            if (schemaType.finalUnion()) {
                state.error(XmlErrorCodes.SIMPLE_TYPE_PROPERTIES$UNION_FINAL, null, parseUnion);
            }
        }

        sImpl.setUnionOfLists(isUnionOfLists);

        sImpl.setUnionMemberTypeRefs(makeRefArray(memberImplList));

        // now deal with facets
        sImpl.setBasicFacets(StscState.FACETS_UNION, StscState.FIXED_FACETS_UNION);

        // now compute our intrinsic properties
        resolveFundamentalFacets(sImpl);
    }

    static void resolveSimpleRestrictionType(SchemaTypeImpl sImpl, RestrictionDocument.Restriction parseRestr, List<SchemaType> anonTypes) {
        QName baseName = parseRestr.getBase();
        SimpleType parseInner = parseRestr.getSimpleType();
        StscState state = StscState.get();

        if (baseName != null && parseInner != null) {
            state.error(XmlErrorCodes.SCHEMA_SIMPLE_TYPE$RESTRICTION_HAS_BOTH_BASE_OR_SIMPLE_TYPE, null, parseRestr);
            // recovery: ignore the inner simple type.
            parseInner = null;
        }

        SchemaTypeImpl baseImpl;

        if (baseName != null) {
            if (sImpl.isRedefinition()) {
                baseImpl = state.findRedefinedGlobalType(parseRestr.getBase(), sImpl.getChameleonNamespace(), sImpl);
                if (baseImpl != null && !baseImpl.getName().equals(sImpl.getName())) {
                    state.error(XmlErrorCodes.SCHEMA_REDEFINE$SAME_TYPE,
                        new Object[]{"<simpleType>",
                            QNameHelper.pretty(baseName),
                            QNameHelper.pretty(sImpl.getName())
                        },
                        parseRestr);
                }
            } else {
                baseImpl = state.findGlobalType(baseName, sImpl.getChameleonNamespace(), sImpl.getTargetNamespace());
            }
            if (baseImpl == null) {
                state.notFoundError(baseName, SchemaType.TYPE, parseRestr.xgetBase(), true);
                // recovery: treat it as an extension of anySimpleType
                baseImpl = BuiltinSchemaTypeSystem.ST_ANY_SIMPLE;
            }
        } else if (parseInner != null) {
            if (sImpl.isRedefinition()) {
                StscState.get().error(XmlErrorCodes.SCHEMA_REDEFINE$EXTEND_OR_RESTRICT,
                    new Object[]{"<simpleType>"}, parseInner);
                // recovery: oh well.
            }

            baseImpl = StscTranslator.translateAnonymousSimpleType(parseInner,
                sImpl.getTargetNamespace(), sImpl.getChameleonNamespace() != null,
                sImpl.getElemFormDefault(), sImpl.getAttFormDefault(), anonTypes, sImpl);
        } else {
            state.error(XmlErrorCodes.SCHEMA_SIMPLE_TYPE$RESTRICTION_HAS_NEITHER_BASE_OR_SIMPLE_TYPE, null, parseRestr);
            // recovery: treat it as an extension of anySimpleType
            baseImpl = BuiltinSchemaTypeSystem.ST_ANY_SIMPLE;
        }

        // Recursion!
        if (!StscResolver.resolveType(baseImpl)) {
            // cyclic dependency recovery: treat it as an extension of anySimpleType
            baseImpl = BuiltinSchemaTypeSystem.ST_ANY_SIMPLE;
        }

        if (baseImpl.finalRestriction()) {
            state.error(XmlErrorCodes.SIMPLE_TYPE_PROPERTIES$RESTRICTION_FINAL, null, parseRestr);
        }

        sImpl.setBaseTypeRef(baseImpl.getRef());
        sImpl.setBaseDepth(baseImpl.getBaseDepth() + 1);
        sImpl.setDerivationType(SchemaType.DT_RESTRICTION);

        if (!baseImpl.isSimpleType()) {
            state.error(XmlErrorCodes.SIMPLE_TYPE_RESTRICTION$ATOMIC_NOT_SIMPLE, null, parseRestr.xgetBase());
            // recovery: treat it as a restriction of anySimpleType
            resolveErrorSimpleType(sImpl);
            return;
        }

        sImpl.setSimpleTypeVariety(baseImpl.getSimpleVariety());

        // copy variety-specific properties
        switch (baseImpl.getSimpleVariety()) {
            case SchemaType.ATOMIC:
                sImpl.setPrimitiveTypeRef(baseImpl.getPrimitiveType().getRef());
                break;
            case SchemaType.UNION:
                sImpl.setUnionOfLists(baseImpl.isUnionOfLists());
                sImpl.setUnionMemberTypeRefs(makeRefArray(Arrays.asList(baseImpl.getUnionMemberTypes())));
                break;
            case SchemaType.LIST:
                sImpl.setListItemTypeRef(baseImpl.getListItemType().getRef());
                break;
        }

        // deal with facets
        resolveFacets(sImpl, parseRestr, baseImpl);

        // now compute our intrinsic properties
        resolveFundamentalFacets(sImpl);
    }

    static int translateWhitespaceCode(XmlAnySimpleType value) {
        // BUGBUG: add whitespace rule to textvalue.
        String textval = value.getStringValue();

        if (textval.equals("collapse")) {
            return SchemaType.WS_COLLAPSE;
        }

        if (textval.equals("preserve")) {
            return SchemaType.WS_PRESERVE;
        }

        if (textval.equals("replace")) {
            return SchemaType.WS_REPLACE;
        }

        // KHK: s4s
        StscState.get().error("Unrecognized whitespace value \"" + textval + "\"", XmlErrorCodes.FACET_VALUE_MALFORMED, value);
        return SchemaType.WS_UNSPECIFIED;
    }

    static boolean isMultipleFacet(int facetcode) {
        return (facetcode == SchemaType.FACET_ENUMERATION ||
                facetcode == SchemaType.FACET_PATTERN);
    }

    static boolean facetAppliesToType(int facetCode, SchemaTypeImpl baseImpl) {
        switch (baseImpl.getSimpleVariety()) {
            case SchemaType.LIST:
                switch (facetCode) {
                    case SchemaType.FACET_LENGTH:
                    case SchemaType.FACET_MIN_LENGTH:
                    case SchemaType.FACET_MAX_LENGTH:
                    case SchemaType.FACET_ENUMERATION:
                    case SchemaType.FACET_PATTERN:
                    case SchemaType.FACET_WHITE_SPACE:
                        return true;
                }
                return false;

            case SchemaType.UNION:
                switch (facetCode) {
                    case SchemaType.FACET_ENUMERATION:
                    case SchemaType.FACET_PATTERN:
                        return true;
                }
                return false;
        }

        // the atomic case

        switch (baseImpl.getPrimitiveType().getBuiltinTypeCode()) {
            case SchemaType.BTC_ANY_SIMPLE:
                return false;

            case SchemaType.BTC_BOOLEAN:
                switch (facetCode) {
                    case SchemaType.FACET_PATTERN:
                    case SchemaType.FACET_WHITE_SPACE:
                        return true;
                }
                return false;

            case SchemaType.BTC_FLOAT:
            case SchemaType.BTC_DOUBLE:
            case SchemaType.BTC_DURATION:
            case SchemaType.BTC_DATE_TIME:
            case SchemaType.BTC_TIME:
            case SchemaType.BTC_DATE:
            case SchemaType.BTC_G_YEAR_MONTH:
            case SchemaType.BTC_G_YEAR:
            case SchemaType.BTC_G_MONTH_DAY:
            case SchemaType.BTC_G_DAY:
            case SchemaType.BTC_G_MONTH:
                switch (facetCode) {
                    case SchemaType.FACET_MIN_EXCLUSIVE:
                    case SchemaType.FACET_MIN_INCLUSIVE:
                    case SchemaType.FACET_MAX_INCLUSIVE:
                    case SchemaType.FACET_MAX_EXCLUSIVE:
                    case SchemaType.FACET_ENUMERATION:
                    case SchemaType.FACET_PATTERN:
                    case SchemaType.FACET_WHITE_SPACE:
                        return true;
                }
                return false;

            case SchemaType.BTC_DECIMAL:
                switch (facetCode) {
                    case SchemaType.FACET_MIN_EXCLUSIVE:
                    case SchemaType.FACET_MIN_INCLUSIVE:
                    case SchemaType.FACET_MAX_INCLUSIVE:
                    case SchemaType.FACET_MAX_EXCLUSIVE:
                    case SchemaType.FACET_TOTAL_DIGITS:
                    case SchemaType.FACET_FRACTION_DIGITS:
                    case SchemaType.FACET_ENUMERATION:
                    case SchemaType.FACET_PATTERN:
                    case SchemaType.FACET_WHITE_SPACE:
                        return true;
                }
                return false;

            case SchemaType.BTC_BASE_64_BINARY:
            case SchemaType.BTC_HEX_BINARY:
            case SchemaType.BTC_ANY_URI:
            case SchemaType.BTC_QNAME:
            case SchemaType.BTC_NOTATION:
            case SchemaType.BTC_STRING:
                switch (facetCode) {
                    case SchemaType.FACET_LENGTH:
                    case SchemaType.FACET_MIN_LENGTH:
                    case SchemaType.FACET_MAX_LENGTH:
                    case SchemaType.FACET_ENUMERATION:
                    case SchemaType.FACET_PATTERN:
                    case SchemaType.FACET_WHITE_SPACE:
                        return true;
                }
                return false;

            default:
                assert (false);
                return false;
        }
    }

    private static int other_similar_limit(int facetcode) {
        switch (facetcode) {
            case SchemaType.FACET_MIN_EXCLUSIVE:
                return SchemaType.FACET_MIN_INCLUSIVE;
            case SchemaType.FACET_MIN_INCLUSIVE:
                return SchemaType.FACET_MIN_EXCLUSIVE;
            case SchemaType.FACET_MAX_INCLUSIVE:
                return SchemaType.FACET_MAX_EXCLUSIVE;
            case SchemaType.FACET_MAX_EXCLUSIVE:
                return SchemaType.FACET_MAX_INCLUSIVE;
            default:
                assert (false);
                throw new IllegalStateException();
        }
    }

    static void resolveFacets(SchemaTypeImpl sImpl, XmlObject restriction, SchemaTypeImpl baseImpl) {
        StscState state = StscState.get();

        boolean[] seenFacet = new boolean[SchemaType.LAST_FACET + 1];
        XmlAnySimpleType[] myFacets = baseImpl.getBasicFacets(); // makes a copy
        boolean[] fixedFacets = baseImpl.getFixedFacets();
        int wsr = SchemaType.WS_UNSPECIFIED;
        List<XmlAnySimpleType> enumeratedValues = null;
        List<RegularExpression> patterns = null;

        if (restriction != null) {
            XmlCursor cur = restriction.newCursor();
            for (boolean more = cur.toFirstChild(); more; more = cur.toNextSibling()) {
                QName facetQName = cur.getName();
                String facetName = facetQName.getLocalPart();
                int code = translateFacetCode(facetQName);
                if (code == -1) {
                    continue;
                }

                Facet facet = (Facet) cur.getObject();

                if (!facetAppliesToType(code, baseImpl)) {
                    state.error(XmlErrorCodes.FACETS_APPLICABLE,
                        new Object[]{facetName, QNameHelper.pretty(baseImpl.getName())}, facet);
                    continue;
                } else if (baseImpl.getSimpleVariety() == SchemaType.ATOMIC &&
                           baseImpl.getPrimitiveType().getBuiltinTypeCode() == SchemaType.BTC_NOTATION
                           && (code == SchemaType.FACET_LENGTH || code == SchemaType.FACET_MIN_LENGTH ||
                               code == SchemaType.FACET_MAX_LENGTH)) {
                    state.warning(XmlErrorCodes.FACETS_DEPRECATED_NOTATION,
                        new Object[]{facetName, QNameHelper.pretty(baseImpl.getName())}, facet);
                }
                if (seenFacet[code] && !isMultipleFacet(code)) {
                    state.error(XmlErrorCodes.DATATYPE_SINGLE_FACET_VALUE, null, facet);
                    continue;
                }
                seenFacet[code] = true;

                switch (code) {
                    case SchemaType.FACET_LENGTH:
//                        if (myFacets[SchemaType.FACET_MIN_LENGTH] != null ||
//                            myFacets[SchemaType.FACET_MAX_LENGTH] != null)
//                        {
//                            state.error(XmlErrorCodes.DATATYPE_LENGTH, null, facet);
//                            continue;
//                        }
                        XmlInteger len = StscTranslator.buildNnInteger(facet.getValue());
                        if (len == null) {
                            state.error("Must be a nonnegative integer", XmlErrorCodes.FACET_VALUE_MALFORMED, facet);
                            continue;
                        }
                        if (fixedFacets[code] && !myFacets[code].valueEquals(len)) {
                            state.error(XmlErrorCodes.FACET_FIXED, new Object[]{facetName}, facet);
                            continue;
                        }
                        if (myFacets[SchemaType.FACET_MIN_LENGTH] != null) {
                            // An error for 'length' and 'minLength' to be specified at the same time
                            // except if the base type had the same value for 'minLength' also
                            XmlAnySimpleType baseMinLength = baseImpl.getFacet(SchemaType.FACET_MIN_LENGTH);
                            if (!(baseMinLength != null &&
                                  baseMinLength.valueEquals(myFacets[SchemaType.FACET_MIN_LENGTH]) &&
                                  baseMinLength.compareValue(len) <= 0)) {
                                state.error(XmlErrorCodes.DATATYPE_LENGTH, null, facet);
                                continue;
                            }
                        }
                        if (myFacets[SchemaType.FACET_MAX_LENGTH] != null) {
                            // An error for 'length' and 'maxLength' to be specified at the same time
                            // except if the base type had the same value for 'maxLength' also
                            XmlAnySimpleType baseMaxLength = baseImpl.getFacet(SchemaType.FACET_MAX_LENGTH);
                            if (!(baseMaxLength != null &&
                                  baseMaxLength.valueEquals(myFacets[SchemaType.FACET_MAX_LENGTH]) &&
                                  baseMaxLength.compareValue(len) >= 0)) {
                                state.error(XmlErrorCodes.DATATYPE_LENGTH, null, facet);
                                continue;
                            }
                        }
                        myFacets[code] = len;
                        break;

                    case SchemaType.FACET_MIN_LENGTH:
                    case SchemaType.FACET_MAX_LENGTH:
                        XmlInteger mlen = StscTranslator.buildNnInteger(facet.getValue());
                        if (mlen == null) {
                            state.error("Must be a nonnegative integer", XmlErrorCodes.FACET_VALUE_MALFORMED, facet);
                            continue;
                        }
                        if (fixedFacets[code] && !myFacets[code].valueEquals(mlen)) {
                            state.error(XmlErrorCodes.FACET_FIXED, new Object[]{facetName}, facet);
                            continue;
                        }
                        if (myFacets[SchemaType.FACET_LENGTH] != null) {
                            // It's an error for 'length' and 'minLength'/'maxLength' to be
                            // specified at the same time, except for the case when
                            // the base type had the same value for 'minLength'/'maxLength'
                            // and the two values are consistent
                            XmlAnySimpleType baseMinMaxLength = baseImpl.getFacet(code);
                            if (!(baseMinMaxLength != null &&
                                  baseMinMaxLength.valueEquals(mlen) &&
                                  (code == SchemaType.FACET_MIN_LENGTH ?
                                      baseMinMaxLength.compareTo(myFacets[SchemaType.FACET_LENGTH]) <= 0 :
                                      baseMinMaxLength.compareTo(myFacets[SchemaType.FACET_LENGTH]) >= 0))) {
                                state.error(XmlErrorCodes.DATATYPE_LENGTH, null, facet);
                                continue;
                            }
                        }
                        if (myFacets[SchemaType.FACET_MAX_LENGTH] != null) {
                            if (mlen.compareValue(myFacets[SchemaType.FACET_MAX_LENGTH]) > 0) {
                                state.error(XmlErrorCodes.DATATYPE_MAX_LENGTH_RESTRICTION, null, facet);
                                continue;
                            }
                        }
                        if (myFacets[SchemaType.FACET_MIN_LENGTH] != null) {
                            if (mlen.compareValue(myFacets[SchemaType.FACET_MIN_LENGTH]) < 0) {
                                state.error(XmlErrorCodes.DATATYPE_MIN_LENGTH_RESTRICTION, null, facet);
                                continue;
                            }
                        }
                        myFacets[code] = mlen;
                        break;

                    case SchemaType.FACET_TOTAL_DIGITS:
                        XmlPositiveInteger dig = StscTranslator.buildPosInteger(facet.getValue());
                        if (dig == null) {
                            state.error("Must be a positive integer", XmlErrorCodes.FACET_VALUE_MALFORMED, facet);
                            break;
                        }
                        if (fixedFacets[code] && !myFacets[code].valueEquals(dig)) {
                            state.error(XmlErrorCodes.FACET_FIXED, new Object[]{facetName}, facet);
                            continue;
                        }
                        if (myFacets[SchemaType.FACET_TOTAL_DIGITS] != null) {
                            if (dig.compareValue(myFacets[SchemaType.FACET_TOTAL_DIGITS]) > 0) {
                                state.error(XmlErrorCodes.DATATYPE_TOTAL_DIGITS_RESTRICTION, null, facet);
                            }
                        }
                        myFacets[code] = dig;
                        break;

                    case SchemaType.FACET_FRACTION_DIGITS:
                        XmlNonNegativeInteger fdig = StscTranslator.buildNnInteger(facet.getValue());
                        if (fdig == null) {
                            state.error("Must be a nonnegative integer", XmlErrorCodes.FACET_VALUE_MALFORMED, facet);
                            break;
                        }
                        if (fixedFacets[code] && !myFacets[code].valueEquals(fdig)) {
                            state.error(XmlErrorCodes.FACET_FIXED, new Object[]{facetName}, facet);
                            continue;
                        }
                        if (myFacets[SchemaType.FACET_FRACTION_DIGITS] != null) {
                            if (fdig.compareValue(myFacets[SchemaType.FACET_FRACTION_DIGITS]) > 0) {
                                state.error(XmlErrorCodes.DATATYPE_FRACTION_DIGITS_RESTRICTION, null, facet);
                            }
                        }
                        if (myFacets[SchemaType.FACET_TOTAL_DIGITS] != null) {
                            if (fdig.compareValue(myFacets[SchemaType.FACET_TOTAL_DIGITS]) > 0) {
                                state.error(XmlErrorCodes.DATATYPE_FRACTION_DIGITS_LE_TOTAL_DIGITS, null, facet);
                            }
                        }
                        myFacets[code] = fdig;
                        break;

                    case SchemaType.FACET_MIN_EXCLUSIVE:
                    case SchemaType.FACET_MIN_INCLUSIVE:
                    case SchemaType.FACET_MAX_INCLUSIVE:
                    case SchemaType.FACET_MAX_EXCLUSIVE:

                        if (seenFacet[other_similar_limit(code)]) {
                            state.error("Cannot define both inclusive and exclusive limit in the same restriciton", XmlErrorCodes.FACET_DUPLICATED, facet);
                            continue;
                        }
                        boolean ismin = (code == SchemaType.FACET_MIN_EXCLUSIVE || code == SchemaType.FACET_MIN_INCLUSIVE);
                        boolean isexclusive = (code == SchemaType.FACET_MIN_EXCLUSIVE || code == SchemaType.FACET_MAX_EXCLUSIVE);

                        XmlAnySimpleType limit;
                        try {
                            limit = baseImpl.newValue(facet.getValue(), true);
                        } catch (XmlValueOutOfRangeException e) {
                            // note: this guarantees that the limit is a valid number in the
                            // base data type!!
                            switch (code) {
                                case SchemaType.FACET_MIN_EXCLUSIVE:
                                    state.error(XmlErrorCodes.DATATYPE_MIN_EXCLUSIVE_RESTRICTION,
                                        new Object[]{e.getMessage()}, facet);
                                    break;
                                case SchemaType.FACET_MIN_INCLUSIVE:
                                    state.error(XmlErrorCodes.DATATYPE_MIN_INCLUSIVE_RESTRICTION,
                                        new Object[]{e.getMessage()}, facet);
                                    break;
                                case SchemaType.FACET_MAX_INCLUSIVE:
                                    state.error(XmlErrorCodes.DATATYPE_MAX_INCLUSIVE_RESTRICTION,
                                        new Object[]{e.getMessage()}, facet);
                                    break;
                                case SchemaType.FACET_MAX_EXCLUSIVE:
                                    state.error(XmlErrorCodes.DATATYPE_MAX_EXCLUSIVE_RESTRICTION,
                                        new Object[]{e.getMessage()}, facet);
                                    break;
                            }

                            // BUGBUG: if there are actual schemas that redefine min/maxExclusive,
                            // they will need this rule relaxed for them!!
                            continue;
                        }
                        if (fixedFacets[code] && !myFacets[code].valueEquals(limit)) {
                            state.error(XmlErrorCodes.FACET_FIXED, new Object[]{facetName}, facet);
                            continue;
                        }
                        if (myFacets[code] != null) {
                            SchemaType limitSType = limit.schemaType();
                            if (limitSType != null && !limitSType.isSimpleType() &&
                                limitSType.getContentType() == SchemaType.SIMPLE_CONTENT) {
                                // in the case of complex types with simple content that has facets
                                // we need to compare values based on the content type
                                limit = baseImpl.getContentBasedOnType().newValue(facet.getValue());
                            }

                            int comparison = limit.compareValue(myFacets[code]);
                            if (comparison == 2 || comparison == (ismin ? -1 : 1)) {
                                state.error(ismin ?
                                        (isexclusive ?
                                            "Must be greater than or equal to previous minExclusive" :
                                            "Must be greater than or equal to previous minInclusive") :
                                        (isexclusive ?
                                            "Must be less than or equal to previous maxExclusive" :
                                            "Must be less than or equal to previous maxInclusive"),
                                    XmlErrorCodes.FACET_VALUE_MALFORMED, facet);
                                continue;
                            }
                        }
                        myFacets[code] = limit;
                        myFacets[other_similar_limit(code)] = null;
                        break;

                    case SchemaType.FACET_WHITE_SPACE:
                        wsr = translateWhitespaceCode(facet.getValue());
                        if (baseImpl.getWhiteSpaceRule() > wsr) {
                            wsr = SchemaType.WS_UNSPECIFIED;
                            state.error(XmlErrorCodes.DATATYPE_WHITESPACE_RESTRICTION, null, facet);
                            continue;
                        }
                        myFacets[code] = StscState.build_wsstring(wsr).get();
                        break;

                    case SchemaType.FACET_ENUMERATION:
                        XmlAnySimpleType enumval;
                        try {
                            enumval = baseImpl.newValue(facet.getValue(), true);
                            // enumval.set(facet.getValue());
                            // ((XmlObjectBase)enumval).setImmutable();
                        } catch (XmlValueOutOfRangeException e) {
                            state.error(XmlErrorCodes.DATATYPE_ENUM_RESTRICTION, new Object[]{facet.getValue().getStringValue(), e.getMessage()}, facet);
                            continue;
                        }
                        if (enumeratedValues == null) {
                            enumeratedValues = new ArrayList<>();
                        }
                        enumeratedValues.add(enumval);
                        break;

                    case SchemaType.FACET_PATTERN:
                        RegularExpression p;
                        try {
                            p = new RegularExpression(facet.getValue().getStringValue(), "X");
                        } catch (ParseException e) {
                            state.error(XmlErrorCodes.PATTERN_REGEX, new Object[]{facet.getValue().getStringValue(), e.getMessage()}, facet);
                            continue;
                        }
                        if (patterns == null) {
                            patterns = new ArrayList<>();
                        }
                        patterns.add(p);
                        break;
                }

                if (facet.getFixed()) {
                    fixedFacets[code] = true;
                }
            }
        }

        // Store the array of basic facets

        sImpl.setBasicFacets(makeValueRefArray(myFacets), fixedFacets);

        // Update the numeric whitespace rule
        if (wsr == SchemaType.WS_UNSPECIFIED) {
            wsr = baseImpl.getWhiteSpaceRule();
        }
        sImpl.setWhiteSpaceRule(wsr);

        // store away the enumerated values
        if (enumeratedValues != null) {
            sImpl.setEnumerationValues(makeValueRefArray(enumeratedValues.toArray(new XmlAnySimpleType[0])));

            SchemaType beType = sImpl;
            if (sImpl.isRedefinition()) {
                beType = sImpl.getBaseType().getBaseEnumType();
                if (beType == null || sImpl.getBaseType() == beType) {
                    beType = sImpl;
                }
            } else if (sImpl.getBaseType().getBaseEnumType() != null) {
                beType = sImpl.getBaseType().getBaseEnumType();
            }
            sImpl.setBaseEnumTypeRef(beType.getRef());
        } else {
            sImpl.copyEnumerationValues(baseImpl);
        }

        // store the pattern list
        RegularExpression[] patternArray = patterns != null ? patterns.toArray(EMPTY_REGEX_ARRAY) : EMPTY_REGEX_ARRAY;
        sImpl.setPatternFacet((patternArray.length > 0 || baseImpl.hasPatternFacet()));
        sImpl.setPatterns(patternArray);

        // Check that, if the base type is NOTATION, there is an enumeration facet
        // http://www.w3.org/TR/xmlschema-2/#NOTATION
        if (baseImpl.getBuiltinTypeCode() == SchemaType.BTC_NOTATION) {
            if (sImpl.getEnumerationValues() == null) {
                state.recover(XmlErrorCodes.DATATYPE_ENUM_NOTATION, null, restriction);
            }
        }
    }

    private static XmlValueRef[] makeValueRefArray(XmlAnySimpleType[] source) {
        XmlValueRef[] result = new XmlValueRef[source.length];
        for (int i = 0; i < result.length; i++) {
            result[i] = (source[i] == null ? null : new XmlValueRef(source[i]));
        }
        return result;
    }

    private static final RegularExpression[] EMPTY_REGEX_ARRAY = new RegularExpression[0];

    private static boolean isDiscreteType(SchemaTypeImpl sImpl) {
        if (sImpl.getFacet(SchemaType.FACET_FRACTION_DIGITS) != null) {
            return true;
        }
        // BUGBUG: spec is silent on enumerations; they're finite too.
        switch (sImpl.getPrimitiveType().getBuiltinTypeCode()) {
            case SchemaType.BTC_DATE:
            case SchemaType.BTC_G_YEAR_MONTH:
            case SchemaType.BTC_G_YEAR:
            case SchemaType.BTC_G_MONTH_DAY:
            case SchemaType.BTC_G_DAY:
            case SchemaType.BTC_G_MONTH:
            case SchemaType.BTC_BOOLEAN:
                return true;
        }
        return false;
    }

    private static boolean isNumericPrimitive(SchemaType sImpl) {
        switch (sImpl.getBuiltinTypeCode()) {
            case SchemaType.BTC_DECIMAL:
            case SchemaType.BTC_FLOAT:
            case SchemaType.BTC_DOUBLE:
                return true;
        }
        return false;
    }

    private static int decimalSizeOfType(SchemaTypeImpl sImpl) {
        int size = mathematicalSizeOfType(sImpl);

        // byte and short are inconvenient, because setByte((byte)4) requires a cast.
        // So use "int" unless you're really a xs:byte, xs:short, or an xs:unsignedByte
        // (the last case is included for alignment with JAXB)

        if (size == SchemaType.SIZE_BYTE && !XmlByte.type.isAssignableFrom(sImpl)) {
            size = SchemaType.SIZE_SHORT;
        }
        if (size == SchemaType.SIZE_SHORT && !XmlShort.type.isAssignableFrom(sImpl) && !XmlUnsignedByte.type.isAssignableFrom(sImpl)) {
            size = SchemaType.SIZE_INT;
        }

        return size;
    }

    private static int mathematicalSizeOfType(SchemaTypeImpl sImpl) {
        if (sImpl.getPrimitiveType().getBuiltinTypeCode() != SchemaType.BTC_DECIMAL) {
            return SchemaType.NOT_DECIMAL;
        }

        if (sImpl.getFacet(SchemaType.FACET_FRACTION_DIGITS) == null ||
            ((SimpleValue) sImpl.getFacet(SchemaType.FACET_FRACTION_DIGITS)).getBigIntegerValue().signum() != 0) {
            return SchemaType.SIZE_BIG_DECIMAL;
        }

        BigInteger min = null;
        BigInteger max = null;

        if (sImpl.getFacet(SchemaType.FACET_MIN_EXCLUSIVE) != null) {
            min = ((SimpleValue) sImpl.getFacet(SchemaType.FACET_MIN_EXCLUSIVE)).getBigIntegerValue(); // .add(BigInteger.ONE);
        }
        if (sImpl.getFacet(SchemaType.FACET_MIN_INCLUSIVE) != null) {
            min = ((SimpleValue) sImpl.getFacet(SchemaType.FACET_MIN_INCLUSIVE)).getBigIntegerValue();
        }
        if (sImpl.getFacet(SchemaType.FACET_MAX_INCLUSIVE) != null) {
            max = ((SimpleValue) sImpl.getFacet(SchemaType.FACET_MAX_INCLUSIVE)).getBigIntegerValue();
        }
        if (sImpl.getFacet(SchemaType.FACET_MAX_EXCLUSIVE) != null) {
            max = ((SimpleValue) sImpl.getFacet(SchemaType.FACET_MAX_EXCLUSIVE)).getBigIntegerValue(); // .subtract(BigInteger.ONE);
        }

        if (sImpl.getFacet(SchemaType.FACET_TOTAL_DIGITS) != null) {
            BigInteger peg = null;
            try {
                BigInteger totalDigits = ((SimpleValue) sImpl.getFacet(SchemaType.FACET_TOTAL_DIGITS)).getBigIntegerValue();

                switch (totalDigits.intValue()) {
                    case 0:
                    case 1:
                    case 2:
                        peg = BigInteger.valueOf(99L); // BYTE size
                        break;
                    case 3:
                    case 4:
                        peg = BigInteger.valueOf(9999L); // SHORT size
                        break;
                    case 5:
                    case 6:
                    case 7:
                    case 8:
                    case 9:
                        peg = BigInteger.valueOf(999999999L); // INT size
                        break;
                    case 10:
                    case 11:
                    case 12:
                    case 13:
                    case 14:
                    case 15:
                    case 16:
                    case 17:
                    case 18:
                        peg = BigInteger.valueOf(999999999999999999L); // LONG size
                        break;
                }
            } catch (XmlValueOutOfRangeException ignored) {
            }
            if (peg != null) {
                min = (min == null ? peg.negate() : min.max(peg.negate()));
                max = (max == null ? peg : max.min(peg));
            }
        }

        if (min != null && max != null) {
            // find the largest "absolute value" number that must be dealt with
            if (min.signum() < 0) {
                min = min.negate().subtract(BigInteger.ONE);
            }
            if (max.signum() < 0) {
                max = max.negate().subtract(BigInteger.ONE);
            }

            max = max.max(min);
            if (max.compareTo(BigInteger.valueOf(Byte.MAX_VALUE)) <= 0) {
                return SchemaType.SIZE_BYTE;
            }
            if (max.compareTo(BigInteger.valueOf(Short.MAX_VALUE)) <= 0) {
                return SchemaType.SIZE_SHORT;
            }
            if (max.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) <= 0) {
                return SchemaType.SIZE_INT;
            }
            if (max.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) <= 0) {
                return SchemaType.SIZE_LONG;
            }
        }

        return SchemaType.SIZE_BIG_INTEGER;
    }


    static void resolveFundamentalFacets(SchemaTypeImpl sImpl) {
        // deal with, isOrdered, isBounded, isFinite, isNumeric
        // also deal with
        switch (sImpl.getSimpleVariety()) {
            case SchemaType.ATOMIC:
                SchemaTypeImpl baseImpl = (SchemaTypeImpl) sImpl.getBaseType();
                sImpl.setOrdered(baseImpl.ordered());
                sImpl.setBounded(
                    (sImpl.getFacet(SchemaType.FACET_MIN_EXCLUSIVE) != null ||
                     sImpl.getFacet(SchemaType.FACET_MIN_INCLUSIVE) != null) &&
                    (sImpl.getFacet(SchemaType.FACET_MAX_INCLUSIVE) != null ||
                     sImpl.getFacet(SchemaType.FACET_MAX_EXCLUSIVE) != null));
                sImpl.setFinite(baseImpl.isFinite() ||
                                sImpl.isBounded() && isDiscreteType(sImpl));
                sImpl.setNumeric(baseImpl.isNumeric() ||
                                 isNumericPrimitive(sImpl.getPrimitiveType()));
                sImpl.setDecimalSize(decimalSizeOfType(sImpl));
                break;
            case SchemaType.UNION:
                SchemaType[] mTypes = sImpl.getUnionMemberTypes();
                int ordered = SchemaType.UNORDERED;
                boolean isBounded = true;
                boolean isFinite = true;
                boolean isNumeric = true;
                // ordered if any is ordered, bounded if all are bounded.
                for (SchemaType mType : mTypes) {
                    if (mType.ordered() != SchemaType.UNORDERED) {
                        ordered = SchemaType.PARTIAL_ORDER;
                    }
                    if (!mType.isBounded()) {
                        isBounded = false;
                    }
                    if (!mType.isFinite()) {
                        isFinite = false;
                    }
                    if (!mType.isNumeric()) {
                        isNumeric = false;
                    }
                }
                sImpl.setOrdered(ordered);
                sImpl.setBounded(isBounded);
                sImpl.setFinite(isFinite);
                sImpl.setNumeric(isNumeric);
                sImpl.setDecimalSize(SchemaType.NOT_DECIMAL);
                break;
            case SchemaType.LIST:
                sImpl.setOrdered(SchemaType.UNORDERED);
                // BUGBUG: the schema spec is wrong here: MIN_LENGTH is not needed, beause len >=0
                sImpl.setBounded(sImpl.getFacet(SchemaType.FACET_LENGTH) != null ||
                                 sImpl.getFacet(SchemaType.FACET_MAX_LENGTH) != null);
                // BUGBUG: the schema spec is wrong here: finite cardinality requires item type is finite
                sImpl.setFinite(sImpl.getListItemType().isFinite() && sImpl.isBounded());
                sImpl.setNumeric(false);
                sImpl.setDecimalSize(SchemaType.NOT_DECIMAL);
                break;
        }
    }

    private static class CodeForNameEntry {
        CodeForNameEntry(QName name, int code) {
            this.name = name;
            this.code = code;
        }

        public QName name;
        public int code;
    }

    private static final CodeForNameEntry[] facetCodes = {
        new CodeForNameEntry(QNameHelper.forLNS("length", "http://www.w3.org/2001/XMLSchema"), SchemaType.FACET_LENGTH),
        new CodeForNameEntry(QNameHelper.forLNS("minLength", "http://www.w3.org/2001/XMLSchema"), SchemaType.FACET_MIN_LENGTH),
        new CodeForNameEntry(QNameHelper.forLNS("maxLength", "http://www.w3.org/2001/XMLSchema"), SchemaType.FACET_MAX_LENGTH),
        new CodeForNameEntry(QNameHelper.forLNS("pattern", "http://www.w3.org/2001/XMLSchema"), SchemaType.FACET_PATTERN),
        new CodeForNameEntry(QNameHelper.forLNS("enumeration", "http://www.w3.org/2001/XMLSchema"), SchemaType.FACET_ENUMERATION),
        new CodeForNameEntry(QNameHelper.forLNS("whiteSpace", "http://www.w3.org/2001/XMLSchema"), SchemaType.FACET_WHITE_SPACE),
        new CodeForNameEntry(QNameHelper.forLNS("maxInclusive", "http://www.w3.org/2001/XMLSchema"), SchemaType.FACET_MAX_INCLUSIVE),
        new CodeForNameEntry(QNameHelper.forLNS("maxExclusive", "http://www.w3.org/2001/XMLSchema"), SchemaType.FACET_MAX_EXCLUSIVE),
        new CodeForNameEntry(QNameHelper.forLNS("minInclusive", "http://www.w3.org/2001/XMLSchema"), SchemaType.FACET_MIN_INCLUSIVE),
        new CodeForNameEntry(QNameHelper.forLNS("minExclusive", "http://www.w3.org/2001/XMLSchema"), SchemaType.FACET_MIN_EXCLUSIVE),
        new CodeForNameEntry(QNameHelper.forLNS("totalDigits", "http://www.w3.org/2001/XMLSchema"), SchemaType.FACET_TOTAL_DIGITS),
        new CodeForNameEntry(QNameHelper.forLNS("fractionDigits", "http://www.w3.org/2001/XMLSchema"), SchemaType.FACET_FRACTION_DIGITS),
    };

    private static final Map<QName, Integer> facetCodeMap = buildFacetCodeMap();

    private static Map<QName, Integer> buildFacetCodeMap() {
        Map<QName, Integer> result = new HashMap<>();
        for (CodeForNameEntry facetCode : facetCodes) {
            result.put(facetCode.name, facetCode.code);
        }
        return result;
    }

    private static int translateFacetCode(QName name) {
        return facetCodeMap.getOrDefault(name, -1);
    }
}
