com.stratumsoft.xmlgen.SchemaTypeXmlGenerator.java Source code

Java tutorial

Introduction

Here is the source code for com.stratumsoft.xmlgen.SchemaTypeXmlGenerator.java

Source

/*
 * Copyright (c) 2010-2014 Stratumsoft Technologies Pvt. Ltd.
 *
 * This file (SchemaTypeXmlGenerator.java) is part of xsd2xml.
 *
 * xsd2xml is a Java program to generate XML instances from an XML Schema document
 *
 * xsd2xml is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * xsd2xml is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; If not, see <http://www.gnu.org/licenses/>.
 *
 * To use xsd2xml in your non-GPL licensed software, contact Stratumsoft Technologies
 * support at support@stratumsoft.com or visit http://www.stratumsoft.com to obtain
 * a commercial license.
 */

package com.stratumsoft.xmlgen;

import org.apache.commons.lang.StringUtils;
import org.apache.ws.commons.schema.*;
import org.apache.ws.commons.schema.constants.Constants;
import org.apache.ws.commons.schema.utils.NamespaceMap;
import org.apache.ws.commons.schema.utils.NamespacePrefixList;
import org.apache.ws.commons.schema.utils.XmlSchemaRef;
import org.dom4j.*;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.namespace.QName;
import java.io.IOException;
import java.io.StringWriter;
import java.text.MessageFormat;
import java.util.*;

/**
 * This class is responsible for processing an XmlSchema type and generating an xml instance for it.
 *
 * @author murakris@stratumsoft.com
 * @since 1.0 (Feb 22, 2010)
 */
public class SchemaTypeXmlGenerator {

    public static final String DEFAULT_PREFIX = "ns"; //NON-NLS

    private DocumentFactory factory;

    private XmlGenOptions options;

    private static final Logger logger = LoggerFactory.getLogger(SchemaTypeXmlGenerator.class);

    /**
     * to keep track of the types being processed in case of recursivity
     */
    private List<XmlSchemaComplexType> processedTypes;

    /**
     * keep track of the recursive count for each
     */
    private Map<QName, Integer> recursiveCount;

    private OutputFormat outputFormat;

    public XMLWriter writer;

    private XmlSchemaType lastRecursiveType;

    private XmlSchemaCollection schemaColl;

    /**
     * Keep track of the current schema whose element's /attributes are being processed
     */
    private Stack<XmlSchema> schemaStack;

    private NamespaceMap nsMap;

    private int prefixCounter = 1;

    public SchemaTypeXmlGenerator(XmlSchemaCollection schemaColl) {
        this(schemaColl, null);
    }

    public SchemaTypeXmlGenerator(XmlSchemaCollection schemaCollection, XmlGenOptions options) {
        this.schemaColl = schemaCollection;

        if (options == null) {
            this.options = new XmlGenOptions();
        } else
            this.options = options;

        init();
    }

    private void init() {
        factory = DocumentFactory.getInstance();

        outputFormat = options.getOutputFormat();
        outputFormat.setExpandEmptyElements(true); //expand elements into form <a></a> instead of <a/>; TBD: expose

        processedTypes = new ArrayList<>();
        recursiveCount = new HashMap<>();

        schemaStack = new Stack<>();

        initNSMap();
    }

    private void initNSMap() {
        nsMap = new NamespaceMap();
        NamespacePrefixList nsCtx = schemaColl.getNamespaceContext();
        populateNSMap(nsCtx);

        XmlSchema[] xmlSchemas = schemaColl.getXmlSchemas();
        if (xmlSchemas != null && xmlSchemas.length > 0) {
            for (XmlSchema schema : xmlSchemas) {
                nsCtx = schema.getNamespaceContext();
                populateNSMap(nsCtx);
            }
        }
        logger.debug(MessageFormat.format("namespace map contains {0} entries", nsMap.size()));
    }

    private void populateNSMap(NamespacePrefixList nsCtx) {
        if (nsCtx != null) {
            String[] prefixes = nsCtx.getDeclaredPrefixes();
            if (prefixes != null) {
                for (String prefix : prefixes) {
                    if (StringUtils.isNotEmpty(prefix))
                        nsMap.add(prefix, nsCtx.getNamespaceURI(prefix));
                }
            }
        }
    }

    /**
     * Generate a dom4j element representing the dom structure for the given schema element qname
     *
     * @param elName qname of the element for which the xml structure must be generated
     */
    public Element generateElement(QName elName) {
        Element elem = null;
        XmlSchemaElement schEl = schemaColl.getElementByQName(elName);
        if (schEl != null)
            elem = handleElement(schEl);
        else {
            String err = MessageFormat.format("Could not get schema element for QName: {0}", elName);
            logger.error(err);
            throw new RuntimeException(err);
        }

        return elem;
    }

    /**
     * Generate an xml representation of the dom structure of the schema element
     *
     * @param elName the QName of the element for which the XML should be generated
     * @return the xml instance for the schema element, or empty string if no element with the given qname was found
     */

    public String generateXml(QName elName) {
        return generateXml(elName, false);
    }

    /**
     * Generate an xml representation of the dom structure of the schema element
     *
     * @param elName        the QName of the element for which the XML should be generated
     * @param isPrettyPrint if true formats and indents the generated xml
     * @return the xml instance for the schema element, or empty string if no element with the given qname was found
     */
    public String generateXml(QName elName, boolean isPrettyPrint) {
        String xml = "";
        if (elName != null && schemaColl != null) {

            Document doc = factory.createDocument("utf-8"); //NON-NLS
            Element el = generateElement(elName);
            if (el != null) {
                doc.add(el);
            } else {
                logger.warn("got null for element generated for qname: {}", elName);
            }

            if (isPrettyPrint)
                outputFormat = OutputFormat.createPrettyPrint();

            StringWriter sw = new StringWriter();
            writer = new XMLWriter(sw, outputFormat);
            try {
                writer.write(doc);
                xml = sw.toString();
                logger.trace("Serialized dom4j doc to xml string: {}", xml);

            } catch (IOException e) {
                logger.error("dom4j Document to xml creation error", e);
            }

        }

        return xml;
    }

    /**
     * Processes the given complex type and adds elements/attributes to the given root element
     *
     * @param typeName
     * @param rootEl
     */
    public void generateXmlForType(QName typeName, Element rootEl) {
        if (typeName != null && rootEl != null) {

            XmlSchemaType schemaType = schemaColl.getTypeByQName(typeName);
            if (schemaType != null) {

                if (schemaType instanceof XmlSchemaComplexType) {
                    handleComplexType((XmlSchemaComplexType) schemaType, rootEl);
                }
            } else {
                logger.warn("Could not locate any type with name: {}", typeName);
            }
        } else {
            logger.warn("Either type name or root element is null, cannot generate xml");
        }
    }

    private Element createDomElemFromSchemaElem(XmlSchemaElement schEl) {
        Element el = null;
        if (schEl != null) {
            QName elName = schEl.getQName();

            //get the value from the schema
            XmlSchema sch = getSchemaForElement(elName);

            //get the element form value
            XmlSchemaForm form;

            if (sch == null) {
                //not a global element 
                form = schEl.getForm();
                if (form == XmlSchemaForm.NONE) {
                    //get the default elem form value from the schema
                    String nsUri = elName.getNamespaceURI();
                    sch = getSchemaByTargetNamespace(nsUri);
                    if (sch != null) {
                        form = sch.getElementFormDefault();
                    } else {
                        form = XmlSchemaForm.UNQUALIFIED;
                    }
                }
            } else {
                //global elements must be qualified
                form = XmlSchemaForm.QUALIFIED;
            }

            org.dom4j.QName dom4jQName = createDom4jQName(elName, form);

            el = factory.createElement(dom4jQName);

        }
        return el;
    }

    public XmlSchema getSchemaByTargetNamespace(String namespaceURI) {
        if (namespaceURI != null) {

            XmlSchema[] xmlSchemas = schemaColl.getXmlSchemas();
            if (xmlSchemas != null) {
                for (XmlSchema schema : xmlSchemas) {
                    String tns = schema.getTargetNamespace();
                    if (tns != null && tns.equals(namespaceURI)) {
                        return schema;
                    }
                }
            }
        }
        return null;
    }

    private XmlSchema getSchemaForElement(QName elName) {
        XmlSchema[] xmlSchemas = schemaColl.getXmlSchemas();
        if (xmlSchemas != null && xmlSchemas.length > 0) {
            for (XmlSchema sch : xmlSchemas) {
                if (sch.getElementByName(elName) != null) {
                    return sch;

                }
            }
        }
        return null;
    }

    private org.dom4j.QName createDom4jQName(QName qname, XmlSchemaForm form) {
        org.dom4j.QName dom4jQname = null;

        if (qname != null) {
            String nsUri = qname.getNamespaceURI();
            Namespace ns = null;
            if (StringUtils.isNotEmpty(nsUri)) {
                if (form == XmlSchemaForm.QUALIFIED) {
                    String prefix = nsMap.getPrefix(nsUri);

                    if (StringUtils.isEmpty(prefix)) {
                        prefix = DEFAULT_PREFIX + prefixCounter++;
                        nsMap.add(prefix, nsUri);
                    }

                    ns = new Namespace(prefix, nsUri);
                }
            }
            dom4jQname = new org.dom4j.QName(qname.getLocalPart(), ns);
        }
        return dom4jQname;
    }

    /**
     * Return the maximum number of element's to generate based on the element's minOccurs, maxOccurs
     * and max repeating elements option
     *
     * @param minOccurs
     * @param maxOccurs
     * @return
     */
    private long getMaxElementsToGenerate(long minOccurs, long maxOccurs) {
        int maxRpt = options.getMaxRepeatingElements();

        return options.isGenOptionalElements() ? Math.max(minOccurs, Math.min(maxRpt, maxOccurs)) : minOccurs;
    }

    /**
     * Handle the XmlSchema Element by processing its type and returning a dom4j element equivalent
     * for it to be added to the parent dom4j element
     *
     * @param schEl the xml schema element to process
     * @return the fully constructed dom4j element equivalent
     */
    private Element handleElement(XmlSchemaElement schEl) {

        XmlSchemaRef<XmlSchemaElement> ref = schEl.getRef();
        XmlSchemaElement refEl = ref.getTarget();
        if (refEl != null) {
            logger.debug("Handling schema element reference {}", refEl.getName());

            schEl = refEl;
        }

        //keep track of the current schema we are working with
        //this is required when processing local attributes whose form value is set to 'qualified'
        XmlSchema sch = getSchemaByTargetNamespace(schEl.getQName().getNamespaceURI());

        //if the schema for this element is null, it could be a local element otherwise keep track of it
        if (sch != null) {
            logger.trace("---> Pushed schema with tns\\: {} into stack", sch.getTargetNamespace());
            schemaStack.push(sch);
        }

        //check for recursivity of this element
        XmlSchemaType type = schEl.getSchemaType();
        QName elName = schEl.getQName();

        if (type instanceof XmlSchemaComplexType) {

            //don't process this type again because this looks like a recursive call within another recursive call
            if (type == lastRecursiveType) {
                return null;
            }

            boolean isProcessed = false;
            for (XmlSchemaComplexType processedType : processedTypes) {
                if (type == processedType) {
                    isProcessed = true;
                    break;
                }
            }

            if (isProcessed) {
                //this may be start of a recursion
                processedTypes.clear();

                int count = recursiveCount.containsKey(elName) ? recursiveCount.get(elName) : 0;
                count++;
                recursiveCount.put(elName, count);

                if (count > options.getMaxRecursiveDepth()) {
                    logger.debug("recursive count exceeded max recursive depth! Resetting count; Schema el= {}",
                            elName);
                    lastRecursiveType = type;

                    return null;
                }
            }
            processedTypes.add((XmlSchemaComplexType) type);
        }

        //create a dom4j element for this schema element
        Element dom4jEl = createDomElemFromSchemaElem(schEl);

        //process the schema type for this element
        if (type != null) {
            if (type instanceof XmlSchemaSimpleType) {
                logger.debug("Handling simple type: {}", type.getName() != null ? type.getName() : "anonymous");
                handleSimpleType((XmlSchemaSimpleType) type, dom4jEl);

            } else if (type instanceof XmlSchemaComplexType) {
                logger.debug("Handling complex type: " + (type.getName() != null ? type.getName() : "anonymous")); //NON-NLS
                XmlSchemaComplexType complexType = (XmlSchemaComplexType) type;

                if (complexType.isAbstract()) {
                    //cannot instantiate an abstract type - so search for another type that
                    //'extends' it
                    //todo: handle abstract complex type
                    logger.warn("Cannot instantiate an abstract complext type!");
                } else {
                    handleComplexType(complexType, dom4jEl);
                }
            }
        }

        if (!schemaStack.isEmpty()) {
            XmlSchema sc = schemaStack.pop();
            logger.trace("<--- Popped schema with tns: {} from stack", sc.getTargetNamespace());
        }

        return dom4jEl;
    }

    /**
     * Add the element to the parent branch. The min number of times to add it is determined by the element's minOccurs
     * value and the max no. of times to add it is determined by the minimum of the max repeating elements option
     * and the element's maxOccurs value
     * <p/>
     * Note: the element may again be added multiple times based on its container minOccurs and maxOccurs values
     *
     * @param branch
     * @param elemToAdd
     * @param schElemOfElemToAdd
     */

    private void addElement(Branch branch, Element elemToAdd, XmlSchemaElement schElemOfElemToAdd) {

        long minCount = schElemOfElemToAdd.getMinOccurs();
        long maxCount = schElemOfElemToAdd.getMaxOccurs();

        //determine how many times to add this element to the parent
        if (branch != null) {

            if (maxCount > 0) {
                long maxEls = getMaxElementsToGenerate(minCount, maxCount);

                for (long i = 1; i <= maxEls; i++) {
                    if (i > minCount) { //anything > the min count but < max is optional
                        if (options.isGenCommentsForParticles()) {
                            branch.add(factory.createComment("optional"));
                        }
                    }
                    logger.trace("Adding dom4j element: {} to branch: {}", elemToAdd.getName(), branch.getName());
                    branch.add(elemToAdd);

                    elemToAdd = elemToAdd.createCopy(); //cannot add same element again, so create a copy

                    //if id attr is present need to set a new value for it in the copied el
                    Attribute idAttr = elemToAdd.attribute("id"); //NON-NLS
                    if (idAttr != null) {
                        idAttr.setValue(SampleValueProvider.get(Constants.XSD_ID));
                    }
                }
            }
        }

    }

    /**
     * Handle the passed in complex type. this involves processing any attributes defined in this type
     * and processing the content model (simple/complex) if any. If content model is absent, process
     * any particle that is part of this complex type
     *
     * @param complexType
     * @param dom4jEl
     */
    private void handleComplexType(XmlSchemaComplexType complexType, Element dom4jEl) {

        //first process the attributes for this type
        handleComplexTypeAttributes(dom4jEl, complexType);

        //process the content type for this complex type
        //content model can be complex or simple content
        XmlSchemaContentModel model = complexType.getContentModel();
        if (model != null) {
            if (model instanceof XmlSchemaSimpleContent) {
                logger.debug("Handling simple content model for complex type {}", complexType.getName());
                handleSimpleContent(((XmlSchemaSimpleContent) model), dom4jEl);
            } else {
                logger.debug("Handling complex content model for complex type {}", complexType.getName());
                handleComplexContent(((XmlSchemaComplexContent) model), dom4jEl);
            }
        } else {
            //check if content is a particle instead
            XmlSchemaParticle particle = complexType.getParticle();
            if (particle != null) {
                logger.debug("handling complex type particle");
                handleParticle(particle, dom4jEl);
            } else {
                logger.debug("complex type has no particle or content model!");
            }
        }
    }

    /**
     * Handle the complex type attributes - this can be a straight attribute or an attribute group reference
     *
     * @param dom4jEl
     * @param complexType
     */
    private void handleComplexTypeAttributes(Element dom4jEl, XmlSchemaComplexType complexType) {
        if (complexType != null) {
            List<XmlSchemaAttributeOrGroupRef> attributes = complexType.getAttributes();
            if (attributes != null) {
                for (XmlSchemaAttributeOrGroupRef o : attributes) {
                    if (o instanceof XmlSchemaAttribute) {
                        XmlSchemaAttribute attr = (XmlSchemaAttribute) o;
                        logger.debug("handling attribute {}", attr.getName());
                        handleAttribute(attr, dom4jEl);
                    } else if (o instanceof XmlSchemaAttributeGroupRef) {
                        XmlSchemaAttributeGroupRef attrGrpRef = (XmlSchemaAttributeGroupRef) o;
                        logger.debug("handling attribute group ref");
                        handleAttributeGroupRef(attrGrpRef, dom4jEl);
                    }
                }
            }
        }
    }

    /**
     * Handle the complex contentModel for the complex type. This can be either an extension or restriction
     *
     * @param contentModel
     * @param dom4jEl
     */
    private void handleComplexContent(XmlSchemaComplexContent contentModel, Element dom4jEl) {
        if (contentModel != null) {

            XmlSchemaContent content = contentModel.getContent();
            if (content != null) {

                if (content instanceof XmlSchemaComplexContentExtension) {
                    logger.debug("complex type content model content is 'extension'");
                    handleComplexContentExtension((XmlSchemaComplexContentExtension) content, dom4jEl);

                } else if (content instanceof XmlSchemaComplexContentRestriction) {
                    logger.debug("complex type content model content is 'restriction'");
                    handleComplexContentRestriction((XmlSchemaComplexContentRestriction) content, dom4jEl);

                }
            } else {
                logger.warn("Complex type content model content is null!");
            }
        }
    }

    /**
     * Handle complex type complex restriction model
     *
     * @param restriction
     * @param dom4jEl
     */
    private void handleComplexContentRestriction(XmlSchemaComplexContentRestriction restriction, Element dom4jEl) {
        logger.debug("Handling complex content restriction...");

        if (restriction != null) {
            XmlSchemaParticle particle = restriction.getParticle();
            if (particle != null) {
                handleParticle(particle, dom4jEl);
            }

            //attributes from base type only need to be specified if they are being restricted in some way.
            //so to generate all the attributes, we have to process the parent complex types and generate all the
            //attributes they have, unless they are being restricted in some way in this restriction, in which
            //case the restricted attr should be generated

            QName baseTypeName = restriction.getBaseTypeName();
            XmlSchemaType type = schemaColl.getTypeByQName(baseTypeName);

            handleComplexTypeAttributes(dom4jEl, (XmlSchemaComplexType) type);

            List<XmlSchemaAttributeOrGroupRef> attributeOrGroupRefs = restriction.getAttributes();
            for (XmlSchemaAttributeOrGroupRef o : attributeOrGroupRefs) {
                if (o instanceof XmlSchemaAttribute) {
                    XmlSchemaAttribute attribute = (XmlSchemaAttribute) o;
                    addRestrictedAttributesToElement(dom4jEl, attribute);

                } else if (o instanceof XmlSchemaAttributeGroupRef) {
                    XmlSchemaAttributeGroupRef groupRef = (XmlSchemaAttributeGroupRef) o;
                    XmlSchemaRef<XmlSchemaAttributeGroup> ref = groupRef.getRef();

                    XmlSchemaAttributeGroup attrGrp = ref.getTarget();

                    if (attrGrp != null) {
                        List<XmlSchemaAttributeGroupMember> grpMembers = attrGrp.getAttributes();
                        if (grpMembers != null) {
                            for (XmlSchemaAttributeGroupMember grpMember : grpMembers) {
                                if (grpMember instanceof XmlSchemaAttribute) {
                                    addRestrictedAttributesToElement(dom4jEl, (XmlSchemaAttribute) grpMember);
                                }
                            }
                        }
                    } //end if
                } //end else if
            } //end for
        } //end if

    }

    /* private XmlSchemaAttributeGroup getAttributeGroup(QName name) {
    XmlSchemaAttributeGroup attrGrp = null;
    XmlSchema[] xmlSchemas = schemaColl.getXmlSchemas();
    for (XmlSchema schema : xmlSchemas) {
        attrGrp = (XmlSchemaAttributeGroup) schema.getAttributeGroups().getItem(name);
        if (attrGrp != null) {
            break;
        }
    }
    return attrGrp;
    }*/

    private void addRestrictedAttributesToElement(Element elementToAddOn, XmlSchemaAttribute attribute) {
        logger.debug("Adding restricted attribute: {}", attribute.getName());
        QName attrQName = attribute.getQName();
        if (attrQName != null) {
            org.dom4j.QName dom4jQName = createDom4jQName(attrQName, attribute.getForm());
            Attribute attr = elementToAddOn.attribute(dom4jQName);
            if (attr != null) {
                //already exists, so remove it, so we can add the restricted version of it
                logger.debug("Removing existing attribute\\: {} to add the restricted attribute",
                        dom4jQName.getName());
                elementToAddOn.remove(attr);
            }

            attr = factory.createAttribute(elementToAddOn, dom4jQName, "");
            elementToAddOn.add(attr);
        }

    }

    /**
     * Handle complex type complex content extension model
     *
     * @param extension
     * @param dom4jEl
     */
    private void handleComplexContentExtension(XmlSchemaComplexContentExtension extension, Element dom4jEl) {

        logger.debug("Handling complex content extension...");

        if (extension != null) {
            //handle the attributes and attributeRef
            List<XmlSchemaAttributeOrGroupRef> attributeOrGroupRefs = extension.getAttributes();
            processAttributeCollection(dom4jEl, attributeOrGroupRefs);

            QName baseTypeName = extension.getBaseTypeName();
            if (baseTypeName != null) {
                logger.debug("Processing complex content base type: {}", baseTypeName);
                XmlSchemaType type = schemaColl.getTypeByQName(baseTypeName);
                handleComplexType((XmlSchemaComplexType) type, dom4jEl);
            } else {
                logger.warn("Complex content base type is null!");
            }

            //handle the complex content particle
            XmlSchemaParticle particle = extension.getParticle();
            handleParticle(particle, dom4jEl);

        }

    }

    /**
     * Handle the given xml schema particle - the particle can be all, any, choice, sequence,
     * group ref or an element
     *
     * @param particle
     * @param dom4jEl
     */
    private void handleParticle(XmlSchemaParticle particle, Element dom4jEl) {

        if (particle != null) {
            if (particle instanceof XmlSchemaAll) {
                logger.debug("handling particle 'all'...");
                handleParticleAll(((XmlSchemaAll) particle), dom4jEl);

            } else if (particle instanceof XmlSchemaAny) {
                handleParticleAny(((XmlSchemaAny) particle), dom4jEl);
                logger.debug("handling particle 'any'...");

            } else if (particle instanceof XmlSchemaChoice) {
                logger.debug("handling particle 'choice'...");
                handleParticleChoice(((XmlSchemaChoice) particle), dom4jEl);

            } else if (particle instanceof XmlSchemaSequence) {
                logger.debug("handling particle 'sequence'...");
                handleParticleSequence(((XmlSchemaSequence) particle), dom4jEl);

            } else if (particle instanceof XmlSchemaGroupRef) {
                logger.debug("handling particle 'GroupRef'...");
                handleParticleGroupRef(((XmlSchemaGroupRef) particle), dom4jEl);

            } else if (particle instanceof XmlSchemaElement) {
                XmlSchemaElement schEl = (XmlSchemaElement) particle;
                logger.debug("handling particle 'element' {}", schEl.getName());
                Element elem = handleElement(schEl);
                if (elem != null) {
                    addElement(dom4jEl, elem, schEl);
                }
            }

        } else {
            logger.warn("Schema particle is null!");
        }
    }

    private void handleParticleGroupRef(XmlSchemaGroupRef groupRef, Element dom4jEl) {
        if (groupRef != null) {
            long minCount = groupRef.getMinOccurs();
            long maxCount = groupRef.getMaxOccurs();

            logger.debug("Group reference minOccurs = {} and maxOccurs = {}", minCount, maxCount);
            QName refName = groupRef.getRefName();
            XmlSchemaGroup group = getGroup(refName);
            if (group != null) {
                logger.debug("Processing group with name: {}", refName);

                XmlSchemaGroupParticle grpParticle = group.getParticle();

                long maxEls = getMaxElementsToGenerate(minCount, maxCount);
                logger.debug("Max group ref generations will be {}", maxEls);

                for (int i = 0; i < maxEls; i++) {
                    handleParticle(grpParticle, dom4jEl);
                }
            }
        }
    }

    private XmlSchemaGroup getGroup(QName name) {
        XmlSchemaGroup group = null;
        for (XmlSchema schema : schemaColl.getXmlSchemas()) {
            group = schema.getGroups().get(name);
            if (group != null)
                break;
        }
        return group;
    }

    /**
     * Handle the choice particle - Depending on the option {@link XmlGenOptions#getChoiceOptions()} set,
     * either the first child particle or a random particle within choice will be processed.
     * Other elements may be generated as comments if the option
     * {@link XmlGenOptions#isGenChoiceOptionsAsComments()} is set to do so
     *
     * @param choice
     * @param dom4jEl
     */
    private void handleParticleChoice(XmlSchemaChoice choice, Element dom4jEl) {
        if (choice != null) {

            List<XmlSchemaObject> choiceItems = choice.getItems();
            int count = choiceItems.size();
            if (count > 0) {
                XmlSchemaParticle childParticle;
                switch (options.getChoiceOptions()) {
                case FIRST:
                    childParticle = (XmlSchemaParticle) choiceItems.get(0);
                    break;
                case RANDOM:
                    int i = new Random().nextInt(count);
                    childParticle = (XmlSchemaParticle) choiceItems.get(i);
                    break;
                default:
                    childParticle = (XmlSchemaParticle) choiceItems.get(0);
                    break;
                }

                long minOccurs = choice.getMinOccurs();
                long maxOccurs = choice.getMaxOccurs();
                logger.debug("Choice minOccurs = {} maxOccurs = {}", minOccurs, maxOccurs);

                long maxCount = getMaxElementsToGenerate(minOccurs, maxOccurs);
                logger.debug("Adding choice particle contents: {}  times", maxCount);

                for (int x = 0; x < maxCount; x++) {

                    handleParticle(childParticle, dom4jEl);

                    //generate other elements as comments?
                    if (options.isGenChoiceOptionsAsComments()) {
                        logger.trace("Adding other elements in choice as comments");
                        for (XmlSchemaObject obj : choiceItems) {
                            if (obj != childParticle) { //already handled
                                //generating only if the other choice is an element!
                                if (obj instanceof XmlSchemaElement) {
                                    Element optEl = handleElement((XmlSchemaElement) obj);
                                    if (optEl != null) {
                                        logger.trace("Adding element: {} as comment to choice compositor",
                                                optEl.getName());

                                        String comment = optEl.asXML();
                                        comment = comment.replace("--", "- -"); // -- is invalid within a comment, so escape it
                                        dom4jEl.addComment(comment);
                                    }
                                }
                            }
                        }

                    }
                }

            }

        }
    }

    /**
     * Handle the 'any' particle - since any element can be present, this just adds a commented
     * element if the gen comments option is set
     *
     * @param any
     * @param dom4jEl
     */
    private void handleParticleAny(XmlSchemaAny any, Element dom4jEl) {
        if (any != null) {
            if (options.isGenCommentsForParticles()) {
                dom4jEl.addComment("Any element can be present here");

                Element el = factory.createElement("SomeElement");
                dom4jEl.addComment(el.asXML());
            }
        }
    }

    /**
     * Handle the 'sequence' particle - sequence can inturn contain XmlSchemaElement, XmlSchemaGroupRef, XmlSchemaChoice,
     * XmlSchemaSequence, or XmlSchemaAny.
     *
     * @param seq
     * @param dom4jEl
     */
    private void handleParticleSequence(XmlSchemaSequence seq, Element dom4jEl) {
        if (seq != null) {
            List<XmlSchemaSequenceMember> seqItems = seq.getItems();

            if (seqItems.size() > 0) {

                if (options.isGenCommentsForParticles()) {
                    dom4jEl.addComment("sequence");
                }

                for (XmlSchemaSequenceMember seqMember : seqItems) {
                    logger.trace("Processing sequence collection particle");

                    if (seqMember instanceof XmlSchemaParticle) {

                        long min = seq.getMinOccurs();
                        long max = seq.getMaxOccurs();
                        logger.debug("sequence particle minOccurs = {}; maxOccurs = {}", min, max);

                        long maxCnt = getMaxElementsToGenerate(min, max);
                        logger.debug("handling sequence particles {} times", maxCnt);

                        for (int i = 0; i < maxCnt; i++) {
                            handleParticle((XmlSchemaParticle) seqMember, dom4jEl);
                        }

                    } else {
                        logger.error("sequence collection particle is not an instanceof XmlSchemaParticle!");
                    }
                }
            } else {
                logger.warn("sequence compositor is empty!");
            }
        }
    }

    /**
     * Handle the 'all' particle
     *
     * @param all
     * @param dom4jEl
     */
    private void handleParticleAll(XmlSchemaAll all, Element dom4jEl) {
        if (all != null) {
            List<XmlSchemaElement> allItems = all.getItems();

            if (options.isGenCommentsForParticles()) {
                //add a comment to indicate that this is an 'all' particle
                dom4jEl.addComment("following elements can appear in any order (all)");
            }

            logger.debug("Handling [{}] elements in 'all' particle", allItems.size());
            for (XmlSchemaElement schEl : allItems) {
                logger.trace("Processing element {}", schEl.getName());

                Element elem = handleElement(schEl);
                if (elem != null) {
                    addElement(dom4jEl, elem, schEl);
                }
            }

        }
    }

    /**
     * Handle the complex type simple content
     *
     * @param contentModel
     * @param dom4jEl
     */
    private void handleSimpleContent(XmlSchemaSimpleContent contentModel, Element dom4jEl) {
        if (contentModel != null) {
            XmlSchemaContent content = contentModel.getContent();
            if (content != null) {
                if (content instanceof XmlSchemaSimpleContentExtension) {
                    handleSimpleContentExtension((XmlSchemaSimpleContentExtension) content, dom4jEl);
                } else if (content instanceof XmlSchemaSimpleContentRestriction) {
                    handleSimpleContentRestriction(((XmlSchemaSimpleContentRestriction) content), dom4jEl);
                }
            }
        }
    }

    /**
     * Handle the complex type simple content restriction
     *
     * @param restriction
     * @param dom4jEl
     */
    private void handleSimpleContentRestriction(XmlSchemaSimpleContentRestriction restriction, Element dom4jEl) {
        logger.debug("Handling simple content restriction");

        //        restriction.getBaseType(); //todo: process the base schema simpletype if present

        QName baseTypeName = restriction.getBaseTypeName();
        logger.debug("Simple content restriction base type name = {}", baseTypeName);

        XmlSchemaType type = schemaColl.getTypeByQName(baseTypeName);
        if (type != null) {
            if (type instanceof XmlSchemaSimpleType) {
                logger.trace("Simple content restriction base type is simple type");
                handleSimpleType((XmlSchemaSimpleType) type, dom4jEl);
            } else if (type instanceof XmlSchemaComplexType) {
                logger.trace("Simple content restriction base type is complex type");
                handleComplexType((XmlSchemaComplexType) type, dom4jEl);

                //for restriction if there are any attributes specified, they are restrictions of the
                //base complex type attributes - so removing any attributes added in the prev step
                //and specified again and re-process it

                List<XmlSchemaAttributeOrGroupRef> attributeOrGroupRefs = restriction.getAttributes();
                if (attributeOrGroupRefs != null) {
                    logger.debug("simple content restriction is overriding base type attributes");

                    //collect all attributes that are being overridden
                    List<XmlSchemaAttribute> attributes = new ArrayList<>();

                    for (XmlSchemaAttributeOrGroupRef o : attributeOrGroupRefs) {

                        if (o instanceof XmlSchemaAttribute) {
                            attributes.add((XmlSchemaAttribute) o);

                        } else if (o instanceof XmlSchemaAttributeGroupRef) {

                            XmlSchemaRef<XmlSchemaAttributeGroup> ref = ((XmlSchemaAttributeGroupRef) o).getRef();
                            if (ref != null) {
                                XmlSchemaAttributeGroup attrGroup = ref.getTarget();
                                if (attrGroup != null) {
                                    List<XmlSchemaAttributeGroupMember> attrGroupMembers = attrGroup
                                            .getAttributes();
                                    for (XmlSchemaAttributeGroupMember attrGroupMember : attrGroupMembers) {
                                        if (attrGroupMember instanceof XmlSchemaAttribute) {
                                            attributes.add((XmlSchemaAttribute) attrGroupMember);
                                        }
                                        //else todo: check if we need to handle group refs within group?
                                    }
                                }
                            }

                        } // end else if
                    } //end while

                    //now if any attribute with the same name as the overridden attribute was added from the base type
                    //remove it and add it again
                    for (XmlSchemaAttribute schAttr : attributes) {
                        QName qname = schAttr.getQName();
                        org.dom4j.QName dom4jQName = createDom4jQName(qname, schAttr.getForm());
                        logger.debug("Created dom4j qname: {} from XmlSchema qname: {}", dom4jQName, qname);

                        //remove this attribute
                        Attribute dom4jAttr = dom4jEl.attribute(dom4jQName);
                        dom4jEl.remove(dom4jAttr);

                        //handle this attribute again - but this time with the restrictions defined for it
                        handleAttribute(schAttr, dom4jEl);
                    }

                } //end if

            } //end else if
        } //end if
    }

    private void handleSimpleContentExtension(XmlSchemaSimpleContentExtension extension, Element dom4jEl) {
        logger.debug("Handling simple content extension");

        //process the attributes if any
        List<XmlSchemaAttributeOrGroupRef> attributeOrGroupRefs = extension.getAttributes();
        if (attributeOrGroupRefs != null) {
            logger.debug("Processing attributes for simple content extension");
            processAttributeCollection(dom4jEl, attributeOrGroupRefs);
        } else {
            logger.trace("Simple content extension does not have any attributes");
        }

        //process the base type
        QName baseTypeName = extension.getBaseTypeName();
        XmlSchemaType type = schemaColl.getTypeByQName(baseTypeName);
        if (type != null) {
            if (type instanceof XmlSchemaSimpleType) {
                logger.debug("Processing base simple type {} for simple content extension", type.getName());
                handleSimpleType((XmlSchemaSimpleType) type, dom4jEl);
            } else if (type instanceof XmlSchemaComplexType) {
                logger.debug("Processing base complex {} type for simple content extension", type.getName());
                handleComplexType((XmlSchemaComplexType) type, dom4jEl);
            }
        }

    }

    private void processAttributeCollection(Element dom4jEl,
            List<XmlSchemaAttributeOrGroupRef> attributeOrGroupRefList) {
        if (attributeOrGroupRefList != null) {

            for (XmlSchemaAttributeOrGroupRef attributeOrGroupRef : attributeOrGroupRefList) {
                if (attributeOrGroupRef instanceof XmlSchemaAttribute) {
                    XmlSchemaAttribute attr = (XmlSchemaAttribute) attributeOrGroupRef;
                    logger.debug("Handling attribute {}", attr.getName());
                    handleAttribute(attr, dom4jEl);
                } else {
                    XmlSchemaAttributeGroupRef attrGrpRef = (XmlSchemaAttributeGroupRef) attributeOrGroupRef;
                    logger.debug("Handling attribute group reference {}", attrGrpRef.getTargetQName());
                    handleAttributeGroupRef(attrGrpRef, dom4jEl);
                }
            }
        }
    }

    /**
     * Handle the attribute ref present within the complex type
     *
     * @param attrGroupRef
     * @param dom4jEl
     */
    private void handleAttributeGroupRef(XmlSchemaAttributeGroupRef attrGroupRef, Element dom4jEl) {

        XmlSchemaRef<XmlSchemaAttributeGroup> ref = attrGroupRef.getRef();
        if (ref != null) {
            logger.debug("Processing attribute group ref: {}", ref.getTargetQName());

            XmlSchemaAttributeGroup attrGrp = ref.getTarget();
            if (attrGrp != null) {
                List<XmlSchemaAttributeGroupMember> attributeGroupMembers = attrGrp.getAttributes();

                for (XmlSchemaAttributeGroupMember attributeGroupMember : attributeGroupMembers) {
                    if (attributeGroupMember instanceof XmlSchemaAttribute) {
                        XmlSchemaAttribute attr = (XmlSchemaAttribute) attributeGroupMember;
                        logger.debug("Processing attribute[{}] within attribute group", attr.getName());
                        handleAttribute(attr, dom4jEl);
                    }
                }
            }
        }

    }

    /**
     * Handle the attribute for the schema type
     *
     * @param attribute
     * @param dom4jEl
     */
    private void handleAttribute(XmlSchemaAttribute attribute, Element dom4jEl) {
        Attribute dom4jAttr = null;
        if (attribute != null) {
            if (attribute.getRef().getTarget() != null) {
                dom4jAttr = handleAttributeRef(attribute, dom4jEl);
            } else {
                dom4jAttr = handleLocalAttribute(attribute, dom4jEl);
            }

            //add this attr to this element
            if (dom4jAttr != null) {
                dom4jEl.add(dom4jAttr);
            }
        }
    }

    private Attribute handleLocalAttribute(XmlSchemaAttribute attribute, Element dom4jEl) {
        Attribute dom4jAttr = null;

        XmlSchemaUse use = attribute.getUse();
        if (use != null) {
            String name = attribute.getName();

            if (use == XmlSchemaUse.PROHIBITED) {
                logger.debug("Attribute {}'s 'use' attribute value is 'prohibited'", name);
                return null;

            } else if (use == XmlSchemaUse.REQUIRED || ((use == XmlSchemaUse.OPTIONAL || use == XmlSchemaUse.NONE)
                    && options.isGenOptionalAttributes())) {

                //if form value is NONE, check the schema's attributeFormDefault
                //if that is also NONE, default to 'unqualified'
                XmlSchemaForm form = attribute.getForm();
                logger.debug("Handling local attribute {} ; form = {}", new Object[] { name, form });

                if (form == XmlSchemaForm.NONE) {
                    //check the schema attributeFormDefault value
                    XmlSchema currentSchema = (!schemaStack.isEmpty() ? schemaStack.peek() : null);
                    if (currentSchema != null) {
                        form = currentSchema.getAttributeFormDefault();
                        logger.debug("Default attribute form to '{}'", form);
                    }
                }

                org.dom4j.QName qname = null;
                if (form == XmlSchemaForm.QUALIFIED) {
                    qname = getDom4jQNameForAttribute(attribute);
                } else {
                    qname = org.dom4j.QName.get(name);
                }

                String attrVal = getAttributeValue(attribute);
                dom4jAttr = factory.createAttribute(dom4jEl, qname, attrVal);
            }

        }

        return dom4jAttr;
    }

    /**
     * Handle a reference inside a complex type to a globally declared attribute
     *
     * @param attribute the attribute declaration with reference to another attr
     * @param dom4jEl
     * @return
     */
    private Attribute handleAttributeRef(XmlSchemaAttribute attribute, Element dom4jEl) {
        Attribute dom4jAttr = null;
        XmlSchemaUse use = attribute.getUse();
        String name = attribute.getName();
        if (use != null) {

            if (use == XmlSchemaUse.PROHIBITED) {
                logger.debug("Attribute {}'s 'use' attribute value is 'prohibited'", name);
                dom4jAttr = null;

            } else if (use == XmlSchemaUse.REQUIRED || ((use == XmlSchemaUse.OPTIONAL || use == XmlSchemaUse.NONE)
                    && options.isGenOptionalAttributes())) {

                XmlSchemaRef<XmlSchemaAttribute> ref = attribute.getRef();

                if (ref.getTarget() != null) {
                    logger.trace("Processing attribute reference");

                    XmlSchema refSchema = getSchemaByTargetNamespace(ref.getTargetQName().getNamespaceURI());
                    boolean isPushed = false;
                    if (refSchema != null) {
                        logger.trace("---> Pushed schema with tns: {} into stack", refSchema.getTargetNamespace());
                        schemaStack.push(refSchema);
                        isPushed = true;
                    }

                    logger.debug("Handling attribute reference {} with use value  {}", name, use);

                    XmlSchemaAttribute refAttr = ref.getTarget();

                    org.dom4j.QName qname = getDom4jQNameForAttribute(refAttr);
                    String attrVal = getAttributeValue(refAttr);
                    dom4jAttr = factory.createAttribute(dom4jEl, qname, attrVal);

                    if (isPushed) {
                        XmlSchema sc = schemaStack.pop();
                        logger.trace("<--- Popped schema with tns\\: {} from stack", sc.getTargetNamespace());
                    }

                }
            }

        }
        return dom4jAttr;
    }

    /**
     * Generate an appropriate value for the given xmlschema attribute based on either
     * its default / fixed attribute. If neither are present and the xml gen options indicates
     * generation of optional attribute (#DefaultValues.DEFAULT), then create a value based
     * on the attribute base type
     *
     * @param attribute
     * @return
     */
    private String getAttributeValue(XmlSchemaAttribute attribute) {
        String defVal = attribute.getDefaultValue();
        String fixedVal = attribute.getFixedValue();
        String name = attribute.getName();
        String attrVal = "";
        //if attr has a fixed value, set it
        if (StringUtils.isNotEmpty(fixedVal)) {
            logger.debug("using fixed value {} for attr: {}", fixedVal, name);
            attrVal = fixedVal;
        } else {
            //if there is already a default value
            if (StringUtils.isNotEmpty(defVal)) {
                logger.debug("using default value {} for attr: {}", defVal, name);
                attrVal = defVal;
            } else {
                //generate a default value if needed
                if (options.getDefVals() == DefaultValues.DEFAULT) {
                    attrVal = SampleValueProvider.get(attribute.getSchemaTypeName());
                    logger.debug("generating new value {}  for attr {}:", attrVal, name);
                }
            }
        }
        return attrVal;
    }

    /**
     * Create a Dom4j QName value using the given XMLSchema attribute instance data
     * The attribute's form value is qualified or is a globally-declared attribute
     *
     * @param attribute local attribute with form = qualified / global attribute
     * @return
     */

    private org.dom4j.QName getDom4jQNameForAttribute(XmlSchemaAttribute attribute) {
        QName attrQName = attribute.getQName();
        String name = attribute.getName();
        String nsUri = attrQName.getNamespaceURI();

        XmlSchema currentSchema = (!schemaStack.isEmpty() ? schemaStack.peek() : null);

        if (StringUtils.isEmpty(nsUri)) {
            nsUri = (currentSchema != null ? currentSchema.getTargetNamespace() : "");
            logger.debug("Attribute ns was empty; Setting it to current schema tns [{}]", nsUri);
        }

        org.dom4j.QName dom4jQName = null;
        if (StringUtils.isEmpty(nsUri)) {
            dom4jQName = org.dom4j.QName.get(name);
        } else {

            String prefix = nsMap.getPrefix(nsUri);
            logger.debug("Got prefix [{}] for ns uri: [{}]", prefix, nsUri);

            if (StringUtils.isEmpty(prefix)) {
                prefix = DEFAULT_PREFIX + prefixCounter++;
                nsMap.put(prefix, nsUri);
                logger.debug("Generated prefix {} for ns uri: {}", prefix, nsUri);
            }

            logger.trace("Qualifying attribute with prefix [{}] and ns [{}]", prefix, nsUri);
            dom4jQName = org.dom4j.QName.get(name, prefix, nsUri);
        }

        return dom4jQName;
    }

    /**
     * Handle the simple type
     *
     * @param simpleType
     * @param dom4jEl
     */
    private void handleSimpleType(XmlSchemaSimpleType simpleType, Element dom4jEl) {

        /* if (content != null) {
        logger.debug(I18nHelper.str("simple.type.has.content"));
            
        if (content instanceof XmlSchemaSimpleTypeRestriction) {
            baseTypeName = ((XmlSchemaSimpleTypeRestriction) content).getBaseTypeName();
            //todo: handle base type which is itself another declared simple type
        }
            
         } else {
        logger.trace(I18nHelper.str("simple.type.has.no.content.must.be.a.base.schema.type"));
        baseTypeName = new QName(Constants.URI_2001_SCHEMA_XSD, simpleType.getName());
         }
        */

        XmlSchemaSimpleTypeContent content = simpleType.getContent();
        QName baseTypeName = null;

        QName name = simpleType.getQName();
        if (name != null && name.getNamespaceURI().equals(Constants.URI_2001_SCHEMA_XSD)) {
            baseTypeName = name;
        } else if (content != null) {
            logger.debug("simple type has content");

            if (content instanceof XmlSchemaSimpleTypeRestriction) {
                baseTypeName = ((XmlSchemaSimpleTypeRestriction) content).getBaseTypeName();
                //todo: handle base type which is itself another declared simple type
            }

        }

        if (options.getDefVals().equals(DefaultValues.DEFAULT)) {
            String val = SampleValueProvider.get(baseTypeName);
            if (StringUtils.isNotEmpty(val)) {
                dom4jEl.setText(val);
                logger.debug("Adding sample value '{}' for simple type base {}", val, baseTypeName);
            } else {
                logger.warn("Could not get sample value for base type name {}", baseTypeName);
            }
        }

    }

    public XmlSchemaCollection getSchemaColl() {
        return schemaColl;
    }

    public void setSchemaColl(XmlSchemaCollection schemaColl) {
        this.schemaColl = schemaColl;
    }

    /////////////////////////////////////// Getters & Setters ///////////////////////////////////////

    public XmlGenOptions getOptions() {
        return options;
    }

    public void setOptions(XmlGenOptions defOpts) {
        this.options = defOpts;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////

}