blob: d66cf4b057608715f6cbc0c08d920e450756be97 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2015 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Matthew Hall - bug 226216
* Stefan Xenos <sxenos@gmail.com> - Bug 335792
* Stefan Xenos <sxenos@gmail.com> - Bug 474065
*******************************************************************************/
package org.eclipse.core.databinding.observable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
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;
/**
* @since 1.0
*
*/
public class Diffs {
private static final class UnmodifiableListDiff<E> extends ListDiff<E> {
private ListDiff<? extends E> toWrap;
public UnmodifiableListDiff(ListDiff<? extends E> diff) {
this.toWrap = diff;
}
@SuppressWarnings("unchecked")
@Override
public ListDiffEntry<E>[] getDifferences() {
ListDiffEntry<? extends E>[] original = toWrap.getDifferences();
ListDiffEntry<?>[] result = new ListDiffEntry<?>[original.length];
System.arraycopy(original, 0, result, 0, original.length);
return (ListDiffEntry<E>[]) result;
}
}
private static final class UnmodifiableSetDiff<E> extends SetDiff<E> {
private SetDiff<? extends E> toWrap;
public UnmodifiableSetDiff(SetDiff<? extends E> diff) {
toWrap = diff;
}
@Override
public Set<E> getAdditions() {
return Collections.unmodifiableSet(toWrap.getAdditions());
}
@Override
public Set<E> getRemovals() {
return Collections.unmodifiableSet(toWrap.getRemovals());
}
}
private static final class UnmodifiableMapDiff<K, V> extends MapDiff<K, V> {
private MapDiff<? extends K, ? extends V> toWrap;
public UnmodifiableMapDiff(MapDiff<? extends K, ? extends V> diff) {
toWrap = diff;
}
@Override
public Set<K> getAddedKeys() {
return Collections.unmodifiableSet(toWrap.getAddedKeys());
}
@Override
public Set<K> getRemovedKeys() {
return Collections.unmodifiableSet(toWrap.getRemovedKeys());
}
@Override
public Set<K> getChangedKeys() {
return Collections.unmodifiableSet(toWrap.getChangedKeys());
}
@Override
public V getOldValue(Object key) {
return toWrap.getOldValue(key);
}
@Override
public V getNewValue(Object key) {
return toWrap.getNewValue(key);
}
}
private static final class UnmodifiableValueDiff<E> extends ValueDiff<E> {
private ValueDiff<? extends E> toWrap;
public UnmodifiableValueDiff(ValueDiff<? extends E> diff) {
toWrap = diff;
}
@Override
public E getOldValue() {
return toWrap.getOldValue();
}
@Override
public E getNewValue() {
return toWrap.getNewValue();
}
}
/**
* Returns an unmodifiable wrapper on top of the given diff. The returned
* diff will suppress any attempt to modify the collections it returns.
* Diffs are normally unmodifiable anyway, so this method is mainly used as
* a type-safe way to convert a {@code ListDiff<? extends E>} into a
* {@code ListDiff<E>}.
*
* @param diff
* the diff to convert
* @return an unmodifiable wrapper on top of the given diff
* @since 1.6
*/
@SuppressWarnings("unchecked")
public static <E> ListDiff<E> unmodifiableDiff(ListDiff<? extends E> diff) {
// If the diff is already unmodifiable, there's no need to wrap it again
if (diff instanceof UnmodifiableListDiff) {
return (ListDiff<E>) diff;
}
return new UnmodifiableListDiff<>(diff);
}
/**
* Returns an unmodifiable wrapper on top of the given diff. The returned
* diff will suppress any attempt to modify the collections it returns.
* Diffs are normally unmodifiable anyway, so this method is mainly used as
* a type-safe way to convert a {@code SetDiff<? extends E>} into a
* {@code SetDiff<E>}.
*
* @param diff
* the diff to convert
* @return an unmodifiable wrapper on top of the given diff
* @since 1.6
*/
@SuppressWarnings("unchecked")
public static <E> SetDiff<E> unmodifiableDiff(SetDiff<? extends E> diff) {
// If the diff is already unmodifiable, there's no need to wrap it again
if (diff instanceof UnmodifiableSetDiff) {
return (SetDiff<E>) diff;
}
return new UnmodifiableSetDiff<>(diff);
}
/**
* Returns an unmodifiable wrapper on top of the given diff. The returned
* diff will suppress any attempt to modify the collections it returns.
* Diffs are normally unmodifiable anyway, so this method is mainly used as
* a type-safe way to convert a {@code MapDiff<? extends K, ? extends V>}
* into a {@code MapDiff<K,V>}.
*
* @param diff
* the diff to convert
* @return an unmodifiable wrapper on top of the given diff
* @since 1.6
*/
@SuppressWarnings("unchecked")
public static <K, V> MapDiff<K, V> unmodifiableDiff(MapDiff<? extends K, ? extends V> diff) {
// If the diff is already unmodifiable, there's no need to wrap it again
if (diff instanceof UnmodifiableMapDiff) {
return (MapDiff<K, V>) diff;
}
return new UnmodifiableMapDiff<>(diff);
}
/**
* Returns an unmodifiable wrapper on top of the given diff. The returned
* diff will suppress any attempt to modify the collections it returns.
* Diffs are normally unmodifiable anyway, so this method is mainly used as
* a type-safe way to convert a {@code ValueDiff<? extends V>} into a
* {@code ValueDiff<V>}.
*
* @param diff
* the diff to convert
* @return an unmodifiable wrapper on top of the given diff
* @since 1.6
*/
@SuppressWarnings("unchecked")
public static <V> ValueDiff<V> unmodifiableDiff(ValueDiff<? extends V> diff) {
// If the diff is already unmodifiable, there's no need to wrap it again
if (diff instanceof UnmodifiableValueDiff) {
return (ValueDiff<V>) diff;
}
return new UnmodifiableValueDiff<>(diff);
}
/**
* Returns a {@link ListDiff} describing the change between the specified
* old and new list states.
*
* @param <E>
* the list element type
*
* @param oldList
* the old list state
* @param newList
* the new list state
* @return the differences between oldList and newList
* @since 1.6
*/
public static <E> ListDiff<E> computeListDiff(List<? extends E> oldList, List<? extends E> newList) {
List<ListDiffEntry<E>> diffEntries = new ArrayList<>();
createListDiffs(new ArrayList<>(oldList), newList, diffEntries);
return createListDiff(diffEntries);
}
/**
* Returns a lazily computed {@link ListDiff} describing the change between
* the specified old and new list states.
*
* @param <E>
* the list element type
*
* @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<? extends E> oldList,
final List<? extends E> newList) {
return new ListDiff<E>() {
ListDiff<E> lazyDiff;
@Override
public ListDiffEntry<E>[] getDifferences() {
if (lazyDiff == null) {
lazyDiff = Diffs.computeListDiff(oldList, newList);
}
return lazyDiff.getDifferences();
}
};
}
/**
* adapted from EMF's ListDifferenceAnalyzer
*/
private static <E> void createListDiffs(List<E> oldList, List<? extends E> newList,
List<ListDiffEntry<E>> listDiffs) {
int index = 0;
for (E newValue : newList) {
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.
* @deprecated Use {@link Objects#equals(Object, Object)} instead
*/
@Deprecated
public static final boolean equals(final Object left, final Object right) {
return Objects.equals(left, right);
}
/**
* Returns a {@link SetDiff} describing the change between the specified old and
* new set states.
*
* @param <E> the set element type
*
* @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.
*/
public static <E> SetDiff<E> computeSetDiff(Set<? extends E> oldSet, Set<? extends E> newSet) {
Set<E> additions = new HashSet<>(newSet);
additions.removeAll(oldSet);
Set<E> removals = new HashSet<>(oldSet);
removals.removeAll(newSet);
return createSetDiff(additions, removals);
}
/**
* Returns a lazily computed {@link SetDiff} describing the change between
* the specified old and new set states.
*
* @param <E>
* the set element type
*
* @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<? extends E> oldSet, final Set<? extends E> newSet) {
return new SetDiff<E>() {
private SetDiff<E> lazyDiff;
private SetDiff<E> getLazyDiff() {
if (lazyDiff == null) {
lazyDiff = computeSetDiff(oldSet, newSet);
}
return lazyDiff;
}
@Override
public Set<E> getAdditions() {
return getLazyDiff().getAdditions();
}
@Override
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<? extends K, ? extends V> oldMap,
Map<? extends K, ? extends 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<>(newMap.keySet());
final Set<K> removedKeys = new HashSet<>();
final Set<K> changedKeys = new HashSet<>();
final Map<K, V> oldValues = new HashMap<>();
final Map<K, V> newValues = new HashMap<>();
for (Entry<? extends K, ? extends V> oldEntry : oldMap.entrySet()) {
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 (!Objects.equals(oldValue, newValue)) {
changedKeys.add(oldKey);
oldValues.put(oldKey, oldValue);
newValues.put(oldKey, newValue);
}
} else {
removedKeys.add(oldKey);
oldValues.put(oldKey, oldEntry.getValue());
}
}
for (K newKey : addedKeys) {
newValues.put(newKey, newMap.get(newKey));
}
return new MapDiff<K, V>() {
@Override
public Set<K> getAddedKeys() {
return addedKeys;
}
@Override
public Set<K> getChangedKeys() {
return changedKeys;
}
@Override
public Set<K> getRemovedKeys() {
return removedKeys;
}
@Override
public V getNewValue(Object key) {
return newValues.get(key);
}
@Override
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<? extends K, ? extends V> oldMap,
final Map<? extends K, ? extends 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;
}
@Override
public Set<K> getAddedKeys() {
return getLazyDiff().getAddedKeys();
}
@Override
public Set<K> getRemovedKeys() {
return getLazyDiff().getRemovedKeys();
}
@Override
public Set<K> getChangedKeys() {
return getLazyDiff().getChangedKeys();
}
@Override
public V getOldValue(Object key) {
return getLazyDiff().getOldValue(key);
}
@Override
public V getNewValue(Object key) {
return getLazyDiff().getNewValue(key);
}
};
}
/**
* Creates a diff between two values
*
* @param <T>
* the value type
* @param oldValue
* @param newValue
* @return a value diff
*/
public static <T> ValueDiff<T> createValueDiff(final T oldValue, final T newValue) {
return new ValueDiff<T>() {
@Override
public T getOldValue() {
return oldValue;
}
@Override
public T getNewValue() {
return newValue;
}
};
}
/**
* @param <E>
* the set element type
* @param additions
* @param removals
* @return a set diff
*/
public static <E> SetDiff<E> createSetDiff(Set<? extends E> additions, Set<? extends E> removals) {
final Set<E> unmodifiableAdditions = Collections
.unmodifiableSet(additions);
final Set<E> unmodifiableRemovals = Collections
.unmodifiableSet(removals);
return new SetDiff<E>() {
@Override
public Set<E> getAdditions() {
return unmodifiableAdditions;
}
@Override
public Set<E> getRemovals() {
return unmodifiableRemovals;
}
};
}
/**
* @param <E>
* the list element type
* @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>
* the list element type
* @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<>(2);
differences.add(difference1);
differences.add(difference2);
return createListDiff(differences);
}
/**
* Creates a new ListDiff object given its constituent ListDiffEntry
* objects.
* <p>
* 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. Use the
* form below which takes a properly parameterized List.
*
* @param <E>
* the list element type
* @param differences
* @return a list diff with the given entries
*/
@SafeVarargs
public static <E> ListDiff<E> createListDiff(final ListDiffEntry<E>... differences) {
return new ListDiff<E>() {
@Override
public ListDiffEntry<E>[] getDifferences() {
return differences;
}
};
}
/**
* Creates a new ListDiff object given its constituent ListDiffEntry
* objects.
*
* @param <E>
* the list element type
* @param differences
* @return a list diff with the given entries
* @since 1.6
*/
public static <E> ListDiff<E> createListDiff(final List<ListDiffEntry<E>> differences) {
final ListDiffEntry<E>[] differencesArray = differences.toArray(new ListDiffEntry[differences.size()]);
return new ListDiff<E>() {
@Override
public ListDiffEntry<E>[] getDifferences() {
return differencesArray;
}
};
}
/**
* @param <E>
* the list element type
* @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>() {
@Override
public int getPosition() {
return position;
}
@Override
public boolean isAddition() {
return isAddition;
}
@Override
public E getElement() {
return element;
}
};
}
/**
* Creates a MapDiff representing the addition of a single added key
*
* @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>() {
@Override
public Set<K> getAddedKeys() {
return Collections.singleton(addedKey);
}
@Override
public Set<K> getChangedKeys() {
return Collections.emptySet();
}
@Override
public V getNewValue(Object key) {
return newValue;
}
@Override
public V getOldValue(Object key) {
return null;
}
@Override
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>() {
@Override
public Set<K> getAddedKeys() {
return Collections.emptySet();
}
@Override
public Set<K> getChangedKeys() {
return Collections.singleton(existingKey);
}
@Override
public V getNewValue(Object key) {
return newValue;
}
@Override
public V getOldValue(Object key) {
return oldValue;
}
@Override
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>() {
@Override
public Set<K> getAddedKeys() {
return Collections.emptySet();
}
@Override
public Set<K> getChangedKeys() {
return Collections.emptySet();
}
@Override
public V getNewValue(Object key) {
return null;
}
@Override
public V getOldValue(Object key) {
return oldValue;
}
@Override
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>() {
@Override
public Set<K> getAddedKeys() {
return Collections.emptySet();
}
@Override
public Set<K> getChangedKeys() {
return Collections.emptySet();
}
@Override
public V getNewValue(Object key) {
return null;
}
@Override
public V getOldValue(Object key) {
return copyOfOldMap.get(key);
}
@Override
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
* @param removedKeys
* @param changedKeys
* @param oldValues
* @param newValues
* @return a map diff
*/
public static <K, V> MapDiff<K, V> createMapDiff(Set<? extends K> addedKeys, Set<? extends K> removedKeys,
Set<? extends K> changedKeys, final Map<? extends K, ? extends V> oldValues,
final Map<? extends K, ? extends V> newValues) {
final Set<K> finalAddedKeys = Collections.unmodifiableSet(addedKeys);
final Set<K> finalRemovedKeys = Collections.unmodifiableSet(removedKeys);
final Set<K> finalChangedKeys = Collections.unmodifiableSet(changedKeys);
return new MapDiff<K, V>() {
@Override
public Set<K> getAddedKeys() {
return finalAddedKeys;
}
@Override
public Set<K> getChangedKeys() {
return finalChangedKeys;
}
@Override
public V getNewValue(Object key) {
return newValues.get(key);
}
@Override
public V getOldValue(Object key) {
return oldValues.get(key);
}
@Override
public Set<K> getRemovedKeys() {
return finalRemovedKeys;
}
};
}
}