| /* |
| * Copyright (c) 1998, 2021 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.helper; |
| |
| import commonj.sdo.DataObject; |
| import commonj.sdo.Property; |
| import commonj.sdo.helper.EqualityHelper; |
| import commonj.sdo.helper.HelperContext; |
| import commonj.sdo.impl.HelperProvider; |
| import java.util.Iterator; |
| import java.util.List; |
| import org.eclipse.persistence.sdo.SDODataObject; |
| import org.eclipse.persistence.sdo.SDOProperty; |
| import org.eclipse.persistence.sdo.SDOSequence; |
| import org.eclipse.persistence.oxm.XMLRoot; |
| import org.eclipse.persistence.oxm.sequenced.Setting; |
| |
| /** |
| * <p><b>Purpose</b>: A helper class for checking deep or shallow equality of DataObjects.</p> |
| * <p>ChangeSummary is not in scope for equality checking. |
| * |
| * @see org.eclipse.persistence.sdo.SDODataObject |
| * @since Oracle TopLink 11.1.1.0.0 |
| * |
| */ |
| public class SDOEqualityHelper implements EqualityHelper { |
| |
| /* |
| References: |
| SDO59-DeepCopy.doc |
| SDO_Ref_BiDir_Relationships_DesignSpec.doc |
| http://files.oraclecorp.com/content/MySharedFolders/ST%20Functional%20Specs/AS11gR1/TopLink/SDO/SDO_Ref_BiDir_Relationships_DesignSpec.doc |
| |
| 09/12/06 - Add bidirectional property support in compareProperty() |
| 09/18/06 - remove shallow opposite property check - just return true if both objects are set and are DO's |
| 01/29/07 - #5852525 handle null properties with isSet=true |
| 04/11/07 - Implement Sequence functionality |
| */ |
| |
| /** hold the context containing all helpers so that we can preserve inter-helper relationships */ |
| private HelperContext aHelperContext; |
| |
| /** |
| * INTERNAL: |
| * This default constructor must be used in conjunction with the setHelperContext() function. |
| * The custom constructor that takes a HelperContext parameter is recommended over this default constructor. |
| */ |
| public SDOEqualityHelper() { |
| } |
| |
| /** |
| * Constructor that takes in a HelperContext instance that contains this equalityHelper.<br> |
| * This is the recommended constructor. |
| * @param aContext |
| */ |
| public SDOEqualityHelper(HelperContext aContext) { |
| aHelperContext = aContext; |
| } |
| |
| /** |
| * <p>Two DataObjects are equalShallow if |
| * they have the same {@link DataObject#getType Type} |
| * and all their compared Properties are equal. |
| * The set of Properties compared are the |
| * {@link DataObject#getInstanceProperties() instance properties} |
| * where property.getType().isDataType() is true |
| * and property.getType() is not ChangeSummaryType. |
| * <br>Two of these Property values are equal if they are both not |
| * {@link DataObject#isSet(Property) set}, or set to an equal value |
| * dataObject1.get(property).equals(dataObject2.get(property)) |
| * <br>If the type is a sequenced type, the sequence entries must be the same. |
| * For each entry x in the sequence where the property is used in the comparison, |
| * dataObject1.getSequence().getValue(x).equals( |
| * dataObject2.getSequence().getValue(x)) and |
| * dataObject1.getSequence().getProperty(x) == |
| * dataObject2.getSequence().getProperty(x) |
| * must be true. |
| * </p> |
| * Returns true the objects have the same Type and all values of all compared Properties are equal. |
| * @param dataObject1 DataObject to be compared |
| * @param dataObject2 DataObject to be compared |
| * @return true the objects have the same Type and all values of all compared Properties are equal. |
| */ |
| @Override |
| public boolean equalShallow(DataObject dataObject1, DataObject dataObject2) { |
| return compareDataObjects(dataObject1, dataObject2, false); |
| } |
| |
| /** |
| * <p>Two DataObjects are equal(Deep) if they are equalShallow, |
| * all their compared Properties are equal, and all reachable DataObjects in their |
| * graphs excluding containers are equal. |
| * The set of Properties compared are the |
| * {@link DataObject#getInstanceProperties() instance properties} |
| * where property.getType().isDataType() is false, |
| * and is not a container property, ie !property.getOpposite().isContainment() |
| * <br>Two of these Property values are equal if they are both not |
| * {@link DataObject#isSet(Property) set}, or all the DataObjects |
| * they refer to are {@link #equal(DataObject, DataObject) equal} in the |
| * context of dataObject1 and dataObject2. |
| * <br>Note that properties to a containing DataObject are not compared |
| * which means two DataObject trees can be equal even if their containers are not equal. |
| * <br>If the type is a sequenced type, the sequence entries must be the same. |
| * For each entry x in the sequence where the property is used in the comparison, |
| * equal(dataObject1.getSequence().getValue(x), |
| * dataObject2.getSequence().getValue(x)) and |
| * dataObject1.getSequence().getProperty(x) == dataObject2.getSequence().getProperty(x) |
| * must be true. |
| * </p><p> |
| * A DataObject directly or indirectly referenced by dataObject1 or dataObject2 |
| * can only be equal to exactly one DataObject directly or indirectly referenced |
| * by dataObject1 or dataObject2, respectively. |
| * This ensures that dataObject1 and dataObject2 are equal if the graph formed by |
| * all their referenced DataObjects have the same shape. |
| * </p> |
| * Returns true if the trees of DataObjects are equal(Deep). |
| * @param dataObject1 DataObject to be compared |
| * @param dataObject2 DataObject to be compared |
| * @return true if the trees of DataObjects are equal(Deep). |
| */ |
| @Override |
| public boolean equal(DataObject dataObject1, DataObject dataObject2) { |
| return compareDataObjects(dataObject1, dataObject2, true); |
| } |
| |
| /** |
| * INTERNAL: |
| * Separately, checks the declared properties and open content properties. |
| * @param dataObject1 the DataObject to be compared |
| * @param dataObject2 the DataObject to be compared |
| * @param isDeep if comparison is deep |
| * @return true if two DataObjects meet requirements of shallow equal or deep equal |
| */ |
| private boolean compareDataObjects(DataObject dataObject1, DataObject dataObject2, boolean isDeep) { |
| // Note that properties to a containing DataObject are not compared which |
| // means two DataObject trees can be equal even if their containers are not equal. |
| |
| if (null == dataObject1) { |
| return dataObject2 == null; |
| } |
| if (null == dataObject2) {// dataObject1 is not null while dataObject2 is null |
| return false; |
| } |
| |
| // they don't have the same type !! assumption is the same for now, but may be changed to use equals !! |
| if (dataObject1.getType() != dataObject2.getType()) { |
| return false; |
| } else {// type is the same for them |
| |
| /** |
| * We have 2 strategies here - sequences or dataObjects first? |
| * Analysis: A sequence and its dataObject share a subset of element properties. |
| * The disjoint set for sequences includes the set of unstructured text settings. |
| * The disjoint set for dataObject includes all attribute properties. |
| * |
| * The choice of which to do first comes down to performance |
| */ |
| /** |
| * Compare sequences - only out of order settings and unstructured |
| * text will not be picked up by a dataObject comparison. |
| * There is no need to check sequenced state on both objects because they share the same SDOType instance. |
| */ |
| if (dataObject1.getType().isSequenced()) { |
| if (!compareSequences((SDOSequence) dataObject1.getSequence(), (SDOSequence) dataObject2.getSequence(), isDeep)) { |
| return false; |
| } |
| } |
| |
| // First, compare properties that are not open content. |
| // Attribute property differences will not be picked up in the sequence comparison |
| if (!compare(dataObject1, dataObject2, isDeep, dataObject1.getType().getProperties())) { |
| return false; |
| } |
| |
| // Second, compare open content properties |
| List properties_1 = ((SDODataObject)dataObject1)._getOpenContentProperties(); |
| List properties_2 = ((SDODataObject)dataObject2)._getOpenContentProperties(); |
| |
| // different size of open content properties |
| if ((properties_1.size() != properties_2.size()) || !properties_1.containsAll(properties_2)) { |
| return false; |
| } |
| if (!compare(dataObject1, dataObject2, isDeep, properties_1)) { |
| return false; |
| } |
| |
| List attrProperties_1 = ((SDODataObject)dataObject1)._getOpenContentPropertiesAttributes(); |
| List attrProperties_2 = ((SDODataObject)dataObject2)._getOpenContentPropertiesAttributes(); |
| |
| // different size of open content properties |
| if ((attrProperties_1.size() != attrProperties_2.size()) || !attrProperties_1.containsAll(attrProperties_2)) { |
| return false; |
| } |
| if (!compare(dataObject1, dataObject2, isDeep, attrProperties_1)) { |
| return false; |
| } |
| |
| return true; |
| } |
| } |
| |
| /** |
| * INTERNAL: Return whether the 2 sequences are equal. |
| * Element properties and unstructured text will be compared - attributes are out of scope. |
| * <p> |
| * For shallow equal - only dataType=true objects are compared, DataObject values are ignored but should be defaults. |
| * Note: A setting object should handle its own isEqual() behavior |
| * |
| * @param aSequence |
| * @param aSequenceCopy |
| * @param isDeep |
| * @return |
| */ |
| private boolean compareSequences(SDOSequence aSequence, SDOSequence aSequenceCopy, boolean isDeep) { |
| // corner case: isSequenced set to true after type definition had not sequence |
| if ((null == aSequence) && (null == aSequenceCopy)) { |
| return true; |
| } |
| |
| // both sequences must be null |
| if ((null == aSequence) || (null == aSequenceCopy)) { |
| return false; |
| } |
| |
| // for shallow equal - match whether we skipped creating settings or set value=null for shallow copies |
| if (isDeep && aSequence.size() != aSequenceCopy.size()) { |
| return false; |
| } |
| // the settings inside the sequence must be new objects |
| List<Setting> originalSettingsList = aSequence.getSettings(); |
| List<Setting> copySettingsList = aSequenceCopy.getSettings(); |
| if ((null == originalSettingsList) || (null == copySettingsList)) { |
| return false; |
| } |
| |
| SDOProperty originalProperty = null; |
| SDOProperty copyProperty = null; |
| |
| /** |
| * For shallow equal when dataType is false we do not check this setting, |
| * the value will be unset (default value) in the shallow copy. |
| * orig v1=String v2=DataObject v3=String |
| * shallowcopy v1=String v2=null(default) v3=String |
| * deepcopy v1=String v2=DataObject v3=String |
| */ |
| if (isDeep) { |
| for (int index = 0, size = aSequence.size(); index < size; index++) { |
| originalProperty = aSequence.getProperty(originalSettingsList.get(index)); |
| copyProperty = aSequenceCopy.getProperty(copySettingsList.get(index)); |
| |
| // we must handle null properties that represent unstructured text |
| // both null = unstructured |
| // one null = invalid state (return not equal) |
| // both !null = valid state (check equality) |
| if (((null == originalProperty) && (null != copyProperty)) || ((null != originalProperty) && (null == copyProperty))) { |
| return false; |
| } |
| |
| // the property field on the setting must point to the same property instance as the original |
| if (!arePropertiesEqual(originalProperty, copyProperty)) {// handle both properties == null |
| return false; |
| } |
| |
| Object originalValue = aSequence.getValue(index); |
| Object copyValue = aSequenceCopy.getValue(index); |
| |
| // for unstructuredText (null property) and simple dataTypes we check equality directly |
| if ((null == originalProperty) || originalProperty.getType().isDataType()) { |
| // if one of the values is null return false |
| if (((null == originalValue) && (null != copyValue)) || // |
| ((null != originalValue) && (null == copyValue))) { |
| return false; |
| } |
| // if both values are null - they are equal |
| if ((null != originalValue) && !originalValue.equals(copyValue)) {// we can also use !.equals() |
| return false; |
| } |
| } else { |
| // For complex types |
| // we do not need to check deep equality on dataObjects twice here, just check instances |
| // because the dataObject compare will iterate all the properties of each dataObject |
| // only compare DataObjects when in a deep equal |
| if (isDeep) { |
| if ((null != originalValue) && (null != copyValue)) { |
| // setting.isSet is ignored for sequences |
| // perform a deep equal on the single item |
| // the values may not match their types - return false instead of a CCE |
| if (originalValue instanceof DataObject && copyValue instanceof DataObject) { |
| if (!equal((DataObject) originalValue, (DataObject) copyValue)) { |
| return false; |
| } |
| } else if (originalValue instanceof XMLRoot && copyValue instanceof XMLRoot) { |
| XMLRoot originalXMLRoot = (XMLRoot) originalValue; |
| XMLRoot copyXMLRoot = (XMLRoot) copyValue; |
| // compare local names of XMLRoot objects |
| if (!originalXMLRoot.getLocalName().equals(copyXMLRoot.getLocalName())) { |
| return false; |
| } |
| // compare uris of XMLRoot objects |
| if (!originalXMLRoot.getNamespaceURI().equals(copyXMLRoot.getNamespaceURI())) { |
| return false; |
| } |
| |
| Object originalUnwrappedValue = (originalXMLRoot).getObject(); |
| Object copyUnwrappedValue = (copyXMLRoot).getObject(); |
| if (originalUnwrappedValue instanceof DataObject && copyUnwrappedValue instanceof DataObject) { |
| if (!equal((DataObject) originalUnwrappedValue, (DataObject) copyUnwrappedValue)) { |
| return false; |
| } |
| } |
| } else { |
| return false; |
| } |
| } else { |
| // both values must be null to be equal |
| if (((null == originalValue) && (null != copyValue)) || ((null == copyValue) && (null != originalValue))) { |
| return false; |
| } |
| } |
| } else { |
| // For DataObjects in general anything that is deep equal is also shallow equal - but not the reverse. |
| // In the case of shallow equal on sequences. We can ignore the state of the 2 complex objects. |
| // UC1: if aSequenceCopy setting was from a shallowCopy then it will be unset. |
| // UC2: if aSequenceCopy setting was from a deepCopy or a reversed argument shallowCopy then it may be unset or set. |
| // We will not check for a default value on either sequence setting. |
| } |
| } |
| } |
| } else { |
| int cpyIdx = 0; |
| boolean locatedSetting; |
| // compare settings |
| for (int idx = 0; idx < aSequence.getSettings().size(); idx++) { |
| SDOProperty nextProperty = aSequence.getProperty(idx); |
| if (nextProperty == null || nextProperty.getType().isDataType()) { |
| // compare to the next copy datatype setting |
| Object nextValue = aSequence.getValue(idx); |
| locatedSetting = false; |
| for (; cpyIdx < aSequenceCopy.getSettings().size(); cpyIdx++) { |
| SDOProperty nextCopyProperty = aSequenceCopy.getProperty(cpyIdx); |
| if (nextCopyProperty == null || nextCopyProperty.getType().isDataType()) { |
| // at this point we need to compare the two Settings and their properties |
| Object nextCopyValue = aSequenceCopy.getValue(cpyIdx); |
| if (nextValue == nextCopyValue && arePropertiesEqual(nextProperty, nextCopyProperty)) { |
| locatedSetting = true; |
| } |
| cpyIdx++; |
| break; |
| } |
| } |
| if (!locatedSetting) { |
| return false; |
| } |
| } |
| } |
| // at this point there should not be any more copy settings |
| if (getIndexOfNextDataTypeSetting(aSequenceCopy, cpyIdx) != -1) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Convenience method for returning the index of the next DataType |
| * Setting in a given sequence. |
| * |
| * @param aSequence |
| * @param index |
| * @return the next Setting after index in the sequence, or -1 if none |
| */ |
| private int getIndexOfNextDataTypeSetting(SDOSequence aSequence, int index) { |
| List<Setting> settings = aSequence.getSettings(); |
| for (int i = index; i < settings.size(); i++) { |
| SDOProperty nextProperty = aSequence.getProperty(i); |
| if (nextProperty == null || nextProperty.getType().isDataType()) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * INTERNAL: |
| * Convenience method that compares two Property objects for equality |
| * @param prop1 |
| * @param prop2 |
| * @return |
| */ |
| private boolean arePropertiesEqual(Property prop1, Property prop2) { |
| if (((null == prop1) && (null != prop2)) || ((null != prop1) && (null == prop2))) { |
| return false; |
| } |
| // the property field on the setting must point to the same property instance as the original |
| if (!prop1.equals(prop2)) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * iterativly, compare the values of shared properties in two target DataObjects |
| * @param dataObject1 the DataObject to be compared |
| * @param dataObject2 the DataObject to be compared |
| * @param isDeep if comparison is deep |
| * @param properties list of properties shared by two DataObjects |
| * @return true if two DataObjects meet requirements of shallow equal or deep equal |
| */ |
| private boolean compare(DataObject dataObject1, DataObject dataObject2, boolean isDeep, List properties) { |
| Iterator iterProperties = properties.iterator(); |
| |
| while (iterProperties.hasNext()) { |
| // !! assumption is two dataobjects share the same property !! |
| SDOProperty p = (SDOProperty)iterProperties.next(); |
| if (!compareProperty(dataObject1, dataObject2, isDeep, p)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Recursively [PreOrder sequence] compare corresponding properties of 2 DataObjects |
| * check value of property p when p is not a many type property. |
| * @param dataObject1 the DataObject to be compared |
| * @param dataObject2 the DataObject to be compared |
| * @param isDeep if comparison is deep |
| * @param p the property shared by two DataObjects |
| * @return true if two DataObjects meet requirements of shallow equal or deep equal |
| */ |
| private boolean compareProperty(DataObject dataObject1, DataObject dataObject2, boolean isDeep, SDOProperty p) { |
| if (p.isMany()) { |
| return compareManyProperty(dataObject1, dataObject2, isDeep, p); |
| } |
| |
| // base step |
| if (p.getType().isDataType() && !p.getType().isChangeSummaryType()) { |
| boolean isSet1 = dataObject1.isSet(p); |
| boolean isSet2 = dataObject2.isSet(p); |
| if (!isSet1 && !isSet2) { |
| return true; |
| } |
| if (isSet1 && isSet2) { |
| Object aProperty1 = dataObject1.get(p); |
| Object aProperty2 = dataObject2.get(p); |
| |
| // if both properties are null then return equality |
| if (null == aProperty1) { |
| return aProperty2 == null; |
| } |
| if (null == aProperty2) {// aProperty1 is not null while aPropertyt2 is null |
| return false; |
| } |
| |
| return aProperty1.equals(aProperty2); |
| } |
| return false; |
| } |
| |
| // recursive step |
| if (isDeep && !p.getType().isChangeSummaryType()) { |
| if (!dataObject1.isSet(p) && !dataObject2.isSet(p)) { |
| return true; |
| } |
| |
| // #5852525 handle null properties with isSet=true |
| if (!p.getType().isDataType()) { |
| if (null == p.getOpposite()) { |
| // compare unidirectional or containment=true properties - recursively |
| return compareDataObjects(dataObject1.getDataObject(p), dataObject2.getDataObject(p), isDeep); |
| } else { |
| // 20060906: handle bidirectional properties |
| // the check across to another branch in the tree will only go 1 shallow level deep |
| // avoiding an infinite recursive loop and deferring the check for that branch when it |
| // is encountered in its PreOrder sequence. |
| return compareDataObjects(dataObject1.getDataObject(p), dataObject2.getDataObject(p), false); |
| |
| // Spec 3.10 "All reachable DOs in their graphs are equal" |
| // return true since both do1.get(p) and do2.get(p) are set and are instances of DO |
| //return true; |
| } |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * iteratively check value of property p when p is many type property. |
| * @param dataObject1 the DataObject to be compared |
| * @param dataObject2 the DataObject to be compared |
| * @param isDeep if comparison is deep |
| * @param p the property shared by two DataObjects |
| * @return true if two DataObjects meet requirements of shallow equal or deep equal |
| */ |
| private boolean compareManyProperty(DataObject dataObject1, DataObject dataObject2, boolean isDeep, Property p) { |
| List l1 = dataObject1.getList(p); |
| List l2 = dataObject2.getList(p); |
| |
| if (p.getType().isDataType()) { |
| if (dataObject1.isSet(p) != dataObject2.isSet(p)) { |
| return false; |
| } |
| if (l1.size() != l2.size()) { |
| return false; |
| } |
| for (int i = 0; i < l1.size(); i++) { |
| Object o1 = l1.get(i); |
| Object o2 = l2.get(i); |
| if (!o1.equals(o2)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| if (isDeep) { |
| if (dataObject1.isSet(p) != dataObject2.isSet(p)) { |
| return false; |
| } |
| if (l1.size() != l2.size()) { |
| return false; |
| } |
| |
| // !! can a list contains duplicated objects: {A, A'} and A equals A' or {A, A} |
| for (int i = 0, size = l1.size(); i < size; i++) { |
| DataObject o1_l1 = (DataObject)l1.get(i); |
| DataObject o2_l2 = (DataObject)l2.get(i); |
| if (!isADataObjectInList(o1_l1, l2)) { |
| return false; |
| } |
| if (!isADataObjectInList(o2_l2, l1)) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * @param dataObject1 |
| * @param objects |
| * @return |
| */ |
| private boolean isADataObjectInList(DataObject dataObject1, List objects) { |
| Iterator iterObjects = objects.iterator(); |
| while (iterObjects.hasNext()) { |
| DataObject dataObject2 = (DataObject)iterObjects.next(); |
| if (compareDataObjects(dataObject1, dataObject2, true)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * INTERNAL: |
| * Return the helperContext containing this equalityHelper. |
| * @return |
| */ |
| public HelperContext getHelperContext() { |
| if(null == aHelperContext) { |
| aHelperContext = HelperProvider.getDefaultContext(); |
| } |
| return aHelperContext; |
| } |
| |
| /** |
| * INTERNAL: |
| * Set the helperContext if this equalityHelper was created using the default constructor. |
| * @param helperContext |
| */ |
| public void setHelperContext(HelperContext helperContext) { |
| aHelperContext = helperContext; |
| } |
| } |