/*******************************************************************************
* 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 262269, 265561, 262287, 268688, 278550, 303847
* Ovidio Mallo - bugs 299619, 301370
* Stefan Xenos <sxenos@gmail.com> - Bug 335792
******************************************************************************/
package org.eclipse.core.internal.databinding.property.value;
import java.util.AbstractSet;
import java.util.Collections;
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.IStaleListener;
import org.eclipse.core.databinding.observable.ObservableTracker;
import org.eclipse.core.databinding.observable.StaleEvent;
import org.eclipse.core.databinding.observable.map.AbstractObservableMap;
import org.eclipse.core.databinding.observable.map.IMapChangeListener;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.map.MapChangeEvent;
import org.eclipse.core.databinding.observable.map.MapDiff;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.core.databinding.observable.set.ISetChangeListener;
import org.eclipse.core.databinding.observable.set.SetChangeEvent;
import org.eclipse.core.databinding.observable.value.ValueDiff;
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.value.SimpleValueProperty;
import org.eclipse.core.internal.databinding.identity.IdentityMap;
import org.eclipse.core.internal.databinding.identity.IdentityObservableSet;
import org.eclipse.core.internal.databinding.identity.IdentitySet;
import org.eclipse.core.internal.databinding.property.Util;
/**
* @param <S>
* type of the source object
* @param <K>
* type of the keys to the map
* @param <I>
* type of the intermediate values
* @param <V>
* type of the values in the map
* @since 1.2
*
*/
public class MapSimpleValueObservableMap<S, K, I extends S, V> extends AbstractObservableMap<K, V>
implements IPropertyObservable<SimpleValueProperty<S, V>> {
private IObservableMap<K, I> masterMap;
private SimpleValueProperty<S, V> detailProperty;
private IObservableSet<I> knownMasterValues;
private Map<I, V> cachedValues;
private Set<I> staleMasterValues;
private boolean updating = false;
private IMapChangeListener<K, I> masterListener = new IMapChangeListener<K, I>() {
@Override
public void handleMapChange(final MapChangeEvent<? extends K, ? extends I> event) {
if (!isDisposed()) {
updateKnownValues();
if (!updating)
fireMapChange(convertDiff(event.diff));
}
}
private void updateKnownValues() {
Set<I> knownValues = new IdentitySet<>(masterMap.values());
knownMasterValues.retainAll(knownValues);
knownMasterValues.addAll(knownValues);
}
private MapDiff<K, V> convertDiff(MapDiff<? extends K, ? extends I> diff) {
Map<K, V> oldValues = new IdentityMap<>();
Map<K, V> newValues = new IdentityMap<>();
Set<? extends K> addedKeys = diff.getAddedKeys();
for (K key : addedKeys) {
I newSource = diff.getNewValue(key);
V newValue = detailProperty.getValue(newSource);
newValues.put(key, newValue);
}
Set<? extends K> removedKeys = diff.getRemovedKeys();
for (K key : removedKeys) {
I oldSource = diff.getOldValue(key);
V oldValue = detailProperty.getValue(oldSource);
oldValues.put(key, oldValue);
}
Set<K> changedKeys = new IdentitySet<K>(diff.getChangedKeys());
for (Iterator<K> it = changedKeys.iterator(); it.hasNext();) {
K key = it.next();
I oldSource = diff.getOldValue(key);
I newSource = diff.getNewValue(key);
V oldValue = detailProperty.getValue(oldSource);
V newValue = detailProperty.getValue(newSource);
if (Util.equals(oldValue, newValue)) {
it.remove();
} else {
oldValues.put(key, oldValue);
newValues.put(key, newValue);
}
}
return Diffs.createMapDiff(addedKeys, removedKeys, changedKeys, oldValues, newValues);
}
};
private IStaleListener staleListener = new IStaleListener() {
@Override
public void handleStale(StaleEvent staleEvent) {
fireStale();
}
};
private INativePropertyListener<S> detailListener;
/**
* @param map
* @param valueProperty
*/
public MapSimpleValueObservableMap(IObservableMap<K, I> map, SimpleValueProperty<S, V> valueProperty) {
super(map.getRealm());
this.masterMap = map;
this.detailProperty = valueProperty;
ISimplePropertyListener<S, ValueDiff<? extends V>> listener = new ISimplePropertyListener<S, ValueDiff<? extends V>>() {
@Override
public void handleEvent(final SimplePropertyEvent<S, ValueDiff<? extends V>> event) {
if (!isDisposed() && !updating) {
getRealm().exec(new Runnable() {
@Override
public void run() {
@SuppressWarnings("unchecked")
I source = (I) event.getSource();
if (event.type == SimplePropertyEvent.CHANGE) {
notifyIfChanged(source);
} else if (event.type == SimplePropertyEvent.STALE) {
boolean wasStale = !staleMasterValues.isEmpty();
staleMasterValues.add(source);
if (!wasStale)
fireStale();
}
}
});
}
}
};
this.detailListener = detailProperty.adaptListener(listener);
}
@Override
public Object getKeyType() {
return masterMap.getKeyType();
}
@Override
public Object getValueType() {
return detailProperty.getValueType();
}
@Override
protected void firstListenerAdded() {
ObservableTracker.setIgnore(true);
try {
knownMasterValues = new IdentityObservableSet<>(getRealm(), null);
} finally {
ObservableTracker.setIgnore(false);
}
cachedValues = new IdentityMap<>();
staleMasterValues = new IdentitySet<>();
knownMasterValues.addSetChangeListener(new ISetChangeListener<I>() {
@Override
public void handleSetChange(SetChangeEvent<? extends I> event) {
for (I key : event.diff.getRemovals()) {
if (detailListener != null)
detailListener.removeFrom(key);
cachedValues.remove(key);
staleMasterValues.remove(key);
}
for (I key : event.diff.getAdditions()) {
cachedValues.put(key, detailProperty.getValue(key));
if (detailListener != null)
detailListener.addTo(key);
}
}
});
getRealm().exec(new Runnable() {
@Override
public void run() {
knownMasterValues.addAll(masterMap.values());
masterMap.addMapChangeListener(masterListener);
masterMap.addStaleListener(staleListener);
}
});
}
@Override
protected void lastListenerRemoved() {
masterMap.removeMapChangeListener(masterListener);
masterMap.removeStaleListener(staleListener);
if (knownMasterValues != null) {
knownMasterValues.dispose();
knownMasterValues = null;
}
cachedValues.clear();
cachedValues = null;
staleMasterValues.clear();
staleMasterValues = null;
}
private Set<Map.Entry<K, V>> entrySet;
@Override
public Set<Map.Entry<K, V>> entrySet() {
getterCalled();
if (entrySet == null)
entrySet = new EntrySet();
return entrySet;
}
class EntrySet extends AbstractSet<Map.Entry<K, V>> {
@Override
public Iterator<Map.Entry<K, V>> iterator() {
return new Iterator<Map.Entry<K, V>>() {
Iterator<Map.Entry<K, I>> it = masterMap.entrySet().iterator();
@Override
public boolean hasNext() {
getterCalled();
return it.hasNext();
}
@Override
public Map.Entry<K, V> next() {
getterCalled();
Map.Entry<K, I> next = it.next();
return new MapEntry(next.getKey());
}
@Override
public void remove() {
it.remove();
}
};
}
@Override
public int size() {
return masterMap.size();
}
}
class MapEntry implements Map.Entry<K, V> {
private K key;
MapEntry(K key) {
this.key = key;
}
@Override
public K getKey() {
getterCalled();
return key;
}
@Override
public V getValue() {
getterCalled();
if (!masterMap.containsKey(key))
return null;
return detailProperty.getValue(masterMap.get(key));
}
@Override
public V setValue(V value) {
if (!masterMap.containsKey(key))
return null;
I source = masterMap.get(key);
V oldValue = detailProperty.getValue(source);
updating = true;
try {
detailProperty.setValue(source, value);
} finally {
updating = false;
}
notifyIfChanged(source);
return oldValue;
}
@Override
public boolean equals(Object o) {
getterCalled();
if (o == this)
return true;
if (o == null)
return false;
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?, ?> that = (Map.Entry<?, ?>) o;
return Util.equals(this.getKey(), that.getKey()) && Util.equals(this.getValue(), that.getValue());
}
@Override
public int hashCode() {
getterCalled();
Object value = getValue();
return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());
}
}
@Override
public boolean containsKey(Object key) {
getterCalled();
return masterMap.containsKey(key);
}
@Override
public V get(Object key) {
getterCalled();
return detailProperty.getValue(masterMap.get(key));
}
@Override
public V put(K key, V value) {
if (!masterMap.containsKey(key))
return null;
I masterValue = masterMap.get(key);
V oldValue = detailProperty.getValue(masterValue);
detailProperty.setValue(masterValue, value);
notifyIfChanged(masterValue);
return oldValue;
}
@Override
public V remove(Object key) {
checkRealm();
I masterValue = masterMap.get(key);
V oldValue = detailProperty.getValue(masterValue);
masterMap.remove(key);
return oldValue;
}
private void notifyIfChanged(I masterValue) {
if (cachedValues != null) {
final Set<K> keys = keysFor(masterValue);
final V oldValue = cachedValues.get(masterValue);
final V newValue = detailProperty.getValue(masterValue);
if (!Util.equals(oldValue, newValue) || staleMasterValues.contains(masterValue)) {
cachedValues.put(masterValue, newValue);
staleMasterValues.remove(masterValue);
fireMapChange(new MapDiff<K, V>() {
@Override
public Set<K> getAddedKeys() {
return Collections.emptySet();
}
@Override
public Set<K> getChangedKeys() {
return keys;
}
@Override
public Set<K> getRemovedKeys() {
return Collections.emptySet();
}
@Override
public V getNewValue(Object key) {
return newValue;
}
@Override
public V getOldValue(Object key) {
return oldValue;
}
});
}
}
}
private Set<K> keysFor(I value) {
Set<K> keys = new IdentitySet<K>();
for (Map.Entry<K, I> entry : masterMap.entrySet()) {
if (entry.getValue() == value) {
keys.add(entry.getKey());
}
}
return keys;
}
@Override
public boolean isStale() {
getterCalled();
return masterMap.isStale() || staleMasterValues != null && !staleMasterValues.isEmpty();
}
private void getterCalled() {
ObservableTracker.getterCalled(this);
}
@Override
public Object getObserved() {
return masterMap;
}
@Override
public SimpleValueProperty<S, V> getProperty() {
return detailProperty;
}
@Override
public synchronized void dispose() {
if (masterMap != null) {
masterMap.removeMapChangeListener(masterListener);
masterMap = null;
}
if (knownMasterValues != null) {
knownMasterValues.clear(); // detaches listeners
knownMasterValues.dispose();
knownMasterValues = null;
}
masterListener = null;
detailListener = null;
detailProperty = null;
cachedValues = null;
staleMasterValues = null;
super.dispose();
}
}