blob: f2ad8998738d661e7bb22bf132638a94ec97e449 [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.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);
int addition = count;
int result = removeValue - addition;
if (result > 0 ) { // more removes still
getRemoveObjectMap().put(key, result);
} else if (result < 0) { // more adds now
getRemoveObjectMap().remove(key);
getAddObjectMap().put(key, Math.abs(result));
} else { // equal
getRemoveObjectMap().remove(key);
}
} else {
if (this.getAddObjectMap().containsKey(key)) {
int addValue = (Integer) this.getAddObjectMap().get(key);
addValue += count;
this.getAddObjectMap().put(key, 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;
int commitValue = 0;
if (getCommitAddMap().containsKey(key)) {
commitValue = (Integer) getCommitAddMap().get(key);
}
getCommitAddMap().put(key, 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);
int addition = count;
int result = removeValue - addition;
if (result > 0 ) { // more removes still
getAddObjectMap().put(key, result);
} else if (result < 0) { // more adds now
getAddObjectMap().remove(key);
getRemoveObjectMap().put(key, Math.abs(result));
} else { // equal
getAddObjectMap().remove(key);
}
} else {
if (this.getRemoveObjectMap().containsKey(key)){
int addValue = (Integer) this.getRemoveObjectMap().get(key);
addValue += count;
this.getRemoveObjectMap().put(key, addValue);
} else {
this.getRemoveObjectMap().put(key, count);
}
}
if(this.isFirstToRemove) {
this.isFirstToRemove = false;
if(this.isFirstToRemoveAlreadyOutCollection) {
return;
}
}
int removeValue = count;
int commitValue = 0;
if (getCommitAddMap().containsKey(key)){
commitValue = (Integer) getCommitAddMap().get(key);
}
getCommitAddMap().put(key, 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);
getCommitAddMap().put(object, ++count);
} else {
getCommitAddMap().put(object, 1);
}
}
/**
* Decrement the count for object
*/
public void decrementDatabaseCount(Object object){
if (getCommitAddMap().containsKey(object)) {
int count = (Integer) getCommitAddMap().get(object);
if(count > 1) {
getCommitAddMap().put(object, --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);
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);
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, 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();
}
}
}