blob: 346051b284f340a0376fc117b299915dc3f1722f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 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 (bug 175735)
* Matthew Hall - bug 262407
******************************************************************************/
package org.eclipse.core.databinding.observable.value;
import java.util.Collection;
import java.util.Iterator;
import org.eclipse.core.databinding.observable.ChangeEvent;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.IStaleListener;
import org.eclipse.core.databinding.observable.StaleEvent;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.internal.databinding.observable.Util;
/**
* @param <T>
* @since 1.2
*/
public abstract class DuplexingObservableValue<T> extends
AbstractObservableValue<T> {
/**
* Returns a DuplexingObservableValue implementation with predefined values
* to use if the list is empty or contains multiple different values.
*
* @param <T>
*
* @param target
* the observable list
* @param emptyValue
* the value to use when the target list is empty
* @param multiValue
* the value to use when the target list contains multiple values
* that are not equivalent to each other.
* @return a DuplexingObservableValue implementation with predefined values
* to use if the list is empty or contains multiple different
* values.
*/
public static <T> DuplexingObservableValue<T> withDefaults(
IObservableList<T> target, final T emptyValue, final T multiValue) {
return new DuplexingObservableValue<T>(target) {
protected T coalesceElements(Collection<T> elements) {
if (elements.isEmpty())
return emptyValue;
Iterator<T> it = elements.iterator();
T first = it.next();
while (it.hasNext())
if (!Util.equals(first, it.next()))
return multiValue;
return first;
}
};
}
private IObservableList<T> target;
private final Object valueType;
private boolean dirty = true;
private boolean updating = false;
private T cachedValue = null; // applicable only while hasListener()
private IChangeListener privateChangeInterface;
private IStaleListener privateStaleInterface;
/**
* @param target
*/
public DuplexingObservableValue(IObservableList<T> target) {
this(target, target.getElementType());
}
/**
* @param target
* @param valueType
*/
public DuplexingObservableValue(IObservableList<T> target, Object valueType) {
super(target.getRealm());
this.target = target;
this.valueType = valueType;
}
private class PrivateChangeInterface implements IChangeListener {
public void handleChange(ChangeEvent event) {
if (!updating)
makeDirty();
}
}
private class PrivateStaleInterface implements IStaleListener {
public void handleStale(StaleEvent staleEvent) {
if (!dirty) {
fireStale();
}
}
}
protected void firstListenerAdded() {
if (privateChangeInterface == null)
privateChangeInterface = new PrivateChangeInterface();
if (privateStaleInterface == null)
privateStaleInterface = new PrivateStaleInterface();
target.addChangeListener(privateChangeInterface);
target.addStaleListener(privateStaleInterface);
}
protected void lastListenerRemoved() {
target.removeChangeListener(privateChangeInterface);
target.removeStaleListener(privateStaleInterface);
}
protected final void makeDirty() {
if (hasListeners() && !dirty) {
dirty = true;
// copy the old value
final T oldValue = cachedValue;
// Fire the "dirty" event. This implementation recomputes the new
// value lazily.
fireValueChange(new ValueDiff<T>() {
public T getOldValue() {
return oldValue;
}
public T getNewValue() {
return getValue();
}
});
}
}
public boolean isStale() {
getValue();
return target.isStale();
}
protected T doGetValue() {
if (!hasListeners())
return coalesceElements(target);
if (dirty) {
cachedValue = coalesceElements(target);
dirty = false;
if (target.isStale())
fireStale();
}
return cachedValue;
}
protected abstract T coalesceElements(Collection<T> elements);
protected void doSetValue(T value) {
final T oldValue = cachedValue;
boolean wasUpdating = updating;
try {
updating = true;
for (int i = 0; i < target.size(); i++)
target.set(i, value);
} finally {
updating = wasUpdating;
}
// Fire the "dirty" event. This implementation recomputes the new
// value lazily.
if (hasListeners()) {
fireValueChange(new ValueDiff<T>() {
public T getOldValue() {
return oldValue;
}
public T getNewValue() {
return getValue();
}
});
}
}
public Object getValueType() {
return valueType;
}
public synchronized void addChangeListener(IChangeListener listener) {
super.addChangeListener(listener);
// If somebody is listening, we need to make sure we attach our own
// listeners
computeValueForListeners();
}
public synchronized void addValueChangeListener(
IValueChangeListener<T> listener) {
super.addValueChangeListener(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() {
public void run() {
// 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 dispose() {
if (privateChangeInterface != null && target != null) {
target.removeChangeListener(privateChangeInterface);
}
if (privateStaleInterface != null && target != null) {
target.removeStaleListener(privateStaleInterface);
}
target = null;
privateChangeInterface = null;
privateStaleInterface = null;
super.dispose();
}
}