blob: 578d01608d68a52906d2b2b96760ad924ba85882 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Matthew Hall - bug 226216
*******************************************************************************/
package org.eclipse.core.databinding.observable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.core.databinding.observable.list.ListDiff;
import org.eclipse.core.databinding.observable.list.ListDiffEntry;
import org.eclipse.core.databinding.observable.map.MapDiff;
import org.eclipse.core.databinding.observable.set.SetDiff;
import org.eclipse.core.databinding.observable.value.ValueDiff;
import org.eclipse.core.internal.databinding.observable.Util;
/**
* @since 1.0
*
*/
public class Diffs {
/**
* Returns a {@link ListDiff} describing the change between the specified
* old and new list states.
*
* @param <E>
*
* @param oldList
* the old list state
* @param newList
* the new list state
* @return the differences between oldList and newList
*/
public static <E> ListDiff<E> computeListDiff(List<E> oldList,
List<E> newList) {
List<ListDiffEntry<E>> diffEntries = new ArrayList<ListDiffEntry<E>>();
createListDiffs(new ArrayList<E>(oldList), newList, diffEntries);
ListDiff<E> listDiff = createListDiff(diffEntries);
return listDiff;
}
/**
* Returns a {@link ListDiff} describing the change between the specified
* old and new list states.
* <P>
* This method does the same thing as computeListDiff but it accepts untyped
* lists and casts them in a type safe manner returning a typed
* {@link ListDiff}.
*
* @param <E>
*
* @param oldList
* the old list state
* @param newList
* the new list state
* @param elementType
* @return the differences between oldList and newList
* @since 1.5
*/
public static <E> ListDiff<E> computeAndCastListDiff(List<?> oldList,
List<?> newList, Class<E> elementType) {
List<ListDiffEntry<E>> diffEntries = new ArrayList<ListDiffEntry<E>>();
/*
* We copy both lists into typed lists. createListDiffs will alter the
* oldList (though not newList), so that one has to be copied anyway.
*/
List<E> oldListTyped = new ArrayList<E>();
for (Object oldElement : oldList) {
oldListTyped.add(elementType.cast(oldElement));
}
List<E> newListTyped = new ArrayList<E>();
for (Object newElement : newList) {
newListTyped.add(elementType.cast(newElement));
}
createListDiffs(oldListTyped, newListTyped, diffEntries);
ListDiff<E> listDiff = createListDiff(diffEntries);
return listDiff;
}
/**
* Returns a lazily computed {@link ListDiff} describing the change between
* the specified old and new list states.
*
* @param <E>
*
* @param oldList
* the old list state
* @param newList
* the new list state
* @return a lazily computed {@link ListDiff} describing the change between
* the specified old and new list states.
* @since 1.3
*/
public static <E> ListDiff<E> computeLazyListDiff(final List<E> oldList,
final List<E> newList) {
return new ListDiff<E>() {
ListDiff<E> lazyDiff;
public ListDiffEntry<?>[] getDifferences() {
if (lazyDiff == null) {
lazyDiff = Diffs.computeListDiff(oldList, newList);
}
return lazyDiff.getDifferences();
}
@Override
public List<ListDiffEntry<E>> getDifferencesAsList() {
if (lazyDiff == null) {
lazyDiff = Diffs.computeListDiff(oldList, newList);
}
return lazyDiff.getDifferencesAsList();
}
};
}
/**
* adapted from EMF's ListDifferenceAnalyzer
*/
private static <E> void createListDiffs(List<E> oldList, List<E> newList,
List<ListDiffEntry<E>> listDiffs) {
int index = 0;
for (Iterator<E> it = newList.iterator(); it.hasNext();) {
E newValue = it.next();
if (oldList.size() <= index) {
// append newValue to newList
listDiffs.add(createListDiffEntry(index, true, newValue));
} else {
boolean done;
do {
done = true;
E oldValue = oldList.get(index);
if (oldValue == null ? newValue != null : !oldValue
.equals(newValue)) {
int oldIndexOfNewValue = listIndexOf(oldList, newValue,
index);
if (oldIndexOfNewValue != -1) {
int newIndexOfOldValue = listIndexOf(newList,
oldValue, index);
if (newIndexOfOldValue == -1) {
// removing oldValue from list[index]
listDiffs.add(createListDiffEntry(index, false,
oldValue));
oldList.remove(index);
done = false;
} else if (newIndexOfOldValue > oldIndexOfNewValue) {
// moving oldValue from list[index] to
// [newIndexOfOldValue]
if (oldList.size() <= newIndexOfOldValue) {
// The element cannot be moved to the
// correct index
// now, however later iterations will insert
// elements
// in front of it, eventually moving it into
// the
// correct spot.
newIndexOfOldValue = oldList.size() - 1;
}
listDiffs.add(createListDiffEntry(index, false,
oldValue));
oldList.remove(index);
listDiffs.add(createListDiffEntry(
newIndexOfOldValue, true, oldValue));
oldList.add(newIndexOfOldValue, oldValue);
done = false;
} else {
// move newValue from list[oldIndexOfNewValue]
// to [index]
listDiffs.add(createListDiffEntry(
oldIndexOfNewValue, false, newValue));
oldList.remove(oldIndexOfNewValue);
listDiffs.add(createListDiffEntry(index, true,
newValue));
oldList.add(index, newValue);
}
} else {
// add newValue at list[index]
oldList.add(index, newValue);
listDiffs.add(createListDiffEntry(index, true,
newValue));
}
}
} while (!done);
}
++index;
}
for (int i = oldList.size(); i > index;) {
// remove excess trailing elements not present in newList
listDiffs.add(createListDiffEntry(--i, false, oldList.get(i)));
}
}
/**
* @param list
* @param object
* @param index
* @return the index, or -1 if not found
*/
private static <E> int listIndexOf(List<E> list, Object object, int index) {
int size = list.size();
for (int i = index; i < size; i++) {
Object candidate = list.get(i);
if (candidate == null ? object == null : candidate.equals(object)) {
return i;
}
}
return -1;
}
/**
* Checks whether the two objects are <code>null</code> -- allowing for
* <code>null</code>.
*
* @param left
* The left object to compare; may be <code>null</code>.
* @param right
* The right object to compare; may be <code>null</code>.
* @return <code>true</code> if the two objects are equivalent;
* <code>false</code> otherwise.
*/
public static final boolean equals(final Object left, final Object right) {
return left == null ? right == null : ((right != null) && left
.equals(right));
}
/**
* Returns a {@link SetDiff} describing the change between the specified old
* and new set states.
*
* @param <E>
*
* @param oldSet
* the old set state
* @param newSet
* the new set state
* @return a {@link SetDiff} describing the change between the specified old
* and new set states.
* @since 1.5
*/
public static <E> SetDiff<E> computeSetDiff(Set<E> oldSet, Set<E> newSet) {
Set<E> additions = new HashSet<E>(newSet);
additions.removeAll(oldSet);
Set<E> removals = new HashSet<E>(oldSet);
removals.removeAll(newSet);
return createSetDiff(additions, removals);
}
/**
* Returns a {@link SetDiff} describing the change between the specified old
* and new set states.
* <P>
* This method does the same thing as computeSetDiff but it accepts untyped
* sets and casts them in a type safe manner returning a typed
* {@link SetDiff}.
*
* @param <E>
*
* @param oldSet
* the old set state
* @param newSet
* the new set state
* @param elementType
* the type of the elements in the sets
* @return a {@link SetDiff} describing the change between the specified old
* and new set states.
* @since 1.5
*/
public static <E> SetDiff<E> computeAndCastSetDiff(Set<?> oldSet,
Set<?> newSet, Class<E> elementType) {
Set<E> additions = new HashSet<E>();
for (Object newElement : newSet) {
if (!oldSet.contains(newElement)) {
additions.add(elementType.cast(newElement));
}
}
Set<E> removals = new HashSet<E>();
for (Object oldElement : oldSet) {
if (!newSet.contains(oldElement)) {
removals.add(elementType.cast(oldElement));
}
}
return createSetDiff(additions, removals);
}
/**
* Returns a lazily computed {@link SetDiff} describing the change between
* the specified old and new set states.
*
* @param <E>
*
* @param oldSet
* the old set state
* @param newSet
* the new set state
* @return a lazily computed {@link SetDiff} describing the change between
* the specified old and new set states.
* @since 1.3
*/
public static <E> SetDiff<E> computeLazySetDiff(final Set<E> oldSet,
final Set<E> newSet) {
return new SetDiff<E>() {
private SetDiff<E> lazyDiff;
private SetDiff<E> getLazyDiff() {
if (lazyDiff == null) {
lazyDiff = computeSetDiff(oldSet, newSet);
}
return lazyDiff;
}
public Set<E> getAdditions() {
return getLazyDiff().getAdditions();
}
public Set<E> getRemovals() {
return getLazyDiff().getRemovals();
}
};
}
/**
* Returns a {@link MapDiff} describing the change between the specified old
* and new map states.
*
* @param <K>
* the type of keys maintained by this map
* @param <V>
* the type of mapped values
* @param oldMap
* the old map state
* @param newMap
* the new map state
* @return a {@link MapDiff} describing the change between the specified old
* and new map states.
*/
public static <K, V> MapDiff<K, V> computeMapDiff(Map<K, V> oldMap,
Map<K, V> newMap) {
// starts out with all keys from the new map, we will remove keys from
// the old map as we go
final Set<K> addedKeys = new HashSet<K>(newMap.keySet());
final Set<K> removedKeys = new HashSet<K>();
final Set<K> changedKeys = new HashSet<K>();
final Map<K, V> oldValues = new HashMap<K, V>();
final Map<K, V> newValues = new HashMap<K, V>();
for (Iterator<Entry<K, V>> it = oldMap.entrySet().iterator(); it
.hasNext();) {
Map.Entry<K, V> oldEntry = it.next();
K oldKey = oldEntry.getKey();
if (addedKeys.remove(oldKey)) {
// potentially changed key since it is in oldMap and newMap
V oldValue = oldEntry.getValue();
V newValue = newMap.get(oldKey);
if (!Util.equals(oldValue, newValue)) {
changedKeys.add(oldKey);
oldValues.put(oldKey, oldValue);
newValues.put(oldKey, newValue);
}
} else {
removedKeys.add(oldKey);
oldValues.put(oldKey, oldEntry.getValue());
}
}
for (Iterator<K> it = addedKeys.iterator(); it.hasNext();) {
K newKey = it.next();
newValues.put(newKey, newMap.get(newKey));
}
return new MapDiff<K, V>() {
public Set<K> getAddedKeys() {
return addedKeys;
}
public Set<K> getChangedKeys() {
return changedKeys;
}
public Set<K> getRemovedKeys() {
return removedKeys;
}
public V getNewValue(Object key) {
return newValues.get(key);
}
public V getOldValue(Object key) {
return oldValues.get(key);
}
};
}
/**
* Returns a {@link MapDiff} describing the change between the specified old
* and new map states.
* <P>
* This version also types the maps. This is useful when the maps have no
* type information (for example they come from reflection) and we require
* the MapDiff to be typed.
*
* @param <K>
* the type of keys maintained by this map
* @param <V>
* the type of mapped values
* @param oldMap
* the old map state
* @param newMap
* the new map state
* @param keyType
* @param valueType
* @return a {@link MapDiff} describing the change between the specified old
* and new map states.
* @since 1.5
*/
public static <K, V> MapDiff<K, V> computeAndCastMapDiff(Map<?, ?> oldMap,
Map<?, ?> newMap, Class<K> keyType, Class<V> valueType) {
// starts out with all keys from the new map, we will remove keys from
// the old map as we go
final Set<K> addedKeys = new HashSet<K>();
final Set<K> removedKeys = new HashSet<K>();
final Set<K> changedKeys = new HashSet<K>();
final Map<K, V> oldValues = new HashMap<K, V>();
final Map<K, V> newValues = new HashMap<K, V>();
for (Object newKey : newMap.keySet()) {
if (keyType == null) {
// Unfortunately we don't always have a class object available
// here to do a safe cast. In fact this occurs in one of the
// test cases.
addedKeys.add((K) newKey);
} else {
addedKeys.add(keyType.cast(newKey));
}
}
for (Map.Entry<?, ?> oldEntry : oldMap.entrySet()) {
// Unfortunately we don't always have a class object available
// here to do a safe cast. In fact this occurs in one of the
// test cases.
K oldKey;
V oldValue;
if (keyType == null) {
oldKey = (K) oldEntry.getKey();
} else {
oldKey = keyType.cast(oldEntry.getKey());
}
if (valueType == null) {
oldValue = (V) oldEntry.getValue();
} else {
oldValue = valueType.cast(oldEntry.getValue());
}
if (addedKeys.remove(oldKey)) {
// potentially changed key since it is in oldMap and newMap
V newValue = valueType.cast(newMap.get(oldKey));
if (!Util.equals(oldValue, newValue)) {
changedKeys.add(oldKey);
oldValues.put(oldKey, oldValue);
newValues.put(oldKey, newValue);
}
} else {
removedKeys.add(oldKey);
oldValues.put(oldKey, oldValue);
}
}
for (Iterator<K> it = addedKeys.iterator(); it.hasNext();) {
K newKey = it.next();
if (keyType == null) {
// Unfortunately we don't always have a class object available
// here to do a safe cast. In fact this occurs in one of the
// test cases.
newValues.put(newKey, (V) newMap.get(newKey));
} else {
newValues.put(newKey, valueType.cast(newMap.get(newKey)));
}
}
return new MapDiff<K, V>() {
public Set<K> getAddedKeys() {
return addedKeys;
}
public Set<K> getChangedKeys() {
return changedKeys;
}
public Set<K> getRemovedKeys() {
return removedKeys;
}
public V getNewValue(Object key) {
return newValues.get(key);
}
public V getOldValue(Object key) {
return oldValues.get(key);
}
};
}
/**
* Returns a lazily computed {@link MapDiff} describing the change between
* the specified old and new map states.
*
* @param <K>
* the type of keys maintained by this map
* @param <V>
* the type of mapped values
* @param oldMap
* the old map state
* @param newMap
* the new map state
* @return a lazily computed {@link MapDiff} describing the change between
* the specified old and new map states.
* @since 1.3
*/
public static <K, V> MapDiff<K, V> computeLazyMapDiff(
final Map<K, V> oldMap, final Map<K, V> newMap) {
return new MapDiff<K, V>() {
private MapDiff<K, V> lazyDiff;
private MapDiff<K, V> getLazyDiff() {
if (lazyDiff == null) {
lazyDiff = computeMapDiff(oldMap, newMap);
}
return lazyDiff;
}
public Set<K> getAddedKeys() {
return getLazyDiff().getAddedKeys();
}
public Set<K> getRemovedKeys() {
return getLazyDiff().getRemovedKeys();
}
public Set<K> getChangedKeys() {
return getLazyDiff().getChangedKeys();
}
public V getOldValue(Object key) {
return getLazyDiff().getOldValue(key);
}
public V getNewValue(Object key) {
return getLazyDiff().getNewValue(key);
}
};
}
/**
* @param <T>
* @param oldValue
* @param newValue
* @return a value diff
*/
public static <T> ValueDiff<T> createValueDiff(final T oldValue,
final T newValue) {
return new ValueDiff<T>() {
public T getOldValue() {
return oldValue;
}
public T getNewValue() {
return newValue;
}
};
}
/**
* @param <E>
* @param additions
* @param removals
* @return a set diff
*/
public static <E> SetDiff<E> createSetDiff(Set<E> additions, Set<E> removals) {
final Set<E> unmodifiableAdditions = Collections
.unmodifiableSet(additions);
final Set<E> unmodifiableRemovals = Collections
.unmodifiableSet(removals);
return new SetDiff<E>() {
public Set<E> getAdditions() {
return unmodifiableAdditions;
}
public Set<E> getRemovals() {
return unmodifiableRemovals;
}
};
}
/**
* @param <E>
* @param difference
* @return a list diff with one differing entry
*/
public static <E> ListDiff<E> createListDiff(ListDiffEntry<E> difference) {
return createListDiff(Collections.singletonList(difference));
}
/**
* @param <E>
* @param difference1
* @param difference2
* @return a list diff with two differing entries
*/
public static <E> ListDiff<E> createListDiff(ListDiffEntry<E> difference1,
ListDiffEntry<E> difference2) {
List<ListDiffEntry<E>> differences = new ArrayList<ListDiffEntry<E>>(2);
differences.add(difference1);
differences.add(difference2);
return createListDiff(differences);
}
/**
* This form cannot be used in a type-safe manner because it is not possible
* to construct an array of generic types in a type-safe manner. The form
* below which takes a properly parameterized List is recommended.
*
* @param <E>
* @param differences
* @return a list diff with the given entries
*/
public static <E> ListDiff<E> createListDiff(
final ListDiffEntry<E>[] differences) {
return new ListDiff<E>() {
public ListDiffEntry<?>[] getDifferences() {
return differences;
}
@Override
public List<ListDiffEntry<E>> getDifferencesAsList() {
return Arrays.asList(differences);
}
};
}
/**
* @param <E>
* @param differences
* @return a list diff with the given entries
* @since 1.5
*/
public static <E> ListDiff<E> createListDiff(
final List<ListDiffEntry<E>> differences) {
final ListDiffEntry<?>[] differencesArray = differences
.toArray(new ListDiffEntry[differences.size()]);
return new ListDiff<E>() {
public ListDiffEntry<?>[] getDifferences() {
return differencesArray;
}
@Override
public List<ListDiffEntry<E>> getDifferencesAsList() {
return differences;
}
};
}
/**
* @param <E>
* @param position
* @param isAddition
* @param element
* @return a list diff entry
*/
public static <E> ListDiffEntry<E> createListDiffEntry(final int position,
final boolean isAddition, final E element) {
return new ListDiffEntry<E>() {
public int getPosition() {
return position;
}
public boolean isAddition() {
return isAddition;
}
public E getElement() {
return element;
}
};
}
/**
* @param <K>
* the type of keys maintained by this map
* @param <V>
* the type of mapped values
* @param addedKey
* @param newValue
* @return a map diff
*/
public static <K, V> MapDiff<K, V> createMapDiffSingleAdd(final K addedKey,
final V newValue) {
return new MapDiff<K, V>() {
public Set<K> getAddedKeys() {
return Collections.singleton(addedKey);
}
public Set<K> getChangedKeys() {
return Collections.emptySet();
}
public V getNewValue(Object key) {
return newValue;
}
public V getOldValue(Object key) {
return null;
}
public Set<K> getRemovedKeys() {
return Collections.emptySet();
}
};
}
/**
* @param <K>
* the type of keys maintained by this map
* @param <V>
* the type of mapped values
* @param existingKey
* @param oldValue
* @param newValue
* @return a map diff
*/
public static <K, V> MapDiff<K, V> createMapDiffSingleChange(
final K existingKey, final V oldValue, final V newValue) {
return new MapDiff<K, V>() {
public Set<K> getAddedKeys() {
return Collections.emptySet();
}
public Set<K> getChangedKeys() {
return Collections.singleton(existingKey);
}
public V getNewValue(Object key) {
return newValue;
}
public V getOldValue(Object key) {
return oldValue;
}
public Set<K> getRemovedKeys() {
return Collections.emptySet();
}
};
}
/**
* @param <K>
* the type of keys maintained by this map
* @param <V>
* the type of mapped values
* @param removedKey
* @param oldValue
* @return a map diff
*/
public static <K, V> MapDiff<K, V> createMapDiffSingleRemove(
final K removedKey, final V oldValue) {
return new MapDiff<K, V>() {
public Set<K> getAddedKeys() {
return Collections.emptySet();
}
public Set<K> getChangedKeys() {
return Collections.emptySet();
}
public V getNewValue(Object key) {
return null;
}
public V getOldValue(Object key) {
return oldValue;
}
public Set<K> getRemovedKeys() {
return Collections.singleton(removedKey);
}
};
}
/**
* @param <K>
* the type of keys maintained by this map
* @param <V>
* the type of mapped values
* @param copyOfOldMap
* @return a map diff
*/
public static <K, V> MapDiff<K, V> createMapDiffRemoveAll(
final Map<K, V> copyOfOldMap) {
return new MapDiff<K, V>() {
public Set<K> getAddedKeys() {
return Collections.emptySet();
}
public Set<K> getChangedKeys() {
return Collections.emptySet();
}
public V getNewValue(Object key) {
return null;
}
public V getOldValue(Object key) {
return copyOfOldMap.get(key);
}
public Set<K> getRemovedKeys() {
return copyOfOldMap.keySet();
}
};
}
/**
* @param <K>
* the type of keys maintained by this map
* @param <V>
* the type of mapped values
* @param addedKeys
* keys that were previously in the map, so does not include
* changed keys
* @param removedKeys
* keys that are no longer in the map, so does not include
* changed keys
* @param changedKeys
* @param oldValues
* old values of all removed and changed keys, so the keys in
* this map will be the keys in removedKeys and the keys in
* changedKeys
* @param newValues
* new values of all added and changed keys, so the keys in this
* map will be the keys in addedKeys and the keys in changedKeys
* @return a map diff
*/
public static <K, V> MapDiff<K, V> createMapDiff(final Set<K> addedKeys,
final Set<K> removedKeys, final Set<K> changedKeys,
final Map<K, V> oldValues, final Map<K, V> newValues) {
return new MapDiff<K, V>() {
public Set<K> getAddedKeys() {
return addedKeys;
}
public Set<K> getChangedKeys() {
return changedKeys;
}
public V getNewValue(Object key) {
return newValues.get(key);
}
public V getOldValue(Object key) {
return oldValues.get(key);
}
public Set<K> getRemovedKeys() {
return removedKeys;
}
};
}
}