/*******************************************************************************
 * 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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.persistence.internal.oxm.XPathFragment;
import org.eclipse.persistence.internal.oxm.record.MarshalContext;
import org.eclipse.persistence.internal.oxm.record.ObjectMarshalContext;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.oxm.NamespaceResolver;
import org.eclipse.persistence.oxm.XMLMarshaller;
import org.eclipse.persistence.oxm.record.MarshalRecord;

/**
 * INTERNAL:
 * <p><b>Purpose</b>:  XPathNodes are used together to build a tree.  The tree
 * is built from all of the XPath statements specified in the mapping metadata 
 * (mappings and policies).  This tree is then navigated by the 
 * TreeObjectBuilder to perform marshal and unmarshal operations.</p>
 * <p>The XPaths "a/b" and "a/c" would result in a tree with the root "a" and 
 * two child nodes "b" and "c".</p>
 * <p><b>Responsibilities</b>:<ul>
 * <li>All tree relationships must be bi-directional.</li>
 * <li>Reference a NodeValue, XPathNodes without a Node value represent grouping
 * elements.</li>
 * <li>Reference an XPathFragment, XPathFragments contain name and namespace
 * information.</li>
 * <li>Must differentiate between child nodes that correspond to elements and 
 * those that do not.</li>
 * <li>Must represent special mapping situations like any and self mappings.</li>
 * </ul> 
 */

public class XPathNode {
    private NodeValue unmarshalNodeValue;
    private NodeValue marshalNodeValue;
    private XPathFragment xPathFragment;
    private XPathNode parent;
    private List attributeChildren;
    private List nonAttributeChildren;
    private List selfChildren;
    private Map attributeChildrenMap;
    private Map nonAttributeChildrenMap;
    private XMLAnyAttributeMappingNodeValue anyAttributeNodeValue;
    private XPathNode anyAttributeNode;

    public XPathFragment getXPathFragment() {
        return xPathFragment;
    }

    public void setXPathFragment(XPathFragment xPathFragment) {
        this.xPathFragment = xPathFragment;
    }

    public NodeValue getNodeValue() {
        return unmarshalNodeValue;
    }

    public void setNodeValue(NodeValue nodeValue) {
        this.marshalNodeValue = nodeValue;
        this.unmarshalNodeValue = nodeValue;
        if (null != nodeValue) {
            nodeValue.setXPathNode(this);
        }
    }
    
    public NodeValue getUnmarshalNodeValue() {
        return unmarshalNodeValue;
    }
    
    public void setUnmarshalNodeValue(NodeValue nodeValue) {
        if (null != nodeValue) {
            nodeValue.setXPathNode(this);
        }
        this.unmarshalNodeValue = nodeValue;
    }

    public NodeValue getMarshalNodeValue() {
        return marshalNodeValue;
    }
    
    public void setMarshalNodeValue(NodeValue nodeValue) {
        if (null != nodeValue) {
            nodeValue.setXPathNode(this);
        }
        this.marshalNodeValue = nodeValue;
    }
    
    public XPathNode getParent() {
        return parent;
    }

    public void setParent(XPathNode parent) {
        this.parent = parent;
    }

    public List getAttributeChildren() {
        return this.attributeChildren;
    }

    public List getNonAttributeChildren() {
        return this.nonAttributeChildren;
    }

    public List getSelfChildren() {
        return this.selfChildren;
    }

    public Map getNonAttributeChildrenMap() {
        return this.nonAttributeChildrenMap;
    }

    public Map getAttributeChildrenMap() {
        return this.attributeChildrenMap;
    }

    public void setAnyAttributeNodeValue(XMLAnyAttributeMappingNodeValue nodeValue) {
        this.anyAttributeNodeValue = nodeValue;
    }

    public XMLAnyAttributeMappingNodeValue getAnyAttributeNodeValue() {
        return this.anyAttributeNodeValue;
    }

    public XPathNode getAnyAttributeNode() {
        return this.anyAttributeNode;
    }

    public boolean equals(Object object) {
        try {
            XPathNode perfNode = (XPathNode)object;

            if ((getXPathFragment() == null) && (perfNode.getXPathFragment() != null)) {
                return false;
            }
            if ((getXPathFragment() != null) && (perfNode.getXPathFragment() == null)) {
                return false;
            }
            if (getXPathFragment() == perfNode.getXPathFragment()) {
                return true;
            }
            return this.getXPathFragment().equals(perfNode.getXPathFragment());

            // turn fix off for now until we re-enable XMLAnyObjectAndAnyCollectionTestCases
            //          } catch (NullPointerException npe) {
            // b5259059 all cases X0X1 (1mapping xpath=null, 2nd mapping xpath=filled
            // catch when object.getXPathFragment() == null
            // (this will also catch case where perfNode XPath is null)
            //          	return false;
        } catch (ClassCastException e) {
            return false;
        }
    }

    public void addChild(XPathFragment anXPathFragment, NodeValue aNodeValue, NamespaceResolver namespaceResolver) {
        if (anXPathFragment != null && namespaceResolver != null && anXPathFragment.getNamespaceURI() == null && !anXPathFragment.nameIsText()) {
            if(!anXPathFragment.isAttribute()) {
                anXPathFragment.setNamespaceURI(namespaceResolver.resolveNamespacePrefix(anXPathFragment.getPrefix()));
            } else if(anXPathFragment.hasNamespace()) {
                anXPathFragment.setNamespaceURI(namespaceResolver.resolveNamespacePrefix(anXPathFragment.getPrefix()));
            }
        }

        XPathNode xPathNode = new XPathNode();
        xPathNode.setXPathFragment(anXPathFragment);

        List children;
        Map childrenMap;

        if ((anXPathFragment != null) && anXPathFragment.isAttribute()) {
            if (null == attributeChildren) {
                attributeChildren = new ArrayList();
                attributeChildrenMap = new HashMap();
            }
            children = attributeChildren;
            childrenMap = attributeChildrenMap;
        } else {
            if (null == nonAttributeChildren) {
                nonAttributeChildren = new ArrayList();
                nonAttributeChildrenMap = new HashMap();
            }
            children = nonAttributeChildren;
            childrenMap = nonAttributeChildrenMap;
        }

        if (null == anXPathFragment) {
            if(aNodeValue.isMarshalNodeValue()) {
                xPathNode.setMarshalNodeValue(aNodeValue);
            }
            if(aNodeValue.isUnmarshalNodeValue()) {
                xPathNode.setUnmarshalNodeValue(aNodeValue);
            }
            xPathNode.setParent(this);
            if (aNodeValue instanceof XMLAnyAttributeMappingNodeValue) {
                setAnyAttributeNodeValue((XMLAnyAttributeMappingNodeValue)aNodeValue);
                anyAttributeNode = xPathNode;
            } else {
                if(!children.contains(xPathNode)) {
                    children.add(xPathNode);
                }
                childrenMap.put(anXPathFragment, xPathNode);
            }
            return;
        }

        int index = children.indexOf(xPathNode);
        if (index >= 0) {
            xPathNode = (XPathNode)children.get(index);
        } else {
            xPathNode.setParent(this);
            if(!children.contains(xPathNode)) {
                children.add(xPathNode);
            }
            if (XPathFragment.SELF_FRAGMENT.equals(anXPathFragment)) {
                if (null == selfChildren) {
                    selfChildren = new ArrayList();
                }
                if(!selfChildren.contains(xPathNode)) {
                    selfChildren.add(xPathNode);
                }
            } else {
                childrenMap.put(anXPathFragment, xPathNode);
            }
        }

        if (aNodeValue.isOwningNode(anXPathFragment)) {
            if(aNodeValue.isMarshalNodeValue()) {
                xPathNode.setMarshalNodeValue(aNodeValue);
            } 
            if(aNodeValue.isUnmarshalNodeValue()) {
                xPathNode.setUnmarshalNodeValue(aNodeValue);
            }
        } else {
            XPathFragment nextFragment = anXPathFragment.getNextFragment();
            xPathNode.addChild(nextFragment, aNodeValue, namespaceResolver);
        }
    }

    public boolean marshal(MarshalRecord marshalRecord, Object object, AbstractSession session, NamespaceResolver namespaceResolver, MarshalContext marshalContext) {
        return marshal(marshalRecord, object, session, namespaceResolver, null, marshalContext);
    }

    public boolean marshal(MarshalRecord marshalRecord, Object object, AbstractSession session, NamespaceResolver namespaceResolver, org.eclipse.persistence.oxm.XMLMarshaller marshaller, MarshalContext marshalContext) {
        if ((null == marshalNodeValue) || marshalNodeValue.isMarshalOnlyNodeValue()) {
            marshalRecord.addGroupingElement(this);

            XPathNode xPathNode;
            boolean hasValue = false;
            if (null != attributeChildren) {
                int size = attributeChildren.size();
                for (int x = 0; x < size; x++) {
                    xPathNode = (XPathNode)attributeChildren.get(x);
                    hasValue = xPathNode.marshal(marshalRecord, object, session, namespaceResolver, marshaller, ObjectMarshalContext.getInstance()) || hasValue;
                }
            }
            if (anyAttributeNode != null) {
                hasValue = anyAttributeNode.marshal(marshalRecord, object, session, namespaceResolver, marshaller, ObjectMarshalContext.getInstance()) || hasValue;
            }
            if (null != nonAttributeChildren) {
                int size = marshalContext.getNonAttributeChildrenSize(this);
                for (int x = 0; x < size; x++) {
                    xPathNode = (XPathNode)marshalContext.getNonAttributeChild(x, this);
                    MarshalContext childMarshalContext = marshalContext.getMarshalContext(x);
                    hasValue = xPathNode.marshal(marshalRecord, object, session, namespaceResolver, marshaller, childMarshalContext) || hasValue;
                }
            }

            if (hasValue) {
                marshalRecord.endElement(this.getXPathFragment(), namespaceResolver);
            } else {
                marshalRecord.removeGroupingElement(this);
            }

            return hasValue;
        } else {
            return marshalContext.marshal(marshalNodeValue, xPathFragment, marshalRecord, object, session, namespaceResolver);
        }
    }

    public boolean startElement(MarshalRecord marshalRecord, XPathFragment anXPathFragment, Object object, AbstractSession session, NamespaceResolver namespaceResolver, TreeObjectBuilder compositeObjectBuilder, Object compositeObject) {
        if (null == anXPathFragment) {
            return false;
        }
        marshalRecord.openStartElement(anXPathFragment, namespaceResolver);
        XPathNode attributeNode;
        boolean hasValue = false;
        if (null != attributeChildren) {
            int size = attributeChildren.size();
            for (int x = 0; x < size; x++) {
                attributeNode = (XPathNode)attributeChildren.get(x);
                hasValue = attributeNode.marshal(marshalRecord, object, session, namespaceResolver, ObjectMarshalContext.getInstance()) || hasValue;
            }
        }
        if (anyAttributeNode != null) {
            //marshal the anyAttribute node here before closeStartElement()
            hasValue = anyAttributeNode.marshal(marshalRecord, object, session, namespaceResolver, ObjectMarshalContext.getInstance()) || hasValue;
        }

        if (null != compositeObjectBuilder) {
            hasValue = compositeObjectBuilder.marshalAttributes(marshalRecord, compositeObject, session) || hasValue;
        }
        marshalRecord.closeStartElement();
        return hasValue;
    }

    /**
     * Marshal any 'self' mapped attributes.  
     * 
     * @param marshalRecord
     * @param object
     * @param session
     * @param namespaceResolver
     * @param marshaller
     * @return
     */
    public boolean marshalSelfAttributes(MarshalRecord marshalRecord, Object object, AbstractSession session, NamespaceResolver namespaceResolver, XMLMarshaller marshaller) {
        if (marshalNodeValue == null) {
            return false;
        }
        return marshalNodeValue.marshalSelfAttributes(xPathFragment, marshalRecord, object, session, namespaceResolver, marshaller);
    }
}