| /* |
| * 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.eis; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.persistence.internal.sessions.CollectionChangeRecord; |
| import org.eclipse.persistence.internal.sessions.ObjectChangeSet; |
| import org.eclipse.persistence.mappings.DatabaseMapping; |
| |
| /** |
| * INTERNAL: |
| * Capture the changes for an ordered collection where |
| * the entire collection is simply replaced if it has changed. |
| */ |
| public class EISOrderedCollectionChangeRecord extends CollectionChangeRecord implements org.eclipse.persistence.sessions.changesets.EISOrderedCollectionChangeRecord { |
| |
| /** The added stuff. */ |
| private List adds; |
| |
| /** The indexes into the new collection of the elements that were added. */ |
| private int[] addIndexes; |
| |
| /** The moved stuff. */ |
| private List moves; |
| |
| /** The index pairs of the elements that were moved (before and after indexes). */ |
| private int[][] moveIndexPairs; |
| |
| /** The removed stuff. */ |
| private List removes; |
| |
| /** The indexes into the old collection of the elements that were removed. */ |
| private int[] removeIndexes; |
| |
| /** |
| * Construct a ChangeRecord that can be used to represent the changes to |
| * an ordered collection. |
| */ |
| public EISOrderedCollectionChangeRecord(ObjectChangeSet owner, String attributeName, DatabaseMapping mapping) { |
| super(); |
| this.owner = owner; |
| this.attribute = attributeName; |
| this.mapping = mapping; |
| } |
| |
| /** |
| * Add an added change set. |
| */ |
| public void addAddedChangeSet(Object changeSet, int index) { |
| getAdds().add(changeSet); |
| setAddIndexes(this.addTo(index, getAddIndexes())); |
| } |
| |
| /** |
| * Add an moved change set. |
| */ |
| public void addMovedChangeSet(Object changeSet, int oldIndex, int newIndex) { |
| getMoves().add(changeSet); |
| int[] pair = new int[2]; |
| pair[0] = oldIndex; |
| pair[1] = newIndex; |
| setMoveIndexPairs(this.addTo(pair, getMoveIndexPairs())); |
| } |
| |
| /** |
| * Add an removed change set. |
| */ |
| public void addRemovedChangeSet(Object changeSet, int index) { |
| getRemoves().add(changeSet); |
| setRemoveIndexes(this.addTo(index, getRemoveIndexes())); |
| } |
| |
| /** |
| * Add the int to the end of the array. |
| * Return the new array. |
| */ |
| private int[] addTo(int newInt, int[] oldArray) { |
| int oldCount = oldArray.length; |
| int[] newArray = new int[oldCount + 1]; |
| System.arraycopy(oldArray, 0, newArray, 0, oldCount); |
| newArray[oldCount] = newInt;// zero-based index |
| return newArray; |
| } |
| |
| /** |
| * Add the int[] to the end of the array. |
| * Return the new array. |
| */ |
| private int[][] addTo(int[] newInts, int[][] oldArray) { |
| int oldCount = oldArray.length; |
| int[][] newArray = new int[oldCount + 1][]; |
| System.arraycopy(oldArray, 0, newArray, 0, oldCount); |
| newArray[oldCount] = newInts;// zero-based index |
| return newArray; |
| } |
| |
| /** |
| * Return the specified add. |
| */ |
| private Object getAdd(int index) { |
| return this.getAdds().get(index); |
| } |
| |
| /** |
| * ADVANCED: |
| * Return the indexes into the new collection of |
| * the elements that were added. |
| */ |
| @Override |
| public int[] getAddIndexes() { |
| if (addIndexes == null) { |
| addIndexes = new int[0]; |
| } |
| return addIndexes; |
| } |
| |
| /** |
| * ADVANCED: |
| * Return the entries for all the elements added to the new collection. |
| * The contents of this collection is determined by the mapping that |
| * populated it |
| */ |
| @Override |
| public List getAdds() { |
| if (adds == null) { |
| adds = new ArrayList(2);// keep it as small as possible |
| } |
| return adds; |
| } |
| |
| /** |
| * Return the index in the adds of the specified change set, |
| * without triggering the instantiation of the collection. |
| */ |
| private int getAddsIndexOf(Object changeSet) { |
| if (adds == null) { |
| return -1; |
| } |
| return adds.indexOf(changeSet); |
| } |
| |
| /** |
| * Return the number of adds, without triggering |
| * the instantiation of the collection. |
| */ |
| private int getAddsSize() { |
| if (adds == null) { |
| return 0; |
| } |
| return adds.size(); |
| } |
| |
| /** |
| * Return the specified move. |
| */ |
| private Object getMove(int index) { |
| return this.getMoves().get(index); |
| } |
| |
| /** |
| * Return the specified "before" move index. |
| */ |
| private int getBeforeMoveIndex(int index) { |
| int[][] pairs = getMoveIndexPairs(); |
| return pairs[index][0]; |
| } |
| |
| /** |
| * ADVANCED: |
| * Return the indexes of the elements that were simply moved |
| * within the collection. |
| * Each element in the outer array is another two-element |
| * array where the first entry [0] is the index of the object in |
| * the old collection and the second entry [1] is the index |
| * of the object in the new collection. These two indexes |
| * can be equal. |
| */ |
| @Override |
| public int[][] getMoveIndexPairs() { |
| if (moveIndexPairs == null) { |
| moveIndexPairs = new int[0][0]; |
| } |
| return moveIndexPairs; |
| } |
| |
| /** |
| * ADVANCED: |
| * Return the entries for all the elements that were simply shuffled |
| * within the collection. |
| * The contents of this collection is determined by the mapping that |
| * populated it |
| */ |
| @Override |
| public List getMoves() { |
| if (moves == null) { |
| moves = new ArrayList(2);// keep it as small as possible |
| } |
| return moves; |
| } |
| |
| /** |
| * Return the index in the moves of the specified change set, |
| * without triggering the instantiation of the collection. |
| */ |
| private int getMovesIndexOf(Object changeSet) { |
| if (moves == null) { |
| return -1; |
| } |
| return moves.indexOf(changeSet); |
| } |
| |
| /** |
| * Return the number of moves, without triggering |
| * the instantiation of the collection. |
| */ |
| private int getMovesSize() { |
| if (moves == null) { |
| return 0; |
| } |
| return moves.size(); |
| } |
| |
| /** |
| * ADVANCED: |
| * Return the entries for all the elements in the new collection. |
| * The contents of this collection is determined by the mapping that |
| * populated it |
| */ |
| @Override |
| public List getNewCollection() { |
| int newSize = getNewCollectionSize(); |
| List newCollection = new ArrayList(newSize); |
| |
| int[] localAddIndexes = addIndexes; |
| if (localAddIndexes == null) { |
| localAddIndexes = new int[0]; |
| } |
| |
| int[][] localMoveIndexPairs = moveIndexPairs; |
| if (localMoveIndexPairs == null) { |
| localMoveIndexPairs = new int[0][0]; |
| } |
| |
| int addIndex = 0; |
| int moveIndex = 0; |
| for (int i = 0; i < newSize; i++) { |
| if ((addIndex < localAddIndexes.length) && (localAddIndexes[addIndex] == i)) { |
| newCollection.add(this.getAdd(addIndex)); |
| addIndex++; |
| continue; |
| } |
| if ((moveIndex < localMoveIndexPairs.length) && (localMoveIndexPairs[moveIndex][1] == i)) { |
| newCollection.add(this.getMove(moveIndex)); |
| moveIndex++; |
| continue; |
| } |
| throw new IllegalStateException(String.valueOf(i)); |
| } |
| return newCollection; |
| } |
| |
| /** |
| * Return the number of elements in the new collection, |
| * without triggering the instantiation of the collections. |
| */ |
| private int getNewCollectionSize() { |
| return this.getAddsSize() + this.getMovesSize(); |
| } |
| |
| /** |
| * Return the specified remove index. |
| */ |
| private int getRemoveIndex(int index) { |
| return this.getRemoveIndexes()[index]; |
| } |
| |
| /** |
| * ADVANCED: |
| * Return the indexes into the old collection of |
| * the elements that were removed. |
| */ |
| @Override |
| public int[] getRemoveIndexes() { |
| if (removeIndexes == null) { |
| removeIndexes = new int[0]; |
| } |
| return removeIndexes; |
| } |
| |
| /** |
| * ADVANCED: |
| * Return the entries for all the elements removed from the old collection. |
| * The contents of this collection is determined by the mapping that |
| * populated it |
| */ |
| @Override |
| public List getRemoves() { |
| if (removes == null) { |
| removes = new ArrayList(2);// keep it as small as possible |
| } |
| return removes; |
| } |
| |
| /** |
| * Return the index in the removes of the specified change set, |
| * without triggering the instantiation of the collection. |
| */ |
| private int getRemovesIndexOf(Object changeSet) { |
| if (removes == null) { |
| return -1; |
| } |
| return removes.indexOf(changeSet); |
| } |
| |
| /** |
| * Return whether any adds have been recorded with the change record. |
| * Directly reference the instance variable, so as to not trigger the lazy instantiation. |
| */ |
| private boolean hasAdds() { |
| return (addIndexes != null) && (addIndexes.length != 0); |
| } |
| |
| /** |
| * Return whether any changes have been recorded with the change record. |
| */ |
| @Override |
| public boolean hasChanges() { |
| if (this.hasAdds() || this.hasRemoves() || this.getOwner().isNew()) { |
| return true; |
| } |
| |
| // BUG#.... the moves always contain everything, must check if any indexes are different. |
| if (hasMoves()) { |
| for (int index = 0; index < moveIndexPairs.length; index++) { |
| if (moveIndexPairs[index][0] != moveIndexPairs[index][1]) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Return whether any moves have been recorded with the change record. |
| * Directly reference the instance variable, so as to not trigger the lazy instantiation. |
| */ |
| private boolean hasMoves() { |
| return (moveIndexPairs != null) && (moveIndexPairs.length != 0); |
| } |
| |
| /** |
| * Return whether any removes have been recorded with the change record. |
| * Directly reference the instance variable, so as to not trigger the lazy instantiation. |
| */ |
| private boolean hasRemoves() { |
| return (removeIndexes != null) && (removeIndexes.length != 0); |
| } |
| |
| /** |
| * Remove the specified slot from the array. |
| * Return the new array. |
| */ |
| private int[] removeFrom(int removeIndex, int[] oldArray) { |
| int oldCount = oldArray.length; |
| int[] newArray = new int[oldCount - 1]; |
| System.arraycopy(oldArray, 0, newArray, 0, removeIndex); |
| System.arraycopy(oldArray, removeIndex + 1, newArray, removeIndex, oldCount - removeIndex - 1); |
| return newArray; |
| } |
| |
| /** |
| * Remove the specified slot from the array. |
| * Return the new array. |
| */ |
| private int[][] removeFrom(int removeIndex, int[][] oldArray) { |
| int oldCount = oldArray.length; |
| int[][] newArray = new int[oldCount - 1][]; |
| System.arraycopy(oldArray, 0, newArray, 0, removeIndex); |
| System.arraycopy(oldArray, removeIndex + 1, newArray, removeIndex, oldCount - removeIndex - 1); |
| return newArray; |
| } |
| |
| /** |
| * The specified change set was added earlier; |
| * cancel it out. |
| */ |
| private void cancelAddedChangeSet(Object changeSet) { |
| int changeSetIndex = this.getAddsIndexOf(changeSet); |
| if (changeSetIndex == -1) { |
| throw new IllegalStateException(changeSet.toString()); |
| } |
| this.getAdds().remove(changeSetIndex); |
| this.setAddIndexes(this.removeFrom(changeSetIndex, this.getAddIndexes())); |
| } |
| |
| /** |
| * Attempt to remove the specified change set |
| * from the collection of moved change sets. |
| * Return true if the change set was moved earlier |
| * and was successfully removed. |
| */ |
| private boolean removeMovedChangeSet(Object changeSet) { |
| int changeSetIndex = this.getMovesIndexOf(changeSet); |
| if (changeSetIndex == -1) { |
| return false; |
| } |
| this.getMoves().remove(changeSetIndex); |
| int beforeMoveIndex = this.getBeforeMoveIndex(changeSetIndex); |
| this.setMoveIndexPairs(this.removeFrom(changeSetIndex, this.getMoveIndexPairs())); |
| // now move the change set over to the collection of removes |
| this.addRemovedChangeSet(changeSet, beforeMoveIndex); |
| return true; |
| } |
| |
| /** |
| * Attempt to restore the specified change set. |
| * Return true if the change set was removed earlier |
| * and was successfully restored to the end of |
| * the collection. |
| */ |
| private boolean restoreRemovedChangeSet(Object changeSet) { |
| int changeSetIndex = this.getRemovesIndexOf(changeSet); |
| if (changeSetIndex == -1) { |
| return false; |
| } |
| this.getRemoves().remove(changeSetIndex); |
| int removeIndex = this.getRemoveIndex(changeSetIndex); |
| this.setRemoveIndexes(this.removeFrom(changeSetIndex, this.getRemoveIndexes())); |
| // now move the change set over to the collection of moves |
| this.addMovedChangeSet(changeSet, removeIndex, this.getNewCollectionSize()); |
| return true; |
| } |
| |
| /** |
| * Set the indexes into the new collection of |
| * the elements that were added. |
| */ |
| private void setAddIndexes(int[] addIndexes) { |
| this.addIndexes = addIndexes; |
| } |
| |
| /** |
| * Set the indexes of the elements that were moved. |
| */ |
| private void setMoveIndexPairs(int[][] moveIndexPairs) { |
| this.moveIndexPairs = moveIndexPairs; |
| } |
| |
| /** |
| * Set the indexes into the old collection of |
| * the elements that were removed. |
| */ |
| private void setRemoveIndexes(int[] removeIndexes) { |
| this.removeIndexes = removeIndexes; |
| } |
| |
| /** |
| * Add a change set after it has been applied. |
| */ |
| public void simpleAddChangeSet(Object changeSet) { |
| // check whether the change set was removed earlier |
| if (!this.restoreRemovedChangeSet(changeSet)) { |
| // the change set is tacked on the end of the new collection |
| this.addAddedChangeSet(changeSet, this.getNewCollectionSize()); |
| } |
| } |
| |
| /** |
| * Remove a change set after it has been applied. |
| */ |
| public void simpleRemoveChangeSet(Object changeSet) { |
| // the change set must have been either moved or added earlier |
| if (!this.removeMovedChangeSet(changeSet)) { |
| this.cancelAddedChangeSet(changeSet); |
| } |
| } |
| } |