/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.axis2.schema;

import org.apache.axis2.namespace.Constants;
import org.apache.axis2.schema.i18n.SchemaCompilerMessages;
import org.apache.axis2.schema.util.PrimitiveTypeFinder;
import org.apache.axis2.schema.util.PrimitiveTypeWrapper;
import org.apache.axis2.schema.util.SchemaPropertyLoader;
import org.apache.axis2.schema.writer.BeanWriter;
import org.apache.axis2.util.SchemaUtil;
import org.apache.axis2.util.URLProcessor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.commons.schema.XmlSchema;
import org.apache.ws.commons.schema.XmlSchemaAll;
import org.apache.ws.commons.schema.XmlSchemaAny;
import org.apache.ws.commons.schema.XmlSchemaAnyAttribute;
import org.apache.ws.commons.schema.XmlSchemaAttribute;
import org.apache.ws.commons.schema.XmlSchemaAttributeGroup;
import org.apache.ws.commons.schema.XmlSchemaAttributeGroupRef;
import org.apache.ws.commons.schema.XmlSchemaChoice;
import org.apache.ws.commons.schema.XmlSchemaComplexContent;
import org.apache.ws.commons.schema.XmlSchemaComplexContentExtension;
import org.apache.ws.commons.schema.XmlSchemaComplexContentRestriction;
import org.apache.ws.commons.schema.XmlSchemaComplexType;
import org.apache.ws.commons.schema.XmlSchemaContent;
import org.apache.ws.commons.schema.XmlSchemaContentModel;
import org.apache.ws.commons.schema.XmlSchemaElement;
import org.apache.ws.commons.schema.XmlSchemaEnumerationFacet;
import org.apache.ws.commons.schema.XmlSchemaExternal;
import org.apache.ws.commons.schema.XmlSchemaGroup;
import org.apache.ws.commons.schema.XmlSchemaGroupBase;
import org.apache.ws.commons.schema.XmlSchemaGroupRef;
import org.apache.ws.commons.schema.XmlSchemaImport;
import org.apache.ws.commons.schema.XmlSchemaInclude;
import org.apache.ws.commons.schema.XmlSchemaLengthFacet;
import org.apache.ws.commons.schema.XmlSchemaMaxExclusiveFacet;
import org.apache.ws.commons.schema.XmlSchemaMaxInclusiveFacet;
import org.apache.ws.commons.schema.XmlSchemaMaxLengthFacet;
import org.apache.ws.commons.schema.XmlSchemaMinExclusiveFacet;
import org.apache.ws.commons.schema.XmlSchemaMinInclusiveFacet;
import org.apache.ws.commons.schema.XmlSchemaMinLengthFacet;
import org.apache.ws.commons.schema.XmlSchemaObject;
import org.apache.ws.commons.schema.XmlSchemaObjectCollection;
import org.apache.ws.commons.schema.XmlSchemaObjectTable;
import org.apache.ws.commons.schema.XmlSchemaParticle;
import org.apache.ws.commons.schema.XmlSchemaPatternFacet;
import org.apache.ws.commons.schema.XmlSchemaSequence;
import org.apache.ws.commons.schema.XmlSchemaSimpleContent;
import org.apache.ws.commons.schema.XmlSchemaSimpleContentExtension;
import org.apache.ws.commons.schema.XmlSchemaSimpleContentRestriction;
import org.apache.ws.commons.schema.XmlSchemaSimpleType;
import org.apache.ws.commons.schema.XmlSchemaSimpleTypeContent;
import org.apache.ws.commons.schema.XmlSchemaSimpleTypeList;
import org.apache.ws.commons.schema.XmlSchemaSimpleTypeRestriction;
import org.apache.ws.commons.schema.XmlSchemaSimpleTypeUnion;
import org.apache.ws.commons.schema.XmlSchemaType;

import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * Schema compiler for ADB. Based on WS-Commons schema object model.
 */
public class SchemaCompiler {

    public static final int COMPONENT_TYPE = 1;
    public static final int COMPONENT_ELEMENT = 2;
    public static final int COMPONENT_ATTRIBUTE = 3;
    public static final int COMPONENT_ATTRIBUTE_GROUP = 4;
    public static final int COMPONENT_GROUP = 5;


    private static final Log log = LogFactory.getLog(SchemaCompiler.class);

    private CompilerOptions options;
    private HashMap processedTypemap;
    // have to keep a seperate group type map since same
    // name can be used to group and complextype
    private HashMap processedGroupTypeMap;

    //the list of processedElements for the outer elements
    private HashMap processedElementMap;

    private HashMap processedAnonymousComplexTypesMap;

    //we need this map to keep the referenced elements. these elements need to be kept seperate
    //to avoid conflicts
    private HashMap processedElementRefMap;
    private HashMap simpleTypesMap;
    private HashMap changedTypeMap;

    private HashSet changedSimpleTypeSet;
    private HashSet changedComplexTypeSet;
    private HashSet changedElementSet;

    // this map is necessary to retain the metainformation of types. The reason why these
    // meta info 'bags' would be useful later is to cater for the extensions and restrictions
    // of types
    private HashMap processedTypeMetaInfoMap;

    //
    private ArrayList processedElementList;

    //a list of nillable elements - used to generate code
    //for nillable elements
    private List nillableElementList;
    // writee reference
    private BeanWriter writer = null;
    private Map baseSchemaTypeMap = null;

    //a map for keeping the already loaded schemas
    //the key is the targetnamespace and the value is the schema object
    private Map loadedSchemaMap = new HashMap();

    // A map keeping the available schemas
    //the key is the targetnamespace and the value is the schema object
    //this map will be populated when multiple schemas
    //are fed to the schema compiler!
    private Map availableSchemaMap = new HashMap();

    private Map loadedSourceURI = new HashMap();

    // a list of externally identified QNames to be processed. This becomes
    // useful when  only a list of external elements need to be processed

    public static final String ANY_ELEMENT_FIELD_NAME = "extraElement";
    public static final String EXTRA_ATTRIBUTE_FIELD_NAME = "extraAttributes";

    public static final String USE_OPTIONAL = "optional";
    public static final String USE_REQUIRED = "required";
    public static final String USE_NONE = "none";

    /**
     * @return the processes element map
     *         includes the Qname of the element as the key and a
     *         String representing the fully qualified class name
     */
    public HashMap getProcessedElementMap() {
        return processedElementMap;
    }


    /**
     * @return a map of Qname vs models. A model can be anything,
     *         ranging from a DOM document to a stream. This is taken from the
     *         writer and the schema compiler has no control over it
     */
    public Map getProcessedModelMap() {
        return writer.getModelMap();
    }

    /**
     * Constructor - Accepts a options bean
     *
     * @param options
     */
    public SchemaCompiler(CompilerOptions options) throws SchemaCompilationException {

        if (options == null) {
            //create an empty options object
            this.options = new CompilerOptions();
        } else {
            this.options = options;
        }

        //instantiate the maps
        processedTypemap = new HashMap();
        processedGroupTypeMap = new HashMap();

        processedElementMap = new HashMap();
        simpleTypesMap = new HashMap();
        processedElementList = new ArrayList();
        processedAnonymousComplexTypesMap = new HashMap();
        changedTypeMap = new HashMap();
        processedTypeMetaInfoMap = new HashMap();
        processedElementRefMap = new HashMap();
        nillableElementList = new ArrayList();

        changedComplexTypeSet = new HashSet();
        changedSimpleTypeSet = new HashSet();
        changedElementSet = new HashSet();

        //load the writer and initiliaze the base types
        writer = SchemaPropertyLoader.getBeanWriterInstance();
        writer.init(this.options);

        //load the base types
        baseSchemaTypeMap = SchemaPropertyLoader.getTypeMapperInstance().getTypeMap();
        // adding all the soap encoding schema classes
        processedTypemap.putAll(SchemaPropertyLoader.getTypeMapperInstance().getSoapEncodingTypesMap());


    }

    /**
     * Compile a list of schemas
     * This actually calls the compile (XmlSchema s) method repeatedly
     *
     * @param schemalist
     * @throws SchemaCompilationException
     * @see #compile(org.apache.ws.commons.schema.XmlSchema)
     */
    public void compile(List schemalist) throws SchemaCompilationException {
        try {

            if (schemalist.isEmpty()) {
                return;
            }

            //clear the loaded and available maps
            loadedSchemaMap.clear();
            availableSchemaMap.clear();

            XmlSchema schema;
            // first round - populate the avaialble map
            for (int i = 0; i < schemalist.size(); i++) {
                schema = (XmlSchema) schemalist.get(i);
                availableSchemaMap.put(
                        schema.getTargetNamespace(),
                        schema);
            }

            //set a mapper package if not avaialable
            if (writer.getExtensionMapperPackageName() == null) {
                String nsp = null;
                //get the first schema from the list and take that namespace as the
                //mapper namespace
                for (int i = 0; nsp == null && i < schemalist.size(); i++) {
                    nsp = ((XmlSchema) schemalist.get(i)).getTargetNamespace();
                    if ((nsp != null) && !nsp.equals("")){
                        break;
                    }
                    XmlSchema[] schemas = SchemaUtil.getAllSchemas((XmlSchema) schemalist.get(i));
                    for (int j = 0; schemas != null && j < schemas.length; j++) {
                        nsp = schemas[j].getTargetNamespace();
                        if (nsp != null)
                            break;
                    }
                }
                if (nsp == null) {
                    nsp = URLProcessor.DEFAULT_PACKAGE;
                }

                // if this name space exists in the ns2p list then we use it.
                if ((options.getNs2PackageMap() != null)
                        && (options.getNs2PackageMap().containsKey(nsp))) {
                    writer.registerExtensionMapperPackageName((String) options.getNs2PackageMap().get(nsp));
                } else {
                    writer.registerExtensionMapperPackageName(nsp == null ? null : URLProcessor.makePackageName(nsp));
                }
            }
            // second round - call the schema compiler one by one
            for (int i = 0; i < schemalist.size(); i++) {
                compile((XmlSchema) schemalist.get(i), true);
            }

            //finish up
            finalizeSchemaCompilation();

        } catch (SchemaCompilationException e) {
            throw e;
        } catch (Exception e) {
            throw new SchemaCompilationException(e);
        }
    }

    /**
     * Compile (rather codegen) a single schema element
     *
     * @param schema
     * @throws SchemaCompilationException
     */
    public void compile(XmlSchema schema) throws SchemaCompilationException {
        compile(schema, false);
    }

    /**
     * Compile (rather codegen) a single schema element
     *
     * @param schema
     * @param isPartofGroup
     * @throws SchemaCompilationException
     */
    private void compile(XmlSchema schema, boolean isPartofGroup) throws SchemaCompilationException {

        // some documents explicitly imports the schema of built in types. We don't actually need to compile
        // the built-in types. So check the target namespace here and ignore it.
        if (Constants.URI_2001_SCHEMA_XSD.equals(schema.getTargetNamespace())) {
            return;
        }

        //register the package from this namespace as the mapper classes package
        if (!isPartofGroup) {
            //set a mapper package if not avaialable
            if (writer.getExtensionMapperPackageName() == null) {
                String ns = schema.getTargetNamespace();
                writer.registerExtensionMapperPackageName(ns == null ? null : URLProcessor.makePackageName(ns));
            }
        }

        //First look for the schemas that are imported and process them
        //Note that these are processed recursively!

        //add the schema to the loaded schema list
        if (!loadedSchemaMap.containsKey(schema.getTargetNamespace())) {
            loadedSchemaMap.put(schema.getTargetNamespace(), schema);
        }

        // If we have/are loading a schema with a specific targetnamespace from a certain URI,
        // then just return back to the caller to avoid recursion.
        if (schema.getSourceURI() != null) {
            String key = schema.getTargetNamespace() + ":" + schema.getSourceURI();
            if (loadedSourceURI.containsKey(key)) {
                return;
            }
            loadedSourceURI.put(key, key);
        }

        XmlSchemaObjectCollection includes = schema.getIncludes();
        if (includes != null) {
            Iterator tempIterator = includes.getIterator();
            while (tempIterator.hasNext()) {
                Object o = tempIterator.next();
                if (o instanceof XmlSchemaImport) {
                    XmlSchema schema1 = ((XmlSchemaImport) o).getSchema();
                    if (schema1 != null) compile(schema1, isPartofGroup);
                }
                if (o instanceof XmlSchemaInclude) {
                    XmlSchema schema1 = ((XmlSchemaInclude) o).getSchema();
                    if (schema1 != null) compile(schema1, isPartofGroup);
                }
            }
        }

        //select all the elements. We generate the code for types
        //only if the elements refer them!!! regardless of the fact that
        //we have a list of elementnames, we'll need to process all the elements
        XmlSchemaObjectTable elements = schema.getElements();
        Iterator xmlSchemaElement1Iterator = elements.getValues();
        while (xmlSchemaElement1Iterator.hasNext()) {
            //this is the set of outer elements so we need to generate classes
            //The outermost elements do not contain occurence counts (!) so we do not need
            //to check for arraytypes
            processElement((XmlSchemaElement) xmlSchemaElement1Iterator.next(), schema);
        }

        Iterator xmlSchemaElement2Iterator = elements.getValues();

        // re-iterate through the elements and write them one by one
        // if the mode is unpack this process will not really write the
        // classes but will accumilate the models for a final single shot
        // write
        while (xmlSchemaElement2Iterator.hasNext()) {
            //this is the set of outer elements so we need to generate classes
            writeElement((XmlSchemaElement) xmlSchemaElement2Iterator.next());
        }

        if (options.isGenerateAll()) {
            Iterator xmlSchemaTypes2Iterator = schema.getSchemaTypes().getValues();
            while (xmlSchemaTypes2Iterator.hasNext()) {
                XmlSchemaType schemaType = (XmlSchemaType) xmlSchemaTypes2Iterator.next();
                if (this.isAlreadyProcessed(schemaType.getQName())) {
                    continue;
                }
                if (schemaType instanceof XmlSchemaComplexType) {
                    //write classes for complex types
                    XmlSchemaComplexType complexType = (XmlSchemaComplexType) schemaType;
                    if (complexType.getName() != null) {
                        processNamedComplexSchemaType(complexType, schema);
                    }
                } else if (schemaType instanceof XmlSchemaSimpleType) {
                    //process simple type
                    processSimpleSchemaType((XmlSchemaSimpleType) schemaType,
                            null,
                            schema, null);
                }
            }
        }

        if (!isPartofGroup) {
            //complete the compilation
            finalizeSchemaCompilation();
        }
    }

    /**
     * Completes the schema compilation process by writing the
     * mappers and the classes in a batch if needed
     *
     * @throws SchemaCompilationException
     */
    private void finalizeSchemaCompilation() throws SchemaCompilationException {
        //write the extension mapping class
        writer.writeExtensionMapper(
                (BeanWriterMetaInfoHolder[])
                        processedTypeMetaInfoMap.values().toArray(
                                new BeanWriterMetaInfoHolder[processedTypeMetaInfoMap.size()]));


        if (options.isWrapClasses()) {
            writer.writeBatch();
        }

        // resets the changed types
        XmlSchemaComplexType xmlSchemaComplexType = null;
        for (Iterator iter = changedComplexTypeSet.iterator();iter.hasNext();){
            xmlSchemaComplexType = (XmlSchemaComplexType) iter.next();
            xmlSchemaComplexType.setName(null);
        }

        XmlSchemaSimpleType xmlSchemaSimpleType = null;
        for (Iterator iter = changedSimpleTypeSet.iterator();iter.hasNext();){
            xmlSchemaSimpleType = (XmlSchemaSimpleType) iter.next();
            xmlSchemaSimpleType.setName(null);
        }

        XmlSchemaElement xmlSchemaElement = null;
        for (Iterator iter = changedElementSet.iterator();iter.hasNext();){
            xmlSchemaElement = (XmlSchemaElement) iter.next();
            xmlSchemaElement.setSchemaTypeName(null);
        }

    }

    /**
     * @return the property map of the schemacompiler.
     *         In this case it would be the property map loaded from
     *         the configuration file
     */
    public Properties getCompilerProperties() {
        return SchemaPropertyLoader.getPropertyMap();
    }


    /**
     * Writes the element
     *
     * @param xsElt
     * @throws SchemaCompilationException
     */
    private void writeElement(XmlSchemaElement xsElt) throws SchemaCompilationException {

        if (this.processedElementMap.containsKey(xsElt.getQName())) {
            return;
        }

        XmlSchemaType schemaType = xsElt.getSchemaType();

        BeanWriterMetaInfoHolder metainf = new BeanWriterMetaInfoHolder();
        if (schemaType != null && schemaType.getName() != null) {
            //this is a named type
            QName qName = schemaType.getQName();
            //find the class name
            String className = findClassName(qName, isArray(xsElt));

            //this means the schema type actually returns a different QName
            if (changedTypeMap.containsKey(qName)) {
                metainf.registerMapping(xsElt.getQName(),
                        (QName) changedTypeMap.get(qName),
                        className);
            } else {
                metainf.registerMapping(xsElt.getQName(),
                        qName,
                        className);
            }

            // register the default value if present
            if (xsElt.getDefaultValue() != null){
                metainf.registerDefaultValue(xsElt.getQName(),xsElt.getDefaultValue());
            }

            if (isBinary(xsElt)) {
                metainf.addtStatus(xsElt.getQName(),
                            SchemaConstants.BINARY_TYPE);
            }

        } else if (xsElt.getRefName() != null) {
            // Since top level elements would not have references
            // and we only write toplevel elements, this should
            // not be a problem , atleast should not occur in a legal schema
        } else if (xsElt.getSchemaTypeName() != null) {
            QName qName = xsElt.getSchemaTypeName();
            String className = findClassName(qName, isArray(xsElt));
            metainf.registerMapping(xsElt.getQName(),
                    qName,
                    className);


        } else if (schemaType != null) {  //the named type should have been handled already

            //we are going to special case the anonymous complex type. Our algorithm for dealing
            //with it is to generate a single object that has the complex content inside. Really the
            //intent of the user when he declares the complexType anonymously is to use it privately
            //First copy the schema types content into the metainf holder
            metainf = (BeanWriterMetaInfoHolder) this.processedAnonymousComplexTypesMap.get(xsElt);
            metainf.setAnonymous(true);
        } else {
            //this means we did not find any schema type associated with the particular element.
            log.warn(SchemaCompilerMessages.getMessage("schema.elementWithNoType", xsElt.getQName().toString()));
            metainf.registerMapping(xsElt.getQName(),
                    null,
                    writer.getDefaultClassName(),
                    SchemaConstants.ANY_TYPE);
        }

        if (nillableElementList.contains(xsElt.getQName())) {
            metainf.registerNillableQName(xsElt.getQName());
        }


        String writtenClassName = writer.write(xsElt, processedTypemap, processedGroupTypeMap, metainf);
        //register the class name
        xsElt.addMetaInfo(SchemaConstants.SchemaCompilerInfoHolder.CLASSNAME_KEY, writtenClassName);
        processedElementMap.put(xsElt.getQName(), writtenClassName);
    }

    /**
     * For inner elements
     *
     * @param xsElt
     * @param innerElementMap
     * @param parentSchema
     * @throws SchemaCompilationException
     */
    private void processElement(XmlSchemaElement xsElt, Map innerElementMap, List localNillableList, XmlSchema parentSchema) throws SchemaCompilationException {
        processElement(xsElt, false, innerElementMap, localNillableList, parentSchema);
    }

    /**
     * For outer elements
     *
     * @param xsElt
     * @param parentSchema
     * @throws SchemaCompilationException
     */
    private void processElement(XmlSchemaElement xsElt, XmlSchema parentSchema) throws SchemaCompilationException {
        processElement(xsElt, true, null, null, parentSchema);
    }

    /**
     * Process and Element
     *
     * @param xsElt
     * @param isOuter We need to know this since the treatment of outer elements is different that
     *                inner elements
     * @throws SchemaCompilationException
     */
    private void processElement(XmlSchemaElement xsElt, boolean isOuter, Map innerElementMap, List localNillableList, XmlSchema parentSchema) throws SchemaCompilationException {

        //if the element is null, which usually happens when the qname is not
        //proper, throw an exceptions
        if (xsElt == null) {
            throw new SchemaCompilationException(
                    SchemaCompilerMessages.getMessage("schema.elementNull"));
        }

        //The processing element logic seems to be quite simple. Look at the relevant schema type
        //for each and every element and process that accordingly.
        //this means that any unused type definitions would not be generated!
        if (isOuter && processedElementList.contains(xsElt.getQName())) {
            return;
        }

        XmlSchemaType schemaType = xsElt.getSchemaType();
        if (schemaType != null) {
            processSchema(xsElt, schemaType, parentSchema, false);
            //at this time it is not wise to directly write the class for the element
            //so we push the complete element to an arraylist and let the process
            //pass through. We'll be iterating through the elements writing them
            //later

            if (!isOuter) {
                if (schemaType.getName() != null) {
                    // this element already has a name. Which means we can directly
                    // register it
                    String className = findClassName(schemaType.getQName(),
                            isArray(xsElt));

                    innerElementMap.put(xsElt.getQName(), className);

                    // always store the class name in the element meta Info itself
                    // this details only needed by the unwrappig to set the complex type
                    if (options.isUseWrapperClasses() &&
                            PrimitiveTypeFinder.isPrimitive(className) &&
                            ((xsElt.getMinOccurs() == 0) || (xsElt.isNillable()))) {
                          className = PrimitiveTypeWrapper.getWrapper(className);
                    }
                    schemaType.addMetaInfo(SchemaConstants.SchemaCompilerInfoHolder.CLASSNAME_KEY, className);
                    xsElt.addMetaInfo(SchemaConstants.SchemaCompilerInfoHolder.CLASSNAME_KEY, className);

                    if (baseSchemaTypeMap.containsValue(className)) {
                        schemaType.addMetaInfo(
                                SchemaConstants.SchemaCompilerInfoHolder.CLASSNAME_PRIMITVE_KEY,
                                Boolean.TRUE);
                    }
                    //since this is a inner element we should add it to the inner element map
                } else {
                    //this is an anon type. This should have been already processed and registered at
                    //the anon map. we've to write it just like we treat a referenced type(giving due
                    //care that this is meant to be an attribute in some class)

                    QName generatedTypeName = generateTypeQName(xsElt.getQName(), parentSchema);

                    if (schemaType instanceof XmlSchemaComplexType) {
                        //set a name
                        schemaType.setName(generatedTypeName.getLocalPart());
                        changedComplexTypeSet.add(schemaType);
                        // Must do this up front to support recursive types
                        String fullyQualifiedClassName = writer.makeFullyQualifiedClassName(schemaType.getQName());
                        processedTypemap.put(schemaType.getQName(), fullyQualifiedClassName);

                        BeanWriterMetaInfoHolder metaInfHolder = (BeanWriterMetaInfoHolder) processedAnonymousComplexTypesMap.get(xsElt);
                        metaInfHolder.setOwnQname(schemaType.getQName());
                        metaInfHolder.setOwnClassName(fullyQualifiedClassName);

                        writeComplexType((XmlSchemaComplexType) schemaType,
                                metaInfHolder);
                        //remove the reference from the anon list since we named the type
                        processedAnonymousComplexTypesMap.remove(xsElt);
                        String className = findClassName(schemaType.getQName(), isArray(xsElt));
                        innerElementMap.put(
                                xsElt.getQName(),
                                className);

                        //store in the schema map to retrive in the unwrapping
                        xsElt.addMetaInfo(
                                SchemaConstants.SchemaCompilerInfoHolder.CLASSNAME_KEY,
                                className);
                    } else if (schemaType instanceof XmlSchemaSimpleType) {
                        //set a name
                        schemaType.setName(generatedTypeName.getLocalPart());
                        changedSimpleTypeSet.add(schemaType);
                        // Must do this up front to support recursive types
                        String fullyQualifiedClassName = writer.makeFullyQualifiedClassName(schemaType.getQName());
                        processedTypemap.put(schemaType.getQName(), fullyQualifiedClassName);

                        BeanWriterMetaInfoHolder metaInfHolder = (BeanWriterMetaInfoHolder) processedAnonymousComplexTypesMap.get(xsElt);
                        metaInfHolder.setOwnQname(schemaType.getQName());
                        metaInfHolder.setOwnClassName(fullyQualifiedClassName);

                        writeSimpleType((XmlSchemaSimpleType) schemaType,
                                metaInfHolder);
                        //remove the reference from the anon list since we named the type
                        processedAnonymousComplexTypesMap.remove(xsElt);
                        String className = findClassName(schemaType.getQName(), isArray(xsElt));
                        innerElementMap.put(
                                xsElt.getQName(),
                                className);

                        //store in the schema map
                        xsElt.addMetaInfo(
                                SchemaConstants.SchemaCompilerInfoHolder.CLASSNAME_KEY,
                                className);
                    }
                }
            } else {
                // set the binary status of this element
                this.processedElementList.add(xsElt.getQName());
            }
            //referenced name
        } else if (xsElt.getRefName() != null) {

            if (xsElt.getRefName().equals(SchemaConstants.XSD_SCHEMA)) {
                innerElementMap.put(xsElt.getQName(), writer.getDefaultClassName());
                return;
            }
            //process the referenced type. It could be thought that the referenced element replaces this
            //element
            XmlSchema resolvedSchema = getParentSchema(parentSchema,xsElt.getRefName(),COMPONENT_ELEMENT);
            if (resolvedSchema == null){
                throw new SchemaCompilationException("can not find the element " + xsElt.getRefName()
                 + " from the parent schema " + parentSchema.getTargetNamespace());
            }
            XmlSchemaElement referencedElement = resolvedSchema.getElementByName(xsElt.getRefName());
            if (referencedElement == null) {
                throw new SchemaCompilationException(
                        SchemaCompilerMessages.getMessage("schema.referencedElementNotFound", xsElt.getRefName().toString()));
            }

            // here what we want is to set the schema type name for the element
            if ((referencedElement.getSchemaType() != null)
                    && (referencedElement.getSchemaType().getQName() != null)){
                // i.e this element refers to an complex type name
                if (!this.processedElementRefMap.containsKey(referencedElement.getQName())) {
                    if (this.baseSchemaTypeMap.containsKey(referencedElement.getSchemaTypeName())) {
                        this.processedElementRefMap.put(referencedElement.getQName(),
                                this.baseSchemaTypeMap.get(referencedElement.getSchemaTypeName()));
                    } else {
                        XmlSchema resolvedTypeSchema = getParentSchema(resolvedSchema,
                                referencedElement.getSchemaTypeName(),
                                COMPONENT_TYPE);
                        XmlSchemaType xmlSchemaType = resolvedTypeSchema.getTypeByName(
                                referencedElement.getSchemaTypeName().getLocalPart());
                        processSchema(referencedElement, xmlSchemaType, resolvedTypeSchema, true);
                        this.processedElementRefMap.put(referencedElement.getQName(),
                                this.processedTypemap.get(referencedElement.getSchemaTypeName()));
                    }
                }
                String javaClassName;
                if (this.baseSchemaTypeMap.containsKey(referencedElement.getSchemaTypeName())) {
                    // here we have to do nothing since we do not generate a name
                } else {
                    javaClassName = (String) this.processedTypemap.get(referencedElement.getSchemaTypeName());
                    referencedElement.addMetaInfo(SchemaConstants.SchemaCompilerInfoHolder.CLASSNAME_KEY,
                            javaClassName);
                    xsElt.addMetaInfo(SchemaConstants.SchemaCompilerInfoHolder.CLASSNAME_KEY,
                            javaClassName);
                }
            } else if (referencedElement.getSchemaType() != null) {
                if (!this.processedElementRefMap.containsKey(referencedElement.getQName())) {

                    processSchema(referencedElement, referencedElement.getSchemaType(), resolvedSchema, true);
                    // if this is an anonomous complex type we have to set this
                    this.processedElementRefMap.put(referencedElement.getQName(),
                            this.processedTypemap.get(referencedElement.getSchemaTypeName()));

                }
                
                String javaClassName = (String) this.processedTypemap.get(referencedElement.getSchemaTypeName());
                referencedElement.addMetaInfo(SchemaConstants.SchemaCompilerInfoHolder.CLASSNAME_KEY,
                        javaClassName);
                xsElt.addMetaInfo(SchemaConstants.SchemaCompilerInfoHolder.CLASSNAME_KEY,
                        javaClassName);
            }

            // schema type name is present but not the schema type object
        } else if (xsElt.getSchemaTypeName() != null) {
            //There can be instances where the SchemaType is null but the schemaTypeName is not!
            //this specifically happens with xsd:anyType.
            QName schemaTypeName = xsElt.getSchemaTypeName();

            XmlSchema resolvedSchema = getParentSchema(parentSchema,schemaTypeName,COMPONENT_TYPE);
            XmlSchemaType typeByName = null;
            if (resolvedSchema != null){
               typeByName = resolvedSchema.getTypeByName(schemaTypeName);
            }

            if (typeByName != null) {
                //this type is found in the schema so we can process it
                processSchema(xsElt, typeByName, resolvedSchema, false);
                if (!isOuter) {
                    String className = findClassName(schemaTypeName, isArray(xsElt));
                    //since this is a inner element we should add it to the inner element map
                    innerElementMap.put(xsElt.getQName(), className);
                    // set the class name to be used in unwrapping
                    xsElt.addMetaInfo(SchemaConstants.SchemaCompilerInfoHolder.CLASSNAME_KEY,
                                className);
                } else {
                    this.processedElementList.add(xsElt.getQName());
                }
            } else {
                //this type is not found at all. we'll just register it with whatever the class name we can comeup with
                if (!isOuter) {
                    String className = findClassName(schemaTypeName, isArray(xsElt));
                    innerElementMap.put(xsElt.getQName(), className);
                    // set the class name to be used in unwrapping
                    xsElt.addMetaInfo(SchemaConstants.SchemaCompilerInfoHolder.CLASSNAME_KEY,
                                className);
                } else {
                    this.processedElementList.add(xsElt.getQName());
                }
            }
        }

        //add this elements QName to the nillable group if it has the  nillable attribute
        if (xsElt.isNillable()) {
            if (isOuter) {
                this.nillableElementList.add(xsElt.getQName());
            } else {
                localNillableList.add(xsElt.getQName());
            }
        }

    }

    /**
     * Generate a unique type Qname using an element name
     *
     * @param referenceEltQName
     * @param parentSchema
     */
    private QName generateTypeQName(QName referenceEltQName, XmlSchema parentSchema) {
        QName generatedTypeName = new QName(referenceEltQName.getNamespaceURI(),
                referenceEltQName.getLocalPart() + getNextTypeSuffix(referenceEltQName.getLocalPart()));
        while (parentSchema.getTypeByName(generatedTypeName) != null) {
            generatedTypeName = new QName(referenceEltQName.getNamespaceURI(),
                    referenceEltQName.getLocalPart() + getNextTypeSuffix(referenceEltQName.getLocalPart()));
        }
        return generatedTypeName;
    }

    /**
     * Finds whether a given class is already made
     *
     * @param qName
     */
    private boolean isAlreadyProcessed(QName qName) {
        return processedTypemap.containsKey(qName) ||
                simpleTypesMap.containsKey(qName) ||
                baseSchemaTypeMap.containsKey(qName) ||
                processedGroupTypeMap.containsKey(qName);
    }


    /**
     * A method to pick the ref class name
     *
     * @param name
     * @param isArray
     */
    private String findRefClassName(QName name, boolean isArray) {
        String className = null;
        if (processedElementRefMap.get(name) != null) {
            className = (String) processedElementRefMap.get(name);

            if (isArray) {
                //append the square braces that say this is an array
                //hope this works for all cases!!!!!!!
                //todo this however is a thing that needs to be
                //todo fixed to get complete language support
                className = className + "[]";
            }
        }
        return className;

    }

    /**
     * Finds a class name from the given Qname
     *
     * @param qName
     * @param isArray
     * @return FQCN
     */
    private String findClassName(QName qName, boolean isArray) throws SchemaCompilationException {

        //find the class name
        String className;
        if (processedTypemap.containsKey(qName)) {
            className = (String) processedTypemap.get(qName);
        } else if (simpleTypesMap.containsKey(qName)) {
            className = (String) simpleTypesMap.get(qName);
        } else if (baseSchemaTypeMap.containsKey(qName)) {
            className = (String) baseSchemaTypeMap.get(qName);
        } else {
            if (isSOAP_ENC(qName.getNamespaceURI())) {
                throw new SchemaCompilationException(SchemaCompilerMessages.getMessage("schema.soapencoding.error", qName.toString()));

            }
            // We seem to have failed in finding a class name for the
            //contained schema type. We better set the default then
            //however it's better if the default can be set through the
            //property file
            className = writer.getDefaultClassName();
            log.warn(SchemaCompilerMessages
                    .getMessage("schema.typeMissing", qName.toString()));
        }

        if (isArray) {
            //append the square braces that say this is an array
            //hope this works for all cases!!!!!!!
            //todo this however is a thing that needs to be
            //todo fixed to get complete language support
            className = className + "[]";
        }
        return className;
    }

    /**
     * Returns true if SOAP_ENC Namespace.
     *
     * @param s a string representing the URI to check
     * @return true if <code>s</code> matches a SOAP ENCODING namespace URI,
     *         false otherwise
     */
    public static boolean isSOAP_ENC(String s) {
        if (s.equals(Constants.URI_SOAP11_ENC))
            return true;
        return s.equals(Constants.URI_SOAP12_ENC);
    }

    /**
     * Process a schema element which has been refered to by an element
     *
     * @param schemaType
     * @throws SchemaCompilationException
     */
    private void processSchema(XmlSchemaElement xsElt,
                               XmlSchemaType schemaType,
                               XmlSchema parentSchema,
                               boolean isWriteAnonComplexType) throws SchemaCompilationException {
        if (schemaType instanceof XmlSchemaComplexType) {
            //write classes for complex types
            XmlSchemaComplexType complexType = (XmlSchemaComplexType) schemaType;
            // complex type name may not be null if we have set it
            if (complexType.getName() != null && !this.changedComplexTypeSet.contains(schemaType)) {
                // here complex type may be in another shcema so we have to find the
                // correct parent schema.
                XmlSchema resolvedSchema = getParentSchema(parentSchema,complexType.getQName(),COMPONENT_TYPE);
                if (resolvedSchema == null){
                    throw new SchemaCompilationException("can not find the parent schema for the " +
                            "complex type " + complexType.getQName() + " from the parent schema " +
                    parentSchema.getTargetNamespace());
                } else {
                   processNamedComplexSchemaType(complexType, resolvedSchema);
                }
            } else {
                processAnonymousComplexSchemaType(xsElt, complexType, parentSchema, isWriteAnonComplexType);
            }
        } else if (schemaType instanceof XmlSchemaSimpleType) {
            //process simple type
            processSimpleSchemaType((XmlSchemaSimpleType) schemaType,
                    xsElt,
                    parentSchema, null);
        }
    }


    /**
     * @param complexType
     * @throws SchemaCompilationException
     */
    private void processAnonymousComplexSchemaType(XmlSchemaElement elt,
                                                   XmlSchemaComplexType complexType,
                                                   XmlSchema parentSchema,
                                                   boolean isWriteAnonComplexType)
            throws SchemaCompilationException {

        //here we have a problem when processing the circulare element
        // references if we differ this processing
        // generate a name to the complex type and register it here
        QName generatedTypeName = null;
        String javaClassName = null;
        if (isWriteAnonComplexType) {

            generatedTypeName = generateTypeQName(elt.getQName(), parentSchema);

            if (elt.getSchemaTypeName() == null) {
                elt.setSchemaTypeName(generatedTypeName);
                this.changedElementSet.add(elt);
            }

            //set a name
            complexType.setName(generatedTypeName.getLocalPart());
            this.changedComplexTypeSet.add(complexType);

            javaClassName = writer.makeFullyQualifiedClassName(generatedTypeName);
            processedTypemap.put(generatedTypeName, javaClassName);
            this.processedElementRefMap.put(elt.getQName(), javaClassName);
            complexType.addMetaInfo(SchemaConstants.SchemaCompilerInfoHolder.CLASSNAME_KEY, javaClassName);
        }


        BeanWriterMetaInfoHolder metaInfHolder = processComplexType(elt.getQName(),complexType, parentSchema);

        // here the only difference is that we generate the class
        // irrespective of where we need it or not
        if (isWriteAnonComplexType) {
            metaInfHolder.setOwnClassName(javaClassName);
            metaInfHolder.setOwnQname(generatedTypeName);
            writeComplexType(complexType, metaInfHolder);
        }

        //since this is a special case (an unnamed complex type) we'll put the already processed
        //metainf holder in a special map to be used later
        this.processedAnonymousComplexTypesMap.put(elt, metaInfHolder);

    }

    /**
     * handle the complex types which are named
     *
     * @param complexType
     */
    private void processNamedComplexSchemaType(XmlSchemaComplexType complexType,
                                               XmlSchema parentSchema) throws SchemaCompilationException {

        if (processedTypemap.containsKey(complexType.getQName())
                || baseSchemaTypeMap.containsKey(complexType.getQName())) {
            return;
        }

        // Must do this up front to support recursive types
        String fullyQualifiedClassName = writer.makeFullyQualifiedClassName(complexType.getQName());
        processedTypemap.put(complexType.getQName(), fullyQualifiedClassName);

        //register that in the schema metainfo bag
        complexType.addMetaInfo(SchemaConstants.SchemaCompilerInfoHolder.CLASSNAME_KEY,
                fullyQualifiedClassName);

        BeanWriterMetaInfoHolder metaInfHolder = processComplexType(complexType.getQName(),complexType, parentSchema);
        //add this information to the metainfo holder
        metaInfHolder.setOwnQname(complexType.getQName());
        metaInfHolder.setOwnClassName(fullyQualifiedClassName);
        //write the class. This type mapping would have been populated right now
        //Note - We always write classes for named complex types
        writeComplexType(complexType, metaInfHolder);


    }

    /**
     * Writes a complex type
     *
     * @param complexType
     * @param metaInfHolder
     * @throws SchemaCompilationException
     */
    private String writeComplexType(XmlSchemaComplexType complexType, BeanWriterMetaInfoHolder metaInfHolder)
            throws SchemaCompilationException {
        String javaClassName = writer.write(complexType.getQName(),
                processedTypemap, processedGroupTypeMap, metaInfHolder, complexType.isAbstract());
        processedTypeMetaInfoMap.put(complexType.getQName(), metaInfHolder);
        return javaClassName;
    }


    /**
     * Writes complex Sequence,Choice, all elements
     * @param qname complex type qname
     * @param metaInfHolder
     * @return  written java class name
     * @throws SchemaCompilationException
     */


    private String writeComplexParticle(QName qname,BeanWriterMetaInfoHolder metaInfHolder)
            throws SchemaCompilationException {
       String javaClassName = writer.write(qname, processedTypemap, processedGroupTypeMap, metaInfHolder,false);
        processedTypeMetaInfoMap.put(qname, metaInfHolder);
        return javaClassName;
    }

    /**
     * Writes a complex type
     *
     * @param simpleType
     * @param metaInfHolder
     * @throws SchemaCompilationException
     */
    private void writeSimpleType(XmlSchemaSimpleType simpleType, BeanWriterMetaInfoHolder metaInfHolder)
            throws SchemaCompilationException {
        writer.write(simpleType, processedTypemap, processedGroupTypeMap, metaInfHolder);
        processedTypeMetaInfoMap.put(simpleType.getQName(), metaInfHolder);
    }

    private BeanWriterMetaInfoHolder processComplexType(
            QName parentElementQName,
            XmlSchemaComplexType complexType,
            XmlSchema parentSchema) throws SchemaCompilationException {
        XmlSchemaParticle particle = complexType.getParticle();
        BeanWriterMetaInfoHolder metaInfHolder = new BeanWriterMetaInfoHolder();
        if (particle != null) {
            //Process the particle
            processParticle(parentElementQName, particle, metaInfHolder, parentSchema);
        }

        //process attributes - first look for the explicit attributes
        processAttributes(complexType.getAttributes(),metaInfHolder,parentSchema);

        //process any attribute
        //somehow the xml schema parser does not seem to pickup the any attribute!!
        XmlSchemaAnyAttribute anyAtt = complexType.getAnyAttribute();
        if (anyAtt != null) {
            processAnyAttribute(metaInfHolder, anyAtt);
        }


        //process content ,either  complex or simple
        if (complexType.getContentModel() != null) {
            processContentModel(complexType.getContentModel(),
                    metaInfHolder,
                    parentSchema);
        }
        return metaInfHolder;
    }

    private void processAttributes(XmlSchemaObjectCollection attributes,
                                   BeanWriterMetaInfoHolder metaInfHolder,
                                   XmlSchema parentSchema) throws SchemaCompilationException {
        Iterator attribIterator = attributes.getIterator();
        while (attribIterator.hasNext()) {
            Object o = attribIterator.next();
            if (o instanceof XmlSchemaAttribute) {
                processAttribute((XmlSchemaAttribute) o, metaInfHolder, parentSchema);
            } else if (o instanceof XmlSchemaAttributeGroupRef){
                processAttributeGroupReference((XmlSchemaAttributeGroupRef)o,metaInfHolder,parentSchema);
            }
        }
    }

    private void processAttributeGroupReference(XmlSchemaAttributeGroupRef attributeGroupRef,
                                                BeanWriterMetaInfoHolder metaInfHolder,
                                                XmlSchema parentSchema) throws SchemaCompilationException {

        QName attributeGroupRefName = attributeGroupRef.getRefName();
        if (attributeGroupRefName != null){
           XmlSchema resolvedSchema = getParentSchema(parentSchema,attributeGroupRefName,COMPONENT_ATTRIBUTE_GROUP);
            if (resolvedSchema == null) {
                throw new SchemaCompilationException("can not find the attribute group reference name " +
                        attributeGroupRefName + " from the parent schema " + parentSchema.getTargetNamespace());
            } else {
                XmlSchemaAttributeGroup xmlSchemaAttributeGroup =
                        (XmlSchemaAttributeGroup) resolvedSchema.getAttributeGroups().getItem(attributeGroupRefName);
                if (xmlSchemaAttributeGroup != null) {
                    processAttributes(xmlSchemaAttributeGroup.getAttributes(), metaInfHolder, resolvedSchema);
                } else {
                    throw new SchemaCompilationException("Can not find an attribute group for group reference "
                            + attributeGroupRefName.getLocalPart());
                }
            }

        } else {
            throw new SchemaCompilationException("No group refernce has given");
        }
    }

    /**
     * Process the content models. A content model is either simple type or a complex type
     * and included inside a complex content
     */
    private void processContentModel(XmlSchemaContentModel content,
                                     BeanWriterMetaInfoHolder metaInfHolder,
                                     XmlSchema parentSchema)
            throws SchemaCompilationException {
        if (content instanceof XmlSchemaComplexContent) {
            processComplexContent((XmlSchemaComplexContent) content, metaInfHolder, parentSchema);
        } else if (content instanceof XmlSchemaSimpleContent) {
            processSimpleContent((XmlSchemaSimpleContent) content, metaInfHolder, parentSchema);
        }
    }

    /**
     * Prcess the complex content
     */
    private void processComplexContent(XmlSchemaComplexContent complexContent,
                                       BeanWriterMetaInfoHolder metaInfHolder,
                                       XmlSchema parentSchema)
            throws SchemaCompilationException {
        XmlSchemaContent content = complexContent.getContent();

        if (content instanceof XmlSchemaComplexContentExtension) {

            // to handle extension we need to attach the extended items to the base type
            // and create a new type
            XmlSchemaComplexContentExtension extension = (XmlSchemaComplexContentExtension)content;

            //process the base type if it has not been processed yet
            if (!isAlreadyProcessed(extension.getBaseTypeName())) {
                //pick the relevant basetype from the schema and process it
                XmlSchema resolvedSchema = getParentSchema(parentSchema, extension.getBaseTypeName(), COMPONENT_TYPE);
                if (resolvedSchema == null) {
                    throw new SchemaCompilationException("can not find the compley type " + extension.getBaseTypeName()
                            + " from the parent type " + parentSchema.getTargetNamespace());
                } else {
                    XmlSchemaType type = resolvedSchema.getTypeByName(extension.getBaseTypeName());
                    if (type instanceof XmlSchemaComplexType) {
                        XmlSchemaComplexType complexType = (XmlSchemaComplexType) type;
                        if (complexType.getName() != null) {
                            processNamedComplexSchemaType(complexType, resolvedSchema);
                        } else {
                            //this is not possible. The extension should always
                            //have a name
                            throw new SchemaCompilationException("Unnamed complex type used in extension");//Internationlize this
                        }
                    } else if (type instanceof XmlSchemaSimpleType) {
                        //process simple type
                        processSimpleSchemaType((XmlSchemaSimpleType) type, null, resolvedSchema, null);
                    }
                }

            }

            // before actually processing this node, we need to recurse through the base types and add their
            // children (sometimes even preserving the order) to the metainfo holder of this type
            // the reason is that for extensions, the prefered way is to have the sequences of the base class
            //* before * the sequence of the child element.
            copyMetaInfoHierarchy(metaInfHolder, extension.getBaseTypeName(), parentSchema);

            //process the particle of this node
            if (extension.getParticle() != null) {
                processParticle(extension.getBaseTypeName(),extension.getParticle(), metaInfHolder, parentSchema);
            }

            // process attributes
            //process attributes - first look for the explicit attributes
            processAttributes(extension.getAttributes(),metaInfHolder,parentSchema);

            //process any attribute
            //somehow the xml schema parser does not seem to pickup the any attribute!!
            XmlSchemaAnyAttribute anyAtt = extension.getAnyAttribute();
            if (anyAtt != null) {
                processAnyAttribute(metaInfHolder, anyAtt);
            }
            String className = findClassName(extension.getBaseTypeName(), false);

            if (!writer.getDefaultClassName().equals(className)) {
                //the particle has been processed, However since this is an extension we need to
                //add the basetype as an extension to the complex type class.
                // The basetype has been processed already
                metaInfHolder.setExtension(true);
                metaInfHolder.setExtensionClassName(className);
                //Note  - this is no array! so the array boolean is false
            }
        } else if (content instanceof XmlSchemaComplexContentRestriction) {
            XmlSchemaComplexContentRestriction restriction = (XmlSchemaComplexContentRestriction) content;

            //process the base type if it has not been processed yet
            if (!isAlreadyProcessed(restriction.getBaseTypeName())) {
                //pick the relevant basetype from the schema and process it
                XmlSchema resolvedSchema = getParentSchema(parentSchema, restriction.getBaseTypeName(), COMPONENT_TYPE);
                if (resolvedSchema == null) {
                    throw new SchemaCompilationException("can not find the complex type " + restriction.getBaseTypeName()
                            + " from the parent type " + parentSchema.getTargetNamespace());
                } else {
                    XmlSchemaType type = resolvedSchema.getTypeByName(restriction.getBaseTypeName());
                    if (type instanceof XmlSchemaComplexType) {
                        XmlSchemaComplexType complexType = (XmlSchemaComplexType) type;
                        if (complexType.getName() != null) {
                            processNamedComplexSchemaType(complexType, resolvedSchema);
                        } else {
                            //this is not possible. The restriction should always
                            //have a name
                            throw new SchemaCompilationException("Unnamed complex type used in restriction");//Internationlize this
                        }
                    } else if (type instanceof XmlSchemaSimpleType) {
                        throw new SchemaCompilationException("Not a valid restriction, complex content restriction base type cannot be a simple type.");
                    }
                }
            }

            copyMetaInfoHierarchy(metaInfHolder, restriction.getBaseTypeName(), parentSchema);

            //process the particle of this node
            processParticle(restriction.getBaseTypeName(),restriction.getParticle(), metaInfHolder, parentSchema);

            //process attributes - first look for the explicit attributes
            processAttributes(restriction.getAttributes(),metaInfHolder,parentSchema);

            //process any attribute
            //somehow the xml schema parser does not seem to pickup the any attribute!!
            XmlSchemaAnyAttribute anyAtt = restriction.getAnyAttribute();
            if (anyAtt != null) {
                processAnyAttribute(metaInfHolder, anyAtt);
            }
            String className = findClassName(restriction.getBaseTypeName(), false);

            if (!writer.getDefaultClassName().equals(className)) {
                metaInfHolder.setRestriction(true);
                metaInfHolder.setRestrictionClassName(findClassName(restriction.getBaseTypeName(), false));
                //Note  - this is no array! so the array boolean is false
            }
        }
    }

    /**
     * Recursive method to populate the metainfo holders with info from the base types
     *
     * @param metaInfHolder
     * @param baseTypeName
     * @param parentSchema
     */
    private void copyMetaInfoHierarchy(BeanWriterMetaInfoHolder metaInfHolder,
                                       QName baseTypeName,
                                       XmlSchema parentSchema)
            throws SchemaCompilationException {

        XmlSchema resolvedSchema = getParentSchema(parentSchema,baseTypeName,COMPONENT_TYPE);
        if (resolvedSchema == null) {
            throw new SchemaCompilationException("can not find type " + baseTypeName
                    + " from the parent schema " + parentSchema.getTargetNamespace());
        } else {
            XmlSchemaType type = resolvedSchema.getTypeByName(baseTypeName);
            BeanWriterMetaInfoHolder baseMetaInfoHolder = (BeanWriterMetaInfoHolder)
                    processedTypeMetaInfoMap.get(baseTypeName);


            if (baseMetaInfoHolder != null) {

                // see whether this type is also extended from some other type first
                // if so proceed to set their parents as well.
                if (type instanceof XmlSchemaComplexType) {
                    XmlSchemaComplexType complexType = (XmlSchemaComplexType) type;
                    if (complexType.getContentModel() != null) {
                        XmlSchemaContentModel content = complexType.getContentModel();
                        if (content instanceof XmlSchemaComplexContent) {
                            XmlSchemaComplexContent complexContent =
                                    (XmlSchemaComplexContent) content;
                            if (complexContent.getContent() instanceof XmlSchemaComplexContentExtension) {
                                XmlSchemaComplexContentExtension extension =
                                        (XmlSchemaComplexContentExtension) complexContent.getContent();
                                //recursively call the copyMetaInfoHierarchy method
                                copyMetaInfoHierarchy(baseMetaInfoHolder,
                                        extension.getBaseTypeName(),
                                        resolvedSchema);

                            } else if (complexContent.getContent() instanceof XmlSchemaComplexContentRestriction) {

                                XmlSchemaComplexContentRestriction restriction =
                                        (XmlSchemaComplexContentRestriction) complexContent.getContent();
                                //recursively call the copyMetaInfoHierarchy method
                                copyMetaInfoHierarchy(baseMetaInfoHolder,
                                        restriction.getBaseTypeName(),
                                        resolvedSchema);

                            } else {
                                throw new SchemaCompilationException(
                                        SchemaCompilerMessages.getMessage("schema.unknowncontenterror"));
                            }

                        } else if (content instanceof XmlSchemaSimpleContent) {
                            throw new SchemaCompilationException(
                                    SchemaCompilerMessages.getMessage("schema.unsupportedcontenterror", "Simple Content"));
                        } else {
                            throw new SchemaCompilationException(
                                    SchemaCompilerMessages.getMessage("schema.unknowncontenterror"));
                        }
                    }
                    //Do the actual parent setting
                    metaInfHolder.setAsParent(baseMetaInfoHolder);

                } else if (type instanceof XmlSchemaSimpleType) {

                    // we have to copy the uion data if the parent simple type restriction
                    // is an union
                    // this union attribute is copied from the child to parent to genrate the parent
                    // code as union
                    if (baseMetaInfoHolder.isUnion()) {
                        metaInfHolder.setUnion(true);
                        Map memberTypes = baseMetaInfoHolder.getMemberTypes();
                        Object qname;
                        for (Iterator iter = memberTypes.keySet().iterator(); iter.hasNext();) {
                            qname = iter.next();
                            metaInfHolder.addMemberType((QName) qname, (String) memberTypes.get(qname));
                        }
                    }

                    // we have to copy the list type data to parent if it is a list
                    if (baseMetaInfoHolder.isList()) {
                        metaInfHolder.setList(true);
                        metaInfHolder.setItemTypeQName(baseMetaInfoHolder.getItemTypeQName());
                        metaInfHolder.setItemTypeClassName(baseMetaInfoHolder.getItemTypeClassName());
                    }
                    metaInfHolder.setAsParent(baseMetaInfoHolder);
                }

            }
        }
    }

    /**
     * @param simpleContent
     * @param metaInfHolder
     * @throws SchemaCompilationException
     */
    private void processSimpleContent(XmlSchemaSimpleContent simpleContent, BeanWriterMetaInfoHolder metaInfHolder, XmlSchema parentSchema)
            throws SchemaCompilationException {
        XmlSchemaContent content;
        content = simpleContent.getContent();
        if (content instanceof XmlSchemaSimpleContentExtension) {
            XmlSchemaSimpleContentExtension extension = (XmlSchemaSimpleContentExtension) content;

            //process the base type if it has not been processed yet
            if (!isAlreadyProcessed(extension.getBaseTypeName())) {
                //pick the relevant basetype from the schema and process it
                XmlSchema resolvedSchema = getParentSchema(parentSchema, extension.getBaseTypeName(), COMPONENT_TYPE);
                if (resolvedSchema == null) {
                    throw new SchemaCompilationException("can not find type " + extension.getBaseTypeName()
                            + " from the parent schema " + parentSchema.getTargetNamespace());
                } else {
                    XmlSchemaType type = resolvedSchema.getTypeByName(extension.getBaseTypeName());
                    if (type instanceof XmlSchemaComplexType) {
                        XmlSchemaComplexType complexType = (XmlSchemaComplexType) type;
                        if (complexType.getName() != null) {
                            processNamedComplexSchemaType(complexType, resolvedSchema);
                        } else {
                            //this is not possible. The extension should always
                            //have a name
                            throw new SchemaCompilationException("Unnamed complex type used in extension");//Internationlize this
                        }
                    } else if (type instanceof XmlSchemaSimpleType) {
                        //process simple type
                        processSimpleSchemaType((XmlSchemaSimpleType) type, null, resolvedSchema, null);
                    }
                }

            }

            //process extension base type
            processSimpleExtensionBaseType(extension.getBaseTypeName(), metaInfHolder, parentSchema);

            //process attributes
            XmlSchemaObjectCollection attribs = extension.getAttributes();
            Iterator attribIterator = attribs.getIterator();
            while (attribIterator.hasNext()) {
                Object attr = attribIterator.next();
                if (attr instanceof XmlSchemaAttribute) {
                    processAttribute((XmlSchemaAttribute) attr, metaInfHolder, parentSchema);

                }
            }

            //process any attribute
            XmlSchemaAnyAttribute anyAtt = extension.getAnyAttribute();
            if (anyAtt != null) {
                processAnyAttribute(metaInfHolder, anyAtt);
            }

        } else if (content instanceof XmlSchemaSimpleContentRestriction) {
            XmlSchemaSimpleContentRestriction restriction = (XmlSchemaSimpleContentRestriction) content;

            //process the base type if it has not been processed yet
            if (!isAlreadyProcessed(restriction.getBaseTypeName())) {
                //pick the relevant basetype from the schema and process it
                XmlSchema resolvedSchema = getParentSchema(parentSchema, restriction.getBaseTypeName(), COMPONENT_TYPE);
                if (resolvedSchema == null) {
                    throw new SchemaCompilationException("can not find type " + restriction.getBaseTypeName()
                            + " from the parent schema " + parentSchema.getTargetNamespace());
                } else {
                    XmlSchemaType type = resolvedSchema.getTypeByName(restriction.getBaseTypeName());

                    if (type instanceof XmlSchemaComplexType) {
                        XmlSchemaComplexType complexType = (XmlSchemaComplexType) type;
                        if (complexType.getName() != null) {
                            processNamedComplexSchemaType(complexType, resolvedSchema);
                        } else {
                            //this is not possible. The extension should always
                            //have a name
                            throw new SchemaCompilationException("Unnamed complex type used in restriction");//Internationlize this
                        }
                    } else if (type instanceof XmlSchemaSimpleType) {
                        //process simple type
                        processSimpleSchemaType((XmlSchemaSimpleType) type, null, resolvedSchema, null);
                    }
                }

            }
            //process restriction base type
            processSimpleRestrictionBaseType(restriction.getBaseTypeName(),
                    restriction.getBaseTypeName(),
                    metaInfHolder,
                    parentSchema);
            metaInfHolder.setSimple(true);
        }
    }

    /**
     * Process Simple Extension Base Type.
     *
     * @param extBaseType
     * @param metaInfHolder
     */
    public void processSimpleExtensionBaseType(QName extBaseType,
                                               BeanWriterMetaInfoHolder metaInfHolder,
                                               XmlSchema parentSchema) throws SchemaCompilationException {

        //find the class name
        String className = findClassName(extBaseType, false);

        // if the base type is an primitive then we do not have to extend them
        // and it is considered as a property
        // on the otherhand if the base type is an generated class then we have to
        // extend from it

        if (baseSchemaTypeMap.containsKey(extBaseType)) {
            //this means the schema type actually returns a different QName
            if (changedTypeMap.containsKey(extBaseType)) {
                metaInfHolder.registerMapping(extBaseType,
                        (QName) changedTypeMap.get(extBaseType),
                        className, SchemaConstants.ELEMENT_TYPE);
            } else {
                metaInfHolder.registerMapping(extBaseType,
                        extBaseType,
                        className, SchemaConstants.ELEMENT_TYPE);
            }
            metaInfHolder.setSimple(true);
            // we have already process when it comes to this place
        } else if (processedTypemap.containsKey(extBaseType)) {
            //set the extension base class name
            XmlSchema resolvedSchema = getParentSchema(parentSchema,extBaseType,COMPONENT_TYPE);
            if (resolvedSchema == null) {
                throw new SchemaCompilationException("can not find the type " + extBaseType
                        + " from the parent schema " + parentSchema.getTargetNamespace());
            } else {
                XmlSchemaType type = resolvedSchema.getTypeByName(extBaseType);
                if (type instanceof XmlSchemaSimpleType) {
                    metaInfHolder.setSimple(true);
                    metaInfHolder.setExtension(true);
                    metaInfHolder.setExtensionClassName(className);

                    copyMetaInfoHierarchy(metaInfHolder, extBaseType, resolvedSchema);
                } else if (type instanceof XmlSchemaComplexType) {
                    XmlSchemaComplexType complexType = (XmlSchemaComplexType) type;
                    if (complexType.getContentModel() == null) {
                        // do not set as a simple type since we want to
                        // print the element names
                        metaInfHolder.setExtension(true);
                        metaInfHolder.setExtensionClassName(className);
                        copyMetaInfoHierarchy(metaInfHolder, extBaseType, resolvedSchema);
                    }
                }
            }

        } else {
            metaInfHolder.setSimple(true);
        }

        //get the binary state and add that to the status map
        if (isBinary(extBaseType)) {
            metaInfHolder.addtStatus(extBaseType,
                    SchemaConstants.BINARY_TYPE);
        }
    }

    /**
     * Process Simple Restriction Base Type.
     *
     * @param resBaseType
     * @param metaInfHolder
     */
    public void processSimpleRestrictionBaseType(QName qName,
                                                 QName resBaseType,
                                                 BeanWriterMetaInfoHolder metaInfHolder,
                                                 XmlSchema parentSchema) throws SchemaCompilationException {

        //find the class name
        String className = findClassName(resBaseType, false);

        //this means the schema type actually returns a different QName
        if (baseSchemaTypeMap.containsKey(resBaseType)) {
            if (changedTypeMap.containsKey(resBaseType)) {
                metaInfHolder.registerMapping(qName,
                        (QName) changedTypeMap.get(resBaseType),
                        className, SchemaConstants.ELEMENT_TYPE);
            } else {
                metaInfHolder.registerMapping(qName,
                        resBaseType,
                        className, SchemaConstants.ELEMENT_TYPE);
            }
        } else if (processedTypemap.containsKey(resBaseType)) {
            //this is not a standared type
            // so the parent class must extend it
            metaInfHolder.setSimple(true);
            metaInfHolder.setRestriction(true);
            metaInfHolder.setRestrictionClassName(className);
            copyMetaInfoHierarchy(metaInfHolder, resBaseType, parentSchema);
        }

        metaInfHolder.setRestrictionBaseType(resBaseType);


    }

    /**
     * Process Facets.
     *
     * @param metaInfHolder
     */
    private void processFacets(XmlSchemaSimpleTypeRestriction restriction,
                               BeanWriterMetaInfoHolder metaInfHolder,
                               XmlSchema parentSchema) {

        XmlSchemaObjectCollection facets = restriction.getFacets();
        Iterator facetIterator = facets.getIterator();

        while (facetIterator.hasNext()) {
            Object obj = facetIterator.next();

            if (obj instanceof XmlSchemaPatternFacet) {
                XmlSchemaPatternFacet pattern = (XmlSchemaPatternFacet) obj;
                // some patterns contain \ so we have to replace them
                String patternString = pattern.getValue().toString();
                // replace backword slashes
                patternString = patternString.replaceAll("\\\\", "\\\\\\\\");
                if ((metaInfHolder.getPatternFacet() != null) &&
                        (metaInfHolder.getPatternFacet().trim().length() > 0)){
                    // i.e there is a pattern faceset
                    patternString = metaInfHolder.getPatternFacet().trim() + "|" + patternString;
                }
                metaInfHolder.setPatternFacet(patternString);
            }

            else if (obj instanceof XmlSchemaEnumerationFacet) {
                XmlSchemaEnumerationFacet enumeration = (XmlSchemaEnumerationFacet) obj;
                if (restriction.getBaseTypeName().equals(SchemaConstants.XSD_QNAME)) {
                    // we have to process the qname here and shoud find the local part and namespace uri
                    String value = enumeration.getValue().toString();
                    String prefix = value.substring(0, value.indexOf(":"));
                    String localPart = value.substring(value.indexOf(":") + 1);

                    String namespaceUri = parentSchema.getNamespaceContext().getNamespaceURI(prefix);
                    // set the string to suite for the convertQname method
                    String qNameString = value + "\", \"" + namespaceUri;
                    metaInfHolder.addEnumFacet(qNameString);
                } else {
                    metaInfHolder.addEnumFacet(enumeration.getValue().toString());
                }

            }

            else if (obj instanceof XmlSchemaLengthFacet) {
                XmlSchemaLengthFacet length = (XmlSchemaLengthFacet) obj;
                metaInfHolder.setLengthFacet(Integer.parseInt(length.getValue().toString()));
            }

            else if (obj instanceof XmlSchemaMaxExclusiveFacet) {
                XmlSchemaMaxExclusiveFacet maxEx = (XmlSchemaMaxExclusiveFacet) obj;
                metaInfHolder.setMaxExclusiveFacet(maxEx.getValue().toString());
            }

            else if (obj instanceof XmlSchemaMinExclusiveFacet) {
                XmlSchemaMinExclusiveFacet minEx = (XmlSchemaMinExclusiveFacet) obj;
                metaInfHolder.setMinExclusiveFacet(minEx.getValue().toString());
            }

            else if (obj instanceof XmlSchemaMaxInclusiveFacet) {
                XmlSchemaMaxInclusiveFacet maxIn = (XmlSchemaMaxInclusiveFacet) obj;
                metaInfHolder.setMaxInclusiveFacet(maxIn.getValue().toString());
            }

            else if (obj instanceof XmlSchemaMinInclusiveFacet) {
                XmlSchemaMinInclusiveFacet minIn = (XmlSchemaMinInclusiveFacet) obj;
                metaInfHolder.setMinInclusiveFacet(minIn.getValue().toString());
            }

            else if (obj instanceof XmlSchemaMaxLengthFacet) {
                XmlSchemaMaxLengthFacet maxLen = (XmlSchemaMaxLengthFacet) obj;
                metaInfHolder.setMaxLengthFacet(Integer.parseInt(maxLen.getValue().toString()));
            }

            else if (obj instanceof XmlSchemaMinLengthFacet) {
                XmlSchemaMinLengthFacet minLen = (XmlSchemaMinLengthFacet) obj;
                metaInfHolder.setMinLengthFacet(Integer.parseInt(minLen.getValue().toString()));
            }
        }
    }

    /**
     * Handle any attribute
     *
     * @param metainf
     */
    private void processAnyAttribute(BeanWriterMetaInfoHolder metainf, XmlSchemaAnyAttribute anyAtt) {

        //The best thing we can do here is to add a set of OMAttributes
        //since attributes do not have the notion of minoccurs/maxoccurs the
        //safest option here is to have an OMAttribute array
        QName qName = new QName(EXTRA_ATTRIBUTE_FIELD_NAME);
        metainf.registerMapping(qName,
                null,
                writer.getDefaultAttribArrayClassName(),//always generate an array of
                //OMAttributes
                SchemaConstants.ANY_TYPE);
        metainf.addtStatus(qName, SchemaConstants.ATTRIBUTE_TYPE);
        metainf.addtStatus(qName, SchemaConstants.ARRAY_TYPE);

    }


    /**
     * Process the attribute
     *
     * @param att
     * @param metainf
     */
    public void processAttribute(XmlSchemaAttribute att, BeanWriterMetaInfoHolder metainf, XmlSchema parentSchema)
            throws SchemaCompilationException {

        QName schemaTypeName = att.getSchemaTypeName();
        if (schemaTypeName != null) {
            if (att.getQName() != null) {
                if (baseSchemaTypeMap.containsKey(schemaTypeName)) {

                    metainf.registerMapping(att.getQName(), schemaTypeName,
                            baseSchemaTypeMap.get(schemaTypeName).toString(), SchemaConstants.ATTRIBUTE_TYPE);

                    // add optional attribute status if set
                    String use = att.getUse().getValue();
                    if (USE_NONE.equals(use) || USE_OPTIONAL.equals(use)) {
                        metainf.addtStatus(att.getQName(), SchemaConstants.OPTIONAL_TYPE);
                    }

                    String className = findClassName(schemaTypeName, false);

                    att.addMetaInfo(
                            SchemaConstants.SchemaCompilerInfoHolder.CLASSNAME_KEY,
                            className);
                    // set the default value
                    if (att.getDefaultValue() != null){
                        metainf.registerDefaultValue(att.getQName(),att.getDefaultValue());
                    }
                    // after
                } else {
                    XmlSchema resolvedSchema = getParentSchema(parentSchema,schemaTypeName,COMPONENT_TYPE);
                    if (resolvedSchema == null) {
                        throw new SchemaCompilationException("can not find the type " + schemaTypeName +
                                " from the parent schema " + parentSchema.getTargetNamespace());
                    } else {
                        XmlSchemaType type = resolvedSchema.getTypeByName(schemaTypeName);
                        if (type instanceof XmlSchemaSimpleType) {
                            XmlSchemaSimpleType simpleType = (XmlSchemaSimpleType) type;

                            if (simpleType != null) {
                                if (!isAlreadyProcessed(schemaTypeName)) {
                                    //process simple type
                                    processSimpleSchemaType(simpleType, null, resolvedSchema, null);
                                }
                                metainf.registerMapping(att.getQName(),
                                        schemaTypeName,
                                        processedTypemap.get(schemaTypeName).toString(),
                                        SchemaConstants.ATTRIBUTE_TYPE);
                                // add optional attribute status if set
                                String use = att.getUse().getValue();
                                if (USE_NONE.equals(use) || USE_OPTIONAL.equals(use)) {
                                    metainf.addtStatus(att.getQName(), SchemaConstants.OPTIONAL_TYPE);
                                }
                            }

                        }
                    }
                }
            } else {
                // this attribute has a type but does not have a name, seems to be invalid
            }

        } else if (att.getRefName() != null) {

            XmlSchema resolvedSchema = getParentSchema(parentSchema,att.getRefName(),COMPONENT_ATTRIBUTE);
            if (resolvedSchema == null){
                throw new SchemaCompilationException("can not find the attribute " + att.getRefName() +
                " from the parent schema " + parentSchema.getTargetNamespace());
            } else {
                XmlSchemaAttribute xmlSchemaAttribute =
                        (XmlSchemaAttribute) resolvedSchema.getAttributes().getItem(att.getRefName());
                if (xmlSchemaAttribute != null) {
                    // call recursively to process the schema
                    processAttribute(xmlSchemaAttribute, metainf, resolvedSchema);
                } else {
                    throw new SchemaCompilationException("Attribute QName reference refer to an invalid attribute " +
                            att.getRefName());
                }
            }

        } else {
            // this attribute refers to a custom type, probably one of the extended simple types.
            // with the inline schema definition
            QName attributeQName = att.getQName();
            if (attributeQName != null) {
                XmlSchemaSimpleType attributeSimpleType = att.getSchemaType();
                XmlSchema resolvedSchema = parentSchema;
                if (attributeSimpleType == null) {
                    // try to get the schema for using qname
                    QName attributeSchemaQname = att.getSchemaTypeName();
                    if (attributeSchemaQname != null) {
                        resolvedSchema = getParentSchema(parentSchema,attributeSchemaQname,COMPONENT_TYPE);
                        if (resolvedSchema == null){
                            throw new SchemaCompilationException("can not find the type " + attributeSchemaQname
                              + " from the parent schema " + parentSchema.getTargetNamespace());
                        } else {
                            attributeSimpleType = (XmlSchemaSimpleType)
                                    resolvedSchema.getTypeByName(attributeSchemaQname);
                        }
                    }
                }

                if (attributeSimpleType != null) {
                    QName schemaTypeQName = att.getSchemaTypeName();
                    if (schemaTypeQName == null) {
                        // set the parent schema target name space since attribute Qname uri is ""
                        if (attributeSimpleType.getQName() != null) {
                            schemaTypeQName = attributeSimpleType.getQName();
                        } else {
                            schemaTypeQName = new QName(parentSchema.getTargetNamespace(),
                                    attributeQName.getLocalPart() + getNextTypeSuffix(attributeQName.getLocalPart()));

                        }
                    }
                    if (!isAlreadyProcessed(schemaTypeQName)){
                        // we have to process only if it has not processed
                        processSimpleSchemaType(attributeSimpleType, null, resolvedSchema, schemaTypeQName);
                    }
                    metainf.registerMapping(att.getQName(),
                            schemaTypeQName,
                            processedTypemap.get(schemaTypeQName).toString(),
                            SchemaConstants.ATTRIBUTE_TYPE);
                    // add optional attribute status if set
                    String use = att.getUse().getValue();
                    if (USE_NONE.equals(use) || USE_OPTIONAL.equals(use)) {
                        metainf.addtStatus(att.getQName(), SchemaConstants.OPTIONAL_TYPE);
                    }
                } else {
                    // TODO: handle the case when no attribute type specifed
                    log.warn("No attribute type has defined to the Attribute " + attributeQName);
                }

            } else {
                throw new SchemaCompilationException("Attribute QName reference refer to an invalid attribute " +
                        attributeQName);
            }

        }
    }

    /**
     * Process a particle- A particle may be a sequence,all or a choice
     * @param parentElementQName - this can either be parent element QName or parent Complex type qname
     * @param particle - particle being processed
     * @param metainfHolder -
     * @param parentSchema
     * @throws SchemaCompilationException
     */
    private void processParticle(QName parentElementQName,
                                 XmlSchemaParticle particle,
                                 BeanWriterMetaInfoHolder metainfHolder
            , XmlSchema parentSchema) throws SchemaCompilationException {

        if (particle instanceof XmlSchemaSequence) {
            XmlSchemaSequence xmlSchemaSequence = (XmlSchemaSequence) particle;

            XmlSchemaObjectCollection items = xmlSchemaSequence.getItems();
            if ((xmlSchemaSequence.getMaxOccurs() > 1) && (parentElementQName != null)) {
                // we have to process many sequence types
                BeanWriterMetaInfoHolder beanWriterMetaInfoHolder = new BeanWriterMetaInfoHolder();
                process(parentElementQName, items, beanWriterMetaInfoHolder, true, parentSchema);
                beanWriterMetaInfoHolder.setParticleClass(true);
                QName sequenceQName = new QName(parentElementQName.getNamespaceURI(),
                         parentElementQName.getLocalPart() + "Sequence");
                String javaClassName = writeComplexParticle(sequenceQName,beanWriterMetaInfoHolder);
                processedTypemap.put(sequenceQName, javaClassName);

                // add this as an array to the original class
                metainfHolder.registerMapping(sequenceQName,
                        sequenceQName,
                        findClassName(sequenceQName,true),
                        SchemaConstants.ARRAY_TYPE);
                metainfHolder.setOrdered(true);
                metainfHolder.registerQNameIndex(sequenceQName,metainfHolder.getOrderStartPoint() + 1);
                metainfHolder.setHasParticleType(true);
                metainfHolder.addtStatus(sequenceQName,SchemaConstants.PARTICLE_TYPE_ELEMENT);
                metainfHolder.addMaxOccurs(sequenceQName,xmlSchemaSequence.getMaxOccurs());
                metainfHolder.addMinOccurs(sequenceQName,xmlSchemaSequence.getMinOccurs());


            } else {
                if (options.isBackwordCompatibilityMode()) {
                    process(parentElementQName,items, metainfHolder, false, parentSchema);
                } else {
                    process(parentElementQName,items, metainfHolder, true, parentSchema);
                }
            }

        } else if (particle instanceof XmlSchemaAll) {
            XmlSchemaObjectCollection items = ((XmlSchemaAll) particle).getItems();
            process(parentElementQName,items, metainfHolder, false, parentSchema);
        } else if (particle instanceof XmlSchemaChoice) {
            XmlSchemaChoice xmlSchemaChoice = (XmlSchemaChoice) particle;
            XmlSchemaObjectCollection items = ((XmlSchemaChoice) particle).getItems();

            if ((xmlSchemaChoice.getMaxOccurs() > 1)) {
                // we have to process many sequence types
                BeanWriterMetaInfoHolder beanWriterMetaInfoHolder = new BeanWriterMetaInfoHolder();
                beanWriterMetaInfoHolder.setChoice(true);
                process(parentElementQName,items, beanWriterMetaInfoHolder, false, parentSchema);
                beanWriterMetaInfoHolder.setParticleClass(true);
                QName choiceQName = new QName(parentElementQName.getNamespaceURI(),
                         parentElementQName.getLocalPart() + "Choice");
                String javaClassName = writeComplexParticle(choiceQName,beanWriterMetaInfoHolder);
                processedTypemap.put(choiceQName, javaClassName);

                // add this as an array to the original class
                metainfHolder.registerMapping(choiceQName,
                        choiceQName,
                        findClassName(choiceQName,true),
                        SchemaConstants.ARRAY_TYPE);
                metainfHolder.setOrdered(true);
                metainfHolder.setHasParticleType(true);
                metainfHolder.registerQNameIndex(choiceQName,metainfHolder.getOrderStartPoint() + 1);
                metainfHolder.addtStatus(choiceQName,SchemaConstants.PARTICLE_TYPE_ELEMENT);
                metainfHolder.addMaxOccurs(choiceQName,xmlSchemaChoice.getMaxOccurs());
                metainfHolder.addMinOccurs(choiceQName,xmlSchemaChoice.getMinOccurs());

            } else {
                metainfHolder.setChoice(true);
                process(parentElementQName,items, metainfHolder, false, parentSchema);
            }


        } else if (particle instanceof XmlSchemaGroupRef){

            XmlSchemaGroupRef xmlSchemaGroupRef = (XmlSchemaGroupRef) particle;
            QName groupQName = xmlSchemaGroupRef.getRefName();
            if (groupQName != null) {
                if (!processedGroupTypeMap.containsKey(groupQName)) {
                    // processe the schema here
                    XmlSchema resolvedParentSchema = getParentSchema(parentSchema,groupQName,COMPONENT_GROUP);
                    if (resolvedParentSchema == null){
                        throw new SchemaCompilationException("can not find the group " + groupQName
                         + " from the parent schema " + parentSchema.getTargetNamespace());
                    } else {
                        XmlSchemaGroup xmlSchemaGroup = (XmlSchemaGroup)
                                resolvedParentSchema.getGroups().getItem(groupQName);
                        processGroup(xmlSchemaGroup, groupQName, resolvedParentSchema);
                    }
                }
            } else {
                throw new SchemaCompilationException("Referenced name is null");
            }
            boolean isArray = xmlSchemaGroupRef.getMaxOccurs() > 1;

            // add this as an array to the original class
            String groupClassName = (String) processedGroupTypeMap.get(groupQName);
            if (isArray){
                groupClassName = groupClassName + "[]";
            }
            metainfHolder.registerMapping(groupQName, groupQName, groupClassName);
            if (isArray) {
                metainfHolder.addtStatus(groupQName, SchemaConstants.ARRAY_TYPE);
            }
            metainfHolder.addtStatus(groupQName, SchemaConstants.PARTICLE_TYPE_ELEMENT);
            metainfHolder.addMaxOccurs(groupQName, xmlSchemaGroupRef.getMaxOccurs());
            metainfHolder.addMinOccurs(groupQName, xmlSchemaGroupRef.getMinOccurs());
            metainfHolder.setHasParticleType(true);
            metainfHolder.setOrdered(true);
            metainfHolder.registerQNameIndex(groupQName,metainfHolder.getOrderStartPoint() + 1);

        }
    }

    /**
     *
     * @param parentElementQName - this could either be the complex type parentElementQName or element parentElementQName
     * @param items
     * @param metainfHolder
     * @param order
     * @param parentSchema
     * @throws SchemaCompilationException
     */
    private void process(QName parentElementQName,
                         XmlSchemaObjectCollection items,
                         BeanWriterMetaInfoHolder metainfHolder,
                         boolean order,
                         XmlSchema parentSchema) throws SchemaCompilationException {
        int count = items.getCount();
        Map processedElementArrayStatusMap = new LinkedHashMap();
        Map processedElementTypeMap = new LinkedHashMap();
        List localNillableList = new ArrayList();

        Map particleQNameMap = new HashMap();

        // this list is used to keep the details of the
        // elements within a choice withing sequence
        List innerChoiceElementList = new ArrayList();

        Map elementOrderMap = new HashMap();

        int sequenceCounter = 0;
        for (int i = 0; i < count; i++) {
            XmlSchemaObject item = items.getItem(i);

            if (item instanceof XmlSchemaElement) {
                //recursively process the element
                XmlSchemaElement xsElt = (XmlSchemaElement) item;

                boolean isArray = isArray(xsElt);
                processElement(xsElt, processedElementTypeMap, localNillableList, parentSchema); //we know for sure this is not an outer type
                processedElementArrayStatusMap.put(xsElt, (isArray) ? Boolean.TRUE : Boolean.FALSE);
                if (order) {
                    //we need to keep the order of the elements. So push the elements to another
                    //hashmap with the order number
                    elementOrderMap.put(xsElt, new Integer(sequenceCounter));
                }

                //handle xsd:any ! We place an OMElement (or an array of OMElements) in the generated class
            } else if (item instanceof XmlSchemaAny) {
                XmlSchemaAny any = (XmlSchemaAny) item;
                processedElementTypeMap.put(new QName(ANY_ELEMENT_FIELD_NAME), any);
                //any can also be inside a sequence
                if (order) {
                    elementOrderMap.put(any, new Integer(sequenceCounter));
                }
                //we do not register the array status for the any type
                processedElementArrayStatusMap.put(any, isArray(any) ? Boolean.TRUE : Boolean.FALSE);
            } else if (item instanceof XmlSchemaSequence) {
                // we have to process many sequence types

                XmlSchemaSequence xmlSchemaSequence = (XmlSchemaSequence) item;
                if (xmlSchemaSequence.getItems().getCount() > 0) {
                    BeanWriterMetaInfoHolder beanWriterMetaInfoHolder = new BeanWriterMetaInfoHolder();
                    process(parentElementQName, xmlSchemaSequence.getItems(), beanWriterMetaInfoHolder, true, parentSchema);
                    beanWriterMetaInfoHolder.setParticleClass(true);
                    String localName = parentElementQName.getLocalPart() + "Sequence";
                    QName sequenceQName = new QName(parentElementQName.getNamespaceURI(),
                            localName + getNextTypeSuffix(localName));
                    String javaClassName = writeComplexParticle(sequenceQName, beanWriterMetaInfoHolder);
                    processedTypemap.put(sequenceQName, javaClassName);

                    //put the partical to array
                    Boolean isArray = xmlSchemaSequence.getMaxOccurs() > 1 ? Boolean.TRUE : Boolean.FALSE;
                    processedElementArrayStatusMap.put(item, isArray);
                    particleQNameMap.put(item, sequenceQName);

                    if (order) {
                        elementOrderMap.put(item, new Integer(sequenceCounter));
                    }
                }

            } else if (item instanceof XmlSchemaChoice) {
                // we have to process many sequence types

                XmlSchemaChoice xmlSchemaChoice = (XmlSchemaChoice) item;
                if (xmlSchemaChoice.getItems().getCount() > 0) {
                    BeanWriterMetaInfoHolder beanWriterMetaInfoHolder = new BeanWriterMetaInfoHolder();
                    beanWriterMetaInfoHolder.setChoice(true);
                    process(parentElementQName, xmlSchemaChoice.getItems(), beanWriterMetaInfoHolder, false, parentSchema);
                    beanWriterMetaInfoHolder.setParticleClass(true);
                    String localName = parentElementQName.getLocalPart() + "Choice";
                    QName choiceQName = new QName(parentElementQName.getNamespaceURI(),
                            localName + getNextTypeSuffix(localName));
                    String javaClassName = writeComplexParticle(choiceQName, beanWriterMetaInfoHolder);
                    processedTypemap.put(choiceQName, javaClassName);

                    //put the partical to array
                    Boolean isArray = xmlSchemaChoice.getMaxOccurs() > 1 ? Boolean.TRUE : Boolean.FALSE;
                    processedElementArrayStatusMap.put(item, isArray);
                    particleQNameMap.put(item, choiceQName);

                    if (order) {
                        elementOrderMap.put(item, new Integer(sequenceCounter));
                    }
                }

            } else if (item instanceof XmlSchemaGroupRef) {

                XmlSchemaGroupRef xmlSchemaGroupRef = (XmlSchemaGroupRef) item;
                QName groupQName = xmlSchemaGroupRef.getRefName();
                if (groupQName != null){
                    if (!processedGroupTypeMap.containsKey(groupQName)){
                        // processe the schema here
                        XmlSchema resolvedParentSchema = getParentSchema(parentSchema,groupQName,COMPONENT_GROUP);
                        if (resolvedParentSchema == null){
                            throw new SchemaCompilationException("Can not find the group with the qname" +
                                    groupQName + " from the parent schema " + parentSchema.getTargetNamespace());
                        } else {
                            XmlSchemaGroup xmlSchemaGroup =
                                    (XmlSchemaGroup) resolvedParentSchema.getGroups().getItem(groupQName);
                            if (xmlSchemaGroup != null){
                                processGroup(xmlSchemaGroup, groupQName, resolvedParentSchema);
                            }
                        }
                    }

                    Boolean isArray = xmlSchemaGroupRef.getMaxOccurs() > 1 ? Boolean.TRUE : Boolean.FALSE;
                    processedElementArrayStatusMap.put(item,isArray);
                    particleQNameMap.put(item,groupQName);

                    if (order){
                        elementOrderMap.put(item, new Integer(sequenceCounter));
                    }

                } else {
                    throw new SchemaCompilationException("Referenced name is null");
                }
            } else {
                //there may be other types to be handled here. Add them
                //when we are ready
            }
          sequenceCounter++;
        }

        // loop through the processed items and add them to the matainf object
        Iterator processedElementsIterator = processedElementArrayStatusMap.keySet().iterator();
        int startingItemNumberOrder = metainfHolder.getOrderStartPoint();
        while (processedElementsIterator.hasNext()) {
            Object child = processedElementsIterator.next();

            // process the XmlSchemaElement
            if (child instanceof XmlSchemaElement) {
                XmlSchemaElement elt = (XmlSchemaElement) child;
                QName referencedQName = null;


                if (elt.getQName() != null) {
                    referencedQName = elt.getQName();
                    QName schemaTypeQName = elt.getSchemaType() != null ? elt.getSchemaType().getQName() : elt.getSchemaTypeName();
                    if (schemaTypeQName != null) {
                        String clazzName = (String) processedElementTypeMap.get(elt.getQName());
                        metainfHolder.registerMapping(referencedQName,
                                schemaTypeQName,
                                clazzName,
                                ((Boolean) processedElementArrayStatusMap.get(elt)).booleanValue() ?
                                        SchemaConstants.ARRAY_TYPE :
                                        SchemaConstants.ELEMENT_TYPE);
                        if (innerChoiceElementList.contains(referencedQName)){
                            metainfHolder.addtStatus(referencedQName,SchemaConstants.INNER_CHOICE_ELEMENT);
                        }
                        // register the default value as well
                        if (elt.getDefaultValue() != null){
                           metainfHolder.registerDefaultValue(referencedQName,elt.getDefaultValue());
                        }

                    }
                }

                if (elt.getRefName() != null) { //probably this is referenced
                    referencedQName = elt.getRefName();
                    boolean arrayStatus = ((Boolean) processedElementArrayStatusMap.get(elt)).booleanValue();
                    String clazzName = findRefClassName(referencedQName, arrayStatus);
                    if (clazzName == null) {
                        clazzName = findClassName(referencedQName, arrayStatus);
                    }
                    XmlSchema resolvedParentSchema = getParentSchema(parentSchema,referencedQName,COMPONENT_ELEMENT);
                    if (resolvedParentSchema == null) {
                        throw new SchemaCompilationException("Can not find the element " + referencedQName +
                                " from the parent schema " + parentSchema.getTargetNamespace());
                    } else {
                        XmlSchemaElement refElement = resolvedParentSchema.getElementByName(referencedQName);

                        // register the mapping if we found the referenced element
                        // else throw an exception
                        if (refElement != null) {
                            metainfHolder.registerMapping(referencedQName,
                                    refElement.getSchemaTypeName()
                                    , clazzName,
                                    arrayStatus ?
                                            SchemaConstants.ARRAY_TYPE :
                                            SchemaConstants.ELEMENT_TYPE);
                        } else {
                            if (referencedQName.equals(SchemaConstants.XSD_SCHEMA)) {
                                metainfHolder.registerMapping(referencedQName,
                                        null,
                                        writer.getDefaultClassName(),
                                        SchemaConstants.ANY_TYPE);
                            } else {
                                throw new SchemaCompilationException(SchemaCompilerMessages.getMessage("schema.referencedElementNotFound", referencedQName.toString()));
                            }
                        }
                    }
                }

                if (referencedQName == null) {
                    throw new SchemaCompilationException(SchemaCompilerMessages.getMessage("schema.emptyName"));
                }

                //register the occurence counts
                metainfHolder.addMaxOccurs(referencedQName, elt.getMaxOccurs());
                // if the strict validation off then we consider all elements have minOccurs zero on it
                if (this.options.isOffStrictValidation()){
                    metainfHolder.addMinOccurs(referencedQName, 0);
                } else {
                    metainfHolder.addMinOccurs(referencedQName, elt.getMinOccurs());
                }
                //we need the order to be preserved. So record the order also
                if (order) {
                    //record the order in the metainf holder
                    Integer integer = (Integer) elementOrderMap.get(elt);
                    metainfHolder.registerQNameIndex(referencedQName,
                            startingItemNumberOrder + integer.intValue());
                }

                //get the nillable state and register that on the metainf holder
                if (localNillableList.contains(elt.getQName())) {
                    metainfHolder.registerNillableQName(elt.getQName());
                }

                //get the binary state and add that to the status map
                if (isBinary(elt)) {
                    metainfHolder.addtStatus(elt.getQName(),
                            SchemaConstants.BINARY_TYPE);
                }
                // process the XMLSchemaAny
            } else if (child instanceof XmlSchemaAny) {
                XmlSchemaAny any = (XmlSchemaAny) child;

                //since there is only one element here it does not matter
                //for the constant. However the problem occurs if the users
                //uses the same name for an element decalration
                QName anyElementFieldName = new QName(ANY_ELEMENT_FIELD_NAME);

                //this can be an array or a single element
                boolean isArray = ((Boolean) processedElementArrayStatusMap.get(any)).booleanValue();
                metainfHolder.registerMapping(anyElementFieldName,
                        null,
                        isArray ? writer.getDefaultClassArrayName() : writer.getDefaultClassName(),
                        SchemaConstants.ANY_TYPE);
                //if it's an array register an extra status flag with the system
                if (isArray) {
                    metainfHolder.addtStatus(anyElementFieldName,
                            SchemaConstants.ARRAY_TYPE);
                }
                metainfHolder.addMaxOccurs(anyElementFieldName, any.getMaxOccurs());
                metainfHolder.addMinOccurs(anyElementFieldName, any.getMinOccurs());

                if (order) {
                    //record the order in the metainf holder for the any
                    Integer integer = (Integer) elementOrderMap.get(any);
                    metainfHolder.registerQNameIndex(anyElementFieldName,
                            startingItemNumberOrder + integer.intValue());
                }
            } else if (child instanceof XmlSchemaSequence) {
                XmlSchemaSequence xmlSchemaSequence = (XmlSchemaSequence) child;
                QName sequenceQName = (QName) particleQNameMap.get(child);
                boolean isArray = xmlSchemaSequence.getMaxOccurs() > 1;

                // add this as an array to the original class
                metainfHolder.registerMapping(sequenceQName,
                        sequenceQName,
                        findClassName(sequenceQName, isArray));
                if (isArray) {
                    metainfHolder.addtStatus(sequenceQName, SchemaConstants.ARRAY_TYPE);
                }
                metainfHolder.addtStatus(sequenceQName, SchemaConstants.PARTICLE_TYPE_ELEMENT);
                metainfHolder.addMaxOccurs(sequenceQName, xmlSchemaSequence.getMaxOccurs());
                metainfHolder.addMinOccurs(sequenceQName, xmlSchemaSequence.getMinOccurs());
                metainfHolder.setHasParticleType(true);

                if (order) {
                    //record the order in the metainf holder for the any
                    Integer integer = (Integer) elementOrderMap.get(child);
                    metainfHolder.registerQNameIndex(sequenceQName,
                            startingItemNumberOrder + integer.intValue());
                }
            } else if (child instanceof XmlSchemaChoice) {
                XmlSchemaChoice xmlSchemaChoice = (XmlSchemaChoice) child;
                QName choiceQName = (QName) particleQNameMap.get(child);
                boolean isArray = xmlSchemaChoice.getMaxOccurs() > 1;

                // add this as an array to the original class
                metainfHolder.registerMapping(choiceQName,
                        choiceQName,
                        findClassName(choiceQName, isArray));
                if (isArray) {
                    metainfHolder.addtStatus(choiceQName, SchemaConstants.ARRAY_TYPE);
                }
                metainfHolder.addtStatus(choiceQName, SchemaConstants.PARTICLE_TYPE_ELEMENT);
                metainfHolder.addMaxOccurs(choiceQName, xmlSchemaChoice.getMaxOccurs());
                metainfHolder.addMinOccurs(choiceQName, xmlSchemaChoice.getMinOccurs());
                metainfHolder.setHasParticleType(true);

                if (order) {
                    //record the order in the metainf holder for the any
                    Integer integer = (Integer) elementOrderMap.get(child);
                    metainfHolder.registerQNameIndex(choiceQName,
                            startingItemNumberOrder + integer.intValue());
                }
            } else if (child instanceof XmlSchemaGroupRef) {
                XmlSchemaGroupRef xmlSchemaGroupRef = (XmlSchemaGroupRef) child;
                QName groupQName = (QName) particleQNameMap.get(child);
                boolean isArray = xmlSchemaGroupRef.getMaxOccurs() > 1;

                // add this as an array to the original class
                String groupClassName = (String) processedGroupTypeMap.get(groupQName);
                if (isArray){
                    groupClassName = groupClassName + "[]";
                }
                metainfHolder.registerMapping(groupQName,
                        groupQName,
                        groupClassName);
                if (isArray) {
                    metainfHolder.addtStatus(groupQName, SchemaConstants.ARRAY_TYPE);
                }
                metainfHolder.addtStatus(groupQName, SchemaConstants.PARTICLE_TYPE_ELEMENT);
                metainfHolder.addMaxOccurs(groupQName, xmlSchemaGroupRef.getMaxOccurs());
                metainfHolder.addMinOccurs(groupQName, xmlSchemaGroupRef.getMinOccurs());
                metainfHolder.setHasParticleType(true);

                if (order) {
                    //record the order in the metainf holder for the any
                    Integer integer = (Integer) elementOrderMap.get(child);
                    metainfHolder.registerQNameIndex(groupQName,
                            startingItemNumberOrder + integer.intValue());
                }
            }
        }

        //set the ordered flag in the metainf holder
        metainfHolder.setOrdered(order);
    }

    /**
     *
     * @param xmlSchemaGroup
     * @param schemaGroupQName- we have to pass this since xml schema does not provide
     * this properly
     * @param parentSchema
     * @throws SchemaCompilationException
     */

    private void processGroup(XmlSchemaGroup xmlSchemaGroup,
                              QName schemaGroupQName,
                              XmlSchema parentSchema) throws SchemaCompilationException {

        // find the group base item
        XmlSchemaGroupBase xmlSchemaGroupBase = xmlSchemaGroup.getParticle();
        if (xmlSchemaGroupBase != null){
            if (xmlSchemaGroupBase instanceof XmlSchemaSequence){
                XmlSchemaSequence xmlSchemaSequence = (XmlSchemaSequence) xmlSchemaGroupBase;
                if (xmlSchemaSequence.getItems().getCount() > 0) {
                    BeanWriterMetaInfoHolder beanWriterMetaInfoHolder = new BeanWriterMetaInfoHolder();
                    process(schemaGroupQName, xmlSchemaSequence.getItems(), beanWriterMetaInfoHolder, true, parentSchema);
                    beanWriterMetaInfoHolder.setParticleClass(true);
                    String javaClassName = writeComplexParticle(schemaGroupQName, beanWriterMetaInfoHolder);
                    processedGroupTypeMap.put(schemaGroupQName, javaClassName);
//                    processedTypemap.put(schemaGroupQName, javaClassName);
                }

            } else if (xmlSchemaGroupBase instanceof XmlSchemaChoice){
                XmlSchemaChoice xmlSchemaChoice = (XmlSchemaChoice) xmlSchemaGroupBase;
                if (xmlSchemaChoice.getItems().getCount() > 0) {
                    BeanWriterMetaInfoHolder beanWriterMetaInfoHolder = new BeanWriterMetaInfoHolder();
                    beanWriterMetaInfoHolder.setChoice(true);
                    process(schemaGroupQName, xmlSchemaChoice.getItems(), beanWriterMetaInfoHolder, false, parentSchema);
                    beanWriterMetaInfoHolder.setParticleClass(true);
                    String javaClassName = writeComplexParticle(schemaGroupQName, beanWriterMetaInfoHolder);
                    processedGroupTypeMap.put(schemaGroupQName, javaClassName);
//                    processedTypemap.put(schemaGroupQName, javaClassName);
                }
            }
        }
    }

    /**
     * Checks whether a given element is a binary element
     *
     * @param elt
     */
    private boolean isBinary(XmlSchemaElement elt) {
        return elt.getSchemaType() != null &&
                SchemaConstants.XSD_BASE64.equals(elt.getSchemaType().getQName());
    }

    /**
     * Checks whether a given qname is a binary
     *
     * @param qName
     */
    private boolean isBinary(QName qName) {
        return qName != null &&
                SchemaConstants.XSD_BASE64.equals(qName);
    }

    /**
     * @param simpleType
     * @param xsElt
     * @param parentSchema
     * @param qname        - fake Qname to use if the xsElt is null.
     * @throws SchemaCompilationException
     */
    private void processSimpleSchemaType(XmlSchemaSimpleType simpleType,
                                         XmlSchemaElement xsElt,
                                         XmlSchema parentSchema,
                                         QName qname) throws SchemaCompilationException {

        String fullyQualifiedClassName = null;
        if (simpleType.getQName() != null) {
            if (processedTypemap.containsKey(simpleType.getQName())
                    || baseSchemaTypeMap.containsKey(simpleType.getQName())) {
                return;
            }

            // Must do this up front to support recursive types
            fullyQualifiedClassName = writer.makeFullyQualifiedClassName(simpleType.getQName());
            // we put the qname to processed type map it is only named type
            // otherwise we have to any way process that element.
            processedTypemap.put(simpleType.getQName(), fullyQualifiedClassName);
        } else {

            QName fakeQname;
            if (xsElt != null) {
                fakeQname = new QName(xsElt.getQName().getNamespaceURI(), xsElt.getQName().getLocalPart() + getNextTypeSuffix(xsElt.getQName().getLocalPart()));
                // we have to set this otherwise the ours attribute would not set properly if refered to this simple
                // type from any other element
                xsElt.setSchemaTypeName(fakeQname);
                changedElementSet.add(xsElt);

            } else {
                fakeQname = qname;
            }
            if (processedTypemap.containsKey(fakeQname)
                    || baseSchemaTypeMap.containsKey(fakeQname)) {
                return;
            }
            fullyQualifiedClassName = writer.makeFullyQualifiedClassName(fakeQname);
            simpleType.addMetaInfo(SchemaConstants.SchemaCompilerInfoHolder.FAKE_QNAME, fakeQname);

            // should put this to the processedTypemap to generate the code correctly
            processedTypemap.put(fakeQname, fullyQualifiedClassName);
        }

        //register that in the schema metainfo bag
        simpleType.addMetaInfo(SchemaConstants.SchemaCompilerInfoHolder.CLASSNAME_KEY,
                fullyQualifiedClassName);

        BeanWriterMetaInfoHolder metaInfHolder = processSimpleType(simpleType, parentSchema);
        metaInfHolder.setSimple(true);

        if (simpleType.getQName() == null) {
            this.processedAnonymousComplexTypesMap.put(xsElt, metaInfHolder);
            QName fakeQname;
            if (xsElt != null) {
                fakeQname = new QName(xsElt.getQName().getNamespaceURI(), xsElt.getQName().getLocalPart());
            } else {
                fakeQname = qname;
                simpleType.setName(fakeQname.getLocalPart());
                changedSimpleTypeSet.add(simpleType);
                simpleType.setSourceURI(fakeQname.getNamespaceURI());
            }
            simpleTypesMap.put(fakeQname, fullyQualifiedClassName);
        }
        //add this information to the metainfo holder
        metaInfHolder.setOwnQname(simpleType.getQName());
        if (fullyQualifiedClassName != null) {
            metaInfHolder.setOwnClassName(fullyQualifiedClassName);
        }
        //write the class. This type mapping would have been populated right now
        //Note - We always write classes for named complex types
        writeSimpleType(simpleType, metaInfHolder);
    }

    private BeanWriterMetaInfoHolder processSimpleType(XmlSchemaSimpleType simpleType, XmlSchema parentSchema) throws SchemaCompilationException {
        BeanWriterMetaInfoHolder metaInfHolder = new BeanWriterMetaInfoHolder();

        // handle the restriction
        XmlSchemaSimpleTypeContent content = simpleType.getContent();
        QName parentSimpleTypeQname = simpleType.getQName();
        if (parentSimpleTypeQname == null) {
            parentSimpleTypeQname = (QName) simpleType.getMetaInfoMap().get(SchemaConstants.SchemaCompilerInfoHolder.FAKE_QNAME);
        }
        if (content != null) {
            if (content instanceof XmlSchemaSimpleTypeRestriction) {
                XmlSchemaSimpleTypeRestriction restriction = (XmlSchemaSimpleTypeRestriction) content;

                QName baseTypeName = restriction.getBaseTypeName();
                //check whether the base type is one of the base schema types

                if (baseSchemaTypeMap.containsKey(baseTypeName)) {
                    //process restriction base type

                    processSimpleRestrictionBaseType(parentSimpleTypeQname, restriction.getBaseTypeName(), metaInfHolder, parentSchema);
                    //process facets
                    if (!SchemaConstants.XSD_BOOLEAN.equals(baseTypeName)){
                        processFacets(restriction, metaInfHolder, parentSchema);
                    }
                } else {
                    //recurse
                    // this must be a xmlschema bug
                    // it should return the schematype for restriction.getBaseType():
                    XmlSchema resolvedSchema = getParentSchema(parentSchema, baseTypeName, COMPONENT_TYPE);
                    if (resolvedSchema == null) {
                        throw new SchemaCompilationException("can not find the type " + baseTypeName +
                                " from the parent schema " + parentSchema.getTargetNamespace());
                    } else {
                        XmlSchemaType restrictionBaseType = resolvedSchema.getTypeByName(baseTypeName);
                        if (restrictionBaseType instanceof XmlSchemaSimpleType) {
                            if ((restrictionBaseType != null) && (!isAlreadyProcessed(baseTypeName))) {
                                processSimpleSchemaType((XmlSchemaSimpleType) restrictionBaseType,
                                        null, resolvedSchema, null);
                            }
                            // process restriction
                            processSimpleRestrictionBaseType(parentSimpleTypeQname,
                                    restriction.getBaseTypeName(), metaInfHolder, resolvedSchema);
                        }
                    }

                }
            } else if (content instanceof XmlSchemaSimpleTypeUnion) {
                XmlSchemaSimpleTypeUnion simpleTypeUnion = (XmlSchemaSimpleTypeUnion) content;
                QName[] qnames = simpleTypeUnion.getMemberTypesQNames();
                if (qnames != null) {
                    QName qname;
                    for (int i = 0; i < qnames.length; i++) {
                        qname = qnames[i];
                        if (baseSchemaTypeMap.containsKey(qname)) {
                            metaInfHolder.addMemberType(qname, (String) baseSchemaTypeMap.get(qname));
                        } else {
                            XmlSchema resolvedSchema = getParentSchema(parentSchema, qname, COMPONENT_TYPE);
                            if (resolvedSchema == null) {
                                throw new SchemaCompilationException("can not find the type " + qname +
                                        " from the parent schema " + parentSchema.getTargetNamespace());
                            } else {
                                XmlSchemaType type = resolvedSchema.getTypeByName(qname);
                                if (type instanceof XmlSchemaSimpleType) {
                                    XmlSchemaSimpleType memberSimpleType = (XmlSchemaSimpleType) type;
                                    if (!isAlreadyProcessed(qname)) {
                                        processSimpleSchemaType(memberSimpleType, null, resolvedSchema, null);
                                    }
                                    metaInfHolder.addMemberType(qname, (String) processedTypemap.get(qname));
                                } else {
                                    throw new SchemaCompilationException("Unions can not have complex types as a member type");
                                }
                            }
                        }
                    }
                } else {
                    XmlSchemaObjectCollection xmlSchemaObjectCollection = simpleTypeUnion.getBaseTypes();
                    XmlSchemaObject xmlSchemaObject;
                    QName childQname;
                    int i = 1;
                    for (Iterator iter = xmlSchemaObjectCollection.getIterator(); iter.hasNext();) {
                        xmlSchemaObject = (XmlSchemaObject) iter.next();
                        i++;
                        if (xmlSchemaObject instanceof XmlSchemaSimpleType) {
                            XmlSchemaSimpleType unionSimpleType = (XmlSchemaSimpleType) xmlSchemaObject;
                            childQname = unionSimpleType.getQName();
                            if (childQname == null) {
                                // we create a fake Qname for all these simple types since most propably they don't have one
                                childQname = new QName(parentSimpleTypeQname.getNamespaceURI(), parentSimpleTypeQname.getLocalPart() + getNextTypeSuffix(parentSimpleTypeQname.getLocalPart()));
                            }
                            // this is an inner simple type of the union so it shold not have
                            // processed
                            processSimpleSchemaType(unionSimpleType, null, parentSchema, childQname);
                            metaInfHolder.addMemberType(childQname, (String) processedTypemap.get(childQname));
                        }

                    }
                }

                metaInfHolder.setUnion(true);

            } else if (content instanceof XmlSchemaSimpleTypeList) {
                XmlSchemaSimpleTypeList simpleTypeList = (XmlSchemaSimpleTypeList) content;
                QName itemTypeQName = simpleTypeList.getItemTypeName();

                if (itemTypeQName != null) {
                    if (!isAlreadyProcessed(itemTypeQName)) {
                        XmlSchema resolvedSchema = getParentSchema(parentSchema, itemTypeQName, COMPONENT_TYPE);
                        if (resolvedSchema == null) {
                            throw new SchemaCompilationException("can not find the type " + itemTypeQName +
                                    " from the parent type " + parentSchema.getTargetNamespace());
                        } else {
                            XmlSchemaType simpleSchemaType = resolvedSchema.getTypeByName(itemTypeQName);
                            if (simpleSchemaType instanceof XmlSchemaSimpleType) {
                                processSimpleSchemaType((XmlSchemaSimpleType) simpleSchemaType, null, resolvedSchema, null);
                            }
                        }
                    }
                } else {
                    XmlSchemaSimpleType listSimpleType = simpleTypeList.getItemType();
                    itemTypeQName = listSimpleType.getQName();
                    if (itemTypeQName == null) {
                        // we create a fake Qname for all these simple types since most propably they don't have one
                        itemTypeQName = new QName(parentSimpleTypeQname.getNamespaceURI(), parentSimpleTypeQname.getLocalPart() + "_type0");
                    }
                    processSimpleSchemaType(listSimpleType, null, parentSchema, itemTypeQName);

                }

                String className = findClassName(itemTypeQName, false);
                metaInfHolder.setList(true);
                metaInfHolder.setItemTypeQName(itemTypeQName);
                metaInfHolder.setItemTypeClassName(className);

            }
        }
        return metaInfHolder;
    }


    /**
     * Find whether a given particle is an array. The logic for deciding
     * whether a given particle is an array is depending on their minOccurs
     * and maxOccurs counts. If Maxoccurs is greater than one (1) then the
     * content is an array.
     * Also no higher level element will have the maxOccurs greater than one
     *
     * @param particle
     * @throws SchemaCompilationException
     */
    private boolean isArray(XmlSchemaParticle particle) throws SchemaCompilationException {
        long minOccurs = particle.getMinOccurs();
        long maxOccurs = particle.getMaxOccurs();

        if (maxOccurs < minOccurs) {
            throw new SchemaCompilationException();
        } else {
            return (maxOccurs > 1);
        }

    }

    HashMap mapTypeCount = new HashMap();
    private String getNextTypeSuffix(String localName) {
        Integer typeCounter = (Integer) mapTypeCount.get(localName);
        int count = 0;
        if (typeCounter != null) {
            if(typeCounter.intValue() == Integer.MAX_VALUE) {
                count = 0;
            } else {
                count = typeCounter.intValue();
            }
        }
        mapTypeCount.put(localName, new Integer(count+1));
        return ("_type" + count);
    }

    /**
     * returns the parent schema of the componet having QName compoentTypeQName.
     * withe the componet type.
     * @param parentSchema - parent schema of the given componet
     * @param componentQName - qname of the componet, of which we want to get the parent schema
     * @param componetType - type of the componet. this can either be type,element,attribute or attribute group
     * @return parent schema.
     */

    private XmlSchema getParentSchema(XmlSchema parentSchema,
                                      QName componentQName,
                                      int componetType) throws SchemaCompilationException {
        // if the componet do not have a propernamesapce or
        // it is equals to the xsd schema namesapce
        // we do not have to do any thing.
        if ((componentQName == null) ||
              (componentQName.getNamespaceURI() == null) ||
                Constants.URI_2001_SCHEMA_XSD.equals(componentQName.getNamespaceURI())){
            return parentSchema;
        }

        List visitedSchemas = new ArrayList();
        visitedSchemas.add(parentSchema);
        XmlSchema newParentSchema = getParentSchemaFromIncludes(parentSchema,
                componentQName,componetType,visitedSchemas);
        if (newParentSchema == null){
            String targetNamespace = componentQName.getNamespaceURI();
            if (loadedSchemaMap.containsKey(targetNamespace)){
                XmlSchema tempSchema = (XmlSchema) loadedSchemaMap.get(targetNamespace);
                if (isComponetExists(tempSchema,componentQName,componetType)){
                    newParentSchema = tempSchema;
                }
            } else if (availableSchemaMap.containsKey(targetNamespace)){
                XmlSchema tempSchema = (XmlSchema) availableSchemaMap.get(targetNamespace);
                if (isComponetExists(tempSchema,componentQName,componetType)){
                    compile(tempSchema);
                    newParentSchema = tempSchema;
                }
            }
        }
        return newParentSchema;
    }

    private XmlSchema getParentSchemaFromIncludes(XmlSchema parentSchema,
                                                  QName componentQName,
                                                  int componetType,
                                                  List visitedSchemas) throws SchemaCompilationException {

        XmlSchema newParentSchema = null;
        if (isComponetExists(parentSchema, componentQName, componetType)) {
            newParentSchema = parentSchema;
        } else {
            // this componet must either be in a import or and include
            XmlSchemaObjectCollection includes = parentSchema.getIncludes();
            if (includes != null) {
                Object externalComponet = null;
                XmlSchema externalSchema = null;
                for (Iterator iter = includes.getIterator(); iter.hasNext();) {
                    externalComponet = iter.next();
                    if (externalComponet instanceof XmlSchemaExternal) {
                        externalSchema = ((XmlSchemaExternal) externalComponet).getSchema();

                        // if this is an inline import without a schema location
                        // xmlschema does not load the schema.
                        // so we try to figure out it either from the available schemas
                        // or from the laded schemas.
                        if ((externalSchema == null) && externalComponet instanceof XmlSchemaImport){
                            XmlSchemaImport xmlSchemaImport = (XmlSchemaImport) externalComponet;
                            String importNamespce = xmlSchemaImport.getNamespace();
                            if ((importNamespce != null) && !importNamespce.equals(Constants.URI_2001_SCHEMA_XSD)) {
                                if (loadedSchemaMap.containsKey(importNamespce)) {
                                    externalSchema = (XmlSchema) loadedSchemaMap.get(importNamespce);
                                } else if (availableSchemaMap.containsKey(importNamespce)) {
                                    XmlSchema tempSchema = (XmlSchema) availableSchemaMap.get(importNamespce);
                                    compile(tempSchema);
                                    externalSchema = tempSchema;
                                }
                            }
                        }
                        if (externalSchema != null) {
                            // find the componet in the new external schema.
                            if (!visitedSchemas.contains(externalSchema)){
                                visitedSchemas.add(externalSchema);
                                newParentSchema = getParentSchemaFromIncludes(externalSchema,
                                        componentQName, componetType, visitedSchemas);
                            }
                        }
                        if (newParentSchema != null) {
                            // i.e we have found the schema
                            break;
                        }
                    }
                }
            }
        }
        return newParentSchema;
    }

    private boolean isComponetExists(XmlSchema schema,
                                     QName componentQName,
                                     int componetType) {
        boolean isExists = false;
        if (!schema.getTargetNamespace().equals(componentQName.getNamespaceURI())){
             return false;
        }
        switch (componetType) {
            case COMPONENT_TYPE : {
                isExists = (schema.getTypeByName(componentQName.getLocalPart()) != null);
                break;
            }
            case COMPONENT_ELEMENT : {
                isExists = (schema.getElementByName(componentQName.getLocalPart()) != null);
                break;
            }
            case COMPONENT_ATTRIBUTE : {
                isExists = (schema.getAttributes().getItem(componentQName) != null);
                break;
            }
            case COMPONENT_ATTRIBUTE_GROUP : {
                isExists = (schema.getAttributeGroups().getItem(componentQName) != null);
                break;
            }
            case COMPONENT_GROUP : {
                isExists = (schema.getGroups().getItem(componentQName) != null);
                break;
            }
        }
       return isExists;
    }
}
