1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
|
/* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.impl.event;
import java.util.Arrays;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.impl.adt.barcode2.Element;
import ca.odell.glazedlists.impl.adt.barcode2.FourColorTree;
import ca.odell.glazedlists.impl.adt.barcode2.FourColorTreeIterator;
import ca.odell.glazedlists.impl.adt.barcode2.ListToByteCoder;
/**
* Manage and describe the differences between two revisions of the
* same List, assuming either one can change at any time.
*
* <p>Initially, the source and target lists are equal. Over time, the
* target list changes. It's also possible that the source list can change,
* which is necessary for long-lived buffered changes.
*
* @author <a href="mailto:jesse@swank.ca">Jesse Wilson</a>
*/
public class Tree4Deltas<E> {
/** all the names of the index sets are with respect to the target */
private static final ListToByteCoder<String> BYTE_CODER = new ListToByteCoder<String>(Arrays.asList("+", "U", "X", "_"));
public static final byte INSERT = BYTE_CODER.colorToByte("+");
public static final byte UPDATE = BYTE_CODER.colorToByte("U");
public static final byte DELETE = BYTE_CODER.colorToByte("X");
public static final byte NO_CHANGE = BYTE_CODER.colorToByte("_");
private static final byte SOURCE_INDICES = BYTE_CODER.colorsToByte(Arrays.asList("U", "X", "_"));
private static final byte TARGET_INDICES = BYTE_CODER.colorsToByte(Arrays.asList("U", "+", "_"));
private static final byte ALL_INDICES = BYTE_CODER.colorsToByte(Arrays.asList("U", "X", "+", "_"));
private static final byte CHANGE_INDICES = BYTE_CODER.colorsToByte(Arrays.asList("U", "X", "+"));
/** the trees values include removed elements */
private FourColorTree<E> tree = new FourColorTree<E>(BYTE_CODER);
private boolean allowContradictingEvents = false;
/**
* When the first change to a list happens, we need to guess what the list's
* capacity is. After that change, we reliably know the list's capacity, so
* we don't need to keep testing the capacity one index at a time.
*/
private boolean initialCapacityKnown = false;
public boolean horribleHackPreferMostRecentValue = false;
public boolean getAllowContradictingEvents() {
return allowContradictingEvents;
}
public void setAllowContradictingEvents(boolean allowContradictingEvents) {
this.allowContradictingEvents = allowContradictingEvents;
}
public int targetToSource(int targetIndex) {
if(!initialCapacityKnown) ensureCapacity(targetIndex + 1);
return tree.convertIndexColor(targetIndex, TARGET_INDICES, SOURCE_INDICES);
}
public int sourceToTarget(int sourceIndex) {
if(!initialCapacityKnown) ensureCapacity(sourceIndex + 1);
return tree.convertIndexColor(sourceIndex, SOURCE_INDICES, TARGET_INDICES);
}
/**
* <p>We should consider removing the loop by only setting on removed elements.
*
* @param oldValue the previous value being replaced
* @param newValue the new value
* @param startIndex the first updated element, inclusive
* @param endIndex the last index, exclusive
*/
public void targetUpdate(int startIndex, int endIndex, E oldValue, E newValue) {
if(!initialCapacityKnown) ensureCapacity(endIndex);
for(int i = startIndex; i < endIndex; i++) {
int overallIndex = tree.convertIndexColor(i, TARGET_INDICES, ALL_INDICES);
Element<E> standingChangeToIndex = tree.get(overallIndex, ALL_INDICES);
if(horribleHackPreferMostRecentValue) {
byte newColor = standingChangeToIndex.getColor() == INSERT ? INSERT : UPDATE;
tree.set(overallIndex, ALL_INDICES, newColor, oldValue, 1);
continue;
}
// don't bother updating an inserted element
if(standingChangeToIndex.getColor() == INSERT) {
continue;
}
// if we're updating an update, the original replaced value stands.
if(standingChangeToIndex.getColor() == UPDATE) {
oldValue = standingChangeToIndex.get();
}
// apply the update to our change description
tree.set(overallIndex, ALL_INDICES, UPDATE, oldValue, 1);
}
}
/**
* Add a value to the target only.
*
* <p>Since this method takes a value parameter, is is only needed
* when the target doesn't store its value, for example with buffered
* changes.
*
* @param startIndex the first inserted element, inclusive
* @param endIndex the last index, exclusive
* @param newValue the inserted value
*/
public void targetInsert(int startIndex, int endIndex, E newValue) {
if(!initialCapacityKnown) ensureCapacity(endIndex);
tree.add(startIndex, TARGET_INDICES, INSERT, newValue, endIndex - startIndex);
}
/**
* <p>We should consider removing the loop from this method by counting
* the inserted elements between startIndex and endIndex, removing those,
* then removing everything else...
*
* @param startIndex the index of the first element to remove
* @param endIndex the last index, exclusive
* @param value the removed value
*/
public void targetDelete(int startIndex, int endIndex, E value) {
if(!initialCapacityKnown) ensureCapacity(endIndex);
for(int i = startIndex; i < endIndex; i++) {
if(startIndex > 0 && startIndex > tree.size(TARGET_INDICES)) {
throw new IllegalArgumentException();
}
int overallIndex = tree.convertIndexColor(startIndex, TARGET_INDICES, ALL_INDICES);
Element<E> standingChangeToIndex = tree.get(overallIndex, ALL_INDICES);
// if we're deleting an insert, remove that insert
if(standingChangeToIndex.getColor() == INSERT) {
if(!allowContradictingEvents) throw new IllegalStateException("Remove " + i + " undoes prior insert at the same index! Consider enabling contradicting events.");
tree.remove(overallIndex, ALL_INDICES, 1);
continue;
}
// if we're deleting an update, the original replaced value stands.
if(standingChangeToIndex.getColor() == UPDATE) {
value = standingChangeToIndex.get();
}
tree.set(overallIndex, ALL_INDICES, DELETE, value, 1);
}
}
public void sourceInsert(int sourceIndex) {
tree.add(sourceIndex, SOURCE_INDICES, NO_CHANGE, ListEvent.<E>unknownValue(), 1);
}
public void sourceDelete(int sourceIndex) {
tree.remove(sourceIndex, SOURCE_INDICES, 1);
}
public void sourceRevert(int sourceIndex) {
tree.set(sourceIndex, SOURCE_INDICES, NO_CHANGE, ListEvent.<E>unknownValue(), 1);
}
public int targetSize() {
return tree.size(TARGET_INDICES);
}
public int sourceSize() {
return tree.size(SOURCE_INDICES);
}
public byte getChangeType(int sourceIndex) {
return tree.get(sourceIndex, SOURCE_INDICES).getColor();
}
/**
* Get the value at the specified target index.
*
* @return the value, or {@link ListEvent#UNKNOWN_VALUE} if this index
* holds a value that hasn't been buffered. In this case, the value
* can be obtained from the source list.
*/
public E getTargetValue(int targetIndex) {
return tree.get(targetIndex, TARGET_INDICES).get();
}
public E getSourceValue(int sourceIndex) {
return tree.get(sourceIndex, SOURCE_INDICES).get();
}
public void reset(int size) {
tree.clear();
initialCapacityKnown = true;
ensureCapacity(size);
}
private void ensureCapacity(int size) {
int currentSize = tree.size(TARGET_INDICES);
int delta = size - currentSize;
if(delta > 0) {
int endOfTree = tree.size(ALL_INDICES);
tree.add(endOfTree, ALL_INDICES, NO_CHANGE, ListEvent.<E>unknownValue(), delta);
}
}
/**
* Add all the specified changes to this.
*/
public void addAll(BlockSequence<E> blocks) {
for(BlockSequence<E>.Iterator i = blocks.iterator(); i.nextBlock(); ) {
int blockStart = i.getBlockStart();
int blockEnd = i.getBlockEnd();
int type = i.getType();
E oldValue = i.getOldValue();
E newValue = i.getNewValue();
if(type == ListEvent.INSERT) {
targetInsert(blockStart, blockEnd, newValue);
} else if(type == ListEvent.UPDATE) {
targetUpdate(blockStart, blockEnd, oldValue, newValue);
} else if(type == ListEvent.DELETE) {
targetDelete(blockStart, blockEnd, oldValue);
} else {
throw new IllegalStateException();
}
}
}
/**
* @return <code>true</code> if this event contains no changes.
*/
public boolean isEmpty() {
return tree.size(CHANGE_INDICES) == 0;
}
public Iterator<E> iterator() {
return new Iterator<E>(tree);
}
@Override
public String toString() {
return tree.asSequenceOfColors();
}
/**
* Iterate through the list of changes in this tree.
*/
public static class Iterator<E> {
private final FourColorTree<E> tree;
private final FourColorTreeIterator<E> treeIterator;
private Iterator(FourColorTree<E> tree) {
this.tree = tree;
this.treeIterator = new FourColorTreeIterator<E>(tree);
}
private Iterator(FourColorTree<E> tree, FourColorTreeIterator<E> treeIterator) {
this.tree = tree;
this.treeIterator = treeIterator;
}
public Iterator<E> copy() {
return new Iterator<E>(tree, treeIterator.copy());
}
public int getIndex() {
return treeIterator.index(TARGET_INDICES);
}
public int getEndIndex() {
// this is peculiar. We add mixed types - an index of current indices
// plus the size of "all indices". . . this is because we describe the
// range of deleted indices from its start to finish, although it's
// finish will ultimately go to zero once the change is applied.
return treeIterator.nodeStartIndex(TARGET_INDICES) + treeIterator.nodeSize(ALL_INDICES);
}
public int getType() {
byte color = treeIterator.color();
if(color == INSERT) return ListEvent.INSERT;
else if(color == UPDATE) return ListEvent.UPDATE;
else if(color == DELETE) return ListEvent.DELETE;
else throw new IllegalStateException();
}
public boolean next() {
if(!hasNext()) return false;
treeIterator.next(CHANGE_INDICES);
return true;
}
public boolean nextNode() {
if(!hasNextNode()) return false;
treeIterator.nextNode(CHANGE_INDICES);
return true;
}
public boolean hasNext() {
return treeIterator.hasNext(CHANGE_INDICES);
}
public boolean hasNextNode() {
return treeIterator.hasNextNode(CHANGE_INDICES);
}
public E getOldValue() {
return treeIterator.node().get();
}
}
}
|