/*
 * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//     Oracle - initial API and implementation from Oracle TopLink
//     Marcel Valovy - 2.6.0 - added case insensitive unmarshalling
package org.eclipse.persistence.internal.oxm.record;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;

import org.eclipse.persistence.core.descriptors.CoreDescriptor;
import org.eclipse.persistence.core.descriptors.CoreDescriptorEventManager;
import org.eclipse.persistence.core.descriptors.CoreInheritancePolicy;
import org.eclipse.persistence.core.queries.CoreAttributeGroup;
import org.eclipse.persistence.descriptors.DescriptorEvent;
import org.eclipse.persistence.descriptors.DescriptorEventManager;
import org.eclipse.persistence.exceptions.EclipseLinkException;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.core.helper.CoreField;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractRecord;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.identitymaps.CacheId;
import org.eclipse.persistence.internal.oxm.Constants;
import org.eclipse.persistence.internal.oxm.ContainerValue;
import org.eclipse.persistence.internal.oxm.ConversionManager;
import org.eclipse.persistence.internal.oxm.IDResolver;
import org.eclipse.persistence.internal.oxm.MappingNodeValue;
import org.eclipse.persistence.internal.oxm.NamespaceResolver;
import org.eclipse.persistence.internal.oxm.NodeValue;
import org.eclipse.persistence.internal.oxm.NullCapableValue;
import org.eclipse.persistence.internal.oxm.ObjectBuilder;
import org.eclipse.persistence.internal.oxm.Reference;
import org.eclipse.persistence.internal.oxm.ReferenceResolver;
import org.eclipse.persistence.internal.oxm.Root;
import org.eclipse.persistence.internal.oxm.SAXFragmentBuilder;
import org.eclipse.persistence.internal.oxm.StrBuffer;
import org.eclipse.persistence.internal.oxm.Unmarshaller;
import org.eclipse.persistence.internal.oxm.XPathFragment;
import org.eclipse.persistence.internal.oxm.XPathNode;
import org.eclipse.persistence.internal.oxm.XPathPredicate;
import org.eclipse.persistence.internal.oxm.XPathQName;
import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
import org.eclipse.persistence.internal.oxm.mappings.DirectMapping;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.internal.oxm.mappings.Mapping;
import org.eclipse.persistence.internal.oxm.mappings.TransformationMapping;
import org.eclipse.persistence.internal.oxm.record.namespaces.StackUnmarshalNamespaceResolver;
import org.eclipse.persistence.internal.oxm.record.namespaces.UnmarshalNamespaceResolver;
import org.eclipse.persistence.internal.oxm.unmapped.UnmappedContentHandler;
import org.eclipse.persistence.internal.security.PrivilegedNewInstanceFromClass;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.sessions.coordination.CommandProcessor;
import org.w3c.dom.Document;
import org.xml.sax.Attributes;
import org.xml.sax.ErrorHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.ext.Locator2;
import org.xml.sax.ext.Locator2Impl;

/**
 * <p><b>Purpose:</b>Provide an implementation of ContentHandler that is used by TopLink OXM to
 * build mapped Java Objects from SAX events.
 * <p><b>Responsibilities:</b><ul>
 * <li>Implement the ContentHandler and LexicalHandler interfaces</li>
 * <li>Make calls into the appropriate NodeValues based on the incoming SAXEvents</li>
 * <li>Make callbacks into XMLReader for newObject events</li>
 * <li>Maintain a map of Collections to be populated for collection mappings.</li>
 *
 * @see org.eclipse.persistence.internal.oxm.XPathNode
 * @see org.eclipse.persistence.internal.oxm.NodeValue
 * @see org.eclipse.persistence.internal.oxm.TreeObjectBuilder
 * @author bdoughan
 *
 */
public class UnmarshalRecordImpl<TRANSFORMATION_RECORD extends TransformationRecord> extends CoreAbstractRecord implements UnmarshalRecord<CoreAbstractSession, CoreField, IDResolver, ObjectBuilder, TRANSFORMATION_RECORD, Unmarshaller> {
    protected XMLReader xmlReader;
    private ObjectBuilder treeObjectBuilder;
    private XPathFragment xPathFragment;
    private XPathNode xPathNode;
    /**
     * Used to increase performance. We are trying to predict next mapping to unmarshal.
     * It can reduce the number of map lookups.
     */
    private XPathNode predictedNextXPathNode;
    private int levelIndex;
    private UnmarshalRecord childRecord;
    protected UnmarshalRecord parentRecord;
    private TRANSFORMATION_RECORD transformationRecord;
    private List<UnmarshalRecord> selfRecords;
    private Map<XPathFragment, Integer> indexMap;
    private List<NullCapableValue> nullCapableValues;
    private Object[] containerInstances;
    private List<ContainerValue> defaultEmptyContainerValues;
    private List<ContainerValue> populatedContainerValues;
    private boolean isBufferCDATA;
    private Attributes attributes;
    private QName typeQName;
    protected String rootElementLocalName;
    protected String rootElementName;
    protected String rootElementNamespaceUri;
    private SAXFragmentBuilder fragmentBuilder;
    private Map<String, String> prefixesForFragment;
    private String encoding;
    private String version;
    private String schemaLocation;
    private String noNamespaceSchemaLocation;
    private boolean isSelfRecord;
    private UnmarshalContext unmarshalContext;
    private UnmarshalNamespaceResolver unmarshalNamespaceResolver;
    private boolean isXsiNil;
    private boolean xpathNodeIsMixedContent = false;
    private int unmappedLevel = -1;
    private ReferenceResolver referenceResolver;


    protected Unmarshaller unmarshaller;
    protected Object currentObject;
    protected CoreAbstractSession session;
    protected boolean namespaceAware;
    private XPathQName leafElementType;
    private NamespaceResolver namespaceResolver;

    private CoreAttributeGroup unmarshalAttributeGroup;

    // The "snapshot" location of this object, for @XmlLocation
    private Locator xmlLocation;

    protected XPathFragment textWrapperFragment;

    private ConversionManager conversionManager;

    protected UnmarshalRecordImpl() {
    }

    public UnmarshalRecordImpl(ObjectBuilder objectBuilder) {
        this(objectBuilder, new ReferenceResolver());
    }

    private UnmarshalRecordImpl(ObjectBuilder objectBuilder, ReferenceResolver referenceResolver) {
        super();
        this.referenceResolver = referenceResolver;
        this.xPathFragment = new XPathFragment();
        xPathFragment.setNamespaceAware(isNamespaceAware());
        this.setUnmarshalAttributeGroup(DEFAULT_ATTRIBUTE_GROUP);
        initialize(objectBuilder);
    }

    public UnmarshalRecord initialize(ObjectBuilder treeObjectBuilder) {
        this.isBufferCDATA = false;
        this.treeObjectBuilder = treeObjectBuilder;
        if (null != treeObjectBuilder) {
            this.xPathNode = treeObjectBuilder.getRootXPathNode();
            if (null != treeObjectBuilder.getNullCapableValues()) {
                this.nullCapableValues = new ArrayList<NullCapableValue>(treeObjectBuilder.getNullCapableValues());
            }
            if (null != treeObjectBuilder.getDefaultEmptyContainerValues()){
        this.defaultEmptyContainerValues = new ArrayList<ContainerValue>(treeObjectBuilder.getDefaultEmptyContainerValues());
            }
        }
        isSelfRecord = false;
        return this;
    }

    private void reset() {
        xPathNode = null;
        childRecord = null;
        transformationRecord = null;
        if(null != selfRecords) {
            selfRecords.clear();
        }
        if(null != indexMap) {
            indexMap.clear();
        }
        nullCapableValues = null;
        isBufferCDATA = false;
        attributes = null;
        typeQName = null;
        isSelfRecord = false;
        unmarshalContext = null;
        isXsiNil = false;
        unmappedLevel = -1;
        predictedNextXPathNode = null;
    }

    @Override
    public String getLocalName() {
        return rootElementLocalName;
    }

    public void setLocalName(String localName) {
        rootElementLocalName = localName;
    }

    public String getNamespaceURI() {
        throw XMLMarshalException.operationNotSupported("getNamespaceURI");
    }

    public void clear() {
        throw XMLMarshalException.operationNotSupported("clear");
    }

    public Document getDocument() {
        throw XMLMarshalException.operationNotSupported("getDocument");
    }

    public String transformToXML() {
        throw XMLMarshalException.operationNotSupported("transformToXML");
    }

    public XMLReader getXMLReader() {
        return this.xmlReader;
    }

    public void setXMLReader(XMLReader xmlReader) {
        this.xmlReader = xmlReader;
        namespaceAware = xmlReader.isNamespaceAware();
        if(xPathFragment != null){
            xPathFragment.setNamespaceAware(isNamespaceAware());
        }
    }

    public UnmarshalRecord getChildRecord() {
        return this.childRecord;
    }

    public void setChildRecord(UnmarshalRecord childRecord) {
        this.childRecord = childRecord;
        if (null != childRecord) {
            childRecord.setParentRecord(this);
        }
    }

    public UnmarshalRecord getParentRecord() {
        return this.parentRecord;
    }

    /**
     * INTERNAL:
     * The ReferenceResolver that is leveraged by key based mappings.
     * @since EclipseLink 2.5.0
     */
    public ReferenceResolver getReferenceResolver() {
        if(null == referenceResolver) {
            referenceResolver = new ReferenceResolver();
        }
        return referenceResolver;
    }

    /**
     * INTERNAL:
     * Set the ReferenceResolver that will be leveraged by key based mappings.
     * @since EclipseLink 2.5.0
     */
    public void setReferenceResolver(ReferenceResolver referenceResolver) {
        this.referenceResolver = referenceResolver;
    }

    /**
     * Return the root element's prefix qualified name
     */
    public String getRootElementName() {
        return rootElementName;
    }

    public void setRootElementName(String qName) {
        this.rootElementName = qName;
    }

    /**
     * Return the root element's namespace URI
     */
    public String getRootElementNamespaceUri() {
        return rootElementNamespaceUri;
    }

    public void setRootElementNamespaceUri(String uri) {
        this.rootElementNamespaceUri = uri;
    }

    public void setParentRecord(UnmarshalRecord parentRecord) {
        this.parentRecord = parentRecord;
    }

    @Override
    public TRANSFORMATION_RECORD getTransformationRecord() {
        return this.transformationRecord;
    }

    @Override
    public void setTransformationRecord(TRANSFORMATION_RECORD transformationRecord) {
        this.transformationRecord = transformationRecord;
    }

    public UnmarshalNamespaceResolver getUnmarshalNamespaceResolver() {
        if(null == unmarshalNamespaceResolver) {
            this.unmarshalNamespaceResolver = new StackUnmarshalNamespaceResolver();
        }
        return this.unmarshalNamespaceResolver;
    }

    public void setUnmarshalNamespaceResolver(UnmarshalNamespaceResolver anUnmarshalNamespaceResolver) {
        this.unmarshalNamespaceResolver = anUnmarshalNamespaceResolver;
    }

    public List getNullCapableValues() {
        if (null == nullCapableValues) {
            this.nullCapableValues = new ArrayList<NullCapableValue>();
        }
        return this.nullCapableValues;
    }

    public void removeNullCapableValue(NullCapableValue nullCapableValue) {
        if(null != nullCapableValues) {
            nullCapableValues.remove(nullCapableValue);
        }
    }

    public Object getContainerInstance(ContainerValue c) {
        return getContainerInstance(c, true);
    }

    public Object getContainerInstance(ContainerValue c, boolean createContainerIfNecessary) {
        Object containerInstance = containerInstances[c.getIndex()];

        if (containerInstance == null) {
            Mapping mapping = c.getMapping();
            //don't attempt to do a get on a readOnly property.
            if(c.getReuseContainer() && !(mapping.isReadOnly())) {
        containerInstance = mapping.getAttributeValueFromObject(currentObject);
            }
            if(null == containerInstance && createContainerIfNecessary) {
                containerInstance = c.getContainerInstance();
            }
            containerInstances[c.getIndex()] = containerInstance;
            populatedContainerValues.add(c);
            if(defaultEmptyContainerValues != null){
        defaultEmptyContainerValues.remove(c);
            }
        }

        return containerInstance;
    }

    public void setContainerInstance(int index, Object containerInstance) {
        containerInstances[index] = containerInstance;
    }

    /**
     * PUBLIC:
     * Gets the encoding for this document. Only set on the root-level UnmarshalRecord
     * @return a String representing the encoding for this doc
     */
    public String getEncoding() {
        return encoding;
    }

    /**
     * INTERNAL:
     */
    public void setEncoding(String enc) {
        this.encoding = enc;
    }

    /**
     * PUBLIC:
     * Gets the XML Version for this document. Only set on the root-level
     * UnmarshalRecord, if supported by the parser.
     */
    public String getVersion() {
        return version;
    }

    /**
     * INTERNAL:
     */
    public void setVersion(String version) {
        this.version = version;
    }

    public String getSchemaLocation() {
        return schemaLocation;
    }

    public void setSchemaLocation(String schemaLocation) {
        this.schemaLocation = schemaLocation;
    }

    public String getNoNamespaceSchemaLocation() {
        return noNamespaceSchemaLocation;
    }

    public void setNoNamespaceSchemaLocation(String location) {
        this.noNamespaceSchemaLocation = location;
    }

    protected StrBuffer getStringBuffer() {
        return unmarshaller.getStringBuffer();
    }

    public CharSequence getCharacters() {
        return unmarshaller.getStringBuffer();
    }

    public Attributes getAttributes() {
        return this.attributes;
    }

    public void setAttributes(Attributes attributes) {
        this.attributes = attributes;
    }

    public QName getTypeQName() {
        return this.typeQName;
    }

    public void setTypeQName(QName typeQName) {
        this.typeQName = typeQName;
    }

    public void setDocumentLocator(Locator locator) {
    if(xmlReader != null){
        xmlReader.setLocator(locator);
        if (null == rootElementName  && null == rootElementLocalName && parentRecord == null && locator instanceof Locator2){
                Locator2 loc = (Locator2)locator;
                this.setEncoding(loc.getEncoding());
                this.setVersion(loc.getXMLVersion());
            }
    }
    }

    public Locator getDocumentLocator() {
    if(xmlReader != null){
        return xmlReader.getLocator();
    }
      return null;
    }

    public Object get(CoreField key) {
        Field xmlField = this.convertToXMLField(key);
        XPathFragment lastFragment = xmlField.getLastXPathFragment();
        String namespaceURI = lastFragment.getNamespaceURI();
        if(namespaceURI == null){
        NamespaceResolver namespaceResolver = xmlField.getNamespaceResolver();
            namespaceURI = Constants.EMPTY_STRING;
            if (null != namespaceResolver && !(lastFragment.isAttribute() && lastFragment.getPrefix() == null)) {
                namespaceURI = namespaceResolver.resolveNamespacePrefix(lastFragment.getPrefix());
                if (null == namespaceURI) {
                    namespaceURI = Constants.EMPTY_STRING;
                }
            }
        }
        if(isNamespaceAware()){
            return attributes.getValue(namespaceURI, lastFragment.getLocalName());
        }
        return attributes.getValue(lastFragment.getLocalName());
    }


    public XPathNode getXPathNode() {
        return xPathNode;
    }

    public Descriptor getDescriptor() {
        return (Descriptor) treeObjectBuilder.getDescriptor();
    }

    public UnmarshalContext getUnmarshalContext() {
        return unmarshalContext;
    }

    public void setUnmarshalContext(UnmarshalContext unmarshalContext) {
        this.unmarshalContext = unmarshalContext;
    }

    public boolean isNil() {
        return this.isXsiNil;
    }

    public void setNil(boolean nil) {
        this.isXsiNil = nil;
    }

    public void startDocument() throws SAXException {
        if (unmarshaller.getIDResolver() != null && parentRecord == null) {
        unmarshaller.getIDResolver().startDocument(unmarshaller.getErrorHandler());
        }
    }

    private void initializeRecord(Attributes attrs) throws SAXException{
        this.setAttributes(attrs);
    Descriptor xmlDescriptor = (Descriptor) treeObjectBuilder.getDescriptor();
    if(!xmlDescriptor.hasInheritance() || xmlDescriptor.getInheritancePolicy().getClassIndicatorField() == null){
        initialize((ObjectBuilder)xmlDescriptor.getObjectBuilder());
        initializeRecord((Mapping)null);
        return;
        }
    CoreInheritancePolicy inheritancePolicy = xmlDescriptor.getInheritancePolicy();
    Class classValue = treeObjectBuilder.classFromRow(this, session);
     if (classValue == null) {
             // no xsi:type attribute - look for type indicator on the default root element
             QName leafElementType = xmlDescriptor.getDefaultRootElementType();

             // if we have a user-set type, try to get the class from the inheritance policy
             if (leafElementType != null) {
         XPathQName xpathQName = new XPathQName(leafElementType, isNamespaceAware());
                 Object indicator = inheritancePolicy.getClassIndicatorMapping().get(xpathQName);
                 if(indicator != null) {
                     classValue = (Class)indicator;
                 }
             }
         }
         if (classValue != null) {
             xmlDescriptor = (Descriptor)session.getDescriptor(classValue);
         }
         initialize((ObjectBuilder)xmlDescriptor.getObjectBuilder());
         initializeRecord((Mapping)null);
    }

    public void initializeRecord(Mapping selfRecordMapping) throws SAXException {
        try {
            Descriptor xmlDescriptor = (Descriptor) treeObjectBuilder.getDescriptor();
            if(xmlDescriptor.isSequencedObject()) {
                unmarshalContext = new SequencedUnmarshalContext();
            } else {
                unmarshalContext = ObjectUnmarshalContext.getInstance();
            }

            currentObject = this.xmlReader.getCurrentObject(session, selfRecordMapping);
            if (currentObject == null) {
                currentObject = treeObjectBuilder.buildNewInstance();
            }
            if (xmlDescriptor.getLocationAccessor() != null && xmlReader.getLocator() != null){
                // Check to see if this Descriptor isLocationAware
                    // Store the snapshot of the current documentLocator
                    xmlLocation  = new Locator2Impl(xmlReader.getLocator());
            }

            Object parentRecordCurrentObject = null;
            if (null != this.parentRecord) {
                parentRecordCurrentObject = parentRecord.getCurrentObject();
            }

            Unmarshaller.Listener xmlUnmarshalListener = unmarshaller.getUnmarshalListener();
            if (null != xmlUnmarshalListener) {
                if (null == this.parentRecord) {
                    xmlUnmarshalListener.beforeUnmarshal(currentObject, null);
                } else {
                    xmlUnmarshalListener.beforeUnmarshal(currentObject, parentRecordCurrentObject);
                }
            }
            if (null == parentRecord) {
                this.xmlReader.newObjectEvent(currentObject, null, selfRecordMapping);
            } else {
                this.xmlReader.newObjectEvent(currentObject, parentRecordCurrentObject, selfRecordMapping);
            }
            List containerValues = treeObjectBuilder.getContainerValues();
            if (null != containerValues) {
                int containerSize = containerValues.size();
                containerInstances = new Object[containerSize];
                populatedContainerValues = new ArrayList(containerSize);
            }

            if (null != xPathNode.getSelfChildren()) {
                int selfChildrenSize = xPathNode.getSelfChildren().size();
                selfRecords = new ArrayList<UnmarshalRecord>(selfChildrenSize);
                for (int x = 0; x < selfChildrenSize; x++) {
                    NodeValue nv = xPathNode.getSelfChildren().get(x).getNodeValue();
                    if (null != nv) {
                        selfRecords.add(nv.buildSelfRecord(this, attributes));
                    }
                }
            }
        } catch (EclipseLinkException e) {
            if (null == xmlReader.getErrorHandler()) {
                throw e;
            } else {
                SAXParseException saxParseException = new SAXParseException(null, getDocumentLocator(), e);
                xmlReader.getErrorHandler().error(saxParseException);
            }
        }
    }

    public void endDocument() throws SAXException {
        if (unmarshaller.getIDResolver() != null && parentRecord == null) {
        unmarshaller.getIDResolver().endDocument();
        }
        if (null != selfRecords) {
            for (int x = 0, selfRecordsSize = selfRecords.size(); x < selfRecordsSize; x++) {
                UnmarshalRecord selfRecord = selfRecords.get(x);
                if(selfRecord != null){
                    selfRecord.endDocument();
                }
            }
        }

        if (null != xPathNode.getSelfChildren()) {
            int selfChildrenSize = xPathNode.getSelfChildren().size();
            for (int x = 0; x < selfChildrenSize; x++) {
                XPathNode selfNode = xPathNode.getSelfChildren().get(x);
                if (null != selfNode.getNodeValue()) {
                    selfNode.getNodeValue().endSelfNodeValue(this, selfRecords.get(x), attributes);
                }
            }
        }

        CoreDescriptor xmlDescriptor = (CoreDescriptor) treeObjectBuilder.getDescriptor();

        try {
            // PROCESS COLLECTION MAPPINGS
        //All populated containerValues need to be set on the object
        if(null != populatedContainerValues){
                for (int populatedCVSize=populatedContainerValues.size(), i = populatedCVSize-1; i>=0; i--) {
                ContainerValue cv = ((ContainerValue) populatedContainerValues.get(i));
                cv.setContainerInstance(currentObject, getContainerInstance(cv, cv.isDefaultEmptyContainer()));
            }
        }

        //Additionally if any containerValues are defaultEmptyContainerValues they need to be set to a new empty container
        if(null != defaultEmptyContainerValues){
                 for (int defaultEmptyCVSize=defaultEmptyContainerValues.size(),i = defaultEmptyCVSize-1; i>=0; i--) {
                     ContainerValue cv = ((ContainerValue) defaultEmptyContainerValues.get(i));
                     cv.setContainerInstance(currentObject, getContainerInstance(cv, cv.isDefaultEmptyContainer()));
                 }

        }
            // PROCESS NULL CAPABLE VALUES
            // This must be done because the node may not have existed to
            // trigger the mapping.
            if(null != nullCapableValues) {
                for (int x = 0, nullValuesSize = nullCapableValues.size(); x < nullValuesSize; x++) {
                    nullCapableValues.get(x).setNullValue(currentObject, session);
                }
            }

            // PROCESS TRANSFORMATION MAPPINGS
            List<TransformationMapping> transformationMappings = treeObjectBuilder.getTransformationMappings();
            if (null != transformationMappings) {
                for (int x = 0, transformationMappingsSize = transformationMappings.size(); x < transformationMappingsSize; x++) {
                    TransformationMapping transformationMapping = transformationMappings.get(x);
                    transformationMapping.readFromRowIntoObject((XMLRecord) transformationRecord, currentObject, session, true);
                }
            }

            Unmarshaller.Listener listener = unmarshaller.getUnmarshalListener();
            if (listener != null) {
                if (this.parentRecord != null) {
                    listener.afterUnmarshal(currentObject, parentRecord.getCurrentObject());
                } else {
                    listener.afterUnmarshal(currentObject, null);
                }
            }

            // HANDLE POST BUILD EVENTS
            if(xmlDescriptor.hasEventManager()) {
                CoreDescriptorEventManager eventManager = xmlDescriptor.getEventManager();
                if (null != eventManager && eventManager.hasAnyEventListeners()) {
                    DescriptorEvent event = new DescriptorEvent(currentObject);
                    event.setSession((AbstractSession) session);
                    event.setRecord(null); //this);
                    event.setEventCode(DescriptorEventManager.PostBuildEvent);
                    eventManager.executeEvent(event);
                }
            }
        } catch (EclipseLinkException e) {
            if (null == xmlReader.getErrorHandler()) {
                throw e;
            } else {
                SAXParseException saxParseException = new SAXParseException(null, getDocumentLocator(), e);
                xmlReader.getErrorHandler().error(saxParseException);
            }
        }

        // if the object has any primary key fields set, add it to the cache
        if(null != referenceResolver) {
            if(null != xmlDescriptor) {
                List primaryKeyFields = xmlDescriptor.getPrimaryKeyFields();
                if(null != primaryKeyFields) {
                    int primaryKeyFieldsSize = primaryKeyFields.size();
                    if (primaryKeyFieldsSize > 0) {
                        CacheId pk = (CacheId) treeObjectBuilder.extractPrimaryKeyFromObject(currentObject, session);
                        for (int x=0; x<primaryKeyFieldsSize; x++) {
                            Object value = pk.getPrimaryKey()[x];
                            if (null == value) {
                                Field pkField = (Field) xmlDescriptor.getPrimaryKeyFields().get(x);
                                pk.set(x, unmarshaller.getContext().getValueByXPath(currentObject, pkField.getXPath(), pkField.getNamespaceResolver(), Object.class));
                            }
                        }
                        referenceResolver.putValue(xmlDescriptor.getJavaClass(), pk, currentObject);

                        if (unmarshaller.getIDResolver() != null) {
                            try {
                                if (primaryKeyFieldsSize > 1) {
                                    Map<String, Object> idWrapper = new HashMap<String, Object>();
                                    for (int x = 0; x < primaryKeyFieldsSize; x++) {
                                        String idName = (String) xmlDescriptor.getPrimaryKeyFieldNames().get(x);
                                        Object idValue = pk.getPrimaryKey()[x];
                                        idWrapper.put(idName, idValue);
                                    }
                                    unmarshaller.getIDResolver().bind(idWrapper, currentObject);
                                } else {
                    unmarshaller.getIDResolver().bind(pk.getPrimaryKey()[0], currentObject);
                                }
                            } catch (SAXException e) {
                                throw XMLMarshalException.unmarshalException(e);
                            }
                        }

                    }
                }
            }
        }

        if(null != parentRecord) {
            reset();
        }

        // Set XML Location if applicable
        if (xmlLocation != null && ((Descriptor) xmlDescriptor).getLocationAccessor() != null) {
            ((Descriptor) xmlDescriptor).getLocationAccessor().setAttributeValueInObject(getCurrentObject(), xmlLocation);
        }
    }

    public void startPrefixMapping(String prefix, String uri) throws SAXException {
        getUnmarshalNamespaceResolver().push(prefix, uri);
        getPrefixesForFragment().put(prefix, uri);
    }

    public void endPrefixMapping(String prefix) throws SAXException {
        getUnmarshalNamespaceResolver().pop(prefix);
    }

    public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
        if (currentObject == null) {
            initializeRecord(atts);
        }

        XPathFragment xPathNodeXPathFragment = xPathNode.getXPathFragment();
        if((null != xPathNodeXPathFragment && xPathNodeXPathFragment.nameIsText()) || xpathNodeIsMixedContent) {
            xpathNodeIsMixedContent = false;
            NodeValue xPathNodeUnmarshalNodeValue = xPathNode.getUnmarshalNodeValue();
            if (null != xPathNodeUnmarshalNodeValue) {
                boolean isIncludedInAttributeGroup = true;
                if(xPathNodeUnmarshalNodeValue.isMappingNodeValue()) {
                    Mapping mapping = ((MappingNodeValue)xPathNodeUnmarshalNodeValue).getMapping();
                    isIncludedInAttributeGroup = this.unmarshalAttributeGroup.containsAttributeInternal(mapping.getAttributeName());
                }
                if(isIncludedInAttributeGroup) {
                    xPathNodeUnmarshalNodeValue.endElement(xPathFragment, this);
                    if (xPathNode.getParent() != null) {
                        xPathNode = xPathNode.getParent();
                    }
                }
            }
        }

        // set the root element's local name and namespace prefix and look for
        // schema locations etc.
        if (null == rootElementName  && null == rootElementLocalName && parentRecord == null){
            rootElementLocalName = localName;
            rootElementName = qName;
            rootElementNamespaceUri = namespaceURI;
            schemaLocation = atts.getValue(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, Constants.SCHEMA_LOCATION);
            noNamespaceSchemaLocation = atts.getValue(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, Constants.NO_NS_SCHEMA_LOCATION);
        }

        try {
            if (null != selfRecords) {
                for (int x = 0, selfRecordsSize = selfRecords.size(); x < selfRecordsSize; x++) {
                    UnmarshalRecord selfRecord = selfRecords.get(x);
                    if(selfRecord == null){
                        getFragmentBuilder().startElement(namespaceURI, localName, qName, atts);
                    }else{
                        selfRecord.startElement(namespaceURI, localName, qName, atts);
                    }
                }
            }

            if(unmappedLevel != -1 && unmappedLevel <= levelIndex) {
                levelIndex++;
                return;
            }

            XPathNode node = null;
            if (null != predictedNextXPathNode) {

                XPathFragment xpf = predictedNextXPathNode.getXPathFragment();

                if (null != xpf && xPathNode == predictedNextXPathNode.getParent() && (localName == xpf.getLocalName() || localName.equals(xpf.getLocalName())) && (namespaceURI == xpf.getNamespaceURI() || namespaceURI.equals(xpf.getNamespaceURI())) && null == xpf.getPredicate() && !xpf.containsIndex()) {

                    updateXPathFragment(qName, localName, namespaceURI);
                    node = predictedNextXPathNode;
                }
            }

            if (null == node) {
                node = getNonAttributeXPathNode(namespaceURI, localName, qName, atts);
            }

            if (null == node) {
                NodeValue parentNodeValue = xPathNode.getUnmarshalNodeValue();
                if ((null == xPathNode.getXPathFragment()) && (parentNodeValue != null)) {
                    XPathFragment parentFragment = new XPathFragment();
                    parentFragment.setNamespaceAware(isNamespaceAware());
                    if(namespaceURI != null && namespaceURI.length() == 0){
                        parentFragment.setLocalName(qName);
                        parentFragment.setNamespaceURI(null);
                    } else {
                        parentFragment.setLocalName(localName);
                        parentFragment.setNamespaceURI(namespaceURI);
                    }
                    if (parentNodeValue.startElement(parentFragment, this, atts)) {
                        levelIndex++;
                    } else {
                        // UNMAPPED CONTENT
                        startUnmappedElement(namespaceURI, localName, qName, atts);
                        return;
                    }
                } else {
                    // UNMAPPED CONTENT
                    levelIndex++;
                    startUnmappedElement(namespaceURI, localName, qName, atts);
                    return;
                }
            } else {

                xPathNode = node;
                unmarshalContext.startElement(this);
                levelIndex++;

                if(xmlReader.getMediaType().isApplicationXML()) {
                    String xsiNilValue = atts.getValue(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI, Constants.SCHEMA_NIL_ATTRIBUTE);
                    if (xsiNilValue != null) {
                        setNil(xsiNilValue.equals(Constants.BOOLEAN_STRING_TRUE) || xsiNilValue.equals("1"));
                    }
                }

                if(node.getNullCapableValue() != null){
                    getNullCapableValues().add(node.getNullCapableValue());
                }

                NodeValue nodeValue = node.getUnmarshalNodeValue();
                if (null != nodeValue) {
                    boolean isIncludedInAttributeGroup = true;
                    if(nodeValue.isMappingNodeValue()) {
                        Mapping mapping = ((MappingNodeValue)nodeValue).getMapping();
                        isIncludedInAttributeGroup = this.unmarshalAttributeGroup.containsAttributeInternal(mapping.getAttributeName());
                    }
                    if (!isIncludedInAttributeGroup || !nodeValue.startElement(xPathFragment, this, atts)) {
                        // UNMAPPED CONTENT
                        startUnmappedElement(namespaceURI, localName, qName, atts);
                        return;
                    }
                }

                //Handle Attributes
                if(xPathNode.getAttributeChildren() != null || xPathNode.getAnyAttributeNodeValue() != null || selfRecords != null) {
                    for (int i = 0, size=atts.getLength(); i < size; i++) {
                        String attNamespace = atts.getURI(i);
                        String attLocalName = atts.getLocalName(i);
                        String value = atts.getValue(i);
                        NodeValue attributeNodeValue = null;

                        // Some parsers don't set the URI/local name for namespace
                        // attributes
                        if ((attLocalName == null) || (attLocalName.length() == 0)) {
                            String qname = atts.getQName(i);
                            if (qname != null) {
                                int qnameLength = qname.length();
                                if (qnameLength > 0) {
                                    int idx = qname.indexOf(Constants.COLON);
                                    if (idx > 0) {
                                        attLocalName = qname.substring(idx + 1, qnameLength);
                                        String attPrefix = qname.substring(0, idx);
                                        if (attPrefix.equals(javax.xml.XMLConstants.XMLNS_ATTRIBUTE)) {
                                            attNamespace = javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
                                        }
                                    } else {
                                        attLocalName = qname;
                                        if (attLocalName.equals(javax.xml.XMLConstants.XMLNS_ATTRIBUTE)) {
                                            attNamespace = javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
                                        }
                                    }
                                }
                            }
                        }

                        //Look for any Self-Mapping nodes that may want this attribute.
                        if (this.selfRecords != null) {
                            for (int j = 0; j < selfRecords.size(); j++) {
                                UnmarshalRecord nestedRecord = selfRecords.get(j);
                                if(nestedRecord != null){
                                    attributeNodeValue = nestedRecord.getAttributeChildNodeValue(attNamespace, attLocalName);
                                    if (attributeNodeValue != null) {
                                        attributeNodeValue.attribute(nestedRecord, attNamespace, attLocalName, value);
                                    }
                                }
                            }
                        }
                        if (attributeNodeValue == null) {
                            attributeNodeValue = this.getAttributeChildNodeValue(attNamespace, attLocalName);

                            try {
                                if (attributeNodeValue != null) {
                                    if(attributeNodeValue.isMappingNodeValue()) {
                                        Mapping mapping = ((MappingNodeValue)attributeNodeValue).getMapping();
                                        if(!unmarshalAttributeGroup.containsAttributeInternal(mapping.getAttributeName())) {
                                            continue;
                                        }
                                    }
                                    attributeNodeValue.attribute(this, attNamespace, attLocalName, value);
                                } else {
                                    if (xPathNode.getAnyAttributeNodeValue() != null) {
                                        xPathNode.getAnyAttributeNodeValue().attribute(this, attNamespace, attLocalName, value);
                                    }
                                }
                            } catch(EclipseLinkException e) {
                                if ((null == xmlReader) || (null == xmlReader.getErrorHandler())) {
                                    throw e;
                                } else {
                                    SAXParseException saxParseException = new SAXParseException(e.getLocalizedMessage(), getDocumentLocator(), e);
                                    xmlReader.getErrorHandler().warning(saxParseException);
                                }
                            }
                        }
                    }
                }
            }
            if(prefixesForFragment != null){
                this.prefixesForFragment.clear();
            }
        } catch (EclipseLinkException e) {
            if ((null == xmlReader) || (null == xmlReader.getErrorHandler())) {
                throw e;
            } else {
                SAXParseException saxParseException = new SAXParseException(e.getLocalizedMessage(), getDocumentLocator(), e);
                xmlReader.getErrorHandler().error(saxParseException);
            }
        }
    }

    private void updateXPathFragment(String qName, String localName, String namespaceURI) {
        if (namespaceURI != null && namespaceURI.length() == 0) {
            xPathFragment.setLocalName(qName);
            xPathFragment.setNamespaceURI(null);
        } else {
            xPathFragment.setLocalName(localName);
            xPathFragment.setNamespaceURI(namespaceURI);
        }
    }

    public void startUnmappedElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
        if(xmlReader.getMediaType().isApplicationXML() && null == selfRecords && !isSelfRecord) {
            ErrorHandler errorHandler = xmlReader.getErrorHandler();
            // Bug 452584 - check if a warning exception should be generated when an unmapped element is encountered
            if(null != errorHandler && unmarshaller.shouldWarnOnUnmappedElement()) {
                StringBuilder messageBuilder = new StringBuilder("unexpected element (uri:\"");
                if(null != namespaceURI) {
                    messageBuilder.append(namespaceURI);
                }
                messageBuilder.append("\", local:\"");
                messageBuilder.append(localName);
                messageBuilder.append("\"). Expected elements are ");
                List<XPathNode> nonAttributeChildren = xPathNode.getNonAttributeChildren();
                if(nonAttributeChildren == null || nonAttributeChildren.size() == 0) {
                    messageBuilder.append("(none)");
                } else {
                    for(int x=0, size=nonAttributeChildren.size(); x<size; x++) {
                        XPathFragment nonAttributeChildXPathFragment = nonAttributeChildren.get(x).getXPathFragment();
                        messageBuilder.append("<{");
                        String nonAttributeChildXPathFragmentNamespaceURI = nonAttributeChildXPathFragment.getNamespaceURI();
                        if(null != nonAttributeChildXPathFragmentNamespaceURI) {
                            messageBuilder.append(nonAttributeChildXPathFragmentNamespaceURI);
                        }
                        messageBuilder.append('}');
                        messageBuilder.append(nonAttributeChildXPathFragment.getLocalName());
                        messageBuilder.append('>');
                        if(x<size-1) {
                            messageBuilder.append(',');
                        }
                    }
                }
                errorHandler.warning(new SAXParseException(messageBuilder.toString(), getDocumentLocator()));
            }
        }
        if ((null != selfRecords) || (null == xmlReader) || isSelfRecord()) {
            if(-1 == unmappedLevel) {
                this.unmappedLevel = this.levelIndex;
            }
            return;
        }
        Class unmappedContentHandlerClass = unmarshaller.getUnmappedContentHandlerClass();
        UnmappedContentHandler unmappedContentHandler;
        if (null == unmappedContentHandlerClass) {
            unmappedContentHandler = DEFAULT_UNMAPPED_CONTENT_HANDLER;
        } else {
            try {
                PrivilegedNewInstanceFromClass privilegedNewInstanceFromClass = new PrivilegedNewInstanceFromClass(unmappedContentHandlerClass);
                unmappedContentHandler = (UnmappedContentHandler)privilegedNewInstanceFromClass.run();
            } catch (ClassCastException e) {
                throw XMLMarshalException.unmappedContentHandlerDoesntImplement(e, unmappedContentHandlerClass.getName());
            } catch (IllegalAccessException e) {
                throw XMLMarshalException.errorInstantiatingUnmappedContentHandler(e, unmappedContentHandlerClass.getName());
            } catch (InstantiationException e) {
                throw XMLMarshalException.errorInstantiatingUnmappedContentHandler(e, unmappedContentHandlerClass.getName());
            }
        }
        UnmappedContentHandlerWrapper unmappedContentHandlerWrapper = new UnmappedContentHandlerWrapper(this, unmappedContentHandler);
        unmappedContentHandlerWrapper.startElement(namespaceURI, localName, qName, atts);
        xmlReader.setContentHandler(unmappedContentHandlerWrapper);
        xmlReader.setLexicalHandler(unmappedContentHandlerWrapper);
    }

    public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
        try {
            if (null != selfRecords) {
                for (int x = 0, selfRecordsSize = selfRecords.size(); x < selfRecordsSize; x++) {
                    UnmarshalRecord selfRecord = selfRecords.get(x);
                    if(selfRecord != null){
                        selfRecord.endElement(namespaceURI, localName, qName);
                    }else{
                        getFragmentBuilder().endSelfElement(namespaceURI, localName, qName);
                    }
                }
            }
            if(-1 != unmappedLevel && unmappedLevel <= levelIndex) {
                if(levelIndex == unmappedLevel) {
                    unmappedLevel = -1;
                }
                levelIndex--;
                return;
            }
            NodeValue unmarshalNodeValue = xPathNode.getUnmarshalNodeValue();
            if (null != unmarshalNodeValue) {
                boolean isIncludedInAttributeGroup = true;
                if(unmarshalNodeValue.isMappingNodeValue()) {
                    Mapping mapping = ((MappingNodeValue)unmarshalNodeValue).getMapping();
                    isIncludedInAttributeGroup = this.unmarshalAttributeGroup.containsAttributeInternal(mapping.getAttributeName());
                }
                try {
                    if(isIncludedInAttributeGroup) {
                        unmarshalNodeValue.endElement(xPathFragment, this);
                    } else {
                        resetStringBuffer();
                    }

                } catch(EclipseLinkException e) {
            if ((null == xmlReader) || (null == xmlReader.getErrorHandler())) {
                        throw e;
                    } else {
                        SAXParseException saxParseException = new SAXParseException(e.getLocalizedMessage(), getDocumentLocator(), e);
                        xmlReader.getErrorHandler().warning(saxParseException);
                    }
                }
            } else {
                XPathNode textNode = xPathNode.getTextNode();

                if (null != textNode && getStringBuffer().length() == 0) {
                    NodeValue textNodeUnmarshalNodeValue = textNode.getUnmarshalNodeValue();
                    if(textNode.isWhitespaceAware()){
                        if (textNodeUnmarshalNodeValue.isMappingNodeValue()) {
                            Mapping mapping = ((MappingNodeValue)textNodeUnmarshalNodeValue).getMapping();
                            if(mapping.isAbstractDirectMapping() && isNil()) {
                                Object nullValue = ((DirectMapping)mapping).getNullValue();
                                if(!(Constants.EMPTY_STRING.equals(nullValue))) {
                                    setAttributeValue(null, mapping);
                                    this.removeNullCapableValue((NullCapableValue)textNodeUnmarshalNodeValue);
                                }
                            } else {
                                textNodeUnmarshalNodeValue.endElement(xPathFragment, this);
                            }
                            setNil(false);
                        }
                    }else{

                       //This means empty tag
                       if(textNodeUnmarshalNodeValue.isMappingNodeValue()) {
                           Mapping mapping = ((MappingNodeValue)textNodeUnmarshalNodeValue).getMapping();
                           if(mapping.isAbstractDirectMapping() && isNil()) {
                               Object nullValue = ((DirectMapping) mapping).getNullValue();
                               if (!(Constants.EMPTY_STRING.equals(nullValue))) {
                                   setAttributeValue(null, mapping);
                               }
                           }
                           if(mapping.isAbstractDirectMapping() && !isNil() && ((DirectMapping)mapping).getNullPolicy().isNullRepresentedByXsiNil()){
                               removeNullCapableValue((NullCapableValue)textNodeUnmarshalNodeValue);
                           }
                       }
                    }
                }
            }
            XPathFragment xPathFragment = xPathNode.getXPathFragment();
            if((null != xPathFragment && xPathFragment.nameIsText()) || (xpathNodeIsMixedContent && xPathNode.getParent() != null)) {
                xPathNode = xPathNode.getParent();
            }

            NodeValue xPathNodeUnmarshalNodeValue = xPathNode.getUnmarshalNodeValue();
            if (null != xPathNodeUnmarshalNodeValue && xPathNodeUnmarshalNodeValue.isContainerValue()) {
                 predictedNextXPathNode = xPathNode;
            } else {
                predictedNextXPathNode = xPathNode.getNextNode();
            }

            if (null != xPathNode.getParent()) {
                xPathNode = xPathNode.getParent();
            }

            xpathNodeIsMixedContent = false;
            unmarshalContext.endElement(this);

            typeQName = null;
            levelIndex--;
            if(isNil() && levelIndex > 0) {
                setNil(false);
            }
            if ((0 == levelIndex) && (null !=parentRecord) && !isSelfRecord()) {
                endDocument();
                // don't endElement on, or pass control to, a 'self' parent
                UnmarshalRecord pRec = parentRecord;
                while (pRec.isSelfRecord()) {
                    pRec = pRec.getParentRecord();
                }
                pRec.endElement(namespaceURI, localName, qName);
                xmlReader.setContentHandler(pRec);
                xmlReader.setLexicalHandler(pRec);
            }

       } catch (EclipseLinkException e) {
            if ((null == xmlReader) || (null == xmlReader.getErrorHandler())) {
                throw e;
            } else {
                Locator locator = xmlReader.getLocator();
                SAXParseException saxParseException = new SAXParseException(null, getDocumentLocator(), e);
                xmlReader.getErrorHandler().warning(saxParseException);
            }
        }
    }

    public void endUnmappedElement(String namespaceURI, String localName, String qName) throws SAXException {
        typeQName = null;
        levelIndex--;
        if ((0 == levelIndex) && (null != parentRecord) && !isSelfRecord()) {
            endDocument();
            // don't endElement on, or pass control to, a 'self' parent
            UnmarshalRecord pRec = parentRecord;
            while (pRec.isSelfRecord()) {
                pRec = pRec.getParentRecord();
            }
            pRec.endElement(namespaceURI, localName, qName);
            xmlReader.setContentHandler(pRec);
            xmlReader.setLexicalHandler(pRec);
        }
        setNil(false); // null unmapped element processed. We have to reset nil status
    }

    public void characters(char[] ch, int start, int length) throws SAXException {
    if(currentObject == null){
        return;
    }
        try {
            int strBufferInitialLength = -1;
            if (null != selfRecords) {
                strBufferInitialLength = getStringBuffer().length();
                for (int x = 0, selfRecordsSize = selfRecords.size(); x < selfRecordsSize; x++) {
                    UnmarshalRecord selfRecord = selfRecords.get(x);
                    if(selfRecord != null){
                        selfRecord.characters(ch, start, length);
                    } else {
            getFragmentBuilder().characters(ch, start, length);
                    }
                }
            }
            if(-1 != unmappedLevel && unmappedLevel <= levelIndex) {
                return;
            }
            XPathNode textNode = xPathNode.getTextNode();
            if (null == textNode) {
                textNode = xPathNode.getAnyNode();
                if (textNode != null) {
                    xpathNodeIsMixedContent = true;
                    this.xPathFragment.setLocalName(null);
                    this.xPathFragment.setNamespaceURI(null);
                    if (0 == length) {
                        return;
                    }
                }
            }
            if (null != textNode) {
                if(textNode.getUnmarshalNodeValue().isMixedContentNodeValue()) {
                    String tmpString = new String(ch, start, length);
                    if (!textNode.isWhitespaceAware() && tmpString.trim().length() == 0) {
                        return;
                    }
                }
                xPathNode = textNode;
                unmarshalContext.characters(this);
            }

            NodeValue unmarshalNodeValue = xPathNode.getUnmarshalNodeValue();
            if (null != unmarshalNodeValue && !unmarshalNodeValue.isWrapperNodeValue()) {
                if(strBufferInitialLength == -1) {
                    getStringBuffer().append(ch, start, length);
                } else {
                    StrBuffer strBuffer = getStringBuffer();
                    if(strBufferInitialLength == strBuffer.length()) {
                        strBuffer.append(ch, start, length);
                    }
                }
            }
        } catch (EclipseLinkException e) {
            if (null == xmlReader.getErrorHandler()) {
                throw e;
            } else {
                SAXParseException saxParseException = new SAXParseException(null, getDocumentLocator(), e);
                xmlReader.getErrorHandler().error(saxParseException);
            }
        }
    }

    public void characters(CharSequence characters) throws SAXException {
        if(null != characters) {
            String string = characters.toString();
            characters(string.toCharArray(), 0, string.length());
        }
    }

    public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
    }

    public void processingInstruction(String target, String data) throws SAXException {
    }

    public void skippedEntity(String name) throws SAXException {
    }

    /**
     * INTERNAL:
     */
    public XPathNode getNonAttributeXPathNode(String namespaceURI, String localName, String qName, Attributes attributes) {
        if (0 == levelIndex) {
            return xPathNode;
        }
        updateXPathFragment(qName, localName, namespaceURI);

        Map<XPathFragment, XPathNode> nonAttributeChildrenMap = xPathNode.getNonAttributeChildrenMap();

        if (null != nonAttributeChildrenMap) {
            XPathNode resultNode;

            if (unmarshaller.isCaseInsensitive()){
                resultNode = getNodeFromLookupTable(nonAttributeChildrenMap, false);
            } else {
                resultNode = nonAttributeChildrenMap.get(xPathFragment);
            }

            XPathNode nonPredicateNode = null;
            if(resultNode != null && resultNode.hasPredicateSiblings()) {
                nonPredicateNode = resultNode;
                resultNode = null;
            }
            if (null == resultNode) {
                // POSITIONAL MAPPING
                int newIndex;
                if (null == this.indexMap) {
                    this.indexMap = new HashMap();
                    newIndex = 1;
                } else {
                    Integer oldIndex = indexMap.get(xPathFragment);
                    if (null == oldIndex) {
                        newIndex = 1;
                    } else {
                        newIndex = oldIndex.intValue() + 1;
                    }
                }
                indexMap.put(xPathFragment, newIndex);
                XPathFragment predicateFragment = new XPathFragment();
                predicateFragment.setNamespaceAware(isNamespaceAware());
                predicateFragment.setNamespaceURI(xPathFragment.getNamespaceURI());
                predicateFragment.setLocalName(xPathFragment.getLocalName());
                predicateFragment.setIndexValue(newIndex);
                resultNode = nonAttributeChildrenMap.get(predicateFragment);
                if (null == resultNode) {
                    predicateFragment.setIndexValue(-1);
                    if(attributes != null){
                        for(int x = 0, length = attributes.getLength(); x<length; x++) {
                            XPathFragment conditionFragment = new XPathFragment();
                            conditionFragment.setLocalName(attributes.getLocalName(x));
                            conditionFragment.setNamespaceURI(attributes.getURI(x));
                            conditionFragment.setAttribute(true);
                            XPathPredicate condition = new XPathPredicate(conditionFragment, attributes.getValue(x));
                            predicateFragment.setPredicate(condition);
                            resultNode = nonAttributeChildrenMap.get(predicateFragment);
                            if(null != resultNode) {
                                break;
                            }
                        }
                    }
                    //if json, check for text wrapper before handing off to the any
                    if(null == resultNode && xPathNode.getTextNode() != null){
                        XPathFragment textWrapperFragment = getTextWrapperFragment();
                        if(textWrapperFragment != null && localName.equals(textWrapperFragment.getLocalName())){
                           resultNode = xPathNode.getTextNode();
                        }
                    }
                    if(null == resultNode && null == nonPredicateNode) {
                        // ANY MAPPING
                        resultNode = xPathNode.getAnyNode();
                    }
                }
            }
            if(resultNode == null && nonPredicateNode != null) {
                return nonPredicateNode;
            }
            return resultNode;
        }
        return null;
    }

    public String resolveNamespacePrefix(String prefix) {
        String namespaceURI = getUnmarshalNamespaceResolver().getNamespaceURI(prefix);
        if(null == namespaceURI && null != parentRecord) {
            namespaceURI = parentRecord.resolveNamespacePrefix(prefix);
        }
        return namespaceURI;
    }

    public String resolveNamespaceUri(String uri) {
        String prefix = getUnmarshalNamespaceResolver().getPrefix(uri);
        if (null == prefix) {
            if (null != parentRecord) {
                prefix = parentRecord.resolveNamespaceUri(uri);
            }
        }
        return prefix;
    }

    public NodeValue getSelfNodeValueForAttribute(String namespace, String localName) {
        if (this.selfRecords != null) {
            for (int i = 0, selfRecordsSize = selfRecords.size(); i < selfRecordsSize; i++) {
                UnmarshalRecord nestedRecord = selfRecords.get(i);
                if(nestedRecord != null){
                    NodeValue node = nestedRecord.getAttributeChildNodeValue(namespace, localName);
                    if (node != null) {
                        return node;
                    }
                }
            }
        }
        return null;
    }

    public NodeValue getAttributeChildNodeValue(String namespace, String localName) {
        Map<XPathFragment, XPathNode> attributeChildrenMap = xPathNode.getAttributeChildrenMap();
        if (attributeChildrenMap != null) {
            XPathNode resultNode;
            xPathFragment.setLocalName(localName);
            xPathFragment.setNamespaceURI(namespace);

            if (unmarshaller.isCaseInsensitive()){
                resultNode = getNodeFromLookupTable(attributeChildrenMap, true);
            } else {
                resultNode = attributeChildrenMap.get(xPathFragment);
            }

            if (resultNode != null) {
                return resultNode.getUnmarshalNodeValue();
            }
        }
        return null;
    }

    /**
     * INTERNAL:
     * Retrieves the XPathNode by searching in the auxiliary case insensitive lookup table.
     *
     * @param childrenMap Original Map for construction of the auxiliary table.
     * @param isAttribute Determine if searching for an element or an attribute.
     * @return XPathNode object reference, which is also present in the original children map.
     * @since 2.6.0
     */
    private XPathNode getNodeFromLookupTable(Map<XPathFragment, XPathNode> childrenMap, boolean isAttribute) {
        Map<String, XPathNode> lookupTable = xPathNode.getChildrenLookupTable(isAttribute);

        if(!xPathNode.isChildrenLookupTableFilled(isAttribute)){
            this.fillLookupTable(childrenMap, lookupTable);
            xPathNode.setChildrenLookupTableFilled(isAttribute);
        }

        String lowerCaseFragment = xPathFragment.getLocalName().toLowerCase();
        if (!xPathFragment.getChildrenCollisionSet(isAttribute).add(lowerCaseFragment))
            handleCollision(lowerCaseFragment, false);
        return lookupTable.get(lowerCaseFragment);
    }

    /**
     * INTERNAL:
     * Creates an auxiliary lookup table containing lower-cased localNames of XPathFragments.
     *
     * Does NOT pass the Turkey test.
     *
     * For future development: Handle ISO-8859-9 encoding.
     * if (encoding.equals("ISO-8859-9")) {
     *      String auxLocalName = entry.getKey().getLocalName().toLowerCase(Locale.forLanguageTag("tr-TR"));
     * }
     *
     * @param childrenMap Table from which the data is acquired.
     * @param lookupTable Table to which the lower-cased data is stored.
     * @since 2.6.0
     */
    private void fillLookupTable(Map<XPathFragment, XPathNode> childrenMap, Map<String, XPathNode> lookupTable) {
        String lookupName;
        for (Map.Entry<XPathFragment, XPathNode> entry : childrenMap.entrySet()) {
            lookupName = entry.getKey().getLocalName().toLowerCase();
            if (lookupTable.put(lookupName, entry.getValue()) != null){
                handleCollision(lookupName, true);
            }
        }
    }

    /**
     * INTERNAL:
     * Handles collisions, i.e. fragments or fields with the same name, different case.
     *
     * @param lookupName Lookup variant of the localName.
     * @param onXPathNode true - the collision occurred on XPathNode (case for Java fields),
     *                    false - the collision occurred on XPathFragment (case for XML elements/attributes).
     * @since 2.6.0
     */
    private void handleCollision(String lookupName, boolean onXPathNode) {
        StringBuilder sb = new StringBuilder()
                .append(">\nUnmarshalRecordImpl.handleCollision() -->\tCOLLISION on ")
                .append(
                        onXPathNode
                        ? "XPathNode fields by case insensitive localName \""
                        : "XPathFragments by case insensitive localName \""
                ).append(lookupName).append("\".");

//        session.setLogLevel(SessionLog.WARNING); // for debugging
        ((AbstractSession) session).logMessage(CommandProcessor.LOG_WARNING, sb.toString());
    }

    public SAXFragmentBuilder getFragmentBuilder() {
        if(this.fragmentBuilder == null){
        fragmentBuilder = new SAXFragmentBuilder(this);
        }
        return fragmentBuilder;
    }

    public void setFragmentBuilder(SAXFragmentBuilder builder) {
        this.fragmentBuilder = builder;
    }

    public void resetStringBuffer() {
        this.getStringBuffer().reset();
        this.isBufferCDATA = false;
    }

    public boolean isBufferCDATA() {
        return isBufferCDATA;
    }

    public void comment(char[] data, int start, int length) {
    }

    public void startCDATA() {
        if (null != xPathNode && xPathNode.getUnmarshalNodeValue() != null) {
            this.isBufferCDATA = true;
        }
    }

    public void endCDATA() {
    }

    public void startEntity(String entity) {
    }

    public void endEntity(String entity) {
    }

    public void startDTD(String a, String b, String c) {
    }

    public void endDTD() {
    }

    /**
     * Sets the flag which indicates if this UnmarshalRecord
     * represents a 'self' record
     *
     * @param isSelfRecord true if this record represents
     * 'self', false otherwise
     */
    public void setSelfRecord(boolean isSelfRecord) {
        this.isSelfRecord = isSelfRecord;
    }

    /**
     * Indicates if this UnmarshalRecord represents a 'self' record
     *
     * @return true if this record represents 'self', false otherwise
     */
    public boolean isSelfRecord() {
        return isSelfRecord;
    }

    public int getLevelIndex() {
        return levelIndex;
    }

    /**
     * INTERNAL
     * @since EclipseLink 2.5.0
     */
    public void setAttributeValue(Object value, Mapping mapping) {
        this.unmarshalContext.setAttributeValue(this, value, mapping);
    }

    public void addAttributeValue(ContainerValue containerValue, Object value) {
        this.unmarshalContext.addAttributeValue(this, containerValue, value);
    }

    public void addAttributeValue(ContainerValue containerValue, Object value, Object collection) {
        this.unmarshalContext.addAttributeValue(this, containerValue, value, collection);
    }

    public void setAttributeValueNull(ContainerValue containerValue) {
        this.unmarshalContext.setAttributeValue(this, null, containerValue.getMapping());
        int containerIndex = containerValue.getIndex();
        populatedContainerValues.remove(containerValue);
        containerInstances[containerIndex] = null;
    }

    public void reference(Reference reference) {
        this.unmarshalContext.reference(reference);
    }

    public void unmappedContent() {
        if(this.xPathNode.getParent() != null) {
            xPathNode = xPathNode.getParent();
        }
        this.unmarshalContext.unmappedContent(this);
    }

    public UnmarshalRecord getChildUnmarshalRecord(ObjectBuilder treeObjectBuilder) {
    if(childRecord != null && !childRecord.isSelfRecord()){
        childRecord.initialize(treeObjectBuilder);
            childRecord.setParentRecord(this);
            return childRecord;
    }else{
        childRecord = new UnmarshalRecordImpl(treeObjectBuilder, referenceResolver);
        childRecord.setSession(session);
            childRecord.setUnmarshaller(unmarshaller);
            childRecord.setTextWrapperFragment(textWrapperFragment);
            childRecord.setXMLReader(this.xmlReader);
            childRecord.setFragmentBuilder(fragmentBuilder);
            childRecord.setUnmarshalNamespaceResolver(unmarshalNamespaceResolver);
            childRecord.setParentRecord(this);
    }
        return childRecord;
    }

    /**
     * INTERNAL:
     */
    public void setUnmarshaller(Unmarshaller unmarshaller) {
        this.unmarshaller = unmarshaller; //super.setUnmarshaller(unmarshaller);
        if(xPathFragment != null){
        xPathFragment.setNamespaceAware(isNamespaceAware());
        }
    }

    /**
     * INTERNAL
     * Returns a Map of any prefix mappings that were made before the most recent start
     * element event. This Map is used so the prefix mappings can be passed along to a
     * fragment builder in the event that the element in question is going to be unmarshalled
     * as a Node.
     */
    public Map<String, String> getPrefixesForFragment() {
    if(prefixesForFragment == null){
        prefixesForFragment = new HashMap<String, String>();
    }

        return prefixesForFragment;
    }


    public char getNamespaceSeparator(){
    return xmlReader.getNamespaceSeparator();
    }

    public void setTextWrapperFragment(XPathFragment newTextWrapperFragment) {
    textWrapperFragment = newTextWrapperFragment;
    }

    public XPathFragment getTextWrapperFragment() {
    if(xmlReader.getMediaType() .isApplicationJSON()){
        if(textWrapperFragment == null){
            textWrapperFragment = new XPathFragment();
            textWrapperFragment.setLocalName(unmarshaller.getValueWrapper());
            textWrapperFragment.setNamespaceAware(isNamespaceAware());
            textWrapperFragment.setNamespaceSeparator(getNamespaceSeparator());
        }
        return textWrapperFragment;
    }
    return null;
    }

    /**
     * INTERNAL:
     * If the UnmarshalRecord has a ReferenceResolver, tell it to resolve its
     * references.
     * @since EclipseLink 2.5.0
     */
    public void resolveReferences(CoreAbstractSession abstractSession, IDResolver idResolver) {
        if(null != referenceResolver) {
            referenceResolver.resolveReferences(abstractSession, idResolver, unmarshaller.getErrorHandler());
        }
    }

    /**
     * INTERNAL:
     * @since EclipseLink 2.5.0
     */
    public Root createRoot() {
        return unmarshaller.createRoot();
    }

    /**
     * WHAT ABOUT THESE?
     */

    private Field convertToXMLField(CoreField key) {
        return (Field) key;
    }

    @Override
    public CoreAbstractSession getSession() {
        return session;
    }

    @Override
    public Unmarshaller getUnmarshaller() {
        return unmarshaller;
    }

    @Override
    public boolean isNamespaceAware() {
        return namespaceAware;
    }

    @Override
    public Object getCurrentObject() {
        return currentObject;
    }

    @Override
    public XPathQName getLeafElementType() {
        return leafElementType;
    }

    @Override
    public void setCurrentObject(Object object) {
        this.currentObject = object;
    }

    @Override
    public void setLeafElementType(QName type) {
        if (type != null) {
            setLeafElementType(new XPathQName(type, isNamespaceAware()));
        } else {
            setLeafElementType((XPathQName) null);
        }
    }

    public void setLeafElementType(XPathQName type) {
        leafElementType = type;
    }

    @Override
    public void setSession(CoreAbstractSession session) {
        this.session = session;
    }

    public CoreAttributeGroup getUnmarshalAttributeGroup() {
        return unmarshalAttributeGroup;
    }

    public void setUnmarshalAttributeGroup(CoreAttributeGroup unmarshalAttributeGroup) {
        this.unmarshalAttributeGroup = unmarshalAttributeGroup;
    }

    /**
     * @since EclipseLink 2.6.0
     */
    @Override
    public ConversionManager getConversionManager() {
        if(null == conversionManager) {
            conversionManager = (ConversionManager) session.getDatasourcePlatform().getConversionManager();
        }
        return conversionManager;
    }

}
