/*******************************************************************************
* Copyright (c) 2009, 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, 281727, 278550
* Stefan Xenos <sxenos@gmail.com> - Bug 335792
******************************************************************************/
package org.eclipse.core.internal.databinding.property.value;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.databinding.observable.ObservableTracker;
import org.eclipse.core.databinding.observable.Realm;
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.set.IObservableSet;
import org.eclipse.core.databinding.observable.set.ISetChangeListener;
import org.eclipse.core.databinding.observable.set.SetChangeEvent;
import org.eclipse.core.databinding.property.value.DelegatingValueProperty;
import org.eclipse.core.databinding.property.value.IValueProperty;
import org.eclipse.core.internal.databinding.identity.IdentityMap;
import org.eclipse.core.internal.databinding.identity.IdentityObservableSet;
/**
* @since 3.3
*
*/
abstract class DelegatingCache<S, K extends S, V> {
private Realm realm;
private DelegatingValueProperty<S, V> detailProperty;
private IObservableSet<K> elements;
private Map<IValueProperty<S, V>, DelegateCache> delegateCaches;
private class DelegateCache implements IMapChangeListener<K, V> {
private final IValueProperty<S, V> delegate;
private final IObservableSet<K> masterElements;
private final IObservableMap<K, V> masterElementValues;
private final Map<K, V> cachedValues;
DelegateCache(IValueProperty<S, V> delegate) {
this.delegate = delegate;
ObservableTracker.setIgnore(true);
try {
this.masterElements = new IdentityObservableSet<>(realm, elements.getElementType());
this.masterElementValues = delegate.observeDetail(masterElements);
} finally {
ObservableTracker.setIgnore(false);
}
this.cachedValues = new IdentityMap<>();
masterElementValues.addMapChangeListener(this);
}
void add(K masterElement) {
boolean wasEmpty = masterElements.isEmpty();
masterElements.add(masterElement);
cachedValues.put(masterElement, masterElementValues.get(masterElement));
if (wasEmpty)
delegateCaches.put(delegate, this);
}
void remove(Object masterElement) {
cachedValues.remove(masterElement);
masterElements.remove(masterElement);
if (cachedValues.isEmpty())
dispose();
}
V get(Object masterElement) {
return cachedValues.get(masterElement);
}
V put(K masterElement, V detailValue) {
V oldValue = masterElementValues.put(masterElement, detailValue);
notifyIfChanged(masterElement);
return oldValue;
}
boolean containsValue(Object detailValue) {
return cachedValues.containsValue(detailValue);
}
@Override
public void handleMapChange(MapChangeEvent<? extends K, ? extends V> event) {
Set<? extends K> changedKeys = event.diff.getChangedKeys();
for (K next : changedKeys) {
notifyIfChanged(next);
}
}
private void notifyIfChanged(K masterElement) {
V oldValue = cachedValues.get(masterElement);
V newValue = masterElementValues.get(masterElement);
if (oldValue != newValue) {
cachedValues.put(masterElement, newValue);
handleValueChange(masterElement, oldValue, newValue);
}
}
void handleValueChange(K masterElement, V oldValue, V newValue) {
DelegatingCache.this.handleValueChange(masterElement, oldValue, newValue);
}
void dispose() {
delegateCaches.remove(delegate);
masterElementValues.dispose();
masterElements.dispose();
cachedValues.clear();
}
}
DelegatingCache(Realm realm, DelegatingValueProperty<S, V> detailProperty) {
this.realm = realm;
this.detailProperty = detailProperty;
ObservableTracker.setIgnore(true);
try {
this.elements = new IdentityObservableSet<>(realm, null);
} finally {
ObservableTracker.setIgnore(false);
}
this.delegateCaches = new IdentityMap<>();
elements.addSetChangeListener(new ISetChangeListener<K>() {
@Override
public void handleSetChange(SetChangeEvent<? extends K> event) {
for (K element : event.diff.getRemovals()) {
getCache(element).remove(element);
}
for (K element : event.diff.getAdditions()) {
getCache(element).add(element);
}
}
});
}
private DelegateCache getCache(Object masterElement) {
// NOTE: This is unsafe. This method can be invoked with something other
// than an element of type S, in which case getDelegate(...) will most
// likely throw a ClassCastException. This should really be redesigned
// such that getDelegete will never be invoked for an element which
// isn't actually part of the cache.
@SuppressWarnings("unchecked")
IValueProperty<S, V> delegate = detailProperty.getDelegate((S) masterElement);
if (delegateCaches.containsKey(delegate)) {
return delegateCaches.get(delegate);
}
return new DelegateCache(delegate);
}
V get(Object element) {
return getCache(element).get(element);
}
V put(K element, V value) {
return getCache(element).put(element, value);
}
boolean containsValue(Object value) {
for (DelegateCache cache : delegateCaches.values()) {
if (cache.containsValue(value))
return true;
}
return false;
}
void addAll(Collection<? extends K> elements) {
this.elements.addAll(elements);
}
void retainAll(Collection<?> elements) {
this.elements.retainAll(elements);
}
abstract void handleValueChange(K masterElement, V oldValue, V newValue);
void dispose() {
if (elements != null) {
elements.clear(); // clears caches
elements.dispose();
elements = null;
}
if (delegateCaches != null) {
for (DelegatingCache<S, K, V>.DelegateCache cache : delegateCaches.values()) {
cache.dispose();
}
delegateCaches.clear();
delegateCaches = null;
}
}
}