Mercurial > projects > dwt2
diff org.eclipse.core.databinding/src/org/eclipse/core/databinding/observable/value/ComputedValue.d @ 78:0a55d2d5a946
Added file for databinding
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Tue, 14 Apr 2009 11:35:29 +0200 |
parents | |
children | 383ce7bd736b |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding/src/org/eclipse/core/databinding/observable/value/ComputedValue.d Tue Apr 14 11:35:29 2009 +0200 @@ -0,0 +1,263 @@ +/******************************************************************************* + * Copyright (c) 2005, 2008 IBM Corporation 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: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 116920 + * Brad Reynolds - bug 147515 + *******************************************************************************/ +module org.eclipse.core.databinding.observable.value.ComputedValue; + +import java.lang.all; + +import org.eclipse.core.databinding.observable.ChangeEvent; +import org.eclipse.core.databinding.observable.IChangeListener; +import org.eclipse.core.databinding.observable.IObservable; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.StaleEvent; + +/** + * A Lazily calculated value that automatically computes and registers listeners + * on its dependencies as long as all of its dependencies are IObservable + * objects + * <p> + * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + * </p> + * + * @since 1.0 + */ +public abstract class ComputedValue : AbstractObservableValue { + + private bool dirty = true; + + private bool stale = false; + + private Object cachedValue = null; + + /** + * Array of observables this computed value depends on. This field has a + * value of <code>null</code> if we are not currently listening. + */ + private IObservable[] dependencies = null; + + /** + * + */ + public this() { + this(Realm.getDefault(), null); + } + + /** + * @param valueType + * can be <code>null</code> + */ + public this(Object valueType) { + this(Realm.getDefault(), valueType); + } + + /** + * @param realm + * + */ + public this(Realm realm) { + this(realm, null); + } + + /** + * @param realm + * @param valueType + */ + public this(Realm realm, Object valueType) { + super(realm); + this.valueType = valueType; + } + + /** + * Inner class that implements interfaces that we don't want to expose as + * public API. Each interface could have been implemented using a separate + * anonymous class, but we combine them here to reduce the memory overhead + * and number of classes. + * + * <p> + * The Runnable calls computeValue and stores the result in cachedValue. + * </p> + * + * <p> + * The IChangeListener stores each observable in the dependencies list. This + * is registered as the listener when calling ObservableTracker, to detect + * every observable that is used by computeValue. + * </p> + * + * <p> + * The IChangeListener is attached to every dependency. + * </p> + * + */ + private class PrivateInterface : Runnable, IChangeListener, + IStaleListener { + public void run() { + cachedValue = calculate(); + } + + public void handleStale(StaleEvent event) { + if (!dirty && !stale) { + stale = true; + fireStale(); + } + } + + public void handleChange(ChangeEvent event) { + makeDirty(); + } + } + + private PrivateInterface privateInterface = new PrivateInterface(); + + private Object valueType; + + protected final Object doGetValue() { + if (dirty) { + // This line will do the following: + // - Run the calculate method + // - While doing so, add any observable that is touched to the + // dependencies list + IObservable[] newDependencies = ObservableTracker.runAndMonitor( + privateInterface, privateInterface, null); + + stale = false; + for (int i = 0; i < newDependencies.length; i++) { + IObservable observable = newDependencies[i]; + // Add a change listener to the new dependency. + if (observable.isStale()) { + stale = true; + } else { + observable.addStaleListener(privateInterface); + } + } + + dependencies = newDependencies; + + dirty = false; + } + + return cachedValue; + } + + /** + * Subclasses must override this method to provide the object's value. + * + * @return the object's value + */ + protected abstract Object calculate(); + + protected final void makeDirty() { + if (!dirty) { + dirty = true; + + stopListening(); + + // copy the old value + final Object oldValue = cachedValue; + // Fire the "dirty" event. This implementation recomputes the new + // value lazily. + fireValueChange(new class() ValueDiff { + + public Object getOldValue() { + return oldValue; + } + + public Object getNewValue() { + return getValue(); + } + }); + } + } + + /** + * + */ + private void stopListening() { + // Stop listening for dependency changes. + if (dependencies !is null) { + for (int i = 0; i < dependencies.length; i++) { + IObservable observable = dependencies[i]; + + observable.removeChangeListener(privateInterface); + observable.removeStaleListener(privateInterface); + } + dependencies = null; + } + } + + public bool isStale() { + // we need to recompute, otherwise staleness wouldn't mean anything + getValue(); + return stale; + } + + public Object getValueType() { + return valueType; + } + + // this method exists here so that we can call it from the runnable below. + /** + * @since 1.1 + */ + protected bool hasListeners() { + return super.hasListeners(); + } + + public synchronized void addChangeListener(IChangeListener listener) { + super.addChangeListener(listener); + // If somebody is listening, we need to make sure we attach our own + // listeners + computeValueForListeners(); + } + + /** + * Some clients just add a listener and expect to get notified even if they + * never called getValue(), so we have to call getValue() ourselves here to + * be sure. Need to be careful about realms though, this method can be + * called outside of our realm. See also bug 198211. If a client calls this + * outside of our realm, they may receive change notifications before the + * runnable below has been executed. It is their job to figure out what to + * do with those notifications. + */ + private void computeValueForListeners() { + getRealm().exec(new class() Runnable { + public void run() { + if (dependencies is null) { + // We are not currently listening. + if (hasListeners()) { + // But someone is listening for changes. Call getValue() + // to make sure we start listening to the observables we + // depend on. + getValue(); + } + } + } + }); + } + + public synchronized void addValueChangeListener( + IValueChangeListener listener) { + super.addValueChangeListener(listener); + // If somebody is listening, we need to make sure we attach our own + // listeners + computeValueForListeners(); + } + + public synchronized void dispose() { + super.dispose(); + stopListening(); + } + +}