package com.scottlogic.util; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.NoSuchElementException; /** * List to store {@code Alteration}s to the backingList of a {@code PatchWorkArray}, so that elements can be accessed * quickly. */ class AlterationList extends NaturalSortedList<Alteration> { private static final long serialVersionUID = 1L; //Cached value of the "performaceHit" of all the Alterations in this list.. private int totalPerformanceHit; AlterationList(){ totalPerformanceHit = 0; //unrequired but for completeness. } /** * Deletes the alteration covering the given index in the {@code PatchWorkArray}'s * backingList. * * @param index the index of the backingList for which the Alteration needs to be removed. * Note that in the case of a {@code Removal}, the index only needs to be covered * @throws IllegalStateException in the case that an attempt is made to remove an Alteration that * doesn't exist. */ void deleteAlterationAtIndex(final int index){ //get the alteration that is the floor of this one.. AlterationNode altNode = getNodeCovering(index); Alteration alt = (altNode == null) ? null : altNode.getValue(); if(alt == null || !alt.coversBackingListIndex(index)){ throw new IllegalStateException("Can not delete alteration covering index: " + index + " as no such " + "alteration, exists!"); } //(unfortunately) we need to deal with Additions and Removals in different ways.. if(alt.getClass() == Addition.class){ remove(altNode); } else { //instance of Removal.. //if the removal doesn't cover multiple elements remove it. if(alt.size() == 1){ remove(altNode); } else { if(alt.index + alt.size() -1 == index){ //case that we're removing "from the end of the Removal" alt.indexDiff--; altNode.updateCachedValues(); } else if(alt.index == index) { //case that we're removing from the start of the Removal.. alt.index++; alt.indexDiff--; altNode.updateCachedValues(); } else { //case that removing from middle of Removal.. //we make the Removal to the left smaller then add a new one to the right.. int oldIndexDiff = alt.indexDiff; int leftIndexDiff = index-alt.index; alt.indexDiff = leftIndexDiff; altNode.updateCachedValues(); Removal toRight = new Removal(index+1, oldIndexDiff-leftIndexDiff-1); add(toRight); } } } } /** * Returns the modCount for this {@code AlterationList}. * * @return the modCount for this list. */ int getModCount(){ return modCount; } /** * Obtains the {@code AlterationNode} that contains the {@code Alteration} covering the given index * or <code>null</code> in the case that no such node exists. * <p> * Takes time <i>O(log(size())</i>. * * @param index the covered index to locate the {@code AlterationNode} for. * @return the {@code AlterationNode} covering the given index, or <code>null</code> if no such node exists. */ AlterationNode getNodeCovering(int index){ Node current = getRoot(); while(current != null){ Alteration alt = ((AlterationNode) current).getValue(); if(alt.coversBackingListIndex(index)){ break; //it's a match! } //compare the index ot see if we need to traverse left of right.. if(alt.index < index){ //need to go right.. current = current.getRightChild(); } else { //need to go left. current = current.getLeftChild(); } } return ((AlterationNode) current); } /** * Moves the {@code Removal} at the given index one position to the left and increments its * size by one, this should be used when removing an element to the left of the this {@code Removal}. * * @param index the index of the {@code Removal}. * @throws ClassCastException in the case that the Alteration at the given index is not a {@code Removal}. * @throws NullPointerException in the case that there is no Alteration with the given index. */ void moveRemovalLeftAndIncrementSize(int index){ alterRemovalCoveringIndex(index, -1, 1); } /** * Gets the Removal which covers the given index, decreases its size by one and shifts its * index one place to the right. * <p> * This should be used in the case that you wish to add an element at the position * held by the start of the {@code Removal} covering the given index and the {@code Removal} * has size at least two. * * @param index the index of the {@code Removal}. */ void moveRemovalRightAndDecrementSize(int index){ alterRemovalCoveringIndex(index, 1, -1); } /** * Gets the {@code Removal} which covers the given index and increments its size by one. * <p> * This should be used in the case that you wish to remove the element to the right of this {@code Removal}. * * @param index the index of the {@code Removal}. */ void incrementSizeOfRemovalForIndex(int index){ alterRemovalCoveringIndex(index, 0, 1); } /** * Gets the Removal which covers the given index and decreases its size by one. * <p> * This should be used in the case that you wish to add an element at the position * held by the end of the {@code Removal} covering this index and the {@code Removal} * has size at least two. * * @param index the index of the {@code Removal}. */ void decrementSizeOfRemovalForIndex(int index){ alterRemovalCoveringIndex(index, 0, -1); } //Gets the Removal covering the given index, adds the given changeToIndex to its index //and adds the changeToIndexDiff to its indexDiff. Finally updates the cached values.. private void alterRemovalCoveringIndex(int index, int changeToIndex, int changeToIndexDiff){ AlterationNode altNode = getNodeCovering(index); Removal rem = (Removal) altNode.getValue(); rem.index += changeToIndex; rem.indexDiff += changeToIndexDiff; altNode.updateCachedValues(); } /** * Returns an Iterator which begins at the given index in this * {@code AlterationList}. * * @param startIndex the index in this list to start at, for example * giving 0 is equivalent to calling {@code #iterator()} in a non-empty list. * @return an {@code Iterator} for going through this list quickly. */ Iterator<Alteration> iterator(int startIndex){ return new CustomStartingItr(startIndex); } //Custom implementation of iterator which provides rapid access //to the elements and can be configured with a starting index.. private class CustomStartingItr implements Iterator<Alteration> { //the next node to show and it's index.. private Node nextNode; private int nextIndex; //the last one returned.. private Node lastReturned = null; private int expectedModCount = modCount; //optimistic concurrent mod check.. CustomStartingItr(int startIndex){ nextIndex = startIndex; nextNode = (startIndex < 0 || startIndex > size() - 1) ? null : findNodeAtIndex(startIndex); } @Override public boolean hasNext() { return nextNode != null; } @Override public Alteration next() { checkModCount(); if(nextNode == null){ throw new NoSuchElementException(); } //update fields.. lastReturned = nextNode; nextNode = nextNode.successor(); nextIndex++; return lastReturned.getValue(); } @Override public void remove() { checkModCount(); if(lastReturned == null){ throw new IllegalStateException(); } AlterationList.this.remove(lastReturned); lastReturned = null; //the nextNode could now be incorrect so need to get it again.. nextIndex--; if(nextIndex < size()){ //check that a node with this index actually exists.. nextNode = findNodeAtIndex(nextIndex); } else { nextNode = null; } expectedModCount = modCount; } private void checkModCount(){ if(expectedModCount != modCount){ throw new ConcurrentModificationException(); } } } /** * Gets the Alteration at the given index and adds the <code>indexDiffChange</code> * to its <code>indexDiff</code>. * <p> * Note that the <code>indexDiffChange</code> should be negative if you are increasing the size * of an {@code Addition} and positive when increasing the size of a {@code Removal}. * * @param index the index of the {@code Alteration}. * @param indexDiffChange the amount to add to it's indexDiff. * @throws ClassCastException in the case that there is an Removal at the given index. * @throws NullPointerException in the case that there is no Alteration at the given index. */ void changeIndexDiffForAlterationAtIndex(int index, int indexDiffChange){ //put in a dummy value then try and find it in the list.. AlterationNode altNode = (AlterationNode) findFirstNodeWithValue(new Addition(index)); Alteration alt = altNode.getValue(); //perform the change ensuring that the cached value is kept sync-ed.. int previousPerformanceHit = alt.getPerformanceHit(); alt.indexDiff += indexDiffChange; totalPerformanceHit += alt.getPerformanceHit() - previousPerformanceHit; altNode.updateCachedValues(); //trigger the values updating up the tree.. } /** * Gets the location of the element at the given index in the backingList that this * {@code AlterationList} is storing the changes to. * <p> * This work in time <i>O(log size())</i>. * * @param index the index in the list about which the {@code Alteration}s in this list refer to. * @return the location of the given element in the referred to list. */ ArrayLocation getLocationOf(final int index){ int mainIndex = index; //the index in the main list that contains the value we want (updated as the search progresses).. int subIndex = -1; //the index of the sub-list that contains the value we want (-1 means no sub-index).. AlterationNode current = (AlterationNode) getRoot(); while(current != null){ //get the alteration of the node.. Alteration alt = current.getValue(); //the change that the current alteration makes to smaller indices make.. AlterationNode leftChild = (AlterationNode) current.getLeftChild(); int leftSideDiff = (leftChild == null) ? 0 : leftChild.totalIndexDiff(); //what's the index taking into account what happens in the smaller indices.. int adjustedIndex = mainIndex + leftSideDiff; //this alteration occurs at an index too big to affect us.. if(adjustedIndex < alt.index){ current = leftChild; } else { //case that this index will affect the index we want to find.. if(alt.indexDiff < 0){ //we may need to return a sub index.. //smallest and largest indices of elements that being stored in sub list... int smallestIndexInSubArray = alt.index - leftSideDiff; int largestIndexInSubArray = smallestIndexInSubArray + Math.abs(alt.indexDiff); //case that we want an element in this location.. if(mainIndex <= largestIndexInSubArray){ subIndex = mainIndex-smallestIndexInSubArray; mainIndex = alt.index; break; } else { //(mainIndex > largestIndexInSubArray) current = (AlterationNode) current.getRightChild(); mainIndex = adjustedIndex + alt.indexDiff; } } else { //alt instanceof Removal current = (AlterationNode) current.getRightChild(); mainIndex = adjustedIndex + alt.indexDiff; //need to change what we are looking for.. } } } return new ArrayLocation(mainIndex, subIndex); } /** * Returns the total "performance hit" of the {@code Alteration}s stored in this * {@code AlterationList}. * <p> * This method make use of a cached value and hence takes constant time. * * @return the total "performance hit" of the {@code Alteration}s stored in this * {@code AlterationList}. */ int getTotalPerformanceHit(){ return totalPerformanceHit; } //Overrides the parent method so that cached values are kept sync-ed.. @Override protected void remove(Node node){ totalPerformanceHit -= ((AlterationNode) node).getValue().getPerformanceHit(); super.remove(node); } //Override so that the AlterationNode class is used instead of the default Node one //and any cached values are correctly sync-ed. @Override public boolean add(Alteration alt){ add(new AlterationNode(alt)); //uses the #add(Node) method of the super class.. totalPerformanceHit += alt.getPerformanceHit(); return true; } //Represents the individual positions in the AlterationList.. class AlterationNode extends Node { //Cached values.. int totalChildrenIndexDiff = 0; //sum of all children index diffs.. AlterationNode(Alteration alt){ super(alt); } //Ensures that the totalChildrenIndexDiff is correctly set.. @Override protected void updateAdditionalCachedValues(){ //set the total child index diff to the sum of left and right diffs.. totalChildrenIndexDiff = 0; AlterationNode leftChild = (AlterationNode) getLeftChild(); totalChildrenIndexDiff += (leftChild == null) ? 0 : leftChild.totalIndexDiff(); AlterationNode rightChild = (AlterationNode) getRightChild(); totalChildrenIndexDiff += (rightChild == null) ? 0 : rightChild.totalIndexDiff(); } /** * The difference in the index over the whole sub-tree rooted at this node. * <p> * This method requires only constant time to run. * * @return the diff over the while sub-tree rooted at this node. */ int totalIndexDiff(){ return getValue().indexDiff + totalChildrenIndexDiff; } } //end of the AlterationNode innerClass. } //end of the AlterationList class.