/*
 * 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.writer;

import org.apache.axis2.schema.BeanWriterMetaInfoHolder;
import org.apache.axis2.schema.CompilerOptions;
import org.apache.axis2.schema.SchemaCompilationException;
import org.apache.axis2.schema.SchemaConstants;
import org.apache.axis2.schema.i18n.SchemaCompilerMessages;
import org.apache.axis2.schema.typemap.JavaTypeMap;
import org.apache.axis2.schema.util.PrimitiveTypeFinder;
import org.apache.axis2.schema.util.SchemaPropertyLoader;
import org.apache.axis2.util.JavaUtils;
import org.apache.axis2.util.XSLTTemplateProcessor;
import org.apache.axis2.util.XSLTUtils;
import org.apache.axis2.wsdl.databinding.CUtils;
import org.apache.ws.commons.schema.XmlSchemaElement;
import org.apache.ws.commons.schema.XmlSchemaSimpleType;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamSource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

//import com.ibm.wsdl.util.xml.DOM2Writer;

/**
 * Java Bean writer for the schema compiler.
 */
public class CStructWriter implements BeanWriter {

    public static final String WRAPPED_DATABINDING_CLASS_NAME = "WrappedDatabinder";
    public static final String EXTENSION_MAPPER_CLASSNAME = "extension_mapper";
    public static final String AXIS2_PREFIX = "adb_";
    private String javaBeanTemplateName = null;
    private boolean templateLoaded = false;
    private Templates sourceTemplateCache;
    private Templates headerTemplateCache;

    private List<String> namesList;
    private static int count = 0;
    private boolean wrapClasses = false;
    private boolean writeClasses = false;

    protected File rootDir;

    private Document globalWrappedSourceDocument;
    private Document globalWrappedHeaderDocument;

    private Map modelMap = new HashMap();
    private static final String ADB_CLASS_PREFIX = "adb_";
    private static final String ADB_CLASS_POSTFIX = "_t*";
    private static final String DEFAULT_C_CLASS_NAME = "axiom_node_t*";

    private Map baseTypeMap = new JavaTypeMap().getTypeMap();

    // 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 DEFAULT_CLASS_NAME = "axiom_node_t*";
    public static final String DEFAULT_CLASS_ARRAY_NAME = "axiom_node_t*";

    public static final String DEFAULT_ATTRIB_CLASS_NAME = "axiom_attribute_t*";
    public static final String DEFAULT_ATTRIB_ARRAY_CLASS_NAME = "axiom_attribute_t*";

    public static final String DEFAULT_TYPE_NS = "http://www.w3.org/2001/XMLSchema";


    /**
     * Default constructor
     */
    public CStructWriter() {
    }

    /**
     * This returns a map of Qnames vs DOMDocument models. One can use this method to
     * obtain the raw DOMmodels used to write the classes.
     * This has no meaning when the classes are supposed to be wrapped  so the
     *
     * @return Returns Map.
     * @see BeanWriter#getModelMap()
     */
    public Map getModelMap() {
        return modelMap;
    }

    public String getDefaultClassName() {
        return DEFAULT_CLASS_NAME;
    }

    public String getDefaultClassArrayName() {
        return DEFAULT_CLASS_ARRAY_NAME;
    }

    public String getDefaultAttribClassName() {
        return DEFAULT_ATTRIB_CLASS_NAME;
    }

    public String getDefaultAttribArrayClassName() {
        return DEFAULT_ATTRIB_ARRAY_CLASS_NAME;
    }
    public void init(CompilerOptions options) throws SchemaCompilationException {
        try {
            initWithFile(options.getOutputLocation());

            writeClasses = options.isWriteOutput();
            if (!writeClasses) {
                wrapClasses = false;
            } else {
                wrapClasses = options.isWrapClasses();
            }

            //if the wrap mode is set then create a global document to keep the wrapped
            //element models
            if (options.isWrapClasses()) {
                globalWrappedSourceDocument = XSLTUtils.getDocument();
                Element rootElement = XSLTUtils.getElement(globalWrappedSourceDocument, "beans");
                globalWrappedSourceDocument.appendChild(rootElement);
                XSLTUtils.addAttribute(globalWrappedSourceDocument, "name", CStructWriter.WRAPPED_DATABINDING_CLASS_NAME, rootElement);

                globalWrappedHeaderDocument = XSLTUtils.getDocument();
                rootElement = XSLTUtils.getElement(globalWrappedHeaderDocument, "beans");
                globalWrappedHeaderDocument.appendChild(rootElement);
                XSLTUtils.addAttribute(globalWrappedHeaderDocument, "name", CStructWriter.WRAPPED_DATABINDING_CLASS_NAME, rootElement);

            }


        } catch (IOException e) {
            throw new SchemaCompilationException(e);
        } catch (ParserConfigurationException e) {
            throw new SchemaCompilationException(e); //todo need to put correct error messages
        }
    }

    /**
     * @param element
     * @param typeMap
     * @param metainf
     * @return Returns String.
     * @throws org.apache.axis2.schema.SchemaCompilationException
     *
     */
    public String write(XmlSchemaElement element,
                        Map<QName,String> typeMap,
                        Map<QName,String> groupTypeMap,
                        BeanWriterMetaInfoHolder metainf) throws SchemaCompilationException {

        try {
            QName qName = element.getQName();

            return process(qName, metainf, typeMap, groupTypeMap, true, false);
        } catch (Exception e) {
            throw new SchemaCompilationException(e);
        }


    }

    /**
     * @param qName
     * @param typeMap
     * @param metainf
     * @param isAbstract
     * @throws org.apache.axis2.schema.SchemaCompilationException
     *
     * @see org.apache.axis2.schema.writer.BeanWriter
     */
    public String write(QName qName,
                        Map<QName,String> typeMap,
                        Map<QName,String> groupTypeMap,
                        BeanWriterMetaInfoHolder metainf,
                        boolean isAbstract)
            throws SchemaCompilationException {

        try {
            //determine the package for this type.
            return process(qName, metainf, typeMap, groupTypeMap, false, isAbstract);

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


    }

    /**
     * @throws SchemaCompilationException
     * @see org.apache.axis2.schema.writer.BeanWriter#writeBatch()
     */
    public void writeBatch() throws SchemaCompilationException {
        try {
            if (wrapClasses) {

                File outSource = createOutFile(CStructWriter.WRAPPED_DATABINDING_CLASS_NAME, ".c", null);
                File outHeader = createOutFile(CStructWriter.WRAPPED_DATABINDING_CLASS_NAME, ".h", null);
                //parse with the template and create the files
                parseSource(globalWrappedSourceDocument, outSource);
                parseHeader(globalWrappedHeaderDocument, outHeader);
            }
        } catch (Exception e) {
            throw new SchemaCompilationException(e);
        }
    }

    /**
     * @param simpleType
     * @param typeMap
     * @param metainf
     * @return Returns String.
     * @throws org.apache.axis2.schema.SchemaCompilationException
     *
     */
    public String write(XmlSchemaSimpleType simpleType,
                        Map<QName,String> typeMap,
                        Map<QName,String> groupTypeMap,
                        BeanWriterMetaInfoHolder metainf) throws SchemaCompilationException {
        try {
            //determine the package for this type.
            QName qName = simpleType.getQName();
            if (qName == null) {
                qName = (QName) simpleType.getMetaInfoMap().get(SchemaConstants.SchemaCompilerInfoHolder.FAKE_QNAME);
            }
            metainf.addtStatus(qName, SchemaConstants.SIMPLE_TYPE_OR_CONTENT);
            return process(qName, metainf, typeMap, groupTypeMap, true, false);

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

    /**
     * @param rootDir
     * @throws java.io.IOException
     * @see org.apache.axis2.schema.writer.BeanWriter
     */
    private void initWithFile(File rootDir) throws IOException {
        if (rootDir == null) {
            this.rootDir = new File(".");
        } else if (!rootDir.isDirectory()) {
            throw new IOException(SchemaCompilerMessages.getMessage("schema.rootnotfolderexception"));
        } else {
            this.rootDir = rootDir;
        }

        namesList = new ArrayList<String>();
        javaBeanTemplateName = SchemaPropertyLoader.getBeanTemplate();
    }


    /**
     * Make the fully qualified class name for an element or named type
     *
     * @param qName the qualified Name for this element or type in the schema
     * @return the appropriate fully qualified class name to use in generated code
     */
    public String makeFullyQualifiedClassName(QName qName) {

        String originalName = qName.getLocalPart();

        return makeUniqueCStructName(this.namesList, originalName);
    }

    /**
     * A util method that holds common code
     * for the complete schema that the generated XML complies to
     * look under other/beanGenerationSchema.xsd
     *
     * @param qName
     * @param metainf
     * @param typeMap
     * @param isElement
     * @return Returns String.
     * @throws Exception
     */
    private String process(QName qName,
                        BeanWriterMetaInfoHolder metainf,
                        Map<QName,String> typeMap,
                        Map<QName,String> groupTypeMap,
                        boolean isElement,
                        boolean isAbstract)
            throws Exception {
        String fullyQualifiedClassName = metainf.getOwnClassName();
        if (fullyQualifiedClassName == null)
            fullyQualifiedClassName = makeFullyQualifiedClassName(qName);
        String className = fullyQualifiedClassName;


        String originalName = qName == null? "" : qName.getLocalPart();
        ArrayList<String> propertyNames = new ArrayList<String>();

        if (!templateLoaded) {
            loadTemplate();
        }

        //if wrapped then do not write the classes now but add the models to a global document. However in order to write the
        //global class that is generated, one needs to call the writeBatch() method
        if (wrapClasses) {
            globalWrappedSourceDocument.getDocumentElement().appendChild(
                    getBeanElement(globalWrappedSourceDocument, className,
                        originalName, qName, isElement, isAbstract, 
                        metainf, propertyNames, typeMap, groupTypeMap));

            globalWrappedHeaderDocument.getDocumentElement().appendChild(
                    getBeanElement(globalWrappedHeaderDocument, className,
                        originalName, qName, isElement, isAbstract, 
                        metainf, propertyNames, typeMap, groupTypeMap));

        } else {
            //create the model
            Document modelSource = XSLTUtils.getDocument();
            Document modelHeader = XSLTUtils.getDocument();
            //make the XML
            modelSource.appendChild(getBeanElement(modelSource, className, originalName,
                    qName, isElement, isAbstract, metainf, propertyNames,
                    typeMap, groupTypeMap));
            modelHeader.appendChild(getBeanElement(modelHeader, className, originalName,
                    qName, isElement, isAbstract, metainf, propertyNames,
                    typeMap, groupTypeMap));

            if (writeClasses) {
                //create the file
                String fileName = className.substring(4, className.length() -3);
                File outSource = createOutFile(fileName, ".c", null);
                File outHeader = createOutFile(fileName, ".h", null);
                //parse with the template and create the files
                parseSource(modelSource, outSource);
                parseHeader(modelHeader, outHeader);
            }

            //add the model to the model map
            modelMap.put(
                    new QName(qName.getNamespaceURI(), className)
                    , modelSource);
            modelMap.put(
                    new QName(qName.getNamespaceURI(), className)
                    , modelHeader);

            /////////////////////////////////////////////////////
            // System.out.println(DOM2Writer.nodeToString(modelSource.getFirstChild()));
            /////////////////////////////////////////////////////

        }

        //return the fully qualified class name
        return fullyQualifiedClassName;

    }


    /**
     * @param model
     * @param className
     * @param originalName
     * @param qName
     * @param isElement
     * @param metainf
     * @param propertyNames
     * @param typeMap
     * @return Returns Element.
     * @throws org.apache.axis2.schema.SchemaCompilationException
     *
     */
    private Element getBeanElement(
            Document model,
            String className,
            String originalName,
            QName qName,
            boolean isElement,
            boolean isAbstract,
            BeanWriterMetaInfoHolder metainf,
            ArrayList<String> propertyNames,
            Map<QName,String> typeMap,
            Map<QName,String> groupTypeMap)
     throws SchemaCompilationException {

        Element rootElt = XSLTUtils.getElement(model, "class");
        String strippedClassName = className.substring(4, className.length() -3);
        XSLTUtils.addAttribute(model, "name", strippedClassName, rootElt);
        XSLTUtils.addAttribute(model, "originalName", originalName, rootElt);
        XSLTUtils.addAttribute(model, "nsuri", qName.getNamespaceURI(), rootElt);
        XSLTUtils.addAttribute(model, "nsprefix", getPrefixForURI(qName.getNamespaceURI(), qName.getPrefix()), rootElt);

        /* use caps for macros */
        String capsName = strippedClassName.toUpperCase();
        XSLTUtils.addAttribute(model, "caps-name", capsName, rootElt);


        if (!wrapClasses) {
            XSLTUtils.addAttribute(model, "unwrapped", "yes", rootElt);
        }

        if (isAbstract) {
            XSLTUtils.addAttribute(model, "isAbstract", "yes", rootElt);
        }

        if (!writeClasses) {
            XSLTUtils.addAttribute(model, "skip-write", "yes", rootElt);
        }

        if (!isElement) {
            XSLTUtils.addAttribute(model, "type", "yes", rootElt);
        }

        if (metainf.isAnonymous()) {
            XSLTUtils.addAttribute(model, "anon", "yes", rootElt);
        }

        if (metainf.isExtension()) {
            XSLTUtils.addAttribute(model, "extension", metainf.getExtensionClassName(), rootElt);
        }

        if (metainf.isRestriction()) {
            XSLTUtils.addAttribute(model, "restriction", metainf
                    .getRestrictionClassName(), rootElt);
        }


        if (metainf.isChoice()) {
            XSLTUtils.addAttribute(model, "choice", "yes", rootElt);
        }

         if (metainf.isSimple()) {
            XSLTUtils.addAttribute(model, "simple", "yes", rootElt);
        }

        if (metainf.isUnion()) {
            XSLTUtils.addAttribute(model, "union", "yes", rootElt);
        }

        if (metainf.isList()) {
            XSLTUtils.addAttribute(model, "list", "yes", rootElt);
        }

        if (metainf.isOrdered()) {
            XSLTUtils.addAttribute(model, "ordered", "yes", rootElt);
        }

        if (isElement && metainf.isNillable(qName)) {
            XSLTUtils.addAttribute(model, "nillable", "yes", rootElt);
        }

        if (metainf.isParticleClass()) {
            XSLTUtils.addAttribute(model, "particleClass", "yes", rootElt);
        }

        if (metainf.isHasParticleType()){
            XSLTUtils.addAttribute(model, "hasParticleType", "yes", rootElt);
        }

        //populate all the information
        populateInfo(metainf, model, rootElt, propertyNames, typeMap, groupTypeMap, false);

        if (metainf.isSimple() && metainf.isUnion()) {
            populateMemberInfo(metainf, model, rootElt, typeMap);
        }

        if (metainf.isSimple() && metainf.isList()) {
            populateListInfo(metainf, model, rootElt, typeMap, groupTypeMap);
        }

        return rootElt;
    }

    protected void populateListInfo(BeanWriterMetaInfoHolder metainf,
                                    Document model,
                                    Element rootElement,
                                    Map<QName,String> typeMap,
                                    Map<QName,String> groupTypeMap) {

        String cName = makeUniqueCStructName(new ArrayList<String>(), metainf.getItemTypeQName().getLocalPart());
        Element itemType = XSLTUtils.addChildElement(model, "itemtype", rootElement);
        XSLTUtils.addAttribute(model, "type", metainf.getItemTypeClassName(), itemType);
        XSLTUtils.addAttribute(model, "nsuri", metainf.getItemTypeQName().getNamespaceURI(), itemType);
        XSLTUtils.addAttribute(model, "originalName", metainf.getItemTypeQName().getLocalPart(), itemType);
        XSLTUtils.addAttribute(model, "cname", cName.substring(4, cName.length()-3), itemType);

        if (typeMap.containsKey(metainf.getItemTypeQName()) ||
                groupTypeMap.containsKey(metainf.getItemTypeClassName())) {
            XSLTUtils.addAttribute(model, "ours", "true", itemType);
        }
        if (PrimitiveTypeFinder.isPrimitive(metainf.getItemTypeClassName())) {
            XSLTUtils.addAttribute(model, "primitive", "yes", itemType);
        }

    }

    protected void populateMemberInfo(BeanWriterMetaInfoHolder metainf,
                                      Document model,
                                      Element rootElement,
                                      Map<QName,String> typeMap) {
        Map<QName,String> memberTypes = metainf.getMemberTypes();
        for (QName memberQName : memberTypes.keySet()) {
            String memberClass = memberTypes.get(memberQName);


        // add member type element
            Element memberType = XSLTUtils.addChildElement(model, "memberType", rootElement);
            XSLTUtils.addAttribute(model, "type", memberClass, memberType);
            XSLTUtils.addAttribute(model, "nsuri", memberQName.getNamespaceURI(), memberType);
            XSLTUtils.addAttribute(model, "originalName", memberQName.getLocalPart(), memberType);
            XSLTUtils.addAttribute(model, "caps-originalName", memberQName.getLocalPart().toUpperCase(), memberType);
            if (typeMap.containsKey(memberQName)) {
                XSLTUtils.addAttribute(model, "ours", "true", memberType);
            }

        }
    }

    /**
     * @param metainf
     * @param model
     * @param rootElt
     * @param propertyNames
     * @param typeMap
     * @throws org.apache.axis2.schema.SchemaCompilationException
     *
     */
    private void populateInfo(BeanWriterMetaInfoHolder metainf,
                              Document model,
                              Element rootElt,
                              ArrayList<String> propertyNames,
                              Map<QName,String> typeMap,
                              Map<QName,String> groupTypeMap,
                              boolean isInherited) throws SchemaCompilationException {
        if (metainf.getParent() != null && (!metainf.isRestriction() || (metainf.isRestriction() && metainf.isSimple())))
        {
            populateInfo(metainf.getParent(), model, rootElt, propertyNames,
                    typeMap, groupTypeMap, true);
        }
        addPropertyEntries(metainf, model, rootElt, propertyNames, typeMap, groupTypeMap, isInherited);

    }

    /**
     * @param metainf
     * @param model
     * @param rootElt
     * @param propertyNames
     * @param typeMap
     * @throws org.apache.axis2.schema.SchemaCompilationException
-     *
     */
    private void addPropertyEntries(BeanWriterMetaInfoHolder metainf,
                                    Document model, Element rootElt,
                                    ArrayList<String> propertyNames,
                                    Map<QName,String> typeMap,
                                    Map<QName,String> groupTypeMap,
                                    boolean isInherited) throws SchemaCompilationException {
        // go in the loop and add the part elements
        QName[] qName;
        ArrayList<QName> missingQNames = new ArrayList<QName>();
        ArrayList<QName> qNames = new ArrayList<QName>();
        BeanWriterMetaInfoHolder parentMetaInf = metainf.getParent();

        if (metainf.isOrdered()) {
            qName = metainf.getOrderedQNameArray();
        } else {
            qName = metainf.getQNameArray();
        }

        for (int i = 0; i < qName.length; i++) {
            qNames.add(qName[i]);
        }
        //adding missing QNames to the end, including elements & attributes.
        // for the simple types we have already add the parent elements
        // it is almost consider as an extension
        if (metainf.isRestriction() && !metainf.isSimple()) {
            addMissingQNames(metainf, qNames, missingQNames);
        }

        QName name;
        for (int i = 0; i < qName.length; i++) {
            Element property = XSLTUtils.addChildElement(model, "property", rootElt);
            name = qName[i];
            String xmlName = makeUniqueCStructName(new ArrayList<String>(), name.getLocalPart());

            XSLTUtils.addAttribute(model, "name", name.getLocalPart(), property);
            XSLTUtils.addAttribute(model, "originalName", name.getLocalPart(), property);


            XSLTUtils.addAttribute(model, "nsuri", name.getNamespaceURI(), property);
            XSLTUtils.addAttribute(model, "prefix", name.getPrefix(), property);

            XSLTUtils.addAttribute(model, "cname", xmlName.substring(4, xmlName.length() -3), property);


            String CClassNameForElement = metainf.getClassNameForQName(name);

            if (CClassNameForElement == null) {
                CClassNameForElement = CStructWriter.DEFAULT_C_CLASS_NAME;
            }
            CClassNameForElement = getShortTypeName(CClassNameForElement);


            XSLTUtils.addAttribute(model, "type", CClassNameForElement, property);

            /**
             * Caps for use in C macros
             */
            XSLTUtils.addAttribute(model, "caps-cname", xmlName.substring(4, xmlName.length() -3 ).toUpperCase(), property);
            XSLTUtils.addAttribute(model, "caps-type", CClassNameForElement.toUpperCase(), property);

            if (PrimitiveTypeFinder.isPrimitive(CClassNameForElement)) {
                XSLTUtils.addAttribute(model, "primitive", "yes", property);
            }
            //add an attribute that says the type is default
            if (isDefault(CClassNameForElement)) {
                XSLTUtils.addAttribute(model, "default", "yes", property);
            }

             // add the default value
            if (metainf.isDefaultValueAvailable(name)){
                QName schemaQName = metainf.getSchemaQNameForQName(name);
                if (baseTypeMap.containsKey(schemaQName)){
                    XSLTUtils.addAttribute(model, "defaultValue",
                            metainf.getDefaultValueForQName(name), property);
                }
            }

            if (typeMap.containsKey(metainf.getSchemaQNameForQName(name)) ||
                    (metainf.getSchemaQNameForQName(name) == null ||
                    !metainf.getSchemaQNameForQName(name).getNamespaceURI().equals(DEFAULT_TYPE_NS))
                     && !CClassNameForElement.equals(DEFAULT_C_CLASS_NAME)
                     && !CClassNameForElement.equals(DEFAULT_ATTRIB_CLASS_NAME)) {
                XSLTUtils.addAttribute(model, "ours", "yes", property);

            }

            if (metainf.getAttributeStatusForQName(name)) {
                XSLTUtils.addAttribute(model, "attribute", "yes", property);
            }else{
                XSLTUtils.addAttribute(model, "notattribute", "yes", property);
            }

            if (metainf.isNillable(name)) {
                XSLTUtils.addAttribute(model, "nillable", "yes", property);
            }

            String shortTypeName;
            if (metainf.getSchemaQNameForQName(name) != null) {
                //see whether the QName is a basetype
                if (baseTypeMap.containsKey(metainf.getSchemaQNameForQName(name))) {
                    shortTypeName = metainf.getSchemaQNameForQName(name).getLocalPart();
                } else {
                    shortTypeName = getShortTypeName(CClassNameForElement);
                }
            } else {
                shortTypeName = getShortTypeName(CClassNameForElement);
            }
            XSLTUtils.addAttribute(model, "shorttypename", shortTypeName, property);

            if (isInherited) {
                XSLTUtils.addAttribute(model, "inherited", "yes", property);
            }

            if (metainf.getAnyStatusForQName(name)) {
                XSLTUtils.addAttribute(model, "any", "yes", property);
            }

            if (metainf.getBinaryStatusForQName(name)) {
                XSLTUtils.addAttribute(model, "binary", "yes", property);
            }

            if (metainf.getSimpleStatusForQName(name)) {
                XSLTUtils.addAttribute(model, "simple", "yes", property);
            }

            //put the min occurs count irrespective of whether it's an array or not
            long minOccurs = metainf.getMinOccurs(name);
            XSLTUtils.addAttribute(model, "minOccurs", minOccurs + "", property);


            if (metainf.getArrayStatusForQName(name)) {
                String attrName = name.getLocalPart();
                int arrayTokenStart = attrName.indexOf("Array");
                if (arrayTokenStart >= 0) {
                    String arrayEle = attrName.substring(0, arrayTokenStart);
                    XSLTUtils.addAttribute(model, "arrayele", arrayEle, property);
                }
                XSLTUtils.addAttribute(model, "isarray", "yes", property);
                XSLTUtils.addAttribute(
                        model,
                        "arrayBaseType",
                        CClassNameForElement,
                        property);

                long maxOccurs = metainf.getMaxOccurs(name);
                if (maxOccurs == Long.MAX_VALUE) {
                    XSLTUtils.addAttribute(model, "unbound", "yes", property);
                }
                XSLTUtils.addAttribute(model, "maxOccurs", maxOccurs + "", property);

            }

            if ((parentMetaInf != null) && metainf.isRestriction() && missingQNames.contains(name)) {
                // this element details should be there with the parent meta Inf
                addAttributesToProperty(
                        parentMetaInf,
                        name,
                        model,
                        property,
                        typeMap,
                        groupTypeMap,
                        CClassNameForElement);

            } else {
                addAttributesToProperty(
                        metainf,
                        name,
                        model,
                        property,
                        typeMap,
                        groupTypeMap,
                        CClassNameForElement);
            }
        }
    }

    private void addAttributesToProperty(BeanWriterMetaInfoHolder metainf,
                                             QName name,
                                             Document model,
                                             Element property,
                                             Map<QName,String> typeMap,
                                             Map<QName,String> groupTypeMap,
                                             String CClassNameForElement) {
            // add an attribute that says the type is default
            if (metainf.getDefaultStatusForQName(name)) {
                XSLTUtils.addAttribute(model, "default", "yes", property);
            }

            if (typeMap.containsKey(metainf.getSchemaQNameForQName(name)) ||
                    groupTypeMap.containsKey(metainf.getSchemaQNameForQName(name)) ||
                    (metainf.getSchemaQNameForQName(name) == null ||
                    !metainf.getSchemaQNameForQName(name).getNamespaceURI().equals(DEFAULT_TYPE_NS))
                     && !CClassNameForElement.equals(DEFAULT_C_CLASS_NAME)
                     && !CClassNameForElement.equals(DEFAULT_ATTRIB_CLASS_NAME)) {
                XSLTUtils.addAttribute(model, "ours", "yes", property);
            }

            if (metainf.getAttributeStatusForQName(name)) {
                XSLTUtils.addAttribute(model, "attribute", "yes", property);
            }

            if (metainf.isNillable(name)) {
                XSLTUtils.addAttribute(model, "nillable", "yes", property);
            }

            if (metainf.getOptionalAttributeStatusForQName(name)) {
                XSLTUtils.addAttribute(model, "optional", "yes", property);
            }

            String shortTypeName;
            if (metainf.getSchemaQNameForQName(name) != null) {
                // see whether the QName is a basetype
                if (baseTypeMap.containsKey(metainf.getSchemaQNameForQName(name))) {
                    shortTypeName = metainf.getSchemaQNameForQName(name).getLocalPart();
                } else {
                    shortTypeName = getShortTypeName(CClassNameForElement);
                }
            } else {
                shortTypeName = getShortTypeName(CClassNameForElement);
            }
            XSLTUtils.addAttribute(model, "shorttypename", shortTypeName, property);


            if (metainf.getAnyStatusForQName(name)) {
                XSLTUtils.addAttribute(model, "any", "yes", property);
            }

            if (metainf.getBinaryStatusForQName(name)) {
                XSLTUtils.addAttribute(model, "binary", "yes", property);
            }

            if (metainf.isSimple() || metainf.getSimpleStatusForQName(name)) {
                XSLTUtils.addAttribute(model, "simple", "yes", property);
            }

            // put the min occurs count irrespective of whether it's an array or
            // not
            long minOccurs = metainf.getMinOccurs(name);
            XSLTUtils.addAttribute(model, "minOccurs", minOccurs + "", property);


            if (metainf.getArrayStatusForQName(name)) {

                XSLTUtils.addAttribute(model, "array", "yes", property);

                int endIndex = CClassNameForElement.indexOf("[");
                if (endIndex >= 0) {
                    XSLTUtils.addAttribute(model, "arrayBaseType",
                            CClassNameForElement.substring(0, endIndex), property);
                } else {
                    XSLTUtils.addAttribute(model, "arrayBaseType",
                            CClassNameForElement, property);
                }

                long maxOccurs = metainf.getMaxOccurs(name);
                if (maxOccurs == Long.MAX_VALUE) {
                    XSLTUtils.addAttribute(model, "unbound", "yes", property);
                } else {
                    XSLTUtils.addAttribute(model, "maxOccurs", maxOccurs + "", property);
                }
            }
            if (metainf.isRestrictionBaseType(name)) {
                XSLTUtils.addAttribute(model, "restrictionBaseType", "yes", property);
            }

            if (metainf.isExtensionBaseType(name)) {
                XSLTUtils.addAttribute(model, "extensionBaseType", "yes", property);
            }

            if (metainf.isRestrictionBaseType(name) && metainf.getLengthFacet() != -1) {
                XSLTUtils.addAttribute(model, "lenFacet", metainf.getLengthFacet() + "", property);
            }

            if (metainf.isRestrictionBaseType(name) && metainf.getMaxLengthFacet() != -1) {
                XSLTUtils.addAttribute(model, "maxLenFacet", metainf.getMaxLengthFacet() + "", property);
            }

            if (metainf.isRestrictionBaseType(name) && metainf.getMinLengthFacet() != -1) {
                XSLTUtils.addAttribute(model, "minLenFacet", metainf.getMinLengthFacet() + "", property);
            }

            if (metainf.isRestrictionBaseType(name) && metainf.getTotalDigitsFacet() != null) {
            XSLTUtils.addAttribute(model, "totalDigitsFacet", metainf.getTotalDigitsFacet() + "", property);
            }

            if (metainf.isRestrictionBaseType(name) && metainf.getMaxExclusiveFacet() != null) {
                XSLTUtils.addAttribute(model, "maxExFacet", metainf.getMaxExclusiveFacet() + "", property);
            }

            if (metainf.isRestrictionBaseType(name) && metainf.getMinExclusiveFacet() != null) {
                XSLTUtils.addAttribute(model, "minExFacet", metainf.getMinExclusiveFacet() + "", property);
            }

            if (metainf.isRestrictionBaseType(name) && metainf.getMaxInclusiveFacet() != null) {
                XSLTUtils.addAttribute(model, "maxInFacet", metainf.getMaxInclusiveFacet() + "", property);
            }

            if (metainf.isRestrictionBaseType(name) && metainf.getMinInclusiveFacet() != null) {
                XSLTUtils.addAttribute(model, "minInFacet", metainf.getMinInclusiveFacet() + "", property);
            }

            if (!metainf.getEnumFacet().isEmpty()) {
                boolean validJava = true;    // Assume all enum values are valid ids

                // Walk the values looking for invalid ids
                for (String value : metainf.getEnumFacet()) {
                    if (!JavaUtils.isJavaId(value)) {
                        validJava = false;
                    }
                }

                int id = 0;
                for (String attribValue : metainf.getEnumFacet()) {
                    Element enumFacet = XSLTUtils.addChildElement(model, "enumFacet", property);
                    XSLTUtils.addAttribute(model, "value", attribValue, enumFacet);
                    if (validJava) {
                        XSLTUtils.addAttribute(model, "id", attribValue.toUpperCase(), enumFacet);
                    } else {
                        id++;
                        // Replace all invalid characters and append an id to avoid collisions
                        XSLTUtils.addAttribute(model, "id", 
                                attribValue.toUpperCase().replaceAll("[^A-Z0-9]", "_") + "_" + id, enumFacet);
                    }
                }
            }

            if (metainf.isRestrictionBaseType(name) && metainf.getPatternFacet() != null) {
                XSLTUtils.addAttribute(model, "patternFacet", metainf.getPatternFacet(), property);
            }
        }



    private void addMissingQNames(BeanWriterMetaInfoHolder metainf, ArrayList<QName> qName, ArrayList<QName> missingQNames) {

        QName[] qNames = null;
        QName[] pQNames = null;

        BeanWriterMetaInfoHolder parentMetaInf = metainf.getParent();

        if (metainf.isOrdered()) {
            qNames = metainf.getOrderedQNameArray();
        } else {
            qNames = metainf.getQNameArray();
        }

        if (parentMetaInf != null) {
            if (parentMetaInf.isOrdered()) {
                pQNames = parentMetaInf.getOrderedQNameArray();
            } else {
                pQNames = parentMetaInf.getQNameArray();
            }
        }


        for (int i = 0; pQNames != null && i < pQNames.length; i++) {
            if (qNameNotFound(pQNames[i], metainf)) {
                missingQNames.add(pQNames[i]);
            }
        }
        //adding missing QNames to the end of list.
        if (!missingQNames.isEmpty()) {
            for (int i = 0; i < missingQNames.size(); i++) {
                qName.add(missingQNames.get(i));
            }
        }

    }

    private boolean qNameNotFound(QName qname, BeanWriterMetaInfoHolder metainf) {

        boolean found = false;
        QName[] qNames;

        if (metainf.isOrdered()) {
            qNames = metainf.getOrderedQNameArray();
        } else {
            qNames = metainf.getQNameArray();
        }

        for (int j = 0; j < qNames.length; j++) {
            if (qname.getLocalPart().equals(qNames[j].getLocalPart())) {
                found = true;
            }
        }
        return !found;
    }

    /**
     * Test whether the given class name matches the default
     *
     * @param CClassNameForElement
     * @return bool
     */
    private boolean isDefault(String CClassNameForElement) {
        return getDefaultClassName().equals(CClassNameForElement) ||
                getDefaultClassArrayName().equals(CClassNameForElement);
    }


    /**
     * Given the xml name, make a unique class name taking into account that some
     * file systems are case sensitive and some are not.
     * -Consider the Jax-WS spec for this
     *
     * @param listOfNames
     * @param xmlName
     * @return Returns String.
     */
    private String makeUniqueCStructName(List<String> listOfNames, String xmlName) {
        String cName;
        if (CUtils.isCKeyword(xmlName)) {
            cName = CUtils.makeNonCKeyword(xmlName);
        } else {
            //javaName = JavaUtils.capitalizeFirstChar(JavaUtils.xmlNameToJava(xmlName));
            cName = xmlName;
        }

        cName = cName.replace('.','_');
        cName = cName.replace('-','_');

        while (listOfNames.contains(cName.toLowerCase())) {
            if (!listOfNames.contains((cName + "E").toLowerCase())){
                cName = cName + "E";
            } else {
                cName = cName + count++;
            }
            cName = cName + CStructWriter.count++;
        }

        listOfNames.add(cName.toLowerCase());

        String modifiedCName = ADB_CLASS_PREFIX  + cName + ADB_CLASS_POSTFIX;
        return modifiedCName;
    }


    /**
     * A bit of code from the old code generator. We are better off using the template
     * engines and such stuff that's already there. But the class writers are hard to be
     * reused so some code needs to be repeated (atleast a bit)
     */
    private void loadTemplate() throws SchemaCompilationException {

        //first get the language specific property map
        Class<?> clazz = this.getClass();
        InputStream xslStream;
        String templateName = javaBeanTemplateName;
        if (templateName != null) {
            try {
                String sourceTemplateName = templateName + "Source.xsl";
                xslStream = clazz.getResourceAsStream(sourceTemplateName);
                sourceTemplateCache = TransformerFactory.newInstance().newTemplates(new StreamSource(xslStream));

                String headerTemplateName = templateName + "Header.xsl";
                xslStream = clazz.getResourceAsStream(headerTemplateName);
                headerTemplateCache = TransformerFactory.newInstance().newTemplates(new StreamSource(xslStream));

                templateLoaded = true;
            } catch (TransformerConfigurationException e) {
                throw new SchemaCompilationException(SchemaCompilerMessages.getMessage("schema.templateLoadException"), e);
            }
        } else {
            throw new SchemaCompilationException(SchemaCompilerMessages.getMessage("schema.templateNotFoundException"));
        }
    }


    /**
     * Creates the output file
     *
     * @param fileName
     * @param extension
     * @throws Exception
     */
    protected File createOutFile(String fileName, String extension, String prefix) throws Exception {
        return org.apache.axis2.util.FileWriter.createClassFile(this.rootDir,
                "",
                (prefix == null ? AXIS2_PREFIX : prefix) + fileName,
                extension);
    }

    /**
     * Writes the output file
     *
     * @param doc
     * @param outputFile
     * @throws Exception
     */
    private void parseSource(Document doc, File outputFile) throws Exception {
        OutputStream outStream = new FileOutputStream(outputFile);
        XSLTTemplateProcessor.parse(outStream,
                doc,
                this.sourceTemplateCache.newTransformer());
        outStream.write('\n');
        outStream.write('\n');
        outStream.flush();
        outStream.close();

    }

    /**
     * Writes the output file
     *
     * @param doc
     * @param outputFile
     * @throws Exception
     */
    private void parseHeader(Document doc, File outputFile) throws Exception {
        OutputStream outStream = new FileOutputStream(outputFile);
        XSLTTemplateProcessor.parse(outStream,
                doc,
                this.headerTemplateCache.newTransformer());
        outStream.write('\n');
        outStream.write('\n');
        outStream.flush();
        outStream.close();

    }

    /**
     * Get a prefix for a namespace URI.  This method will ALWAYS
     * return a valid prefix - if the given URI is already mapped in this
     * serialization, we return the previous prefix.  If it is not mapped,
     * we will add a new mapping and return a generated prefix of the form
     * "ns<num>".
     *
     * @param uri is the namespace uri
     * @return Returns prefix.
     */
    public String getPrefixForURI(String uri) {
        return getPrefixForURI(uri, null);
    }

    /**
     * Last used index suffix for "ns"
     */
    private int lastPrefixIndex = 1;

    /**
     * Map of namespaces URI to prefix(es)
     */
    HashMap<String,String> mapURItoPrefix = new HashMap<String,String>();
    HashMap<String,String> mapPrefixtoURI = new HashMap<String,String>();

    /**
     * Get a prefix for the given namespace URI.  If one has already been
     * defined in this serialization, use that.  Otherwise, map the passed
     * default prefix to the URI, and return that.  If a null default prefix
     * is passed, use one of the form "ns<num>"
     */
    public String getPrefixForURI(String uri, String defaultPrefix) {
        if ((uri == null) || (uri.length() == 0))
            return null;
        String prefix = mapURItoPrefix.get(uri);
        if (prefix == null) {
            if (defaultPrefix == null || defaultPrefix.length() == 0) {
                prefix = "ns" + lastPrefixIndex++;
                while (mapPrefixtoURI.get(prefix) != null) {
                    prefix = "ns" + lastPrefixIndex++;
                }
            } else {
                prefix = defaultPrefix;
            }
            mapPrefixtoURI.put(prefix, uri);
            mapURItoPrefix.put(uri, prefix);
        }
        return prefix;
    }

    private String getShortTypeName(String typeClassName) {
        if (typeClassName.endsWith("[]")) {
            typeClassName = typeClassName.substring(0, typeClassName.lastIndexOf("["));
        }
        return typeClassName;

    }

    /**
     * Keep unimplemented
     *
     * @param mapperPackageName
     * @see BeanWriter#registerExtensionMapperPackageName(String)
     */
    public void registerExtensionMapperPackageName(String mapperPackageName) {
        //unimplemented
    }

    /**
     * Generate the Extension Mapper module. This provides pseudo-polymorphism
     * support to the generated code, allowing to send and receive derived
     * classes in requests/replies that expect a base class.
     * @param metainfArray
     * @see BeanWriter#writeExtensionMapper(org.apache.axis2.schema.BeanWriterMetaInfoHolder[])
     */
    public void writeExtensionMapper(BeanWriterMetaInfoHolder[] metainfArray) throws SchemaCompilationException {
        // generate the element
        try {
            Document model = XSLTUtils.getDocument();
            Element rootElt = XSLTUtils.getElement(model, "mapper");
            Element rootElt2 = XSLTUtils.getElement(model, "mapper");
            String extMapperName = CStructWriter.EXTENSION_MAPPER_CLASSNAME;

            if (!wrapClasses) {
                XSLTUtils.addAttribute(model, "unwrapped", "yes", rootElt);
            }

            if (!writeClasses) {
                XSLTUtils.addAttribute(model, "skip-write", "yes", rootElt);
            }

            for (BeanWriterMetaInfoHolder metainf : metainfArray) {
                QName ownQname = metainf.getOwnQname();
                String className = metainf.getOwnClassName();
                //do  not add when the qname is not availble
                if (ownQname != null) {
                    Element typeChild = XSLTUtils.addChildElement(model, "type", rootElt);
                    XSLTUtils.addAttribute(model, "nsuri", ownQname.getNamespaceURI(), typeChild);
                    XSLTUtils.addAttribute(model, "classname", className == null ? "" : className, typeChild);
                    XSLTUtils.addAttribute(model, "shortname", ownQname == null ? "" :
                            ownQname.getLocalPart(), typeChild);
                }
            }

            model.appendChild(rootElt);

            if (!templateLoaded) {
                loadTemplate();
            }

            // if wrapped then do not write the classes now but add the models to a global document. However in order to write the
            // global class that is generated, one needs to call the writeBatch() method
            if (wrapClasses) {
                rootElt2 = (Element) globalWrappedSourceDocument.importNode(rootElt, true);
                // add to the global wrapped document
                globalWrappedSourceDocument.getDocumentElement().appendChild(rootElt2);
                XSLTUtils.addAttribute(globalWrappedSourceDocument, "name", extMapperName, rootElt2);
                XSLTUtils.addAttribute(globalWrappedSourceDocument, "caps-name", extMapperName.toUpperCase(), rootElt2);

                rootElt2 = (Element) globalWrappedHeaderDocument.importNode(rootElt, true);
                // add to the global wrapped document
                globalWrappedHeaderDocument.getDocumentElement().appendChild(rootElt2);
                XSLTUtils.addAttribute(globalWrappedHeaderDocument, "name", extMapperName, rootElt2);
                XSLTUtils.addAttribute(globalWrappedHeaderDocument, "caps-name", extMapperName.toUpperCase(), rootElt2);
        } else {

            XSLTUtils.addAttribute(model, "name", extMapperName, model.getDocumentElement());
            XSLTUtils.addAttribute(model, "caps-name", extMapperName.toUpperCase(), rootElt);

            if (writeClasses) {
                // create the files
                File outSource = createOutFile(extMapperName, ".c", "axis2_");
                File outHeader = createOutFile(extMapperName, ".h", "axis2_");
                // parse with the templates
                parseSource(model, outSource);
                parseHeader(model, outHeader);
            }

            // add the model to the model map
            modelMap.put(new QName(extMapperName), model);
            modelMap.put(new QName(extMapperName), model);

        }

        } catch (ParserConfigurationException e) {
            throw new SchemaCompilationException(SchemaCompilerMessages.getMessage("schema.document.error"), e);
        } catch (Exception e) {
            e.printStackTrace();
            throw new SchemaCompilationException(e);
        }
    }

    /**
     * Keep unimplemented
     *
     * @see BeanWriter#getExtensionMapperPackageName()
     */
    public String getExtensionMapperPackageName() {
        return null;
    }
}
