/******************************************************************************* * 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 194734) * Matthew Hall - bugs 265561, 262287, 268203, 268688, 301774, 303847 * Ovidio Mallo - bug 332367 * Stefan Xenos <sxenos@gmail.com> - Bug 335792 ******************************************************************************/ package org.eclipse.core.internal.databinding.property.map; import java.util.AbstractSet; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.eclipse.core.databinding.observable.Diffs; import org.eclipse.core.databinding.observable.ObservableTracker; import org.eclipse.core.databinding.observable.Realm; import org.eclipse.core.databinding.observable.map.AbstractObservableMap; import org.eclipse.core.databinding.observable.map.MapDiff; import org.eclipse.core.databinding.property.INativePropertyListener; import org.eclipse.core.databinding.property.IPropertyObservable; import org.eclipse.core.databinding.property.ISimplePropertyListener; import org.eclipse.core.databinding.property.SimplePropertyEvent; import org.eclipse.core.databinding.property.map.SimpleMapProperty; /** * @param <S> * type of the source object * @param <K> * type of the keys to the map * @param <V> * type of the values in the map * @since 1.2 */ public class SimplePropertyObservableMap<S, K, V> extends AbstractObservableMap<K, V> implements IPropertyObservable<SimpleMapProperty<S, K, V>> { private S source; private SimpleMapProperty<S, K, V> property; private volatile boolean updating = false; private volatile int modCount = 0; private INativePropertyListener<S> listener; private Map<K, V> cachedMap; private boolean stale; /** * @param realm * @param source * @param property */ public SimplePropertyObservableMap(Realm realm, S source, SimpleMapProperty<S, K, V> property) { super(realm); this.source = source; this.property = property; } @Override public Object getKeyType() { return property.getKeyType(); } @Override public Object getValueType() { return property.getValueType(); } private void getterCalled() { ObservableTracker.getterCalled(this); } @Override protected void firstListenerAdded() { if (!isDisposed() && listener == null) { listener = property.adaptListener(new ISimplePropertyListener<S, MapDiff<K, V>>() { @Override public void handleEvent(final SimplePropertyEvent<S, MapDiff<K, V>> event) { if (!isDisposed() && !updating) { getRealm().exec(new Runnable() { @Override public void run() { if (event.type == SimplePropertyEvent.CHANGE) { modCount++; notifyIfChanged(event.diff); } else if (event.type == SimplePropertyEvent.STALE && !stale) { stale = true; fireStale(); } } }); } } }); } getRealm().exec(new Runnable() { @Override public void run() { cachedMap = new HashMap<>(getMap()); stale = false; if (listener != null) listener.addTo(source); } }); } @Override protected void lastListenerRemoved() { if (listener != null) listener.removeFrom(source); cachedMap.clear(); cachedMap = null; stale = false; } // Queries private Map<K, V> getMap() { return property.getMap(source); } // Single change operations private void updateMap(Map<K, V> map, MapDiff<K, V> diff) { if (!diff.isEmpty()) { boolean wasUpdating = updating; updating = true; try { property.updateMap(source, diff); modCount++; } finally { updating = wasUpdating; } notifyIfChanged(null); } } private EntrySet es = new EntrySet(); @Override public Set<Map.Entry<K, V>> entrySet() { getterCalled(); return es; } private class EntrySet extends AbstractSet<Map.Entry<K, V>> { @Override public Iterator<Map.Entry<K, V>> iterator() { return new EntrySetIterator(); } @Override public int size() { return getMap().size(); } } private class EntrySetIterator implements Iterator<Map.Entry<K, V>> { private volatile int expectedModCount = modCount; Map<K, V> map = new HashMap<>(getMap()); Iterator<Map.Entry<K, V>> iterator = map.entrySet().iterator(); Map.Entry<K, V> last = null; @Override public boolean hasNext() { getterCalled(); checkForComodification(); return iterator.hasNext(); } @Override public Map.Entry<K, V> next() { getterCalled(); checkForComodification(); last = iterator.next(); return last; } @Override public void remove() { getterCalled(); checkForComodification(); MapDiff<K, V> diff = Diffs.createMapDiffSingleRemove(last.getKey(), last.getValue()); updateMap(map, diff); iterator.remove(); // stay in sync last = null; expectedModCount = modCount; } private void checkForComodification() { if (expectedModCount != modCount) throw new ConcurrentModificationException(); } } @Override public Set<K> keySet() { getterCalled(); // AbstractMap depends on entrySet() to fulfil keySet() API, so all // getterCalled() and comodification checks will still be handled return super.keySet(); } @Override public boolean containsKey(Object key) { getterCalled(); return getMap().containsKey(key); } @Override public V get(Object key) { getterCalled(); return getMap().get(key); } @Override public V put(K key, V value) { checkRealm(); Map<K, V> map = getMap(); boolean add = !map.containsKey(key); V oldValue = map.get(key); MapDiff<K, V> diff; if (add) diff = Diffs.createMapDiffSingleAdd(key, value); else diff = Diffs.createMapDiffSingleChange(key, oldValue, value); updateMap(map, diff); return oldValue; } @Override public void putAll(Map<? extends K, ? extends V> m) { checkRealm(); Map<K, V> map = getMap(); Map<K, V> oldValues = new HashMap<K, V>(); Map<K, V> newValues = new HashMap<K, V>(); Set<K> changedKeys = new HashSet<K>(); Set<K> addedKeys = new HashSet<K>(); for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) { K key = entry.getKey(); V newValue = entry.getValue(); if (map.containsKey(key)) { changedKeys.add(key); oldValues.put(key, map.get(key)); } else { addedKeys.add(key); } newValues.put(key, newValue); } MapDiff<K, V> diff = Diffs.createMapDiff(addedKeys, Collections.<K> emptySet(), changedKeys, oldValues, newValues); updateMap(map, diff); } @Override public V remove(Object key) { checkRealm(); Map<K, V> map = getMap(); if (!map.containsKey(key)) return null; V oldValue = map.get(key); @SuppressWarnings("unchecked") // if we contain this key, then it is of // type K MapDiff<K, V> diff = Diffs.createMapDiffSingleRemove((K) key, oldValue); updateMap(map, diff); return oldValue; } @Override public void clear() { getterCalled(); Map<K, V> map = getMap(); if (map.isEmpty()) return; MapDiff<K, V> diff = Diffs.createMapDiffRemoveAll(new HashMap<K, V>(map)); updateMap(map, diff); } @Override public Collection<V> values() { getterCalled(); // AbstractMap depends on entrySet() to fulfil values() API, so all // getterCalled() and comodification checks will still be handled return super.values(); } private void notifyIfChanged(MapDiff<K, V> diff) { if (hasListeners()) { Map<K, V> oldMap = cachedMap; Map<K, V> newMap = cachedMap = new HashMap<K, V>(getMap()); if (diff == null) diff = Diffs.computeMapDiff(oldMap, newMap); if (!diff.isEmpty() || stale) { stale = false; fireMapChange(diff); } } } @Override public boolean isStale() { getterCalled(); return stale; } @Override public Object getObserved() { return source; } @Override public SimpleMapProperty<S, K, V> getProperty() { return property; } @Override public synchronized void dispose() { if (!isDisposed()) { if (listener != null) listener.removeFrom(source); property = null; source = null; listener = null; stale = false; } super.dispose(); } }