/******************************************************************************* * Copyright (c) 2008, 2015 Matthew Hall 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: * Matthew Hall - initial API and implementation (bug 237718) * Matthew Hall - but 246626, 226289 * Stefan Xenos <sxenos@gmail.com> - Bug 335792 * Stefan Xenos <sxenos@gmail.com> - Bug 474065 ******************************************************************************/ package org.eclipse.core.databinding.observable.map; import java.util.Collection; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.eclipse.core.databinding.observable.DecoratingObservable; import org.eclipse.core.databinding.observable.Diffs; /** * An observable map which decorates another observable map. * * @param <K> * type of the keys to the map * @param <V> * type of the values in the map * * @since 1.2 */ public class DecoratingObservableMap<K, V> extends DecoratingObservable implements IObservableMap<K, V> { private IObservableMap<K, V> decorated; private IMapChangeListener<K, V> mapChangeListener; /** * Constructs a DecoratingObservableMap which decorates the given * observable. * * @param decorated * the observable map being decorated * @param disposeDecoratedOnDispose */ public DecoratingObservableMap(IObservableMap<K, V> decorated, boolean disposeDecoratedOnDispose) { super(decorated, disposeDecoratedOnDispose); this.decorated = decorated; } @Override public synchronized void addMapChangeListener(IMapChangeListener<? super K, ? super V> listener) { addListener(MapChangeEvent.TYPE, listener); } @Override public synchronized void removeMapChangeListener(IMapChangeListener<? super K, ? super V> listener) { removeListener(MapChangeEvent.TYPE, listener); } @Override public Object getKeyType() { return decorated.getKeyType(); } @Override public Object getValueType() { return decorated.getValueType(); } protected void fireMapChange(MapDiff<K, V> diff) { // fire general change event first super.fireChange(); fireEvent(new MapChangeEvent<>(this, diff)); } @Override protected void fireChange() { throw new RuntimeException( "fireChange should not be called, use fireListChange() instead"); //$NON-NLS-1$ } @Override protected void firstListenerAdded() { if (mapChangeListener == null) { mapChangeListener = new IMapChangeListener<K, V>() { @Override public void handleMapChange(MapChangeEvent<? extends K, ? extends V> event) { DecoratingObservableMap.this.handleMapChange(event); } }; } decorated.addMapChangeListener(mapChangeListener); super.firstListenerAdded(); } @Override protected void lastListenerRemoved() { super.lastListenerRemoved(); if (mapChangeListener != null) { decorated.removeMapChangeListener(mapChangeListener); mapChangeListener = null; } } /** * Called whenever a MapChangeEvent is received from the decorated * observable. By default, this method fires the map change event again, * with the decorating observable as the event source. Subclasses may * override to provide different behavior. * * @param event * the change event received from the decorated observable */ protected void handleMapChange(final MapChangeEvent<? extends K, ? extends V> event) { fireMapChange(Diffs.unmodifiableDiff(event.diff)); } @Override public void clear() { checkRealm(); decorated.clear(); } @Override public boolean containsKey(Object key) { getterCalled(); return decorated.containsKey(key); } @Override public boolean containsValue(Object value) { getterCalled(); return decorated.containsValue(value); } private class BackedCollection<E> implements Collection<E> { private Collection<E> collection; BackedCollection(Collection<E> set) { this.collection = set; } @Override public boolean add(E o) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection<? extends E> arg0) { throw new UnsupportedOperationException(); } @Override public void clear() { checkRealm(); collection.clear(); } @Override public boolean contains(Object o) { getterCalled(); return collection.contains(o); } @Override public boolean containsAll(Collection<?> c) { getterCalled(); return collection.containsAll(c); } @Override public boolean isEmpty() { getterCalled(); return collection.isEmpty(); } @Override public Iterator<E> iterator() { final Iterator<E> iterator = collection.iterator(); return new Iterator<E>() { @Override public boolean hasNext() { getterCalled(); return iterator.hasNext(); } @Override public E next() { getterCalled(); return iterator.next(); } @Override public void remove() { checkRealm(); iterator.remove(); } }; } @Override public boolean remove(Object o) { getterCalled(); return collection.remove(o); } @Override public boolean removeAll(Collection<?> c) { getterCalled(); return collection.removeAll(c); } @Override public boolean retainAll(Collection<?> c) { getterCalled(); return collection.retainAll(c); } @Override public int size() { getterCalled(); return collection.size(); } @Override public Object[] toArray() { getterCalled(); return collection.toArray(); } @Override public <T> T[] toArray(T[] array) { getterCalled(); return collection.toArray(array); } @Override public boolean equals(Object obj) { getterCalled(); return collection.equals(obj); } @Override public int hashCode() { getterCalled(); return collection.hashCode(); } @Override public String toString() { getterCalled(); return collection.toString(); } } private class BackedSet<E> extends BackedCollection<E> implements Set<E> { BackedSet(Set<E> set) { super(set); } } Set<Entry<K, V>> entrySet = null; @Override public Set<Entry<K, V>> entrySet() { getterCalled(); if (entrySet == null) { entrySet = new BackedSet<>(decorated.entrySet()); } return entrySet; } @Override public V get(Object key) { getterCalled(); return decorated.get(key); } @Override public boolean isEmpty() { getterCalled(); return decorated.isEmpty(); } Set<K> keySet = null; @Override public Set<K> keySet() { getterCalled(); if (keySet == null) { keySet = new BackedSet<>(decorated.keySet()); } return keySet; } @Override public V put(K key, V value) { checkRealm(); return decorated.put(key, value); } @Override public void putAll(Map<? extends K, ? extends V> m) { checkRealm(); decorated.putAll(m); } @Override public V remove(Object key) { checkRealm(); return decorated.remove(key); } @Override public int size() { getterCalled(); return decorated.size(); } Collection<V> values; @Override public Collection<V> values() { getterCalled(); if (values == null) { values = new BackedCollection<>(decorated.values()); } return values; } @Override public boolean equals(Object obj) { getterCalled(); if (this == obj) { return true; } return decorated.equals(obj); } @Override public int hashCode() { getterCalled(); return decorated.hashCode(); } @Override public String toString() { getterCalled(); return decorated.toString(); } @Override public synchronized void dispose() { if (decorated != null && mapChangeListener != null) { decorated.removeMapChangeListener(mapChangeListener); } decorated = null; mapChangeListener = null; super.dispose(); } }