/*******************************************************************************
 * Copyright (c) 1998, 2008 Oracle. All rights reserved.
 * This program and the accompanying materials are made available under the 
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     Oracle - initial API and implementation from Oracle TopLink
 ******************************************************************************/
package org.eclipse.persistence.internal.oxm;

import java.util.List;
import javax.xml.namespace.QName;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.descriptors.Namespace;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.oxm.record.ObjectMarshalContext;
import org.eclipse.persistence.internal.oxm.record.MarshalContext;
import org.eclipse.persistence.internal.oxm.record.deferred.AnyMappingContentHandler;
import org.eclipse.persistence.internal.queries.ContainerPolicy;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.logging.AbstractSessionLog;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.oxm.NamespaceResolver;
import org.eclipse.persistence.oxm.XMLConstants;
import org.eclipse.persistence.oxm.XMLContext;
import org.eclipse.persistence.oxm.XMLDescriptor;
import org.eclipse.persistence.oxm.XMLMarshaller;
import org.eclipse.persistence.oxm.XMLRoot;
import org.eclipse.persistence.oxm.mappings.UnmarshalKeepAsElementPolicy;
import org.eclipse.persistence.oxm.mappings.XMLAnyCollectionMapping;
import org.eclipse.persistence.oxm.record.MarshalRecord;
import org.eclipse.persistence.oxm.record.UnmarshalRecord;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

/**
 * INTERNAL:
 * <p><b>Purpose</b>: This is how the XML Any Collection Mapping is handled when
 * used with the TreeObjectBuilder.</p>
 */
public class XMLAnyCollectionMappingNodeValue extends XMLRelationshipMappingNodeValue implements ContainerValue {
    private XMLAnyCollectionMapping xmlAnyCollectionMapping;
    private XMLDescriptor workingDescriptor;

    public XMLAnyCollectionMappingNodeValue(XMLAnyCollectionMapping xmlAnyCollectionMapping) {
        super();
        this.xmlAnyCollectionMapping = xmlAnyCollectionMapping;
    }

    public boolean isOwningNode(XPathFragment xPathFragment) {
        return null == xPathFragment;
    }

    public boolean marshal(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, AbstractSession session, NamespaceResolver namespaceResolver) {
        if (xmlAnyCollectionMapping.isReadOnly()) {
            return false;
        }
        ContainerPolicy cp = xmlAnyCollectionMapping.getContainerPolicy();
        Object collection = xmlAnyCollectionMapping.getAttributeAccessor().getAttributeValueFromObject(object);
        if (null == collection) {
            return false;
        }
        Object iterator = cp.iteratorFor(collection);
        if (cp.hasNext(iterator)) {
            XPathFragment groupingFragment = marshalRecord.openStartGroupingElements(namespaceResolver);
            marshalRecord.closeStartGroupingElements(groupingFragment);
        } else {
            return false;
        }
        Object objectValue;
        while (cp.hasNext(iterator)) {
            objectValue = cp.next(iterator, session);
            if(xmlAnyCollectionMapping.getConverter() != null) {
            	objectValue = xmlAnyCollectionMapping.getConverter().convertObjectValueToDataValue(objectValue, session, marshalRecord.getMarshaller());
            }
            marshalSingleValue(xPathFragment, marshalRecord, object, objectValue, session, namespaceResolver, ObjectMarshalContext.getInstance());
        }

        return true;
    }

    public boolean startElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord, Attributes atts) {
        try {
            // Mixed Content
            Object collection = unmarshalRecord.getContainerInstance(this);
            startElementProcessText(unmarshalRecord, collection);
            XMLDescriptor xmlDescriptor = null;

            XMLContext xmlContext = unmarshalRecord.getUnmarshaller().getXMLContext();

            if (xmlAnyCollectionMapping.usesXMLRoot()) {
                String schemaType = atts.getValue(XMLConstants.SCHEMA_INSTANCE_URL, XMLConstants.SCHEMA_TYPE_ATTRIBUTE);
                XPathFragment frag = new XPathFragment();
                if ((null != schemaType) && (!schemaType.equals(""))) {
                    frag.setXPath(schemaType);

                    if (frag.hasNamespace()) {
                        String prefix = frag.getPrefix();
                        String url = unmarshalRecord.resolveNamespacePrefix(prefix);
                        frag.setNamespaceURI(url);

                        QName qname = new QName(url, frag.getLocalName());
                        unmarshalRecord.setTypeQName(qname);
                    }

                    xmlDescriptor = xmlContext.getDescriptorByGlobalType(frag);
                }
            }
            if (xmlDescriptor == null) {
                QName qname = new QName(xPathFragment.getNamespaceURI(), xPathFragment.getLocalName());
                xmlDescriptor = xmlContext.getDescriptor(qname);
                // Check if descriptor is for a wrapper, if it is null it out and let continue
                if (xmlDescriptor != null && xmlDescriptor.isWrapper()) {
                	xmlDescriptor = null;
                }
            }
            workingDescriptor = xmlDescriptor;
            UnmarshalKeepAsElementPolicy policy = xmlAnyCollectionMapping.getKeepAsElementPolicy();
            if (((xmlDescriptor == null) && (policy == UnmarshalKeepAsElementPolicy.KEEP_UNKNOWN_AS_ELEMENT)) || (policy == UnmarshalKeepAsElementPolicy.KEEP_ALL_AS_ELEMENT)) {
                //setup handler stuff
                SAXFragmentBuilder builder = unmarshalRecord.getFragmentBuilder();
                builder.setOwningRecord(unmarshalRecord);
                try {
                    String namespaceURI = "";
                    if (xPathFragment.getNamespaceURI() != null) {
                        namespaceURI = xPathFragment.getNamespaceURI();
                    }
                    String qName = xPathFragment.getLocalName();
                    if (xPathFragment.getPrefix() != null) {
                        qName = xPathFragment.getPrefix() + ":" + qName;
                    }

                    builder.startElement(namespaceURI, xPathFragment.getLocalName(), qName, atts);
                    unmarshalRecord.getXMLReader().setContentHandler(builder);
                } catch (SAXException ex) {
                }
            } else if (xmlDescriptor != null) {
                processChild(xPathFragment, unmarshalRecord, atts, xmlDescriptor);
            } else {
                //need to give to special handler, let it find out what to do depending on if this is simple or complex content
                AnyMappingContentHandler handler = new AnyMappingContentHandler(unmarshalRecord, xmlAnyCollectionMapping.usesXMLRoot());
                String qnameString = xPathFragment.getLocalName();
                if (xPathFragment.getPrefix() != null) {
                    qnameString = xPathFragment.getPrefix() + ":" + qnameString;
                }
                handler.startElement(xPathFragment.getNamespaceURI(), xPathFragment.getLocalName(), qnameString, atts);
                unmarshalRecord.getXMLReader().setContentHandler(handler);
                return true;
            }
        } catch (SAXException e) {
            throw XMLMarshalException.unmarshalException(e);
        }
        return true;
    }

    public void endElement(XPathFragment xPathFragment, UnmarshalRecord unmarshalRecord) {
        Object collection = unmarshalRecord.getContainerInstance(this);
        if (null != unmarshalRecord.getChildRecord()) {
            // OBJECT VALUE
            if (!xmlAnyCollectionMapping.usesXMLRoot()) {
            	Object objectValue = unmarshalRecord.getChildRecord().getCurrentObject();
            	if(xmlAnyCollectionMapping.getConverter() != null) {
            		objectValue = xmlAnyCollectionMapping.getConverter().convertDataValueToObjectValue(objectValue, unmarshalRecord.getSession(), unmarshalRecord.getUnmarshaller());
            	}
                unmarshalRecord.addAttributeValue(this, objectValue);
            }
            if (xmlAnyCollectionMapping.usesXMLRoot()) {
                Object childObject = unmarshalRecord.getChildRecord().getCurrentObject();

                if (workingDescriptor != null) {
                    String prefix = xPathFragment.getPrefix();
                    if ((prefix == null) && (xPathFragment.getNamespaceURI() != null)) {
                        prefix = unmarshalRecord.resolveNamespaceUri(xPathFragment.getNamespaceURI());
                    }
                    childObject = workingDescriptor.wrapObjectInXMLRoot(childObject, xPathFragment.getNamespaceURI(), xPathFragment.getLocalName(), prefix, false);
                	if(xmlAnyCollectionMapping.getConverter() != null) {
                		childObject = xmlAnyCollectionMapping.getConverter().convertDataValueToObjectValue(childObject, unmarshalRecord.getSession(), unmarshalRecord.getUnmarshaller());
                	}
                    unmarshalRecord.addAttributeValue(this, childObject);
                }
            }
            unmarshalRecord.setChildRecord(null);
            workingDescriptor = null;
        } else {
            SAXFragmentBuilder builder = unmarshalRecord.getFragmentBuilder();

            UnmarshalKeepAsElementPolicy keepAsElementPolicy = xmlAnyCollectionMapping.getKeepAsElementPolicy();
            if ((((keepAsElementPolicy == UnmarshalKeepAsElementPolicy.KEEP_UNKNOWN_AS_ELEMENT) || (keepAsElementPolicy == UnmarshalKeepAsElementPolicy.KEEP_ALL_AS_ELEMENT))) && (builder.getNodes().size() != 0)) {
                //Grab the fragment and put it into the collection
            	Object node = builder.getNodes().pop();
            	if(xmlAnyCollectionMapping.getConverter() != null) {
            		node = xmlAnyCollectionMapping.getConverter().convertDataValueToObjectValue(node, unmarshalRecord.getSession(), unmarshalRecord.getUnmarshaller());
            	}
                unmarshalRecord.addAttributeValue(this, node);
            } else {
                //TEXT VALUE
                endElementProcessText(unmarshalRecord, collection, xPathFragment);
            }
        }
    }

    private void startElementProcessText(UnmarshalRecord unmarshalRecord, Object collection) {
        Object value = unmarshalRecord.getStringBuffer().toString().trim();
        unmarshalRecord.resetStringBuffer();
        if (!EMPTY_STRING.equals(value) && xmlAnyCollectionMapping.isMixedContent()) {
            unmarshalRecord.addAttributeValue(this, value);
        }
    }

    private void endElementProcessText(UnmarshalRecord unmarshalRecord, Object collection, XPathFragment xPathFragment) {
        Object value = unmarshalRecord.getStringBuffer().toString().trim();
    	if(xmlAnyCollectionMapping.getConverter() != null) {
    		value = xmlAnyCollectionMapping.getConverter().convertDataValueToObjectValue(value, unmarshalRecord.getSession(), unmarshalRecord.getUnmarshaller());
    	}
        unmarshalRecord.resetStringBuffer();
        if (!EMPTY_STRING.equals(value) && xmlAnyCollectionMapping.isMixedContent()) {
            QName qname = unmarshalRecord.getTypeQName();
            if (qname != null) {
                Class theClass = (Class) XMLConversionManager.getDefaultXMLTypes().get(qname);
                if (theClass != null) {
                    value = ((XMLConversionManager) unmarshalRecord.getSession().getDatasourcePlatform().getConversionManager()).convertObject(value, theClass, qname);
                }
            }

            if (!xmlAnyCollectionMapping.usesXMLRoot()) {
                unmarshalRecord.addAttributeValue(this, value);
            } else {
                XMLRoot xmlRoot = new XMLRoot();
                xmlRoot.setNamespaceURI(xPathFragment.getNamespaceURI());
                xmlRoot.setLocalName(xPathFragment.getLocalName());
                xmlRoot.setSchemaType(qname);
                xmlRoot.setObject(value);
                unmarshalRecord.addAttributeValue(this, xmlRoot);
            }
        }
    }

    public Object getContainerInstance() {
        return getContainerPolicy().containerInstance();
    }

    public void setContainerInstance(Object object, Object containerInstance) {
        xmlAnyCollectionMapping.setAttributeValueInObject(object, containerInstance);
    }

    public ContainerPolicy getContainerPolicy() {
        return xmlAnyCollectionMapping.getContainerPolicy();
    }

    public boolean isContainerValue() {
        return true;
    }

    private Namespace setupFragment(XMLRoot originalValue, XPathFragment xmlRootFragment, MarshalRecord marshalRecord) {
        Namespace generatedNamespace = null;
        String xpath = originalValue.getLocalName();
        if (originalValue.getNamespaceURI() != null) {
            xmlRootFragment.setNamespaceURI((originalValue).getNamespaceURI());
            String prefix = marshalRecord.getNamespaceResolver().resolveNamespaceURI((originalValue).getNamespaceURI());
            if ((prefix == null) || prefix.equals("")) {
                prefix = marshalRecord.getNamespaceResolver().generatePrefix();
                generatedNamespace = new Namespace(prefix, xmlRootFragment.getNamespaceURI());
            }
            xpath = prefix + ":" + xpath;
        }
        xmlRootFragment.setXPath(xpath);
        return generatedNamespace;
    }

    public void marshalSingleValue(XPathFragment xPathFragment, MarshalRecord marshalRecord, Object object, Object value, AbstractSession session, NamespaceResolver namespaceResolver, MarshalContext marshalContext) {
        if (null == value) {
            return;
        }

        boolean wasXMLRoot = false;
        XPathFragment xmlRootFragment = null;
        Object originalValue = value;

        XMLDescriptor descriptor;
        TreeObjectBuilder objectBuilder;
        AbstractSession childSession;
        XMLMarshaller marshaller = marshalRecord.getMarshaller();
        XPathFragment rootFragment;

        if (xmlAnyCollectionMapping.usesXMLRoot() && (value instanceof XMLRoot)) {
            xmlRootFragment = new XPathFragment();
            wasXMLRoot = true;
            value = ((XMLRoot) value).getObject();
        }
        UnmarshalKeepAsElementPolicy keepAsElementPolicy = xmlAnyCollectionMapping.getKeepAsElementPolicy();
        if (value instanceof String) {
            marshalSimpleValue(xmlRootFragment, marshalRecord, originalValue, object, value, session, namespaceResolver);
        } else if (((keepAsElementPolicy == UnmarshalKeepAsElementPolicy.KEEP_UNKNOWN_AS_ELEMENT) || (keepAsElementPolicy == UnmarshalKeepAsElementPolicy.KEEP_ALL_AS_ELEMENT)) && value instanceof org.w3c.dom.Node) {
            marshalRecord.node((org.w3c.dom.Node) value, marshalRecord.getNamespaceResolver());
        } else {
            try {
                childSession = marshaller.getXMLContext().getSession(value);
            } catch (XMLMarshalException e) {
                marshalSimpleValue(xmlRootFragment, marshalRecord, originalValue, object, value, session, namespaceResolver);
                return;
            }
            descriptor = (XMLDescriptor) childSession.getDescriptor(value);
            objectBuilder = (TreeObjectBuilder) descriptor.getObjectBuilder();
            List extraNamespaces = objectBuilder.addExtraNamespacesToNamespaceResolver(descriptor, marshalRecord, session);
            if (wasXMLRoot) {
                Namespace generatedNamespace = setupFragment(((XMLRoot) originalValue), xmlRootFragment, marshalRecord);
                if (generatedNamespace != null) {
                    if (extraNamespaces == null) {
                        extraNamespaces = new java.util.ArrayList();
                    }
                    extraNamespaces.add(generatedNamespace);
                }
            }

            /*
             * B5112171: 25 Apr 2006
             * During marshalling - XML AnyObject and AnyCollection
             * mappings throw a NullPointerException when the
             * "document root element" on child object descriptors are not
             * all defined.  These nodes will be ignored with a warning.
             */
            String defaultRootElementString = descriptor.getDefaultRootElement();

            if (!wasXMLRoot && (defaultRootElementString == null)) {
                AbstractSessionLog.getLog().log(SessionLog.WARNING, "marshal_warning_null_document_root_element", new Object[] { Helper.getShortClassName(this.getClass()), descriptor });
            } else {
                if ((marshaller != null) && (marshaller.getMarshalListener() != null)) {
                    marshaller.getMarshalListener().beforeMarshal(value);
                }

                if (xmlRootFragment != null) {
                    rootFragment = xmlRootFragment;
                } else {
                    rootFragment = new XPathFragment(defaultRootElementString);
                    //resolve URI
                    if ((rootFragment.getNamespaceURI() == null) && (rootFragment.getPrefix() != null)) {
                        String uri = descriptor.getNonNullNamespaceResolver().resolveNamespacePrefix(rootFragment.getPrefix());
                        rootFragment.setNamespaceURI(uri);
                    }
                }

                if (!wasXMLRoot) {
                    marshalRecord.setLeafElementType(descriptor.getDefaultRootElementType());
                }

                getXPathNode().startElement(marshalRecord, rootFragment, object, childSession, marshalRecord.getNamespaceResolver(), objectBuilder, value);

                writeExtraNamespaces(extraNamespaces, marshalRecord, session);
                if (xmlAnyCollectionMapping.shouldAddXsiType(marshaller, descriptor, originalValue, wasXMLRoot)) {
                    String typeValue = descriptor.getSchemaReference().getSchemaContext();
                    addTypeAttribute(descriptor, marshalRecord, typeValue);
                }

                objectBuilder.buildRow(marshalRecord, value, session, marshaller);

                objectBuilder.removeExtraNamespacesFromNamespaceResolver(marshalRecord, extraNamespaces, session);

                marshalRecord.endElement(rootFragment, namespaceResolver);
                if ((marshaller != null) && (marshaller.getMarshalListener() != null)) {
                    marshaller.getMarshalListener().afterMarshal(value);
                }
            }
        }
    }

    private void marshalSimpleValue(XPathFragment xmlRootFragment, MarshalRecord marshalRecord, Object originalValue, Object object, Object value, AbstractSession session, NamespaceResolver namespaceResolver) {
        if (xmlRootFragment != null) {
            QName qname = ((XMLRoot) originalValue).getSchemaType();
            value = ((XMLConversionManager) session.getDatasourcePlatform().getConversionManager()).convertObject(value, ClassConstants.STRING, qname);
            Namespace generatedNamespace = setupFragment((XMLRoot) originalValue, xmlRootFragment, marshalRecord);
            getXPathNode().startElement(marshalRecord, xmlRootFragment, object, session, namespaceResolver, null, null);
            if (generatedNamespace != null) {
                marshalRecord.attribute(XMLConstants.XMLNS_URL, XMLConstants.XMLNS_URL, XMLConstants.XMLNS + ":" + generatedNamespace.getPrefix(), generatedNamespace.getNamespaceURI());
            }
            if (qname != null) {
                String prefix = marshalRecord.getNamespaceResolver().resolveNamespaceURI(qname.getNamespaceURI());
                if ((prefix == null) || prefix.equals("")) {
                    prefix = marshalRecord.getNamespaceResolver().generatePrefix();
                    marshalRecord.attribute(XMLConstants.XMLNS_URL, XMLConstants.XMLNS_URL, XMLConstants.XMLNS + ":" + prefix, qname.getNamespaceURI());
                }
                String typeValue = prefix + ":" + qname.getLocalPart();
                addTypeAttribute(marshalRecord, typeValue);
            }
        }

        marshalRecord.characters((String) value);

        if (xmlRootFragment != null) {
            marshalRecord.endElement(xmlRootFragment, namespaceResolver);
        }
    }

    public XMLAnyCollectionMapping getMapping() {
        return xmlAnyCollectionMapping;
    }

}