blob: 4b9dd194bedc7ea6562d8e6d9e9416eaaa75e8c8 [file] [log] [blame]
/*
* 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.eis.mappings;
import java.util.List;
import org.eclipse.persistence.eis.EISCollectionChangeRecord;
import org.eclipse.persistence.eis.EISOrderedCollectionChangeRecord;
import org.eclipse.persistence.internal.queries.ContainerPolicy;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.sessions.ChangeRecord;
import org.eclipse.persistence.internal.sessions.MergeManager;
import org.eclipse.persistence.internal.sessions.ObjectChangeSet;
import org.eclipse.persistence.internal.sessions.UnitOfWorkImpl;
import org.eclipse.persistence.mappings.DatabaseMapping;
/**
* INTERNAL:
* Helper class to consolidate all the heinous comparing
* and merging code for the EIS one to many mappings.
*/
public class EISOneToManyMappingHelper {
/** The mapping that needs help comparing and merging. */
private EISOneToManyMapping mapping;
private static Object XXX = new Object();// object used to marked cleared out slots when comparing
/**
* Constructor.
*/
public EISOneToManyMappingHelper(EISOneToManyMapping mapping) {
super();
this.mapping = mapping;
}
/**
* Convenience method.
*/
private Object buildAddedElementFromChangeSet(Object changeSet, MergeManager mergeManager, AbstractSession targetSession) {
return this.getMapping().buildAddedElementFromChangeSet(changeSet, mergeManager, targetSession);
}
/**
* Convenience method.
*/
private Object buildChangeSet(Object element, ObjectChangeSet owner, AbstractSession session) {
return this.getMapping().buildChangeSet(element, owner, session);
}
/**
* Convenience method.
*/
private Object buildElementFromElement(Object element, MergeManager mergeManager, AbstractSession targetSession) {
return this.getMapping().buildElementFromElement(element, mergeManager, targetSession);
}
/**
* Convenience method.
*/
private Object buildRemovedElementFromChangeSet(Object changeSet, MergeManager mergeManager, AbstractSession targetSession) {
return this.getMapping().buildRemovedElementFromChangeSet(changeSet, mergeManager, targetSession);
}
/**
* Compare the attributes. Return true if they are alike.
* Assume the passed-in attributes are non-null.
*/
private boolean compareAttributeValues(Object collection1, Object collection2, AbstractSession session) {
ContainerPolicy cp = this.getContainerPolicy();
if (cp.sizeFor(collection1) != cp.sizeFor(collection2)) {
return false;
}
// if they are both empty, go no further...
if (cp.sizeFor(collection1) == 0) {
return true;
}
if (cp.hasOrder()) {
return this.compareAttributeValuesWithOrder(collection1, collection2, session);
} else {
return this.compareAttributeValuesWithoutOrder(collection1, collection2, session);
}
}
/**
* Build and return the change record that results
* from comparing the two collection attributes.
* The order of the elements is significant.
*/
private ChangeRecord compareAttributeValuesForChangeWithOrder(Object cloneCollection, Object backupCollection, ObjectChangeSet owner, AbstractSession session) {
ContainerPolicy cp = this.getContainerPolicy();
List cloneVector = cp.vectorFor(cloneCollection, session);// convert it to a Vector so we can preserve the order and use indexes
List backupVector = cp.vectorFor(backupCollection, session);// "clone" it so we can clear out the slots
EISOrderedCollectionChangeRecord changeRecord = new EISOrderedCollectionChangeRecord(owner, this.getAttributeName(), this.getDatabaseMapping());
for (int i = 0; i < cloneVector.size(); i++) {
Object cloneElement = cloneVector.get(i);
boolean found = false;
for (int j = 0; j < backupVector.size(); j++) {
if (this.compareElementsForChange(cloneElement, backupVector.get(j), session)) {
// the clone element was found in the backup collection
found = true;
backupVector.set(j, XXX);// clear out the matching backup element
changeRecord.addMovedChangeSet(this.buildChangeSet(cloneElement, owner, session), j, i);
break;// matching backup element found - skip the rest of them
}
}
if (!found) {
// the clone element was not found, so it must have been added
changeRecord.addAddedChangeSet(this.buildChangeSet(cloneElement, owner, session), i);
}
}
for (int i = 0; i < backupVector.size(); i++) {
Object backupElement = backupVector.get(i);
if (backupElement != XXX) {
// the backup element was not in the clone collection, so it must have been removed
changeRecord.addRemovedChangeSet(this.buildChangeSet(backupElement, owner, session), i);
}
}
if (changeRecord.hasChanges()) {
return changeRecord;
} else {
return null;
}
}
/**
* Build and return the change record that results
* from comparing the two collection attributes.
* Ignore the order of the elements.
*/
private ChangeRecord compareAttributeValuesForChangeWithoutOrder(Object cloneCollection, Object backupCollection, ObjectChangeSet owner, AbstractSession session) {
ContainerPolicy cp = this.getContainerPolicy();
List backupVector = cp.vectorFor(backupCollection, session);// "clone" it so we can clear out the slots
EISCollectionChangeRecord changeRecord = new EISCollectionChangeRecord(owner, this.getAttributeName(), this.getDatabaseMapping());
for (Object cloneIter = cp.iteratorFor(cloneCollection); cp.hasNext(cloneIter);) {
Object cloneElement = cp.next(cloneIter, session);
boolean found = false;
for (int i = 0; i < backupVector.size(); i++) {
if (this.compareElementsForChange(cloneElement, backupVector.get(i), session)) {
// the clone element was found in the backup collection
found = true;
backupVector.set(i, XXX);// clear out the matching backup element
if (this.mapKeyHasChanged(cloneElement, session)) {
changeRecord.addChangedMapKeyChangeSet(this.buildChangeSet(cloneElement, owner, session));
}
break;// matching backup element found - skip the rest of them
}
}
if (!found) {
// the clone element was not found, so it must have been added
changeRecord.addAddedChangeSet(this.buildChangeSet(cloneElement, owner, session));
}
}
for (int i = 0; i < backupVector.size(); i++) {
Object backupElement = backupVector.get(i);
if (backupElement != XXX) {
// the backup element was not in the clone collection, so it must have been removed
changeRecord.addRemovedChangeSet(this.buildChangeSet(backupElement, owner, session));
}
}
if (changeRecord.hasChanges()) {
return changeRecord;
} else {
return null;
}
}
/**
* Compare the attributes. Return true if they are alike.
* The order of the elements is significant.
*/
private boolean compareAttributeValuesWithOrder(Object collection1, Object collection2, AbstractSession session) {
ContainerPolicy cp = this.getContainerPolicy();
Object iter1 = cp.iteratorFor(collection1);
Object iter2 = cp.iteratorFor(collection2);
while (cp.hasNext(iter1)) {
if (!this.compareElements(cp.next(iter1, session), cp.next(iter2, session), session)) {
return false;
}
}
return true;
}
/**
* Compare the attributes. Return true if they are alike.
* Ignore the order of the elements.
*/
private boolean compareAttributeValuesWithoutOrder(Object collection1, Object collection2, AbstractSession session) {
ContainerPolicy cp = this.getContainerPolicy();
List vector2 = cp.vectorFor(collection2, session);// "clone" it so we can clear out the slots
for (Object iter1 = cp.iteratorFor(collection1); cp.hasNext(iter1);) {
Object element1 = cp.next(iter1, session);
boolean found = false;
for (int i = 0; i < vector2.size(); i++) {
if (this.compareElements(element1, vector2.get(i), session)) {
found = true;
vector2.set(i, XXX);// clear out the matching element
break;// matching element found - skip the rest of them
}
}
if (!found) {
return false;
}
}
// look for elements that were not in collection1
for (Object value : vector2) {
if (value != XXX) {
return false;
}
}
return true;
}
/**
* Convenience method.
* Check for null values before delegating to the mapping.
*/
private boolean compareElements(Object element1, Object element2, AbstractSession session) {
if ((element1 == null) && (element2 == null)) {
return true;
}
if ((element1 == null) || (element2 == null)) {
return false;
}
if (element2 == XXX) {// if element2 was marked as cleared out, it is not a match
return false;
}
return this.getMapping().compareElements(element1, element2, session);
}
/**
* Convenience method.
* Check for null values before delegating to the mapping.
*/
private boolean compareElementsForChange(Object element1, Object element2, AbstractSession session) {
if ((element1 == null) && (element2 == null)) {
return true;
}
if ((element1 == null) || (element2 == null)) {
return false;
}
if (element2 == XXX) {// if element2 was marked as cleared out, it is not a match
return false;
}
return this.getMapping().compareElementsForChange(element1, element2, session);
}
/**
* INTERNAL:
* Build and return the change record that results
* from comparing the two collection attributes.
*/
public ChangeRecord compareForChange(Object clone, Object backup, ObjectChangeSet owner, AbstractSession session) {
ContainerPolicy cp = this.getContainerPolicy();
Object cloneCollection = this.getRealCollectionAttributeValueFromObject(clone, session);
Object backupCollection = null;
if (owner.isNew()) {
backupCollection = cp.containerInstance(1);
} else {
backupCollection = this.getRealCollectionAttributeValueFromObject(backup, session);
}
if (cp.hasOrder()) {
return this.compareAttributeValuesForChangeWithOrder(cloneCollection, backupCollection, owner, session);
} else {
return this.compareAttributeValuesForChangeWithoutOrder(cloneCollection, backupCollection, owner, session);
}
}
/**
* INTERNAL:
* Compare the attributes belonging to this mapping for the objects.
*/
public boolean compareObjects(Object object1, Object object2, AbstractSession session) {
return this.compareAttributeValues(this.getRealCollectionAttributeValueFromObject(object1, session), this.getRealCollectionAttributeValueFromObject(object2, session), session);
}
/**
* Convenience method.
*/
private String getAttributeName() {
return this.getMapping().getAttributeName();
}
/**
* Convenience method.
*/
private ContainerPolicy getContainerPolicy() {
return this.getMapping().getContainerPolicy();
}
/**
* INTERNAL:
* Return the mapping, casted a bit more generally.
*/
public DatabaseMapping getDatabaseMapping() {
return this.getMapping();
}
/**
* INTERNAL:
* Return the mapping.
*/
public EISOneToManyMapping getMapping() {
return mapping;
}
/**
* Convenience method.
*/
private Object getRealCollectionAttributeValueFromObject(Object object, AbstractSession session) {
return this.getMapping().getRealCollectionAttributeValueFromObject(object, session);
}
/**
* Convenience method.
*/
private boolean mapKeyHasChanged(Object element, AbstractSession session) {
return this.getMapping().mapKeyHasChanged(element, session);
}
/**
* INTERNAL:
* Merge changes from the source to the target object.
*/
public void mergeChangesIntoObject(Object target, ChangeRecord changeRecord, Object source, MergeManager mergeManager, AbstractSession targetSession) {
if (this.getContainerPolicy().hasOrder()) {
this.mergeChangesIntoObjectWithOrder(target, changeRecord, source, mergeManager, targetSession);
} else {
this.mergeChangesIntoObjectWithoutOrder(target, changeRecord, source, mergeManager, targetSession);
}
}
/**
* Merge changes from the source to the target object.
* Simply replace the entire target collection.
*/
private void mergeChangesIntoObjectWithOrder(Object target, ChangeRecord changeRecord, Object source, MergeManager mergeManager, AbstractSession targetSession) {
ContainerPolicy cp = this.getContainerPolicy();
AbstractSession session = mergeManager.getSession();
List changes = ((EISOrderedCollectionChangeRecord)changeRecord).getNewCollection();
Object targetCollection = cp.containerInstance(changes.size());
for (Object changed : changes) {
Object targetElement = buildAddedElementFromChangeSet(changed, mergeManager, targetSession);
cp.addInto(targetElement, targetCollection, session);
}
// reset the attribute to allow for set method to re-morph changes if the collection is not being stored directly
this.setRealAttributeValueInObject(target, targetCollection);
}
/**
* Merge changes from the source to the target object.
* Make the necessary removals and adds and map key modifications.
*/
private void mergeChangesIntoObjectWithoutOrder(Object target, ChangeRecord changeRecord, Object source, MergeManager mergeManager, AbstractSession targetSession) {
EISCollectionChangeRecord sdkChangeRecord = (EISCollectionChangeRecord)changeRecord;
ContainerPolicy cp = this.getContainerPolicy();
AbstractSession session = mergeManager.getSession();
Object targetCollection = null;
if (sdkChangeRecord.getOwner().isNew()) {
targetCollection = cp.containerInstance(sdkChangeRecord.getAdds().size());
} else {
targetCollection = this.getRealCollectionAttributeValueFromObject(target, session);
}
List removes = sdkChangeRecord.getRemoves();
List adds = sdkChangeRecord.getAdds();
List changedMapKeys = sdkChangeRecord.getChangedMapKeys();
synchronized (targetCollection) {
for (Object removed : removes) {
Object removeElement = buildRemovedElementFromChangeSet(removed, mergeManager, targetSession);
Object targetElement = null;
for (Object iter = cp.iteratorFor(targetCollection); cp.hasNext(iter);) {
targetElement = cp.next(iter, session);
if (this.compareElements(targetElement, removeElement, session)) {
break;// matching element found - skip the rest of them
}
}
if (targetElement != null) {
// a matching element was found, remove it
cp.removeFrom(targetElement, targetCollection, session);
}
}
for (Object added : adds) {
Object addElement = buildAddedElementFromChangeSet(added, mergeManager, targetSession);
cp.addInto(addElement, targetCollection, session);
}
for (Object changed : changedMapKeys) {
Object changedMapKeyElement = buildAddedElementFromChangeSet(changed, mergeManager, targetSession);
Object originalElement = ((UnitOfWorkImpl)session).getOriginalVersionOfObject(changedMapKeyElement);
cp.removeFrom(originalElement, targetCollection, session);
cp.addInto(changedMapKeyElement, targetCollection, session);
}
}
// reset the attribute to allow for set method to re-morph changes if the collection is not being stored directly
setRealAttributeValueInObject(target, targetCollection);
}
/**
* INTERNAL:
* Merge changes from the source to the target object.
* Simply replace the entire target collection.
*/
public void mergeIntoObject(Object target, boolean isTargetUnInitialized, Object source, MergeManager mergeManager, AbstractSession targetSession) {
ContainerPolicy cp = this.getContainerPolicy();
AbstractSession session = mergeManager.getSession();
Object sourceCollection = this.getRealCollectionAttributeValueFromObject(source, session);
Object targetCollection = cp.containerInstance(cp.sizeFor(sourceCollection));
for (Object iter = cp.iteratorFor(sourceCollection); cp.hasNext(iter);) {
Object targetElement = this.buildElementFromElement(cp.next(iter, session), mergeManager, targetSession);
cp.addInto(targetElement, targetCollection, session);
}
// reset the attribute to allow for set method to re-morph changes if the collection is not being stored directly
setRealAttributeValueInObject(target, targetCollection);
}
/**
* Convenience method.
*/
private void setRealAttributeValueInObject(Object object, Object attributeValue) {
getMapping().setRealAttributeValueInObject(object, attributeValue);
}
/**
* ADVANCED:
* This method is used to add an object to a collection once the changeSet is applied.
* The referenceKey parameter should only be used for direct Maps.
*/
public void simpleAddToCollectionChangeRecord(Object referenceKey, Object changeSetToAdd, ObjectChangeSet changeSet, AbstractSession session) {
if (this.getContainerPolicy().hasOrder()) {
this.simpleAddToCollectionChangeRecordWithOrder(referenceKey, changeSetToAdd, changeSet, session);
} else {
this.simpleAddToCollectionChangeRecordWithoutOrder(referenceKey, changeSetToAdd, changeSet, session);
}
}
/**
* Add stuff to an ordered collection.
*/
private void simpleAddToCollectionChangeRecordWithOrder(Object referenceKey, Object changeSetToAdd, ObjectChangeSet changeSet, AbstractSession session) {
EISOrderedCollectionChangeRecord changeRecord = (EISOrderedCollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName());
if (changeRecord == null) {
changeRecord = new EISOrderedCollectionChangeRecord(changeSet, this.getAttributeName(), this.getDatabaseMapping());
changeSet.addChange(changeRecord);
}
changeRecord.simpleAddChangeSet(changeSetToAdd);
}
/**
* Add stuff to an unordered collection.
*/
private void simpleAddToCollectionChangeRecordWithoutOrder(Object referenceKey, Object changeSetToAdd, ObjectChangeSet changeSet, AbstractSession session) {
EISCollectionChangeRecord changeRecord = (EISCollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName());
if (changeRecord == null) {
changeRecord = new EISCollectionChangeRecord(changeSet, this.getAttributeName(), this.getDatabaseMapping());
changeSet.addChange(changeRecord);
}
changeRecord.simpleAddChangeSet(changeSetToAdd);
}
/**
* ADVANCED:
* This method is used to remove an object from a collection once the changeSet is applied.
* The referenceKey parameter should only be used for direct Maps.
*/
public void simpleRemoveFromCollectionChangeRecord(Object referenceKey, Object changeSetToRemove, ObjectChangeSet changeSet, AbstractSession session) {
if (this.getContainerPolicy().hasOrder()) {
this.simpleRemoveFromCollectionChangeRecordWithOrder(referenceKey, changeSetToRemove, changeSet, session);
} else {
this.simpleRemoveFromCollectionChangeRecordWithoutOrder(referenceKey, changeSetToRemove, changeSet, session);
}
}
/**
* Remove stuff from an ordered collection.
*/
private void simpleRemoveFromCollectionChangeRecordWithOrder(Object referenceKey, Object changeSetToRemove, ObjectChangeSet changeSet, AbstractSession session) {
EISOrderedCollectionChangeRecord changeRecord = (EISOrderedCollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName());
if (changeRecord == null) {
changeRecord = new EISOrderedCollectionChangeRecord(changeSet, this.getAttributeName(), this.getDatabaseMapping());
changeSet.addChange(changeRecord);
}
changeRecord.simpleRemoveChangeSet(changeSetToRemove);
}
/**
* Remove stuff from an unordered collection.
*/
private void simpleRemoveFromCollectionChangeRecordWithoutOrder(Object referenceKey, Object changeSetToRemove, ObjectChangeSet changeSet, AbstractSession session) {
EISCollectionChangeRecord changeRecord = (EISCollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName());
if (changeRecord == null) {
changeRecord = new EISCollectionChangeRecord(changeSet, this.getAttributeName(), this.getDatabaseMapping());
changeSet.addChange(changeRecord);
}
changeRecord.simpleRemoveChangeSet(changeSetToRemove);
}
}