/* * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javafx.collections; import com.sun.javafx.collections.ListListenerHelper; import com.sun.javafx.collections.MapListenerHelper; import com.sun.javafx.collections.SetListenerHelper; import java.lang.reflect.Array; import java.util.AbstractList; import java.util.AbstractMap; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.NoSuchElementException; import java.util.Random; import java.util.Set; import javafx.beans.InvalidationListener; import com.sun.javafx.collections.ObservableListWrapper; import com.sun.javafx.collections.ObservableMapWrapper; import com.sun.javafx.collections.ObservableSetWrapper; import com.sun.javafx.collections.MapAdapterChange; import com.sun.javafx.collections.ObservableFloatArrayImpl; import com.sun.javafx.collections.ObservableIntegerArrayImpl; import com.sun.javafx.collections.ObservableSequentialListWrapper; import com.sun.javafx.collections.SetAdapterChange; import com.sun.javafx.collections.SortableList; import com.sun.javafx.collections.SourceAdapterChange; import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection; import java.util.RandomAccess; import javafx.beans.Observable; import javafx.collections.ListChangeListener.Change; import javafx.util.Callback; /** * Utility class that consists of static methods that are 1:1 copies of java.util.Collections methods. *

* The wrapper methods (like synchronizedObservableList or emptyObservableList) has exactly the same * functionality as the methods in Collections, with exception that they return ObservableList and are * therefore suitable for methods that require ObservableList on input. *

* The utility methods are here mainly for performance reasons. All methods are optimized in a way that * they yield only limited number of notifications. On the other hand, java.util.Collections methods * might call "modification methods" on an ObservableList multiple times, resulting in a number of notifications. * * @since JavaFX 2.0 */ public class FXCollections { /** Not to be instantiated. */ private FXCollections() { } /** * Constructs an ObservableList that is backed by the specified list. * Mutation operations on the ObservableList instance will be reported * to observers that have registered on that instance.
* Note that mutation operations made directly to the underlying list are * not reported to observers of any ObservableList that * wraps it. * * @param list a concrete List that backs this ObservableList * @return a newly created ObservableList */ public static ObservableList observableList(List list) { if (list == null) { throw new NullPointerException(); } return list instanceof RandomAccess ? new ObservableListWrapper(list) : new ObservableSequentialListWrapper(list); } /** * Constructs an ObservableList that is backed by the specified list. * Mutation operations on the ObservableList instance will be reported * to observers that have registered on that instance.
* Note that mutation operations made directly to the underlying list are * not reported to observers of any ObservableList that * wraps it. *
* This list also reports mutations of the elements in it by using extractor. * Observable objects returned by extractor (applied to each list element) are listened for changes * and transformed into "update" change of ListChangeListener. * * @param list a concrete List that backs this ObservableList * @param extractor element to Observable[] convertor * @since JavaFX 2.1 * @return a newly created ObservableList */ public static ObservableList observableList(List list, Callback extractor) { if (list == null || extractor == null) { throw new NullPointerException(); } return list instanceof RandomAccess ? new ObservableListWrapper(list, extractor) : new ObservableSequentialListWrapper(list, extractor); } /** * Constructs an ObservableMap that is backed by the specified map. * Mutation operations on the ObservableMap instance will be reported * to observers that have registered on that instance.
* Note that mutation operations made directly to the underlying map are not * reported to observers of any ObservableMap that wraps it. * @param map a Map that backs this ObservableMap * @return a newly created ObservableMap */ public static ObservableMap observableMap(Map map) { if (map == null) { throw new NullPointerException(); } return new ObservableMapWrapper(map); } /** * Constructs an ObservableSet that is backed by the specified set. * Mutation operations on the ObservableSet instance will be reported * to observers that have registered on that instance.
* Note that mutation operations made directly to the underlying set are not * reported to observers of any ObservableSet that wraps it. * @param set a Set that backs this ObservableSet * @return a newly created ObservableSet * @since JavaFX 2.1 */ public static ObservableSet observableSet(Set set) { if (set == null) { throw new NullPointerException(); } return new ObservableSetWrapper(set); } /** * Constructs an ObservableSet backed by a HashSet * that contains all the specified elements. * @param elements elements that will be added into returned ObservableSet * @return a newly created ObservableSet * @since JavaFX 2.1 */ public static ObservableSet observableSet(E... elements) { if (elements == null) { throw new NullPointerException(); } Set set = new HashSet(elements.length); Collections.addAll(set, elements); return new ObservableSetWrapper(set); } /** * Constructs a read-only interface to the specified ObservableMap. Only * mutation operations made to the underlying ObservableMap will be reported * to observers that have registered on the unmodifiable instance. This allows * clients to track changes in a Map but disallows the ability to modify it. * @param map an ObservableMap that is to be monitored by this interface * @return a newly created UnmodifiableObservableMap */ @ReturnsUnmodifiableCollection public static ObservableMap unmodifiableObservableMap(ObservableMap map) { if (map == null) { throw new NullPointerException(); } return new com.sun.javafx.collections.UnmodifiableObservableMap(map); } /** * Creates and returns a typesafe wrapper on top of provided observable map. * @param map an Observable map to be wrapped * @param keyType the type of key that {@code map} is permitted to hold * @param valueType the type of value that {@code map} is permitted to hold * @return a dynamically typesafe view of the specified map * @see Collections#checkedMap(java.util.Map, java.lang.Class) * @since JavaFX 8.0 */ public static ObservableMap checkedObservableMap(ObservableMap map, Class keyType, Class valueType) { if (map == null || keyType == null || valueType == null) { throw new NullPointerException(); } return new CheckedObservableMap(map, keyType, valueType); } /** * Creates and returns a synchronized wrapper on top of provided observable map. * @param map the map to be "wrapped" in a synchronized map. * @return A synchronized version of the observable map * @see Collections#synchronizedMap(java.util.Map) * @since JavaFX 8.0 */ public static ObservableMap synchronizedObservableMap(ObservableMap map) { if (map == null) { throw new NullPointerException(); } return new SynchronizedObservableMap(map); } private static ObservableMap EMPTY_OBSERVABLE_MAP = new EmptyObservableMap(); /** * Creates and empty unmodifiable observable map. * @return An empty unmodifiable observable map * @see Collections#emptyMap() * @since JavaFX 8.0 */ @SuppressWarnings("unchecked") @ReturnsUnmodifiableCollection public static ObservableMap emptyObservableMap() { return EMPTY_OBSERVABLE_MAP; } /** * Creates a new empty observable integer array. * @return a newly created ObservableIntegerArray * @since JavaFX 8.0 */ public static ObservableIntegerArray observableIntegerArray() { return new ObservableIntegerArrayImpl(); } /** * Creates a new observable integer array with {@code values} set to it. * @param values the values that will be in the new observable integer array * @return a newly created ObservableIntegerArray * @since JavaFX 8.0 */ public static ObservableIntegerArray observableIntegerArray(int... values) { return new ObservableIntegerArrayImpl(values); } /** * Creates a new observable integer array with copy of elements in given * {@code array}. * @param array observable integer array to copy * @return a newly created ObservableIntegerArray * @since JavaFX 8.0 */ public static ObservableIntegerArray observableIntegerArray(ObservableIntegerArray array) { return new ObservableIntegerArrayImpl(array); } /** * Creates a new empty observable float array. * @return a newly created ObservableFloatArray * @since JavaFX 8.0 */ public static ObservableFloatArray observableFloatArray() { return new ObservableFloatArrayImpl(); } /** * Creates a new observable float array with {@code values} set to it. * @param values the values that will be in the new observable float array * @return a newly created ObservableFloatArray * @since JavaFX 8.0 */ public static ObservableFloatArray observableFloatArray(float... values) { return new ObservableFloatArrayImpl(values); } /** * Creates a new observable float array with copy of elements in given * {@code array}. * @param array observable float array to copy * @return a newly created ObservableFloatArray * @since JavaFX 8.0 */ public static ObservableFloatArray observableFloatArray(ObservableFloatArray array) { return new ObservableFloatArrayImpl(array); } /** * Creates a new empty observable list that is backed by an arraylist. * @see #observableList(java.util.List) * @return a newly created ObservableList */ @SuppressWarnings("unchecked") public static ObservableList observableArrayList() { return observableList(new ArrayList()); } /** * Creates a new empty observable list backed by an arraylist. * * This list reports element updates. * @param extractor element to Observable[] convertor. Observable objects are listened for changes on the element. * @see #observableList(java.util.List, javafx.util.Callback) * @since JavaFX 2.1 * @return a newly created ObservableList */ public static ObservableList observableArrayList(Callback extractor) { return observableList(new ArrayList(), extractor); } /** * Creates a new observable array list with {@code items} added to it. * @return a newly created observableArrayList * @param items the items that will be in the new observable ArrayList * @see #observableArrayList() */ public static ObservableList observableArrayList(E... items) { ObservableList list = observableArrayList(); list.addAll(items); return list; } /** * Creates a new observable array list and adds a content of collection {@code col} * to it. * @param col a collection which content should be added to the observableArrayList * @return a newly created observableArrayList */ public static ObservableList observableArrayList(Collection col) { ObservableList list = observableArrayList(); list.addAll(col); return list; } /** * Creates a new empty observable map that is backed by a HashMap. * @param the type of keys * @param the type of values * @return a newly created observable HashMap */ public static ObservableMap observableHashMap() { return observableMap(new HashMap()); } /** * Concatenates more observable lists into one. The resulting list * would be backed by an arraylist. * @param lists lists to concatenate * @return new observable array list concatenated from the arguments */ public static ObservableList concat(ObservableList... lists) { if (lists.length == 0 ) { return observableArrayList(); } if (lists.length == 1) { return observableArrayList(lists[0]); } ArrayList backingList = new ArrayList(); for (ObservableList s : lists) { backingList.addAll(s); } return observableList(backingList); } /** * Creates and returns unmodifiable wrapper list on top of provided observable list. * @param list an ObservableList that is to be wrapped * @return an ObserableList wrapper that is unmodifiable * @see Collections#unmodifiableList(java.util.List) */ @ReturnsUnmodifiableCollection public static ObservableList unmodifiableObservableList(ObservableList list) { if (list == null) { throw new NullPointerException(); } return new UnmodifiableObservableListImpl(list); } /** * Creates and returns a typesafe wrapper on top of provided observable list. * @param list an Observable list to be wrapped * @param type the type of element that list is permitted to hold * @return a dynamically typesafe view of the specified list * @see Collections#checkedList(java.util.List, java.lang.Class) */ public static ObservableList checkedObservableList(ObservableList list, Class type) { if (list == null) { throw new NullPointerException(); } return new CheckedObservableList(list, type); } /** * Creates and returns a synchronized wrapper on top of provided observable list. * @param list the list to be "wrapped" in a synchronized list. * @return A synchronized version of the observable list * @see Collections#synchronizedList(java.util.List) */ public static ObservableList synchronizedObservableList(ObservableList list) { if (list == null) { throw new NullPointerException(); } return new SynchronizedObservableList(list); } private static ObservableList EMPTY_OBSERVABLE_LIST = new EmptyObservableList(); /** * Creates and empty unmodifiable observable list. * @return An empty unmodifiable observable list * @see Collections#emptyList() */ @SuppressWarnings("unchecked") @ReturnsUnmodifiableCollection public static ObservableList emptyObservableList() { return EMPTY_OBSERVABLE_LIST; } /** * Creates an unmodifiable observable list with single element. * @param e the only elements that will be contained in this singleton observable list * @return a singleton observable list * @see Collections#singletonList(java.lang.Object) */ @ReturnsUnmodifiableCollection public static ObservableList singletonObservableList(E e) { return new SingletonObservableList(e); } /** * Creates and returns unmodifiable wrapper on top of provided observable set. * @param set an ObservableSet that is to be wrapped * @return an ObserableSet wrapper that is unmodifiable * @see Collections#unmodifiableSet(java.util.Set) * @since JavaFX 8.0 */ @ReturnsUnmodifiableCollection public static ObservableSet unmodifiableObservableSet(ObservableSet set) { if (set == null) { throw new NullPointerException(); } return new UnmodifiableObservableSet(set); } /** * Creates and returns a typesafe wrapper on top of provided observable set. * @param set an Observable set to be wrapped * @param type the type of element that set is permitted to hold * @return a dynamically typesafe view of the specified set * @see Collections#checkedSet(java.util.Set, java.lang.Class) * @since JavaFX 8.0 */ public static ObservableSet checkedObservableSet(ObservableSet set, Class type) { if (set == null) { throw new NullPointerException(); } return new CheckedObservableSet(set, type); } /** * Creates and returns a synchronized wrapper on top of provided observable set. * @param set the set to be "wrapped" in a synchronized set. * @return A synchronized version of the observable set * @see Collections#synchronizedSet(java.util.Set) * @since JavaFX 8.0 */ public static ObservableSet synchronizedObservableSet(ObservableSet set) { if (set == null) { throw new NullPointerException(); } return new SynchronizedObservableSet(set); } private static ObservableSet EMPTY_OBSERVABLE_SET = new EmptyObservableSet(); /** * Creates and empty unmodifiable observable set. * @return An empty unmodifiable observable set * @see Collections#emptySet() * @since JavaFX 8.0 */ @SuppressWarnings("unchecked") @ReturnsUnmodifiableCollection public static ObservableSet emptyObservableSet() { return EMPTY_OBSERVABLE_SET; } /** * Copies elements from src to dest. Fires only one change notification on dest. * @param dest the destination observable list * @param src the source list * @see Collections#copy(java.util.List, java.util.List) */ @SuppressWarnings("unchecked") public static void copy(ObservableList dest, List src) { final int srcSize = src.size(); if (srcSize > dest.size()) { throw new IndexOutOfBoundsException("Source does not fit in dest"); } T[] destArray = (T[]) dest.toArray(); System.arraycopy(src.toArray(), 0, destArray, 0, srcSize); dest.setAll(destArray); } /** * Fills the provided list with obj. Fires only one change notification on the list. * @param list the list to fill * @param obj the object to fill the list with * @see Collections#fill(java.util.List, java.lang.Object) */ @SuppressWarnings("unchecked") public static void fill(ObservableList list, T obj) { T[] newContent = (T[]) new Object[list.size()]; Arrays.fill(newContent, obj); list.setAll(newContent); } /** * Replace all oldVal elements in the list with newVal element. * Fires only one change notification on the list. * @param list the list which will have it's elements replaced * @param oldVal the element that is going to be replace * @param newVal the replacement * @return true if the list was modified * @see Collections#replaceAll(java.util.List, java.lang.Object, java.lang.Object) */ @SuppressWarnings("unchecked") public static boolean replaceAll(ObservableList list, T oldVal, T newVal) { T[] newContent = (T[]) list.toArray(); boolean modified = false; for (int i = 0 ; i < newContent.length; ++i) { if (newContent[i].equals(oldVal)) { newContent[i] = newVal; modified = true; } } if (modified) { list.setAll(newContent); } return modified; } /** * Reverse the order in the list * Fires only one change notification on the list. * @param list the list to be reversed * @see Collections#reverse(java.util.List) */ @SuppressWarnings("unchecked") public static void reverse(ObservableList list) { Object[] newContent = list.toArray(); for (int i = 0; i < newContent.length / 2; ++i) { Object tmp = newContent[i]; newContent[i] = newContent[newContent.length - i - 1]; newContent[newContent.length -i - 1] = tmp; } list.setAll(newContent); } /** * Rotates the list by distance. * Fires only one change notification on the list. * @param list the list to be rotated * @param distance the distance of rotation * @see Collections#rotate(java.util.List, int) */ @SuppressWarnings("unchecked") public static void rotate(ObservableList list, int distance) { Object[] newContent = list.toArray(); int size = list.size(); distance = distance % size; if (distance < 0) distance += size; if (distance == 0) return; for (int cycleStart = 0, nMoved = 0; nMoved != size; cycleStart++) { Object displaced = newContent[cycleStart]; Object tmp; int i = cycleStart; do { i += distance; if (i >= size) i -= size; tmp = newContent[i]; newContent[i] = displaced; displaced = tmp; nMoved ++; } while(i != cycleStart); } list.setAll(newContent); } /** * Shuffles all elements in the observable list. * Fires only one change notification on the list. * @param list the list to shuffle * @see Collections#shuffle(java.util.List) */ public static void shuffle(ObservableList list) { if (r == null) { r = new Random(); } shuffle(list, r); } private static Random r; /** * Shuffles all elements in the observable list. * Fires only one change notification on the list. * @param list the list to be shuffled * @param rnd the random generator used for shuffling * @see Collections#shuffle(java.util.List, java.util.Random) */ @SuppressWarnings("unchecked") public static void shuffle(ObservableList list, Random rnd) { Object newContent[] = list.toArray(); for (int i = list.size(); i > 1; i--) { swap(newContent, i - 1, rnd.nextInt(i)); } list.setAll(newContent); } private static void swap(Object[] arr, int i, int j) { Object tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } /** * Sorts the provided observable list. * Fires only one change notification on the list. * @see Collections#sort(java.util.List) */ @SuppressWarnings("unchecked") public static > void sort(ObservableList list) { if (list instanceof SortableList) { ((SortableList)list).sort(); } else { List newContent = new ArrayList(list); Collections.sort(newContent); list.setAll((Collection)newContent); } } /** * Sorts the provided observable list using the c comparator. * Fires only one change notification on the list. * @param list the list to sort * @param c comparator used for sorting. Null if natural ordering is required. * @see Collections#sort(java.util.List, java.util.Comparator) */ @SuppressWarnings("unchecked") public static void sort(ObservableList list, Comparator c) { if (list instanceof SortableList) { ((SortableList)list).sort(c); } else { List newContent = new ArrayList(list); Collections.sort(newContent, c); list.setAll((Collection)newContent); } } private static class EmptyObservableList extends AbstractList implements ObservableList { private static final ListIterator iterator = new ListIterator() { @Override public boolean hasNext() { return false; } @Override public Object next() { throw new NoSuchElementException(); } @Override public void remove() { throw new UnsupportedOperationException(); } @Override public boolean hasPrevious() { return false; } @Override public Object previous() { throw new NoSuchElementException(); } @Override public int nextIndex() { return 0; } @Override public int previousIndex() { return -1; } @Override public void set(Object e) { throw new UnsupportedOperationException(); } @Override public void add(Object e) { throw new UnsupportedOperationException(); } }; public EmptyObservableList() { } @Override public final void addListener(InvalidationListener listener) { } @Override public final void removeListener(InvalidationListener listener) { } @Override public void addListener(ListChangeListener o) { } @Override public void removeListener(ListChangeListener o) { } @Override public int size() { return 0; } @Override public boolean contains(Object o) { return false; } @Override @SuppressWarnings("unchecked") public Iterator iterator() { return iterator; } @Override public boolean containsAll(Collection c) { return c.isEmpty(); } @Override public E get(int index) { throw new IndexOutOfBoundsException(); } @Override public int indexOf(Object o) { return -1; } @Override public int lastIndexOf(Object o) { return -1; } @Override @SuppressWarnings("unchecked") public ListIterator listIterator() { return iterator; } @Override @SuppressWarnings("unchecked") public ListIterator listIterator(int index) { if (index != 0) { throw new IndexOutOfBoundsException(); } return iterator; } @Override public List subList(int fromIndex, int toIndex) { if (fromIndex != 0 || toIndex != 0) { throw new IndexOutOfBoundsException(); } return this; } @Override public boolean addAll(E... elements) { throw new UnsupportedOperationException(); } @Override public boolean setAll(E... elements) { throw new UnsupportedOperationException(); } @Override public boolean setAll(Collection col) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(E... elements) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(E... elements) { throw new UnsupportedOperationException(); } @Override public void remove(int from, int to) { throw new UnsupportedOperationException(); } } private static class SingletonObservableList extends AbstractList implements ObservableList { private final E element; public SingletonObservableList(E element) { if (element == null) { throw new NullPointerException(); } this.element = element; } @Override public boolean addAll(E... elements) { throw new UnsupportedOperationException(); } @Override public boolean setAll(E... elements) { throw new UnsupportedOperationException(); } @Override public boolean setAll(Collection col) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(E... elements) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(E... elements) { throw new UnsupportedOperationException(); } @Override public void remove(int from, int to) { throw new UnsupportedOperationException(); } @Override public void addListener(InvalidationListener listener) { } @Override public void removeListener(InvalidationListener listener) { } @Override public void addListener(ListChangeListener o) { } @Override public void removeListener(ListChangeListener o) { } @Override public int size() { return 1; } @Override public boolean isEmpty() { return false; } @Override public boolean contains(Object o) { return element.equals(o); } @Override public E get(int index) { if (index != 0) { throw new IndexOutOfBoundsException(); } return element; } } private static class UnmodifiableObservableListImpl extends ObservableListBase implements ObservableList { private final ObservableList backingList; private final ListChangeListener listener; public UnmodifiableObservableListImpl(ObservableList backingList) { this.backingList = backingList; listener = c -> { fireChange(new SourceAdapterChange(UnmodifiableObservableListImpl.this, c)); }; this.backingList.addListener(new WeakListChangeListener(listener)); } @Override public T get(int index) { return backingList.get(index); } @Override public int size() { return backingList.size(); } @Override public boolean addAll(T... elements) { throw new UnsupportedOperationException(); } @Override public boolean setAll(T... elements) { throw new UnsupportedOperationException(); } @Override public boolean setAll(Collection col) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(T... elements) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(T... elements) { throw new UnsupportedOperationException(); } @Override public void remove(int from, int to) { throw new UnsupportedOperationException(); } } private static class SynchronizedList implements List { final Object mutex; private final List backingList; SynchronizedList(List list, Object mutex) { this.backingList = list; this.mutex = mutex; } @Override public int size() { synchronized(mutex) { return backingList.size(); } } @Override public boolean isEmpty() { synchronized(mutex) { return backingList.isEmpty(); } } @Override public boolean contains(Object o) { synchronized(mutex) { return backingList.contains(o); } } @Override public Iterator iterator() { return backingList.iterator(); } @Override public Object[] toArray() { synchronized(mutex) { return backingList.toArray(); } } @Override public T[] toArray(T[] a) { synchronized(mutex) { return backingList.toArray(a); } } @Override public boolean add(T e) { synchronized(mutex) { return backingList.add(e); } } @Override public boolean remove(Object o) { synchronized(mutex) { return backingList.remove(o); } } @Override public boolean containsAll(Collection c) { synchronized(mutex) { return backingList.containsAll(c); } } @Override public boolean addAll(Collection c) { synchronized(mutex) { return backingList.addAll(c); } } @Override public boolean addAll(int index, Collection c) { synchronized(mutex) { return backingList.addAll(index, c); } } @Override public boolean removeAll(Collection c) { synchronized(mutex) { return backingList.removeAll(c); } } @Override public boolean retainAll(Collection c) { synchronized(mutex) { return backingList.retainAll(c); } } @Override public void clear() { synchronized(mutex) { backingList.clear(); } } @Override public T get(int index) { synchronized(mutex) { return backingList.get(index); } } @Override public T set(int index, T element) { synchronized(mutex) { return backingList.set(index, element); } } @Override public void add(int index, T element) { synchronized(mutex) { backingList.add(index, element); } } @Override public T remove(int index) { synchronized(mutex) { return backingList.remove(index); } } @Override public int indexOf(Object o) { synchronized(mutex) { return backingList.indexOf(o); } } @Override public int lastIndexOf(Object o) { synchronized(mutex) { return backingList.lastIndexOf(o); } } @Override public ListIterator listIterator() { return backingList.listIterator(); } @Override public ListIterator listIterator(int index) { synchronized(mutex) { return backingList.listIterator(index); } } @Override public List subList(int fromIndex, int toIndex) { synchronized(mutex) { return new SynchronizedList(backingList.subList(fromIndex, toIndex), mutex); } } @Override public String toString() { synchronized(mutex) { return backingList.toString(); } } @Override public int hashCode() { synchronized(mutex) { return backingList.hashCode(); } } @Override public boolean equals(Object o) { synchronized(mutex) { return backingList.equals(o); } } } private static class SynchronizedObservableList extends SynchronizedList implements ObservableList { private ListListenerHelper helper; private final ObservableList backingList; private final ListChangeListener listener; SynchronizedObservableList(ObservableList seq, Object mutex) { super(seq, mutex); this.backingList = seq; listener = c -> { ListListenerHelper.fireValueChangedEvent(helper, new SourceAdapterChange(SynchronizedObservableList.this, c)); }; backingList.addListener(new WeakListChangeListener(listener)); } SynchronizedObservableList(ObservableList seq) { this(seq, new Object()); } @Override public boolean addAll(T... elements) { synchronized(mutex) { return backingList.addAll(elements); } } @Override public boolean setAll(T... elements) { synchronized(mutex) { return backingList.setAll(elements); } } @Override public boolean removeAll(T... elements) { synchronized(mutex) { return backingList.removeAll(elements); } } @Override public boolean retainAll(T... elements) { synchronized(mutex) { return backingList.retainAll(elements); } } @Override public void remove(int from, int to) { synchronized(mutex) { backingList.remove(from, to); } } @Override public boolean setAll(Collection col) { synchronized(mutex) { return backingList.setAll(col); } } @Override public final void addListener(InvalidationListener listener) { synchronized (mutex) { helper = ListListenerHelper.addListener(helper, listener); } } @Override public final void removeListener(InvalidationListener listener) { synchronized (mutex) { helper = ListListenerHelper.removeListener(helper, listener); } } @Override public void addListener(ListChangeListener listener) { synchronized (mutex) { helper = ListListenerHelper.addListener(helper, listener); } } @Override public void removeListener(ListChangeListener listener) { synchronized (mutex) { helper = ListListenerHelper.removeListener(helper, listener); } } } private static class CheckedObservableList extends ObservableListBase implements ObservableList { private final ObservableList list; private final Class type; private final ListChangeListener listener; CheckedObservableList(ObservableList list, Class type) { if (list == null || type == null) { throw new NullPointerException(); } this.list = list; this.type = type; listener = c -> { fireChange(new SourceAdapterChange(CheckedObservableList.this, c)); }; list.addListener(new WeakListChangeListener(listener)); } void typeCheck(Object o) { if (o != null && !type.isInstance(o)) { throw new ClassCastException("Attempt to insert " + o.getClass() + " element into collection with element type " + type); } } @Override public int size() { return list.size(); } @Override public boolean isEmpty() { return list.isEmpty(); } @Override public boolean contains(Object o) { return list.contains(o); } @Override public Object[] toArray() { return list.toArray(); } @Override public T[] toArray(T[] a) { return list.toArray(a); } @Override public String toString() { return list.toString(); } @Override public boolean remove(Object o) { return list.remove(o); } @Override public boolean containsAll(Collection coll) { return list.containsAll(coll); } @Override public boolean removeAll(Collection coll) { return list.removeAll(coll); } @Override public boolean retainAll(Collection coll) { return list.retainAll(coll); } @Override public boolean removeAll(T... elements) { return list.removeAll(elements); } @Override public boolean retainAll(T... elements) { return list.retainAll(elements); } @Override public void remove(int from, int to) { list.remove(from, to); } @Override public void clear() { list.clear(); } @Override public boolean equals(Object o) { return o == this || list.equals(o); } @Override public int hashCode() { return list.hashCode(); } @Override public T get(int index) { return list.get(index); } @Override public T remove(int index) { return list.remove(index); } @Override public int indexOf(Object o) { return list.indexOf(o); } @Override public int lastIndexOf(Object o) { return list.lastIndexOf(o); } @Override public T set(int index, T element) { typeCheck(element); return list.set(index, element); } @Override public void add(int index, T element) { typeCheck(element); list.add(index, element); } @Override @SuppressWarnings("unchecked") public boolean addAll(int index, Collection c) { T[] a = null; try { a = c.toArray((T[]) Array.newInstance(type, 0)); } catch (ArrayStoreException e) { throw new ClassCastException(); } return this.list.addAll(index, Arrays.asList(a)); } @Override @SuppressWarnings("unchecked") public boolean addAll(Collection coll) { T[] a = null; try { a = coll.toArray((T[]) Array.newInstance(type, 0)); } catch (ArrayStoreException e) { throw new ClassCastException(); } return this.list.addAll(Arrays.asList(a)); } @Override public ListIterator listIterator() { return listIterator(0); } @Override public ListIterator listIterator(final int index) { return new ListIterator() { ListIterator i = list.listIterator(index); @Override public boolean hasNext() { return i.hasNext(); } @Override public T next() { return i.next(); } @Override public boolean hasPrevious() { return i.hasPrevious(); } @Override public T previous() { return i.previous(); } @Override public int nextIndex() { return i.nextIndex(); } @Override public int previousIndex() { return i.previousIndex(); } @Override public void remove() { i.remove(); } @Override public void set(T e) { typeCheck(e); i.set(e); } @Override public void add(T e) { typeCheck(e); i.add(e); } }; } @Override public Iterator iterator() { return new Iterator() { private final Iterator it = list.iterator(); @Override public boolean hasNext() { return it.hasNext(); } @Override public T next() { return it.next(); } @Override public void remove() { it.remove(); } }; } @Override public boolean add(T e) { typeCheck(e); return list.add(e); } @Override public List subList(int fromIndex, int toIndex) { return Collections.checkedList(list.subList(fromIndex, toIndex), type); } @Override @SuppressWarnings("unchecked") public boolean addAll(T... elements) { try { T[] array = (T[]) Array.newInstance(type, elements.length); System.arraycopy(elements, 0, array, 0, elements.length); return list.addAll(array); } catch (ArrayStoreException e) { throw new ClassCastException(); } } @Override @SuppressWarnings("unchecked") public boolean setAll(T... elements) { try { T[] array = (T[]) Array.newInstance(type, elements.length); System.arraycopy(elements, 0, array, 0, elements.length); return list.setAll(array); } catch (ArrayStoreException e) { throw new ClassCastException(); } } @Override @SuppressWarnings("unchecked") public boolean setAll(Collection col) { T[] a = null; try { a = col.toArray((T[]) Array.newInstance(type, 0)); } catch (ArrayStoreException e) { throw new ClassCastException(); } return list.setAll(Arrays.asList(a)); } } private static class EmptyObservableSet extends AbstractSet implements ObservableSet { public EmptyObservableSet() { } @Override public void addListener(InvalidationListener listener) { } @Override public void removeListener(InvalidationListener listener) { } @Override public void addListener(SetChangeListener listener) { } @Override public void removeListener(SetChangeListener listener) { } @Override public int size() { return 0; } @Override public boolean isEmpty() { return true; } @Override public boolean contains(Object obj) { return false; } @Override public boolean containsAll(Collection c) { return c.isEmpty(); } @Override public Object[] toArray() { return new Object[0]; } @Override public E[] toArray(E[] a) { if (a.length > 0) a[0] = null; return a; } @Override public Iterator iterator() { return new Iterator() { @Override public boolean hasNext() { return false; } @Override public Object next() { throw new NoSuchElementException(); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } } private static class UnmodifiableObservableSet extends AbstractSet implements ObservableSet { private final ObservableSet backingSet; private SetListenerHelper listenerHelper; private SetChangeListener listener; public UnmodifiableObservableSet(ObservableSet backingSet) { this.backingSet = backingSet; this.listener = null; } private void initListener() { if (listener == null) { listener = c -> { callObservers(new SetAdapterChange(UnmodifiableObservableSet.this, c)); }; this.backingSet.addListener(new WeakSetChangeListener(listener)); } } private void callObservers(SetChangeListener.Change change) { SetListenerHelper.fireValueChangedEvent(listenerHelper, change); } @Override public Iterator iterator() { return new Iterator() { private final Iterator i = backingSet.iterator(); @Override public boolean hasNext() { return i.hasNext(); } @Override public E next() { return i.next(); } }; } @Override public int size() { return backingSet.size(); } @Override public boolean isEmpty() { return backingSet.isEmpty(); } @Override public boolean contains(Object o) { return backingSet.contains(o); } @Override public void addListener(InvalidationListener listener) { initListener(); listenerHelper = SetListenerHelper.addListener(listenerHelper, listener); } @Override public void removeListener(InvalidationListener listener) { listenerHelper = SetListenerHelper.removeListener(listenerHelper, listener); } @Override public void addListener(SetChangeListener listener) { initListener(); listenerHelper = SetListenerHelper.addListener(listenerHelper, listener); } @Override public void removeListener(SetChangeListener listener) { listenerHelper = SetListenerHelper.removeListener(listenerHelper, listener); } @Override public boolean add(E e) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } } private static class SynchronizedSet implements Set { final Object mutex; private final Set backingSet; SynchronizedSet(Set set, Object mutex) { this.backingSet = set; this.mutex = mutex; } SynchronizedSet(Set set) { this(set, new Object()); } @Override public int size() { synchronized(mutex) { return backingSet.size(); } } @Override public boolean isEmpty() { synchronized(mutex) { return backingSet.isEmpty(); } } @Override public boolean contains(Object o) { synchronized(mutex) { return backingSet.contains(o); } } @Override public Iterator iterator() { return backingSet.iterator(); } @Override public Object[] toArray() { synchronized(mutex) { return backingSet.toArray(); } } @Override public E[] toArray(E[] a) { synchronized(mutex) { return backingSet.toArray(a); } } @Override public boolean add(E e) { synchronized(mutex) { return backingSet.add(e); } } @Override public boolean remove(Object o) { synchronized(mutex) { return backingSet.remove(o); } } @Override public boolean containsAll(Collection c) { synchronized(mutex) { return backingSet.containsAll(c); } } @Override public boolean addAll(Collection c) { synchronized(mutex) { return backingSet.addAll(c); } } @Override public boolean retainAll(Collection c) { synchronized(mutex) { return backingSet.retainAll(c); } } @Override public boolean removeAll(Collection c) { synchronized(mutex) { return backingSet.removeAll(c); } } @Override public void clear() { synchronized(mutex) { backingSet.clear(); } } @Override public boolean equals(Object o) { if (o == this) { return true; } synchronized(mutex) { return backingSet.equals(o); } } @Override public int hashCode() { synchronized (mutex) { return backingSet.hashCode(); } } } private static class SynchronizedObservableSet extends SynchronizedSet implements ObservableSet { private final ObservableSet backingSet; private SetListenerHelper listenerHelper; private final SetChangeListener listener; SynchronizedObservableSet(ObservableSet set, Object mutex) { super(set, mutex); backingSet = set; listener = c -> { SetListenerHelper.fireValueChangedEvent(listenerHelper, new SetAdapterChange(SynchronizedObservableSet.this, c)); }; backingSet.addListener(new WeakSetChangeListener(listener)); } SynchronizedObservableSet(ObservableSet set) { this(set, new Object()); } @Override public void addListener(InvalidationListener listener) { synchronized (mutex) { listenerHelper = SetListenerHelper.addListener(listenerHelper, listener); } } @Override public void removeListener(InvalidationListener listener) { synchronized (mutex) { listenerHelper = SetListenerHelper.removeListener(listenerHelper, listener); } } @Override public void addListener(SetChangeListener listener) { synchronized (mutex) { listenerHelper = SetListenerHelper.addListener(listenerHelper, listener); } } @Override public void removeListener(SetChangeListener listener) { synchronized (mutex) { listenerHelper = SetListenerHelper.removeListener(listenerHelper, listener); } } } private static class CheckedObservableSet extends AbstractSet implements ObservableSet { private final ObservableSet backingSet; private final Class type; private SetListenerHelper listenerHelper; private final SetChangeListener listener; CheckedObservableSet(ObservableSet set, Class type) { if (set == null || type == null) { throw new NullPointerException(); } backingSet = set; this.type = type; listener = c -> { callObservers(new SetAdapterChange(CheckedObservableSet.this, c)); }; backingSet.addListener(new WeakSetChangeListener(listener)); } private void callObservers(SetChangeListener.Change c) { SetListenerHelper.fireValueChangedEvent(listenerHelper, c); } void typeCheck(Object o) { if (o != null && !type.isInstance(o)) { throw new ClassCastException("Attempt to insert " + o.getClass() + " element into collection with element type " + type); } } @Override public void addListener(InvalidationListener listener) { listenerHelper = SetListenerHelper.addListener(listenerHelper, listener); } @Override public void removeListener(InvalidationListener listener) { listenerHelper = SetListenerHelper.removeListener(listenerHelper, listener); } @Override public void addListener(SetChangeListener listener) { listenerHelper = SetListenerHelper.addListener(listenerHelper, listener); } @Override public void removeListener(SetChangeListener listener) { listenerHelper = SetListenerHelper.removeListener(listenerHelper, listener); } @Override public int size() { return backingSet.size(); } @Override public boolean isEmpty() { return backingSet.isEmpty(); } @Override public boolean contains(Object o) { return backingSet.contains(o); } @Override public Object[] toArray() { return backingSet.toArray(); } @Override public T[] toArray(T[] a) { return backingSet.toArray(a); } @Override public boolean add(E e) { typeCheck(e); return backingSet.add(e); } @Override public boolean remove(Object o) { return backingSet.remove(o); } @Override public boolean containsAll(Collection c) { return backingSet.containsAll(c); } @Override @SuppressWarnings("unchecked") public boolean addAll(Collection c) { E[] a = null; try { a = c.toArray((E[]) Array.newInstance(type, 0)); } catch (ArrayStoreException e) { throw new ClassCastException(); } return backingSet.addAll(Arrays.asList(a)); } @Override public boolean retainAll(Collection c) { return backingSet.retainAll(c); } @Override public boolean removeAll(Collection c) { return backingSet.removeAll(c); } @Override public void clear() { backingSet.clear(); } @Override public boolean equals(Object o) { return o == this || backingSet.equals(o); } @Override public int hashCode() { return backingSet.hashCode(); } @Override public Iterator iterator() { final Iterator it = backingSet.iterator(); return new Iterator() { @Override public boolean hasNext() { return it.hasNext(); } @Override public E next() { return it.next(); } @Override public void remove() { it.remove(); } }; } } private static class EmptyObservableMap extends AbstractMap implements ObservableMap { public EmptyObservableMap() { } @Override public void addListener(InvalidationListener listener) { } @Override public void removeListener(InvalidationListener listener) { } @Override public void addListener(MapChangeListener listener) { } @Override public void removeListener(MapChangeListener listener) { } @Override public int size() { return 0; } @Override public boolean isEmpty() { return true; } @Override public boolean containsKey(Object key) { return false; } @Override public boolean containsValue(Object value) { return false; } @Override public V get(Object key) { return null; } @Override public Set keySet() { return emptyObservableSet(); } @Override public Collection values() { return emptyObservableSet(); } @Override public Set> entrySet() { return emptyObservableSet(); } @Override public boolean equals(Object o) { return (o instanceof Map) && ((Map)o).isEmpty(); } @Override public int hashCode() { return 0; } } private static class CheckedObservableMap extends AbstractMap implements ObservableMap { private final ObservableMap backingMap; private final Class keyType; private final Class valueType; private MapListenerHelper listenerHelper; private final MapChangeListener listener; CheckedObservableMap(ObservableMap map, Class keyType, Class valueType) { backingMap = map; this.keyType = keyType; this.valueType = valueType; listener = c -> { callObservers(new MapAdapterChange(CheckedObservableMap.this, c)); }; backingMap.addListener(new WeakMapChangeListener(listener)); } private void callObservers(MapChangeListener.Change c) { MapListenerHelper.fireValueChangedEvent(listenerHelper, c); } void typeCheck(Object key, Object value) { if (key != null && !keyType.isInstance(key)) { throw new ClassCastException("Attempt to insert " + key.getClass() + " key into map with key type " + keyType); } if (value != null && !valueType.isInstance(value)) { throw new ClassCastException("Attempt to insert " + value.getClass() + " value into map with value type " + valueType); } } @Override public void addListener(InvalidationListener listener) { listenerHelper = MapListenerHelper.addListener(listenerHelper, listener); } @Override public void removeListener(InvalidationListener listener) { listenerHelper = MapListenerHelper.removeListener(listenerHelper, listener); } @Override public void addListener(MapChangeListener listener) { listenerHelper = MapListenerHelper.addListener(listenerHelper, listener); } @Override public void removeListener(MapChangeListener listener) { listenerHelper = MapListenerHelper.removeListener(listenerHelper, listener); } @Override public int size() { return backingMap.size(); } @Override public boolean isEmpty() { return backingMap.isEmpty(); } @Override public boolean containsKey(Object key) { return backingMap.containsKey(key); } @Override public boolean containsValue(Object value) { return backingMap.containsValue(value); } @Override public V get(Object key) { return backingMap.get(key); } @Override public V put(K key, V value) { typeCheck(key, value); return backingMap.put(key, value); } @Override public V remove(Object key) { return backingMap.remove(key); } @Override @SuppressWarnings("unchecked") public void putAll(Map t) { // Satisfy the following goals: // - good diagnostics in case of type mismatch // - all-or-nothing semantics // - protection from malicious t // - correct behavior if t is a concurrent map Object[] entries = t.entrySet().toArray(); List> checked = new ArrayList>(entries.length); for (Object o : entries) { Map.Entry e = (Map.Entry) o; Object k = e.getKey(); Object v = e.getValue(); typeCheck(k, v); checked.add( new AbstractMap.SimpleImmutableEntry((K) k, (V) v)); } for (Map.Entry e : checked) backingMap.put(e.getKey(), e.getValue()); } @Override public void clear() { backingMap.clear(); } @Override public Set keySet() { return backingMap.keySet(); } @Override public Collection values() { return backingMap.values(); } private transient Set> entrySet = null; @Override public Set entrySet() { if (entrySet==null) entrySet = new CheckedEntrySet(backingMap.entrySet(), valueType); return entrySet; } @Override public boolean equals(Object o) { return o == this || backingMap.equals(o); } @Override public int hashCode() { return backingMap.hashCode(); } static class CheckedEntrySet implements Set> { private final Set> s; private final Class valueType; CheckedEntrySet(Set> s, Class valueType) { this.s = s; this.valueType = valueType; } @Override public int size() { return s.size(); } @Override public boolean isEmpty() { return s.isEmpty(); } @Override public String toString() { return s.toString(); } @Override public int hashCode() { return s.hashCode(); } @Override public void clear() { s.clear(); } @Override public boolean add(Map.Entry e) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection> coll) { throw new UnsupportedOperationException(); } @Override public Iterator> iterator() { final Iterator> i = s.iterator(); final Class valueType = this.valueType; return new Iterator>() { @Override public boolean hasNext() { return i.hasNext(); } @Override public void remove() { i.remove(); } @Override public Map.Entry next() { return checkedEntry(i.next(), valueType); } }; } @Override @SuppressWarnings("unchecked") public Object[] toArray() { Object[] source = s.toArray(); /* * Ensure that we don't get an ArrayStoreException even if * s.toArray returns an array of something other than Object */ Object[] dest = (CheckedEntry.class.isInstance( source.getClass().getComponentType()) ? source : new Object[source.length]); for (int i = 0; i < source.length; i++) dest[i] = checkedEntry((Map.Entry)source[i], valueType); return dest; } @Override @SuppressWarnings("unchecked") public T[] toArray(T[] a) { // We don't pass a to s.toArray, to avoid window of // vulnerability wherein an unscrupulous multithreaded client // could get his hands on raw (unwrapped) Entries from s. T[] arr = s.toArray(a.length==0 ? a : Arrays.copyOf(a, 0)); for (int i=0; i)arr[i], valueType); if (arr.length > a.length) return arr; System.arraycopy(arr, 0, a, 0, arr.length); if (a.length > arr.length) a[arr.length] = null; return a; } /** * This method is overridden to protect the backing set against * an object with a nefarious equals function that senses * that the equality-candidate is Map.Entry and calls its * setValue method. */ @Override public boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry) o; return s.contains( (e instanceof CheckedEntry) ? e : checkedEntry(e, valueType)); } /** * The bulk collection methods are overridden to protect * against an unscrupulous collection whose contains(Object o) * method senses when o is a Map.Entry, and calls o.setValue. */ @Override public boolean containsAll(Collection c) { for (Object o : c) if (!contains(o)) // Invokes safe contains() above return false; return true; } @Override public boolean remove(Object o) { if (!(o instanceof Map.Entry)) return false; return s.remove(new AbstractMap.SimpleImmutableEntry ((Map.Entry)o)); } @Override public boolean removeAll(Collection c) { return batchRemove(c, false); } @Override public boolean retainAll(Collection c) { return batchRemove(c, true); } private boolean batchRemove(Collection c, boolean complement) { boolean modified = false; Iterator> it = iterator(); while (it.hasNext()) { if (c.contains(it.next()) != complement) { it.remove(); modified = true; } } return modified; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Set)) return false; Set that = (Set) o; return that.size() == s.size() && containsAll(that); // Invokes safe containsAll() above } static CheckedEntry checkedEntry(Map.Entry e, Class valueType) { return new CheckedEntry(e, valueType); } /** * This "wrapper class" serves two purposes: it prevents * the client from modifying the backing Map, by short-circuiting * the setValue method, and it protects the backing Map against * an ill-behaved Map.Entry that attempts to modify another * Map.Entry when asked to perform an equality check. */ private static class CheckedEntry implements Map.Entry { private final Map.Entry e; private final Class valueType; CheckedEntry(Map.Entry e, Class valueType) { this.e = e; this.valueType = valueType; } @Override public K getKey() { return e.getKey(); } @Override public V getValue() { return e.getValue(); } @Override public int hashCode() { return e.hashCode(); } @Override public String toString() { return e.toString(); } @Override public V setValue(V value) { if (value != null && !valueType.isInstance(value)) throw new ClassCastException(badValueMsg(value)); return e.setValue(value); } private String badValueMsg(Object value) { return "Attempt to insert " + value.getClass() + " value into map with value type " + valueType; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof Map.Entry)) return false; return e.equals(new AbstractMap.SimpleImmutableEntry ((Map.Entry)o)); } } } } private static class SynchronizedMap implements Map { final Object mutex; private final Map backingMap; SynchronizedMap(Map map, Object mutex) { backingMap = map; this.mutex = mutex; } SynchronizedMap(Map map) { this(map, new Object()); } @Override public int size() { synchronized (mutex) { return backingMap.size(); } } @Override public boolean isEmpty() { synchronized (mutex) { return backingMap.isEmpty(); } } @Override public boolean containsKey(Object key) { synchronized (mutex) { return backingMap.containsKey(key); } } @Override public boolean containsValue(Object value) { synchronized (mutex) { return backingMap.containsValue(value); } } @Override public V get(Object key) { synchronized (mutex) { return backingMap.get(key); } } @Override public V put(K key, V value) { synchronized (mutex) { return backingMap.put(key, value); } } @Override public V remove(Object key) { synchronized (mutex) { return backingMap.remove(key); } } @Override public void putAll(Map m) { synchronized (mutex) { backingMap.putAll(m); } } @Override public void clear() { synchronized (mutex) { backingMap.clear(); } } private transient Set keySet = null; private transient Set> entrySet = null; private transient Collection values = null; @Override public Set keySet() { synchronized(mutex) { if (keySet==null) keySet = new SynchronizedSet(backingMap.keySet(), mutex); return keySet; } } @Override public Collection values() { synchronized(mutex) { if (values==null) values = new SynchronizedCollection(backingMap.values(), mutex); return values; } } @Override public Set> entrySet() { synchronized(mutex) { if (entrySet==null) entrySet = new SynchronizedSet>(backingMap.entrySet(), mutex); return entrySet; } } @Override public boolean equals(Object o) { if (o == this) { return true; } synchronized(mutex) { return backingMap.equals(o); } } @Override public int hashCode() { synchronized(mutex) { return backingMap.hashCode(); } } } private static class SynchronizedCollection implements Collection { private final Collection backingCollection; final Object mutex; SynchronizedCollection(Collection c, Object mutex) { backingCollection = c; this.mutex = mutex; } SynchronizedCollection(Collection c) { this(c, new Object()); } @Override public int size() { synchronized (mutex) { return backingCollection.size(); } } @Override public boolean isEmpty() { synchronized (mutex) { return backingCollection.isEmpty(); } } @Override public boolean contains(Object o) { synchronized (mutex) { return backingCollection.contains(o); } } @Override public Iterator iterator() { return backingCollection.iterator(); } @Override public Object[] toArray() { synchronized (mutex) { return backingCollection.toArray(); } } @Override public T[] toArray(T[] a) { synchronized (mutex) { return backingCollection.toArray(a); } } @Override public boolean add(E e) { synchronized (mutex) { return backingCollection.add(e); } } @Override public boolean remove(Object o) { synchronized (mutex) { return backingCollection.remove(o); } } @Override public boolean containsAll(Collection c) { synchronized (mutex) { return backingCollection.containsAll(c); } } @Override public boolean addAll(Collection c) { synchronized (mutex) { return backingCollection.addAll(c); } } @Override public boolean removeAll(Collection c) { synchronized (mutex) { return backingCollection.removeAll(c); } } @Override public boolean retainAll(Collection c) { synchronized (mutex) { return backingCollection.retainAll(c); } } @Override public void clear() { synchronized (mutex) { backingCollection.clear(); } } } private static class SynchronizedObservableMap extends SynchronizedMap implements ObservableMap { private final ObservableMap backingMap; private MapListenerHelper listenerHelper; private final MapChangeListener listener; SynchronizedObservableMap(ObservableMap map, Object mutex) { super(map, mutex); backingMap = map; listener = c -> { MapListenerHelper.fireValueChangedEvent(listenerHelper, new MapAdapterChange(SynchronizedObservableMap.this, c)); }; backingMap.addListener(new WeakMapChangeListener(listener)); } SynchronizedObservableMap(ObservableMap map) { this(map, new Object()); } @Override public void addListener(InvalidationListener listener) { synchronized (mutex) { listenerHelper = MapListenerHelper.addListener(listenerHelper, listener); } } @Override public void removeListener(InvalidationListener listener) { synchronized (mutex) { listenerHelper = MapListenerHelper.removeListener(listenerHelper, listener); } } @Override public void addListener(MapChangeListener listener) { synchronized (mutex) { listenerHelper = MapListenerHelper.addListener(listenerHelper, listener); } } @Override public void removeListener(MapChangeListener listener) { synchronized (mutex) { listenerHelper = MapListenerHelper.removeListener(listenerHelper, listener); } } } }