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();
    }
}