| /* |
| * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // Contributors: |
| // Oracle - initial API and implementation from Oracle TopLink |
| package org.eclipse.persistence.internal.sessions; |
| |
| import java.util.*; |
| |
| 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 DirectCollectionChangeRecord extends DeferrableChangeRecord implements org.eclipse.persistence.sessions.changesets.DirectCollectionChangeRecord { |
| protected HashMap addObjectMap; |
| protected HashMap removeObjectMap; |
| /** |
| * Contains the number of objects that must be inserted to once the value is removed |
| * in the database as a delete where value = "value" will remove all instances |
| * of that value in the database not just one. |
| */ |
| protected HashMap commitAddMap; |
| |
| /** |
| * Used only in case listOrderField != null in the mapping. |
| * Maps each object which has been added or removed or which order in the list has changed |
| * to an array of two (non-intersecting) sets of indexes - old and new. |
| */ |
| protected Map changedIndexes; |
| protected int oldSize; |
| protected int newSize; |
| |
| protected boolean isFirstToAddAlreadyInCollection; |
| protected boolean isFirstToRemoveAlreadyOutCollection; |
| |
| protected boolean isFirstToAdd = true; |
| protected boolean isFirstToRemove = true; |
| |
| /** |
| * Indicates whether IndirectList's order has been repaired. |
| */ |
| protected boolean orderHasBeenRepaired; |
| |
| /** |
| * This default constructor. |
| */ |
| public DirectCollectionChangeRecord() { |
| super(); |
| } |
| |
| /** |
| * This constructor returns a changeRecord representing the DirectCollection mapping. |
| */ |
| public DirectCollectionChangeRecord(ObjectChangeSet owner) { |
| this.owner = owner; |
| } |
| |
| /** |
| * This method takes a map of primitive objects and adds them to the add list. |
| * The map stores the number of times the object is in the list. |
| */ |
| public void addAdditionChange(HashMap additions, HashMap databaseCount) { |
| Iterator enumtr = additions.keySet().iterator(); |
| while (enumtr.hasNext()) { |
| Object object = enumtr.next(); |
| if (databaseCount.containsKey(object)){ |
| getCommitAddMap().put(object, databaseCount.get(object)); |
| } |
| addAdditionChange(object, (Integer)additions.get(object)); |
| } |
| } |
| |
| /** |
| * This method takes a single addition value and records it. |
| */ |
| public void addAdditionChange(Object key, Integer count){ |
| if (getRemoveObjectMap().containsKey(key)) { |
| int removeValue = ((Integer)getRemoveObjectMap().get(key)).intValue(); |
| int addition = count.intValue(); |
| int result = removeValue - addition; |
| if (result > 0 ) { // more removes still |
| getRemoveObjectMap().put(key, Integer.valueOf(result)); |
| } else if (result < 0) { // more adds now |
| getRemoveObjectMap().remove(key); |
| getAddObjectMap().put(key, Integer.valueOf(Math.abs(result))); |
| } else { // equal |
| getRemoveObjectMap().remove(key); |
| } |
| } else { |
| if (this.getAddObjectMap().containsKey(key)) { |
| int addValue = ((Integer)this.getAddObjectMap().get(key)).intValue(); |
| addValue += count.intValue(); |
| this.getAddObjectMap().put(key, Integer.valueOf(addValue)); |
| } else { |
| this.getAddObjectMap().put(key, count); |
| } |
| } |
| if(this.isFirstToAdd) { |
| this.isFirstToAdd = false; |
| if(this.isFirstToAddAlreadyInCollection) { |
| return; |
| } |
| } |
| // this is an attribute change track add keep count |
| int addValue = count.intValue(); |
| int commitValue = 0; |
| if (getCommitAddMap().containsKey(key)) { |
| commitValue = ((Integer)getCommitAddMap().get(key)).intValue(); |
| } |
| getCommitAddMap().put(key, Integer.valueOf(addValue + commitValue)); |
| } |
| |
| /** |
| * This method takes a hashtable of primitive objects and adds them to the remove list. |
| * Each reference in the hashtable lists the number of this object that needs to be removed from the |
| * collection. |
| */ |
| public void addRemoveChange(HashMap additions, HashMap databaseCount) { |
| Iterator enumtr = additions.keySet().iterator(); |
| while (enumtr.hasNext()) { |
| Object object = enumtr.next(); |
| if (databaseCount.containsKey(object)){ |
| getCommitAddMap().put(object, databaseCount.get(object)); |
| } |
| addRemoveChange(object, (Integer)additions.get(object)); |
| } |
| } |
| |
| /** |
| * This method takes a single remove change and integrates it with this changeset. |
| */ |
| public void addRemoveChange(Object key, Integer count){ |
| if (getAddObjectMap().containsKey(key)) { |
| int removeValue = ((Integer)getAddObjectMap().get(key)).intValue(); |
| int addition = count.intValue(); |
| int result = removeValue - addition; |
| if (result > 0 ) { // more removes still |
| getAddObjectMap().put(key, Integer.valueOf(result)); |
| } else if (result < 0) { // more adds now |
| getAddObjectMap().remove(key); |
| getRemoveObjectMap().put(key, Integer.valueOf(Math.abs(result))); |
| } else { // equal |
| getAddObjectMap().remove(key); |
| } |
| } else { |
| if (this.getRemoveObjectMap().containsKey(key)){ |
| int addValue = ((Integer)this.getRemoveObjectMap().get(key)).intValue(); |
| addValue += count.intValue(); |
| this.getRemoveObjectMap().put(key, Integer.valueOf(addValue)); |
| } else { |
| this.getRemoveObjectMap().put(key, count); |
| } |
| } |
| if(this.isFirstToRemove) { |
| this.isFirstToRemove = false; |
| if(this.isFirstToRemoveAlreadyOutCollection) { |
| return; |
| } |
| } |
| int removeValue = count.intValue(); |
| int commitValue = 0; |
| if (getCommitAddMap().containsKey(key)){ |
| commitValue = ((Integer)getCommitAddMap().get(key)).intValue(); |
| } |
| getCommitAddMap().put(key, Integer.valueOf(commitValue - removeValue)); |
| } |
| |
| /** |
| * This method takes a hashtable of primitives and adds them to the commit list. |
| * This count value provided is the number of instances that will need to be |
| * inserted into the database once a remove has occurred. This is only set |
| * once for each object type. |
| */ |
| public void setCommitAddition(Hashtable additions){ |
| Enumeration enumtr = additions.keys(); |
| while (enumtr.hasMoreElements()) { |
| Object object = enumtr.nextElement(); |
| getCommitAddMap().put(object, additions.get(object)); |
| } |
| } |
| |
| /** |
| * This method will iterate over the collection and store the database counts for |
| * the objects within the collection, this is used for minimal updates. |
| */ |
| public void storeDatabaseCounts(Object collection, ContainerPolicy containerPolicy, AbstractSession session){ |
| Object iterator = containerPolicy.iteratorFor(collection); |
| while (containerPolicy.hasNext(iterator)) { |
| Object object = containerPolicy.next(iterator, session); |
| incrementDatabaseCount(object); |
| } |
| } |
| |
| /** |
| * Increment the count for object |
| */ |
| public void incrementDatabaseCount(Object object){ |
| if (getCommitAddMap().containsKey(object)) { |
| int count = ((Integer)getCommitAddMap().get(object)).intValue(); |
| getCommitAddMap().put(object, Integer.valueOf(++count)); |
| } else { |
| getCommitAddMap().put(object, Integer.valueOf(1)); |
| } |
| } |
| |
| /** |
| * Decrement the count for object |
| */ |
| public void decrementDatabaseCount(Object object){ |
| if (getCommitAddMap().containsKey(object)) { |
| int count = ((Integer)getCommitAddMap().get(object)).intValue(); |
| if(count > 1) { |
| getCommitAddMap().put(object, Integer.valueOf(--count)); |
| } else { |
| getCommitAddMap().remove(object); |
| } |
| } |
| } |
| |
| /** |
| * ADVANCED: |
| * This method returns the list of added objects. |
| */ |
| @Override |
| public Vector getAddObjectList(){ |
| Vector vector = new Vector(); |
| for (Iterator iterator = getAddObjectMap().keySet().iterator(); iterator.hasNext();){ |
| Object object = iterator.next(); |
| int count = ((Integer)getAddObjectMap().get(object)).intValue(); |
| while (count > 0){ |
| vector.add(object); |
| --count; |
| } |
| } |
| return vector; |
| } |
| |
| /** |
| * This method returns the collection of objects that were added to the collection. |
| */ |
| public HashMap getAddObjectMap() { |
| if (this.addObjectMap == null) { |
| this.addObjectMap = new HashMap(1); |
| } |
| return addObjectMap; |
| } |
| |
| /** |
| * This method returns the collection of objects that were added to the collection. |
| */ |
| public HashMap getCommitAddMap() { |
| if (this.commitAddMap == null) { |
| this.commitAddMap = new HashMap(1); |
| } |
| return commitAddMap; |
| } |
| |
| /** |
| * ADVANCED: |
| * This method returns the list of removed objects. |
| */ |
| @Override |
| public Vector getRemoveObjectList(){ |
| Vector vector = new Vector(); |
| for (Iterator iterator = getRemoveObjectMap().keySet().iterator(); iterator.hasNext();){ |
| Object object = iterator.next(); |
| int count = ((Integer)getRemoveObjectMap().get(object)).intValue(); |
| while (count > 0){ |
| vector.add(object); |
| --count; |
| } |
| } |
| return vector; |
| } |
| |
| /** |
| * This method returns the collection of objects that were removed from the collection. |
| */ |
| public HashMap getRemoveObjectMap() { |
| if (this.removeObjectMap == null) { |
| removeObjectMap = new HashMap(1); |
| } |
| return removeObjectMap; |
| } |
| |
| /** |
| * Returns true if the change set has changes. |
| */ |
| public boolean hasChanges() { |
| return (!((this.addObjectMap == null || this.addObjectMap.isEmpty()) |
| && (this.removeObjectMap == null || this.removeObjectMap.isEmpty()) |
| && (this.changedIndexes == null || this.changedIndexes.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{ |
| this.isDeferred = true; |
| this.originalCollection = ((DeferrableChangeRecord)mergeFromRecord).originalCollection; |
| this.latestCollection = ((DeferrableChangeRecord)mergeFromRecord).latestCollection; |
| return; |
| } |
| } |
| HashMap addMapToMerge = ((DirectCollectionChangeRecord)mergeFromRecord).getAddObjectMap(); |
| HashMap removeMapToMerge = ((DirectCollectionChangeRecord)mergeFromRecord).getRemoveObjectMap(); |
| //merge additions |
| for (Iterator iterator = addMapToMerge.keySet().iterator(); iterator.hasNext();){ |
| Object added = iterator.next(); |
| if (!((DirectCollectionChangeRecord)mergeFromRecord).getCommitAddMap().containsKey(added)){ |
| // we have not recorded a change of this type in this class before so add it |
| this.getCommitAddMap().put(added, ((DirectCollectionChangeRecord)mergeFromRecord).getCommitAddMap().get(added)); |
| } |
| this.addAdditionChange(added, (Integer)addMapToMerge.get(added)); |
| } |
| //merge removals |
| for (Iterator iterator = removeMapToMerge.keySet().iterator(); iterator.hasNext();){ |
| Object removed = iterator.next(); |
| if (!((DirectCollectionChangeRecord)mergeFromRecord).getCommitAddMap().containsKey(removed)){ |
| // we have not recorded a change of this type in this class before so add it |
| this.getCommitAddMap().put(removed, Integer.valueOf(1)); |
| } |
| this.addRemoveChange(removed, (Integer)removeMapToMerge.get(removed)); |
| } |
| |
| if(this.changedIndexes != null) { |
| if(((DirectCollectionChangeRecord)mergeFromRecord).getChangedIndexes() != null) { |
| Iterator<Map.Entry<Object, Set[]>> itEntries = ((DirectCollectionChangeRecord)mergeFromRecord).getChangedIndexes().entrySet().iterator(); |
| while(itEntries.hasNext()) { |
| Map.Entry<Object, Set[]> entry = itEntries.next(); |
| Object obj = entry.getValue(); |
| Set[] indexes = entry.getValue(); |
| if(this.changedIndexes.containsKey(obj)) { |
| // we assuming that these are two consecutive change records: |
| // oldIndexes[1] should be equal to newIndexes[0] |
| ((Set[])(this.changedIndexes.get(obj)))[1] = indexes[1]; |
| } else { |
| this.changedIndexes.put(obj, indexes); |
| } |
| } |
| this.newSize = ((DirectCollectionChangeRecord)mergeFromRecord).getNewSize(); |
| } |
| } else { |
| if(((DirectCollectionChangeRecord)mergeFromRecord).getChangedIndexes() != null) { |
| this.changedIndexes = new HashMap(((DirectCollectionChangeRecord)mergeFromRecord).getChangedIndexes()); |
| this.oldSize = ((DirectCollectionChangeRecord)mergeFromRecord).getOldSize(); |
| this.newSize = ((DirectCollectionChangeRecord)mergeFromRecord).getNewSize(); |
| } |
| } |
| } |
| |
| /** |
| * This method will be used to update the objectsChangeSets references |
| */ |
| @Override |
| public void updateReferences(UnitOfWorkChangeSet mergeToChangeSet, UnitOfWorkChangeSet mergeFromChangeSet) { |
| //nothing for this record type to do as it does not reference any changesets |
| } |
| |
| public static class NULL { |
| // This is a placeholder for null instances. |
| public NULL(){ |
| } |
| @Override |
| public boolean equals(Object object){ |
| return object instanceof NULL; |
| } |
| } |
| |
| public void setFirstToAddAlreadyInCollection(boolean flag) { |
| this.isFirstToAddAlreadyInCollection = flag; |
| } |
| public boolean isFirstToAddAlreadyInCollection() { |
| return this.isFirstToAddAlreadyInCollection; |
| } |
| |
| public void setFirstToRemoveAlreadyOutCollection(boolean flag) { |
| this.isFirstToRemoveAlreadyOutCollection = flag; |
| } |
| public boolean isFirstToRemoveAlreadyOutCollection() { |
| return this.isFirstToRemoveAlreadyOutCollection; |
| } |
| |
| public void setChangedIndexes(Map changedIndexes) { |
| this.changedIndexes = changedIndexes; |
| } |
| public Map getChangedIndexes() { |
| return this.changedIndexes; |
| } |
| public void setOldSize(int size) { |
| this.oldSize = size; |
| } |
| public int getOldSize() { |
| return this.oldSize; |
| } |
| public void setNewSize(int size) { |
| this.newSize = size; |
| } |
| public int getNewSize() { |
| return this.newSize; |
| } |
| |
| /** |
| * Recreates the original state of the collection. |
| */ |
| @Override |
| public void internalRecreateOriginalCollection(Object currentCollection, AbstractSession session) { |
| ContainerPolicy cp = this.mapping.getContainerPolicy(); |
| if(this.removeObjectMap != null) { |
| Iterator it = this.removeObjectMap.entrySet().iterator(); |
| while(it.hasNext()) { |
| Map.Entry entry = (Map.Entry)it.next(); |
| Object obj = entry.getKey(); |
| int n = (Integer)entry.getValue(); |
| for(int i=0; i < n; i++) { |
| cp.addInto(obj, currentCollection, session); |
| } |
| } |
| } |
| if(this.addObjectMap != null) { |
| Iterator it = this.addObjectMap.entrySet().iterator(); |
| while(it.hasNext()) { |
| Map.Entry entry = (Map.Entry)it.next(); |
| Object obj = entry.getKey(); |
| int n = (Integer)entry.getValue(); |
| for(int i=0; i < n; i++) { |
| cp.removeFrom(obj, currentCollection, session); |
| } |
| } |
| } |
| } |
| |
| 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(this.removeObjectMap != null) { |
| this.removeObjectMap.clear(); |
| } |
| if(this.addObjectMap != null) { |
| this.addObjectMap.clear(); |
| } |
| if(this.addObjectMap != null) { |
| this.addObjectMap.clear(); |
| } |
| if(this.removeObjectMap != null) { |
| this.removeObjectMap.clear(); |
| } |
| if(this.commitAddMap != null) { |
| this.commitAddMap.clear(); |
| } |
| } |
| } |