Mercurial > projects > dwt2
view org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ComputedList.d @ 96:b74ac5dfcc06
package to module and java.lang.all import
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Tue, 21 Apr 2009 11:03:33 +0200 |
parents | 6208d4f6a277 |
children | c86eb8b3098e |
line wrap: on
line source
/************************************************************************************************************ * Copyright (c) 2007 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 * IBM Corporation - initial API and implementation * Brad Reynolds - initial API and implementation (through bug 116920 and bug 147515) * Matthew Hall - bug 211786 ***********************************************************************************************************/ module org.eclipse.core.databinding.observable.list.ComputedList; import java.lang.all; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.core.databinding.observable.ChangeEvent; import org.eclipse.core.databinding.observable.Diffs; 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 list 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.1 */ public abstract class ComputedList : AbstractObservableList { private List cachedList = new ArrayList(); private bool dirty = true; private bool stale = false; private IObservable[] dependencies = new IObservable[0]; /** * Creates a computed list in the default realm and with an unknown (null) * element type. */ public this() { this(Realm.getDefault(), null); } /** * Creates a computed list in the default realm and with the given element * type. * * @param elementType * the element type, may be <code>null</code> to indicate * unknown element type */ public this(Object elementType) { this(Realm.getDefault(), elementType); } /** * Creates a computed list in given realm and with an unknown (null) element * type. * * @param realm * the realm * */ public this(Realm realm) { this(realm, null); } /** * Creates a computed list in the given realm and with the given element * type. * * @param realm * the realm * @param elementType * the element type, may be <code>null</code> to indicate * unknown element type */ public this(Realm realm, Object elementType) { super(realm); this.elementType = elementType; } /** * 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 calculate and stores the result in cachedList. * </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() { cachedList = calculate(); if (cachedList is null) cachedList = Collections.EMPTY_LIST; } public void handleStale(StaleEvent event) { if (!dirty) makeStale(); } public void handleChange(ChangeEvent event) { makeDirty(); } } private PrivateInterface privateInterface = new PrivateInterface(); private Object elementType; protected int doGetSize() { return doGetList().size(); } public Object get(int index) { getterCalled(); return doGetList().get(index); } private final List getList() { getterCalled(); return doGetList(); } final List doGetList() { 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); // If any dependencies are stale, a stale event will be fired here // even if we were already stale before recomputing. This is in case // clients assume that a list change is indicative of non-staleness. stale = false; for (int i = 0; i < newDependencies.length; i++) { if (newDependencies[i].isStale()) { makeStale(); break; } } if (!stale) { for (int i = 0; i < newDependencies.length; i++) { newDependencies[i].addStaleListener(privateInterface); } } dependencies = newDependencies; dirty = false; } return cachedList; } private void getterCalled() { ObservableTracker.getterCalled(this); } /** * Subclasses must override this method to calculate the list contents. * * @return the object's list. */ protected abstract List calculate(); private void makeDirty() { if (!dirty) { dirty = true; makeStale(); stopListening(); // copy the old list final List oldList = new ArrayList(cachedList); // Fire the "dirty" event. This implementation recomputes the new // list lazily. fireListChange(new class() ListDiff { ListDiffEntry[] differences; public ListDiffEntry[] getDifferences() { if (differences is null) differences = Diffs.computeListDiff(oldList, getList()) .getDifferences(); return differences; } }); } } private void stopListening() { if (dependencies !is null) { for (int i = 0; i < dependencies.length; i++) { IObservable observable = dependencies[i]; observable.removeChangeListener(privateInterface); observable.removeStaleListener(privateInterface); } dependencies = null; } } private void makeStale() { if (!stale) { stale = true; fireStale(); } } public bool isStale() { // recalculate list if dirty, to ensure staleness is correct. getList(); return stale; } public Object getElementType() { return elementType; } public synchronized void addChangeListener(IChangeListener listener) { super.addChangeListener(listener); // If somebody is listening, we need to make sure we attach our own // listeners computeListForListeners(); } public synchronized void addListChangeListener(IListChangeListener listener) { super.addListChangeListener(listener); // If somebody is listening, we need to make sure we attach our own // listeners computeListForListeners(); } private void computeListForListeners() { // 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. getRealm().exec(new class() Runnable { public void run() { if (dependencies is null) { // We are not currently listening. // But someone is listening for changes. Call getValue() // to make sure we start listening to the observables we // depend on. getList(); } } }); } public synchronized void dispose() { stopListening(); super.dispose(); } }