/******************************************************************************* * 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, 266754, 265561, 262287, 268688 * Ovidio Mallo - bug 299619 * Stefan Xenos <sxenos@gmail.com> - Bug 335792 ******************************************************************************/ package org.eclipse.core.internal.databinding.property.value; import java.util.Map; import java.util.Set; import org.eclipse.core.databinding.observable.Diffs; import org.eclipse.core.databinding.observable.map.ComputedObservableMap; import org.eclipse.core.databinding.observable.set.IObservableSet; 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.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 <V> * type of the values in the map * @since 1.2 */ public class SetSimpleValueObservableMap<S, K extends S, V> extends ComputedObservableMap<K, V> implements IPropertyObservable<SimpleValueProperty<S, V>> { private SimpleValueProperty<S, V> detailProperty; private INativePropertyListener<S> listener; private Map<K, V> cachedValues; private Set<K> staleKeys; private boolean updating; /** * @param keySet * @param valueProperty */ public SetSimpleValueObservableMap(IObservableSet<K> keySet, SimpleValueProperty<S, V> valueProperty) { super(keySet, valueProperty.getValueType()); this.detailProperty = valueProperty; } @Override protected void firstListenerAdded() { if (listener == null) { listener = detailProperty.adaptListener(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") K source = (K) event.getSource(); if (event.type == SimplePropertyEvent.CHANGE) { notifyIfChanged(source); } else if (event.type == SimplePropertyEvent.STALE) { boolean wasStale = !staleKeys.isEmpty(); staleKeys.add(source); if (!wasStale) fireStale(); } } }); } } }); } cachedValues = new IdentityMap<>(); staleKeys = new IdentitySet<>(); super.firstListenerAdded(); } @Override protected void lastListenerRemoved() { super.lastListenerRemoved(); cachedValues.clear(); cachedValues = null; staleKeys.clear(); staleKeys = null; } @Override protected void hookListener(K addedKey) { if (cachedValues != null) { cachedValues.put(addedKey, detailProperty.getValue(addedKey)); if (listener != null) listener.addTo(addedKey); } } @Override protected void unhookListener(K removedKey) { if (cachedValues != null) { if (listener != null) listener.removeFrom(removedKey); cachedValues.remove(removedKey); staleKeys.remove(removedKey); } } @Override @SuppressWarnings("unchecked") protected V doGet(Object key) { // NOTE/TODO: This is unsafe and will cause ClassCastExceptions // if this map is queried with keys that are not of type S return detailProperty.getValue((S) key); } @Override protected V doPut(K key, V value) { V oldValue = detailProperty.getValue(key); updating = true; try { detailProperty.setValue(key, value); } finally { updating = false; } notifyIfChanged(key); return oldValue; } private void notifyIfChanged(K key) { if (cachedValues != null) { V oldValue = cachedValues.get(key); V newValue = detailProperty.getValue(key); if (!Util.equals(oldValue, newValue) || staleKeys.contains(key)) { cachedValues.put(key, newValue); staleKeys.remove(key); fireMapChange(Diffs.createMapDiffSingleChange(key, oldValue, newValue)); } } } @Override public Object getObserved() { return keySet(); } @Override public SimpleValueProperty<S, V> getProperty() { return detailProperty; } @Override public boolean isStale() { return super.isStale() || staleKeys != null && !staleKeys.isEmpty(); } @Override public synchronized void dispose() { if (cachedValues != null) { cachedValues.clear(); cachedValues = null; } listener = null; detailProperty = null; cachedValues = null; staleKeys = null; super.dispose(); } }