/**
 * 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.cxf.javascript.types;

import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import javax.xml.namespace.QName;

import org.apache.cxf.common.i18n.Message;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.common.xmlschema.SchemaCollection;
import org.apache.cxf.common.xmlschema.XmlSchemaUtils;
import org.apache.cxf.javascript.AttributeInfo;
import org.apache.cxf.javascript.ItemInfo;
import org.apache.cxf.javascript.JavascriptUtils;
import org.apache.cxf.javascript.NameManager;
import org.apache.cxf.javascript.NamespacePrefixAccumulator;
import org.apache.cxf.javascript.ParticleInfo;
import org.apache.cxf.javascript.UnsupportedConstruct;
import org.apache.ws.commons.schema.XmlSchema;
import org.apache.ws.commons.schema.XmlSchemaAnnotated;
import org.apache.ws.commons.schema.XmlSchemaAny;
import org.apache.ws.commons.schema.XmlSchemaAttributeOrGroupRef;
import org.apache.ws.commons.schema.XmlSchemaCollection;
import org.apache.ws.commons.schema.XmlSchemaComplexType;
import org.apache.ws.commons.schema.XmlSchemaElement;
import org.apache.ws.commons.schema.XmlSchemaObject;
import org.apache.ws.commons.schema.XmlSchemaSimpleType;
import org.apache.ws.commons.schema.XmlSchemaType;
import org.apache.ws.commons.schema.constants.Constants;

/**
 * Generate Javascript for a schema, and provide information needed for the service builder. As of this pass,
 * there is no support for non-sequence types or for attribute mappings.
 */
public class SchemaJavascriptBuilder {

    private static final Logger LOG = LogUtils.getL7dLogger(SchemaJavascriptBuilder.class);
    private static int anyPrefixCounter;
    private SchemaCollection xmlSchemaCollection;
    private NameManager nameManager;
    private NamespacePrefixAccumulator prefixAccumulator;
    // private SchemaInfo schemaInfo;

    // In general, I (bimargulies) hate fields that are temporary communications
    // between members of a class. However, given the style restrictions on the
    // number of parameters, it's the least of the evils.
    private StringBuilder code;
    private StringBuilder accessors;
    private JavascriptUtils utils;
    private XmlSchema xmlSchema;

    public SchemaJavascriptBuilder(SchemaCollection schemaCollection,
                                   NamespacePrefixAccumulator prefixAccumulator, NameManager nameManager) {
        this.xmlSchemaCollection = schemaCollection;
        this.nameManager = nameManager;
        this.prefixAccumulator = prefixAccumulator;
    }

    public String generateCodeForSchemaCollection(XmlSchemaCollection collection) {
        StringBuilder accumulatedCode = new StringBuilder();
        for (XmlSchema schema : collection.getXmlSchemas()) {
            if (!Constants.URI_2001_SCHEMA_XSD.equals(schema.getTargetNamespace())) {
                accumulatedCode.append(generateCodeForSchema(schema));
            }
        }
        return accumulatedCode.toString();
    }

    public String generateCodeForSchema(XmlSchema schema) {
        xmlSchema = schema;
        code = new StringBuilder();
        code.append("//\n");
        code.append("// Definitions for schema: ").append(schema.getTargetNamespace());
        if (schema.getSourceURI() != null) {
            code.append("\n//  ").append(schema.getSourceURI());
        }
        code.append("\n//\n");

        Map<QName, XmlSchemaType> schemaTypes = schema.getSchemaTypes();
        for (Map.Entry<QName, XmlSchemaType> e : schemaTypes.entrySet()) {
            XmlSchemaType type = e.getValue();
            if (type instanceof XmlSchemaComplexType) {
                try {
                    XmlSchemaComplexType complexType = (XmlSchemaComplexType)type;
                    if (!JavascriptUtils.notVeryComplexType(complexType) && complexType.getName() != null) {
                        complexTypeConstructorAndAccessors(complexType.getQName(), complexType);
                        complexTypeSerializerFunction(complexType.getQName(), complexType);
                        domDeserializerFunction(complexType.getQName(), complexType);
                    }
                } catch (UnsupportedConstruct usc) {
                    LOG.warning(usc.toString());
                    continue; // it could be empty, but the style checker
                    // would complain.
                }
            } else if (type instanceof XmlSchemaSimpleType) {
                XmlSchemaSimpleType simpleType = (XmlSchemaSimpleType)type;
                if (XmlSchemaUtils.isEumeration(simpleType)) {
                    List<String> values = XmlSchemaUtils.enumeratorValues(simpleType);
                    code.append("//\n");
                    code.append("// Simple type (enumeration) ").append(simpleType.getQName()).append('\n');
                    code.append("//\n");
                    for (String value : values) {
                        code.append("// - ").append(value).append('\n');
                    }
                }
            }
        }

        for (Map.Entry<QName, XmlSchemaElement> e : schema.getElements().entrySet()) {
            XmlSchemaElement element = e.getValue();
            try {
                if (element.getSchemaTypeName() == null && element.getSchemaType() == null) {
                    Message message = new Message("ELEMENT_MISSING_TYPE", LOG, element.getQName(),
                                                  element.getSchemaTypeName(), schema.getTargetNamespace());
                    LOG.warning(message.toString());
                    continue;
                }
                XmlSchemaType type;
                if (element.getSchemaType() != null) {
                    type = element.getSchemaType();
                } else {
                    type = schema.getTypeByName(element.getSchemaTypeName());
                }
                if (!(type instanceof XmlSchemaComplexType)) {
                    // we never make classes for simple type.
                    continue;
                }

                XmlSchemaComplexType complexType = (XmlSchemaComplexType)type;
                // for named types we don't bother to generate for the
                // element.
                if (!JavascriptUtils.notVeryComplexType(complexType) && complexType.getName() == null) {
                    complexTypeConstructorAndAccessors(element.getQName(), complexType);
                    complexTypeSerializerFunction(element.getQName(), complexType);
                    domDeserializerFunction(element.getQName(), complexType);
                }
            } catch (UnsupportedConstruct usc) {
                LOG.warning(usc.getMessage());
                continue; // it could be empty, but the style checker
                // would complain.
            }
        }

        String returnValue = code.toString();
        LOG.finer(returnValue);
        return returnValue;
    }

    // In general, I (bimargulies) hate fields that are temporary communications
    // between members of a class. However, given the style restrictions on the
    // number
    // of parameters, it's the least of the evils.

    public void complexTypeConstructorAndAccessors(QName name, XmlSchemaComplexType type) {
        accessors = new StringBuilder();
        utils = new JavascriptUtils(code);
        List<XmlSchemaObject> items = JavascriptUtils.getContentElements(type, xmlSchemaCollection);
        List<XmlSchemaAnnotated> attrs = XmlSchemaUtils.getContentAttributes(type, xmlSchemaCollection);

        final String elementPrefix = "this._";

        String typeObjectName = nameManager.getJavascriptName(name);
        code.append("//\n");
        code.append("// Constructor for XML Schema item ").append(name.toString()).append('\n');
        code.append("//\n");
        code.append("function ").append(typeObjectName).append(" () {\n");
        // to assist in debugging we put a type property into every object.
        utils.appendLine("this.typeMarker = '" + typeObjectName + "';");
        for (XmlSchemaObject thing : items) {
            ParticleInfo itemInfo = ParticleInfo.forLocalItem(thing, xmlSchema, xmlSchemaCollection,
                                                              prefixAccumulator, type.getQName());
            constructItem(type, elementPrefix, typeObjectName, itemInfo);
        }

        for (XmlSchemaAnnotated thing : attrs) {
            AttributeInfo itemInfo = AttributeInfo.forLocalItem(thing, xmlSchema, xmlSchemaCollection,
                                                                prefixAccumulator, type.getQName());
            constructOneItem(type, elementPrefix, typeObjectName, itemInfo);
        }

        code.append("}\n\n");
        code.append(accessors.toString());
    }

    private void constructItem(XmlSchemaComplexType type, final String elementPrefix,
                               String typeObjectName, ParticleInfo itemInfo) {
        if (!itemInfo.isGroup()) {
            constructOneItem(type, elementPrefix, typeObjectName, itemInfo);
            return;
        }

        for (ParticleInfo childInfo : itemInfo.getChildren()) {
            constructItem(type, elementPrefix, typeObjectName, childInfo);
        }
    }

    private void constructOneItem(XmlSchemaComplexType type, final String elementPrefix,
                                  String typeObjectName, ItemInfo itemInfo) {

        String accessorSuffix = StringUtils.capitalize(itemInfo.getJavascriptName());

        String accessorName = typeObjectName + "_get" + accessorSuffix;
        String getFunctionProperty = typeObjectName + ".prototype.get" + accessorSuffix;
        String setFunctionProperty = typeObjectName + ".prototype.set" + accessorSuffix;
        accessors.append("//\n");
        accessors.append("// accessor is ").append(getFunctionProperty).append('\n');
        accessors.append("// element get for ").append(itemInfo.getJavascriptName()).append('\n');
        if (itemInfo.isAny()) {
            accessors.append("// - xs:any\n");
        } else {
            if (itemInfo.getType() != null) {
                accessors.append("// - element type is ").append(itemInfo.getType().getQName()).append('\n');
            }
        }

        if (itemInfo.isOptional()) {
            accessors.append("// - optional element\n");
        } else {
            accessors.append("// - required element\n");

        }

        if (itemInfo.isArray()) {
            accessors.append("// - array\n");

        }

        if (itemInfo.isNillable()) {
            accessors.append("// - nillable\n");
        }

        accessors.append("//\n");
        accessors.append("// element set for ").append(itemInfo.getJavascriptName()).append('\n');
        accessors.append("// setter function is is ").append(setFunctionProperty).append('\n');
        accessors.append("//\n");
        accessors.append("function ").append(accessorName).append("() { return this._")
                        .append(itemInfo.getJavascriptName()).append(";}\n\n");
        accessors.append(getFunctionProperty).append(" = ").append(accessorName).append(";\n\n");
        accessorName = typeObjectName + "_set" + accessorSuffix;
        accessors.append("function ").append(accessorName).append("(value) { this._")
                        .append(itemInfo.getJavascriptName()).append(" = value;}\n\n");
        accessors.append(setFunctionProperty).append(" = ").append(accessorName).append(";\n");

        if (itemInfo.isOptional() || (itemInfo.isNillable() && !itemInfo.isArray())) {
            utils.appendLine("this._" + itemInfo.getJavascriptName() + " = null;");
        } else if (itemInfo.isArray()) {
            utils.appendLine("this._" + itemInfo.getJavascriptName() + " = [];");
        } else if (itemInfo.isAny() || itemInfo.getType() instanceof XmlSchemaComplexType) {
            // even for required complex elements, we leave them null.
            // otherwise, we could end up in a cycle or otherwise miserable. The
            // application code is responsible for this.
            utils.appendLine("this._" + itemInfo.getJavascriptName() + " = null;");
        } else {

            if (itemInfo.getDefaultValue() == null) {
                itemInfo.setDefaultValue(utils.getDefaultValueForSimpleType(itemInfo.getType()));
            }
            utils.appendLine("this._" + itemInfo.getJavascriptName() + " = " + itemInfo.getDefaultValue()
                             + ";");
        }
    }

    /**
     * Produce a serializer function for a type. These functions emit the surrounding element XML if the
     * caller supplies an XML element name. It's not quite as simple as that, though. The element name may
     * need namespace qualification, and this function will add more namespace prefixes as needed.
     *
     * @param type
     * @return
     */
    public void complexTypeSerializerFunction(QName name, XmlSchemaComplexType type) {

        StringBuilder bodyCode = new StringBuilder();
        JavascriptUtils bodyUtils = new JavascriptUtils(bodyCode);
        bodyUtils.setXmlStringAccumulator("xml");

        complexTypeSerializerBody(type, "this._", bodyUtils);

        utils = new JavascriptUtils(code);
        String functionName = nameManager.getJavascriptName(name) + "_" + "serialize";
        code.append("//\n");
        code.append("// Serialize ").append(name).append('\n');
        code.append("//\n");
        code.append("function ").append(functionName).append("(cxfjsutils, elementName, extraNamespaces) {\n");
        utils.startXmlStringAccumulator("xml");
        utils.startIf("elementName !== null");
        utils.appendString("<");
        utils.appendExpression("elementName");
        // now add any accumulated namespaces.
        String moreNamespaces = prefixAccumulator.getAttributes();
        if (moreNamespaces.length() > 0) {
            utils.appendString(" ");
            utils.appendString(moreNamespaces);
        }
        utils.startIf("extraNamespaces");
        utils.appendExpression("' ' + extraNamespaces");
        utils.endBlock();
        // attributes
        complexTypeSerializeAttributes(type, "this._");
        utils.appendString(">");
        utils.endBlock();
        code.append(bodyCode);
        utils.startIf("elementName !== null");
        utils.appendString("</");
        utils.appendExpression("elementName");
        utils.appendString(">");
        utils.endBlock();
        utils.appendLine("return xml;");
        code.append("}\n\n");
        code.append(nameManager.getJavascriptName(name)).append(".prototype.serialize = ")
            .append(functionName).append(";\n\n");
    }

    private void complexTypeSerializeAttributes(XmlSchemaComplexType type, String string) {
        @SuppressWarnings("unused")
        List<XmlSchemaAttributeOrGroupRef> attributes = type.getAttributes();
        // work in progress.
    }

    /**
     * Build the serialization code for a complex type. At the top level, this operates on single items, so it
     * does not pay attention to minOccurs and maxOccurs. However, as it works through the sequence, it
     * manages optional elements and arrays.
     *
     * @param type
     * @param elementPrefix
     * @param bodyUtils
     * @return
     */
    protected void complexTypeSerializerBody(XmlSchemaComplexType type, String elementPrefix,
                                             JavascriptUtils bodyUtils) {
        List<XmlSchemaObject> items = JavascriptUtils.getContentElements(type, xmlSchemaCollection);
        for (XmlSchemaObject sequenceItem : items) {
            ParticleInfo itemInfo = ParticleInfo.forLocalItem(sequenceItem, xmlSchema, xmlSchemaCollection,
                                                              prefixAccumulator, type.getQName());

            // If the item is 'any', it could be ANY of our top-level elements.
            if (itemInfo.isAny()) {
                serializeAny(itemInfo, bodyUtils);
            } else {
                bodyUtils.generateCodeToSerializeElement(itemInfo, "this._", xmlSchemaCollection);
            }
        }
    }

    private void serializeAny(ParticleInfo itemInfo, JavascriptUtils bodyUtils) {
        String prefix = "cxfjsany" + anyPrefixCounter;
        anyPrefixCounter++;
        bodyUtils.generateCodeToSerializeAny(itemInfo, prefix, xmlSchemaCollection);
    }

    /**
     * Generate a JavaScript function that takes an element for a complex type and walks through its children
     * using them to fill in the values for a JavaScript object.
     *
     * @param type schema type for the process
     * @return the string contents of the JavaScript.
     */
    public void domDeserializerFunction(QName name, XmlSchemaComplexType type) {
        utils = new JavascriptUtils(code);

        List<XmlSchemaObject> contentElements = JavascriptUtils.getContentElements(type, xmlSchemaCollection);
        String typeObjectName = nameManager.getJavascriptName(name);
        code.append("function ").append(typeObjectName).append("_deserialize (cxfjsutils, element) {\n");
        // create the object we are deserializing into.
        utils.appendLine("var newobject = new " + typeObjectName + "();");
        utils.appendLine("cxfjsutils.trace('element: ' + cxfjsutils.traceElementName(element));");
        utils.appendLine("var curElement = cxfjsutils.getFirstElementChild(element);");

        utils.appendLine("var item;");

        int nContentElements = contentElements.size();
        for (int i = 0; i < contentElements.size(); i++) {
            XmlSchemaObject contentElement = contentElements.get(i);
            utils.appendLine("cxfjsutils.trace('curElement: ' + cxfjsutils.traceElementName(curElement));");
            ParticleInfo itemInfo = ParticleInfo.forLocalItem(contentElement, xmlSchema, xmlSchemaCollection,
                                                              prefixAccumulator, type.getQName());
            if (itemInfo.isAny()) {
                ParticleInfo nextItem = null;
                if (i != nContentElements - 1) {
                    XmlSchemaObject nextThing = contentElements.get(i + 1);
                    nextItem = ParticleInfo.forLocalItem(nextThing, xmlSchema, xmlSchemaCollection,
                                                         prefixAccumulator, type.getQName());
                    // theoretically, you could have two anys with different
                    // namespaces.
                    if (nextItem.isAny()) {
                        unsupportedConstruct("MULTIPLE_ANY", type.getQName());
                    }
                }
                deserializeAny(type, itemInfo, nextItem);
            } else {
                deserializeElement(type, itemInfo);
            }
        }
        utils.appendLine("return newobject;");
        code.append("}\n\n");
    }

    private String buildNamespaceList(String anyNamespaceSpec) {
        StringBuilder nslist = new StringBuilder();
        String[] namespaces = anyNamespaceSpec.split("\\s");
        nslist.append("[ ");
        for (int x = 0; x < namespaces.length; x++) {
            String ns = namespaces[x];
            nslist.append('\'');
            if ("##targetNamespace".equals(ns)) {
                nslist.append(xmlSchema.getTargetNamespace());
            } else if ("##local".equals(ns)) {
                // nothing, empty string
            } else {
                nslist.append(ns);
            }
            nslist.append('\'');
            if (x < namespaces.length - 1) {
                nslist.append(',');
            }
        }
        nslist.append(']');
        return nslist.toString();
    }

    private void deserializeAny(XmlSchemaComplexType type, ParticleInfo itemInfo, ParticleInfo nextItem) {
        XmlSchemaAny any = (XmlSchemaAny)itemInfo.getParticle();

        boolean array = XmlSchemaUtils.isParticleArray(any);
        boolean optional = XmlSchemaUtils.isParticleOptional(any);

        if (array) {
            utils.appendLine("var anyObject = [];");
        } else {
            utils.appendLine("var anyObject = null;");

        }

        String anyNamespaceSpec = any.getNamespace();
        // we aren't dealing with any-after-any.
        XmlSchemaElement nextElement = null;
        if (nextItem != null) {
            nextElement = (XmlSchemaElement)nextItem.getParticle();
        }
        String matchType;
        String namespaceList = "[]";

        if (anyNamespaceSpec == null || "##any".equals(anyNamespaceSpec) || "".equals(anyNamespaceSpec)) {
            matchType = "org_apache_cxf_any_ns_matcher.ANY";
        } else if ("##other".equals(anyNamespaceSpec)) {
            matchType = "org_apache_cxf_any_ns_matcher.OTHER";
        } else if ("##local".equals(anyNamespaceSpec)) {
            matchType = "org_apache_cxf_any_ns_matcher.LOCAL";
        } else {
            matchType = "org_apache_cxf_any_ns_matcher.LISTED";
            namespaceList = buildNamespaceList(anyNamespaceSpec);
        }

        String nextLocalPartConstant = "null";
        if (nextElement != null) {
            nextLocalPartConstant = "'" + nextElement.getQName().getLocalPart() + "'";
        }

        utils.appendLine("var matcher = new org_apache_cxf_any_ns_matcher(" + matchType + ", '"
                         + xmlSchema.getTargetNamespace() + "'" + ", " + namespaceList + ", "
                         + nextLocalPartConstant + ");");

        if (array) {
            utils.appendLine("var anyNeeded = " + any.getMinOccurs() + ";");
            utils.appendLine("var anyAllowed = " + any.getMaxOccurs() + ";");
        } else if (optional) {
            utils.appendLine("var anyNeeded = 0;");
            utils.appendLine("var anyAllowed = 1;");
        } else {
            utils.appendLine("var anyNeeded = 1;");
            utils.appendLine("var anyAllowed = 1;");
        }

        utils.startWhile("anyNeeded > 0 || anyAllowed > 0");

        utils.appendLine("var anyURI;");
        utils.appendLine("var anyLocalPart;");
        utils.appendLine("var anyMatched = false;");
        utils.startIf("curElement");

        utils.appendLine("anyURI = cxfjsutils.getElementNamespaceURI(curElement);");
        utils.appendLine("anyLocalPart = cxfjsutils.getNodeLocalName(curElement);");
        utils.appendLine("var anyQName = '{' + anyURI + '}' + anyLocalPart;");
        utils.appendLine("cxfjsutils.trace('any match: ' + anyQName);");
        utils.appendLine("anyMatched = matcher.match(anyURI, anyLocalPart)");
        utils.appendLine("cxfjsutils.trace(' --> ' + anyMatched);");

        utils.endBlock(); // curElement != null

        utils.startIf("anyMatched"); // if match
        utils.appendLine("anyDeserializer = "
                         + "cxfjsutils.interfaceObject.globalElementDeserializers[anyQName];");
        utils.appendLine("cxfjsutils.trace(' deserializer: ' + anyDeserializer);");
        utils.startIf("anyDeserializer"); // if complex/serializer function
        utils.appendLine("var anyValue = anyDeserializer(cxfjsutils, curElement);");
        utils.appendElse(); // else complex/serializer function
        // TODO: for simple types we really need a dictionary of the simple type qnames.
        utils.appendLine("var anyValue = curElement.nodeValue;");
        utils.endBlock(); // complex/serializer function

        if (array) {
            utils.appendLine("anyObject.push(anyValue);");
        } else {
            utils.appendLine("anyObject = anyValue;");
        }

        utils.appendLine("anyNeeded--;");
        utils.appendLine("anyAllowed--;");
        // if we consumed the element, we advance.
        utils.appendLine("curElement = cxfjsutils.getNextElementSibling(curElement);");
        utils.appendElse(); // match
        // non-matching case
        utils.startIf("anyNeeded > 0");
        utils.appendLine("throw 'not enough ws:any elements';");
        utils.appendElse(); // else non-match
        utils.appendLine("break;");
        utils.endBlock(); // non-match+required
        utils.endBlock(); // match/non-match.
        utils.endBlock(); // while

        utils.appendLine("var anyHolder = new org_apache_cxf_any_holder(anyURI, anyLocalPart, anyValue);");
        utils.appendLine("newobject.setAny(anyHolder);");
    }

    private void deserializeElement(XmlSchemaComplexType type, ParticleInfo itemInfo) {
        if (itemInfo.isGroup()) {
            for (ParticleInfo childElement : itemInfo.getChildren()) {
                deserializeElement(type, childElement);
            }
            return;
        }

        XmlSchemaType itemType = itemInfo.getType();
        boolean simple = itemType instanceof XmlSchemaSimpleType
                         || JavascriptUtils.notVeryComplexType(itemType);
        boolean mtomCandidate = JavascriptUtils.mtomCandidateType(itemType);
        String accessorName = "set" + StringUtils.capitalize(itemInfo.getJavascriptName());
        utils.appendLine("cxfjsutils.trace('processing " + itemInfo.getJavascriptName() + "');");
        XmlSchemaElement element = (XmlSchemaElement)itemInfo.getParticle();
        QName elementQName = XmlSchemaUtils.getElementQualifiedName(element, xmlSchema);
        String elementNamespaceURI = elementQName.getNamespaceURI();
        boolean elementNoNamespace = "".equals(elementNamespaceURI);
        XmlSchema elementSchema = null;
        if (!elementNoNamespace) {
            elementSchema = xmlSchemaCollection.getSchemaByTargetNamespace(elementNamespaceURI);
        }
        boolean qualified = !elementNoNamespace
                            && XmlSchemaUtils.isElementQualified(element, itemInfo.isGlobal(), xmlSchema,
                                                                 elementSchema);

        if (!qualified) {
            elementNamespaceURI = "";
        }

        String localName = elementQName.getLocalPart();
        String valueTarget = "item";

        if (itemInfo.isOptional() || itemInfo.isArray()) {
            utils.startIf("curElement != null && cxfjsutils.isNodeNamedNS(curElement, '"
                          + elementNamespaceURI + "', '" + localName + "')");
            if (itemInfo.isArray()) {
                utils.appendLine("item = [];");
                utils.startDo();
                valueTarget = "arrayItem";
                utils.appendLine("var arrayItem = null;");
            }
        }

        utils.appendLine("var value = null;");
        utils.startIf("!cxfjsutils.isElementNil(curElement)");
        if (itemInfo.isAnyType()) {
            // use our utility
            utils.appendLine(valueTarget + " = org_apache_cxf_deserialize_anyType(cxfjsutils, curElement);");
        } else if (simple) {
            if (mtomCandidate) {
                utils.appendLine(valueTarget + " = cxfjsutils.deserializeBase64orMom(curElement);");
            } else {
                utils.appendLine("value = cxfjsutils.getNodeText(curElement);");
                utils.appendLine(valueTarget + " = " + utils.javascriptParseExpression(itemType, "value")
                                 + ";");
            }
        } else {
            XmlSchemaComplexType complexType = (XmlSchemaComplexType)itemType;
            QName baseQName = complexType.getQName();
            if (baseQName == null) {
                baseQName = element.getQName();
            }

            String elTypeJsName = nameManager.getJavascriptName(baseQName);
            utils.appendLine(valueTarget + " = " + elTypeJsName + "_deserialize(cxfjsutils, curElement);");
        }

        utils.endBlock(); // the if for the nil.
        if (itemInfo.isArray()) {
            utils.appendLine("item.push(arrayItem);");
            utils.appendLine("curElement = cxfjsutils.getNextElementSibling(curElement);");
            utils.endBlock();
            utils.appendLine("  while(curElement != null && cxfjsutils.isNodeNamedNS(curElement, '"
                             + elementNamespaceURI + "', '" + localName + "'));");
        }
        utils.appendLine("newobject." + accessorName + "(item);");
        utils.appendLine("var item = null;");
        if (!itemInfo.isArray()) {
            utils.startIf("curElement != null");
            utils.appendLine("curElement = cxfjsutils.getNextElementSibling(curElement);");
            utils.endBlock();
        }
        if (itemInfo.isOptional() || itemInfo.isArray()) {
            utils.endBlock();
        }
    }

    private void unsupportedConstruct(String messageKey, Object... args) {
        Message message = new Message(messageKey, LOG, args);
        throw new UnsupportedConstruct(message);
    }
}
