/******************************************************************************* * Copyright (c) 2012 Pivotal Software, Inc. * 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: * Pivotal Software, Inc. - initial API and implementation *******************************************************************************/ package org.springsource.ide.eclipse.commons.livexp.core; import org.eclipse.core.runtime.ListenerList; import org.springsource.ide.eclipse.commons.livexp.ui.Disposable; import com.google.common.base.Function; /** * A 'live' expression is something that conceptually one would like to think of as an expression * that returns a value. However, this expression provides a listener-style interface so that * interested parties can subscribe to be notified when the value of the expression changes. */ public abstract class LiveExpression<V> implements Disposable, OnDispose { public static final LiveExpression<Boolean> TRUE = constant(true); public static final LiveExpression<Boolean> FALSE = constant(false); private ListenerList fListeners = new ListenerList(); private ListenerList fDisposeHandlers = new ListenerList(); /** * An optional 'owner' for this expression. Useful when expressions * are part of a model, in which case listeners may want to discover * the owner of a LiveExpression to do something to/with the owner * (e.g. redraw it) when something inside it changes. */ protected Object owner = null; /** * The last computed value of the expression. */ protected V value; public LiveExpression(V initialValue, Object owner) { this.value = initialValue; this.owner = owner; } public LiveExpression(V initialValue) { this(initialValue, null); } public LiveExpression() { this(null); } /** * Clients may call this method to request a recomputation of the expression's value from its inputs. */ public void refresh() { //V oldValue = value; boolean changed = false; synchronized (this) { V newValue = compute(); if (!equals(newValue, value)) { value = newValue; changed = true; } } if (changed) { changed(); } } /** * Implementation of value equals that works if either one of the values is null. */ private static <V> boolean equals(V a, V b) { if (a==null||b==null) { return a==b; } else { return a.equals(b); } } /** * Declare that this liveExpression depends on some other live expression. This ensures * that this expression will be refreshed if the value of the other expression changes. */ public <O> LiveExpression<V> dependsOn(final LiveExpression<O> other) { final ValueListener<O> listener = new ValueListener<O>() { public void gotValue(LiveExpression<O> exp, O value) { refresh(); } }; other.addListener(listener); onDispose(new DisposeListener() { public void disposed(Disposable disposed) { other.removeListener(listener); } }); return this; }; protected abstract V compute(); protected void changed() { if (fListeners!=null) { Object[] listeners = fListeners.getListeners(); for (Object _l : listeners) { @SuppressWarnings("unchecked") ValueListener<V> l = (ValueListener<V>) _l; l.gotValue(this, value); } } } /** * Retrieves the current (cached) value of the expression. */ public V getValue() { return value; } public void addListener(ValueListener<V> l) { fListeners.add(l); l.gotValue(this, value); } public void removeListener(ValueListener<V> l) { if (fListeners!=null) { fListeners.remove(l); } } public static <V> LiveExpression<V> constant(final V value) { //TODO: Constant expression can be implemented more efficiently they do not need really any of the // super class infrastructure since the value of a constant can never change. return new LiveExpression<V>(value) { @Override protected V compute() { return value; } @Override public void addListener(ValueListener<V> l) { l.gotValue(this, value); //Beyond the initial notification ... we ignore listeners... we will never notify again since //constants can't change } @Override public void removeListener(ValueListener<V> l) { //Ignore all listeners we will never notify anyone since //constants can't change } /* (non-Javadoc) * @see org.springsource.ide.eclipse.gradle.core.util.expression.LiveExpression#refresh() */ @Override public void refresh() { //Ignore all refreshes... no need to refresh anything since //constants can't change } }; } /** * Filter liveexp value to elimante values that are not instances of a given class. * <p> * When target expression has a value is not an instance of the class then the * resulting expression's value is 'null' otherwise its value is the same as the * target expression. */ public <T> LiveExpression<T> filter(final Class<T> klass) { final LiveExpression<V> target = this; return new LiveExpression<T>() { { dependsOn(target); } @SuppressWarnings("unchecked") @Override protected T compute() { V input = target.getValue(); if (klass.isInstance(input)) { return (T) input; } else { return null; } } }; } public <R> LiveExpression<R> apply(final Function<V,R> fun) { final LiveExpression<V> target = this; LiveExpression<R> result = new LiveExpression<R>() { { dependsOn(target); } @Override protected R compute() { return fun.apply(target.getValue()); } }; return result; } public Object getOwner() { return owner; } @SuppressWarnings("unchecked") public <T> T getOwner(Class<T> cls) { if (owner!=null && cls.isAssignableFrom(owner.getClass())) { return (T) owner; } return null; } @Override public void dispose() { if (fDisposeHandlers!=null) { for (Object _handler : fDisposeHandlers.getListeners()) { DisposeListener handler = (DisposeListener) _handler; handler.disposed(this); } fDisposeHandlers = null; } fListeners = null; } @Override public void onDispose(DisposeListener listener) { fDisposeHandlers.add(listener); } public void setOwner(Object owner) { this.owner = owner; } }