/*******************************************************************************
* Copyright (c) 2010, 2015 Ovidio Mallo 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:
* Ovidio Mallo - initial API and implementation (bug 305367)
* Stefan Xenos <sxenos@gmail.com> - Bug 335792
******************************************************************************/
package org.eclipse.core.internal.databinding.observable.masterdetail;
import java.util.AbstractSet;
import java.util.Collections;
import java.util.IdentityHashMap;
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.DisposeEvent;
import org.eclipse.core.databinding.observable.IDisposeListener;
import org.eclipse.core.databinding.observable.IObserving;
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.masterdetail.IObservableFactory;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.IValueChangeListener;
import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
import org.eclipse.core.internal.databinding.identity.IdentityMap;
import org.eclipse.core.internal.databinding.identity.IdentitySet;
import org.eclipse.core.internal.databinding.observable.Util;
/**
* @param <K>
* type of the keys (the keys to both the given master observable map
* and the keys to the returned detail map, both of which are the
* same set of keys)
* @param <M>
* type of the master observables in the master set, being the values
* of the given master observable map
* @param <E>
* type of the detail elements, being the values of the returned
* detail map
* @since 1.4
*/
public class MapDetailValueObservableMap<K, M, E> extends
AbstractObservableMap<K, E> implements IObserving {
private IObservableMap<K, M> masterMap;
private IObservableFactory<? super M, IObservableValue<E>> observableValueFactory;
private Object detailValueType;
private Set<Map.Entry<K, E>> entrySet;
private IdentityHashMap<K, IObservableValue<E>> keyDetailMap = new IdentityHashMap<>();
private IdentitySet<IObservableValue<E>> staleDetailObservables = new IdentitySet<>();
private IMapChangeListener<K, M> masterMapListener = new IMapChangeListener<K, M>() {
@Override
public void handleMapChange(MapChangeEvent<? extends K, ? extends M> event) {
handleMasterMapChange(event.diff);
}
};
private IStaleListener masterStaleListener = new IStaleListener() {
@Override
public void handleStale(StaleEvent staleEvent) {
fireStale();
}
};
private IStaleListener detailStaleListener = new IStaleListener() {
@SuppressWarnings("unchecked")
@Override
public void handleStale(StaleEvent staleEvent) {
addStaleDetailObservable((IObservableValue<E>) staleEvent.getObservable());
}
};
/**
* @param masterMap
* @param observableValueFactory
* @param detailValueType
*/
public MapDetailValueObservableMap(
IObservableMap<K, M> masterMap,
IObservableFactory<? super M, IObservableValue<E>> observableValueFactory,
Object detailValueType) {
super(masterMap.getRealm());
this.masterMap = masterMap;
this.observableValueFactory = observableValueFactory;
this.detailValueType = detailValueType;
// Add change/stale/dispose listeners on the master map.
masterMap.addMapChangeListener(masterMapListener);
masterMap.addStaleListener(masterStaleListener);
masterMap.addDisposeListener(new IDisposeListener() {
@Override
public void handleDispose(DisposeEvent event) {
MapDetailValueObservableMap.this.dispose();
}
});
// Initialize the map with the current state of the master map.
Map<K, M> emptyMap = Collections.emptyMap();
MapDiff<K, M> initMasterDiff = Diffs.computeMapDiff(emptyMap, masterMap);
handleMasterMapChange(initMasterDiff);
}
private void handleMasterMapChange(MapDiff<? extends K, ? extends M> diff) {
// Collect the detail values for the master values in the input diff.
IdentityMap<K, E> oldValues = new IdentityMap<>();
IdentityMap<K, E> newValues = new IdentityMap<>();
// Handle added master values.
Set<? extends K> addedKeys = diff.getAddedKeys();
for (K addedKey : addedKeys) {
// For added master values, we set up a new detail observable.
addDetailObservable(addedKey);
// Get the value of the created detail observable for the new diff.
IObservableValue<E> detailValue = getDetailObservableValue(addedKey);
newValues.put(addedKey, detailValue.getValue());
}
// Handle removed master values.
Set<? extends K> removedKeys = diff.getRemovedKeys();
for (K removedKey : removedKeys) {
// First of all, get the current detail value and add it to the set
// of old values of the new diff.
IObservableValue<E> detailValue = getDetailObservableValue(removedKey);
oldValues.put(removedKey, detailValue.getValue());
// For removed master values, we dispose the detail observable.
removeDetailObservable(removedKey);
}
// Handle changed master values.
Set<? extends K> changedKeys = diff.getChangedKeys();
for (K changedKey : changedKeys) {
// Get the detail value prior to the change and add it to the set of
// old values of the new diff.
IObservableValue<E> oldDetailValue = getDetailObservableValue(changedKey);
oldValues.put(changedKey, oldDetailValue.getValue());
// Remove the old detail value for the old master value and add it
// again for the new master value.
removeDetailObservable(changedKey);
addDetailObservable(changedKey);
// Get the new detail value and add it to the set of new values.
IObservableValue<E> newDetailValue = getDetailObservableValue(changedKey);
newValues.put(changedKey, newDetailValue.getValue());
}
// The different key sets are the same, only the values change.
fireMapChange(Diffs.createMapDiff(addedKeys, removedKeys, changedKeys,
oldValues, newValues));
}
private void addDetailObservable(final K addedKey) {
M masterElement = masterMap.get(addedKey);
IObservableValue<E> detailValue = keyDetailMap.get(addedKey);
if (detailValue == null) {
detailValue = createDetailObservable(masterElement);
keyDetailMap.put(addedKey, detailValue);
detailValue.addValueChangeListener(new IValueChangeListener<E>() {
@Override
public void handleValueChange(ValueChangeEvent<? extends E> event) {
if (!event.getObservableValue().isStale()) {
staleDetailObservables.remove(event.getSource());
}
fireMapChange(Diffs.createMapDiffSingleChange(addedKey,
event.diff.getOldValue(), event.diff.getNewValue()));
}
});
if (detailValue.isStale()) {
addStaleDetailObservable(detailValue);
}
}
detailValue.addStaleListener(detailStaleListener);
}
private IObservableValue<E> createDetailObservable(M masterElement) {
ObservableTracker.setIgnore(true);
try {
return observableValueFactory.createObservable(masterElement);
} finally {
ObservableTracker.setIgnore(false);
}
}
private void removeDetailObservable(Object removedKey) {
if (isDisposed()) {
return;
}
IObservableValue<E> detailValue = keyDetailMap.remove(removedKey);
staleDetailObservables.remove(detailValue);
detailValue.dispose();
}
private IObservableValue<E> getDetailObservableValue(Object masterKey) {
return keyDetailMap.get(masterKey);
}
private void addStaleDetailObservable(IObservableValue<E> detailObservable) {
boolean wasStale = isStale();
staleDetailObservables.add(detailObservable);
if (!wasStale) {
fireStale();
}
}
@Override
public Set<K> keySet() {
getterCalled();
return masterMap.keySet();
}
@Override
public E get(Object key) {
getterCalled();
if (!containsKey(key)) {
return null;
}
IObservableValue<E> detailValue = getDetailObservableValue(key);
return detailValue.getValue();
}
@Override
public E put(K key, E value) {
if (!containsKey(key)) {
return null;
}
IObservableValue<E> detailValue = getDetailObservableValue(key);
E oldValue = detailValue.getValue();
detailValue.setValue(value);
return oldValue;
}
@Override
public boolean containsKey(Object key) {
getterCalled();
return masterMap.containsKey(key);
}
@Override
public E remove(Object key) {
checkRealm();
if (!containsKey(key)) {
return null;
}
IObservableValue<E> detailValue = getDetailObservableValue(key);
E oldValue = detailValue.getValue();
masterMap.remove(key);
return oldValue;
}
@Override
public int size() {
getterCalled();
return masterMap.size();
}
@Override
public boolean isStale() {
return super.isStale()
|| (masterMap != null && masterMap.isStale())
|| (staleDetailObservables != null && !staleDetailObservables
.isEmpty());
}
@Override
public Object getKeyType() {
return masterMap.getKeyType();
}
@Override
public Object getValueType() {
return detailValueType;
}
@Override
public Object getObserved() {
return masterMap;
}
@Override
public synchronized void dispose() {
if (masterMap != null) {
masterMap.removeMapChangeListener(masterMapListener);
masterMap.removeStaleListener(masterStaleListener);
}
if (keyDetailMap != null) {
for (IObservableValue<E> detailValue : keyDetailMap.values()) {
detailValue.dispose();
}
keyDetailMap.clear();
}
masterMap = null;
observableValueFactory = null;
detailValueType = null;
keyDetailMap = null;
masterStaleListener = null;
detailStaleListener = null;
staleDetailObservables = null;
super.dispose();
}
@Override
public Set<Map.Entry<K, E>> entrySet() {
getterCalled();
if (entrySet == null) {
entrySet = new EntrySet();
}
return entrySet;
}
private void getterCalled() {
ObservableTracker.getterCalled(this);
}
private class EntrySet extends AbstractSet<Map.Entry<K, E>> {
@Override
public Iterator<Map.Entry<K, E>> iterator() {
final Iterator<K> keyIterator = keySet().iterator();
return new Iterator<Map.Entry<K, E>>() {
@Override
public boolean hasNext() {
return keyIterator.hasNext();
}
@Override
public Map.Entry<K, E> next() {
K key = keyIterator.next();
return new MapEntry(key);
}
@Override
public void remove() {
keyIterator.remove();
}
};
}
@Override
public int size() {
return MapDetailValueObservableMap.this.size();
}
}
private final class MapEntry implements Map.Entry<K, E> {
private final K key;
private MapEntry(K key) {
this.key = key;
}
@Override
public K getKey() {
MapDetailValueObservableMap.this.getterCalled();
return key;
}
@Override
public E getValue() {
return MapDetailValueObservableMap.this.get(getKey());
}
@Override
public E setValue(E value) {
return MapDetailValueObservableMap.this.put(getKey(), value);
}
@Override
public boolean equals(Object o) {
MapDetailValueObservableMap.this.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() {
MapDetailValueObservableMap.this.getterCalled();
Object value = getValue();
return (getKey() == null ? 0 : getKey().hashCode())
^ (value == null ? 0 : value.hashCode());
}
}
}