/*
 * Copyright (c) 1998, 2018 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
package org.eclipse.persistence.sdo;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;

import org.eclipse.persistence.exceptions.SDOException;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.oxm.XMLConversionManager;
import org.eclipse.persistence.oxm.XMLConstants;
import org.eclipse.persistence.oxm.XMLRoot;
import org.eclipse.persistence.oxm.sequenced.SequencedObject;
import org.eclipse.persistence.oxm.sequenced.Setting;
import org.eclipse.persistence.sdo.helper.ListWrapper;
import org.eclipse.persistence.sdo.helper.SDODataHelper;
import org.eclipse.persistence.sdo.helper.SDOTypeHelper;
import org.eclipse.persistence.sdo.helper.XPathEngine;

import commonj.sdo.ChangeSummary;
import commonj.sdo.DataGraph;
import commonj.sdo.DataObject;
import commonj.sdo.Property;
import commonj.sdo.Sequence;
import commonj.sdo.Type;
import commonj.sdo.helper.DataHelper;
import commonj.sdo.helper.HelperContext;

public class SDODataObject implements DataObject, SequencedObject {

    /**
     * Development Guidelines:
     * (1) All no-argument get and single argument set functions that are outside the DataObject interface
     * must be proceeded with a _ underscore in the form _getValue()/_setValue(value).
     * The reason for this is to avoid naming collisions with generated classes that extend SDODataObject
     * where the generated get/set methods for POJO fields would collide with the same function here.
     */
    /** The Type that this DataObject represents */
    private SDOType type;

    // t20070714_bc4j: Add pluggable DO support for BC4J using currentValueStore
    private SDODataObject container;

    /**
     * The (currentValueStore) will maintain the current state of our model
     *   after logged changes - it is a shallow copy of the original, progressively becoming deeper with changes.
     */
    private ValueStore currentValueStore;
    private List openContentProperties;
    private List openContentPropertiesAttributes;
    private Map openContentAliasNames;
    private String containmentPropertyName;
    private SDOChangeSummary changeSummary;
    private List<SDOProperty> instanceProperties;
    private String sdoRef;
    private SDOSequence sequence;
    private DataGraph dataGraph;

    /** hold the current context containing all helpers so that we can preserve inter-helper relationships */
    private HelperContext aHelperContext;

    /**Unique hash ID of this Externalizable class - not required at this point because we serialize the xml representation */

    @SuppressWarnings("unused")
    private String text;

    //static final long serialVersionUID = 5930094058760264198L;

    /**
     * INTERNAL:
     * Private constructor.
     * Use {@link org.eclipse.persistence.sdo.helper.delegates.SDODataFactoryDelegate#create(commonj.sdo.Type) SDODataFactoryDelegate.create(Type)} instead
     */
    public SDODataObject() {
        // Implementors should not be calling this constructor directly - please use SDODataFactory
        // however if it is called we will initialize the default implementation of the currentValueStore Map
        // initialize Map Implementation
        // set currentValueStore Map implementation (replace any that was set in the constructor in newInstance() above)
        _setCurrentValueStore(new DefaultValueStore());
    }

    /**
     * INTERNAL:
     * Set the HelperContext that will be associated with this DataObject.
     * @param aContext
     */
    public void _setHelperContext(HelperContext aContext) {
        aHelperContext = aContext;
        // reset the HelperContext on ChangeSummary
        if (changeSummary != null) {
            changeSummary.setHelperContext(aHelperContext);
        }
    }

    @Override
    public Object get(String path) {// path like "a/b/c"
        try {
            return XPathEngine.getInstance().get(path, this);
        } catch (Exception e) {
            // Swallow exception and return null, as per SDO 2.1 spec
            return null;
        }
    }

    @Override
    public void set(String path, Object value) throws ClassCastException, UnsupportedOperationException, IllegalArgumentException {
        XPathEngine.getInstance().set(path, value, this, false);
    }

    @Override
    public boolean isSet(String path) {
        return XPathEngine.getInstance().isSet(path, this);
    }

    @Override
    public void unset(String path) {
        XPathEngine.getInstance().unset(path, this);
    }

    @Override
    public boolean getBoolean(String path) throws ClassCastException {
        try {
            Boolean value = (Boolean)XPathEngine.getInstance().convertObjectToValueByPath(path, Boolean.class, this);

            if (value == null) {
                return false;
            }
            return value.booleanValue();
        } catch (Exception e) {
            // Swallow exception and return null, as per SDO 2.1 spec
            return false;
        }
    }

    @Override
    public byte getByte(String path) {
        try {
            Byte value = (Byte)XPathEngine.getInstance().convertObjectToValueByPath(path, Byte.class, this);
            if (value == null) {
                return 0;
            }
            return value.byteValue();
        } catch (Exception e) {
            // Swallow exception and return null, as per SDO 2.1 spec
            return 0;
        }
    }

    @Override
    public char getChar(String path) {
        try {
            Character value = (Character)XPathEngine.getInstance().convertObjectToValueByPath(path, Character.class, this);
            if (value == null) {
                return '\u0000';
            }
            return value.charValue();
        } catch (Exception e) {
            // Swallow exception and return null, as per SDO 2.1 spec
            return '\u0000';
        }
    }

    @Override
    public double getDouble(String path) {
        try {
            Double value = (Double)XPathEngine.getInstance().convertObjectToValueByPath(path, Double.class, this);
            if (value == null) {
                return 0.0d;
            }
            return value.doubleValue();
        } catch (Exception e) {
            // Swallow exception and return null, as per SDO 2.1 spec
            return 0.0d;
        }
    }

    @Override
    public float getFloat(String path) {
        try {
            Float value = (Float)XPathEngine.getInstance().convertObjectToValueByPath(path, Float.class, this);
            if (value == null) {
                return 0.0f;
            }
            return value.floatValue();
        } catch (Exception e) {
            // Swallow exception and return null, as per SDO 2.1 spec
            return 0.0f;
        }
    }

    @Override
    public int getInt(String path) {
        try {
            Integer value = (Integer)XPathEngine.getInstance().convertObjectToValueByPath(path, Integer.class, this);
            if (value == null) {
                return 0;
            }
            return value.intValue();
        } catch (Exception e) {
            // Swallow exception and return null, as per SDO 2.1 spec
            return 0;
        }
    }

    @Override
    public long getLong(String path) {
        try {
            Long value = (Long)XPathEngine.getInstance().convertObjectToValueByPath(path, Long.class, this);
            if (value == null) {
                return 0L;
            }
            return value.longValue();
        } catch (Exception e) {
            // Swallow exception and return null, as per SDO 2.1 spec
            return 0L;
        }
    }

    @Override
    public short getShort(String path) {
        try {
            Short value = (Short)XPathEngine.getInstance().convertObjectToValueByPath(path, Short.class, this);
            if (value == null) {
                return 0;
            }
            return value.shortValue();
        } catch (Exception e) {
            // Swallow exception and return null, as per SDO 2.1 spec
            return 0;
        }
    }

    @Override
    public byte[] getBytes(String path) {
        try {
            byte[] value = (byte[])XPathEngine.getInstance().convertObjectToValueByPath(path, byte[].class, this);
            return value;
        } catch (Exception e) {
            // Swallow exception and return null, as per SDO 2.1 spec
            return null;
        }
    }

    @Override
    public BigDecimal getBigDecimal(String path) {
        try {
            BigDecimal value = (BigDecimal)XPathEngine.getInstance().convertObjectToValueByPath(path, BigDecimal.class, this);
            return value;
        } catch (Exception e) {
            // Swallow exception and return null, as per SDO 2.1 spec
            return null;
        }
    }

    @Override
    public BigInteger getBigInteger(String path) {
        try {
            BigInteger value = (BigInteger)XPathEngine.getInstance().convertObjectToValueByPath(path, BigInteger.class, this);
            return value;
        } catch (Exception e) {
            // Swallow exception and return null, as per SDO 2.1 spec
            return null;
        }
    }

    @Override
    public SDODataObject getDataObject(String path) throws ClassCastException {
        Object value = get(path);
        if(value instanceof ListWrapper) {
            return (SDODataObject)((ListWrapper)value).get(0);
        }
        return (SDODataObject)value;
    }

    @Override
    public Date getDate(String path) {
        try {
            Date value = (Date)XPathEngine.getInstance().convertObjectToValueByPath(path, Date.class, this);
            return value;
        } catch (Exception e) {
            // Swallow exception and return null, as per SDO 2.1 spec
            return null;
        }
    }

    @Override
    public String getString(String path) {
        try {
            String value = (String)XPathEngine.getInstance().convertObjectToValueByPath(path, String.class, this);
            return value;
        } catch (Exception e) {
            // Swallow exception and return null, as per SDO 2.1 spec
            return null;
        }
    }

    @Override
    public List getList(String path) {
        try {
            return (List)XPathEngine.getInstance().convertObjectToValueByPath(path, List.class, this);
        } catch (Exception e) {
            // Swallow exception and return null, as per SDO 2.1 spec
            return null;
        }
    }

    @Override
    public void setBoolean(String path, boolean value) {
        convertValueAndSet(path, value);
    }

    @Override
    public void setByte(String path, byte value) {
        convertValueAndSet(path, value);
    }

    @Override
    public void setChar(String path, char value) {
        convertValueAndSet(path, value);
    }

    @Override
    public void setDouble(String path, double value) {
        convertValueAndSet(path, value);
    }

    @Override
    public void setFloat(String path, float value) {
        convertValueAndSet(path, value);
    }

    @Override
    public void setInt(String path, int value) {
        convertValueAndSet(path, value);
    }

    @Override
    public void setLong(String path, long value) {
        convertValueAndSet(path, value);
    }

    @Override
    public void setShort(String path, short value) {
        convertValueAndSet(path, value);
    }

    @Override
    public void setBytes(String path, byte[] value) {
        convertValueAndSet(path, value);
    }

    @Override
    public void setBigDecimal(String path, BigDecimal value) {
        convertValueAndSet(path, value);
    }

    @Override
    public void setBigInteger(String path, BigInteger value) {
        convertValueAndSet(path, value);
    }

    @Override
    public void setDataObject(String path, DataObject value) {
        set(path, value);
    }

    @Override
    public void setDate(String path, Date value) {
        convertValueAndSet(path, value);
    }

    @Override
    public void setString(String path, String value) {
        convertValueAndSet(path, value);
    }

    @Override
    public void setList(String path, List value) {
        convertValueAndSet(path, value);
    }

    @Override
    public Object get(int propertyIndex) throws IllegalArgumentException {
        Property p = getInstanceProperty(propertyIndex);
        return get(p);
    }

    @Override
    public void set(int propertyIndex, Object value) {
        try {
            Property p = getInstanceProperty(propertyIndex);
            set(p, value);
        } catch (IndexOutOfBoundsException e) {
            throw new IllegalArgumentException("PropertyIndex invalid.");
        }
    }

    @Override
    public boolean isSet(int propertyIndex) {
        Property p = getInstanceProperty(propertyIndex);
        return isSet(p);
    }

    @Override
    public void unset(int propertyIndex) {
        Property p = getInstanceProperty(propertyIndex);
        unset(p);
    }

    @Override
    public boolean getBoolean(int propertyIndex) throws IllegalArgumentException, ClassCastException {
        Property property = getInstanceProperty(propertyIndex);
        return getBoolean(property);
    }

    @Override
    public byte getByte(int propertyIndex) throws IllegalArgumentException, ClassCastException {
        Property property = getInstanceProperty(propertyIndex);
        return getByte(property);
    }

    @Override
    public char getChar(int propertyIndex) throws IllegalArgumentException, ClassCastException {
        Property property = getInstanceProperty(propertyIndex);
        return getChar(property);
    }

    @Override
    public double getDouble(int propertyIndex) throws IllegalArgumentException, ClassCastException {
        Property property = getInstanceProperty(propertyIndex);
        return getDouble(property);
    }

    @Override
    public float getFloat(int propertyIndex) throws IllegalArgumentException, ClassCastException {
        Property property = getInstanceProperty(propertyIndex);
        return getFloat(property);
    }

    @Override
    public int getInt(int propertyIndex) throws IllegalArgumentException, ClassCastException {
        Property property = getInstanceProperty(propertyIndex);
        return getInt(property);
    }

    @Override
    public long getLong(int propertyIndex) throws IllegalArgumentException, ClassCastException {
        Property property = getInstanceProperty(propertyIndex);
        return getLong(property);
    }

    @Override
    public short getShort(int propertyIndex) throws IllegalArgumentException, ClassCastException {
        Property property = getInstanceProperty(propertyIndex);
        return getShort(property);
    }

    @Override
    public byte[] getBytes(int propertyIndex) throws IllegalArgumentException, ClassCastException {
        Property property = getInstanceProperty(propertyIndex);
        return getBytes(property);
    }

    @Override
    public BigDecimal getBigDecimal(int propertyIndex) throws IllegalArgumentException, ClassCastException {
        Property property = getInstanceProperty(propertyIndex);
        return getBigDecimal(property);
    }

    @Override
    public BigInteger getBigInteger(int propertyIndex) throws IllegalArgumentException, ClassCastException {
        Property property = getInstanceProperty(propertyIndex);
        return getBigInteger(property);
    }

    @Override
    public SDODataObject getDataObject(int propertyIndex) {
        Property property = getInstanceProperty(propertyIndex);
        return getDataObject(property);
    }

    @Override
    public Date getDate(int propertyIndex) throws IllegalArgumentException, ClassCastException {
        Property property = getInstanceProperty(propertyIndex);
        return getDate(property);
    }

    @Override
    public String getString(int propertyIndex) throws IllegalArgumentException, ClassCastException {
        Property property = getInstanceProperty(propertyIndex);
        return getString(property);
    }

    @Override
    public List getList(int propertyIndex) {
        Property property = getInstanceProperty(propertyIndex);
        return getList(property);
    }

    /**
     * @deprecated in SDO 2.1.0.
     */
    @Deprecated
    @Override
    public Sequence getSequence(String path) {
        // get property from path
        Object anObject = get(path);

        if ((null == anObject) || !(anObject instanceof DataObject)) {
            // throw exception because there is no way to get the property from a null object in this context
            throw SDOException.sequenceNotFoundForPath(path);
        } else {
            // we cannot use containmentProperty in the many case as the index is missing - use the DO
            return ((DataObject)anObject).getSequence();
        }
    }

    /**
     * @deprecated in SDO 2.1.0.
     */
    @Deprecated
    @Override
    public Sequence getSequence(int propertyIndex) {
        // get property
        Property aProperty = getInstanceProperty(propertyIndex);
        return getSequencePrivate(aProperty);
    }

    /**
     * @deprecated in SDO 2.1.0.
     */
    @Deprecated
    @Override
    public Sequence getSequence(Property property) {
        return getSequencePrivate(property);
    }

    /**
     * INTERNAL:
     * Return a Sequence object when the conditions of many=false and dataType=false are met.
     * Throw an UnsupportedOperationException in all other cases.
     */
    private Sequence getSequencePrivate(Property aProperty) {
        // we process only complex types that are non-many
        if ((aProperty != null) && ((SDOProperty)aProperty).getType().isSequenced() && isSet(aProperty) &&//
                !((SDOProperty)aProperty).getType().isDataType() && !aProperty.isMany()) {
            return ((DataObject)get(aProperty)).getSequence();
        } else {
            throw SDOException.sequenceNotSupportedForProperty(aProperty.getName());
        }
    }

    @Override
    public SDOSequence getSequence() {
        // sequence object should be null if !sequenced
        return sequence;
    }

    @Override
    public void setBoolean(int propertyIndex, boolean value) {
        convertValueAndSet(propertyIndex, value);
    }

    @Override
    public void setByte(int propertyIndex, byte value) {
        convertValueAndSet(propertyIndex, value);
    }

    @Override
    public void setChar(int propertyIndex, char value) {
        convertValueAndSet(propertyIndex, value);
    }

    @Override
    public void setDouble(int propertyIndex, double value) {
        convertValueAndSet(propertyIndex, value);
    }

    @Override
    public void setFloat(int propertyIndex, float value) {
        convertValueAndSet(propertyIndex, value);
    }

    @Override
    public void setInt(int propertyIndex, int value) {
        convertValueAndSet(propertyIndex, value);
    }

    @Override
    public void setLong(int propertyIndex, long value) {
        convertValueAndSet(propertyIndex, value);
    }

    @Override
    public void setShort(int propertyIndex, short value) {
        convertValueAndSet(propertyIndex, value);
    }

    @Override
    public void setBytes(int propertyIndex, byte[] value) {
        convertValueAndSet(propertyIndex, value);
    }

    @Override
    public void setBigDecimal(int propertyIndex, BigDecimal value) {
        convertValueAndSet(propertyIndex, value);
    }

    @Override
    public void setBigInteger(int propertyIndex, BigInteger value) {
        convertValueAndSet(propertyIndex, value);
    }

    @Override
    public void setDataObject(int propertyIndex, DataObject value) {
        set(propertyIndex, value);
    }

    @Override
    public void setDate(int propertyIndex, Date value) {
        convertValueAndSet(propertyIndex, value);
    }

    @Override
    public void setString(int propertyIndex, String value) {
        convertValueAndSet(propertyIndex, value);
    }

    @Override
    public void setList(int propertyIndex, List value) {
        convertValueAndSet(propertyIndex, value);
    }

    @Override
    public Object get(Property property) throws IllegalArgumentException {
        if (null == property) {// check null property before null type
            throw new IllegalArgumentException("Argument not Supported.");
        }

        if ((null != type) && !type.isOpen() && property.isOpenContent()) {
            throw new IllegalArgumentException("Argument not Supported.");
        }
        if (property.isMany()) {
            return getList(property);
        }

        if (isSet(property)) {
            return getPropertyInternal(property);
        }

        // return the default or a cached pseudo default Object for an unset property
        // JIRA-253: We are wrapping primitive numerics with a wrapper Object
        return property.getDefault();
    }

    /**
     * INTERNAL:
     * Create a dynamic open content property if no property exists for (name).
     * @param name
     * @param value
     * @return Property
     * @throws UnsupportedOperationException
     * @throws IllegalArgumentException
     */
    public Property defineOpenContentProperty(String name, Object value) throws UnsupportedOperationException, IllegalArgumentException {
        if (value == null) {
            return null;
        }
        DataObject propertyDO = aHelperContext.getDataFactory().create(SDOConstants.SDO_URL, SDOConstants.PROPERTY);

        propertyDO.set("name", name);
        Type sdotype = null;
        boolean isMany = false;
        boolean isContainment = false;
        Class valueClass = value.getClass();

        if (value instanceof Collection) {
            if (((Collection)value).size() > 0) {
                Object firstObject = ((Collection)value).iterator().next();
                if (firstObject != null) {
                    valueClass = firstObject.getClass();
                    if (firstObject instanceof DataObject) {
                        if (((DataObject)firstObject).getContainer() == null) {
                            isContainment = true;
                        }
                        sdotype = ((DataObject)firstObject).getType();
                    } else {
                        sdotype = ((SDOTypeHelper)aHelperContext.getTypeHelper()).getTypeForSimpleJavaType(valueClass);
                    }
                } else {
                    return null;
                }
            } else {
                return null;
            }
            isMany = true;
        } else if (value instanceof DataObject) {
            if (((DataObject)value).getContainer() == null) {
                isContainment = true;
            }
            sdotype = ((DataObject)value).getType();
        } else {
            Property xmlElementProperty = aHelperContext.getTypeHelper().getOpenContentProperty(SDOConstants.SDOXML_URL, SDOConstants.XMLELEMENT_PROPERTY_NAME);
            propertyDO.set(xmlElementProperty, Boolean.TRUE);
            sdotype = ((SDOTypeHelper)aHelperContext.getTypeHelper()).getTypeForSimpleJavaType(valueClass);
        }
        propertyDO.set("type", sdotype);

        propertyDO.set("many", isMany);

        propertyDO.set("containment", isContainment);
        return aHelperContext.getTypeHelper().defineOpenContentProperty(null, propertyDO);
    }

    public Property defineOpenContentProperty(String name, Object value, Type sdotype) throws UnsupportedOperationException, IllegalArgumentException {
        if (value == null) {
            return null;
        }
        if(sdotype == null){
          return defineOpenContentProperty(name, value);
        }
        DataObject propertyDO = aHelperContext.getDataFactory().create(SDOConstants.SDO_URL, SDOConstants.PROPERTY);

        propertyDO.set("name", name);
        boolean isMany = false;
        boolean isContainment = false;
        Class valueClass = value.getClass();

        if (value instanceof Collection) {
            if (((Collection)value).size() > 0) {
                Object firstObject = ((Collection)value).iterator().next();
                if (firstObject != null) {
                    valueClass = firstObject.getClass();
                    if (firstObject instanceof DataObject) {
                        if (((DataObject)firstObject).getContainer() == null) {
                            isContainment = true;
                        }
                        sdotype = ((DataObject)firstObject).getType();
                    } else {
                        sdotype = ((SDOTypeHelper)aHelperContext.getTypeHelper()).getTypeForSimpleJavaType(valueClass);
                    }
                }
            }
            isMany = true;
        } else if (value instanceof DataObject) {
            if (((DataObject)value).getContainer() == null) {
                isContainment = true;
            }
            sdotype = ((DataObject)value).getType();
        } else {
            sdotype = ((SDOTypeHelper)aHelperContext.getTypeHelper()).getTypeForSimpleJavaType(valueClass);
        }
        propertyDO.set("type", sdotype);

        propertyDO.set("many", isMany);

        propertyDO.set("containment", isContainment);
        return aHelperContext.getTypeHelper().defineOpenContentProperty(null, propertyDO);
    }

    @Override
    public void set(Property property, Object value) throws UnsupportedOperationException, IllegalArgumentException {
        set((SDOProperty) property, value, true);
    }

    public void setInternal(SDOProperty property, Object value, boolean updateSequence) throws UnsupportedOperationException, IllegalArgumentException {
        if (null == getType()) {
            throw new UnsupportedOperationException("Type is null");
        }

        if (property.isOpenContent() && !getType().isOpen()) {// not open and not in types currentValueStore
            throw new IllegalArgumentException("DataObject " + this + " is not Open for property " + property.getName());
        }

        // Note: get() will call setPropertyInternal() if the list is null = not set yet - we need to propagate the updateSequence flag
        Object oldValue = get(property);
        boolean wasSet = isSet(property);
        if (wasSet && (oldValue == value)) {
            return;
        } else {
            _setModified(true);
        }

        if (property.isMany()) {
            List listValue;
            if (null == value) {
                listValue = new ListWrapper(this, property);
            } else {
                try {
                    listValue = (List) value;
                } catch(ClassCastException e) {
                    throw new IllegalArgumentException("Properties with isMany = true can only be set on list values.");
                }
            }
            if (property.isContainment() || isContainedByDataGraph(property)) {
                for(Object next: listValue) {
                    if(next instanceof SDODataObject) {
                        if (parentContains(next)) {
                            throw new IllegalArgumentException("Circular reference.");
                        }
                    }
                }
            }

            ListWrapper oldValueListWrapper = (ListWrapper) oldValue;
            // 20060529: v33: Move clear() out of ListWrapper.addAll()
            // handle clearing of elements which also calls removeContainment(prop) outside of addAll()
            oldValueListWrapper.clear(updateSequence);
            // handle updateContainment and sequences inside addAll()
            oldValueListWrapper.addAll((Collection)value, updateSequence);// for non-default Pluggable impl this function is not required
        } else {
            if (property.isContainment() || isContainedByDataGraph(property)) {
                if (parentContains(value)) {
                    throw new IllegalArgumentException("Circular reference.");
                }

                // detach the oldValue from this dataObject
                if(null != oldValue) {
                    detach(property, oldValue);
                }

                // sets the new value's container and containment prop to this dataobject, detaches from other owner...not right

                /**
                 * Case: set(do) is actually a move(do) between two CS - detach required
                 * Case: set(do) is actually an add(do) on a single CS - detach not required
                 */
                SDODataObject dataObjectValue = (SDODataObject) value;
                if (dataObjectValue != null) {
                    updateContainment(property, dataObjectValue);
                }
            }

            // process pluggable currentValueStore and set [value] as a property of [this] as well as sequences
            setPropertyInternal(property, value, updateSequence);
        }
        if (getType().isOpen() && property.isOpenContent()) {
            addOpenContentProperty(property);
        }
    }

    /**
     * INTERNAL:
     * Sets the value of the given property of the object to the new value.
     * <p>
     * The use of a false updateSequence flag is internally implemented during an SDOSequence.add() call.
     * Refactor: we need to abstract this function using a type of Command pattern to handle the sequence context.
     * @param property
     * @param value
     * @param updateSequence
     * @throws UnsupportedOperationException
     * @throws IllegalArgumentException
     */
    public void set(SDOProperty property, Object value, boolean updateSequence) throws UnsupportedOperationException, IllegalArgumentException {
        if (null == property) {
            throw new IllegalArgumentException("Illegal Argument.");
        }

        if (property.isReadOnly()) {
            throw new UnsupportedOperationException("Property is Readonly." + property.getName() + "  " + getType().getName());
        }

        // Can't set null on a non-nullable property - implementation specific handling, one of:
        // 1) perform an unset operation
        // 2) throw new UnsupportedOperationException("Property ["+ property.getName() +"] is non-nullable");
        // We will perform an unset op...
        if (value == null && !property.isNullable()) {
            unset(property, false, updateSequence);
        } else {
            setInternal(property, value, updateSequence);
        }
    }

    @Override
    public boolean isSet(Property property) {
        if (null == property) {
            throw SDOException.cannotPerformOperationOnNullArgument("isSet");
        }

        if (property.isMany()) {
            List value = getList(property);
            return !value.isEmpty();
        } else {
            return isSetInternal(property);
        }
    }

    @Override
    public void unset(Property property) {
        if (null == property) {
            throw SDOException.cannotPerformOperationOnNullArgument("unset");
        }
        unset(property, false);
    }

    /**
     * INTERNAL:
     * This function is implemented internally to unset the specified property on this DataObject
     * @param property
     * @param fromDelete
     */
    private void unset(Property property, boolean fromDelete) {
        unset(property, fromDelete, true);
    }

    /**
     * INTERNAL:
     * Unset the specified property on this DataObject.
     * The fromDelete parameter specifies whether we are from a delete or unset/detach operation.
     * The updateSequence parameter is used internally to stop a bidirectional update in the SDOSequence
     * when originally called from this Sequence.
     * @param property
     * @param fromDelete
     * @param updateSequence
     */
    public void unset(Property property, boolean fromDelete, boolean updateSequence) {
        if (null == property) {
            throw SDOException.cannotPerformOperationOnNullArgument("unset");
        }

        if (((SDOProperty)property).isReadOnly()) {
            throw new UnsupportedOperationException("Property is Readonly." + property.getName() + "  " + getType().getName());
        }

        boolean wasSet = isSet(property);

        if (wasSet) {
            if(!fromDelete){
              _setModified(true);
            }
        } else {
            return;
        }

        if (property.isContainment() || isContainedByDataGraph(property)) {
            Object oldValue = get(property);
            if (oldValue != null) {
                if (property.isMany()) {
                    for (int itemIndex = 0, size = ((List)oldValue).size(); itemIndex < size;
                             itemIndex++) {
                        SDODataObject manyItem = (SDODataObject)((List)oldValue).get(itemIndex);
                        if (manyItem != null) {
                            manyItem.detachOrDelete(fromDelete);
                        }
                    }
                } else {
                    ((SDODataObject)oldValue).detachOrDelete(fromDelete);
                }
            }
        }

        if (wasSet) {
            unsetInternal(property, updateSequence);
        }
    }

    @Override
    public boolean getBoolean(Property property) throws IllegalArgumentException, ClassCastException {
        Boolean propertyBooleanValue = (Boolean)convertObjectToValue(property, Boolean.class);
        if (propertyBooleanValue == null) {
            return false;
        }
        return propertyBooleanValue.booleanValue();
    }

    @Override
    public byte getByte(Property property) throws IllegalArgumentException, ClassCastException {
        Byte propertyByteValue = (Byte)convertObjectToValue(property, Byte.class);
        if (propertyByteValue == null) {
            return 0;
        }
        return propertyByteValue.byteValue();
    }

    @Override
    public char getChar(Property property) throws IllegalArgumentException {
        Character propertyCharValue = (Character)convertObjectToValue(property, Character.class);
        if (propertyCharValue == null) {
            return '\u0000';
        }
        return propertyCharValue.charValue();
    }

    @Override
    public double getDouble(Property property) throws IllegalArgumentException {
        Double propertyDoubleValue = (Double)convertObjectToValue(property, Double.class);
        if (propertyDoubleValue == null) {
            return 0.0d;
        }
        return propertyDoubleValue.doubleValue();
    }

    @Override
    public float getFloat(Property property) throws IllegalArgumentException {
        Float propertyFloatValue = (Float)convertObjectToValue(property, Float.class);
        if (propertyFloatValue == null) {
            return 0.0f;
        }
        return propertyFloatValue.floatValue();
    }

    @Override
    public int getInt(Property property) throws IllegalArgumentException {
        Integer propertyIntegerValue = (Integer)convertObjectToValue(property, Integer.class);
        if (propertyIntegerValue == null) {
            return 0;
        }
        return propertyIntegerValue.intValue();
    }

    @Override
    public long getLong(Property property) throws IllegalArgumentException {
        Long propertyLongValue = (Long)convertObjectToValue(property, Long.class);
        if (propertyLongValue == null) {
            return 0L;
        }
        return propertyLongValue.longValue();
    }

    @Override
    public short getShort(Property property) throws IllegalArgumentException {
        Short propertyShortValue = (Short)convertObjectToValue(property, Short.class);
        if (propertyShortValue == null) {
            return 0;
        }
        return propertyShortValue.shortValue();
    }

    @Override
    public byte[] getBytes(Property property) throws IllegalArgumentException {
        byte[] propertyByteValue = (byte[])convertObjectToValue(property, byte[].class);
        return propertyByteValue;
    }

    @Override
    public BigDecimal getBigDecimal(Property property) throws IllegalArgumentException {
        BigDecimal propertyDecimalValue = (BigDecimal)convertObjectToValue(property, BigDecimal.class);
        return propertyDecimalValue;
    }

    @Override
    public BigInteger getBigInteger(Property property) throws IllegalArgumentException {
        BigInteger propertyBigIntegerValue = (BigInteger)convertObjectToValue(property, BigInteger.class);
        return propertyBigIntegerValue;
    }

    @Override
    public SDODataObject getDataObject(Property property) throws IllegalArgumentException, ClassCastException {
        if(property != null && property.isMany()) {
            List value = (List)get(property);
            if(value.size() > 0) {
                return (SDODataObject)value.get(0);
            } else {
                return null;
            }
        }
        return (SDODataObject)get(property);
    }

    @Override
    public Date getDate(Property property) {
        if (null == property) {
            throw SDOException.cannotPerformOperationOnNullArgument("getDate");
        }
        if (property.getType().equals(SDOConstants.SDO_STRING)) {
            DataHelper dHelper = aHelperContext.getDataHelper();
            String dateString = (String)get(property);
            return dHelper.toDate(dateString);
        }
        Date propertyDateValue = (Date)convertObjectToValue(property, Date.class);
        return propertyDateValue;
    }

    @Override
    public String getString(Property property) {
        String propertyStringValue = (String)convertObjectToValue(property, String.class);
        return propertyStringValue;
    }

    @Override
    public List getList(Property property) {
        if (null == property) {
            throw SDOException.cannotPerformOperationOnNullArgument("getList");
        }

        if (((type != null) && !type.isOpen() && property.isOpenContent())) {
            throw new IllegalArgumentException("Argument not Supported.");
        }
        if (!property.isMany()) {
            throw new ClassCastException("can not call getList for a property that has isMany false.");
        }

        /*
         * Check the currentValueStore map
         * (1) If the property is stored
         *     (1a) return the ListWrapper or
         *     (1b) return and store an empty default value ListWrapper
         * (2) if the property is not set
         *     (2a) return and store an empty default value ListWrapper
         */
        Object value = getPropertyInternal(property);
        if ((value != null) && value instanceof List) {
            return (List)value;
        }

        // get a default empty list
        List theList = new ListWrapper(this, property);
        if (getType().isOpen() && property.isOpenContent()) {
            addOpenContentProperty(property);
        }

        // set the property to a default empty list
        setPropertyInternal((SDOProperty) property, theList, false);
        return theList;
    }

    @Override
    public void setBoolean(Property property, boolean value) {
        convertValueAndSet(property, value);
    }

    @Override
    public void setByte(Property property, byte value) {
        convertValueAndSet(property, value);
    }

    @Override
    public void setChar(Property property, char value) {
        convertValueAndSet(property, value);
    }

    @Override
    public void setDouble(Property property, double value) {
        convertValueAndSet(property, value);
    }

    @Override
    public void setFloat(Property property, float value) {
        convertValueAndSet(property, value);
    }

    @Override
    public void setInt(Property property, int value) {
        convertValueAndSet(property, value);
    }

    @Override
    public void setLong(Property property, long value) {
        convertValueAndSet(property, value);
    }

    @Override
    public void setShort(Property property, short value) {
        convertValueAndSet(property, value);
    }

    @Override
    public void setBytes(Property property, byte[] value) {
        convertValueAndSet(property, value);
    }

    @Override
    public void setBigDecimal(Property property, BigDecimal value) {
        convertValueAndSet(property, value);
    }

    @Override
    public void setBigInteger(Property property, BigInteger value) {
        convertValueAndSet(property, value);
    }

    @Override
    public void setDataObject(Property property, DataObject value) {
        set(property, value);
    }

    @Override
    public void setDate(Property property, Date value) {
        convertValueAndSet(property, value);
    }

    @Override
    public void setString(Property property, String value) {
        convertValueAndSet(property, value);
    }

    @Override
    public void setList(Property property, List value) {
        convertValueAndSet(property, value);
    }

    @Override
    public DataObject createDataObject(String propertyName) {
        Property aProperty = getInstanceProperty(propertyName);
        return createDataObject(aProperty);
    }

    @Override
    public SDODataObject createDataObject(int propertyIndex) {
        Property aProperty = getInstanceProperty(propertyIndex);
        return createDataObject(aProperty);
    }

    @Override
    public SDODataObject createDataObject(Property aProperty) {
        if (aProperty.isContainment()) {
            Type aType = aProperty.getType();
            if (aType != null) {
                return createDataObject(aProperty, aType);
            }
        }
        return null;
    }

    @Override
    public SDODataObject createDataObject(String propertyName, String namespaceURI, String typeName) {
        Property aProperty = getInstanceProperty(propertyName);
        Type aType = aHelperContext.getTypeHelper().getType(namespaceURI, typeName);
        return createDataObject(aProperty, aType);
    }

    @Override
    public SDODataObject createDataObject(int propertyIndex, String namespaceURI, String typeName) {
        Property aProperty = getInstanceProperty(propertyIndex);
        Type aType = aHelperContext.getTypeHelper().getType(namespaceURI, typeName);
        return createDataObject(aProperty, aType);
    }

    @Override
    public SDODataObject createDataObject(Property property, Type aType) {
        SDODataObject created = (SDODataObject) aHelperContext.getDataFactory().create(aType);

        if (property.isMany()) {
            //getList.add will call updateContainment
            ((ListWrapper)getList(property)).add(created, true);
        } else {
            set(property, created);
        }

        _setModified(true);
        created._setCreated(true);

        return created;
    }

    /**
     * INTERNAL:
     * Perform a detach on a DataObject or List of DataObjects
     * @param property
     * @param oldValue (List or Object)
     */
    private void detach(Property property, Object oldValue) {
        // This function is called internally from set(property, object, boolean) when a detach of the existing object is required
        if (property.isMany()) {
            List oldListValue = (List) oldValue;
            for (int i = 0, size = oldListValue.size(); i < size; i++) {
                Object nextValue = oldListValue.get(i);
                if ((nextValue != null) && (nextValue instanceof SDODataObject)) {
                    ((SDODataObject)nextValue).detachOrDelete(false);
                }
            }
        } else {
            if ((oldValue != null) && (oldValue instanceof SDODataObject)) {
                ((SDODataObject)oldValue).detachOrDelete(false);
            }
        }
    }

    /**
     * INTERNAL:
     * Recursively walk the tree and set oldSettings for a detached/deleted object.
     * This function performs a single preOrder traversal of the tree.
     * An unset is done for each property if the action = delete
     * Implementors: detach() and delete() via detach
     * @param fromDelete (flag the action true = delete, false = detach)
     */
    public void detachOrDelete(boolean fromDelete) {
        // If we are detaching the root - perform a no-operation
        if ((null == container) && !fromDelete) {
            return;
        }
        boolean isCSRoot = (null != changeSummary) && (changeSummary.getRootObject() == this);

        // Detaching the dataObject that is the root of the CS doesn't need to do anything - truncate the recursive traversal
        if(!fromDelete && isCSRoot){
          return;
        }

        // This is the root of the recursive loop
        detachDeleteRecursivePrivate(fromDelete, !isCSRoot, true);
    }

    /**
     * INTERNAL:
     * Recursively walk the tree and set oldSettings for a detached/deleted object.
     * @param fromDelete
     * @param clearCS (true = clear the cs field) = !(is root of the detach/delete subtree the CS root?)
     * @param isRootOfRecursiveLoop (are we at the root of the detach/delete or inside the subtree)
     */
    private void detachDeleteRecursivePrivate(boolean fromDelete, boolean clearCS, boolean isRootOfRecursiveLoop) {
        // Store flag for (is root of the detach/delete subtree the CS root?) before we modify it when this object has no container
        boolean subTreeRootHasCStoClear = clearCS;
        if (null == getContainer()) {
            clearCS = false;
        }
        if (isRootOfRecursiveLoop || fromDelete) {
            if (null != getContainer()) {
                getContainer()._setModified(true);
                _setContainer(null);
                _setContainmentPropertyName(null);
            }
        }
        _setDeleted(true);

        /** Order here is important - the dataGraph pointer should be cleared preorder before we enter the recursive loop to clear child DO's */
        DataGraph previousDataGraph = getDataGraph();
        // Remove back pointer to containing DataGraph
        setDataGraph(null);

        List instancePropertiesList = getInstanceProperties();
        for (int i = 0, psize = instancePropertiesList.size(); i < psize; i++) {
            SDOProperty nextProperty = (SDOProperty)instancePropertiesList.get(i);
            Object oldValue = get(nextProperty);

            if (!nextProperty.getType().isChangeSummaryType()) {
                // Non-containment nodes have changeSummary and dataGraph pointers if they were in a dataGraph
                if (nextProperty.isContainment() || isContainedByDataGraph(previousDataGraph, nextProperty)) {
                    if (nextProperty.isMany()) {
                        Object manyItem;
                        for (int j = 0, lsize = ((List)oldValue).size(); j < lsize; j++) {
                            manyItem = ((List)oldValue).get(j);
                            detachDeleteRecursivePrivateHelper((SDODataObject)manyItem, fromDelete, clearCS);
                        }
                    } else {
                        detachDeleteRecursivePrivateHelper((SDODataObject)oldValue, fromDelete, clearCS);
                    }
                }
                if (fromDelete && !nextProperty.isReadOnly()) {
                    unset(nextProperty, fromDelete);
                }
            }
        }

        /**
         * Bug # 6202793
         * We delete the changeSummary field in the following use cases
         * in addition to when the clearCS field was passed in as or transformed to true.
         * Case 0: detach subtree root from root (with cs) = false
         * Case 1: detach subtree internal from root (with cs) = false
         * Case 2: delete subtree root from root (with cs) = false
         * Case 3: delete subtree internal from root (with cs) = true
         * Case 4: detach subtree root from root (without cs) = false
         * Case 5: detach subtree internal from root (without cs) = false
         * Case 6: delete subtree root from root (without cs) = true
         * Case 7: delete subtree internal from root (without cs) = true
         */
        if (clearCS || subTreeRootHasCStoClear) {
            _setChangeSummary(null);
        }
    }

    /**
     * INTERNAL:
     * Call detach or delete recursively on aDataObject after possibly changing the flag whether to clear the ChangeSummary pointer at this level
     * @param aDataObject
     * @param fromDelete
     * @param clearCS
     */
    private void detachDeleteRecursivePrivateHelper(SDODataObject aDataObject, boolean fromDelete, boolean clearCS) {
        if (aDataObject != null) {
            // Return whether (aDataObject) is the changeSummary root.
            boolean isCSRoot = (aDataObject.getChangeSummary() != null) &&//
            (aDataObject.getChangeSummary().getRootObject() == aDataObject);

            // If we are at the CS root - we do not clear the changeSummary
            if (isCSRoot) {
                clearCS = false;
            } else {
                if (aDataObject.getContainer() != null) {
                    ChangeSummary containerCS = aDataObject.getContainer().getChangeSummary();

                    // If there is no CS field set above this object then clear any ChangeSummary pointer at this level
                    if (containerCS == null) {
                        clearCS = true;
                    }
                }
            }
            aDataObject.detachDeleteRecursivePrivate(fromDelete, clearCS, false);
        }
    }

    @Override
    public void detach() {
        detachWithSequenceUpdate();
    }

    /**
     * INTERNAL:
     * Perform a detach action that originated from a detach and update the sequence.
     * Case 01
     */
    private void detachWithSequenceUpdate() {
        detach(false, true);
    }

    /**
     * INTERNAL:
     * Perform a detach action that originated from a delete and update the sequence.
     * Case 11
     */
    private void deleteWithSequenceUpdate() {
        detach(true, true);
    }

    /**
     * INTERNAL:
     * Removes this DataObject from its container, if any.
     * Same as
     *  getContainer().getList(getContainmentProperty()).remove(this) or
     *  getContainer().unset(getContainmentProperty())
     *  depending on getContainmentProperty().isMany() respectively.
     *  @param fromDelete (true = delete action, false = detach/unset)
     *  @param updateSequence
     */
    private void detach(boolean fromDelete, boolean updateSequence) {
        // Note: there is no case10 where fromDelete=true and updateSequence=false
        SDOProperty containmentProperty = getContainmentProperty();

        if ((containmentProperty != null) && containmentProperty.isReadOnly()) {
            throw new UnsupportedOperationException("Property is Readonly." + containmentProperty.getName() + "  " + getType().getName());
        }

        if (containmentProperty != null) {
            if (getContainmentProperty().isMany()) {
                List oldList = getContainer().getList(containmentProperty);

                // pass remove containment flag instead of calling remove(this) and detachOrDelete(fromDelete) separately
                // This will invoke creation of an intact list copy before removing its containment and clearing its changeSummary
                ((ListWrapper)oldList).remove(this, fromDelete, updateSequence);
            } else {
                getContainer().unset(containmentProperty, fromDelete, updateSequence);
            }
        } else {
            _setDeleted(true);
            detachOrDelete(fromDelete);
        }
    }

    @Override
    public void delete() {
        deleteWithSequenceUpdate();
    }

    @Override
    public SDODataObject getContainer() {
        return container;
    }

    @Override
    public SDOProperty getContainmentProperty() {
        if ((container != null) && (containmentPropertyName != null)) {
            return container.getInstanceProperty(containmentPropertyName);
        } else {
            return null;
        }
    }

    @Override
    public DataGraph getDataGraph() {
        return dataGraph;
    }

    public void setDataGraph(DataGraph dataGraph) {
        this.dataGraph = dataGraph;
    }

    @Override
    public SDOType getType() {
        return type;
    }

    @Override
    public List getInstanceProperties() {
        if (null == instanceProperties) {
            instanceProperties = new ArrayList();
        }
        return instanceProperties;
    }

    @Override
    public SDOProperty getProperty(String propertyName) {
        return getInstanceProperty(propertyName);
    }

    @Override
    public SDOProperty getInstanceProperty(String propertyName) {
        if (getType() == null) {
            throw new UnsupportedOperationException("Type is null");
        }
        SDOProperty property = getType().getProperty(propertyName);
        if (null == property) {
            property = (SDOProperty)_getOpenContentAliasNamesMap().get(propertyName);
            if (null == property) {
                for (int i = 0; i < _getOpenContentProperties().size(); i++) {
                    SDOProperty nextProp = (SDOProperty)_getOpenContentProperties().get(i);
                    if (nextProp.getName().equals(propertyName)) {
                        return nextProp;
                    }
                }
                if (null == property) {
                    for (int i = 0; i < _getOpenContentPropertiesAttributes().size(); i++) {
                        SDOProperty nextProp = (SDOProperty)_getOpenContentPropertiesAttributes().get(i);
                        if (nextProp.getName().equals(propertyName)) {
                           return nextProp;
                        }
                    }
                }
            }
        }
        return property;
    }

    SDOProperty getInstanceProperty(String propertyName, Object value) {
        SDOProperty sdoProperty = getInstanceProperty(propertyName);
        if(null == sdoProperty) {
            sdoProperty = (SDOProperty) defineOpenContentProperty(propertyName, value);
        }
        return sdoProperty;
    }

    /**
     * INTERNAL:
     * @param propertyIndex
     * @return
     * @throws SDOException
     */
    public SDOProperty getInstanceProperty(int propertyIndex) throws IllegalArgumentException {
        try {
            SDOProperty property = getInstancePropertiesArray()[propertyIndex];
            return property;
        } catch (IndexOutOfBoundsException e) {
            throw SDOException.propertyNotFoundAtIndex(e, propertyIndex);
        }
    }

    @Override
    public SDODataObject getRootObject() {
        if (getContainer() != null) {
            return getContainer().getRootObject();
        }
        return this;
    }

    @Override
    public SDOChangeSummary getChangeSummary() {
        return changeSummary;
    }

    /**
     * INTERNAL:
     * Set flag created value.
     * @param created   flag created's new value.
     */
    public void _setCreated(boolean created) {
        if (changeSummary != null) {
            changeSummary.setCreated(this, created);
        }
    }

    /**
     * INTERNAL:
     * Set flag modified value.
     * @param modified   flag modified's new value.
     */
    public void _setModified(boolean modified) {
        if (changeSummary != null) {
            if (isLogging()) {
                updateChangeSummaryWithOriginalValues();
            }
        }
    }

    /**
     * INTERNAL:
     * Set flag deleted value.
     * @param deleted   flag deleted's new value.
     */
    private void _setDeleted(boolean deleted) {
        // reduced scope from public to private 17 May 2007 - use the public deprecated public function until we remove it
        if (changeSummary != null) {
            boolean wasDeleted = changeSummary.setDeleted(this, deleted);
            if (wasDeleted && isLogging()) {
                updateChangeSummaryWithOriginalValues();
            }
        }
    }

    /**
     * INTERNAL:
     * @param csm
     */
    private void setChangeSummaryNonRecursive(ChangeSummary csm) {
        // set changeSummary instance using property
        changeSummary = (SDOChangeSummary) csm;
        if (getType() != null) {
            SDOProperty changeSummaryProperty = getType().getChangeSummaryProperty();
            if (changeSummaryProperty != null) {
                setChangeSummaryProperty(changeSummaryProperty, csm);
            }
        }
    }

    /**
     * INTERNAL:
      * Recursively Set this DataObject's ChangeSummary as passed in value.
     * @param aChangeSummary   the ChangeSummary taking this DataObject as root.
     */
    public void _setChangeSummary(SDOChangeSummary aChangeSummary) {
        updateChangeSummaryAndDataGraph(aChangeSummary, getDataGraph());
    }

    /**
     * INTERNAL:
     * Recursively set this DataObject's changeSummary and dataGraph.
     * The parent changeSummary and dataGraph may be null.
     * Preconditions: No changeSummary must exist on the child tree.
     * Callers: Typically updateContainment (during a set) or delete/detach
     *   of an object without its own changeSummary property.
     * @param aDataGraph
     */
    private void updateChangeSummaryAndDataGraph(ChangeSummary aChangeSummary, DataGraph aDataGraph) {
        Iterator iterProperties = getInstanceProperties().iterator();
        Property property;
        Object object;
        Object listContainedObject;

        // Add back pointer to containing DataGraph - in preOrder sequence before recursing
        setDataGraph(aDataGraph);

        // Recurse currentValueStore
        while (iterProperties.hasNext()) {
            property = (Property)iterProperties.next();

            // Note: Both opposite currentValueStore cannot be isContainment=true
            // special handling for bidirectional currentValueStore, do a shallow set
            // do not recurse bidirectional currentValueStore that are non-containment
            if (property.isContainment() || isContainedByDataGraph(property)) {
                object = get(property);
                if (object instanceof SDODataObject) {// DataObject child of this DataObject
                    ((SDODataObject)object).updateChangeSummaryAndDataGraph(aChangeSummary, aDataGraph);
                }

                if (object instanceof ListWrapper) {// ListWrapper child of this DataObject
                    Iterator anIterator = ((ListWrapper)object).iterator();
                    while (anIterator.hasNext()) {
                        listContainedObject = anIterator.next();
                        if (listContainedObject instanceof SDODataObject) {
                            ((SDODataObject)listContainedObject).updateChangeSummaryAndDataGraph(aChangeSummary, aDataGraph);
                        }
                    }
                }
            }
        }
        // Set/Clear changeSummary in postOrder sequence
        setChangeSummaryNonRecursive(aChangeSummary);
    }


    /**
     * INTERNAL:
     * Recursively set this DataObject's DataGraph
     * This function serves as a copy of updateChangeSummaryAndDataGraph() to recursively walk and set the dataGraph.
     * that will be run when no recursion occurs in the case that an object (with a changeSummary)
     * is set internally to a tree (without a changeSummary).
     * Callers: Typically updateContainment (during a set) or delete/detach
     *   when the current object is internal with its own changeSummary property.
     * @param aDataGraph
     */
    private void updateDataGraph(DataGraph aDataGraph) {
        Iterator iterProperties = getInstanceProperties().iterator();
        Property property;
        Object object;
        Object listContainedObject;

        // Add back pointer to containing DataGraph - in preOrder sequence before recursing
        setDataGraph(aDataGraph);

        // Recurse currentValueStore
        while (iterProperties.hasNext()) {
            property = (Property)iterProperties.next();

            // Note: Both opposite currentValueStore cannot be isContainment=true
            // special handling for bidirectional currentValueStore, do a shallow set
            // do not recurse bidirectional currentValueStore that are non-containment
            if (property.isContainment() || isContainedByDataGraph(property)) {
                object = get(property);
                if (object instanceof SDODataObject) {// DataObject child of this DataObject
                    ((SDODataObject)object).updateDataGraph(aDataGraph);
                }

                if (object instanceof ListWrapper) {// ListWrapper child of this DataObject
                    Iterator anIterator = ((ListWrapper)object).iterator();
                    while (anIterator.hasNext()) {
                        listContainedObject = anIterator.next();
                        if (listContainedObject instanceof SDODataObject) {
                            ((SDODataObject)listContainedObject).updateDataGraph(aDataGraph);
                        }
                    }
                }
            }
        }
    }

    /**
     * INTERNAL:
     *
     * @param property
     * @param value
     */
    private void setChangeSummaryProperty(SDOProperty property, ChangeSummary value) {
        if (property.isOpenContent()) {
            throw new IllegalArgumentException("ChangeSummary can not be on an open content property.");
        }
        if (property.isMany()) {
            throw new IllegalArgumentException("ChangeSummary can not be on a property with many set to true.");
        }

        if (isLogging()) {
            _setModified(true);
        }

        // process pluggable currentValueStore
        setPropertyInternal(property, value, true);
    }

    /**
     * INTERNAL:
     * This function reverses any operations that were performed on this object since change tracking
     * was turned on.  The object is returned to the state when logging was first started.<br>
     * @param isCSRoot
     * @param cs
     * @param origContainer
     * @param origContainmentPropName
     */
    public void undoChanges(boolean isCSRoot, ChangeSummary cs,//
                            SDODataObject origContainer, String origContainmentPropName) {
        // dont do anything if the changeSummary is null
        if (null == cs) {
            return;
        }

        /**
         * Logic: 4 steps - we defer undo responsibilty to each of DataObject, ListWrapper and Sequence.
         * 1) swap ValueStores (for DataObject)
         * 2) swap ListWrapper (if it exists)
         * 3) swap Sequence (if it exists)
         * 4) #5928954 restore openContent properties to the 3 data structures on SDODataObject -
         *     openContentPropertiesMap<String,Property>, openContentProperties(List<Property>). instanceProperties (List<Property>)
         *
         * Use Cases:
         *   UC1: no change
         *     doDirty=false seqDirty=false
         *   UC2: change non-sequenced do
         *     doDirty=false seqDirty=false
         *   UC3: change sequenced do
         *     doDirty=true. SeqDirty=true
         *   UC4: change unstructured text on sequence only
         *     [doDirty=false seqDirty=true] - special case - verify that we do not do it twice (here and in UC3)
         *   UC5: unset(oc property), undoChanges(), get("open property")
         *
         */

        // base step: swap valueStores at the current node
        if (((SDOChangeSummary)cs).isDirty(this)) {
            // reset only if changes were done
            if (!isCSRoot) {
                // reset changeSummary
                if (null == changeSummary) {
                    // we are not recursively setting cs - so don't use the set function as child nodes will be replaced later
                    changeSummary = (SDOChangeSummary) cs;
                }

                // reset container
                if (null == container) {
                    _setContainer(origContainer);
                }

                // reset containmentProperty name
                if (null == containmentPropertyName) {
                    _setContainmentPropertyName(origContainmentPropName);
                }
            }

            // swap valueStores
            _setCurrentValueStore((ValueStore)((SDOChangeSummary)cs).getOriginalValueStores().get(this));
            // return original to null (no changes)
            ((SDOChangeSummary)cs).getOriginalValueStores().remove(this);
        }

        // swap sequences (may be UC2(DataObject and sequence change) or UC4 (sequence change only)
        if (getType().isSequenced()) {// assume sequence is !null
            // perform sequence.isDirty check independent of a dataObject.isDirty check
            if (((SDOChangeSummary)cs).isDirty(sequence)) {
                Sequence currentSequence = sequence;
                SDOSequence originalSequence = (SDOSequence)((SDOChangeSummary)cs).getOriginalSequences().get(this);

                // both sequences are either null or set
                if ((null == originalSequence) && (null != currentSequence)) {
                    throw SDOException.oldSequenceNotFound();
                } else {
                    sequence = originalSequence;
                }

                // reset the cache map
                ((SDOChangeSummary)cs).getOldSequences().remove(this);
                // rest the primary map
                ((SDOChangeSummary)cs).getOriginalSequences().remove(this);
            }
        }

        // restore any open content properties to the 3 data structures on SDODataObject, and remove from cs.unsetOCPropsMap
        // see openContentPropertiesMap<String,Property>, openContentProperties(List<Property>). instanceProperties (List<Property>)
        List oldUnsetOCList =  ((SDOChangeSummary)cs).getUnsetOCProperties(this);
        for (int i = 0, size = oldUnsetOCList.size(); i < size; i++) {
             // it is essential that the oc property is removed from the cs or we will get an unsupported op during resetChanges()
             // the property will be added only when logging=true, we reference the first item in the list as it reduces in size
             addOpenContentProperty((Property)oldUnsetOCList.get(0));
        }

        // recursive step: swap valueStores on every contained subtree
        for (Iterator iterProperties = getInstanceProperties().iterator();
                 iterProperties.hasNext();) {
            SDOProperty property = (SDOProperty)iterProperties.next();
            if (!property.getType().isChangeSummaryType()) {
                Object value = get(property);
                if ((null == value) && (null != getChangeSummary())) {
                    // no changes for null properties
                } else {
                    if (property.isMany()) {
                        // check for modified ListWrapper
                        // assumption that we never have a null ListWrapper should still avoid a possible NPE
                        if (null != value) {
                            ((ListWrapper)value).undoChanges(getChangeSummary());
                            if (!property.getType().isDataType()) {
                                for (Iterator iterMany = ((List)value).iterator();
                                         iterMany.hasNext();) {
                                    Object valueMany = iterMany.next();
                                    if (null != valueMany) {// handle micro-corner case ListWrapper is null
                                        ((SDODataObject)valueMany).undoChanges(false, changeSummary, this, property.getName());
                                    }
                                }
                            }
                        }
                    } else {
                        if (!property.getType().isDataType() && (null != value)) {
                            ((SDODataObject)value).undoChanges(false, changeSummary, this, property.getName());
                        }
                    }
                }
            }
        }
    }

    /**
     * INTERNAL:
     * Process ValueStore changes on any set/delete/detach/unset
     * when logging is on.
     */
    private void updateChangeSummaryWithOriginalValues() {

        /**
         * ValueStore processing:
         * We switch the current VS to original VS.
         * The current VS will be a shallow copy of the original for this dataobject at its first change
         * If the VS is already copied don't reswap them again
         *
         * Case: Move/Reset
         *  a detach of a child modifies its parent - trigering a copy of parent's ValueStore
         *  a set on the parent will trigger another copy - but we will skip this one as we already
         *  have a copy of the original.
         *  Issue: if we reset the child back the same place (in effect doing our own undo)
         *  do we optimize this and remove the copy - for now a real undoChanges() will do the same.
         *
         */

        // dont store an original sequence if there are already is one in the map
        if (isLogging() && (!getChangeSummary().isDirty(this)) && (!getChangeSummary().isCreated(this))) {
            // dont copy containers of many props
            // original valuestore becomes current one (from null)
            getChangeSummary().getOriginalValueStores().put(this, _getCurrentValueStore());
            // we make a shallow copy of the current valuestore arrays at this level in the tree
            currentValueStore = _getCurrentValueStore().copy();

            // handle Sequences only in UC2 where we have modified the container object - not when only the sequence is dirty
            if (getType().isSequenced()) {
                // deep copy the list of settings so a modification of the current sequence will not affect a setting in the originalSequences map
                SDOSequence copySequence = getSequence().copy();

                // store original sequence on ChangeSummary
                getChangeSummary().getOriginalSequences().put(this, copySequence);
            }
        }
    }

    /**
     * INTERNAL:
     * Initialize all old settings related to ChangeSummary and recursively go down
     * the tree to initialize all DataObjects rooted at this DataObject.
     */
    public void resetChanges() {
        // fill all oldSettings when logging is turned on
        if ((container != null) && (containmentPropertyName != null) && (changeSummary != null)) {
            changeSummary.setOldContainer(this, container);
            changeSummary.setOldContainmentProperty(this, container.getInstanceProperty(containmentPropertyName));
        }

        // Note: valueStores will not be switched and originalValueStore will continue to be null until the first modification
        // initialize empty list for current dataObject
        for(int x=0, instancePropertiesSize=instanceProperties.size(); x<instancePropertiesSize; x++) {
            SDOProperty property = instanceProperties.get(x);
            Object value = get(property);

            // #5878436 12-FEB-07 do not recurse into a non-containment relationship unless inside a dataGraph
            if ((property.isContainment() || isContainedByDataGraph(property)) && //
                !property.isMany() && (value != null) && !property.getType().isChangeSummaryType()) {

                ((SDODataObject)value).resetChanges();
            } else {
                // Handle isMany objects and store the oldCont* values for each item in the list
                if (property.isMany()) {
                    if (null != value) {// secondary NPE check
                        List listValue = (List) value;
                        for(int y=0, listValueSize=listValue.size(); y<listValueSize; y++) {
                            Object valueMany = listValue.get(y);

                            // do not recurse into a non-containment relationship unless inside a dataGraph
                            if ((property.isContainment() || isContainedByDataGraph(property)) && //
                                (valueMany != null) && !property.getType().isChangeSummaryType()) {
                                ((SDODataObject)valueMany).resetChanges();
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * INTERNAL:
     * @param aType
     */
    public void _setType(Type aType) {
        type = (SDOType) aType;

        if (getInstanceProperties().isEmpty()) {
            if (type != null) {
                getInstanceProperties().addAll(type.getProperties());
            }
            getInstanceProperties().addAll(_getOpenContentProperties());
        }

        if (type != null) {
            _getCurrentValueStore().initialize(this);
            // sequence is not initialized unless type.isSequenced=true, null otherwise
            if (type.isSequenced()) {
                sequence = new SDOSequence(this);
            }

            Property csmProperty = type.getChangeSummaryProperty();
            if (csmProperty != null) {
                SDOChangeSummary aChangeSummary = new SDOChangeSummary(this, aHelperContext);
                aChangeSummary.endLogging();
                _setChangeSummary(aChangeSummary);
            }
        }
    }

    /**
     * INTERNAL:
     * Pluggable Interface for substituting the default Map with a custom Map Class
     * @param aValueStore
     */
    public void _setCurrentValueStore(ValueStore aValueStore) {
        currentValueStore = aValueStore;
    }

    /**
     * INTERNAL:
     * Map interface into the currentValueStore of this DataObject.<br>
     * Note: Implementers of the {@link ValueStore ValueStore} interface require this accessor.
     * @return
     */
    public ValueStore _getCurrentValueStore() {
        // Note: do not remove this function as it is the accessor into the pluggable ValueStore
        // Notification of any change to this API is required
        return currentValueStore;
    }

    /**
     * INTERNAL:
     * Sets the DataObject which contains this DataObject.
     * @param aContainer the DataObject which is the container of this DataObject.
     */
    public void _setContainer(DataObject aContainer) {
        container = (SDODataObject) aContainer;
    }

    public Map _getOpenContentPropertiesAttributesMap() {
        Map openContentPropertiesAttrs = new HashMap();
        for (int i = 0, size = _getOpenContentPropertiesAttributes().size(); i < size; i++) {
            Property next = (Property)_getOpenContentPropertiesAttributes().get(i);
            //if uri is null then attributes won't be prefix qualified
            QName qname = new QName(((SDOProperty)next).getUri(), next.getName());
            openContentPropertiesAttrs.put(qname, get(next));
        }

        return openContentPropertiesAttrs;

    }

    public void _setOpenContentPropertiesAttributesMap(Map openAttributeProperties) {
        Iterator iter = openAttributeProperties.keySet().iterator();

        while (iter.hasNext()) {
            QName nextKey = (QName)iter.next();

            if ((!nextKey.getNamespaceURI().equals(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) && (!((nextKey.getNamespaceURI().equals(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI)) && nextKey.getLocalPart().equals(XMLConstants.SCHEMA_TYPE_ATTRIBUTE)))) {
                Object value = openAttributeProperties.get(nextKey);
                SDOProperty prop = (SDOProperty)aHelperContext.getXSDHelper().getGlobalProperty(nextKey.getNamespaceURI(), nextKey.getLocalPart(), false);
                if (prop == null) {
                    DataObject propDo = aHelperContext.getDataFactory().create(SDOConstants.SDO_URL, SDOConstants.PROPERTY);
                    propDo.set("name", nextKey.getLocalPart());
                    propDo.set("type", SDOConstants.SDO_STRING);
                    propDo.set("many", false);
                    prop = (SDOProperty)aHelperContext.getTypeHelper().defineOpenContentProperty(null, propDo);
                    prop.setInstanceProperty(SDOConstants.XMLELEMENT_PROPERTY, Boolean.FALSE);
                    prop.setUri(nextKey.getNamespaceURI());

                    set(prop, value);
                } else {
                    set(prop, value);
                }
            }
        }
    }

    /**
     * INTERNAL:
     * This function is implemented by SDOType.setOpen() in a mapping setGet/SetMethodName call
     * @param openContentPropertiesWithXMLRoots
     */
    public void _setOpenContentPropertiesWithXMLRoots(List openContentPropertiesWithXMLRoots) {
        for (int i = 0, size = openContentPropertiesWithXMLRoots.size(); i < size; i++) {
            Object next = openContentPropertiesWithXMLRoots.get(i);
            String propertyName = null;
            String propertyUri = null;
            Object value = null;
            Type theType = null;
            if (next instanceof XMLRoot) {
                XMLRoot nextXMLRoot = (XMLRoot)next;
                value = nextXMLRoot.getObject();
                propertyName = nextXMLRoot.getLocalName();
                propertyUri = nextXMLRoot.getNamespaceURI();
                if (value instanceof DataObject) {
                    theType = ((DataObject)value).getType();
                } else {
                    theType = ((SDOTypeHelper)aHelperContext.getTypeHelper()).getTypeForSimpleJavaType(value.getClass());
                }
            } else if (next instanceof DataObject) {
                value = next;
                String qualifiedName = ((SDOType)((DataObject)next).getType()).getXmlDescriptor().getDefaultRootElement();
                int colonIndex = qualifiedName.indexOf(":");
                if (colonIndex > -1) {
                    String prefix = qualifiedName.substring(0, colonIndex);
                    if ((prefix != null) && !prefix.equals("")) {
                        propertyUri = ((SDOType)((DataObject)next).getType()).getXmlDescriptor().getNonNullNamespaceResolver().resolveNamespacePrefix(prefix);
                    }
                    propertyName = qualifiedName.substring(colonIndex + 1, qualifiedName.length());
                } else {
                    propertyName = qualifiedName;
                }
                theType = ((DataObject)next).getType();
            }
            else{
              theType = ((SDOTypeHelper)aHelperContext.getTypeHelper()).getTypeForSimpleJavaType(next.getClass());
            }
            if (propertyName != null) {
                SDOProperty prop = (SDOProperty)aHelperContext.getXSDHelper().getGlobalProperty(propertyUri, propertyName, true);
                if (prop == null) {
                    prop = getInstanceProperty(propertyName);
                    if(prop != null){
                      if(prop.getUri() == null && propertyUri != null){
                        prop = createNewProperty(propertyName,propertyUri, theType);
                      }else{
                        if(prop.getUri() != null){
                          if(propertyUri == null){
                            prop = createNewProperty(propertyName,propertyUri, theType);
                          }else if(!prop.getUri().equals(propertyUri)){
                             prop = createNewProperty(propertyName,propertyUri, theType);
                          }
                        }
                      }
                    }else{
                       prop = createNewProperty(propertyName,propertyUri, theType);
                    }
                }

                if (prop.isMany()) {
                    ((ListWrapper)getList(prop)).add(value, false);
                } else {
                    set(prop, value, false);
                }
            }
        }
    }

    private SDOProperty createNewProperty(String propertyName,String propertyUri, Type theType){
       DataObject propDo = aHelperContext.getDataFactory().create(SDOConstants.SDO_URL, SDOConstants.PROPERTY);
       propDo.set("name", propertyName);
       propDo.set("type", theType);
       propDo.set("many", true);
       SDOProperty prop = (SDOProperty)aHelperContext.getTypeHelper().defineOpenContentProperty(null, propDo);
       prop.setUri(propertyUri);
       return prop;
    }

    /**
     * INTERNAL:
     * This function is implemented by SDOType.setOpen() in a mapping setGet/SetMethodName call
     * @return
     */
    public List _getOpenContentPropertiesWithXMLRoots() {
        List returnList = new ArrayList();
        for (int i = 0, size = openContentProperties.size(); i < size; i++) {
            SDOProperty next = (SDOProperty) openContentProperties.get(i);

            XMLRoot root = new XMLRoot();
            String localName = next.getXPath();
            if (next.getType() != null) {
                if (!next.getType().isDataType()) {
                    String uri = next.getUri();
                    root.setNamespaceURI(uri);
                } else {
                    String uri = next.getUri();
                    root.setNamespaceURI(uri);
                }
            }

            root.setLocalName(localName);

            Object value = get(next);
            if (next.isMany()) {
                for (int j = 0, sizel = ((List)value).size(); j < sizel; j++) {
                    XMLRoot nextRoot = new XMLRoot();
                    nextRoot.setNamespaceURI(root.getNamespaceURI());
                    nextRoot.setLocalName(root.getLocalName());
                    Object nextItem = ((List)value).get(j);
                    if ((next.getType() != null) && (next.getType().getXmlDescriptor() == null)) {
                        nextItem = XMLConversionManager.getDefaultXMLManager().convertObject(nextItem, String.class);
                    }
                    nextRoot.setObject(nextItem);
                    returnList.add(nextRoot);
                }
            } else {
                if ((next.getType() != null) && (next.getType().getXmlDescriptor() == null)) {
                    value = XMLConversionManager.getDefaultXMLManager().convertObject(value, String.class);
                }
                root.setObject(value);
                returnList.add(root);
            }
        }
        return returnList;
    }

    /**
     * INTERNAL:
      * Returns a list of the Properties currently used in this DataObject which are not
      * included in getType().getProperties
      * @return the List of open content Properties currently used in this DataObject.
      */
    public List _getOpenContentProperties() {
        if (openContentProperties == null) {
            openContentProperties = new ArrayList();
        }
        return openContentProperties;
    }


    private Map<String, Property> _getOpenContentAliasNamesMap() {
        if (openContentAliasNames == null) {
            openContentAliasNames = new HashMap();
        }
        return openContentAliasNames;
    }

   /**
     * INTERNAL:
     */
    private void convertValueAndSet(Property property, Object originalValue) {
        Object convertedValue;
        if (property.isMany()) {
            if (originalValue == null) {
                convertedValue = new ArrayList();
            } else if (originalValue instanceof List) {
                convertedValue = aHelperContext.getDataHelper().convert(property, originalValue);
            } else {
                List l = new ArrayList();
                l.add(originalValue);
                convertedValue = aHelperContext.getDataHelper().convert(property, l);
            }
        } else {
            convertedValue = aHelperContext.getDataHelper().convert(property, originalValue);
        }
        set(property, convertedValue);
    }

    /**
    * INTERNAL:
    */
    private void convertValueAndSet(int propertyIndex, Object originalValue) {
        Property property = getInstanceProperty(propertyIndex);

        Object convertedValue = aHelperContext.getDataHelper().convert(property, originalValue);
        set(property, convertedValue);
    }

    /**
     * INTERNAL:
     */
    private void convertValueAndSet(String path, Object originalValue) {
        XPathEngine.getInstance().set(path, originalValue, this, true);
    }

    /**
     * INTERNAL:
     *
     * @param property
     * @param cls
     * @return
     * @throws ClassCastException
     * @throws IllegalArgumentException
     */
    public Object convertObjectToValue(Property property, Class cls) throws ClassCastException, IllegalArgumentException {
        //ie get String - convert(thisprop, String.class)
        if (null == property) {
            throw new IllegalArgumentException(SDOException.cannotPerformOperationOnNullArgument("convertObjectToValue"));
        }

        Object obj = unwrapListValue(property, get(property));

        try {
            return ((SDODataHelper)aHelperContext.getDataHelper()).convertValueToClass(property, obj, cls);
        } catch (Exception e) {
            throw new ClassCastException("An error occurred during converison or an unsupported conversion was attempted.");
        }
    }

    /**
     * INTERNAL:
     *
     * @param property
     * @param position
     * @param cls
     * @return
     * @throws ClassCastException
     * @throws IllegalArgumentException
     */
    public Object convertObjectToValue(Property property, int position, Class cls) throws ClassCastException, IllegalArgumentException {
        if (null == property) {
            throw new IllegalArgumentException(SDOException.cannotPerformOperationOnNullArgument("convertObjectToValue"));
        }

        if ((cls == ClassConstants.List_Class) && !property.isMany()) {
            throw new ClassCastException("can not call getList for a property that has isMany false.");
        }

        Object obj;
        if (position == -1) {
            if (cls == ClassConstants.List_Class) {
                obj = get(property);
            } else {
                obj = unwrapListValue(property, get(property));
            }
        } else {
            obj = getList(property).get(position);
        }

        try {
            return ((SDODataHelper)aHelperContext.getDataHelper()).convertValueToClass(property, obj, cls);
        } catch (Exception e) {
            throw new ClassCastException("An error occurred during conversion or an unsupported conversion was attempted.");
        }
    }

    /**
     * INTERNAL:
     * Sets the name of the property on the containing DataObject which contains this DataObject as a value.
     * @param aContainmentPropertyName the name of the property on the containing DataObject which has this DataObject as a value.
     */
    public void _setContainmentPropertyName(String aContainmentPropertyName) {
        containmentPropertyName = aContainmentPropertyName;
    }

    /**
     * INTERNAL:
     * Return the name of the Property of the DataObject containing this data object
     * or null if there is no container.
     * @return the property containing this data object.
     */
    public String _getContainmentPropertyName() {
        return containmentPropertyName;
    }

    /**
     * INTERNAL:
     *
     * @param value
     * @return
     */
    public boolean parentContains(Object value) {
        if ((value == null) || (!(value instanceof DataObject))) {
            return false;
        }
        if ((getContainer() != null) && (getContainmentProperty() != null)) {
            if (value.equals(getContainer())) {
                return true;
            }
            return getContainer().parentContains(value);
        }
        return false;
    }

    /**
     * INTERNAL:
     * Return whether the current dataObject(this) is part of a dataGraph.
     * Typically this is used to determine whether to treat !isContainment dataObjects as containment while inside a dataGraph.
     * @param aProperty
     * @return
     */
    private boolean isContainedByDataGraph(Property aProperty) {
        // The property is of type DataObject that points to a DataGraph
        return isContainedByDataGraph(getDataGraph(),  (SDOProperty) aProperty);
    }

    /**
     * INTERNAL:
     * Return whether the current dataObject(this) is was part of the passed in dataGraph.
     * Typically this is used to determine whether to treat !isContainment dataObjects as containment while inside a dataGraph.
     * The dataGraph input is used when the current DataGraph pointer has already been cleared
     * @param aDataGraph
     * @param aProperty
     * @return
     */
    private boolean isContainedByDataGraph(DataGraph aDataGraph, SDOProperty aProperty) {
        // The property is of type DataObject that points to a DataGraph
        return (null != aDataGraph) && (null != aProperty.getType()) && !aProperty.getType().isDataType();
    }

    /**
     * INTERNAL:
     * Update containment with flagged update sequence state
     * @param property
     * @param values
     * @param updateSequence
     */
    public void updateContainment(Property property, Collection values, boolean updateSequence) {
        if (property.isContainment() || isContainedByDataGraph(property)) {//

            Object[] valuesArray = values.toArray();
            for(int i=0; i<valuesArray.length; i++){
               Object next = valuesArray[i];
                if (next instanceof SDODataObject) {
                    updateContainment(property, (SDODataObject)next, updateSequence);
                }
            }
        }
    }

    /**
     * INTERNAL:
     * Update containment on the specified collection of values and default to true = update the sequence
     * @param property
     * @param values
     */
    public void updateContainment(Property property, Collection values) {
        updateContainment(property, values, true);
    }

    /**
     * INTERNAL:
     * Update containment on the dataObject with specified update sequence state
     * @param property
     * @param aDataObject
     * @param updateSequence
     */
    public void updateContainment(Property property, SDODataObject aDataObject, boolean updateSequence) {
        if (property.isContainment() || isContainedByDataGraph(property)) {
            boolean wasInNewCS = (getChangeSummary() != null) && //
                (aDataObject.getChangeSummary() != null) && getChangeSummary().equals(aDataObject.getChangeSummary());

            // Remove property or old changeSummary
            if(aDataObject.getContainer() != null) {
                aDataObject.detach(false, updateSequence);
            }

            // Need to know if the DO was deleted and being re-added later on in this method.
            // Since getChangeSummary().isDeleted() will be affected before the code in question
            // is hit, store the value now.
            boolean isDeleted = false;
            if (getChangeSummary() != null) {
                isDeleted = getChangeSummary().isDeleted(aDataObject);
            }

            // Check that the target object is not a changeSummary root - an internal set of an internal changeSummary
            /**
             * The following 2 update functions are kept separate for performance reasons in #6473342..
             * Alternate implementations could have used a Command pattern object or boolean states to enable a
             * single function to perform multiple operations.
             *
             * The DataGraph pointer on all subtrees are always updated regardless of the dataGraph value.
             * However, the ChangeSummary pointer is only updated if the dataObject(value) being updated is not
             * itself an internal changeSummary root.
             * For example: the following would not update the CS to null when we get to dataObject B - as it has its own changeSummary-B
             *  root
             *     -> B
             *             -> CS-B
             *             -> D (String)
             * But, the following would update the CS to CS-root when we get to dataObject B because the changeSummary root is above it
             *  root
             *     -> B
             *             -> D (String)
             *     -> CS-root
             */
            if ((getChangeSummary() != null) && (aDataObject.getType() != null) && //
                    (aDataObject.getType().getChangeSummaryProperty() == null)) {
                // Recursively set the current changeSummary and dataGraph - the CS root is above or absent
                aDataObject.updateChangeSummaryAndDataGraph(getChangeSummary(), getDataGraph());
            } else {
                // Recursively set the dataGraph when this level (value) is the CS root n the subtree
                if(aDataObject.getDataGraph() != getDataGraph()) {
                    aDataObject.updateDataGraph(getDataGraph());
                }
            }

            // add value as a property of (this)
            aDataObject._setContainer(this);
            aDataObject._setContainmentPropertyName(property.getName());

            // We don't setCreated for objects that were previously deleted
            if (!wasInNewCS && (getChangeSummary() != null) && !getChangeSummary().isDeleted(aDataObject)) {
                aDataObject._setCreated(true);
            }

            // If we are adding a previously deleted object, we must cancel out the isDeleted with an isCreated
            // so we end up with all isDeleted, isModified == false
            if ((getChangeSummary() != null) && getChangeSummary().isDeleted(aDataObject)) {
                // explicitly clear the oldSetting and clear the key:value pair in the deleted map
                aDataObject._setDeleted(false);

                // remove oldSetting from map only when we return to original position
                // by comparing an undo() with the projected model after set() - for now keep oldSettings
            }

            // modify container object
            _setModified(true);

            // In the case of re-adding a previously deleted/detached data object, we may need to update
            // the change summary's OriginalValueStores list to ensure isModified correctness
            if (isDeleted) {
                // May need to remove the corresponding entry in the originalValueStore map.
                // Compare the value store to the corresponding entry in the originalValueStore map.
                // Check to see if we're adding into the old parent (undo delete) or into a new parent (move)
                Map originalValueStores = getChangeSummary().getOriginalValueStores();
                ValueStore originalVS = ((ValueStore) originalValueStores.get(aDataObject));
                if (originalVS.equals(aDataObject._getCurrentValueStore())) {
                    // Remove the old value store from the change summary
                    originalValueStores.remove(aDataObject);
                }

                DataObject oldParentDO = getChangeSummary().getOldContainer(aDataObject);
                if (oldParentDO == this) {
                    // The data object was deleted and is now being re-added to same parent object.
                    // May need to remove the corresponding entry in the oldValueStore map.
                    // Compare the parent value store to the corresponding entry in the oldValueStore map.
                    ValueStore originalParentVS = ((ValueStore) originalValueStores.get(oldParentDO));
                    if (originalParentVS.equals(this._getCurrentValueStore())) {
                        // Value stores are equal, now make sure no ListWrappers have been modified
                        // For each typePropertyValue that is a ListWrapper in the currentValueStore, check
                        // the OriginalElements list in the CS to see if that wrapper exists in the list (indicating
                        // the wrapper's list was modified at some point) and if it does, compare the current list
                        // to that original list.  If they are equal, and the value stores are equal, the entries
                        // can be removed from the change summary so isModified will work correctly
                        Object prop;
                        Object oldList;
                        Map originalElements = getChangeSummary().getOriginalElements();
                        List oldParentProps = oldParentDO.getInstanceProperties();
                        for (int i=0; i<oldParentProps.size(); i++) {
                            prop = originalParentVS.getDeclaredProperty(i);
                            oldList = originalElements.get(prop);
                            if (oldList != null) {
                                List oldElements = (List) oldList;
                                List currentElements = ((ListWrapper) prop).getCurrentElements();
                                if (oldElements.size() != currentElements.size()) {
                                    // A ListWrapper has been modified - don't remove the old value store entry
                                    return;
                                }
                                Iterator elementIt = currentElements.iterator();
                                Iterator oldelementIt = oldElements.iterator();
                                while (elementIt.hasNext()) {
                                    if (elementIt.next() != oldelementIt.next()) {
                                        // A ListWrapper has been modified - don't remove the old value store entry
                                        return;
                                    }
                                }
                            }
                        }
                        // Remove the old value store from the change summary
                        originalValueStores.remove(oldParentDO);
                    }
                }
            }
        }
    }

    /**
     * INTERNAL:
     * update containment and the update the sequence value by default.
     * @param property
     * @param value
     */
    public void updateContainment(Property property, SDODataObject value) {
        updateContainment(property, value, true);
    }

    /**
     * INTERNAL:
     * Return the changeSummary logging state
     * @return
     */
    private boolean isLogging() {
        return ((changeSummary != null) && changeSummary.isLogging());
    }

    /**
     * INTERNAL:
     * Defined in SDO 2.01 spec on page 65 Externalizable function is called by
     * ObjectStream.writeObject() A replacement object for serialization can be
     * called here.
     * <p>Security Note:
     *     This public function exposes a data replacement vulnerability where an outside client
     *     can gain access and modify their non-final constants.
     *     We may need to wrap the GZIP streams in some sort of encryption when we are not
     *     using HTTPS or SSL/TLS on the wire.
     *
     * @see org.eclipse.persistence.sdo.SDOResolvable
     */
    public Object writeReplace() {
        // JIRA129: pass the helperContext to the constructor to enable non-static contexts
        return new SDOExternalizableDelegator(this, aHelperContext);
    }

    /**
     * INTERNAL:
     * Return the SDO Path from the root to the current internal node
     *
     * Prereq: We know that the targetObject will always have a parent as called
     * from getPath()
     *
     * Matching conditions:
     *   Iterate up the tree
     *   return a non-null string for the XPath at when we reach the root node
     *
     * Function is partially based on SDOCopyHelper.copy(DataObject dataObject)
     *
     * Performance: This function is O(log n) where n=# of children in the tree
     *
     * @param currentPath
     * @param targetObject
     * @param aSeparator (XPath separator is written only between elements not for the first call)
     * @return String (representing the XPath)
     */
    private String getPathPrivate(String currentPath,//
                                  SDODataObject targetObject,//
                                  String aSeparator) {
        // Base Case: check we are at the root (get parent first)
        SDODataObject aParent = targetObject.getContainer();
        if (aParent == null) {
            // check for indexed property if root is a ListWrapper
            return currentPath;
        }

        // Recursive Case: O(log(n)) recursive calls, 1 for each tree level
        // get parent property based on parent property name in target, property will always be set
        String aContainmentPropertyName = targetObject._getContainmentPropertyName();
        SDOProperty aChild = aParent.getInstanceProperty(aContainmentPropertyName);

        // Handle ListWrapper contained DataObjects
        if (aChild.isMany()) {
            int index = (aParent.getList(aChild)).indexOf(targetObject);

            return getPathPrivate(aContainmentPropertyName +//
                                  SDOConstants.SDO_XPATH_LIST_INDEX_OPEN_BRACKET +//
                                  (1 + index) +// [indexes] start at 1
                                  SDOConstants.SDO_XPATH_LIST_INDEX_CLOSE_BRACKET +//
                                  aSeparator +//
                                  currentPath,//
                                  aParent,//
                                  SDOConstants.SDO_XPATH_SEPARATOR_FRAGMENT);
        }

        // recursive call to non ListWrappers
        return getPathPrivate(aContainmentPropertyName +//
                              aSeparator +//
                              currentPath,//
                              aParent,//
                              SDOConstants.SDO_XPATH_SEPARATOR_FRAGMENT);
    }

    /**
     * INTERNAL:
     * Return an SDO Path string from root of the caller to itself
     * @return String
     */
    public String _getPath() {
        // Base Case: The internal node is actually the root
        if (getContainer() == null) {
            return SDOConstants.SDO_XPATH_TO_ROOT;
        } else {
            // Recursive Case: call private recursive reverse O(logn) traversal
            // function on root object
            return getPathPrivate(SDOConstants.EMPTY_STRING,//
                                  this,//
                                  SDOConstants.EMPTY_STRING);
        }
    }

    /**
     * INTERNAL:
     *
     * @return
     */
    private SDOProperty[] getInstancePropertiesArray() {
        if ((openContentProperties == null) || openContentProperties.isEmpty()) {
            return getType().getPropertiesArray();
        }

        SDOProperty[] props = getType().getPropertiesArray();
        SDOProperty[] ret = new SDOProperty[openContentProperties.size() + props.length];
        for (int i = 0; i < props.length; i++) {
            ret[i] = props[i];
        }
        for (int i = props.length; i < ret.length; i++) {
            ret[i] = (SDOProperty)openContentProperties.get(i - props.length);
        }
        return ret;
    }

    /**
     * INTERNAL:
     * Get the value of the property (open-content or declared)..
     * @param property
     * @return
     */
    public Object getPropertyInternal(Property property) {
        int index = ((SDOProperty)property).getIndexInType();
        if (index == -1) {
            return _getCurrentValueStore().getOpenContentProperty(property);
        } else {
            return _getCurrentValueStore().getDeclaredProperty(index);
        }
    }

    /**
     * INTERNAL:
     * Update the ValueStore with the new property value and update any sequence if it exists.
     * @param property
     * @param value
     * @param updateSequence (truncate call back from sequence when this function was called from sequence)
     */
    public void setPropertyInternal(SDOProperty property, Object value, boolean updateSequence) {
        /*
         * Update sequence object if element and without invoking a back pointer from sequence.add() to this dataObject.
         * The sequence != null required in case where non-public type.setSequenced(true) called after type define.
         * Assume: aHelperContext is assumed not to be null.
         * ChangeSummaries are not part of sequences.
         *
         * See JIRA-242, bug#6031657: A modify operation will need logic to either
         * 1 - update the existing setting in place
         * 2 - add a new setting and remove the previous one - TBD
         *
         * The following code needs to check for !attribute instead of !element because
         * we have 3 states( !attribute, !element and (!attribute && !element))
         * - two of which are valid for sequence setting creation.
         */
        Object origValue = getPropertyInternal(property);

        if (type.isSequenced() && updateSequence//
                 &&!property.getType().isChangeSummaryType() && !aHelperContext.getXSDHelper().isAttribute(property)) {
            // As we do for ListWrappers and DataObjects we will need to remove the previous setting if this set is actually a modify
            // keep sequence code here for backdoor sets
            if(property.isMany()) {
                getSequence().addSettingWithoutModifyingDataObject(property, value);
            } else {
                if(isSet(property)) {
                    getSequence().updateSettingWithoutModifyingDataObject(property, get(property), value);
                } else {
                    getSequence().addSettingWithoutModifyingDataObject(property, value);
                }
            }
        }

        int index = property.getIndexInType();
        if (index == -1) {
            _getCurrentValueStore().setOpenContentProperty(property, value);
        } else {
            _getCurrentValueStore().setDeclaredProperty(index, value);
            SDOProperty oppositeProperty = property.getOpposite();
            if(null != oppositeProperty) {
                if(null != value) {
                    if(property.isMany()) {
                        List<SDODataObject> valueList = (List<SDODataObject>) value;
                        for(SDODataObject valueDO : valueList) {
                            if(this != valueDO.get(oppositeProperty)) {
                                valueDO.setInternal(oppositeProperty, this, true);
                            }
                        }
                    } else {
                        SDODataObject valueDO = (SDODataObject) value;
                        if(oppositeProperty.isMany()) {
                            List oppositeList = valueDO.getList(oppositeProperty);
                            if(!oppositeList.contains(this)) {
                                oppositeList.add(this);
                            }
                        } else {
                            if(this != valueDO.get(oppositeProperty)) {
                                valueDO.setInternal(oppositeProperty, this, true);
                            }
                        }
                    }
                }

                if(null != origValue) {
                    if(property.isMany()) {
                        List<SDODataObject> origValueList = (List<SDODataObject>) origValue;
                        for(SDODataObject origValueDO : origValueList) {
                            if(null != origValueDO.get(oppositeProperty)) {
                                origValueDO.set(oppositeProperty, null);
                            }
                        }
                    } else {
                        DataObject origValueDO = (DataObject) origValue;
                        if(oppositeProperty.isMany()) {
                            List oppositeList = origValueDO.getList(oppositeProperty);
                            if(oppositeList.contains(this)) {
                                oppositeList.remove(this);
                            }
                        } else {
                            if(null != origValueDO.get(oppositeProperty)) {
                                origValueDO.set(oppositeProperty, null);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * INTERNAL:
     * Add the open content property into all 3 data structures.
     * Remove the property from the unset map.
     * @param property
     */
    public void addOpenContentProperty(Property property) {
        List theList = null;
        if (aHelperContext.getXSDHelper().isAttribute(property)) {
            theList = _getOpenContentPropertiesAttributes();
        }else {
            theList = _getOpenContentProperties();
        }

        if (!theList.contains(property)) {
            if (isLogging()) {
                getChangeSummary().removeUnsetOCProperty(this, property);
            }
            theList.add(property);
            getInstanceProperties().add(property);
            if (((SDOProperty)property).hasAliasNames()) {
                for (int i = 0, size = ((SDOProperty)property).getAliasNames().size(); i < size; i++) {
                    _getOpenContentAliasNamesMap().put((String)((SDOProperty)property).getAliasNames().get(i), property);
                }
            }
        }
    }

    /**
     * INTERNAL:
     * Remove the open content property (property) from all 3 data structures.
     * Add the property to the unset map.
     * We restore this OC property in undoChanges
     * @param property
     */
    public void removeOpenContentProperty(Property property) {
        // save OC property in changeSummary for use during cs.copy and undoChanges
        if (isLogging()) {
            getChangeSummary().setUnsetOCProperty(this, property);
        }

        // remove oc property
        _getOpenContentProperties().remove(property);
        _getOpenContentPropertiesAttributes().remove(property);
        getInstanceProperties().remove(property);
        if (((SDOProperty)property).hasAliasNames()) {
            for (int i = 0, size = ((SDOProperty)property).getAliasNames().size(); i < size; i++) {
               _getOpenContentAliasNamesMap().remove(((SDOProperty)property).getAliasNames().get(i));
            }
        }
    }

    /**
     * INTERNAL:
     * Return whether the property (open-content or declared) is set?
     * @param property
     * @return true if set, false otherwise
     */
    public boolean isSetInternal(Property property) {
        int index = ((SDOProperty)property).getIndexInType();
        if (index == -1) {
            return _getCurrentValueStore().isSetOpenContentProperty(property);
        } else {
            return _getCurrentValueStore().isSetDeclaredProperty(index);
        }
    }

    /**
     * INTERNAL:
     * Unset the property on the ValueStore interface and update the sequence if it exists.
     * Used by implementers that require
     * @param property
     * @param updateSequence
     */
    private void unsetInternal(Property property, boolean updateSequence) {
        if (property.isMany()) {
            getList(property).clear();
            if (property.isOpenContent()) {
                removeOpenContentProperty(property);
            } else {
                _getCurrentValueStore().unsetDeclaredProperty(((SDOProperty)property).getIndexInType());
            }
        } else {
            if (property.isOpenContent()) {
                _getCurrentValueStore().unsetOpenContentProperty(property);
                removeOpenContentProperty(property);
            } else {
                _getCurrentValueStore().unsetDeclaredProperty(((SDOProperty)property).getIndexInType());
            }
        }

        /*
         * Update sequence object if element and without invoking a back pointer from sequence.add() to this dataObject.
         * The sequence != null required in case where non-public type.setSequenced(true) called after type define
         */
        if (type.isSequenced() && (sequence != null) && updateSequence && aHelperContext.getXSDHelper().isElement(property)) {
            // remove all instances of the property from the sequence
            sequence.removeSettingWithoutModifyingDataObject(property);
        }
    }

    /**
     * INTERNAL:
     * Return the sdoref attribute value during unmarshaling
     * @return
     */
    public String _getSdoRef() {
        return sdoRef;
    }

    public void _setOpenContentPropertiesAttributes(List openContentPropertiesAttributes) {
        this.openContentPropertiesAttributes = openContentPropertiesAttributes;
    }

    public List _getOpenContentPropertiesAttributes() {
        if (openContentPropertiesAttributes == null) {
            openContentPropertiesAttributes = new ArrayList();
        }
        return openContentPropertiesAttributes;
    }

    @Override
    public List<Setting> getSettings() {
        if(null != sequence) {
            return getSequence().getSettings();
        } else {
            return null;
        }
    }

    public void _setSdoRef(String newRef) {
        sdoRef = newRef;
    }

    /**
     * Convenience method that unwraps a list and returns the first element,
     * if necessary.
     *
     * @param prop
     * @param val
     * @return
     */
    private Object unwrapListValue(Property prop, Object val) {
        if (prop == null || val == null) {
            return null;
        }
        if (! (prop.isMany() &&  val instanceof ListWrapper) ) {
            return val;
        }
        List l = (ListWrapper) val;
        if (l.size() == 0) {
            return null;
        }
        return l.get(0);
    }

}
