/*******************************************************************************
* Copyright (c) 2016 itemis AG 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:
* Matthias Wienand (itemis AG) - initial API and implementation
* Alexander Nyßen (itemis AG) - initial API and implementation
*
*******************************************************************************/
package org.eclipse.gef.common.collections;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import org.eclipse.gef.common.collections.ListListenerHelperEx.AtomicChange;
import org.eclipse.gef.common.collections.ListListenerHelperEx.ElementarySubChange;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import com.google.common.collect.SetMultimap;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
/**
* The {@link CollectionUtils} contains a method to compute the old value of an
* {@link ObservableList} for a given
* {@link javafx.collections.ListChangeListener.Change} event. For details, see
* {@link #getPreviousContents(javafx.collections.ListChangeListener.Change)}.
*
* @author mwienand
* @author anyssen
*
*/
public class CollectionUtils {
/**
* Returns an empty, unmodifiable {@link ObservableMultiset}.
*
* @param <E>
* The element type of the {@link ObservableMultiset}.
* @return An empty, unmodifiable {@link ObservableMultiset}.
*/
public static <E> ObservableMultiset<E> emptyMultiset() {
// TODO: use singleton field
return new UnmodifiableObservableMultisetWrapper<>(
new ObservableMultisetWrapper<>(HashMultiset.<E> create()));
}
/**
* Returns an empty, unmodifiable {@link ObservableSetMultimap}.
*
* @param <K>
* The key type of the {@link ObservableSetMultimap}.
* @param <V>
* The value type of the {@link ObservableSetMultimap}.
* @return An empty, unmodifiable {@link ObservableSetMultimap}.
*/
public static <K, V> ObservableSetMultimap<K, V> emptySetMultimap() {
// TODO: use singleton field
return new UnmodifiableObservableSetMultimapWrapper<>(
new ObservableSetMultimapWrapper<>(
HashMultimap.<K, V> create()));
}
/**
* Computes the permutation for the given {@link Change}.
*
* @param <E>
* The element type of the {@link ObservableList} that was
* changed.
* @param change
* The change, for which {@link Change#wasPermutated()} has to
* return <code>true</code>.
* @return An integer array mapping previous indexes to current ones.
*/
public static <E> int[] getPermutation(
ListChangeListener.Change<? extends E> change) {
if (!change.wasPermutated()) {
throw new IllegalArgumentException(
"Change is no permutation change.");
}
if (change instanceof AtomicChange) {
return ((AtomicChange<?>) change).getPermutation();
}
int[] permutation = new int[change.getTo() - change.getFrom()];
for (int oldIndex = change.getFrom(); oldIndex < change
.getTo(); oldIndex++) {
int newIndex = change.getPermutation(oldIndex);
permutation[oldIndex] = newIndex;
}
return permutation;
}
/**
* Computes the previous contents of the source {@link ObservableList}
* before the given {@link javafx.collections.ListChangeListener.Change} was
* applied.
*
* @param <E>
* The element type of the {@link ObservableList}.
* @param change
* The {@link javafx.collections.ListChangeListener.Change} for
* which to compute the previous contents.
* @return A newly created {@link List} that resembles the state of the
* source {@link ObservableList} before the change.
*/
public static <E> List<E> getPreviousContents(
ListChangeListener.Change<E> change) {
if (change instanceof AtomicChange) {
return ((AtomicChange<E>) change).getPreviousContents();
}
ObservableList<E> currentList = change.getList();
ObservableList<E> previousList = FXCollections
.observableArrayList(currentList);
// walk over elementary changes and record them in a list
change.reset();
List<ElementarySubChange<E>> changes = ListListenerHelperEx
.getElementaryChanges(change);
// undo the changes in reverse order
for (int i = changes.size() - 1; i >= 0; i--) {
ElementarySubChange<E> c = changes.get(i);
int from = c.getFrom();
int to = c.getTo();
if (ElementarySubChange.Kind.ADD.equals(c.getKind())
|| ElementarySubChange.Kind.REPLACE.equals(c.getKind())) {
// remove added elements
for (int j = to - 1; j >= from; j--) {
previousList.remove(j);
}
}
if (ElementarySubChange.Kind.REMOVE.equals(c.getKind())
|| ElementarySubChange.Kind.REPLACE.equals(c.getKind())) {
// add removed elements
List<E> removed = c.getRemoved();
previousList.addAll(from, removed);
}
if (ElementarySubChange.Kind.PERMUTATE.equals(c.getKind())) {
// create sub list with old permutation
int[] permutation = c.getPermutation();
List<E> subList = new ArrayList<>(to - from);
for (int j = from; j < to; j++) {
int k = permutation[j - from];
subList.add(currentList.get(k));
}
// insert sub list at correct position
previousList.remove(from, to);
previousList.addAll(from, subList);
}
}
return previousList;
}
/**
* Returns a (modifiable) new {@link ObservableList} wrapping an
* {@link ArrayList}.
*
* Please note that in order to obtain proper change notifications when
* sorting the returned {@link ObservableList},
* {@link #sort(ObservableList)} or
* {@link #sort(ObservableList, Comparator)} have to be used instead of
* {@link FXCollections#sort(ObservableList)} and
* {@link FXCollections#sort(ObservableList, Comparator)}.
*
* @param <E>
* The element type of the {@link ObservableList}. The
* {@link List} to wrap.
* @return An {@link ObservableList} wrapping the given {@link List}.
*/
public static <E> ObservableList<E> observableArrayList() {
return observableList(new ArrayList<E>());
}
/**
* Create a new {@link ObservableList} that is backed by an
* {@link ArrayList} that contains the contents of the given
* {@link Collection}.
*
* @param <E>
* The element type of the {@link ObservableList}.
* @param collection
* The {@link Collection} that provides the initial contents of
* the to be created {@link ObservableList}.
* @return A new {@link ObservableList} containing the given contents.
*/
public static <E> ObservableList<E> observableArrayList(
Collection<? extends E> collection) {
ObservableList<E> list = observableArrayList();
list.addAll(collection);
return list;
}
/**
* Creates a new {@link ObservableList} that contains the given elements.
*
* @param <E>
* The element type of the {@link ObservableList}.
*
* @return a newly created observableArrayList
* @param elements
* The elements that will be added to the returned
* {@link ObservableList}
*/
@SuppressWarnings("unchecked")
public static <E> ObservableList<E> observableArrayList(E... elements) {
ObservableList<E> list = observableArrayList();
list.addAll(elements);
return list;
}
/**
* Returns a (modifiable) new {@link ObservableSetMultimap} wrapping a
* {@link HashMultimap}.
*
* @param <K>
* The key type of the {@link ObservableSetMultimap}.
* @param <V>
* The value type of the {@link ObservableSetMultimap}
* @return An {@link ObservableSetMultimap} wrapping a {@link HashMultimap}.
*/
public static <K, V> ObservableSetMultimap<K, V> observableHashMultimap() {
return observableSetMultimap(HashMultimap.<K, V> create());
}
/**
* Returns a (modifiable) new {@link ObservableMultiset} wrapping a
* {@link HashMultiset}.
*
* @param <E>
* The element type of the {@link ObservableList}.
* @return An {@link ObservableMultiset} wrapping a {@link HashMultiset}.
*/
public static <E> ObservableMultiset<E> observableHashMultiset() {
return observableMultiset(HashMultiset.<E> create());
}
/**
* Returns a (modifiable) new {@link ObservableList} wrapping the given
* {@link List}.
*
* Please note that in order to obtain proper change notifications when
* sorting the returned {@link ObservableList},
* {@link #sort(ObservableList)} or
* {@link #sort(ObservableList, Comparator)} have to be used instead of
* {@link FXCollections#sort(ObservableList)} and
* {@link FXCollections#sort(ObservableList, Comparator)}.
*
* @param <E>
* The element type of the {@link ObservableList}.
* @param list
* The {@link List} to wrap.
* @return An {@link ObservableList} wrapping the given {@link List}.
*/
public static <E> ObservableList<E> observableList(List<E> list) {
if (list == null) {
throw new NullPointerException();
}
return new ObservableListWrapperEx<>(list);
}
/**
* Returns a (modifiable) new {@link ObservableMultiset} wrapping the given
* {@link List}.
*
* @param <E>
* The element type of the {@link ObservableList}.
* @param multiset
* The {@link Multiset} to wrap.
* @return An {@link ObservableMultiset} wrapping the given {@link List}.
*/
public static <E> ObservableMultiset<E> observableMultiset(
Multiset<E> multiset) {
if (multiset == null) {
throw new NullPointerException();
}
return new ObservableMultisetWrapper<>(multiset);
}
/**
* Returns a (modifiable) new {@link ObservableSetMultimap} wrapping the
* given {@link SetMultimap}.
*
* @param <K>
* The key type of the {@link ObservableSetMultimap}.
* @param <V>
* The value type of the {@link ObservableSetMultimap}
* @param setMultimap
* The {@link SetMultimap} to wrap.
* @return An {@link ObservableSetMultimap} wrapping the given {@link List}.
*/
public static <K, V> ObservableSetMultimap<K, V> observableSetMultimap(
SetMultimap<K, V> setMultimap) {
if (setMultimap == null) {
throw new NullPointerException();
}
return new ObservableSetMultimapWrapper<>(setMultimap);
}
/**
* Sorts the given {@link ObservableList} using the default
* {@link Comparator} .
*
* @param <E>
* The value type of the {@link ObservableList}.
* @param observableList
* The {@link ObservableList} to sort.
*/
public static <E extends Comparable<? super E>> void sort(
ObservableList<E> observableList) {
if (observableList instanceof ObservableListWrapperEx) {
((ObservableListWrapperEx<? extends E>) observableList).sort();
} else {
FXCollections.sort(observableList);
}
}
/**
* Sorts the given {@link ObservableList} using the given {@link Comparator}
* .
*
* @param <E>
* The value type of the {@link ObservableList}.
* @param observableList
* The {@link ObservableList} to sort.
* @param comparator
* The {@link Comparator} to use.
*/
public static <E> void sort(ObservableList<E> observableList,
Comparator<? super E> comparator) {
if (observableList instanceof ObservableListWrapperEx) {
((ObservableListWrapperEx<? extends E>) observableList)
.sort(comparator);
} else {
FXCollections.sort(observableList, comparator);
}
}
/**
* Returns an unmodifiable {@link ObservableMultiset} wrapping the given
* {@link ObservableMultiset}.
*
* @param <E>
* The element type of the {@link ObservableMultiset}.
* @param multiset
* The {@link ObservableMultiset} to wrap.
* @return An unmodifiable wrapper around the given
* {@link ObservableMultiset}.
*/
public static <E> ObservableMultiset<E> unmodifiableObservableMultiset(
ObservableMultiset<E> multiset) {
if (multiset == null) {
throw new NullPointerException();
}
return new UnmodifiableObservableMultisetWrapper<>(multiset);
}
/**
* Returns an unmodifiable {@link ObservableSetMultimap} wrapping the given
* {@link ObservableSetMultimap}.
*
* @param <K>
* The key type of the {@link ObservableSetMultimap}.
* @param <V>
* The value type of the {@link ObservableSetMultimap}.
* @param setMultimap
* The {@link ObservableSetMultimap} to wrap.
* @return An unmodifiable wrapper around the given
* {@link ObservableSetMultimap}.
*/
public static <K, V> ObservableSetMultimap<K, V> unmodifiableObservableSetMultimap(
ObservableSetMultimap<K, V> setMultimap) {
if (setMultimap == null) {
throw new NullPointerException();
}
return new UnmodifiableObservableSetMultimapWrapper<>(setMultimap);
}
}