/*   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.common.XMLChar;
import org.apache.xmlbeans.impl.schema.StscImporter.SchemaToProcess;
import org.apache.xmlbeans.impl.values.NamespaceContext;
import org.apache.xmlbeans.impl.values.XmlNonNegativeIntegerImpl;
import org.apache.xmlbeans.impl.values.XmlPositiveIntegerImpl;
import org.apache.xmlbeans.impl.values.XmlValueOutOfRangeException;
import org.apache.xmlbeans.impl.xb.xsdschema.*;
import org.apache.xmlbeans.impl.xb.xsdschema.RedefineDocument.Redefine;
import org.apache.xmlbeans.impl.xb.xsdschema.SchemaDocument.Schema;
import org.apache.xmlbeans.impl.xpath.XPath;
import org.apache.xmlbeans.soap.SOAPArrayType;

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

public class StscTranslator {
    private static final QName WSDL_ARRAYTYPE_NAME =
        QNameHelper.forLNS("arrayType", "http://schemas.xmlsoap.org/wsdl/");
    private static final String FORM_QUALIFIED = "qualified";

    public static void addAllDefinitions(SchemaToProcess[] schemasAndChameleons) {
        // Build all redefine objects
        List<RedefinitionHolder> redefinitions = new ArrayList<>();
        for (SchemaToProcess schemasAndChameleon : schemasAndChameleons) {
            List<SchemaToProcess> redefines = schemasAndChameleon.getRedefines();
            if (redefines != null) {
                List<Redefine> redefineObjects = schemasAndChameleon.getRedefineObjects();
                Iterator<SchemaToProcess> it = redefines.iterator();
                Iterator<Redefine> ito = redefineObjects.iterator();
                while (it.hasNext()) {
                    assert ito.hasNext() :
                        "The array of redefines and redefine objects have to have the same length";
                    redefinitions.add(new RedefinitionHolder(it.next(), ito.next()));
                }
            }
        }
        RedefinitionMaster globalRedefinitions = new RedefinitionMaster(redefinitions.toArray(new RedefinitionHolder[0]));

        StscState state = StscState.get();
        for (SchemaToProcess schemasAndChameleon : schemasAndChameleons) {
            Schema schema = schemasAndChameleon.getSchema();
            String givenTargetNamespace = schemasAndChameleon.getChameleonNamespace();

            // quick check for a few unsupported features

            if (schema.sizeOfNotationArray() > 0) {
                state.warning("Schema <notation> is not yet supported for this release.", XmlErrorCodes.UNSUPPORTED_FEATURE, schema.getNotationArray(0));
            }

            // figure namespace (taking into account chameleons)
            String targetNamespace = schema.getTargetNamespace();
            boolean chameleon = false;
            if (givenTargetNamespace != null && targetNamespace == null) {
                targetNamespace = givenTargetNamespace;
                chameleon = true;
            }
            if (targetNamespace == null) {
                targetNamespace = "";
            }

            //SchemaContainer container = null;
            if (targetNamespace.length() > 0 || !isEmptySchema(schema)) {
                state.registerContribution(targetNamespace, schema.documentProperties().getSourceName());
                state.addNewContainer(targetNamespace);
                //container = state.getContainer(targetNamespace);
            }

            List<Annotated> redefChain = new ArrayList<>();
            TopLevelComplexType[] complexTypes = schema.getComplexTypeArray();
            for (TopLevelComplexType complexType : complexTypes) {
                TopLevelComplexType type = complexType;
                TopLevelComplexType redef;
                // 1. Traverse the list of redefining Schemas putting all redefinitions
                // of this type in a List
                RedefinitionHolder[] rhArray = globalRedefinitions.getComplexTypeRedefinitions(
                    type.getName(), schemasAndChameleon);
                for (RedefinitionHolder redefinitionHolder : rhArray) {
                    // In error cases, some redefinitions were nulled out in the list
                    // which is why we need to perform this check
                    if (redefinitionHolder != null) {
                        redef = redefinitionHolder.redefineComplexType(type.getName());
                        assert redef != null; // This was already checked
                        redefChain.add(type);
                        type = redef;
                    }
                }

                SchemaTypeImpl t = translateGlobalComplexType(type, targetNamespace, chameleon, redefChain.size() > 0);
                state.addGlobalType(t, null);
                SchemaTypeImpl r;
                // 2. Traverse the List built in step 1 in reverse and add all the
                // types in it to the list of redefined types
                for (int k = redefChain.size() - 1; k >= 0; k--) {
                    redef = (TopLevelComplexType) redefChain.remove(k);
                    r = translateGlobalComplexType(redef, targetNamespace, chameleon, k > 0);
                    state.addGlobalType(r, t);
                    t = r;
                }
            }

            TopLevelSimpleType[] simpleTypes = schema.getSimpleTypeArray();
            for (TopLevelSimpleType simpleType : simpleTypes) {
                TopLevelSimpleType type = simpleType;
                TopLevelSimpleType redef;
                RedefinitionHolder[] rhArray = globalRedefinitions.getSimpleTypeRedefinitions(
                    type.getName(), schemasAndChameleon);
                for (RedefinitionHolder redefinitionHolder : rhArray) {
                    // In error cases, some redefinitions were nulled out in the list
                    // which is why we need to perform this check
                    if (redefinitionHolder != null) {
                        redef = redefinitionHolder.redefineSimpleType(type.getName());
                        assert redef != null; // This was already checked
                        redefChain.add(type);
                        type = redef;
                    }
                }

                SchemaTypeImpl t = translateGlobalSimpleType(type, targetNamespace, chameleon, redefChain.size() > 0);
                state.addGlobalType(t, null);
                SchemaTypeImpl r;
                for (int k = redefChain.size() - 1; k >= 0; k--) {
                    redef = (TopLevelSimpleType) redefChain.remove(k);
                    r = translateGlobalSimpleType(redef, targetNamespace, chameleon, k > 0);
                    state.addGlobalType(r, t);
                    t = r;
                }
            }

            TopLevelElement[] elements = schema.getElementArray();
            for (TopLevelElement element : elements) {
                state.addDocumentType(translateDocumentType(element, targetNamespace, chameleon), QNameHelper.forLNS(element.getName(), targetNamespace));
            }

            TopLevelAttribute[] attributes = schema.getAttributeArray();
            for (TopLevelAttribute attribute : attributes) {
                state.addAttributeType(translateAttributeType(attribute, targetNamespace, chameleon), QNameHelper.forLNS(attribute.getName(), targetNamespace));
            }

            NamedGroup[] modelgroups = schema.getGroupArray();
            for (NamedGroup group : modelgroups) {
                NamedGroup redef;
                RedefinitionHolder[] rhArray = globalRedefinitions.getModelGroupRedefinitions(
                    group.getName(), schemasAndChameleon);
                for (RedefinitionHolder redefinitionHolder : rhArray) {
                    // In error cases, some redefinitions were nulled out in the list
                    // which is why we need to perform this check
                    if (redefinitionHolder != null) {
                        redef = redefinitionHolder.redefineModelGroup(group.getName());
                        assert redef != null; // This was already checked
                        redefChain.add(group);
                        group = redef;
                    }
                }

                SchemaModelGroupImpl g = translateModelGroup(group, targetNamespace, chameleon, redefChain.size() > 0);
                state.addModelGroup(g, null);
                SchemaModelGroupImpl r;
                for (int k = redefChain.size() - 1; k >= 0; k--) {
                    redef = (NamedGroup) redefChain.remove(k);
                    r = translateModelGroup(redef, targetNamespace, chameleon, k > 0);
                    state.addModelGroup(r, g);
                    g = r;
                }
            }

            NamedAttributeGroup[] attrgroups = schema.getAttributeGroupArray();
            for (NamedAttributeGroup group : attrgroups) {
                NamedAttributeGroup redef;
                RedefinitionHolder[] rhArray = globalRedefinitions.getAttributeGroupRedefinitions(
                    group.getName(), schemasAndChameleon);
                for (RedefinitionHolder redefinitionHolder : rhArray) {
                    // In error cases, some redefinitions were nulled out in the list
                    // which is why we need to perform this check
                    if (redefinitionHolder != null) {
                        redef = redefinitionHolder.redefineAttributeGroup(group.getName());
                        assert redef != null; // This was already checked
                        redefChain.add(group);
                        group = redef;
                    }
                }

                SchemaAttributeGroupImpl g = translateAttributeGroup(group, targetNamespace, chameleon, redefChain.size() > 0);
                state.addAttributeGroup(g, null);
                SchemaAttributeGroupImpl r;
                for (int k = redefChain.size() - 1; k >= 0; k--) {
                    redef = (NamedAttributeGroup) redefChain.remove(k);
                    r = translateAttributeGroup(redef, targetNamespace, chameleon, k > 0);
                    state.addAttributeGroup(r, g);
                    g = r;
                }
            }

            AnnotationDocument.Annotation[] annotations = schema.getAnnotationArray();
            for (AnnotationDocument.Annotation annotation : annotations) {
                state.addAnnotation(SchemaAnnotationImpl.getAnnotation(state.getContainer(targetNamespace), schema, annotation), targetNamespace);
            }
        }

        for (Object redefinition : redefinitions) {
            ((RedefinitionHolder) redefinition).complainAboutMissingDefinitions();
        }
    }

    private static class RedefinitionHolder {
        // record redefinitions
        private Map<String, TopLevelSimpleType> stRedefinitions = Collections.emptyMap();
        private Map<String, TopLevelComplexType> ctRedefinitions = Collections.emptyMap();
        private Map<String, NamedAttributeGroup> agRedefinitions = Collections.emptyMap();
        private Map<String, NamedGroup> mgRedefinitions = Collections.emptyMap();
        private String schemaLocation = "";
        private final SchemaToProcess schemaRedefined;

        // first build set of redefined components
        RedefinitionHolder(SchemaToProcess schemaToProcess, Redefine redefine) {
            schemaRedefined = schemaToProcess;
            if (redefine != null) {
                StscState state = StscState.get();

                stRedefinitions = new HashMap<>();
                ctRedefinitions = new HashMap<>();
                agRedefinitions = new HashMap<>();
                mgRedefinitions = new HashMap<>();
                if (redefine.getSchemaLocation() != null) {
                    schemaLocation = redefine.getSchemaLocation();
                }

                TopLevelComplexType[] complexTypes = redefine.getComplexTypeArray();
                for (TopLevelComplexType complexType : complexTypes) {
                    if (complexType.getName() != null) {
                        // KHK: which rule? sch-props-correct.2?
                        if (ctRedefinitions.containsKey(complexType.getName())) {
                            state.error("Duplicate type redefinition: " + complexType.getName(), XmlErrorCodes.DUPLICATE_GLOBAL_TYPE, null);
                        } else {
                            ctRedefinitions.put(complexType.getName(), complexType);
                        }
                    }
                }

                TopLevelSimpleType[] simpleTypes = redefine.getSimpleTypeArray();
                for (TopLevelSimpleType simpleType : simpleTypes) {
                    if (simpleType.getName() != null) {
                        if (stRedefinitions.containsKey(simpleType.getName())) {
                            state.error("Duplicate type redefinition: " + simpleType.getName(), XmlErrorCodes.DUPLICATE_GLOBAL_TYPE, null);
                        } else {
                            stRedefinitions.put(simpleType.getName(), simpleType);
                        }
                    }
                }

                NamedGroup[] modelgroups = redefine.getGroupArray();
                for (NamedGroup modelgroup : modelgroups) {
                    if (modelgroup.getName() != null) {
                        if (mgRedefinitions.containsKey(modelgroup.getName())) {
                            state.error("Duplicate type redefinition: " + modelgroup.getName(), XmlErrorCodes.DUPLICATE_GLOBAL_TYPE, null);
                        } else {
                            mgRedefinitions.put(modelgroup.getName(), modelgroup);
                        }
                    }
                }

                NamedAttributeGroup[] attrgroups = redefine.getAttributeGroupArray();
                for (NamedAttributeGroup attrgroup : attrgroups) {
                    if (attrgroup.getName() != null) {
                        if (agRedefinitions.containsKey(attrgroup.getName())) {
                            state.error("Duplicate type redefinition: " + attrgroup.getName(), XmlErrorCodes.DUPLICATE_GLOBAL_TYPE, null);
                        } else {
                            agRedefinitions.put(attrgroup.getName(), attrgroup);
                        }
                    }
                }
            }
        }

        public TopLevelSimpleType redefineSimpleType(String name) {
            return name == null || !stRedefinitions.containsKey(name) ? null : stRedefinitions.remove(name);
        }

        public TopLevelComplexType redefineComplexType(String name) {
            return name == null || !ctRedefinitions.containsKey(name) ? null : ctRedefinitions.remove(name);
        }

        public NamedGroup redefineModelGroup(String name) {
            return name == null || !mgRedefinitions.containsKey(name) ? null : mgRedefinitions.remove(name);
        }

        public NamedAttributeGroup redefineAttributeGroup(String name) {
            return name == null || !agRedefinitions.containsKey(name) ? null : agRedefinitions.remove(name);
        }

        public void complainAboutMissingDefinitions() {
            if (stRedefinitions.isEmpty() && ctRedefinitions.isEmpty() &&
                agRedefinitions.isEmpty() && mgRedefinitions.isEmpty()) {
                return;
            }

            StscState state = StscState.get();

            for (String name : stRedefinitions.keySet()) {
                state.error("Redefined simple type " + name + " not found in " + schemaLocation, XmlErrorCodes.GENERIC_ERROR, stRedefinitions.get(name));
            }

            for (String name : ctRedefinitions.keySet()) {
                state.error("Redefined complex type " + name + " not found in " + schemaLocation, XmlErrorCodes.GENERIC_ERROR, ctRedefinitions.get(name));
            }

            for (String name : agRedefinitions.keySet()) {
                state.error("Redefined attribute group " + name + " not found in " + schemaLocation, XmlErrorCodes.GENERIC_ERROR, agRedefinitions.get(name));
            }

            for (String name : mgRedefinitions.keySet()) {
                state.error("Redefined model group " + name + " not found in " + schemaLocation, XmlErrorCodes.GENERIC_ERROR, mgRedefinitions.get(name));
            }
        }
    }

    /**
     * This is used to aggregate all redefinitions for a specific component name.
     * The idea is to record the list of &lt;redefine%gt; sections that could
     * potentially redefine this component. When the list of actual redefinitions
     * is requested, the potential redefinitions are first filtered based on
     * accessibilty of the schema currently being processed from the redefining Schemas
     * and then topologically sorted based on the inclusion relationship to
     * ensure that redefinitions are applied in the right order.
     */
    private static class RedefinitionMaster {
        private static final short SIMPLE_TYPE = 1;
        private static final short COMPLEX_TYPE = 2;
        private static final short MODEL_GROUP = 3;
        private static final short ATTRIBUTE_GROUP = 4;

        // record redefinitions
        private Map<String, List<RedefinitionHolder>> stRedefinitions = Collections.emptyMap();
        private Map<String, List<RedefinitionHolder>> ctRedefinitions = Collections.emptyMap();
        private Map<String, List<RedefinitionHolder>> agRedefinitions = Collections.emptyMap();
        private Map<String, List<RedefinitionHolder>> mgRedefinitions = Collections.emptyMap();
        private static final RedefinitionHolder[] EMPTY_REDEFINTION_HOLDER_ARRAY =
            new RedefinitionHolder[0];


        RedefinitionMaster(RedefinitionHolder[] redefHolders) {
            if (redefHolders.length > 0) {
                stRedefinitions = new HashMap<>();
                ctRedefinitions = new HashMap<>();
                agRedefinitions = new HashMap<>();
                mgRedefinitions = new HashMap<>();

                for (RedefinitionHolder redefHolder : redefHolders) {
                    for (String key : redefHolder.stRedefinitions.keySet()) {
                        stRedefinitions.computeIfAbsent(key, k -> new ArrayList<>()).add(redefHolder);
                    }
                    for (String key : redefHolder.ctRedefinitions.keySet()) {
                        ctRedefinitions.computeIfAbsent(key, k -> new ArrayList<>()).add(redefHolder);
                    }
                    for (String key : redefHolder.agRedefinitions.keySet()) {
                        agRedefinitions.computeIfAbsent(key, k -> new ArrayList<>()).add(redefHolder);
                    }
                    for (String key : redefHolder.mgRedefinitions.keySet()) {
                        mgRedefinitions.computeIfAbsent(key, k -> new ArrayList<>()).add(redefHolder);
                    }
                }
            }
        }

        RedefinitionHolder[] getSimpleTypeRedefinitions(String name,
                                                        SchemaToProcess schema) {
            List<RedefinitionHolder> redefines = stRedefinitions.get(name);
            if (redefines == null) {
                return EMPTY_REDEFINTION_HOLDER_ARRAY;
            }
            return doTopologicalSort(redefines, schema, name, SIMPLE_TYPE);
        }

        RedefinitionHolder[] getComplexTypeRedefinitions(String name,
                                                         SchemaToProcess schema) {
            List<RedefinitionHolder> redefines = ctRedefinitions.get(name);
            if (redefines == null) {
                return EMPTY_REDEFINTION_HOLDER_ARRAY;
            }
            return doTopologicalSort(redefines, schema, name, COMPLEX_TYPE);
        }

        RedefinitionHolder[] getAttributeGroupRedefinitions(String name,
                                                            SchemaToProcess schema) {
            List<RedefinitionHolder> redefines = agRedefinitions.get(name);
            if (redefines == null) {
                return EMPTY_REDEFINTION_HOLDER_ARRAY;
            }
            return doTopologicalSort(redefines, schema, name, ATTRIBUTE_GROUP);
        }

        RedefinitionHolder[] getModelGroupRedefinitions(String name,
                                                        SchemaToProcess schema) {
            List<RedefinitionHolder> redefines = mgRedefinitions.get(name);
            if (redefines == null) {
                return EMPTY_REDEFINTION_HOLDER_ARRAY;
            }
            return doTopologicalSort(redefines, schema, name, MODEL_GROUP);
        }

        private RedefinitionHolder[] doTopologicalSort(List<RedefinitionHolder> genericRedefines,
                                                       SchemaToProcess schema, String name, short componentType) {
            // We have a list of files that redefine this name
            // Filter out the ones that don't redefine this file in particular
            RedefinitionHolder[] specificRedefines = new RedefinitionHolder[genericRedefines.size()];
            int n = 0;
            for (RedefinitionHolder h : genericRedefines) {
                if (h.schemaRedefined == schema ||
                    h.schemaRedefined.indirectIncludes(schema)) {
                    specificRedefines[n++] = h;
                }
            }
            // Now we have the list of files that specifically redefine the
            // name in the file that we are looking for
            // Sort this list into topological order to get the right order
            // and figure out if there are multiple redefinitions involved
            RedefinitionHolder[] sortedRedefines = new RedefinitionHolder[n];
            int[] numberOfIncludes = new int[n];
            // Just count the number of inclusions for each redefinition
            for (int i = 0; i < n - 1; i++) {
                RedefinitionHolder current = specificRedefines[i];
                for (int j = i + 1; j < n; j++) {
                    if (current.schemaRedefined.indirectIncludes(specificRedefines[j].schemaRedefined)) {
                        numberOfIncludes[i]++;
                    }
                    if (specificRedefines[j].schemaRedefined.indirectIncludes(current.schemaRedefined)) {
                        numberOfIncludes[j]++;
                    }
                }
            }
            // Eliminate members one by one, according to the number of schemas
            // that they include, to complete the sort
            int position = 0;
            boolean errorReported = false;
            while (position < n) {
                int index = -1;
                for (int i = 0; i < numberOfIncludes.length; i++) {
                    if (numberOfIncludes[i] == 0) {
                        if (index < 0) {
                            index = i;
                        }
                    }
                }
                if (index < 0) {
                    // Error! Circular redefinition
                    if (!errorReported) {
                        StringBuilder fileNameList = new StringBuilder();
                        XmlObject location = null;
                        for (int i = 0; i < n; i++) {
                            if (specificRedefines[i] != null) {
                                fileNameList.append(specificRedefines[i].schemaLocation).
                                    append(',').append(' ');
                                if (location == null) {
                                    location = locationFromRedefinitionAndCode(
                                        specificRedefines[i], name, componentType);
                                }
                            }
                        }
                        StscState.get().error("Detected circular redefinition of " +
                                              componentNameFromCode(componentType) + " \"" + name +
                                              "\"; Files involved: " + fileNameList.toString(),
                            XmlErrorCodes.GENERIC_ERROR, location);
                        errorReported = true;
                    }
                    int min = n;
                    for (int i = 0; i < n; i++) {
                        if (numberOfIncludes[i] > 0 && numberOfIncludes[i] < min) {
                            min = numberOfIncludes[i];
                            index = i;
                        }
                    }
                    numberOfIncludes[index]--;
                } else {
                    assert specificRedefines[index] != null;
                    sortedRedefines[position++] = specificRedefines[index];
                    for (int i = 0; i < n; i++) {
                        if (specificRedefines[i] != null &&
                            specificRedefines[i].schemaRedefined.
                                indirectIncludes(specificRedefines[index].
                                    schemaRedefined)) {
                            numberOfIncludes[i]--;
                        }
                    }
                    specificRedefines[index] = null;
                    numberOfIncludes[index]--;
                }
            }
            // Nice. We now have all the redefinitions of this name in the list
            // Each one has to transitively redefine the one before, otherwise
            // it means we are attepting two different redefinitions for the same
            // component
            for (int i = 1; i < n; i++) {
                // Find the previous index with non-null Schema
                // Since i is never 0, such index always exists
                int j;
                for (j = i - 1; j >= 0; j--) {
                    if (sortedRedefines[j] != null) {
                        break;
                    }
                }

                if (!sortedRedefines[i].schemaRedefined.indirectIncludes(
                    sortedRedefines[j].schemaRedefined)) {
                    StscState.get().error("Detected multiple redefinitions of " +
                                          componentNameFromCode(componentType) +
                                          " \"" + name + "\"; Files involved: " +
                                          sortedRedefines[j].schemaRedefined.getSourceName() + ", " +
                                          sortedRedefines[i].schemaRedefined.getSourceName(),
                        XmlErrorCodes.DUPLICATE_GLOBAL_TYPE,
                        locationFromRedefinitionAndCode(sortedRedefines[i], name, componentType));
                    // Ignore this redefinition
                    switch (componentType) {
                        case SIMPLE_TYPE:
                            sortedRedefines[i].redefineSimpleType(name);
                            break;
                        case COMPLEX_TYPE:
                            sortedRedefines[i].redefineComplexType(name);
                            break;
                        case ATTRIBUTE_GROUP:
                            sortedRedefines[i].redefineAttributeGroup(name);
                            break;
                        case MODEL_GROUP:
                            sortedRedefines[i].redefineModelGroup(name);
                            break;
                    }
                    sortedRedefines[i] = null;
                }
            }

            return sortedRedefines;
        }

        private String componentNameFromCode(short code) {
            String componentName;
            switch (code) {
                case SIMPLE_TYPE:
                    componentName = "simple type";
                    break;
                case COMPLEX_TYPE:
                    componentName = "complex type";
                    break;
                case MODEL_GROUP:
                    componentName = "model group";
                    break;
                case ATTRIBUTE_GROUP:
                    componentName = "attribute group";
                    break;
                default:
                    componentName = "";
            }
            return componentName;
        }

        private XmlObject locationFromRedefinitionAndCode(RedefinitionHolder redefinition,
                                                          String name, short code) {
            XmlObject location;
            switch (code) {
                case SIMPLE_TYPE:
                    location = redefinition.stRedefinitions.get(name);
                    break;
                case COMPLEX_TYPE:
                    location = redefinition.ctRedefinitions.get(name);
                    break;
                case MODEL_GROUP:
                    location = redefinition.mgRedefinitions.get(name);
                    break;
                case ATTRIBUTE_GROUP:
                    location = redefinition.agRedefinitions.get(name);
                    break;
                default:
                    location = null;
            }
            return location;
        }
    }

    private static String findFilename(XmlObject xobj) {
        return StscState.get().sourceNameForUri(xobj.documentProperties().getSourceName());
    }

    private static SchemaTypeImpl translateDocumentType(TopLevelElement xsdType, String targetNamespace, boolean chameleon) {
        SchemaTypeImpl sType = new SchemaTypeImpl(StscState.get().getContainer(targetNamespace));

        sType.setDocumentType(true);
        sType.setParseContext(xsdType, targetNamespace, chameleon, null, null, false);
        sType.setFilename(findFilename(xsdType));

        return sType;
    }

    private static SchemaTypeImpl translateAttributeType(TopLevelAttribute xsdType, String targetNamespace, boolean chameleon) {
        SchemaTypeImpl sType = new SchemaTypeImpl(StscState.get().getContainer(targetNamespace));

        sType.setAttributeType(true);
        sType.setParseContext(xsdType, targetNamespace, chameleon, null, null, false);
        sType.setFilename(findFilename(xsdType));

        return sType;
    }

    private static SchemaTypeImpl translateGlobalComplexType(TopLevelComplexType xsdType, String targetNamespace, boolean chameleon, boolean redefinition) {
        StscState state = StscState.get();

        String localname = xsdType.getName();
        if (localname == null) {
            state.error(XmlErrorCodes.MISSING_NAME, new Object[]{"global type"}, xsdType);
            // recovery: ignore unnamed types.
            return null;
        }
        if (!XMLChar.isValidNCName(localname)) {
            state.error(XmlErrorCodes.INVALID_VALUE, new Object[]{localname, "name"}, xsdType.xgetName());
            // recovery: let the name go through anyway.
        }

        QName name = QNameHelper.forLNS(localname, targetNamespace);

        if (isReservedTypeName(name)) {
            state.warning(XmlErrorCodes.RESERVED_TYPE_NAME, new Object[]{QNameHelper.pretty(name)}, xsdType);
            return null;
        }
        // System.err.println("Recording type " + QNameHelper.pretty(name));

        SchemaTypeImpl sType = new SchemaTypeImpl(state.getContainer(targetNamespace));
        sType.setParseContext(xsdType, targetNamespace, chameleon, null, null, redefinition);
        sType.setFilename(findFilename(xsdType));
        sType.setName(QNameHelper.forLNS(localname, targetNamespace));
        sType.setAnnotation(SchemaAnnotationImpl.getAnnotation(state.getContainer(targetNamespace), xsdType));
        sType.setUserData(getUserData(xsdType));
        return sType;
    }

    private static SchemaTypeImpl translateGlobalSimpleType(TopLevelSimpleType xsdType, String targetNamespace, boolean chameleon, boolean redefinition) {
        StscState state = StscState.get();

        String localname = xsdType.getName();
        if (localname == null) {
            state.error(XmlErrorCodes.MISSING_NAME, new Object[]{"global type"}, xsdType);
            // recovery: ignore unnamed types.
            return null;
        }
        if (!XMLChar.isValidNCName(localname)) {
            state.error(XmlErrorCodes.INVALID_VALUE, new Object[]{localname, "name"}, xsdType.xgetName());
            // recovery: let the name go through anyway.
        }

        QName name = QNameHelper.forLNS(localname, targetNamespace);

        if (isReservedTypeName(name)) {
            state.warning(XmlErrorCodes.RESERVED_TYPE_NAME, new Object[]{QNameHelper.pretty(name)}, xsdType);
            return null;
        }
        // System.err.println("Recording type " + QNameHelper.pretty(name));

        SchemaTypeImpl sType = new SchemaTypeImpl(state.getContainer(targetNamespace));
        sType.setSimpleType(true);
        sType.setParseContext(xsdType, targetNamespace, chameleon, null, null, redefinition);
        sType.setFilename(findFilename(xsdType));
        sType.setName(name);
        sType.setAnnotation(SchemaAnnotationImpl.getAnnotation(state.getContainer(targetNamespace), xsdType));
        sType.setUserData(getUserData(xsdType));
        return sType;
    }

    /*package*/
    static SchemaTypeImpl translateAnonymousSimpleType(SimpleType typedef,
                                                       String targetNamespace, boolean chameleon, String elemFormDefault,
                                                       String attFormDefault, List<SchemaType> anonymousTypes, SchemaType outerType) {
        StscState state = StscState.get();
        SchemaTypeImpl sType = new SchemaTypeImpl(state.getContainer(targetNamespace));
        sType.setSimpleType(true);
        sType.setParseContext(typedef, targetNamespace, chameleon,
            elemFormDefault, attFormDefault, false);
        sType.setOuterSchemaTypeRef(outerType.getRef());
        sType.setAnnotation(SchemaAnnotationImpl.getAnnotation(state.getContainer(targetNamespace), typedef));
        sType.setUserData(getUserData(typedef));
        anonymousTypes.add(sType);
        return sType;
    }

    static FormChoice findElementFormDefault(XmlObject obj) {
        XmlCursor cur = obj.newCursor();
        while (cur.getObject().schemaType() != Schema.type) {
            if (!cur.toParent()) {
                return null;
            }
        }
        return ((Schema) cur.getObject()).xgetElementFormDefault();
    }

    public static boolean uriMatch(String s1, String s2) {
        if (s1 == null) {
            return s2 == null || s2.equals("");
        }
        if (s2 == null) {
            return s1.equals("");
        }
        return s1.equals(s2);
    }

    public static void copyGlobalElementToLocalElement(SchemaGlobalElement referenced, SchemaLocalElementImpl target) {

        target.setNameAndTypeRef(referenced.getName(), referenced.getType().getRef());
        target.setNillable(referenced.isNillable());
        target.setDefault(referenced.getDefaultText(), referenced.isFixed(), ((SchemaGlobalElementImpl) referenced).getParseObject());
        target.setIdentityConstraints(((SchemaLocalElementImpl) referenced).getIdentityConstraintRefs());
        target.setBlock(referenced.blockExtension(), referenced.blockRestriction(), referenced.blockSubstitution());
        target.setAbstract(referenced.isAbstract());
        target.setTransitionRules(((SchemaParticle) referenced).acceptedStartNames(),
            ((SchemaParticle) referenced).isSkippable());
        target.setAnnotation(referenced.getAnnotation());
    }

    public static void copyGlobalAttributeToLocalAttribute(SchemaGlobalAttributeImpl referenced, SchemaLocalAttributeImpl target) {
        target.init(
            referenced.getName(), referenced.getTypeRef(), referenced.getUse(),
            referenced.getDefaultText(),
            referenced.getParseObject(), referenced._defaultValue,
            referenced.isFixed(),
            referenced.getWSDLArrayType(),
            referenced.getAnnotation(), null);
    }

    /**
     * Translates a local or global schema element.
     */
    // check rule 3.3.3
    // http://www.w3c.org/TR/#section-Constraints-on-XML-Representations-of-Element-Declarations
    public static SchemaLocalElementImpl translateElement(
        Element xsdElt, String targetNamespace, boolean chameleon,
        String elemFormDefault, String attFormDefault,
        List<SchemaType> anonymousTypes, SchemaType outerType) {
        StscState state = StscState.get();

        SchemaTypeImpl sgHead = null;

        // translate sg head
        if (xsdElt.isSetSubstitutionGroup()) {
            sgHead = state.findDocumentType(xsdElt.getSubstitutionGroup(),
                ((SchemaTypeImpl) outerType).getChameleonNamespace(), targetNamespace);

            if (sgHead != null) {
                StscResolver.resolveType(sgHead);
            }
        }

        String name = xsdElt.getName();
        QName ref = xsdElt.getRef();


        if (ref != null && name != null) {
            // if (name.equals(ref.getLocalPart()) && uriMatch(targetNamespace, ref.getNamespaceURI()))
            //     state.warning("Element " + name + " specifies both a ref and a name", XmlErrorCodes.ELEMENT_EXTRA_REF, xsdElt.xgetRef());
            // else
            state.error(XmlErrorCodes.SCHEMA_ELEM$REF_OR_NAME_HAS_BOTH, new Object[]{name}, xsdElt.xgetRef());
            // ignore name
            name = null;
        }
        if (ref == null && name == null) {
            state.error(XmlErrorCodes.SCHEMA_ELEM$REF_OR_NAME_HAS_NEITHER, null, xsdElt);
            // recovery: ignore this element
            return null;
        }
        if (name != null && !XMLChar.isValidNCName(name)) {
            state.error(XmlErrorCodes.INVALID_VALUE, new Object[]{name, "name"}, xsdElt.xgetName());
            // recovery: let the name go through anyway.
        }

        if (ref != null) {
            if (xsdElt.getType() != null) {
                state.error(XmlErrorCodes.SCHEMA_ELEM$REF_FEATURES, new Object[]{"type"}, xsdElt.xgetType());
                // recovery: let the name go through anyway.
            }

            if (xsdElt.getSimpleType() != null) {
                state.error(XmlErrorCodes.SCHEMA_ELEM$REF_FEATURES, new Object[]{"<simpleType>"}, xsdElt.getSimpleType());
                // recovery: let the name go through anyway.
            }

            if (xsdElt.getComplexType() != null) {
                state.error(XmlErrorCodes.SCHEMA_ELEM$REF_FEATURES, new Object[]{"<complexType>"}, xsdElt.getComplexType());
                // recovery: let the name go through anyway.
            }

            if (xsdElt.getForm() != null) {
                state.error(XmlErrorCodes.SCHEMA_ELEM$REF_FEATURES, new Object[]{"form"}, xsdElt.xgetForm());
                // recovery: let the name go through anyway.
            }

            if (xsdElt.sizeOfKeyArray() > 0) {
                state.warning(XmlErrorCodes.SCHEMA_ELEM$REF_FEATURES, new Object[]{"<key>"}, xsdElt);
                // recovery: ignore
            }

            if (xsdElt.sizeOfKeyrefArray() > 0) {
                state.warning(XmlErrorCodes.SCHEMA_ELEM$REF_FEATURES, new Object[]{"<keyref>"}, xsdElt);
                // recovery: ignore
            }

            if (xsdElt.sizeOfUniqueArray() > 0) {
                state.warning(XmlErrorCodes.SCHEMA_ELEM$REF_FEATURES, new Object[]{"<unique>"}, xsdElt);
                // recovery: ignore
            }

            if (xsdElt.isSetDefault()) {
                state.warning(XmlErrorCodes.SCHEMA_ELEM$REF_FEATURES, new Object[]{"default"}, xsdElt.xgetDefault());
                // recovery: ignore
            }

            if (xsdElt.isSetFixed()) {
                state.warning(XmlErrorCodes.SCHEMA_ELEM$REF_FEATURES, new Object[]{"fixed"}, xsdElt.xgetFixed());
                // recovery: ignore
            }

            if (xsdElt.isSetBlock()) {
                state.warning(XmlErrorCodes.SCHEMA_ELEM$REF_FEATURES, new Object[]{"block"}, xsdElt.xgetBlock());
                // recovery: ignore
            }

            if (xsdElt.isSetNillable()) {
                state.warning(XmlErrorCodes.SCHEMA_ELEM$REF_FEATURES, new Object[]{"nillable"}, xsdElt.xgetNillable());
                // recovery: ignore
            }

            assert (xsdElt instanceof LocalElement);
            SchemaGlobalElement referenced = state.findGlobalElement(ref, chameleon ? targetNamespace : null, targetNamespace);
            if (referenced == null) {
                state.notFoundError(ref, SchemaType.ELEMENT, xsdElt.xgetRef(), true);
                // recovery: ignore this element
                return null;
            }
            SchemaLocalElementImpl target = new SchemaLocalElementImpl();
            target.setParticleType(SchemaParticle.ELEMENT);
            target.setUserData(getUserData(xsdElt));
            copyGlobalElementToLocalElement(referenced, target);
            return target;
        }

        QName qname;
        SchemaLocalElementImpl impl;
        SchemaType sType = null;

        if (xsdElt instanceof LocalElement) {
            impl = new SchemaLocalElementImpl();
            boolean qualified;
            FormChoice form = xsdElt.xgetForm();
            if (form != null) {
                qualified = form.getStringValue().equals(FORM_QUALIFIED);
            } else if (elemFormDefault != null) {
                qualified = elemFormDefault.equals(FORM_QUALIFIED);
            } else {
                form = findElementFormDefault(xsdElt);
                qualified = form != null && form.getStringValue().equals(FORM_QUALIFIED);
            }

            qname = qualified ? QNameHelper.forLNS(name, targetNamespace) : QNameHelper.forLN(name);
        } else {
            SchemaGlobalElementImpl gelt = new SchemaGlobalElementImpl(state.getContainer(targetNamespace));
            impl = gelt;

            // Set subst group head
            if (sgHead != null) {
                SchemaGlobalElementImpl head = state.findGlobalElement(xsdElt.getSubstitutionGroup(), chameleon ? targetNamespace : null, targetNamespace);
                if (head != null) {
                    gelt.setSubstitutionGroup(head.getRef());
                }
            }

            // Set subst group members
            qname = QNameHelper.forLNS(name, targetNamespace);
            SchemaTypeImpl docType = (SchemaTypeImpl) outerType;

            QName[] sgMembers = docType.getSubstitutionGroupMembers();
            QNameSetBuilder transitionRules = new QNameSetBuilder();
            transitionRules.add(qname);

            for (QName sgMember : sgMembers) {
                gelt.addSubstitutionGroupMember(sgMember);
                transitionRules.add(sgMember);
            }

            impl.setTransitionRules(QNameSet.forSpecification(transitionRules), false);
            impl.setTransitionNotes(QNameSet.EMPTY, true);

            boolean finalExt = false;
            boolean finalRest = false;
            Object ds = xsdElt.getFinal();
            if (ds != null) {
                if (ds instanceof String && ds.equals("#all")) {
                    // #ALL value
                    finalExt = finalRest = true;
                } else if (ds instanceof List) {
                    List<?> dsList = (List<?>) ds;
                    if (dsList.contains("extension")) {
                        finalExt = true;
                    }
                    if (dsList.contains("restriction")) {
                        finalRest = true;
                    }
                }
            }

            gelt.setFinal(finalExt, finalRest);
            gelt.setAbstract(xsdElt.getAbstract());
            gelt.setFilename(findFilename(xsdElt));
            gelt.setParseContext(xsdElt, targetNamespace, chameleon);
        }

        SchemaAnnotationImpl ann = SchemaAnnotationImpl.getAnnotation(state.getContainer(targetNamespace), xsdElt);
        impl.setAnnotation(ann);
        impl.setUserData(getUserData(xsdElt));
        if (xsdElt.getType() != null) {
            sType = state.findGlobalType(xsdElt.getType(), chameleon ? targetNamespace : null, targetNamespace);
            if (sType == null) {
                state.notFoundError(xsdElt.getType(), SchemaType.TYPE, xsdElt.xgetType(), true);
            }
        }

        boolean simpleTypedef = false;
        Annotated typedef = xsdElt.getComplexType();
        if (typedef == null) {
            typedef = xsdElt.getSimpleType();
            simpleTypedef = true;
        }

        if ((sType != null) && typedef != null) {
            state.error(XmlErrorCodes.SCHEMA_ELEM$TYPE_ATTR_OR_NESTED_TYPE, null, typedef);
            typedef = null;
        }

        if (typedef != null) {
            Object[] grps = state.getCurrentProcessing();
            QName[] context = new QName[grps.length];
            for (int i = 0; i < context.length; i++) {
                if (grps[i] instanceof SchemaModelGroupImpl) {
                    context[i] = ((SchemaModelGroupImpl) grps[i]).getName();
                }
            }
            SchemaType repeat = checkRecursiveGroupReference(context, qname, (SchemaTypeImpl) outerType);
            if (repeat != null) {
                sType = repeat;
            } else {
                SchemaTypeImpl sTypeImpl = new SchemaTypeImpl(state.getContainer(targetNamespace));
                sType = sTypeImpl;
                sTypeImpl.setContainerField(impl);
                sTypeImpl.setOuterSchemaTypeRef(outerType == null ? null : outerType.getRef());
                sTypeImpl.setGroupReferenceContext(context);
                // leave the anonymous type unresolved: it will be resolved later.
                anonymousTypes.add(sType);
                sTypeImpl.setSimpleType(simpleTypedef);
                sTypeImpl.setParseContext(typedef, targetNamespace, chameleon,
                    elemFormDefault, attFormDefault, false);
                sTypeImpl.setAnnotation(SchemaAnnotationImpl.getAnnotation(state.getContainer(targetNamespace), typedef));
                sTypeImpl.setUserData(getUserData(typedef));
            }
        }

        if (sType == null) {
            // type may inherit from substitution group head
            if (sgHead != null) {
                SchemaGlobalElement head = state.findGlobalElement(xsdElt.getSubstitutionGroup(), chameleon ? targetNamespace : null, targetNamespace);

                // Bug - Do I need to copy the type if it's anonymous?
                // If element does not exist, error has already been reported
                if (head != null) {
                    sType = head.getType();
                }
            }

        }


        if (sType == null) {
            sType = BuiltinSchemaTypeSystem.ST_ANY_TYPE;
        }

        SOAPArrayType wat = null;
        XmlCursor c = xsdElt.newCursor();
        String arrayType = c.getAttributeText(WSDL_ARRAYTYPE_NAME);
        c.dispose();
        if (arrayType != null) {
            try {
                wat = new SOAPArrayType(arrayType, new NamespaceContext(xsdElt));
            } catch (XmlValueOutOfRangeException e) {
                state.error(XmlErrorCodes.SOAPARRAY, new Object[]{arrayType}, xsdElt);
            }
        }
        impl.setWsdlArrayType(wat);

        boolean isFixed = xsdElt.isSetFixed();
        if (xsdElt.isSetDefault() && isFixed) {
            state.error(XmlErrorCodes.SCHEMA_ELEM$DEFAULT_OR_FIXED, null, xsdElt.xgetFixed());
            // recovery: ignore fixed
            isFixed = false;
        }
        impl.setParticleType(SchemaParticle.ELEMENT);
        impl.setNameAndTypeRef(qname, sType.getRef());
        impl.setNillable(xsdElt.getNillable());
        impl.setDefault(isFixed ? xsdElt.getFixed() : xsdElt.getDefault(), isFixed, xsdElt);

        Object block = xsdElt.getBlock();
        boolean blockExt = false;
        boolean blockRest = false;
        boolean blockSubst = false;

        if (block != null) {
            if (block instanceof String && block.equals("#all")) {
                // #ALL value
                blockExt = blockRest = blockSubst = true;
            } else if (block instanceof List) {
                List<?> blockList = (List<?>) block;
                if (blockList.contains("extension")) {
                    blockExt = true;
                }
                if (blockList.contains("restriction")) {
                    blockRest = true;
                }
                if (blockList.contains("substitution")) {
                    blockSubst = true;
                }
            }
        }

        impl.setBlock(blockExt, blockRest, blockSubst);

        boolean constraintFailed = false;

        // Translate Identity constraints

        int length = xsdElt.sizeOfKeyArray() + xsdElt.sizeOfKeyrefArray() + xsdElt.sizeOfUniqueArray();
        SchemaIdentityConstraintImpl[] constraints = new SchemaIdentityConstraintImpl[length];
        int cur = 0;

        // Handle key constraints
        Keybase[] keys = xsdElt.getKeyArray();
        for (int i = 0; i < keys.length; i++, cur++) {
            constraints[cur] = translateIdentityConstraint(keys[i], targetNamespace, chameleon);
            if (constraints[cur] != null) {
                constraints[cur].setConstraintCategory(SchemaIdentityConstraint.CC_KEY);
            } else {
                constraintFailed = true;
            }
        }

        // Handle unique constraints
        Keybase[] uc = xsdElt.getUniqueArray();
        for (int i = 0; i < uc.length; i++, cur++) {
            constraints[cur] = translateIdentityConstraint(uc[i], targetNamespace, chameleon);
            if (constraints[cur] != null) {
                constraints[cur].setConstraintCategory(SchemaIdentityConstraint.CC_UNIQUE);
            } else {
                constraintFailed = true;
            }
        }

        // Handle keyref constraints
        KeyrefDocument.Keyref[] krs = xsdElt.getKeyrefArray();
        for (int i = 0; i < krs.length; i++, cur++) {
            constraints[cur] = translateIdentityConstraint(krs[i], targetNamespace, chameleon);
            if (constraints[cur] != null) {
                constraints[cur].setConstraintCategory(SchemaIdentityConstraint.CC_KEYREF);
            } else {
                constraintFailed = true;
            }
        }

        if (!constraintFailed) {
            SchemaIdentityConstraint.Ref[] refs = new SchemaIdentityConstraint.Ref[length];
            for (int i = 0; i < refs.length; i++) {
                refs[i] = constraints[i].getRef();
            }

            impl.setIdentityConstraints(refs);
        }

        return impl;
    }

    /**
     * We need to do this because of the following kind of Schemas:
     * <xs:group name="e">
     * <xs:sequence>
     * <xs:element name="error">
     * <xs:complexType>
     * <xs:group ref="e"/>
     * </xs:complexType>
     * </xs:element>
     * </xs:sequence>
     * </xs:group>
     * (see JIRA bug XMLBEANS-35)
     * Even though this should not be allowed because it produces an infinite
     * number of anonymous types and local elements nested within each other,
     * the de facto consensus among Schema processors is that this should be
     * valid, therefore we have to detect this situation and "patch up" the
     * Schema object model so that instead of creating a new anonymous type,
     * we refer to the one that was already created earlier.
     * In order to accomplish that, we store inside every anonymous type the
     * list of groups that were dereferenced at the moment the type was created
     * and if the same pattern is about to repeat, it means that we are in a
     * case similar to the above.
     */
    private static SchemaType checkRecursiveGroupReference(QName[] context, QName containingElement, SchemaTypeImpl outerType) {
        if (context.length < 1) {
            return null;
        }
        SchemaTypeImpl type = outerType;

        while (type != null) {
            if (type.getName() != null || type.isDocumentType()) {
                return null; // not anonymous
            }
            if (containingElement.equals(type.getContainerField().getName())) {
                QName[] outerContext = type.getGroupReferenceContext();
                if (outerContext != null && outerContext.length == context.length) {
                    // Smells like trouble
                    boolean equal = true;
                    for (int i = 0; i < context.length; i++) {
                        if (!(context[i] == null && outerContext[i] == null ||
                              context[i] != null && context[i].equals(outerContext[i]))) {
                            equal = false;
                            break;
                        }
                    }
                    if (equal) {
                        return type;
                    }
                }
            }
            type = (SchemaTypeImpl) type.getOuterType();
        }
        return null;
    }

    private static String removeWhitespace(String xpath) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < xpath.length(); i++) {
            char ch = xpath.charAt(i);
            if (XMLChar.isSpace(ch)) {
                continue;
            }
            sb.append(ch);
        }
        return sb.toString();
    }

    public static final org.apache.xmlbeans.impl.regex.RegularExpression XPATH_REGEXP = new org.apache.xmlbeans.impl.regex.RegularExpression("(\\.//)?((((child::)?((\\i\\c*:)?(\\i\\c*|\\*)))|\\.)/)*((((child::)?((\\i\\c*:)?(\\i\\c*|\\*)))|\\.)|((attribute::|@)((\\i\\c*:)?(\\i\\c*|\\*))))(\\|(\\.//)?((((child::)?((\\i\\c*:)?(\\i\\c*|\\*)))|\\.)/)*((((child::)?((\\i\\c*:)?(\\i\\c*|\\*)))|\\.)|((attribute::|@)((\\i\\c*:)?(\\i\\c*|\\*)))))*", "X");

    private static boolean checkXPathSyntax(String xpath) {
        if (xpath == null) {
            return false;
        }

        // strip whitespace from xpath
        xpath = removeWhitespace(xpath);

        // apply regexp
        synchronized (XPATH_REGEXP) {
            return (XPATH_REGEXP.matches(xpath));
        }
    }

    private static SchemaIdentityConstraintImpl translateIdentityConstraint(Keybase parseIC,
                                                                            String targetNamespace, boolean chameleon) {
        StscState state = StscState.get();

        // first do some checking
        String selector = parseIC.getSelector() == null ? null : parseIC.getSelector().getXpath();
        if (!checkXPathSyntax(selector)) {
            state.error(XmlErrorCodes.SELECTOR_XPATH, new Object[]{selector}, parseIC.getSelector().xgetXpath());
            return null;
        }

        FieldDocument.Field[] fieldElts = parseIC.getFieldArray();
        for (FieldDocument.Field fieldElt : fieldElts) {
            if (!checkXPathSyntax(fieldElt.getXpath())) {
                state.error(XmlErrorCodes.FIELDS_XPATH, new Object[]{fieldElt.getXpath()}, fieldElt.xgetXpath());
                return null;
            }
        }

        // then translate.
        SchemaIdentityConstraintImpl ic = new SchemaIdentityConstraintImpl(state.getContainer(targetNamespace));
        ic.setName(QNameHelper.forLNS(parseIC.getName(), targetNamespace));
        ic.setSelector(parseIC.getSelector().getXpath());
        ic.setParseContext(parseIC, targetNamespace, chameleon);
        SchemaAnnotationImpl ann = SchemaAnnotationImpl.getAnnotation(state.getContainer(targetNamespace), parseIC);
        ic.setAnnotation(ann);
        ic.setUserData(getUserData(parseIC));

        // Set the ns map
        XmlCursor c = parseIC.newCursor();
        Map<String, String> nsMap = new HashMap<>();

        c.getAllNamespaces(nsMap);
        nsMap.remove(""); // Remove the default mapping. This cannot be used by the xpath expressions.
        ic.setNSMap(nsMap);
        c.dispose();

        String[] fields = new String[fieldElts.length];
        for (int j = 0; j < fields.length; j++) {
            fields[j] = fieldElts[j].getXpath();
        }
        ic.setFields(fields);

        try {
            ic.buildPaths();
        } catch (XPath.XPathCompileException e) {
            state.error(XmlErrorCodes.INVALID_XPATH, new Object[]{e.getMessage()}, parseIC);
            return null;
        }

        state.addIdConstraint(ic);
        ic.setFilename(findFilename(parseIC));

        return state.findIdConstraint(ic.getName(), targetNamespace, null);

    }

    public static SchemaModelGroupImpl translateModelGroup(NamedGroup namedGroup, String targetNamespace, boolean chameleon, boolean redefinition) {
        String name = namedGroup.getName();
        if (name == null) {
            StscState.get().error(XmlErrorCodes.MISSING_NAME, new Object[]{"model group"}, namedGroup);
            return null;
        }
        SchemaContainer c = StscState.get().getContainer(targetNamespace);
        SchemaModelGroupImpl result = new SchemaModelGroupImpl(c);
        SchemaAnnotationImpl ann = SchemaAnnotationImpl.getAnnotation(c, namedGroup);
        FormChoice elemFormDefault = findElementFormDefault(namedGroup);
        FormChoice attFormDefault = findAttributeFormDefault(namedGroup);
        result.init(QNameHelper.forLNS(name, targetNamespace), targetNamespace, chameleon,
            elemFormDefault == null ? null : elemFormDefault.getStringValue(),
            attFormDefault == null ? null : attFormDefault.getStringValue(),
            redefinition, namedGroup, ann, getUserData(namedGroup));
        result.setFilename(findFilename(namedGroup));
        return result;
    }

    public static SchemaAttributeGroupImpl translateAttributeGroup(AttributeGroup attrGroup, String targetNamespace, boolean chameleon, boolean redefinition) {
        String name = attrGroup.getName();
        if (name == null) {
            StscState.get().error(XmlErrorCodes.MISSING_NAME, new Object[]{"attribute group"}, attrGroup);
            return null;
        }
        SchemaContainer c = StscState.get().getContainer(targetNamespace);
        SchemaAttributeGroupImpl result = new SchemaAttributeGroupImpl(c);
        SchemaAnnotationImpl ann = SchemaAnnotationImpl.getAnnotation(c, attrGroup);
        FormChoice formDefault = findAttributeFormDefault(attrGroup);
        result.init(QNameHelper.forLNS(name, targetNamespace), targetNamespace, chameleon,
            formDefault == null ? null : formDefault.getStringValue(),
            redefinition, attrGroup, ann, getUserData(attrGroup));
        result.setFilename(findFilename(attrGroup));
        return result;
    }

    static FormChoice findAttributeFormDefault(XmlObject obj) {
        XmlCursor cur = obj.newCursor();
        while (cur.getObject().schemaType() != Schema.type) {
            if (!cur.toParent()) {
                return null;
            }
        }
        return ((Schema) cur.getObject()).xgetAttributeFormDefault();
    }

    static SchemaLocalAttributeImpl translateAttribute(
        Attribute xsdAttr, String targetNamespace, String formDefault, boolean chameleon,
        List<SchemaType> anonymousTypes, SchemaType outerType, SchemaAttributeModel baseModel,
        boolean local) {
        StscState state = StscState.get();

        String name = xsdAttr.getName();
        QName ref = xsdAttr.getRef();

        if (ref != null && name != null) {
            if (name.equals(ref.getLocalPart()) && uriMatch(targetNamespace, ref.getNamespaceURI())) {
                state.warning(XmlErrorCodes.SCHEMA_ATTR$REF_OR_NAME_HAS_BOTH, new Object[]{name}, xsdAttr.xgetRef());
            } else {
                state.error(XmlErrorCodes.SCHEMA_ATTR$REF_OR_NAME_HAS_BOTH, new Object[]{name}, xsdAttr.xgetRef());
            }
            // ignore name
            name = null;
        }
        if (ref == null && name == null) {
            state.error(XmlErrorCodes.SCHEMA_ATTR$REF_OR_NAME_HAS_NEITHER, null, xsdAttr);
            // recovery: ignore this element
            return null;
        }
        if (name != null && !XMLChar.isValidNCName(name)) {
            state.error(XmlErrorCodes.INVALID_VALUE, new Object[]{name, "name"}, xsdAttr.xgetName());
            // recovery: let the name go through anyway.
        }

        boolean isFixed = false;
        String deftext = null;
        String fmrfixedtext = null;
        QName qname;
        SchemaLocalAttributeImpl sAttr;
        SchemaType sType = null;
        int use = SchemaLocalAttribute.OPTIONAL;

        if (local) {
            sAttr = new SchemaLocalAttributeImpl();
        } else {
            sAttr = new SchemaGlobalAttributeImpl(StscState.get().getContainer(targetNamespace));
            ((SchemaGlobalAttributeImpl) sAttr).setParseContext(xsdAttr, targetNamespace, chameleon);
        }

        if (ref != null) {
            if (xsdAttr.getType() != null) {
                state.error(XmlErrorCodes.SCHEMA_ATTR$REF_FEATURES, new Object[]{"type"}, xsdAttr.xgetType());
                // recovery: ignore type, simpleType
            }

            if (xsdAttr.getSimpleType() != null) {
                state.error(XmlErrorCodes.SCHEMA_ATTR$REF_FEATURES, new Object[]{"<simpleType>"}, xsdAttr.getSimpleType());
                // recovery: ignore type, simpleType
            }

            if (xsdAttr.getForm() != null) {
                state.error(XmlErrorCodes.SCHEMA_ATTR$REF_FEATURES, new Object[]{"form"}, xsdAttr.xgetForm());
                // recovery: ignore form
            }

            SchemaGlobalAttribute referenced = state.findGlobalAttribute(ref, chameleon ? targetNamespace : null, targetNamespace);
            if (referenced == null) {
                state.notFoundError(ref, SchemaType.ATTRIBUTE, xsdAttr.xgetRef(), true);
                // recovery: ignore this element
                return null;
            }

            qname = ref;
            use = referenced.getUse();
            sType = referenced.getType();
            deftext = referenced.getDefaultText();
            if (deftext != null) {
                isFixed = referenced.isFixed();
                if (isFixed) {
                    fmrfixedtext = deftext;
                }
            }
        } else {
            if (local) {
                boolean qualified;
                FormChoice form = xsdAttr.xgetForm();
                if (form != null) {
                    qualified = form.getStringValue().equals(FORM_QUALIFIED);
                } else if (formDefault != null) {
                    qualified = formDefault.equals(FORM_QUALIFIED);
                } else {
                    form = findAttributeFormDefault(xsdAttr);
                    qualified = form != null && form.getStringValue().equals(FORM_QUALIFIED);
                }

                qname = qualified ? QNameHelper.forLNS(name, targetNamespace) : QNameHelper.forLN(name);
            } else {
                qname = QNameHelper.forLNS(name, targetNamespace);
            }

            if (xsdAttr.getType() != null) {
                sType = state.findGlobalType(xsdAttr.getType(), chameleon ? targetNamespace : null, targetNamespace);
                if (sType == null) {
                    state.notFoundError(xsdAttr.getType(), SchemaType.TYPE, xsdAttr.xgetType(), true);
                }
            }

            if (qname.getNamespaceURI().equals("http://www.w3.org/2001/XMLSchema-instance")) {
                state.error(XmlErrorCodes.NO_XSI, new Object[]{"http://www.w3.org/2001/XMLSchema-instance"}, xsdAttr.xgetName());
            }

            if (qname.getNamespaceURI().length() == 0 && qname.getLocalPart().equals("xmlns")) {
                state.error(XmlErrorCodes.NO_XMLNS, null, xsdAttr.xgetName());
            }

            LocalSimpleType typedef = xsdAttr.getSimpleType();

            if ((sType != null) && typedef != null) {
                state.error(XmlErrorCodes.SCHEMA_ATTR$TYPE_ATTR_OR_NESTED_TYPE, null, typedef);
                typedef = null;
            }

            if (typedef != null) {
                SchemaTypeImpl sTypeImpl = new SchemaTypeImpl(state.getContainer(targetNamespace));
                sType = sTypeImpl;
                sTypeImpl.setContainerField(sAttr);
                sTypeImpl.setOuterSchemaTypeRef(outerType == null ? null : outerType.getRef());
                // leave the anonymous type unresolved: it will be resolved later.
                anonymousTypes.add(sType);
                sTypeImpl.setSimpleType(true);
                sTypeImpl.setParseContext(typedef, targetNamespace, chameleon, null, null, false);
                sTypeImpl.setAnnotation(SchemaAnnotationImpl.getAnnotation(state.getContainer(targetNamespace), typedef));
                sTypeImpl.setUserData(getUserData(typedef));
            }

            if (sType == null && baseModel != null && baseModel.getAttribute(qname) != null) {
                sType = baseModel.getAttribute(qname).getType();
            }
        }

        if (sType == null) {
            sType = BuiltinSchemaTypeSystem.ST_ANY_SIMPLE;
        }

        if (!sType.isSimpleType()) {
            // KHK: which rule? could use #a-simple_type_definition
            state.error("Attributes must have a simple type (not complex).", XmlErrorCodes.INVALID_SCHEMA, xsdAttr);
            // recovery: switch to the any-type
            sType = BuiltinSchemaTypeSystem.ST_ANY_SIMPLE;
        }

        if (xsdAttr.isSetUse()) {
            use = translateUseCode(xsdAttr.xgetUse());

            // ignore referenced default if no longer optional
            if (use != SchemaLocalAttribute.OPTIONAL && !isFixed) {
                deftext = null;
            }
        }

        if (xsdAttr.isSetDefault() || xsdAttr.isSetFixed()) {
            if (isFixed && !xsdAttr.isSetFixed()) {
                state.error("A use of a fixed attribute definition must also be fixed", XmlErrorCodes.REDUNDANT_DEFAULT_FIXED, xsdAttr.xgetFixed());
            }

            isFixed = xsdAttr.isSetFixed();

            if (xsdAttr.isSetDefault() && isFixed) {
                state.error(XmlErrorCodes.SCHEMA_ATTR$DEFAULT_OR_FIXED, null, xsdAttr.xgetFixed());
                // recovery: ignore fixed
                isFixed = false;
            }
            deftext = isFixed ? xsdAttr.getFixed() : xsdAttr.getDefault();
            // BUGBUG(radup) this is not good, since they should be compared by value
            // in StscChecker; but there we don't have yet access to the referred attr
            if (fmrfixedtext != null && !fmrfixedtext.equals(deftext)) {
                state.error(XmlErrorCodes.SCHEMA_ATTR$FIXED_NOT_MATCH, null, xsdAttr.xgetFixed());
                // recovery: reset fixed to the original value
                deftext = fmrfixedtext;
            }
        }

        if (!local) {
            ((SchemaGlobalAttributeImpl) sAttr).setFilename(findFilename(xsdAttr));
        }

        SOAPArrayType wat = null;
        XmlCursor c = xsdAttr.newCursor();
        String arrayType = c.getAttributeText(WSDL_ARRAYTYPE_NAME);
        c.dispose();
        if (arrayType != null) {
            try {
                wat = new SOAPArrayType(arrayType, new NamespaceContext(xsdAttr));
            } catch (XmlValueOutOfRangeException e) {
                state.error(XmlErrorCodes.SOAPARRAY, new Object[]{arrayType}, xsdAttr);
            }
        }

        SchemaAnnotationImpl ann = SchemaAnnotationImpl.getAnnotation(state.getContainer(targetNamespace), xsdAttr);
        sAttr.init(
            qname,
            sType.getRef(),
            use,
            deftext, xsdAttr, null, isFixed,
            wat, ann, getUserData(xsdAttr));

        return sAttr;
    }

    static int translateUseCode(Attribute.Use attruse) {
        if (attruse == null) {
            return SchemaLocalAttribute.OPTIONAL;
        }

        String val = attruse.getStringValue();
        if (val.equals("optional")) {
            return SchemaLocalAttribute.OPTIONAL;
        }
        if (val.equals("required")) {
            return SchemaLocalAttribute.REQUIRED;
        }
        if (val.equals("prohibited")) {
            return SchemaLocalAttribute.PROHIBITED;
        }
        return SchemaLocalAttribute.OPTIONAL;
    }

    static BigInteger buildBigInt(XmlAnySimpleType value) {
        if (value == null) {
            return null;
        }
        String text = value.getStringValue();
        BigInteger bigInt;
        try {
            bigInt = new BigInteger(text);
        } catch (NumberFormatException e) {
            StscState.get().error(XmlErrorCodes.INVALID_VALUE_DETAIL, new Object[]{text, "nonNegativeInteger", e.getMessage()}, value);
            return null;
        }

        if (bigInt.signum() < 0) {
            StscState.get().error(XmlErrorCodes.INVALID_VALUE, new Object[]{text, "nonNegativeInteger"}, value);
            return null;
        }

        return bigInt;
    }


    static XmlNonNegativeInteger buildNnInteger(XmlAnySimpleType value) {
        BigInteger bigInt = buildBigInt(value);
        try {
            XmlNonNegativeIntegerImpl i = new XmlNonNegativeIntegerImpl();
            i.setBigIntegerValue(bigInt);
            i.setImmutable();
            return i;
        } catch (XmlValueOutOfRangeException e) {
            StscState.get().error("Internal error processing number", XmlErrorCodes.MALFORMED_NUMBER, value);
            return null;
        }
    }

    static XmlPositiveInteger buildPosInteger(XmlAnySimpleType value) {
        BigInteger bigInt = buildBigInt(value);
        try {
            XmlPositiveIntegerImpl i = new XmlPositiveIntegerImpl();
            i.setBigIntegerValue(bigInt);
            i.setImmutable();
            return i;
        } catch (XmlValueOutOfRangeException e) {
            StscState.get().error("Internal error processing number", XmlErrorCodes.MALFORMED_NUMBER, value);
            return null;
        }
    }


    private static Object getUserData(XmlObject pos) {
        XmlCursor.XmlBookmark b = pos.newCursor().getBookmark(SchemaBookmark.class);
        if (b instanceof SchemaBookmark) {
            return ((SchemaBookmark) b).getValue();
        } else {
            return null;
        }
    }

    private static boolean isEmptySchema(Schema schema) {
        XmlCursor cursor = schema.newCursor();
        boolean result = !cursor.toFirstChild();
        cursor.dispose();
        return result;
    }

    private static boolean isReservedTypeName(QName name) {
        return (BuiltinSchemaTypeSystem.get().findType(name) != null);
    }
}
