| /* |
| * 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.internal.sessions; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.descriptors.changetracking.CollectionChangeEvent; |
| import org.eclipse.persistence.exceptions.ValidationException; |
| import org.eclipse.persistence.internal.queries.ContainerPolicy; |
| |
| /** |
| * <p> |
| * <b>Purpose</b>: This class holds the record of the changes made to a collection attribute of |
| * an object. |
| * <p> |
| * <b>Description</b>: Collections must be compared to each other and added and removed objects must |
| * be recorded separately. |
| */ |
| public class CollectionChangeRecord extends DeferrableChangeRecord implements org.eclipse.persistence.sessions.changesets.CollectionChangeRecord { |
| |
| /** |
| * Contains the added values to the collection and their corresponding ChangeSets. |
| */ |
| protected Map<ObjectChangeSet, ObjectChangeSet> addObjectList; |
| |
| /** |
| * Contains the added values to the collection and their corresponding ChangeSets in order. |
| */ |
| protected List<ObjectChangeSet> orderedAddObjects; |
| |
| /** |
| * Contains the added values index to the collection. |
| */ |
| protected Map<ObjectChangeSet, Integer> orderedAddObjectIndices; |
| |
| /** |
| * Contains OrderedChangeObjects representing each change made to the collection. |
| */ |
| protected List<OrderedChangeObject> orderedChangeObjectList; |
| |
| /** |
| * Contains the removed values to the collection and their corresponding ChangeSets. |
| */ |
| protected Map<Integer, ObjectChangeSet> orderedRemoveObjects; |
| |
| /** |
| * Contains the removed values index to the collection. |
| */ |
| protected transient List<Integer> orderedRemoveObjectIndices; |
| |
| /** |
| * Contains a list of extra adds. These extra adds are used by attribute change tracking |
| * to replicate behavior when someone adds the same object to a list and removes it once. |
| * In this case the object should still appear once in the change set. |
| */ |
| protected transient List<ObjectChangeSet> addOverFlow; |
| |
| /** |
| * Contains the removed values from the collection and their corresponding ChangeSets. |
| */ |
| protected Map<ObjectChangeSet, ObjectChangeSet> removeObjectList; |
| |
| /** |
| * Indicates whether IndirectList's order has been repaired. |
| */ |
| protected boolean orderHasBeenRepaired; |
| |
| /** |
| * This default constructor. |
| */ |
| public CollectionChangeRecord() { |
| super(); |
| } |
| |
| /** |
| * Constructor for the ChangeRecord representing a collection mapping |
| * @param owner the changeSet that uses this record |
| */ |
| public CollectionChangeRecord(ObjectChangeSet owner) { |
| this.owner = owner; |
| } |
| |
| /** |
| * This method takes a Map of objects, converts these into ObjectChangeSets. |
| */ |
| public void addAdditionChange(Map objectChanges, ContainerPolicy cp, UnitOfWorkChangeSet changeSet, AbstractSession session) { |
| Iterator enumtr = objectChanges.values().iterator(); |
| while (enumtr.hasNext()) { |
| Object object = cp.unwrapElement(enumtr.next()); |
| ObjectChangeSet change = session.getDescriptor(object.getClass()).getObjectBuilder().createObjectChangeSet(object, changeSet, session); |
| if (change.hasKeys()){ |
| // if change set has keys this is a map comparison. Maps are |
| // not supported in change tracking so do not need to prevent duplicates |
| // when map support is added this will have to be refactored |
| getAddObjectList().put(change, change); |
| } else { |
| if (getRemoveObjectList().containsKey(change)) { |
| getRemoveObjectList().remove(change); |
| } else { |
| getAddObjectList().put(change, change); |
| } |
| } |
| } |
| } |
| |
| /** |
| * This method takes a list of objects and converts them into |
| * ObjectChangeSets. This method should only be called from a |
| * ListContainerPolicy. Additions to the list are made by index, hence, |
| * the second Map of objectChangesIndices. |
| */ |
| public void addOrderedAdditionChange(List<Object> orderedObjectsToAdd, Map<Object, Integer> objectChangesIndices, UnitOfWorkChangeSet changeSet, AbstractSession session) { |
| for (Object object : orderedObjectsToAdd) { |
| ObjectChangeSet change = session.getDescriptor(object.getClass()).getObjectBuilder().createObjectChangeSet(object, changeSet, session); |
| getOrderedAddObjects().add(change); |
| getOrderedAddObjectIndices().put(change, objectChangesIndices.get(object)); |
| } |
| } |
| |
| /** |
| * This method takes a map of objects and converts them into |
| * ObjectChangeSets. This method should only be called from a |
| * ListContainerPolicy. Deletions from the list is made by index, hence, |
| * the second Vector of indicesToRemove. |
| */ |
| public void addOrderedRemoveChange(List<Integer> indicesToRemove, Map objectChanges, UnitOfWorkChangeSet changeSet, AbstractSession session) { |
| this.orderedRemoveObjectIndices = indicesToRemove; |
| |
| for (Integer index : indicesToRemove) { |
| Object object = objectChanges.get(index); |
| ObjectChangeSet change = session.getDescriptor(object.getClass()).getObjectBuilder().createObjectChangeSet(object, changeSet, session); |
| getOrderedRemoveObjects().put(index, change); |
| } |
| } |
| |
| /** |
| * This method takes a Map of objects, converts these into ObjectChangeSets. |
| */ |
| public void addRemoveChange(Map objectChanges, ContainerPolicy cp, UnitOfWorkChangeSet changeSet, AbstractSession session) { |
| // There is no need to keep track of removed new objects because it will not be in the backup, |
| // It will not be in the backup because it is new. |
| if(objectChanges.isEmpty()) { |
| return; |
| } |
| ClassDescriptor descriptor = this.mapping.getReferenceDescriptor(); |
| boolean hasChildren = (descriptor.hasInheritance() && descriptor.getInheritancePolicy().hasChildren()) |
| || descriptor.hasTablePerClassPolicy(); |
| Iterator enumtr = cp.getChangeValuesFrom(objectChanges); |
| while (enumtr.hasNext()) { |
| Object object = cp.unwrapElement(enumtr.next()); |
| if (hasChildren) { |
| descriptor = getReferenceDescriptor(object, session); |
| } |
| ObjectChangeSet change = descriptor.getObjectBuilder().createObjectChangeSet(object, changeSet, session); |
| if (change.hasKeys()) { |
| // if change set has keys this is a map comparison. Maps are |
| // not support in change tracking so do not need to prevent duplicates |
| // when map support is added this will have to be refactored |
| getRemoveObjectList().put(change, change); |
| } else { |
| if (getAddObjectList().containsKey(change)) { |
| getAddObjectList().remove(change); |
| } else { |
| getRemoveObjectList().put(change, change); |
| } |
| } |
| } |
| } |
| |
| /** |
| * ADVANCED: |
| * This method returns the collection of ChangeSets that were added to the collection. |
| */ |
| @Override |
| public Map<ObjectChangeSet, ObjectChangeSet> getAddObjectList() { |
| if (addObjectList == null) { |
| addObjectList = new IdentityHashMap(10); |
| } |
| return addObjectList; |
| } |
| |
| /** |
| * Returns a list of extra adds. |
| * These extra adds are used by attribute change tracking |
| * to replicate behavior when someone adds the same object to a list and removes it once. |
| * In this case the object should still appear once in the change set. |
| */ |
| public List<ObjectChangeSet> getAddOverFlow() { |
| if (addOverFlow == null) { |
| addOverFlow = new ArrayList(); |
| } |
| return addOverFlow; |
| } |
| |
| /** |
| * Returns descriptor corresponding to the object. |
| */ |
| ClassDescriptor getReferenceDescriptor(Object object, AbstractSession session) { |
| return session.getClassDescriptor(object); |
| } |
| |
| /** |
| * PUBLIC: |
| * This method returns the Map that contains the removed values from the collection |
| * and their corresponding ChangeSets. |
| */ |
| @Override |
| public Map<ObjectChangeSet, ObjectChangeSet> getRemoveObjectList() { |
| if (removeObjectList == null) { |
| removeObjectList = new IdentityHashMap(); |
| } |
| return removeObjectList; |
| } |
| |
| /** |
| * PUBLIC: |
| * Returns true if the change set has changes. |
| */ |
| @Override |
| public boolean hasChanges() { |
| return (!( (this.addObjectList == null || this.addObjectList.isEmpty()) && |
| (this.removeObjectList == null || this.removeObjectList.isEmpty()) && |
| (this.orderedAddObjects == null || this.orderedAddObjects.isEmpty()) && |
| (this.orderedRemoveObjects == null || this.orderedRemoveObjects.isEmpty()) && |
| (this.orderedChangeObjectList == null || this.orderedChangeObjectList.isEmpty()))) |
| || getOwner().isNew(); |
| } |
| |
| /** |
| * This method will be used to merge one record into another. |
| */ |
| @Override |
| public void mergeRecord(ChangeRecord mergeFromRecord, UnitOfWorkChangeSet mergeToChangeSet, UnitOfWorkChangeSet mergeFromChangeSet) { |
| if (((DeferrableChangeRecord)mergeFromRecord).isDeferred()){ |
| if (this.hasChanges()){ |
| //merging into existing change record need to combine changes |
| mergeFromRecord.getMapping().calculateDeferredChanges(mergeFromRecord, mergeToChangeSet.getSession()); |
| }else{ |
| if (! this.isDeferred){ |
| this.originalCollection = ((DeferrableChangeRecord)mergeFromRecord).originalCollection; |
| } |
| this.isDeferred = true; |
| this.latestCollection = ((DeferrableChangeRecord)mergeFromRecord).latestCollection; |
| return; |
| } |
| } |
| Map<ObjectChangeSet, ObjectChangeSet> changeSets = new HashMap<>(); |
| Iterator<ObjectChangeSet> addEnum = ((CollectionChangeRecord)mergeFromRecord).getAddObjectList().keySet().iterator(); |
| while (addEnum.hasNext()) { |
| ObjectChangeSet mergingObject = addEnum.next(); |
| ObjectChangeSet localChangeSet = mergeToChangeSet.findOrIntegrateObjectChangeSet(mergingObject, mergeFromChangeSet); |
| if (getRemoveObjectList().containsKey(localChangeSet)) { |
| getRemoveObjectList().remove(localChangeSet); |
| } else { |
| changeSets.put(localChangeSet, localChangeSet); |
| } |
| } |
| getAddObjectList().putAll(changeSets); |
| changeSets = new HashMap<>(); |
| Iterator<ObjectChangeSet> removeEnum = ((CollectionChangeRecord)mergeFromRecord).getRemoveObjectList().keySet().iterator(); |
| while (removeEnum.hasNext()) { |
| ObjectChangeSet mergingObject = removeEnum.next(); |
| ObjectChangeSet localChangeSet = mergeToChangeSet.findOrIntegrateObjectChangeSet(mergingObject, mergeFromChangeSet); |
| if (getAddObjectList().containsKey(localChangeSet)) { |
| getAddObjectList().remove(localChangeSet); |
| } else { |
| changeSets.put(localChangeSet, localChangeSet); |
| } |
| } |
| getRemoveObjectList().putAll(changeSets); |
| //237545: merge the changes for ordered list's attribute change tracking. (still need to check if deferred changes need to be merged) |
| List<OrderedChangeObject> orderedChangeSets = new ArrayList<>(); |
| Iterator<OrderedChangeObject> orderedChangeObjectEnum = ((CollectionChangeRecord)mergeFromRecord).getOrderedChangeObjectList().iterator(); |
| while (orderedChangeObjectEnum.hasNext()) { |
| OrderedChangeObject changeObject = orderedChangeObjectEnum.next(); |
| ObjectChangeSet mergingObject = changeObject.getChangeSet(); |
| ObjectChangeSet localChangeSet = mergeToChangeSet.findOrIntegrateObjectChangeSet(mergingObject, mergeFromChangeSet); |
| |
| OrderedChangeObject orderedChangeObject = new OrderedChangeObject(changeObject.getChangeType(), changeObject.getIndex(), localChangeSet); |
| orderedChangeSets.add(orderedChangeObject); |
| } |
| getOrderedChangeObjectList().addAll(orderedChangeSets); |
| } |
| |
| /** |
| * Sets the Added objects list. |
| */ |
| public void setAddObjectList(Map<ObjectChangeSet, ObjectChangeSet> objectChangesList) { |
| this.addObjectList = objectChangesList; |
| } |
| |
| /** |
| * Sets the removed objects list. |
| */ |
| public void setRemoveObjectList(Map<ObjectChangeSet, ObjectChangeSet> objectChangesList) { |
| this.removeObjectList = objectChangesList; |
| } |
| |
| /** |
| * This method will be used to update the objectsChangeSets references. |
| */ |
| @Override |
| public void updateReferences(UnitOfWorkChangeSet mergeToChangeSet, UnitOfWorkChangeSet mergeFromChangeSet) { |
| Map addList = new IdentityHashMap(this.getAddObjectList().size() + 1); |
| Map removeList = new IdentityHashMap(this.getRemoveObjectList().size() + 1); |
| // If we have ordered lists we need to iterate through those. |
| if (getOrderedAddObjects().size() > 0 || getOrderedRemoveObjectIndices().size() > 0) { |
| // Do the ordered adds first ... |
| List<ObjectChangeSet> orderedAddList = new ArrayList(getOrderedAddObjects().size()); |
| Map orderedAddListIndices = new IdentityHashMap(getOrderedAddObjectIndices().size()); |
| |
| for (int i = 0; i < getOrderedAddObjects().size(); i++) { |
| ObjectChangeSet changeSet = getOrderedAddObjects().get(i); |
| ObjectChangeSet localChangeSet = mergeToChangeSet.findOrIntegrateObjectChangeSet(changeSet, mergeFromChangeSet); |
| |
| orderedAddList.add(localChangeSet); |
| orderedAddListIndices.put(localChangeSet, getOrderedAddObjectIndices().get(changeSet)); |
| |
| // Object was actually added and not moved. |
| if (getAddObjectList().containsKey(changeSet)) { |
| addList.put(localChangeSet, localChangeSet); |
| } |
| } |
| |
| setOrderedAddObjects(orderedAddList); |
| setOrderedAddObjectIndices(orderedAddListIndices); |
| |
| // Do the ordered removes now ... |
| Map orderedRemoveList = new HashMap(getOrderedRemoveObjects().size()); |
| for (Object index : getOrderedRemoveObjects().keySet()) { |
| ObjectChangeSet changeSet = getOrderedRemoveObjects().get(index); |
| ObjectChangeSet localChangeSet = mergeToChangeSet.findOrIntegrateObjectChangeSet(changeSet, mergeFromChangeSet); |
| |
| orderedRemoveList.put(index, localChangeSet); |
| |
| // Object was actually removed and not moved. |
| if (getRemoveObjectList().containsKey(changeSet)) { |
| removeList.put(localChangeSet, localChangeSet); |
| } |
| } |
| |
| setOrderedRemoveObjects(orderedRemoveList); |
| // Don't need to worry about the vector of indices (Integer's), just leave them as is. |
| } else { |
| Iterator<ObjectChangeSet> changes = getAddObjectList().values().iterator(); |
| while (changes.hasNext()) { |
| ObjectChangeSet localChangeSet = mergeToChangeSet.findOrIntegrateObjectChangeSet(changes.next(), mergeFromChangeSet); |
| addList.put(localChangeSet, localChangeSet); |
| } |
| |
| changes = getRemoveObjectList().values().iterator(); |
| while (changes.hasNext()) { |
| ObjectChangeSet localChangeSet = mergeToChangeSet.findOrIntegrateObjectChangeSet(changes.next(), mergeFromChangeSet); |
| removeList.put(localChangeSet, localChangeSet); |
| } |
| } |
| |
| setAddObjectList(addList); |
| setRemoveObjectList(removeList); |
| } |
| |
| /** |
| * This method returns the collection of ChangeSets in the order they were |
| * added to the collection. This list includes those objects that were |
| * moved within the collection. |
| */ |
| public List<ObjectChangeSet> getOrderedAddObjects() { |
| if (orderedAddObjects == null) { |
| orderedAddObjects = new ArrayList(); |
| } |
| |
| return orderedAddObjects; |
| } |
| |
| /** |
| * This method returns the index of an object added to the collection. |
| */ |
| public Integer getOrderedAddObjectIndex(ObjectChangeSet changes) { |
| return getOrderedAddObjectIndices().get(changes); |
| } |
| |
| /** |
| * This method returns the collection of ChangeSets that they were |
| * added to the collection. |
| */ |
| public Map<ObjectChangeSet, Integer> getOrderedAddObjectIndices() { |
| if (orderedAddObjectIndices == null) { |
| orderedAddObjectIndices = new IdentityHashMap(); |
| } |
| |
| return orderedAddObjectIndices; |
| } |
| |
| /** |
| * This method returns the Vector of OrderedChangeObjects. These objects represent |
| * all changes made to the collection, and their order in the vector represents the order |
| * they were performed. |
| */ |
| public List<OrderedChangeObject> getOrderedChangeObjectList() { |
| if (orderedChangeObjectList == null) { |
| orderedChangeObjectList = new ArrayList(); |
| } |
| |
| return orderedChangeObjectList; |
| } |
| |
| /** |
| * This method returns the ordered list of indices to remove from the collection. |
| */ |
| public List<Integer> getOrderedRemoveObjectIndices() { |
| if (this.orderedRemoveObjectIndices == null) { |
| this.orderedRemoveObjectIndices = new ArrayList(); |
| } |
| |
| return this.orderedRemoveObjectIndices; |
| } |
| |
| /** |
| * This method returns the index of an object removed from the collection. |
| */ |
| public Object getOrderedRemoveObject(Integer index) { |
| return getOrderedRemoveObjects().get(index); |
| } |
| |
| /** |
| * This method returns the collection of ChangeSets of objects removed from |
| * the collection. |
| */ |
| public Map<Integer, ObjectChangeSet> getOrderedRemoveObjects() { |
| if (this.orderedRemoveObjects == null) { |
| this.orderedRemoveObjects = new HashMap(); |
| } |
| |
| return this.orderedRemoveObjects; |
| } |
| |
| /** |
| * Sets collection of ChangeSets (and their respective index) that they |
| * were added to the collection. |
| */ |
| public void setOrderedAddObjectIndices(Map<ObjectChangeSet, Integer> orderedAddObjectIndices) { |
| this.orderedAddObjectIndices = orderedAddObjectIndices; |
| } |
| |
| /** |
| * Sets collection of ChangeSets that they were added to the collection. |
| */ |
| public void setOrderedAddObjects(List<ObjectChangeSet> orderedAddObjects) { |
| this.orderedAddObjects = orderedAddObjects; |
| } |
| |
| public void setOrderedChangeObjectList(List<OrderedChangeObject> orderedChangeObjectList) { |
| this.orderedChangeObjectList = orderedChangeObjectList; |
| } |
| |
| /** |
| * Sets collection of ChangeSets that they were removed from the collection. |
| */ |
| public void setOrderedRemoveObjects(Map<Integer, ObjectChangeSet> orderedRemoveObjects) { |
| this.orderedRemoveObjects = orderedRemoveObjects; |
| } |
| |
| /** |
| * The same size as original list, |
| * at the i-th position holds the index of the i-th original object in the current list (-1 if the object was removed): |
| * for example: {0, -1, 1, -1, 3} means that: |
| * previous(0) == current(0); |
| * previous(1) was removed; |
| * previous(2) == current(1); |
| * previous(3) was removed; |
| * previous(4) == current(3); |
| */ |
| public List<Integer> getCurrentIndexesOfOriginalObjects(List newList) { |
| int newSize = newList.size(); |
| List<Integer> currentIndexes = new ArrayList(newSize); |
| for(int i=0; i < newSize; i++) { |
| currentIndexes.add(i); |
| } |
| if(orderedChangeObjectList != null) { |
| for (int i = this.orderedChangeObjectList.size() - 1; i>=0; i--) { |
| OrderedChangeObject orderedChange = orderedChangeObjectList.get(i); |
| Object obj = orderedChange.getAddedOrRemovedObject(); |
| Integer index = orderedChange.getIndex(); |
| int changeType = orderedChange.getChangeType(); |
| if(changeType == CollectionChangeEvent.ADD) { |
| // the object was added - remove the corresponding index |
| if(index == null) { |
| currentIndexes.remove(currentIndexes.size()-1); |
| } else { |
| currentIndexes.remove(index.intValue()); |
| } |
| } else if(changeType == CollectionChangeEvent.REMOVE) { |
| // the object was removed - add its index in the new list |
| if(index == null) { |
| throw ValidationException.collectionRemoveEventWithNoIndex(getMapping()); |
| } else { |
| currentIndexes.add(index, newList.indexOf(obj)); |
| } |
| } |
| } |
| } |
| return currentIndexes; |
| } |
| |
| /** |
| * Recreates the original state of currentCollection. |
| */ |
| @Override |
| public void internalRecreateOriginalCollection(Object currentCollection, AbstractSession session) { |
| ContainerPolicy cp = this.mapping.getContainerPolicy(); |
| if(orderedChangeObjectList == null || orderedChangeObjectList.isEmpty()) { |
| if(this.removeObjectList != null) { |
| Iterator<ObjectChangeSet> it = this.removeObjectList.keySet().iterator(); |
| while(it.hasNext()) { |
| ObjectChangeSet changeSet = it.next(); |
| cp.addInto(changeSet.getUnitOfWorkClone(), currentCollection, session); |
| } |
| } |
| if(this.addObjectList != null) { |
| Iterator<ObjectChangeSet> it = this.addObjectList.keySet().iterator(); |
| while(it.hasNext()) { |
| ObjectChangeSet changeSet = it.next(); |
| cp.removeFrom(changeSet.getUnitOfWorkClone(), currentCollection, session); |
| } |
| } |
| } else { |
| List originalList = (List)currentCollection; |
| for (int i = this.orderedChangeObjectList.size() - 1; i>=0; i--) { |
| OrderedChangeObject orderedChange = this.orderedChangeObjectList.get(i); |
| Object obj = orderedChange.getAddedOrRemovedObject(); |
| Integer index = orderedChange.getIndex(); |
| int changeType = orderedChange.getChangeType(); |
| if(changeType == CollectionChangeEvent.ADD) { |
| // the object was added - remove the corresponding index |
| if(index == null) { |
| originalList.remove(originalList.size()-1); |
| } else { |
| originalList.remove(index.intValue()); |
| } |
| } else if(changeType == CollectionChangeEvent.REMOVE) { |
| // the object was removed - add its index in the new list |
| if(index == null) { |
| throw ValidationException.collectionRemoveEventWithNoIndex(getMapping()); |
| } else { |
| originalList.add(index, obj); |
| } |
| } |
| } |
| } |
| } |
| |
| public void setOrderHasBeenRepaired(boolean hasBeenRepaired) { |
| this.orderHasBeenRepaired = hasBeenRepaired; |
| } |
| public boolean orderHasBeenRepaired() { |
| return this.orderHasBeenRepaired; |
| } |
| |
| /** |
| * Clears info about added / removed objects set by change tracker. |
| */ |
| @Override |
| public void clearChanges() { |
| if(orderedChangeObjectList != null) { |
| this.orderedChangeObjectList.clear(); |
| } |
| if(this.removeObjectList != null) { |
| this.removeObjectList.clear(); |
| } |
| if(this.addObjectList != null) { |
| this.addObjectList.clear(); |
| } |
| } |
| } |