/******************************************************************************* * Copyright (c) 2005, 2016 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 - bugs 116920, 147515 * Matthew Hall - bug 274081 * Stefan Xenos <sxenos@gmail.com> - Bug 335792 * Simon Scholz <simon.scholz@vogella.com> - Bug 488145 *******************************************************************************/ package org.eclipse.core.databinding.observable.value; import java.util.function.Supplier; 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; import org.eclipse.core.databinding.observable.list.IObservableList; /** * A Lazily calculated value that automatically computes and registers listeners * on its dependencies as long as all of its dependencies are * {@link IObservable} objects. Any change to one of the observable dependencies * causes the value to be recomputed. * <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> * <p> * Example: compute the sum of all elements in an {@link IObservableList} < * {@link Integer} >. * </p> * * <pre> * final IObservableList addends = WritableValue.withValueType(Integer.TYPE); * addends.add(Integer.valueOf(0)); * addends.add(Integer.valueOf(1)); * addends.add(Integer.valueOf(2)); * * IObservableValue sum = new ComputedValue() { * protected Object calculate() { * int sum = 0; * for (Iterator it = addends.iterator(); it.hasNext();) { * Integer addend = (Integer) it.next(); * sum += addend.intValue(); * } * return sum; * } * }; * * System.out.println(sum.getValue()); // => 3 * * addends.add(Integer.valueOf(10)); * System.out.println(sum.getValue()); // => 13 * </pre> * * @param <T> * the type of value being observed * * @since 1.0 */ public abstract class ComputedValue<T> extends AbstractObservableValue<T> { private boolean dirty = true; private boolean stale = false; private T 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; /** * Factory method to create {@link ComputedValue} objects in an easy manner. * <br/> * <br/> * Example observing the size of an {@link IObservableList}: * * <pre> * IObservableValue<Integer> listSizeObservable = ComputedValue.create(() -> observableList.size()); * </pre> * * @param supplier * {@link Supplier}, whose {@link Supplier#get()} method is a * TrackedGetter. See * {@link ObservableTracker#getterCalled(IObservable)} for * details. * @return {@link ComputedValue} whose value is computed using the given * {@link Supplier}. * @since 1.6 */ public static <T> IObservableValue<T> create(Supplier<T> supplier) { return new ComputedValue<T>() { @Override protected T calculate() { return supplier.get(); } }; } /** * */ public ComputedValue() { this(Realm.getDefault(), null); } /** * @param valueType * can be <code>null</code> */ public ComputedValue(Object valueType) { this(Realm.getDefault(), valueType); } /** * @param realm * */ public ComputedValue(Realm realm) { this(realm, null); } /** * @param realm * @param valueType */ public ComputedValue(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 implements Runnable, IChangeListener, IStaleListener { @Override public void run() { cachedValue = calculate(); } @Override public void handleStale(StaleEvent event) { if (!dirty && !stale) { stale = true; fireStale(); } } @Override public void handleChange(ChangeEvent event) { makeDirty(); } } private PrivateInterface privateInterface = new PrivateInterface(); private Object valueType; @Override protected final T 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 (IObservable observable : newDependencies) { // 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. Any * dependencies used to calculate the value must be {@link IObservable}, and * implementers must use one of the interface methods tagged TrackedGetter * for ComputedValue to recognize it as a dependency. * * @return the object's value */ protected abstract T calculate(); protected final void makeDirty() { if (!dirty) { dirty = true; stopListening(); // copy the old value final T oldValue = cachedValue; // Fire the "dirty" event. This implementation recomputes the new // value lazily. fireValueChange(new ValueDiff<T>() { @Override public T getOldValue() { return oldValue; } @Override public T getNewValue() { return getValue(); } }); } } /** * */ private void stopListening() { // Stop listening for dependency changes. if (dependencies != null) { for (IObservable observable : dependencies) { observable.removeChangeListener(privateInterface); observable.removeStaleListener(privateInterface); } dependencies = null; } } @Override public boolean isStale() { // we need to recompute, otherwise staleness wouldn't mean anything getValue(); return stale; } @Override public Object getValueType() { return valueType; } // this method exists here so that we can call it from the runnable below. /** * @since 1.1 */ @Override protected boolean hasListeners() { return super.hasListeners(); } @Override 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 Runnable() { @Override public void run() { if (dependencies == 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(); } } } }); } @Override public synchronized void addValueChangeListener( IValueChangeListener<? super T> listener) { super.addValueChangeListener(listener); // If somebody is listening, we need to make sure we attach our own // listeners computeValueForListeners(); } @Override public synchronized void dispose() { super.dispose(); stopListening(); } }