# HG changeset patch
# User Frank Benoit
+ * This interface is not meant to be implemented by clients.
+ *
+This package provides classes that can be used to observe objects that conform to the JavaBean specification for bound properties.
+ * Implementations must not manage listeners themselves, listener management
+ * must be delegated to a private instance of type {@link ChangeSupport} if it
+ * is not inherited from {@link AbstractObservable}.
+ *
+ * Because observables can only be accessed from within one realm, and they
+ * always fire events on that realm, their state can be observed in an
+ * incremental way. It is always safe to call getters of an observable from
+ * within a change listener attached to that observable.
+ * PropertyChangeEvents
. This is done to
+ * provide the same behavior as is expected from arrays as specified in the
+ * bean spec in section 7.2.
+ *
+ * @param realm
+ * the realm
+ * @param bean
+ * the bean object
+ * @param propertyName
+ * the name of the property
+ * @param elementType
+ * type of the elements in the list. If null
and
+ * the property is an array the type will be inferred. If
+ * null
and the property type cannot be inferred
+ * element type will be null
.
+ * @return an observable list tracking the collection-typed named property
+ * of the given bean object
+ */
+ public static IObservableList observeList(Realm realm, Object bean,
+ String propertyName, Class elementType) {
+ PropertyDescriptor propertyDescriptor = getPropertyDescriptor(bean
+ .getClass(), propertyName);
+ elementType = getCollectionElementType(elementType, propertyDescriptor);
+
+ return new JavaBeanObservableList(realm, bean, propertyDescriptor,
+ elementType);
+ }
+
+ /**
+ * Returns an observable set in the given realm tracking the
+ * collection-typed named property of the given bean object
+ *
+ * @param realm
+ * the realm
+ * @param bean
+ * the bean object
+ * @param propertyName
+ * the name of the property
+ * @return an observable set tracking the collection-typed named property of
+ * the given bean object
+ */
+ public static IObservableSet observeSet(Realm realm, Object bean,
+ String propertyName) {
+ return observeSet(realm, bean, propertyName, null);
+ }
+
+ /**
+ * Returns a factory for creating obervable values tracking the given
+ * property of a particular bean object
+ *
+ * @param realm
+ * the realm to use
+ * @param propertyName
+ * the name of the property
+ * @return an observable value factory
+ */
+ public static IObservableFactory valueFactory(final Realm realm,
+ final String propertyName) {
+ return new class() IObservableFactory {
+ public IObservable createObservable(Object target) {
+ return observeValue(realm, target, propertyName);
+ }
+ };
+ }
+
+ /**
+ * Returns a factory for creating obervable lists tracking the given
+ * property of a particular bean object
+ *
+ * @param realm
+ * the realm to use
+ * @param propertyName
+ * the name of the property
+ * @param elementType
+ * @return an observable list factory
+ */
+ public static IObservableFactory listFactory(final Realm realm,
+ final String propertyName, final Class elementType) {
+ return new class() IObservableFactory {
+ public IObservable createObservable(Object target) {
+ return observeList(realm, target, propertyName, elementType);
+ }
+ };
+ }
+
+ /**
+ * Returns a factory for creating obervable sets tracking the given property
+ * of a particular bean object
+ *
+ * @param realm
+ * the realm to use
+ * @param propertyName
+ * the name of the property
+ * @return an observable set factory
+ */
+ public static IObservableFactory setFactory(final Realm realm,
+ final String propertyName) {
+ return new class() IObservableFactory {
+ public IObservable createObservable(Object target) {
+ return observeSet(realm, target, propertyName);
+ }
+ };
+ }
+
+ /**
+ * Helper method for
+ * MasterDetailObservables.detailValue(master, valueFactory(realm,
+ propertyName), propertyType)
+ *
+ * @param realm
+ * @param master
+ * @param propertyName
+ * @param propertyType
+ * can be null
+ * @return an observable value that tracks the current value of the named
+ * property for the current value of the master observable value
+ *
+ * @see MasterDetailObservables
+ */
+ public static IObservableValue observeDetailValue(Realm realm,
+ IObservableValue master, String propertyName, Class propertyType) {
+
+ IObservableValue value = MasterDetailObservables.detailValue(master,
+ valueFactory(realm, propertyName), propertyType);
+ BeanObservableValueDecorator decorator = new BeanObservableValueDecorator(
+ value, master, getValueTypePropertyDescriptor(master,
+ propertyName));
+
+ return decorator;
+ }
+
+ /**
+ * Helper method for
+ * MasterDetailObservables.detailValue(master, valueFactory(realm,
+ * propertyName), propertyType)
.
+ * This method returns an {@link IBeanObservable} with a
+ * {@link PropertyDescriptor} based on the given master type and property
+ * name.
+ *
+ * @param realm
+ * the realm
+ * @param master
+ * the master observable value, for example tracking the
+ * selection in a list
+ * @param masterType
+ * the type of the master observable value
+ * @param propertyName
+ * the property name
+ * @param propertyType
+ * can be null
+ * @return an observable value that tracks the current value of the named
+ * property for the current value of the master observable value
+ *
+ * @see MasterDetailObservables
+ * @since 1.1
+ */
+ public static IObservableValue observeDetailValue(Realm realm,
+ IObservableValue master, Class masterType, String propertyName, Class propertyType) {
+ Assert.isNotNull(masterType, "masterType cannot be null"); //$NON-NLS-1$
+ IObservableValue value = MasterDetailObservables.detailValue(master,
+ valueFactory(realm, propertyName), propertyType);
+ BeanObservableValueDecorator decorator = new BeanObservableValueDecorator(
+ value, master, getPropertyDescriptor(masterType,
+ propertyName));
+
+ return decorator;
+ }
+
+ /**
+ * Helper method for
+ * MasterDetailObservables.detailList(master, listFactory(realm,
+ propertyName, propertyType), propertyType)
+ *
+ * @param realm
+ * @param master
+ * @param propertyName
+ * @param propertyType
+ * can be null
+ * @return an observable list that tracks the named property for the current
+ * value of the master observable value
+ *
+ * @see MasterDetailObservables
+ */
+ public static IObservableList observeDetailList(Realm realm,
+ IObservableValue master, String propertyName, Class propertyType) {
+ IObservableList observableList = MasterDetailObservables.detailList(
+ master, listFactory(realm, propertyName, propertyType),
+ propertyType);
+ BeanObservableListDecorator decorator = new BeanObservableListDecorator(
+ observableList, master, getValueTypePropertyDescriptor(master,
+ propertyName));
+
+ return decorator;
+ }
+
+ /**
+ * Helper method for
+ * MasterDetailObservables.detailSet(master, setFactory(realm,
+ propertyName), propertyType)
+ *
+ * @param realm
+ * @param master
+ * @param propertyName
+ * @param propertyType
+ * can be null
+ * @return an observable set that tracks the named property for the current
+ * value of the master observable value
+ *
+ * @see MasterDetailObservables
+ */
+ public static IObservableSet observeDetailSet(Realm realm,
+ IObservableValue master, String propertyName, Class propertyType) {
+
+ IObservableSet observableSet = MasterDetailObservables.detailSet(
+ master, setFactory(realm, propertyName, propertyType),
+ propertyType);
+ BeanObservableSetDecorator decorator = new BeanObservableSetDecorator(
+ observableSet, master, getValueTypePropertyDescriptor(master,
+ propertyName));
+
+ return decorator;
+ }
+
+ /**
+ * Helper method for
+ * MasterDetailObservables.detailMap(master, mapFactory(realm, propertyName))
+ *
+ * @param realm
+ * the realm
+ * @param master
+ * @param propertyName
+ * @return an observable map that tracks the map-type named property for the
+ * current value of the master observable value.
+ * @since 1.1
+ */
+ public static IObservableMap observeDetailMap(Realm realm,
+ IObservableValue master, String propertyName) {
+ IObservableMap observableMap = MasterDetailObservables.detailMap(
+ master, mapPropertyFactory(realm, propertyName));
+ BeanObservableMapDecorator decorator = new BeanObservableMapDecorator(
+ observableMap, master, getValueTypePropertyDescriptor(master,
+ propertyName));
+ return decorator;
+ }
+
+ /**
+ * @param realm
+ * @param bean
+ * @param propertyName
+ * @param elementType
+ * can be null
+ * @return an observable set that tracks the current value of the named
+ * property for given bean object
+ */
+ public static IObservableSet observeSet(Realm realm, Object bean,
+ String propertyName, Class elementType) {
+ PropertyDescriptor propertyDescriptor = getPropertyDescriptor(bean
+ .getClass(), propertyName);
+ elementType = getCollectionElementType(elementType, propertyDescriptor);
+
+ return new JavaBeanObservableSet(realm, bean, propertyDescriptor,
+ elementType);
+ }
+
+ /**
+ * @param realm
+ * @param propertyName
+ * @param elementType
+ * can be null
+ * @return an observable set factory for creating observable sets
+ */
+ public static IObservableFactory setFactory(final Realm realm,
+ final String propertyName, final Class elementType) {
+ return new class() IObservableFactory {
+ public IObservable createObservable(Object target) {
+ return observeSet(realm, target, propertyName, elementType);
+ }
+ };
+ }
+
+ /**
+ * Returns a factory for creating an observable map. The factory, when
+ * provided with an {@link IObservableSet}, will create an
+ * {@link IObservableMap} in the same realm as the underlying set that
+ * tracks the current values of the named property for the beans in the
+ * given set.
+ *
+ * @param beanClass
+ * the common base type of bean objects that may be in the set
+ * @param propertyName
+ * the name of the property
+ * @return a factory for creating {@link IObservableMap} objects
+ *
+ * @since 1.1
+ */
+ public static IObservableFactory setToMapFactory(final Class beanClass, final String propertyName) {
+ return new class() IObservableFactory {
+ public IObservable createObservable(Object target) {
+ return observeMap(cast(IObservableSet) target, beanClass, propertyName);
+ }
+ };
+ }
+
+ /**
+ * Returns a factory for creating an observable map. The factory, when
+ * provided with a bean object, will create an {@link IObservableMap} in the
+ * given realm that tracks the map-typed named property for the specified
+ * bean.
+ *
+ * @param realm
+ * the realm assigned to observables created by the returned
+ * factory.
+ * @param propertyName
+ * the name of the property
+ * @return a factory for creating {@link IObservableMap} objects.
+ * @since 1.1
+ */
+ public static IObservableFactory mapPropertyFactory(final Realm realm,
+ final String propertyName) {
+ return new class() IObservableFactory {
+ public IObservable createObservable(Object target) {
+ return observeMap(realm, target, propertyName);
+ }
+ };
+ }
+
+ /**
+ * @param elementType
+ * can be null
+ * @param propertyDescriptor
+ * @return type of the items in a collection/array property
+ */
+ /*package*/ static Class getCollectionElementType(Class elementType,
+ PropertyDescriptor propertyDescriptor) {
+ if (elementType is null) {
+ Class propertyType = propertyDescriptor.getPropertyType();
+ elementType = propertyType.isArray() ? propertyType
+ .getComponentType() : Object.class;
+ }
+
+ return elementType;
+ }
+
+ /**
+ * @param observable
+ * @param propertyName
+ * @return property descriptor or null
+ */
+ /* package*/ static PropertyDescriptor getValueTypePropertyDescriptor(
+ IObservableValue observable, String propertyName) {
+ return (observable.getValueType() !is null) ? getPropertyDescriptor(
+ cast(Class) observable.getValueType(), propertyName) : null;
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/IBeanObservable.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/IBeanObservable.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Brad Reynolds 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:
+ * Brad Reynolds - initial API and implementation
+ * Brad Reynolds - bug 147515
+ ******************************************************************************/
+
+package org.eclipse.core.databinding.beans;
+
+import java.beans.PropertyDescriptor;
+
+import org.eclipse.core.databinding.observable.IObserving;
+
+/**
+ * Provides access to details of bean observables.
+ * null
if the runtime time information was not
+ * provided on construction of the observable
+ */
+ public PropertyDescriptor getPropertyDescriptor();
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/PojoObservables.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/PojoObservables.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,433 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2008 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
+ * Matthew Hall - bug 221704
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.beans;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyDescriptor;
+
+import org.eclipse.core.databinding.observable.IObservable;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.list.IObservableList;
+import org.eclipse.core.databinding.observable.map.IObservableMap;
+import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
+import org.eclipse.core.databinding.observable.masterdetail.MasterDetailObservables;
+import org.eclipse.core.databinding.observable.set.IObservableSet;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.internal.databinding.beans.BeanObservableListDecorator;
+import org.eclipse.core.internal.databinding.beans.BeanObservableMapDecorator;
+import org.eclipse.core.internal.databinding.beans.BeanObservableSetDecorator;
+import org.eclipse.core.internal.databinding.beans.BeanObservableValueDecorator;
+import org.eclipse.core.internal.databinding.beans.JavaBeanObservableList;
+import org.eclipse.core.internal.databinding.beans.JavaBeanObservableMap;
+import org.eclipse.core.internal.databinding.beans.JavaBeanObservableSet;
+import org.eclipse.core.internal.databinding.beans.JavaBeanObservableValue;
+import org.eclipse.core.internal.databinding.beans.JavaBeanPropertyObservableMap;
+
+/**
+ * A factory for creating observable objects for POJOs (plain old java objects)
+ * that conform to idea of an object with getters and setters but does not
+ * provide {@link PropertyChangeEvent property change events} on change. This
+ * factory is identical to {@link BeansObservables} except for this fact.
+ *
+ * @since 1.1
+ */
+final public class PojoObservables {
+
+ /**
+ * Returns an observable value in the default realm tracking the current
+ * value of the named property of the given pojo.
+ *
+ * @param pojo
+ * the object
+ * @param propertyName
+ * the name of the property
+ * @return an observable value tracking the current value of the named
+ * property of the given pojo
+ */
+ public static IObservableValue observeValue(Object pojo, String propertyName) {
+ return observeValue(Realm.getDefault(), pojo, propertyName);
+ }
+
+ /**
+ * Returns an observable value in the given realm tracking the current value
+ * of the named property of the given pojo.
+ *
+ * @param realm
+ * the realm
+ * @param pojo
+ * the object
+ * @param propertyName
+ * the name of the property
+ * @return an observable value tracking the current value of the named
+ * property of the given pojo
+ */
+ public static IObservableValue observeValue(Realm realm, Object pojo,
+ String propertyName) {
+
+ PropertyDescriptor descriptor = BeansObservables.getPropertyDescriptor(
+ pojo.getClass(), propertyName);
+ return new JavaBeanObservableValue(realm, pojo, descriptor, false);
+ }
+
+ /**
+ * Returns an observable map in the default realm tracking the current
+ * values of the named property for the pojos in the given set.
+ *
+ * @param domain
+ * the set of pojo objects
+ * @param pojoClass
+ * the common base type of pojo objects that may be in the set
+ * @param propertyName
+ * the name of the property
+ * @return an observable map tracking the current values of the named
+ * property for the pojos in the given domain set
+ */
+ public static IObservableMap observeMap(IObservableSet domain,
+ Class pojoClass, String propertyName) {
+ PropertyDescriptor descriptor = BeansObservables.getPropertyDescriptor(
+ pojoClass, propertyName);
+ return new JavaBeanObservableMap(domain, descriptor, false);
+ }
+
+ /**
+ * Returns an array of observable maps in the default realm tracking the
+ * current values of the named propertys for the pojos in the given set.
+ *
+ * @param domain
+ * the set of objects
+ * @param pojoClass
+ * the common base type of objects that may be in the set
+ * @param propertyNames
+ * the array of property names
+ * @return an array of observable maps tracking the current values of the
+ * named propertys for the pojos in the given domain set
+ */
+ public static IObservableMap[] observeMaps(IObservableSet domain,
+ Class pojoClass, String[] propertyNames) {
+ IObservableMap[] result = new IObservableMap[propertyNames.length];
+ for (int i = 0; i < propertyNames.length; i++) {
+ result[i] = observeMap(domain, pojoClass, propertyNames[i]);
+ }
+ return result;
+ }
+
+ /**
+ * Returns an observable map in the given realm tracking the map-typed named
+ * property of the given pojo object.
+ *
+ * @param realm
+ * the realm
+ * @param pojo
+ * the pojo object
+ * @param propertyName
+ * the name of the property
+ * @return an observable map tracking the map-typed named property of the
+ * given pojo object
+ */
+ public static IObservableMap observeMap(Realm realm, Object pojo,
+ String propertyName) {
+ PropertyDescriptor descriptor = BeansObservables.getPropertyDescriptor(
+ pojo.getClass(), propertyName);
+ return new JavaBeanPropertyObservableMap(realm, pojo, descriptor, false);
+ }
+
+ /**
+ * Returns an observable list in the given realm tracking the
+ * collection-typed named property of the given pojo object. The returned
+ * list is mutable.
+ *
+ * @param realm
+ * the realm
+ * @param pojo
+ * the object
+ * @param propertyName
+ * the name of the collection-typed property
+ * @return an observable list tracking the collection-typed named property
+ * of the given pojo object
+ * @see #observeList(Realm, Object, String, Class)
+ */
+ public static IObservableList observeList(Realm realm, Object pojo,
+ String propertyName) {
+ return observeList(realm, pojo, propertyName, null);
+ }
+
+ /**
+ * Returns an observable list in the given realm tracking the
+ * collection-typed named property of the given bean object. The returned
+ * list is mutable. When an item is added or removed the setter is invoked
+ * for the list on the parent bean to provide notification to other
+ * listeners via PropertyChangeEvents
. This is done to
+ * provide the same behavior as is expected from arrays as specified in the
+ * bean spec in section 7.2.
+ *
+ * @param realm
+ * the realm
+ * @param pojo
+ * the bean object
+ * @param propertyName
+ * the name of the property
+ * @param elementType
+ * type of the elements in the list. If null
and
+ * the property is an array the type will be inferred. If
+ * null
and the property type cannot be inferred
+ * element type will be null
.
+ * @return an observable list tracking the collection-typed named property
+ * of the given bean object
+ */
+ public static IObservableList observeList(Realm realm, Object pojo,
+ String propertyName, Class elementType) {
+ PropertyDescriptor propertyDescriptor = BeansObservables
+ .getPropertyDescriptor(pojo.getClass(), propertyName);
+ elementType = BeansObservables.getCollectionElementType(elementType,
+ propertyDescriptor);
+
+ return new JavaBeanObservableList(realm, pojo, propertyDescriptor,
+ elementType, false);
+ }
+
+ /**
+ * Returns an observable set in the given realm tracking the
+ * collection-typed named property of the given pojo object.
+ *
+ * @param realm
+ * the realm
+ * @param pojo
+ * the pojo object
+ * @param propertyName
+ * the name of the property
+ * @return an observable set tracking the collection-typed named property of
+ * the given pojo object
+ */
+ public static IObservableSet observeSet(Realm realm, Object pojo,
+ String propertyName) {
+ return observeSet(realm, pojo, propertyName, null);
+ }
+
+ /**
+ * @param realm
+ * @param pojo
+ * @param propertyName
+ * @param elementType
+ * can be null
+ * @return an observable set that tracks the current value of the named
+ * property for given pojo object
+ */
+ public static IObservableSet observeSet(Realm realm, Object pojo,
+ String propertyName, Class elementType) {
+ PropertyDescriptor propertyDescriptor = BeansObservables
+ .getPropertyDescriptor(pojo.getClass(), propertyName);
+ elementType = BeansObservables.getCollectionElementType(elementType,
+ propertyDescriptor);
+
+ return new JavaBeanObservableSet(realm, pojo, propertyDescriptor,
+ elementType, false);
+ }
+
+ /**
+ * Returns a factory for creating obervable values tracking the given
+ * property of a particular pojo object
+ *
+ * @param realm
+ * the realm to use
+ * @param propertyName
+ * the name of the property
+ * @return an observable value factory
+ */
+ public static IObservableFactory valueFactory(final Realm realm,
+ final String propertyName) {
+ return new class() IObservableFactory {
+ public IObservable createObservable(Object target) {
+ return observeValue(realm, target, propertyName);
+ }
+ };
+ }
+
+ /**
+ * Returns a factory for creating obervable lists tracking the given
+ * property of a particular pojo object
+ *
+ * @param realm
+ * the realm to use
+ * @param propertyName
+ * the name of the property
+ * @param elementType
+ * @return an observable list factory
+ */
+ public static IObservableFactory listFactory(final Realm realm,
+ final String propertyName, final Class elementType) {
+ return new class() IObservableFactory {
+ public IObservable createObservable(Object target) {
+ return observeList(realm, target, propertyName, elementType);
+ }
+ };
+ }
+
+ /**
+ * Returns a factory for creating obervable sets tracking the given property
+ * of a particular pojo object
+ *
+ * @param realm
+ * the realm to use
+ * @param propertyName
+ * the name of the property
+ * @return an observable set factory
+ */
+ public static IObservableFactory setFactory(final Realm realm,
+ final String propertyName) {
+ return new class() IObservableFactory {
+ public IObservable createObservable(Object target) {
+ return observeSet(realm, target, propertyName);
+ }
+ };
+ }
+
+ /**
+ * @param realm
+ * @param propertyName
+ * @param elementType
+ * can be null
+ * @return an observable set factory for creating observable sets
+ */
+ public static IObservableFactory setFactory(final Realm realm,
+ final String propertyName, final Class elementType) {
+ return new class() IObservableFactory {
+ public IObservable createObservable(Object target) {
+ return observeSet(realm, target, propertyName, elementType);
+ }
+ };
+ }
+
+ /**
+ * Returns a factory for creating an observable map. The factory, when
+ * provided with a pojo object, will create an {@link IObservableMap} in the
+ * given realm that tracks the map-typed named property for the specified
+ * pojo.
+ *
+ * @param realm
+ * the realm assigned to observables created by the returned
+ * factory.
+ * @param propertyName
+ * the name of the property
+ * @return a factory for creating {@link IObservableMap} objects.
+ */
+ public static IObservableFactory mapPropertyFactory(final Realm realm,
+ final String propertyName) {
+ return new class() IObservableFactory {
+ public IObservable createObservable(Object target) {
+ return observeMap(realm, target, propertyName);
+ }
+ };
+ }
+
+ /**
+ * Helper method for
+ * MasterDetailObservables.detailValue(master, valueFactory(realm,
+ propertyName), propertyType)
+ *
+ * @param realm
+ * @param master
+ * @param propertyName
+ * @param propertyType
+ * can be null
+ * @return an observable value that tracks the current value of the named
+ * property for the current value of the master observable value
+ *
+ * @see MasterDetailObservables
+ */
+ public static IObservableValue observeDetailValue(Realm realm,
+ IObservableValue master, String propertyName, Class propertyType) {
+
+ IObservableValue value = MasterDetailObservables.detailValue(master,
+ valueFactory(realm, propertyName), propertyType);
+ BeanObservableValueDecorator decorator = new BeanObservableValueDecorator(
+ value, master, BeansObservables.getValueTypePropertyDescriptor(
+ master, propertyName));
+
+ return decorator;
+ }
+
+ /**
+ * Helper method for
+ * MasterDetailObservables.detailList(master, listFactory(realm,
+ propertyName, propertyType), propertyType)
+ *
+ * @param realm
+ * @param master
+ * @param propertyName
+ * @param propertyType
+ * can be null
+ * @return an observable list that tracks the named property for the current
+ * value of the master observable value
+ *
+ * @see MasterDetailObservables
+ */
+ public static IObservableList observeDetailList(Realm realm,
+ IObservableValue master, String propertyName, Class propertyType) {
+ IObservableList observableList = MasterDetailObservables.detailList(
+ master, listFactory(realm, propertyName, propertyType),
+ propertyType);
+ BeanObservableListDecorator decorator = new BeanObservableListDecorator(
+ observableList, master, BeansObservables
+ .getValueTypePropertyDescriptor(master, propertyName));
+
+ return decorator;
+ }
+
+ /**
+ * Helper method for
+ * MasterDetailObservables.detailSet(master, setFactory(realm,
+ propertyName), propertyType)
+ *
+ * @param realm
+ * @param master
+ * @param propertyName
+ * @param propertyType
+ * can be null
+ * @return an observable set that tracks the named property for the current
+ * value of the master observable value
+ *
+ * @see MasterDetailObservables
+ */
+ public static IObservableSet observeDetailSet(Realm realm,
+ IObservableValue master, String propertyName, Class propertyType) {
+
+ IObservableSet observableSet = MasterDetailObservables.detailSet(
+ master, setFactory(realm, propertyName, propertyType),
+ propertyType);
+ BeanObservableSetDecorator decorator = new BeanObservableSetDecorator(
+ observableSet, master, BeansObservables
+ .getValueTypePropertyDescriptor(master, propertyName));
+
+ return decorator;
+ }
+
+ /**
+ * Helper method for
+ * MasterDetailObservables.detailMap(master, mapFactory(realm, propertyName))
+ *
+ * @param realm
+ * @param master
+ * @param propertyName
+ * @return an observable map that tracks the map-type named property for the
+ * current value of the master observable value.
+ */
+ public static IObservableMap observeDetailMap(Realm realm,
+ IObservableValue master, String propertyName) {
+ IObservableMap observableMap = MasterDetailObservables.detailMap(
+ master, mapPropertyFactory(realm, propertyName));
+ BeanObservableMapDecorator decorator = new BeanObservableMapDecorator(
+ observableMap, master, BeansObservables
+ .getValueTypePropertyDescriptor(master, propertyName));
+ return decorator;
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/package.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/package.html Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+Package Specification
+PropertyChangeEvent
events
+ * for a set of target JavaBeans
+ *
+ * @since 1.0
+ */
+public class ListenerSupport {
+
+ private Set elementsListenedTo = new HashSet();
+
+ private PropertyChangeListener listener;
+
+ private String propertyName;
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param listener is the callback that will be called
+ * when a PropertyChangeEvent
is fired on any
+ * of the target objects. Will only receive change events
+ * when the provided propertyName
changes.
+ * @param propertyName
+ */
+ public this(final PropertyChangeListener listener,
+ final String propertyName) {
+ Assert.isNotNull(listener, "Listener cannot be null"); //$NON-NLS-1$
+ Assert.isNotNull(propertyName, "Property name cannot be null"); //$NON-NLS-1$
+
+ this.propertyName = propertyName;
+ this.listener = new class() PropertyChangeListener {
+ public void propertyChange(PropertyChangeEvent evt) {
+ if (propertyName.equals(evt.getPropertyName())) {
+ listener.propertyChange(evt);
+ }
+ }
+ };
+ }
+
+ /**
+ * Start listen to target (if it supports the JavaBean property change listener pattern)
+ *
+ * @param target
+ */
+ public void hookListener(Object target) {
+ if (processListener(
+ "addPropertyChangeListener", "Could not attach listener to ", target)) { //$NON-NLS-1$ //$NON-NLS-2$
+ elementsListenedTo.add(new IdentityWrapper(target));
+ }
+ }
+
+ /**
+ * Add listeners for new targets (those this instance ofListenerSupport
does not
+ * already listen to),
+ * Stop to listen to those object that this instance listen to and is one of the object in targets
+ *
+ * @param targets
+ */
+ public void setHookTargets(Object[] targets) {
+ Set elementsToUnhook = new HashSet(elementsListenedTo);
+ if (targets!isnull) {
+ for (int i = 0; i < targets.length; i++) {
+ Object newValue = targets[i];
+ IdentityWrapper identityWrapper = new IdentityWrapper(newValue);
+ if(!elementsToUnhook.remove(identityWrapper))
+ hookListener(newValue);
+ }
+ }
+
+ for (Iterator it = elementsToUnhook.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if (o.getClass()!isIdentityWrapper.class)
+ o = new IdentityWrapper(o);
+ elementsListenedTo.remove(o);
+ unhookListener(o);
+ }
+ }
+
+ /**
+ * Stop listen to target
+ *
+ * @param target
+ */
+ public void unhookListener(Object target) {
+ if (target.getClass() is IdentityWrapper.class)
+ target = (cast(IdentityWrapper) target).unwrap();
+
+ if (processListener(
+ "removePropertyChangeListener", "Cound not remove listener from ", target)) { //$NON-NLS-1$//$NON-NLS-2$
+ elementsListenedTo.remove(new IdentityWrapper(target));
+ }
+ }
+
+
+ /**
+ *
+ */
+ public void dispose() {
+ if (elementsListenedTo!isnull) {
+ Object[] targets = elementsListenedTo.toArray();
+ for (int i = 0; i < targets.length; i++) {
+ unhookListener(targets[i]);
+ }
+ elementsListenedTo=null;
+ listener=null;
+ }
+ }
+
+ /**
+ * @return elements that were registred to
+ */
+ public Object[] getHookedTargets() {
+ Object[] targets = null;
+ if (elementsListenedTo!isnull && elementsListenedTo.size()>0) {
+ Object[] identityList = elementsListenedTo.toArray();
+ targets = new Object[identityList.length];
+ for (int i = 0; i < identityList.length; i++)
+ targets[i]=(cast(IdentityWrapper)identityList[i]).unwrap();
+ }
+ return targets;
+ }
+
+ /**
+ * Invokes the method for the provided methodName
attempting
+ * to first use the method with the property name and then the unnamed
+ * version.
+ *
+ * @param methodName
+ * either addPropertyChangeListener or
+ * removePropertyChangeListener
+ * @param message
+ * string that will be prefixed to the target in an error message
+ * @param target
+ * object to invoke the method on
+ * @return true
if the method was invoked successfully
+ */
+ private bool processListener(String methodName, String message,
+ Object target) {
+ Method method = null;
+ Object[] parameters = null;
+
+ try {
+ try {
+ method = target.getClass().getMethod(
+ methodName,
+ new Class[] { String.class,
+ PropertyChangeListener.class });
+
+ parameters = new Object[] { propertyName, listener };
+ } catch (NoSuchMethodException e) {
+ method = target.getClass().getMethod(methodName,
+ new Class[] { PropertyChangeListener.class });
+
+ parameters = new Object[] { listener };
+ }
+ } catch (SecurityException e) {
+ // ignore
+ } catch (NoSuchMethodException e) {
+ log(IStatus.WARNING, message + target, e);
+ }
+
+ if (method !is null) {
+ if (!method.isAccessible()) {
+ method.setAccessible(true);
+ }
+ try {
+ method.invoke(target, parameters);
+ return true;
+ } catch (IllegalArgumentException e) {
+ log(IStatus.WARNING, message + target, e);
+ } catch (IllegalAccessException e) {
+ log(IStatus.WARNING, message + target, e);
+ } catch (InvocationTargetException e) {
+ log(IStatus.WARNING, message + target, e);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Logs a message to the Data Binding logger.
+ */
+ private void log(int severity, String message, Throwable throwable) {
+ if cast(BeansObservables.DEBUG) {
+ Policy.getLog().log(
+ new Status(severity, Policy.JFACE_DATABINDING, IStatus.OK,
+ message, throwable));
+ }
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/AbstractObservable.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/AbstractObservable.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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 - bug 164653
+ * Matthew Hall - bug 118516
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.AssertionFailedException;
+
+/**
+ * @since 1.0
+ */
+public abstract class AbstractObservable : ChangeManager , IObservable {
+
+ /**
+ * @param realm
+ */
+ public this(Realm realm) {
+ super(realm);
+ }
+
+ public synchronized void addChangeListener(IChangeListener listener) {
+ addListener(ChangeEvent.TYPE, listener);
+ }
+
+ public synchronized void removeChangeListener(IChangeListener listener) {
+ removeListener(ChangeEvent.TYPE, listener);
+ }
+
+ public synchronized void addStaleListener(IStaleListener listener) {
+ addListener(StaleEvent.TYPE, listener);
+ }
+
+ public synchronized void removeStaleListener(IStaleListener listener) {
+ removeListener(StaleEvent.TYPE, listener);
+ }
+
+ protected void fireChange() {
+ checkRealm();
+ fireEvent(new ChangeEvent(this));
+ }
+
+ protected void fireStale() {
+ checkRealm();
+ fireEvent(new StaleEvent(this));
+ }
+
+ /**
+ *
+ */
+ public synchronized void dispose() {
+ super.dispose();
+ }
+
+ /**
+ * Asserts that the realm is the current realm.
+ *
+ * @see Realm#isCurrent()
+ * @throws AssertionFailedException if the realm is not the current realm
+ */
+ protected void checkRealm() {
+ Assert.isTrue(getRealm().isCurrent(),
+ "This operation must be run within the observable's realm"); //$NON-NLS-1$
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ChangeEvent.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ChangeEvent.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable;
+
+/**
+ * Generic change event denoting that the state of an {@link IObservable} object
+ * has changed. This event does not carry information about the kind of change
+ * that occurred.
+ *
+ * @since 1.0
+ *
+ */
+public class ChangeEvent : ObservableEvent {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -3241193109844979384L;
+ static final Object TYPE = new Object();
+
+ /**
+ * Creates a new change event object.
+ *
+ * @param source
+ * the observable that changed state
+ */
+ public this(IObservable source) {
+ super(source);
+ }
+
+ protected void dispatch(IObservablesListener listener) {
+ (cast(IChangeListener) listener).handleChange(this);
+ }
+
+ protected Object getListenerType() {
+ return TYPE;
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ChangeManager.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ChangeManager.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,162 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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
+ * Matthew Hall - bug 118516
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable;
+
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.ListenerList;
+
+/**
+ * Listener management implementation. Exposed to subclasses in form of
+ * {@link AbstractObservable} and {@link ChangeSupport}.
+ *
+ * @since 1.0
+ *
+ */
+/* package */ class ChangeManager {
+
+ ListenerList[] listenerLists = null;
+ Object listenerTypes[] = null;
+ private Realm realm;
+
+ /**
+ * @param realm
+ *
+ */
+ /* package */ ChangeManager(Realm realm) {
+ Assert.isNotNull(realm, "Realm cannot be null"); //$NON-NLS-1$
+ this.realm = realm;
+ }
+
+ /**
+ * @param listenerType
+ * @param listener
+ */
+ protected void addListener(Object listenerType,
+ IObservablesListener listener) {
+ int listenerTypeIndex = findListenerTypeIndex(listenerType);
+ if (listenerTypeIndex is -1) {
+ int length;
+ if (listenerTypes is null) {
+ length = 0;
+ listenerTypes = new Object[1];
+ listenerLists = new ListenerList[1];
+ } else {
+ length = listenerTypes.length;
+ System.arraycopy(listenerTypes, 0,
+ listenerTypes = new Object[length + 1], 0, length);
+ System
+ .arraycopy(listenerLists, 0,
+ listenerLists = new ListenerList[length + 1],
+ 0, length);
+ }
+ listenerTypes[length] = listenerType;
+ listenerLists[length] = new ListenerList();
+ bool hadListeners = hasListeners();
+ listenerLists[length].add(listener);
+ if (!hadListeners) {
+ this.firstListenerAdded();
+ }
+ return;
+ }
+ ListenerList listenerList = listenerLists[listenerTypeIndex];
+ bool hadListeners = true;
+ if (listenerList.size() is 0) {
+ hadListeners = hasListeners();
+ }
+ listenerList.add(listener);
+ if (!hadListeners) {
+ firstListenerAdded();
+ }
+ }
+
+ /**
+ * @param listenerType
+ * @param listener
+ */
+ protected void removeListener(Object listenerType,
+ IObservablesListener listener) {
+ int listenerTypeIndex = findListenerTypeIndex(listenerType);
+ if (listenerTypeIndex !is -1) {
+ listenerLists[listenerTypeIndex].remove(listener);
+ if (listenerLists[listenerTypeIndex].size() is 0) {
+ if (!hasListeners()) {
+ this.lastListenerRemoved();
+ }
+ }
+ }
+ }
+
+ protected bool hasListeners() {
+ if (listenerTypes is null) {
+ return false;
+ }
+ for (int i = 0; i < listenerTypes.length; i++) {
+ if (listenerLists[i].size() > 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int findListenerTypeIndex(Object listenerType) {
+ if (listenerTypes !is null) {
+ for (int i = 0; i < listenerTypes.length; i++) {
+ if (listenerTypes[i] is listenerType) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ protected void fireEvent(ObservableEvent event) {
+ Object listenerType = event.getListenerType();
+ int listenerTypeIndex = findListenerTypeIndex(listenerType);
+ if (listenerTypeIndex !is -1) {
+ Object[] listeners = listenerLists[listenerTypeIndex]
+ .getListeners();
+ for (int i = 0; i < listeners.length; i++) {
+ event.dispatch(cast(IObservablesListener) listeners[i]);
+ }
+ }
+ }
+
+ /**
+ *
+ */
+ protected void firstListenerAdded() {
+ }
+
+ /**
+ *
+ */
+ protected void lastListenerRemoved() {
+ }
+
+ /**
+ *
+ */
+ public void dispose() {
+ listenerLists = null;
+ listenerTypes = null;
+ realm = null;
+ }
+
+ /**
+ * @return Returns the realm.
+ */
+ public Realm getRealm() {
+ return realm;
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ChangeSupport.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ChangeSupport.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable;
+
+/**
+ * @since 1.0
+ *
+ */
+public abstract class ChangeSupport : ChangeManager {
+
+ /**
+ * @param realm
+ */
+ public this(Realm realm) {
+ super(realm);
+ }
+
+ public void addListener(Object listenerType,
+ IObservablesListener listener) {
+ super.addListener(listenerType, listener);
+ }
+
+ public void removeListener(Object listenerType,
+ IObservablesListener listener) {
+ super.removeListener(listenerType, listener);
+ }
+
+ public void fireEvent(ObservableEvent event) {
+ super.fireEvent(event);
+ }
+
+ /**
+ *
+ */
+ protected abstract void firstListenerAdded();
+
+ /**
+ *
+ */
+ protected abstract void lastListenerRemoved();
+
+ /**
+ * @param listener
+ */
+ public void addChangeListener(IChangeListener listener) {
+ addListener(ChangeEvent.TYPE, listener);
+ }
+
+ /**
+ * @param listener
+ */
+ public void removeChangeListener(IChangeListener listener) {
+ removeListener(ChangeEvent.TYPE, listener);
+ }
+
+ /**
+ * @param listener
+ */
+ public void addStaleListener(IStaleListener listener) {
+ addListener(StaleEvent.TYPE, listener);
+ }
+
+ /**
+ * @param listener
+ */
+ public void removeStaleListener(IStaleListener listener) {
+ removeListener(StaleEvent.TYPE, listener);
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/Diffs.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/Diffs.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,470 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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
+ * Matthew Hall - bug 226216
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import org.eclipse.core.databinding.observable.list.ListDiff;
+import org.eclipse.core.databinding.observable.list.ListDiffEntry;
+import org.eclipse.core.databinding.observable.map.MapDiff;
+import org.eclipse.core.databinding.observable.set.SetDiff;
+import org.eclipse.core.databinding.observable.value.ValueDiff;
+import org.eclipse.core.internal.databinding.Util;
+
+/**
+ * @since 1.0
+ *
+ */
+public class Diffs {
+
+ /**
+ * @param oldList
+ * @param newList
+ * @return the differences between oldList and newList
+ */
+ public static ListDiff computeListDiff(List oldList, List newList) {
+ List diffEntries = new ArrayList();
+ createListDiffs(new ArrayList(oldList), newList, diffEntries);
+ ListDiff listDiff = createListDiff(cast(ListDiffEntry[]) diffEntries
+ .toArray(new ListDiffEntry[diffEntries.size()]));
+ return listDiff;
+ }
+
+ /**
+ * adapted from EMF's ListDifferenceAnalyzer
+ */
+ private static void createListDiffs(List oldList, List newList,
+ List listDiffs) {
+ int index = 0;
+ for (Iterator it = newList.iterator(); it.hasNext();) {
+ Object newValue = it.next();
+ if (oldList.size() <= index) {
+ // append newValue to newList
+ listDiffs.add(createListDiffEntry(index, true, newValue));
+ } else {
+ bool done;
+ do {
+ done = true;
+ Object oldValue = oldList.get(index);
+ if (oldValue is null ? newValue !is null : !oldValue.equals(newValue)) {
+ int oldIndexOfNewValue = listIndexOf(oldList, newValue, index);
+ if (oldIndexOfNewValue !is -1) {
+ int newIndexOfOldValue = listIndexOf(newList, oldValue, index);
+ if (newIndexOfOldValue is -1) {
+ // removing oldValue from list[index]
+ listDiffs.add(createListDiffEntry(index, false, oldValue));
+ oldList.remove(index);
+ done = false;
+ } else if (newIndexOfOldValue > oldIndexOfNewValue) {
+ // moving oldValue from list[index] to [newIndexOfOldValue]
+ if (oldList.size() <= newIndexOfOldValue) {
+ // The element cannot be moved to the correct index
+ // now, however later iterations will insert elements
+ // in front of it, eventually moving it into the
+ // correct spot.
+ newIndexOfOldValue = oldList.size() - 1;
+ }
+ listDiffs.add(createListDiffEntry(index, false, oldValue));
+ oldList.remove(index);
+ listDiffs.add(createListDiffEntry(newIndexOfOldValue, true, oldValue));
+ oldList.add(newIndexOfOldValue, oldValue);
+ done = false;
+ } else {
+ // move newValue from list[oldIndexOfNewValue] to [index]
+ listDiffs.add(createListDiffEntry(oldIndexOfNewValue, false, newValue));
+ oldList.remove(oldIndexOfNewValue);
+ listDiffs.add(createListDiffEntry(index, true, newValue));
+ oldList.add(index, newValue);
+ }
+ } else {
+ // add newValue at list[index]
+ oldList.add(index, newValue);
+ listDiffs.add(createListDiffEntry(index, true, newValue));
+ }
+ }
+ } while (!done);
+ }
+ ++index;
+ }
+ for (int i = oldList.size(); i > index;) {
+ // remove excess trailing elements not present in newList
+ listDiffs.add(createListDiffEntry(--i, false, oldList.get(i)));
+ }
+ }
+
+ /**
+ * @param list
+ * @param object
+ * @param index
+ * @return the index, or -1 if not found
+ */
+ private static int listIndexOf(List list, Object object, int index) {
+ int size = list.size();
+ for (int i=index; inull
.
+ *
+ * @param left
+ * The left object to compare; may be null
.
+ * @param right
+ * The right object to compare; may be null
.
+ * @return true
if the two objects are equivalent;
+ * false
otherwise.
+ */
+ public static final override equals_t opEquals(final Object left, final Object right) {
+ return left is null ? right is null : ((right !is null) && left
+ .equals(right));
+ }
+
+ /**
+ * @param oldSet
+ * @param newSet
+ * @return a set diff
+ */
+ public static SetDiff computeSetDiff(Set oldSet, Set newSet) {
+ Set additions = new HashSet(newSet);
+ additions.removeAll(oldSet);
+ Set removals = new HashSet(oldSet);
+ removals.removeAll(newSet);
+ return createSetDiff(additions, removals);
+ }
+
+ /**
+ * Computes the difference between two maps.
+ *
+ * @param oldMap
+ * @param newMap
+ * @return a map diff representing the changes needed to turn oldMap into
+ * newMap
+ */
+ public static MapDiff computeMapDiff(Map oldMap, Map newMap) {
+ // starts out with all keys from the new map, we will remove keys from
+ // the old map as we go
+ final Set addedKeys = new HashSet(newMap.keySet());
+ final Set removedKeys = new HashSet();
+ final Set changedKeys = new HashSet();
+ final Map oldValues = new HashMap();
+ final Map newValues = new HashMap();
+ for (Iterator it = oldMap.entrySet().iterator(); it.hasNext();) {
+ Map.Entry oldEntry = cast(Entry) it.next();
+ Object oldKey = oldEntry.getKey();
+ if (addedKeys.remove(oldKey)) {
+ // potentially changed key since it is in oldMap and newMap
+ Object oldValue = oldEntry.getValue();
+ Object newValue = newMap.get(oldKey);
+ if (!Util.equals(oldValue, newValue)) {
+ changedKeys.add(oldKey);
+ oldValues.put(oldKey, oldValue);
+ newValues.put(oldKey, newValue);
+ }
+ } else {
+ removedKeys.add(oldKey);
+ oldValues.put(oldKey, oldEntry.getValue());
+ }
+ }
+ for (Iterator it = addedKeys.iterator(); it.hasNext();) {
+ Object newKey = it.next();
+ newValues.put(newKey, newMap.get(newKey));
+ }
+ return new class() MapDiff {
+ public Set getAddedKeys() {
+ return addedKeys;
+ }
+
+ public Set getChangedKeys() {
+ return changedKeys;
+ }
+
+ public Set getRemovedKeys() {
+ return removedKeys;
+ }
+
+ public Object getNewValue(Object key) {
+ return newValues.get(key);
+ }
+
+ public Object getOldValue(Object key) {
+ return oldValues.get(key);
+ }
+ };
+ }
+
+ /**
+ * @param oldValue
+ * @param newValue
+ * @return a value diff
+ */
+ public static ValueDiff createValueDiff(final Object oldValue,
+ final Object newValue) {
+ return new class() ValueDiff {
+
+ public Object getOldValue() {
+ return oldValue;
+ }
+
+ public Object getNewValue() {
+ return newValue;
+ }
+ };
+ }
+
+ /**
+ * @param additions
+ * @param removals
+ * @return a set diff
+ */
+ public static SetDiff createSetDiff(Set additions, Set removals) {
+ final Set unmodifiableAdditions = Collections
+ .unmodifiableSet(additions);
+ final Set unmodifiableRemovals = Collections.unmodifiableSet(removals);
+ return new class() SetDiff {
+
+ public Set getAdditions() {
+ return unmodifiableAdditions;
+ }
+
+ public Set getRemovals() {
+ return unmodifiableRemovals;
+ }
+ };
+ }
+
+ /**
+ * @param difference
+ * @return a list diff with one differing entry
+ */
+ public static ListDiff createListDiff(ListDiffEntry difference) {
+ return createListDiff(new ListDiffEntry[] { difference });
+ }
+
+ /**
+ * @param difference1
+ * @param difference2
+ * @return a list diff with two differing entries
+ */
+ public static ListDiff createListDiff(ListDiffEntry difference1,
+ ListDiffEntry difference2) {
+ return createListDiff(new ListDiffEntry[] { difference1, difference2 });
+ }
+
+ /**
+ * @param differences
+ * @return a list diff with the given entries
+ */
+ public static ListDiff createListDiff(final ListDiffEntry[] differences) {
+ return new class() ListDiff {
+ public ListDiffEntry[] getDifferences() {
+ return differences;
+ }
+ };
+ }
+
+ /**
+ * @param position
+ * @param isAddition
+ * @param element
+ * @return a list diff entry
+ */
+ public static ListDiffEntry createListDiffEntry(final int position,
+ final bool isAddition, final Object element) {
+ return new class() ListDiffEntry {
+
+ public int getPosition() {
+ return position;
+ }
+
+ public bool isAddition() {
+ return isAddition;
+ }
+
+ public Object getElement() {
+ return element;
+ }
+ };
+ }
+
+ /**
+ * @param addedKey
+ * @param newValue
+ * @return a map diff
+ */
+ public static MapDiff createMapDiffSingleAdd(final Object addedKey,
+ final Object newValue) {
+ return new class() MapDiff {
+
+ public Set getAddedKeys() {
+ return Collections.singleton(addedKey);
+ }
+
+ public Set getChangedKeys() {
+ return Collections.EMPTY_SET;
+ }
+
+ public Object getNewValue(Object key) {
+ return newValue;
+ }
+
+ public Object getOldValue(Object key) {
+ return null;
+ }
+
+ public Set getRemovedKeys() {
+ return Collections.EMPTY_SET;
+ }
+ };
+ }
+
+ /**
+ * @param existingKey
+ * @param oldValue
+ * @param newValue
+ * @return a map diff
+ */
+ public static MapDiff createMapDiffSingleChange(final Object existingKey,
+ final Object oldValue, final Object newValue) {
+ return new class() MapDiff {
+
+ public Set getAddedKeys() {
+ return Collections.EMPTY_SET;
+ }
+
+ public Set getChangedKeys() {
+ return Collections.singleton(existingKey);
+ }
+
+ public Object getNewValue(Object key) {
+ return newValue;
+ }
+
+ public Object getOldValue(Object key) {
+ return oldValue;
+ }
+
+ public Set getRemovedKeys() {
+ return Collections.EMPTY_SET;
+ }
+ };
+ }
+
+ /**
+ * @param removedKey
+ * @param oldValue
+ * @return a map diff
+ */
+ public static MapDiff createMapDiffSingleRemove(final Object removedKey,
+ final Object oldValue) {
+ return new class() MapDiff {
+
+ public Set getAddedKeys() {
+ return Collections.EMPTY_SET;
+ }
+
+ public Set getChangedKeys() {
+ return Collections.EMPTY_SET;
+ }
+
+ public Object getNewValue(Object key) {
+ return null;
+ }
+
+ public Object getOldValue(Object key) {
+ return oldValue;
+ }
+
+ public Set getRemovedKeys() {
+ return Collections.singleton(removedKey);
+ }
+ };
+ }
+
+ /**
+ * @param copyOfOldMap
+ * @return a map diff
+ */
+ public static MapDiff createMapDiffRemoveAll(final Map copyOfOldMap) {
+ return new class() MapDiff {
+
+ public Set getAddedKeys() {
+ return Collections.EMPTY_SET;
+ }
+
+ public Set getChangedKeys() {
+ return Collections.EMPTY_SET;
+ }
+
+ public Object getNewValue(Object key) {
+ return null;
+ }
+
+ public Object getOldValue(Object key) {
+ return copyOfOldMap.get(key);
+ }
+
+ public Set getRemovedKeys() {
+ return copyOfOldMap.keySet();
+ }
+ };
+ }
+
+ /**
+ * @param addedKeys
+ * @param removedKeys
+ * @param changedKeys
+ * @param oldValues
+ * @param newValues
+ * @return a map diff
+ */
+ public static MapDiff createMapDiff(final Set addedKeys,
+ final Set removedKeys, final Set changedKeys, final Map oldValues,
+ final Map newValues) {
+ return new class() MapDiff {
+
+ public Set getAddedKeys() {
+ return addedKeys;
+ }
+
+ public Set getChangedKeys() {
+ return changedKeys;
+ }
+
+ public Object getNewValue(Object key) {
+ return newValues.get(key);
+ }
+
+ public Object getOldValue(Object key) {
+ return oldValues.get(key);
+ }
+
+ public Set getRemovedKeys() {
+ return removedKeys;
+ }
+ };
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IChangeListener.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IChangeListener.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable;
+
+import org.eclipse.core.databinding.observable.list.IListChangeListener;
+import org.eclipse.core.databinding.observable.map.IMapChangeListener;
+import org.eclipse.core.databinding.observable.set.ISetChangeListener;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+
+/**
+ * Listener for generic change events. Note that the change events do not carry
+ * information about the change, they only specify the affected observable. To
+ * listen for specific change events, use more specific change listeners.
+ *
+ * @see IValueChangeListener
+ * @see IListChangeListener
+ * @see ISetChangeListener
+ * @see IMapChangeListener
+ *
+ * @since 1.0
+ */
+public interface IChangeListener : IObservablesListener {
+
+ /**
+ * Handle a generic change to the given observable. The given event object
+ * must only be used locally in this method because it may be reused for
+ * other change notifications.
+ *
+ * @param event
+ */
+ public void handleChange(ChangeEvent event);
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObservable.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObservable.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2008 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
+ *******************************************************************************/
+package org.eclipse.core.databinding.observable;
+
+/**
+ * An object with state that allows to listen for state changes.
+ *
+ *
null
+ * if this observable collection is untyped.
+ */
+ Object getElementType();
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObservablesListener.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObservablesListener.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable;
+
+/**
+ * Marker interface for all listener types in the observables framework.
+ *
+ * @noimplement This interface is not intended to be implemented by clients.
+ *
+ * @since 1.0
+ */
+public interface IObservablesListener {
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObserving.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObserving.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2007 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
+ ******************************************************************************/
+
+package org.eclipse.core.databinding.observable;
+
+/**
+ *
+ * Mixin interface for IObservables that observe other objects.
+ *
+ * @since 1.0
+ *
+ */
+public interface IObserving {
+
+ /**
+ * Returns the observed object, or null
if this observing
+ * object does not currently observe an object.
+ *
+ * @return the observed object, or null
+ */
+ public Object getObserved();
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IStaleListener.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IStaleListener.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable;
+
+/**
+ * Listener for staleness events. An observable object is stale if its state
+ * will change eventually.
+ *
+ * @since 1.0
+ */
+public interface IStaleListener : IObservablesListener {
+
+ /**
+ * Handle the event that the given observable object is now stale. The given
+ * event object must only be used locally in this method because it may be
+ * reused for other change notifications.
+ *
+ * @param staleEvent
+ */
+ public void handleStale(StaleEvent staleEvent);
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ObservableEvent.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ObservableEvent.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable;
+
+import java.util.EventObject;
+
+/**
+ * Abstract event object for events fired by {@link IObservable} objects. All
+ * events fired by observables must be derived from this class so that the way
+ * of dispatching events can be improved in later versions of the framework.
+ *
+ * @since 1.0
+ *
+ */
+public abstract class ObservableEvent : EventObject {
+
+ /**
+ * Creates a new observable event.
+ *
+ * @param source
+ */
+ public this(IObservable source) {
+ super(source);
+ }
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 7693906965267871813L;
+
+ /**
+ * Returns the observable that generated this event.
+ *
+ * @return the observable that generated this event
+ */
+ public IObservable getObservable() {
+ return cast(IObservable) getSource();
+ }
+
+ /**
+ * Dispatch this event to the given listener. Subclasses must implement this
+ * method by calling the appropriate type-safe event handling method on the
+ * given listener according to the type of this event.
+ *
+ * @param listener
+ * the listener that should handle the event
+ */
+ protected abstract void dispatch(IObservablesListener listener);
+
+ /**
+ * Returns a unique object used for distinguishing this event type from
+ * others.
+ *
+ * @return a unique object representing the concrete type of this event.
+ */
+ protected abstract Object getListenerType();
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ObservableTracker.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ObservableTracker.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,200 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2008 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
+ * Matthew Hall - Fix NPE, more detailed assert messages (bug 210115)
+ *******************************************************************************/
+package org.eclipse.core.databinding.observable;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.eclipse.core.internal.databinding.IdentityWrapper;
+import org.eclipse.core.runtime.Assert;
+
+/**
+ * This class makes it possible to monitor whenever an IObservable is read from.
+ * This can be used to automatically attach and remove listeners. How to use it:
+ *
+ * + * If you are implementing an IObservable, invoke getterCalled(this) whenever a + * getter is called - that is, whenever your observable is read from. You only + * need to do this once per method call. If one getter delegates to another, the + * outer getter doesn't need to call the method since the inner one will. + *
+ * + *+ * If you want to determine what observables were used in a particular block of + * code, call runAndMonitorcast(Runnable). This will execute the given runnable and + * return the set of observables that were read from. + *
+ * + *+ * This can be used to automatically attach listeners. For example, imagine you + * have a block of code that updates some widget by reading from a bunch of + * observables. Whenever one of those observables changes, you want to re-run + * the code and cause the widget to be refreshed. You could do this in the + * traditional manner by attaching one listener to each observable and + * re-running your widget update code whenever one of them changes, but this + * code is repetitive and requires updating the listener code whenever you + * refactor the widget updating code. + *
+ * + *+ * Alternatively, you could use a utility class that runs the code in a + * runAndMonitor block and automatically attach listeners to any observable used + * in updating the widget. The advantage of the latter approach is that it, + * eliminates the code for attaching and detaching listeners and will always + * stay in synch with changes to the widget update logic. + *
+ * + * @since 1.0 + */ +public class ObservableTracker { + + /** + * Threadlocal storage pointing to the current Set of IObservables, or null + * if none. Note that this is actually the top of a stack. Whenever a method + * changes the current value, it remembers the old value as a local variable + * and restores the old value when the method exits. + */ + private static ThreadLocal currentChangeListener = new ThreadLocal(); + + private static ThreadLocal currentStaleListener = new ThreadLocal(); + + private static ThreadLocal currentObservableSet = new ThreadLocal(); + + /** + * Invokes the given runnable, and returns the set of IObservables that were + * read by the runnable. If the runnable calls this method recursively, the + * result will not contain IObservables that were used within the inner + * runnable. + * + * @param runnable + * runnable to execute + * @param changeListener + * listener to register with all accessed observables + * @param staleListener + * listener to register with all accessed observables, or + *null
if no stale listener is to be registered
+ * @return an array of unique observable objects
+ */
+ public static IObservable[] runAndMonitor(Runnable runnable,
+ IChangeListener changeListener, IStaleListener staleListener) {
+ // Remember the previous value in the listener stack
+ Set lastObservableSet = cast(Set) currentObservableSet.get();
+ IChangeListener lastChangeListener = cast(IChangeListener) currentChangeListener
+ .get();
+ IStaleListener lastStaleListener = cast(IStaleListener) currentStaleListener
+ .get();
+
+ Set observableSet = new HashSet();
+ // Push the new listeners to the top of the stack
+ currentObservableSet.set(observableSet);
+ currentChangeListener.set(changeListener);
+ currentStaleListener.set(staleListener);
+ try {
+ runnable.run();
+ } finally {
+ // Pop the new listener off the top of the stack (by restoring the
+ // previous listener)
+ currentObservableSet.set(lastObservableSet);
+ currentChangeListener.set(lastChangeListener);
+ currentStaleListener.set(lastStaleListener);
+ }
+
+ int i = 0;
+ IObservable[] result = new IObservable[observableSet.size()];
+ for (Iterator it = observableSet.iterator(); it.hasNext();) {
+ IdentityWrapper wrapper = cast(IdentityWrapper) it.next();
+ result[i++] = cast(IObservable) wrapper.unwrap();
+ }
+
+ return result;
+ }
+
+ /**
+ * Runs the given runnable without tracking dependencies.
+ * @param runnable
+ *
+ * @since 1.1
+ */
+ public static void runAndIgnore(Runnable runnable) {
+ // Remember the previous value in the listener stack
+ Set lastObservableSet = cast(Set) currentObservableSet.get();
+ IChangeListener lastChangeListener = cast(IChangeListener) currentChangeListener
+ .get();
+ IStaleListener lastStaleListener = cast(IStaleListener) currentStaleListener
+ .get();
+ currentObservableSet.set(null);
+ currentChangeListener.set(null);
+ currentStaleListener.set(null);
+ try {
+ runnable.run();
+ } finally {
+ // Pop the new listener off the top of the stack (by restoring the
+ // previous listener)
+ currentObservableSet.set(lastObservableSet);
+ currentChangeListener.set(lastChangeListener);
+ currentStaleListener.set(lastStaleListener);
+ }
+ }
+
+ /*
+ * Returns the same string as the default Object.toString() implementation.
+ * getterCalled() uses this method IObservable.toString() to avoid infinite
+ * recursion and stack overflow.
+ */
+ private static String toString(IObservable observable) {
+ return observable.getClass().getName() + "@" //$NON-NLS-1$
+ + Integer.toHexString(System.identityHashCode(observable));
+ }
+
+ /**
+ * Notifies the ObservableTracker that an observable was read from. The
+ * JavaDoc for methods that invoke this method should include the following
+ * tag: "@TrackedGetter This method will notify ObservableTracker that the
+ * receiver has been read from". This lets callers know that they can rely
+ * on automatic updates from the object without explicitly attaching a
+ * listener.
+ *
+ * @param observable
+ */
+ public static void getterCalled(IObservable observable) {
+ Realm realm = observable.getRealm();
+ if (realm is null) // observable.isDisposed() would be more appropriate if it existed
+ Assert.isTrue(false, "Getter called on disposed observable " //$NON-NLS-1$
+ + toString(observable));
+ if (!realm.isCurrent())
+ Assert.isTrue(false, "Getter called outside realm of observable " //$NON-NLS-1$
+ + toString(observable));
+
+ Set lastObservableSet = cast(Set) currentObservableSet.get();
+ if (lastObservableSet is null) {
+ return;
+ }
+ IChangeListener lastChangeListener = cast(IChangeListener) currentChangeListener
+ .get();
+ IStaleListener lastStaleListener = cast(IStaleListener) currentStaleListener
+ .get();
+
+ bool added = false;
+ if (lastObservableSet !is null) {
+ added = lastObservableSet.add(new IdentityWrapper(observable));
+ }
+
+ // If anyone is listening for observable usage...
+ if (added && lastChangeListener !is null) {
+ observable.addChangeListener(lastChangeListener);
+ }
+ if (added && lastStaleListener !is null) {
+ observable.addStaleListener(lastStaleListener);
+ }
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/Observables.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/Observables.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,514 @@
+/*******************************************************************************
+ * Copyright (c) 2006-2008 Cerner 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:
+ * Brad Reynolds - initial API and implementation
+ * Matt Carter - bug 212518 (constantObservableValue)
+ * Matthew Hall - bugs 208332, 212518, 219909, 184830
+ * Marko Topolnik - bug 184830
+ ******************************************************************************/
+
+package org.eclipse.core.databinding.observable;
+
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.list.IListChangeListener;
+import org.eclipse.core.databinding.observable.list.IObservableList;
+import org.eclipse.core.databinding.observable.list.ObservableList;
+import org.eclipse.core.databinding.observable.map.IObservableMap;
+import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
+import org.eclipse.core.databinding.observable.masterdetail.MasterDetailObservables;
+import org.eclipse.core.databinding.observable.set.IObservableSet;
+import org.eclipse.core.databinding.observable.set.ISetChangeListener;
+import org.eclipse.core.databinding.observable.set.ObservableSet;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.internal.databinding.observable.ConstantObservableValue;
+import org.eclipse.core.internal.databinding.observable.EmptyObservableList;
+import org.eclipse.core.internal.databinding.observable.EmptyObservableSet;
+import org.eclipse.core.internal.databinding.observable.MapEntryObservableValue;
+import org.eclipse.core.internal.databinding.observable.ProxyObservableList;
+import org.eclipse.core.internal.databinding.observable.ProxyObservableSet;
+import org.eclipse.core.internal.databinding.observable.StalenessObservableValue;
+import org.eclipse.core.internal.databinding.observable.UnmodifiableObservableList;
+import org.eclipse.core.internal.databinding.observable.UnmodifiableObservableSet;
+import org.eclipse.core.internal.databinding.observable.UnmodifiableObservableValue;
+import org.eclipse.core.runtime.Assert;
+
+/**
+ * Contains static methods to operate on or return
+ * {@link IObservable Observables}.
+ *
+ * @since 1.0
+ */
+public class Observables {
+ /**
+ * Returns an unmodifiable observable value backed by the given observable
+ * value.
+ *
+ * @param value
+ * the value to wrap in an unmodifiable value
+ * @return an unmodifiable observable value backed by the given observable
+ * value
+ * @since 1.1
+ */
+ public static IObservableValue unmodifiableObservableValue(
+ IObservableValue value) {
+ Assert.isNotNull(value, "Argument 'value' cannot be null"); //$NON-NLS-1$
+ return new UnmodifiableObservableValue(value);
+ }
+
+ /**
+ * Returns an observable value with the given constant value.
+ *
+ * @param realm
+ * the observable's realm
+ * @param value
+ * the observable's constant value
+ * @param valueType
+ * the observable's value type
+ * @return an immutable observable value with the given constant value
+ * @since 1.1
+ */
+ public static IObservableValue constantObservableValue(Realm realm,
+ Object value, Object valueType) {
+ return new ConstantObservableValue(realm, value, valueType);
+ }
+
+ /**
+ * Returns an observable value with the given constant value.
+ *
+ * @param realm
+ * the observable's realm
+ * @param value
+ * the observable's constant value
+ * @return an immutable observable value with the given constant value
+ * @since 1.1
+ */
+ public static IObservableValue constantObservableValue(Realm realm,
+ Object value) {
+ return constantObservableValue(realm, value, null);
+ }
+
+ /**
+ * Returns an observable value with the given constant value.
+ *
+ * @param value
+ * the observable's constant value
+ * @param valueType
+ * the observable's value type
+ * @return an immutable observable value with the given constant value
+ * @since 1.1
+ */
+ public static IObservableValue constantObservableValue(Object value,
+ Object valueType) {
+ return constantObservableValue(Realm.getDefault(), value, valueType);
+ }
+
+ /**
+ * Returns an observable value with the given constant value.
+ *
+ * @param value
+ * the observable's constant value
+ * @return an immutable observable value with the given constant value
+ * @since 1.1
+ */
+ public static IObservableValue constantObservableValue(Object value) {
+ return constantObservableValue(Realm.getDefault(), value, null);
+ }
+
+ /**
+ * Returns an unmodifiable observable list backed by the given observable
+ * list.
+ *
+ * @param list
+ * the list to wrap in an unmodifiable list
+ * @return an unmodifiable observable list backed by the given observable
+ * list
+ */
+ public static IObservableList unmodifiableObservableList(
+ IObservableList list) {
+ if (list is null) {
+ throw new IllegalArgumentException("List parameter cannot be null."); //$NON-NLS-1$
+ }
+
+ return new UnmodifiableObservableList(list);
+ }
+
+ /**
+ * Returns an unmodifiable observable set backed by the given observable
+ * set.
+ *
+ * @param set
+ * the set to wrap in an unmodifiable set
+ * @return an unmodifiable observable set backed by the given observable set
+ * @since 1.1
+ */
+ public static IObservableSet unmodifiableObservableSet(IObservableSet set) {
+ if (set is null) {
+ throw new IllegalArgumentException("Set parameter cannot be null"); //$NON-NLS-1$
+ }
+
+ return new UnmodifiableObservableSet(set);
+ }
+
+ /**
+ * Returns an empty observable list. The returned list continues to work
+ * after it has been disposed of and can be disposed of multiple times.
+ *
+ * @return an empty observable list.
+ */
+ public static IObservableList emptyObservableList() {
+ return emptyObservableList(Realm.getDefault(), null);
+ }
+
+ /**
+ * Returns an empty observable list of the given element type. The returned
+ * list continues to work after it has been disposed of and can be disposed
+ * of multiple times.
+ *
+ * @param elementType
+ * the element type of the returned list
+ * @return an empty observable list
+ * @since 1.1
+ */
+ public static IObservableList emptyObservableList(Object elementType) {
+ return emptyObservableList(Realm.getDefault(), elementType);
+ }
+
+ /**
+ * Returns an empty observable list belonging to the given realm. The
+ * returned list continues to work after it has been disposed of and can be
+ * disposed of multiple times.
+ *
+ * @param realm
+ * the realm of the returned list
+ * @return an empty observable list.
+ */
+ public static IObservableList emptyObservableList(Realm realm) {
+ return emptyObservableList(realm, null);
+ }
+
+ /**
+ * Returns an empty observable list of the given element type and belonging
+ * to the given realm. The returned list continues to work after it has been
+ * disposed of and can be disposed of multiple times.
+ *
+ * @param realm
+ * the realm of the returned list
+ * @param elementType
+ * the element type of the returned list
+ * @return an empty observable list
+ * @since 1.1
+ */
+ public static IObservableList emptyObservableList(Realm realm,
+ Object elementType) {
+ return new EmptyObservableList(realm, elementType);
+ }
+
+ /**
+ * Returns an empty observable set. The returned set continues to work after
+ * it has been disposed of and can be disposed of multiple times.
+ *
+ * @return an empty observable set.
+ */
+ public static IObservableSet emptyObservableSet() {
+ return emptyObservableSet(Realm.getDefault(), null);
+ }
+
+ /**
+ * Returns an empty observable set of the given element type. The returned
+ * set continues to work after it has been disposed of and can be disposed
+ * of multiple times.
+ *
+ * @param elementType
+ * the element type of the returned set
+ * @return an empty observable set
+ * @since 1.1
+ */
+ public static IObservableSet emptyObservableSet(Object elementType) {
+ return emptyObservableSet(Realm.getDefault(), elementType);
+ }
+
+ /**
+ * Returns an empty observable set belonging to the given realm. The
+ * returned set continues to work after it has been disposed of and can be
+ * disposed of multiple times.
+ *
+ * @param realm
+ * the realm of the returned set
+ * @return an empty observable set.
+ */
+ public static IObservableSet emptyObservableSet(Realm realm) {
+ return emptyObservableSet(realm, null);
+ }
+
+ /**
+ * Returns an empty observable set of the given element type and belonging
+ * to the given realm. The returned set continues to work after it has been
+ * disposed of and can be disposed of multiple times.
+ *
+ * @param realm
+ * the realm of the returned set
+ * @param elementType
+ * the element type of the returned set
+ * @return an empty observable set
+ * @since 1.1
+ */
+ public static IObservableSet emptyObservableSet(Realm realm,
+ Object elementType) {
+ return new EmptyObservableSet(realm, elementType);
+ }
+
+ /**
+ * Returns an observable set backed by the given set.
+ *
+ * @param set
+ * the set to wrap in an IObservableSet
+ * @return an observable set backed by the given set
+ */
+ public static IObservableSet staticObservableSet(Set set) {
+ return staticObservableSet(Realm.getDefault(), set, Object.class);
+ }
+
+ /**
+ * Returns an observable set of the given element type, backed by the given
+ * set.
+ *
+ * @param set
+ * the set to wrap in an IObservableSet
+ * @param elementType
+ * the element type of the returned set
+ * @return Returns an observable set backed by the given unchanging set
+ * @since 1.1
+ */
+ public static IObservableSet staticObservableSet(Set set, Object elementType) {
+ return staticObservableSet(Realm.getDefault(), set, elementType);
+ }
+
+ /**
+ * Returns an observable set belonging to the given realm, backed by the
+ * given set.
+ *
+ * @param realm
+ * the realm of the returned set
+ * @param set
+ * the set to wrap in an IObservableSet
+ * @return an observable set backed by the given unchanging set
+ */
+ public static IObservableSet staticObservableSet(Realm realm, Set set) {
+ return staticObservableSet(realm, set, Object.class);
+ }
+
+ /**
+ * Returns an observable set of the given element type and belonging to the
+ * given realm, backed by the given set.
+ *
+ * @param realm
+ * the realm of the returned set
+ * @param set
+ * the set to wrap in an IObservableSet
+ * @param elementType
+ * the element type of the returned set
+ * @return an observable set backed by the given set
+ * @since 1.1
+ */
+ public static IObservableSet staticObservableSet(Realm realm, Set set,
+ Object elementType) {
+ return new class(realm, set, elementType) ObservableSet {
+ public void addChangeListener(IChangeListener listener) {
+ }
+
+ public void addStaleListener(IStaleListener listener) {
+ }
+
+ public void addSetChangeListener(ISetChangeListener listener) {
+ }
+ };
+ }
+
+ /**
+ * Returns an observable set that contains the same elements as the given
+ * set, and fires the same events as the given set, but can be disposed of
+ * without disposing of the wrapped set.
+ *
+ * @param target
+ * the set to wrap
+ * @return a disposable proxy for the given observable set
+ */
+ public static IObservableSet proxyObservableSet(IObservableSet target) {
+ return new ProxyObservableSet(target);
+ }
+
+ /**
+ * Returns an observable list that contains the same elements as the given
+ * list, and fires the same events as the given list, but can be disposed of
+ * without disposing of the wrapped list.
+ *
+ * @param target
+ * the list to wrap
+ * @return a disposable proxy for the given observable list
+ * @since 1.1
+ */
+ public static IObservableList proxyObservableList(IObservableList target) {
+ return new ProxyObservableList(target);
+ }
+
+ /**
+ * Returns an observable list backed by the given list.
+ *
+ * @param list
+ * the list to wrap in an IObservableList
+ * @return an observable list backed by the given unchanging list
+ */
+ public static IObservableList staticObservableList(List list) {
+ return staticObservableList(Realm.getDefault(), list, Object.class);
+ }
+
+ /**
+ * Returns an observable list of the given element type, backed by the given
+ * list.
+ *
+ * @param list
+ * the list to wrap in an IObservableList
+ * @param elementType
+ * the element type of the returned list
+ * @return an observable list backed by the given unchanging list
+ * @since 1.1
+ */
+ public static IObservableList staticObservableList(List list,
+ Object elementType) {
+ return staticObservableList(Realm.getDefault(), list, elementType);
+ }
+
+ /**
+ * Returns an observable list belonging to the given realm, backed by the
+ * given list.
+ *
+ * @param realm
+ * the realm of the returned list
+ * @param list
+ * the list to wrap in an IObservableList
+ * @return an observable list backed by the given unchanging list
+ */
+ public static IObservableList staticObservableList(Realm realm, List list) {
+ return staticObservableList(realm, list, Object.class);
+ }
+
+ /**
+ * Returns an observable list of the given element type and belonging to the
+ * given realm, backed by the given list.
+ *
+ * @param realm
+ * the realm of the returned list
+ * @param list
+ * the list to wrap in an IObservableList
+ * @param elementType
+ * the element type of the returned list
+ * @return an observable list backed by the given unchanging list
+ * @since 1.1
+ */
+ public static IObservableList staticObservableList(Realm realm, List list,
+ Object elementType) {
+ return new class(realm, list, elementType) ObservableList {
+ public void addChangeListener(IChangeListener listener) {
+ }
+
+ public void addStaleListener(IStaleListener listener) {
+ }
+
+ public void addListChangeListener(IListChangeListener listener) {
+ }
+ };
+ }
+
+ /**
+ * Returns an observable value of type Boolean.TYPE
which
+ * tracks whether the given observable is stale.
+ *
+ * @param observable
+ * the observable to track
+ * @return an observable value which tracks whether the given observable is
+ * stale
+ *
+ * @since 1.1
+ */
+ public static IObservableValue observeStale(IObservable observable) {
+ return new StalenessObservableValue(observable);
+ }
+
+ /**
+ * Returns an observable value that tracks changes to the value of an
+ * observable map's entry specified by its key.
+ *
+ * The state where the key does not exist in the map is equivalent to the
+ * state where the key exists and its value is null
. The
+ * transition between these two states is not considered a value change and
+ * no event is fired.
+ *
+ * @param map
+ * the observable map whose entry will be tracked.
+ * @param key
+ * the key identifying the map entry to track.
+ * @param valueType
+ * the type of the value. May be null
, meaning
+ * the value is untyped.
+ * @return an observable value that tracks the value associated with the
+ * specified key in the given map
+ * @since 1.1
+ */
+ public static IObservableValue observeMapEntry(IObservableMap map,
+ Object key, Object valueType) {
+ return new MapEntryObservableValue(map, key, valueType);
+ }
+
+ /**
+ * Returns a factory for creating obervable values tracking the value of the
+ * {@link IObservableMap observable map} entry identified by a particular
+ * key.
+ *
+ * @param map
+ * the observable map whose entry will be tracked.
+ * @param valueType
+ * the type of the value. May be null
, meaning
+ * the value is untyped.
+ * @return a factory for creating observable values tracking the value of
+ * the observable map entry identified by a particular key object.
+ * @since 1.1
+ */
+ public static IObservableFactory mapEntryValueFactory(
+ final IObservableMap map, final Object valueType) {
+ return new class() IObservableFactory {
+ public IObservable createObservable(Object key) {
+ return observeMapEntry(map, key, valueType);
+ }
+ };
+ }
+
+ /**
+ * Helper method for MasterDetailObservables.detailValue(master,
+ * mapEntryValueFactory(map, valueType), valueType)
.
+ *
+ * @param map
+ * the observable map whose entry will be tracked.
+ * @param master
+ * the observable value that identifies which map entry to track.
+ * @param valueType
+ * the type of the value. May be null
, meaning
+ * the value is untyped.
+ * @return an observable value tracking the current value of the specified
+ * key in the given map an observable value that tracks the current
+ * value of the named property for the current value of the master
+ * observable value
+ * @since 1.1
+ */
+ public static IObservableValue observeDetailMapEntry(IObservableMap map,
+ IObservableValue master, Object valueType) {
+ return MasterDetailObservables.detailValue(master,
+ mapEntryValueFactory(map, valueType), valueType);
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/Realm.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/Realm.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,293 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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 - bug 168153
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable;
+
+import org.eclipse.core.databinding.Binding;
+import org.eclipse.core.databinding.util.Policy;
+import org.eclipse.core.internal.databinding.Queue;
+import org.eclipse.core.runtime.ISafeRunnable;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.SafeRunner;
+import org.eclipse.core.runtime.Status;
+
+/**
+ * A realm defines a context from which objects implementing {@link IObservable}
+ * must be accessed, and on which these objects will notify their listeners. To
+ * bridge between observables from different realms, subclasses of
+ * {@link Binding} can be used.
+ *
+ * A block of code is said to be executing within a realm if calling + * {@link #isCurrent()} from that block returns true. Code reached by calling + * methods from that block will execute within the same realm, with the + * exception of methods on this class that can be used to execute code within a + * specific realm. Clients can use {@link #syncExeccast(Runnable)}, + * {@link #asyncExeccast(Runnable)}, or {@link #execcast(Runnable)} to execute a + * runnable within this realm. Note that using {@link #syncExeccast(Runnable)} can + * lead to deadlocks and should be avoided if the current thread holds any + * locks. + *
+ *+ * It is instructive to think about possible implementations of Realm: It can be + * based on executing on a designated thread such as a UI thread, or based on + * holding a lock. In the former case, calling syncExec on a realm that is not + * the current realm will execute the given runnable on a different thread (the + * designated thread). In the latter case, calling syncExec may execute the + * given runnable on the calling thread, but calling + * {@link #asyncExeccast(Runnable)} will execute the given runnable on a different + * thread. Therefore, no assumptions can be made about the thread that will + * execute arguments to {@link #asyncExeccast(Runnable)}, + * {@link #syncExeccast(Runnable)}, or {@link #execcast(Runnable)}. + *
+ *+ * It is possible that a block of code is executing within more than one realm. + * This can happen for implementations of Realm that are based on holding a lock + * but don't use a separate thread to run runnables given to + * {@link #syncExeccast(Runnable)}. Realm implementations of this kind should be + * appropriately documented because it increases the opportunity for deadlock. + *
+ *+ * Some implementations of {@link IObservable} provide constructors which do not + * take a Realm argument and are specified to create the observable instance + * with the current default realm. The default realm can be set for the + * currently executing thread by using {@link #runWithDefault(Realm, Runnable)}. + * Note that the default realm does not have to be the current realm. + *
+ *+ * Subclasses must override at least one of asyncExec()/syncExec(). For realms + * based on a designated thread, it may be easier to implement asyncExec and + * keep the default implementation of syncExec. For realms based on holding a + * lock, it may be easier to implement syncExec and keep the default + * implementation of asyncExec. + *
+ * + * @since 1.0 + * + * @see IObservable + */ +public abstract class Realm { + + private static ThreadLocal defaultRealm = new ThreadLocal(); + + /** + * Returns the default realm for the calling thread, ornull
+ * if no default realm has been set.
+ *
+ * @return the default realm, or null
+ */
+ public static Realm getDefault() {
+ return cast(Realm) defaultRealm.get();
+ }
+
+ /**
+ * Sets the default realm for the calling thread, returning the current
+ * default thread. This method is inherently unsafe, it is recommended to
+ * use {@link #runWithDefault(Realm, Runnable)} instead. This method is
+ * exposed to subclasses to facilitate testing.
+ *
+ * @param realm
+ * the new default realm, or null
+ * @return the previous default realm, or null
+ */
+ protected static Realm setDefault(Realm realm) {
+ Realm oldValue = getDefault();
+ defaultRealm.set(realm);
+ return oldValue;
+ }
+
+ /**
+ * @return true if the caller is executing in this realm. This method must
+ * not have side-effects (such as, for example, implicitly placing
+ * the caller in this realm).
+ */
+ abstract public bool isCurrent();
+
+ private Thread workerThread;
+
+ Queue workQueue = new Queue();
+
+ /**
+ * Runs the given runnable. If an exception occurs within the runnable, it
+ * is logged and not re-thrown. If the runnable implements
+ * {@link ISafeRunnable}, the exception is passed to its
+ * handleException method.
+ *
+ * @param runnable
+ */
+ protected static void safeRun(final Runnable runnable) {
+ ISafeRunnable safeRunnable;
+ if ( null !is cast(ISafeRunnable)runnable ) {
+ safeRunnable = cast(ISafeRunnable) runnable;
+ } else {
+ safeRunnable = new class() ISafeRunnable {
+ public void handleException(Throwable exception) {
+ Policy
+ .getLog()
+ .log(
+ new Status(
+ IStatus.ERROR,
+ Policy.JFACE_DATABINDING,
+ IStatus.OK,
+ "Unhandled exception: " + exception.getMessage(), exception)); //$NON-NLS-1$
+ }
+ public void run() {
+ runnable.run();
+ }
+ };
+ }
+ SafeRunner.run(safeRunnable);
+ }
+
+ /**
+ * Causes the run()
method of the runnable to be invoked from
+ * within this realm. If the caller is executing in this realm, the
+ * runnable's run method is invoked directly, otherwise it is run at the
+ * next reasonable opportunity using asyncExec.
+ *
+ * If the given runnable is an instance of {@link ISafeRunnable}, its
+ * exception handler method will be called if any exceptions occur while
+ * running it. Otherwise, the exception will be logged.
+ *
+ *
+ * @param runnable
+ */
+ public void exec(Runnable runnable) {
+ if (isCurrent()) {
+ safeRun(runnable);
+ } else {
+ asyncExec(runnable);
+ }
+ }
+
+ /**
+ * Causes the run()
method of the runnable to be invoked from
+ * within this realm at the next reasonable opportunity. The caller of this
+ * method continues to run in parallel, and is not notified when the
+ * runnable has completed.
+ *
+ * If the given runnable is an instance of {@link ISafeRunnable}, its
+ * exception handler method will be called if any exceptions occur while
+ * running it. Otherwise, the exception will be logged.
+ *
+ *
+ * Subclasses should use {@link #safeRuncast(Runnable)} to run the runnable.
+ *
+ *
+ * @param runnable
+ */
+ public void asyncExec(Runnable runnable) {
+ synchronized (workQueue) {
+ ensureWorkerThreadIsRunning();
+ workQueue.enqueue(runnable);
+ workQueue.notifyAll();
+ }
+ }
+
+ /**
+ *
+ */
+ private void ensureWorkerThreadIsRunning() {
+ if (workerThread is null) {
+ workerThread = new class() Thread {
+ public void run() {
+ try {
+ while (true) {
+ Runnable work = null;
+ synchronized (workQueue) {
+ while (workQueue.isEmpty()) {
+ workQueue.wait();
+ }
+ work = cast(Runnable) workQueue.dequeue();
+ }
+ syncExec(work);
+ }
+ } catch (InterruptedException e) {
+ // exit
+ }
+ }
+ };
+ workerThread.start();
+ }
+ }
+
+ /**
+ * Causes the run()
method of the runnable to be invoked from
+ * within this realm at the next reasonable opportunity. This method is
+ * blocking the caller until the runnable completes.
+ *
+ * If the given runnable is an instance of {@link ISafeRunnable}, its
+ * exception handler method will be called if any exceptions occur while
+ * running it. Otherwise, the exception will be logged.
+ *
+ *
+ * Subclasses should use {@link #safeRuncast(Runnable)} to run the runnable.
+ *
+ *
+ * Note: This class is not meant to be called by clients and therefore has
+ * only protected access.
+ *
+ *
+ * @param runnable
+ */
+ protected void syncExec(Runnable runnable) {
+ SyncRunnable syncRunnable = new SyncRunnable(runnable);
+ asyncExec(syncRunnable);
+ synchronized (syncRunnable) {
+ while (!syncRunnable.hasRun) {
+ try {
+ syncRunnable.wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+ }
+
+ static class SyncRunnable : Runnable {
+ bool hasRun = false;
+
+ private Runnable runnable;
+
+ this(Runnable runnable) {
+ this.runnable = runnable;
+ }
+
+ public void run() {
+ try {
+ safeRun(runnable);
+ } finally {
+ synchronized (this) {
+ hasRun = true;
+ this.notifyAll();
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the provided realm
as the default for the duration of
+ * {@link Runnable#run()} and resets the previous realm after completion.
+ * Note that this will not set the given realm as the current realm.
+ *
+ * @param realm
+ * @param runnable
+ */
+ public static void runWithDefault(Realm realm, Runnable runnable) {
+ Realm oldRealm = Realm.getDefault();
+ try {
+ defaultRealm.set(realm);
+ runnable.run();
+ } finally {
+ defaultRealm.set(oldRealm);
+ }
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/StaleEvent.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/StaleEvent.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable;
+
+/**
+ * Generic event denoting that the state of an {@link IObservable} object is
+ * about to change. Note that this event is only fired when an observable
+ * becomes stale, not when it becomes unstale; an observable that becomes
+ * unstale should always fire a change event. Staleness can be used (for
+ * example) to notify listeners when an observable has started a background
+ * thread for updating its state. Clients can safely ignore staleness.
+ *
+ * @see IObservable#isStale()
+ *
+ * @since 1.0
+ *
+ */
+public class StaleEvent : ObservableEvent {
+
+ /**
+ * Creates a new stale event.
+ *
+ * @param source
+ * the source observable
+ */
+ public this(IObservable source) {
+ super(source);
+ }
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 3491012225431471077L;
+
+ static final Object TYPE = new Object();
+
+ protected void dispatch(IObservablesListener listener) {
+ (cast(IStaleListener) listener).handleStale(this);
+ }
+
+ protected Object getListenerType() {
+ return TYPE;
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/AbstractObservableList.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/AbstractObservableList.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,312 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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 - bug 164653
+ * Brad Reynolds - bug 167204
+ * Matthew Hall - bug 118516
+ * Matthew Hall - bug 208858
+ * Matthew Hall - bug 208332
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.list;
+
+import java.util.AbstractList;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.eclipse.core.databinding.observable.ChangeEvent;
+import org.eclipse.core.databinding.observable.ChangeSupport;
+import org.eclipse.core.databinding.observable.IChangeListener;
+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.runtime.Assert;
+import org.eclipse.core.runtime.AssertionFailedException;
+
+/**
+ * Subclasses should override at least get(int index) and size().
+ *
+ *
+ * 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.
+ *
+ *
+ * @since 1.0
+ *
+ */
+public abstract class AbstractObservableList : AbstractList ,
+ IObservableList {
+
+ private ChangeSupport changeSupport;
+
+ /**
+ * @param realm
+ *
+ */
+ public this(Realm realm) {
+ Assert.isNotNull(realm, "Realm cannot be null"); //$NON-NLS-1$
+ changeSupport = new class(realm) ChangeSupport {
+ protected void firstListenerAdded() {
+ this.outer.firstListenerAdded();
+ }
+ protected void lastListenerRemoved() {
+ this.outer.lastListenerRemoved();
+ }
+ };
+ }
+
+ /**
+ *
+ */
+ public this() {
+ this(Realm.getDefault());
+ }
+
+ public bool isStale() {
+ getterCalled();
+ return false;
+ }
+
+ public synchronized void addListChangeListener(IListChangeListener listener) {
+ changeSupport.addListener(ListChangeEvent.TYPE, listener);
+ }
+
+ public synchronized void removeListChangeListener(IListChangeListener listener) {
+ changeSupport.removeListener(ListChangeEvent.TYPE, listener);
+ }
+
+ protected void fireListChange(ListDiff diff) {
+ // fire general change event first
+ fireChange();
+ changeSupport.fireEvent(new ListChangeEvent(this, diff));
+ }
+
+ public synchronized void addChangeListener(IChangeListener listener) {
+ changeSupport.addChangeListener(listener);
+ }
+
+ public synchronized void removeChangeListener(IChangeListener listener) {
+ changeSupport.removeChangeListener(listener);
+ }
+
+ public synchronized void addStaleListener(IStaleListener listener) {
+ changeSupport.addStaleListener(listener);
+ }
+
+ public synchronized void removeStaleListener(IStaleListener listener) {
+ changeSupport.removeStaleListener(listener);
+ }
+
+ /**
+ * Fires change event. Must be invoked from the current realm.
+ */
+ protected void fireChange() {
+ checkRealm();
+ changeSupport.fireEvent(new ChangeEvent(this));
+ }
+
+ /**
+ * Fires stale event. Must be invoked from the current realm.
+ */
+ protected void fireStale() {
+ checkRealm();
+ changeSupport.fireEvent(new StaleEvent(this));
+ }
+
+ /**
+ *
+ */
+ protected void firstListenerAdded() {
+ }
+
+ /**
+ *
+ */
+ protected void lastListenerRemoved() {
+ }
+
+ /**
+ *
+ */
+ public synchronized void dispose() {
+ changeSupport = null;
+ lastListenerRemoved();
+ }
+
+ public final int size() {
+ getterCalled();
+ return doGetSize();
+ }
+
+ /**
+ * @return the size
+ */
+ protected abstract int doGetSize();
+
+ /**
+ *
+ */
+ private void getterCalled() {
+ ObservableTracker.getterCalled(this);
+ }
+
+ public bool isEmpty() {
+ getterCalled();
+ return super.isEmpty();
+ }
+
+ public bool contains(Object o) {
+ getterCalled();
+ return super.contains(o);
+ }
+
+ public Iterator iterator() {
+ getterCalled();
+ final Iterator wrappedIterator = super.iterator();
+ return new class() Iterator {
+ public void remove() {
+ wrappedIterator.remove();
+ }
+
+ public bool hasNext() {
+ return wrappedIterator.hasNext();
+ }
+
+ public Object next() {
+ return wrappedIterator.next();
+ }
+ };
+ }
+
+ public Object[] toArray() {
+ getterCalled();
+ return super.toArray();
+ }
+
+ public Object[] toArray(Object a[]) {
+ getterCalled();
+ return super.toArray(a);
+ }
+
+ // Modification Operations
+
+ public bool add(Object o) {
+ getterCalled();
+ return super.add(o);
+ }
+
+ /**
+ * Moves the element located at oldIndex
to
+ * newIndex
. This method is equivalent to calling
+ * add(newIndex, remove(oldIndex))
.
+ *
+ * Subclasses should override this method to deliver list change
+ * notification for the remove and add operations in the same
+ * ListChangeEvent, as this allows {@link ListDiff#acceptcast(ListDiffVisitor)}
+ * to recognize the operation as a move.
+ *
+ * @param oldIndex
+ * the element's position before the move. Must be within the
+ * range 0 <= oldIndex < size()
.
+ * @param newIndex
+ * the element's position after the move. Must be within the
+ * range 0 <= newIndex < size()
.
+ * @return the element that was moved.
+ * @throws IndexOutOfBoundsException
+ * if either argument is out of range (0 <= index < size()
).
+ * @see ListDiffVisitor#handleMove(int, int, Object)
+ * @see ListDiff#acceptcast(ListDiffVisitor)
+ * @since 1.1
+ */
+ public Object move(int oldIndex, int newIndex) {
+ checkRealm();
+ int size = doGetSize();
+ if (oldIndex < 0 || oldIndex >= size)
+ throw new IndexOutOfBoundsException(
+ "oldIndex: " + oldIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$
+ if (newIndex < 0 || newIndex >= size)
+ throw new IndexOutOfBoundsException(
+ "newIndex: " + newIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$
+ Object element = remove(oldIndex);
+ add(newIndex, element);
+ return element;
+ }
+
+ public bool remove(Object o) {
+ getterCalled();
+ return super.remove(o);
+ }
+
+ // Bulk Modification Operations
+
+ public bool containsAll(Collection c) {
+ getterCalled();
+ return super.containsAll(c);
+ }
+
+ public bool addAll(Collection c) {
+ getterCalled();
+ return super.addAll(c);
+ }
+
+ public bool addAll(int index, Collection c) {
+ getterCalled();
+ return super.addAll(c);
+ }
+
+ public bool removeAll(Collection c) {
+ getterCalled();
+ return super.removeAll(c);
+ }
+
+ public bool retainAll(Collection c) {
+ getterCalled();
+ return super.retainAll(c);
+ }
+
+ // Comparison and hashing
+
+ public override equals_t opEquals(Object o) {
+ getterCalled();
+ return super.equals(o);
+ }
+
+ public override hash_t toHash() {
+ getterCalled();
+ return super.hashCode();
+ }
+
+ public int indexOf(Object o) {
+ getterCalled();
+ return super.indexOf(o);
+ }
+
+ public int lastIndexOf(Object o) {
+ getterCalled();
+ return super.lastIndexOf(o);
+ }
+
+ public Realm getRealm() {
+ return changeSupport.getRealm();
+ }
+
+ /**
+ * Asserts that the realm is the current realm.
+ *
+ * @see Realm#isCurrent()
+ * @throws AssertionFailedException
+ * if the realm is not the current realm
+ */
+ protected void checkRealm() {
+ Assert.isTrue(getRealm().isCurrent(),
+ "This operation must be run within the observable's realm"); //$NON-NLS-1$
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ComputedList.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ComputedList.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,290 @@
+/************************************************************************************************************
+ * 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
+ ***********************************************************************************************************/
+package org.eclipse.core.databinding.observable.list;
+
+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
+ *
+ * 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.
+ *
+ *
+ * @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 null
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 null
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.
+ *
+ *
+ * The Runnable calls calculate and stores the result in cachedList.
+ *
+ *
+ *
+ * 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.
+ *
+ *
+ *
+ * The IChangeListener is attached to every dependency.
+ *
+ *
+ */
+ 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();
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/IListChangeListener.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/IListChangeListener.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.list;
+
+import org.eclipse.core.databinding.observable.IObservablesListener;
+
+/**
+ * Listener for changes to observable lists.
+ *
+ * @since 1.0
+ */
+public interface IListChangeListener : IObservablesListener {
+
+ /**
+ * Handle a change to an observable list. The change is described by the
+ * diff object. The given event object must only be used locally in this
+ * method because it may be reused for other change notifications. The diff
+ * object referenced by the event is immutable and may be used non-locally.
+ *
+ * @param event
+ */
+ void handleListChange(ListChangeEvent event);
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/IObservableList.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/IObservableList.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,198 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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 - bug 167204
+ * Matthew Hall - bug 208858
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.list;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.eclipse.core.databinding.observable.IObservableCollection;
+
+/**
+ * A list whose changes can be tracked by list change listeners.
+ *
+ * @noextend This interface is not intended to be extended by clients.
+ * @noimplement This interface is not intended to be implemented by clients.
+ * Clients should instead subclass one of the framework classes
+ * that implement this interface. Note that direct implementers of
+ * this interface outside of the framework will be broken in future
+ * releases when methods are added to this interface.
+ *
+ * @see AbstractObservableList
+ * @see ObservableList
+ *
+ * @since 1.0
+ */
+public interface IObservableList : List, IObservableCollection {
+
+ /**
+ * Adds the given list change listener to the list of list change listeners.
+ * @param listener
+ */
+ public void addListChangeListener(IListChangeListener listener);
+
+ /**
+ * Removes the given list change listener from the list of list change listeners.
+ * Has no effect if the given listener is not registered as a list change listener.
+ *
+ * @param listener
+ */
+ public void removeListChangeListener(IListChangeListener listener);
+
+ /**
+ * @TrackedGetter
+ */
+ public int size();
+
+ /**
+ * @TrackedGetter
+ */
+ public bool isEmpty();
+
+ /**
+ * @TrackedGetter
+ */
+ public bool contains(Object o);
+
+ /**
+ * @TrackedGetter
+ */
+ public Iterator iterator();
+
+ /**
+ * @TrackedGetter
+ */
+ public Object[] toArray();
+
+ /**
+ * @TrackedGetter
+ */
+ public Object[] toArray(Object a[]);
+
+ /**
+ *
+ */
+ public bool add(Object o);
+
+ /**
+ *
+ */
+ public bool remove(Object o);
+
+ /**
+ * @TrackedGetter
+ */
+ public bool containsAll(Collection c);
+
+ /**
+ *
+ */
+ public bool addAll(Collection c);
+
+ /**
+ *
+ */
+ public bool addAll(int index, Collection c);
+
+ /**
+ *
+ */
+ public bool removeAll(Collection c);
+
+ /**
+ *
+ */
+ public bool retainAll(Collection c);
+
+ /**
+ * @TrackedGetter
+ */
+ public override equals_t opEquals(Object o);
+
+ /**
+ * @TrackedGetter
+ */
+ public override hash_t toHash();
+
+ /**
+ * @TrackedGetter
+ */
+ public Object get(int index);
+
+ /**
+ *
+ */
+ public Object set(int index, Object element);
+
+ /**
+ * Moves the element located at oldIndex
to
+ * newIndex
. This method is equivalent to calling
+ * add(newIndex, remove(oldIndex))
.
+ *
+ * Implementors should deliver list change notification for the remove and
+ * add operations in the same ListChangeEvent, as this allows
+ * {@link ListDiff#acceptcast(ListDiffVisitor)} to recognize the operation as a
+ * move.
+ *
+ * @param oldIndex
+ * the element's position before the move. Must be within the
+ * range 0 <= oldIndex < size()
.
+ * @param newIndex
+ * the element's position after the move. Must be within the
+ * range 0 <= newIndex < size()
.
+ * @return the element that was moved.
+ * @throws IndexOutOfBoundsException
+ * if either argument is out of range (0 <= index < size()
).
+ * @see ListDiffVisitor#handleMove(int, int, Object)
+ * @see ListDiff#acceptcast(ListDiffVisitor)
+ * @since 1.1
+ */
+ public Object move(int oldIndex, int newIndex);
+
+ /**
+ *
+ */
+ public Object remove(int index);
+
+ /**
+ * @TrackedGetter
+ */
+ public int indexOf(Object o);
+
+ /**
+ * @TrackedGetter
+ */
+ public int lastIndexOf(Object o);
+
+ /**
+ * @TrackedGetter
+ */
+ public ListIterator listIterator();
+
+ /**
+ * @TrackedGetter
+ */
+ public ListIterator listIterator(int index);
+
+ /**
+ * @TrackedGetter
+ */
+ public List subList(int fromIndex, int toIndex);
+
+ /**
+ * @return the type of the elements or null
if untyped
+ */
+ Object getElementType();
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListChangeEvent.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListChangeEvent.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.list;
+
+import org.eclipse.core.databinding.observable.IObservablesListener;
+import org.eclipse.core.databinding.observable.ObservableEvent;
+
+/**
+ * List change event describing an incremental change of an
+ * {@link IObservableList} object.
+ *
+ * @since 1.0
+ */
+public class ListChangeEvent : ObservableEvent {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -9154315534258776672L;
+
+ static final Object TYPE = new Object();
+
+ /**
+ * Description of the change to the source observable list. Listeners must
+ * not change this field.
+ */
+ public ListDiff diff;
+
+ /**
+ * Creates a new list change event.
+ *
+ * @param source
+ * the source observable list
+ * @param diff
+ * the list change
+ */
+ public this(IObservableList source, ListDiff diff) {
+ super(source);
+ this.diff = diff;
+ }
+
+ /**
+ * Returns the observable list from which this event originated.
+ *
+ * @return the observable list from which this event originated
+ */
+ public IObservableList getObservableList() {
+ return cast(IObservableList) getSource();
+ }
+
+ protected void dispatch(IObservablesListener listener) {
+ (cast(IListChangeListener) listener).handleListChange(this);
+ }
+
+ protected Object getListenerType() {
+ return TYPE;
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListDiff.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListDiff.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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
+ * Matthew Hall - bug 208858
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.list;
+
+import org.eclipse.core.internal.databinding.Util;
+
+/**
+ * Object describing a diff between two lists.
+ *
+ * @since 1.0
+ */
+public abstract class ListDiff {
+
+ /**
+ * Returns a ListDiffEntry array representing the differences in the list,
+ * in the order they are to be processed.
+ *
+ * @return a ListDiffEntry array representing the differences in the list,
+ * in the order they are to be processed.
+ */
+ public abstract ListDiffEntry[] getDifferences();
+
+ /**
+ * Traverses the {@link #getDifferences()} array, calling the appropriate
+ * method in visitor
for each difference.
+ *
+ * - {@link ListDiffVisitor#handleReplace(int, Object, Object)} is called
+ * whenever a remove entry is immediately followed by an add entry which
+ * shares the same list index.
+ *
- {@link ListDiffVisitor#handleMove(int, int, Object)} is called
+ * whenever a remove entry is immediately followed by an add entry with an
+ * equivalent element.
+ *
- {@link ListDiffVisitor#handleRemove(int, Object)} is called whenever
+ * a remove entry does not match conditions 1 or 2.
+ *
- {@link ListDiffVisitor#handleAdd(int, Object)} is called whenever an
+ * add entry does not match conditions in 1 or 2.
+ *
+ *
+ * @param visitor
+ * the visitor to receive callbacks.
+ * @see ListDiffVisitor
+ * @since 1.1
+ */
+ public void accept(ListDiffVisitor visitor) {
+ ListDiffEntry[] differences = getDifferences();
+ for (int i = 0; i < differences.length; i++) {
+ ListDiffEntry entry = differences[i];
+ int position = entry.getPosition();
+ Object element = entry.getElement();
+ bool addition = entry.isAddition();
+
+ if (!addition && i + 1 < differences.length) {
+ ListDiffEntry entry2 = differences[i + 1];
+ if (entry2.isAddition()) {
+ int position2 = entry2.getPosition();
+ Object element2 = entry2.getElement();
+ if (position is position2) {
+ visitor.handleReplace(position, element, element2);
+ i++;
+ continue;
+ }
+ if (Util.equals(element, element2)) {
+ visitor.handleMove(position, position2, element);
+ i++;
+ continue;
+ }
+ }
+ }
+ if (addition)
+ visitor.handleAdd(position, element);
+ else
+ visitor.handleRemove(position, element);
+ }
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ ListDiffEntry[] differences = getDifferences();
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(getClass().getName());
+
+ if (differences is null || differences.length is 0) {
+ buffer
+ .append("{}"); //$NON-NLS-1$
+ } else {
+ buffer
+ .append("{"); //$NON-NLS-1$
+
+ for (int i = 0; i < differences.length; i++) {
+ if (i > 0)
+ buffer.append(", "); //$NON-NLS-1$
+
+ buffer
+ .append("difference[") //$NON-NLS-1$
+ .append(i)
+ .append("] [") //$NON-NLS-1$
+ .append(differences[i] !is null ? differences[i].toString() : "null") //$NON-NLS-1$
+ .append("]"); //$NON-NLS-1$
+ }
+ buffer.append("}"); //$NON-NLS-1$
+ }
+
+ return buffer.toString();
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListDiffEntry.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListDiffEntry.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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
+ ******************************************************************************/
+
+package org.eclipse.core.databinding.observable.list;
+
+/**
+ * A single addition of an element to a list or removal of an element from a list.
+ *
+ * @since 1.0
+ */
+public abstract class ListDiffEntry {
+
+ /**
+ * @return the 0-based position of the addition or removal
+ */
+ public abstract int getPosition();
+
+ /**
+ * @return true if this represents an addition, false if this represents a removal
+ */
+ public abstract bool isAddition();
+
+ /**
+ * @return the element that was added or removed
+ */
+ public abstract Object getElement();
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ buffer
+ .append(this.getClass().getName())
+ .append("{position [") //$NON-NLS-1$
+ .append(getPosition())
+ .append("], isAddition [") //$NON-NLS-1$
+ .append(isAddition())
+ .append("], element [") //$NON-NLS-1$
+ .append(getElement() !is null ? getElement().toString() : "null") //$NON-NLS-1$
+ .append("]}"); //$NON-NLS-1$
+
+ return buffer.toString();
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListDiffVisitor.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListDiffVisitor.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * 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 (bug 208858)
+ ******************************************************************************/
+
+package org.eclipse.core.databinding.observable.list;
+
+import java.util.List;
+
+/**
+ * A visitor for processing differences in a ListDiff.
+ *
+ * @see ListDiff#acceptcast(ListDiffVisitor)
+ * @since 1.1
+ */
+public abstract class ListDiffVisitor {
+ /**
+ * Notifies the visitor that element
was added to the list at
+ * position index
.
+ *
+ * @param index
+ * the index where the element was added
+ * @param element
+ * the element that was added
+ */
+ public abstract void handleAdd(int index, Object element);
+
+ /**
+ * Notifies the visitor that element
was removed from the
+ * list at position index
.
+ *
+ * @param index
+ * the index where the element was removed
+ * @param element
+ * the element that was removed
+ */
+ public abstract void handleRemove(int index, Object element);
+
+ /**
+ * Notifies the visitor that element
was moved in the list
+ * from position oldIndex
to position newIndex
.
+ *
+ * The default implementation of this method calls
+ * {@link #handleRemove(int, Object)} with the old position, then
+ * {@link #handleAdd(int, Object)} with the new position. Clients which are
+ * interested in recognizing "moves" in a list (i.e. calls to
+ * {@link IObservableList#move(int, int)}) should override this method.
+ *
+ * @param oldIndex
+ * the index that the element was moved from.
+ * @param newIndex
+ * the index that the element was moved to.
+ * @param element
+ * the element that was moved
+ * @see IObservableList#move(int, int)
+ */
+ public void handleMove(int oldIndex, int newIndex, Object element) {
+ handleRemove(oldIndex, element);
+ handleAdd(newIndex, element);
+ }
+
+ /**
+ * Notifies the visitor that oldElement
, located at position
+ * index
in the list, was replaced by newElement
.
+ *
+ * The default implementation of this method calls
+ * {@link #handleRemove(int, Object)} with the old element, then
+ * {@link #handleAdd(int, Object)} with the new element. Clients which are
+ * interested in recognizing "replaces" in a list (i.e. calls to
+ * {@link List#set(int, Object)}) should override this method.
+ *
+ * @param index
+ * the index where the element was replaced.
+ * @param oldElement
+ * the element being replaced.
+ * @param newElement
+ * the element that replaced oldElement.
+ * @see List#set(int, Object)
+ */
+ public void handleReplace(int index, Object oldElement, Object newElement) {
+ handleRemove(index, oldElement);
+ handleAdd(index, newElement);
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ObservableList.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ObservableList.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,379 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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 164653, 167204
+ * Matthew Hall - bugs 208858, 208332, 245183
+ * Tom Schindl - bug 245183
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.list;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.eclipse.core.databinding.observable.AbstractObservable;
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.ObservableTracker;
+import org.eclipse.core.databinding.observable.Realm;
+
+/**
+ *
+ * Abstract implementation of {@link IObservableList}, based on an underlying regular list.
+ *
+ * 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.
+ *
+ * @since 1.0
+ *
+ */
+public abstract class ObservableList : AbstractObservable ,
+ IObservableList {
+
+ protected List wrappedList;
+
+ /**
+ * Stale state of the list. Access must occur in the current realm.
+ */
+ private bool stale = false;
+
+ private Object elementType;
+
+ protected this(List wrappedList, Object elementType) {
+ this(Realm.getDefault(), wrappedList, elementType);
+ }
+
+ protected this(Realm realm, List wrappedList, Object elementType) {
+ super(realm);
+ this.wrappedList = wrappedList;
+ this.elementType = elementType;
+ }
+
+ public synchronized void addListChangeListener(IListChangeListener listener) {
+ addListener(ListChangeEvent.TYPE, listener);
+ }
+
+ public synchronized void removeListChangeListener(IListChangeListener listener) {
+ removeListener(ListChangeEvent.TYPE, listener);
+ }
+
+ protected void fireListChange(ListDiff diff) {
+ // fire general change event first
+ super.fireChange();
+ fireEvent(new ListChangeEvent(this, diff));
+ }
+
+ public bool contains(Object o) {
+ getterCalled();
+ return wrappedList.contains(o);
+ }
+
+ public bool containsAll(Collection c) {
+ getterCalled();
+ return wrappedList.containsAll(c);
+ }
+
+ public override equals_t opEquals(Object o) {
+ getterCalled();
+
+ if (o is this)
+ return true;
+ if (o is null)
+ return false;
+ if (getClass() is o.getClass()) {
+ return wrappedList.equals((cast(ObservableList) o).wrappedList);
+ }
+
+ return wrappedList.equals(o);
+ }
+
+ public override hash_t toHash() {
+ getterCalled();
+ return wrappedList.hashCode();
+ }
+
+ public bool isEmpty() {
+ getterCalled();
+ return wrappedList.isEmpty();
+ }
+
+ public Iterator iterator() {
+ getterCalled();
+ final Iterator wrappedIterator = wrappedList.iterator();
+ return new class() Iterator {
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool hasNext() {
+ return wrappedIterator.hasNext();
+ }
+
+ public Object next() {
+ return wrappedIterator.next();
+ }
+ };
+ }
+
+ public int size() {
+ getterCalled();
+ return wrappedList.size();
+ }
+
+ public Object[] toArray() {
+ getterCalled();
+ return wrappedList.toArray();
+ }
+
+ public Object[] toArray(Object[] a) {
+ getterCalled();
+ return wrappedList.toArray(a);
+ }
+
+ public String toString() {
+ getterCalled();
+ return wrappedList.toString();
+ }
+
+ /**
+ * @TrackedGetter
+ */
+ public Object get(int index) {
+ getterCalled();
+ return wrappedList.get(index);
+ }
+
+ /**
+ * @TrackedGetter
+ */
+ public int indexOf(Object o) {
+ getterCalled();
+ return wrappedList.indexOf(o);
+ }
+
+ /**
+ * @TrackedGetter
+ */
+ public int lastIndexOf(Object o) {
+ getterCalled();
+ return wrappedList.lastIndexOf(o);
+ }
+
+ // List Iterators
+
+ /**
+ * @TrackedGetter
+ */
+ public ListIterator listIterator() {
+ return listIterator(0);
+ }
+
+ /**
+ * @TrackedGetter
+ */
+ public ListIterator listIterator(int index) {
+ getterCalled();
+ final ListIterator wrappedIterator = wrappedList.listIterator(index);
+ return new class() ListIterator {
+
+ public int nextIndex() {
+ return wrappedIterator.nextIndex();
+ }
+
+ public int previousIndex() {
+ return wrappedIterator.previousIndex();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool hasNext() {
+ return wrappedIterator.hasNext();
+ }
+
+ public bool hasPrevious() {
+ return wrappedIterator.hasPrevious();
+ }
+
+ public Object next() {
+ return wrappedIterator.next();
+ }
+
+ public Object previous() {
+ return wrappedIterator.previous();
+ }
+
+ public void add(Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void set(Object o) {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+
+ public List subList(final int fromIndex, final int toIndex) {
+ getterCalled();
+ if (fromIndex < 0 || fromIndex > toIndex || toIndex > size()) {
+ throw new IndexOutOfBoundsException();
+ }
+ return new AbstractObservableList(getRealm()) {
+
+ public Object getElementType() {
+ return this.outer.getElementType();
+ }
+
+ public Object get(int location) {
+ return this.outer.get(fromIndex + location);
+ }
+
+ protected int doGetSize() {
+ return toIndex - fromIndex;
+ }
+ };
+ }
+
+ protected void getterCalled() {
+ ObservableTracker.getterCalled(this);
+ }
+
+ public Object set(int index, Object element) {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Moves the element located at oldIndex
to
+ * newIndex
. This method is equivalent to calling
+ * add(newIndex, remove(oldIndex))
.
+ *
+ * Subclasses should override this method to deliver list change
+ * notification for the remove and add operations in the same
+ * ListChangeEvent, as this allows {@link ListDiff#acceptcast(ListDiffVisitor)}
+ * to recognize the operation as a move.
+ *
+ * @param oldIndex
+ * the element's position before the move. Must be within the
+ * range 0 <= oldIndex < size()
.
+ * @param newIndex
+ * the element's position after the move. Must be within the
+ * range 0 <= newIndex < size()
.
+ * @return the element that was moved.
+ * @throws IndexOutOfBoundsException
+ * if either argument is out of range (0 <= index < size()
).
+ * @see ListDiffVisitor#handleMove(int, int, Object)
+ * @see ListDiff#acceptcast(ListDiffVisitor)
+ * @since 1.1
+ */
+ public Object move(int oldIndex, int newIndex) {
+ checkRealm();
+ int size = wrappedList.size();
+ if (oldIndex < 0 || oldIndex >= size)
+ throw new IndexOutOfBoundsException(
+ "oldIndex: " + oldIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$
+ if (newIndex < 0 || newIndex >= size)
+ throw new IndexOutOfBoundsException(
+ "newIndex: " + newIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$
+ Object element = remove(oldIndex);
+ add(newIndex, element);
+ return element;
+ }
+
+ public Object remove(int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool add(Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void add(int index, Object element) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool addAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool addAll(int index, Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool remove(Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool removeAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool retainAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns the stale state. Must be invoked from the current realm.
+ *
+ * @return stale state
+ */
+ public bool isStale() {
+ getterCalled();
+ return stale;
+ }
+
+ /**
+ * Sets the stale state. Must be invoked from the current realm.
+ *
+ * @param stale
+ * The stale state to list. This will fire a stale event if the
+ * given bool is true and this observable list was not already
+ * stale.
+ */
+ public void setStale(bool stale) {
+ checkRealm();
+
+ bool wasStale = this.stale;
+ this.stale = stale;
+ if (!wasStale && stale) {
+ fireStale();
+ }
+ }
+
+ protected void fireChange() {
+ throw new RuntimeException("fireChange should not be called, use fireListChange() instead"); //$NON-NLS-1$
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.provisional.databinding.observable.AbstractObservable#dispose()
+ */
+ public synchronized void dispose() {
+ super.dispose();
+ }
+
+ public Object getElementType() {
+ return elementType;
+ }
+
+ protected void updateWrappedList(List newList) {
+ List oldList = wrappedList;
+ ListDiff listDiff = Diffs.computeListDiff(oldList, newList);
+ wrappedList = newList;
+ fireListChange(listDiff);
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/WritableList.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/WritableList.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,239 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2008 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 - bug 164653
+ * Brad Reynolds - bug 167204
+ * Gautam Saggar - bug 169529
+ * Brad Reynolds - bug 147515
+ * Matthew Hall - bug 208858, 213145
+ *******************************************************************************/
+package org.eclipse.core.databinding.observable.list;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.Realm;
+
+/**
+ * Mutable observable list backed by an ArrayList.
+ *
+ *
+ * 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.
+ *
+ *
+ * @since 1.0
+ */
+public class WritableList : ObservableList {
+
+ /**
+ * Creates an empty writable list in the default realm with a
+ * null
element type.
+ *
+ */
+ public this() {
+ this(Realm.getDefault());
+ }
+
+ /**
+ * Creates an empty writable list with a null
element type.
+ *
+ * @param realm
+ */
+ public this(Realm realm) {
+ this(realm, new ArrayList(), null);
+ }
+
+ /**
+ * Constructs a new instance with the default realm.
+ *
+ * @param toWrap
+ * @param elementType
+ * can be null
+ */
+ public this(List toWrap, Object elementType) {
+ this(Realm.getDefault(), toWrap, elementType);
+ }
+
+ /**
+ * Creates a writable list containing elements of the given type, wrapping
+ * an existing client-supplied list.
+ *
+ * @param realm
+ * @param toWrap
+ * The java.utilList to wrap
+ * @param elementType
+ * can be null
+ */
+ public this(Realm realm, List toWrap, Object elementType) {
+ super(realm, toWrap, elementType);
+ }
+
+ public Object set(int index, Object element) {
+ checkRealm();
+ Object oldElement = wrappedList.set(index, element);
+ fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(index,
+ false, oldElement), Diffs.createListDiffEntry(index, true,
+ element)));
+ return oldElement;
+ }
+
+ /**
+ * @since 1.1
+ */
+ public Object move(int oldIndex, int newIndex) {
+ checkRealm();
+ int size = wrappedList.size();
+ if (oldIndex < 0 || oldIndex >= size)
+ throw new IndexOutOfBoundsException(
+ "oldIndex: " + oldIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$
+ if (newIndex < 0 || newIndex >= size)
+ throw new IndexOutOfBoundsException(
+ "newIndex: " + newIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$
+ if (oldIndex is newIndex)
+ return wrappedList.get(oldIndex);
+ Object element = wrappedList.remove(oldIndex);
+ wrappedList.add(newIndex, element);
+ fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(oldIndex,
+ false, element), Diffs.createListDiffEntry(newIndex, true,
+ element)));
+ return element;
+ }
+
+ public Object remove(int index) {
+ checkRealm();
+ Object oldElement = wrappedList.remove(index);
+ fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(index,
+ false, oldElement)));
+ return oldElement;
+ }
+
+ public bool add(Object element) {
+ checkRealm();
+ bool added = wrappedList.add(element);
+ if (added) {
+ fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(
+ wrappedList.size() - 1, true, element)));
+ }
+ return added;
+ }
+
+ public void add(int index, Object element) {
+ checkRealm();
+ wrappedList.add(index, element);
+ fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(index,
+ true, element)));
+ }
+
+ public bool addAll(Collection c) {
+ checkRealm();
+ ListDiffEntry[] entries = new ListDiffEntry[c.size()];
+ int i = 0;
+ int addIndex = wrappedList.size();
+ for (Iterator it = c.iterator(); it.hasNext();) {
+ Object element = it.next();
+ entries[i++] = Diffs.createListDiffEntry(addIndex++, true, element);
+ }
+ bool added = wrappedList.addAll(c);
+ fireListChange(Diffs.createListDiff(entries));
+ return added;
+ }
+
+ public bool addAll(int index, Collection c) {
+ checkRealm();
+ ListDiffEntry[] entries = new ListDiffEntry[c.size()];
+ int i = 0;
+ int addIndex = index;
+ for (Iterator it = c.iterator(); it.hasNext();) {
+ Object element = it.next();
+ entries[i++] = Diffs.createListDiffEntry(addIndex++, true, element);
+ }
+ bool added = wrappedList.addAll(index, c);
+ fireListChange(Diffs.createListDiff(entries));
+ return added;
+ }
+
+ public bool remove(Object o) {
+ checkRealm();
+ int index = wrappedList.indexOf(o);
+ if (index is -1) {
+ return false;
+ }
+ wrappedList.remove(index);
+ fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(index,
+ false, o)));
+ return true;
+ }
+
+ public bool removeAll(Collection c) {
+ checkRealm();
+ List entries = new ArrayList();
+ for (Iterator it = c.iterator(); it.hasNext();) {
+ Object element = it.next();
+ int removeIndex = wrappedList.indexOf(element);
+ if (removeIndex !is -1) {
+ wrappedList.remove(removeIndex);
+ entries.add(Diffs.createListDiffEntry(removeIndex, false,
+ element));
+ }
+ }
+ if (entries.size() > 0)
+ fireListChange(Diffs.createListDiff(cast(ListDiffEntry[]) entries
+ .toArray(new ListDiffEntry[entries.size()])));
+ return entries.size() > 0;
+ }
+
+ public bool retainAll(Collection c) {
+ checkRealm();
+ List entries = new ArrayList();
+ int removeIndex = 0;
+ for (Iterator it = wrappedList.iterator(); it.hasNext();) {
+ Object element = it.next();
+ if (!c.contains(element)) {
+ entries.add(Diffs.createListDiffEntry(removeIndex, false,
+ element));
+ it.remove();
+ } else {
+ // only increment if we haven't removed the current element
+ removeIndex++;
+ }
+ }
+ if (entries.size() > 0)
+ fireListChange(Diffs.createListDiff(cast(ListDiffEntry[]) entries
+ .toArray(new ListDiffEntry[entries.size()])));
+ return entries.size() > 0;
+ }
+
+ public void clear() {
+ checkRealm();
+ List entries = new ArrayList();
+ for (Iterator it = wrappedList.iterator(); it.hasNext();) {
+ Object element = it.next();
+ // always report 0 as the remove index
+ entries.add(Diffs.createListDiffEntry(0, false, element));
+ it.remove();
+ }
+ fireListChange(Diffs.createListDiff(cast(ListDiffEntry[]) entries
+ .toArray(new ListDiffEntry[entries.size()])));
+ }
+
+ /**
+ * @param elementType
+ * can be null
+ * @return new list with the default realm.
+ */
+ public static WritableList withElementType(Object elementType) {
+ return new WritableList(Realm.getDefault(), new ArrayList(),
+ elementType);
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/package.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/package.html Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ Package-level Javadoc
+
+
+Provides classes for observing changes in lists.
+
+Package Specification
+
+This package provides classes for observing changes in lists.
+
+
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/AbstractObservableMap.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/AbstractObservableMap.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,181 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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 - bug 164653
+ * Matthew Hall - bugs 118516, 240931
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.map;
+
+import java.util.AbstractMap;
+
+import org.eclipse.core.databinding.observable.ChangeEvent;
+import org.eclipse.core.databinding.observable.ChangeSupport;
+import org.eclipse.core.databinding.observable.IChangeListener;
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.StaleEvent;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.AssertionFailedException;
+
+/**
+ *
+ *
+ * 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.
+ *
+ * @since 1.0
+ */
+public abstract class AbstractObservableMap : AbstractMap ,
+ IObservableMap {
+
+ private ChangeSupport changeSupport;
+
+ private bool stale;
+
+ /**
+ */
+ public this() {
+ this(Realm.getDefault());
+ }
+
+ /**
+ *
+ */
+ protected void lastListenerRemoved() {
+ }
+
+ /**
+ *
+ */
+ protected void firstListenerAdded() {
+ }
+
+ /**
+ * @param realm
+ */
+ public this(Realm realm) {
+ Assert.isNotNull(realm, "Realm cannot be null"); //$NON-NLS-1$
+ changeSupport = new class(realm) ChangeSupport {
+ protected void firstListenerAdded() {
+ this.outer.firstListenerAdded();
+ }
+ protected void lastListenerRemoved() {
+ this.outer.lastListenerRemoved();
+ }
+ };
+ }
+
+ public synchronized void addMapChangeListener(IMapChangeListener listener) {
+ if (changeSupport !is null) {
+ changeSupport.addListener(MapChangeEvent.TYPE, listener);
+ }
+ }
+
+ public synchronized void removeMapChangeListener(IMapChangeListener listener) {
+ if (changeSupport !is null) {
+ changeSupport.removeListener(MapChangeEvent.TYPE, listener);
+ }
+ }
+
+ public synchronized void addChangeListener(IChangeListener listener) {
+ if (changeSupport !is null) {
+ changeSupport.addChangeListener(listener);
+ }
+ }
+
+ public synchronized void addStaleListener(IStaleListener listener) {
+ if (changeSupport !is null) {
+ changeSupport.addStaleListener(listener);
+ }
+ }
+
+ public synchronized void dispose() {
+ if (changeSupport !is null) {
+ changeSupport.dispose();
+ changeSupport = null;
+ }
+ }
+
+ public Realm getRealm() {
+ if (changeSupport !is null) {
+ return changeSupport.getRealm();
+ }
+ return null;
+ }
+
+ public bool isStale() {
+ checkRealm();
+ return stale;
+ }
+
+ public synchronized void removeChangeListener(IChangeListener listener) {
+ if (changeSupport !is null) {
+ changeSupport.removeChangeListener(listener);
+ }
+ }
+
+ public synchronized void removeStaleListener(IStaleListener listener) {
+ if (changeSupport !is null) {
+ changeSupport.removeStaleListener(listener);
+ }
+ }
+
+ /**
+ * Sets the stale state. Must be invoked from the current realm.
+ *
+ * @param stale
+ */
+ public void setStale(bool stale) {
+ checkRealm();
+ this.stale = stale;
+ if (stale) {
+ fireStale();
+ }
+ }
+
+ /**
+ * Fires stale events. Must be invoked from current realm.
+ */
+ protected void fireStale() {
+ checkRealm();
+ changeSupport.fireEvent(new StaleEvent(this));
+ }
+
+ /**
+ * Fires change events. Must be invoked from current realm.
+ */
+ protected void fireChange() {
+ checkRealm();
+ changeSupport.fireEvent(new ChangeEvent(this));
+ }
+
+ /**
+ * Fires map change events. Must be invoked from current realm.
+ *
+ * @param diff
+ */
+ protected void fireMapChange(MapDiff diff) {
+ checkRealm();
+ changeSupport.fireEvent(new MapChangeEvent(this, diff));
+ }
+
+ /**
+ * Asserts that the realm is the current realm.
+ *
+ * @see Realm#isCurrent()
+ * @throws AssertionFailedException
+ * if the realm is not the current realm
+ */
+ protected void checkRealm() {
+ Assert.isTrue(getRealm().isCurrent(),
+ "This operation must be run within the observable's realm"); //$NON-NLS-1$
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/BidirectionalMap.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/BidirectionalMap.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,104 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+package org.eclipse.core.databinding.observable.map;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.Realm;
+
+/**
+ *
+ *
+ * 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.
+ *
+ * @since 1.0
+ *
+ */
+public class BidirectionalMap : ObservableMap {
+
+ private Map valueToElements = new HashMap();
+
+ private IMapChangeListener mapListener = new class() IMapChangeListener {
+
+ public void handleMapChange(MapChangeEvent event) {
+ MapDiff diff = event.diff;
+ for (Iterator it = diff.getAddedKeys().iterator(); it.hasNext();) {
+ Object addedKey = it.next();
+ addMapping(addedKey, diff.getNewValue(addedKey));
+ }
+ for (Iterator it = diff.getChangedKeys().iterator(); it.hasNext();) {
+ Object changedKey = it.next();
+ removeMapping(changedKey, diff.getOldValue(changedKey));
+ addMapping(changedKey, diff.getNewValue(changedKey));
+ }
+ for (Iterator it = diff.getRemovedKeys().iterator(); it.hasNext();) {
+ Object removedKey = it.next();
+ removeMapping(removedKey, diff.getOldValue(removedKey));
+ }
+ fireMapChange(diff);
+ }
+ };
+
+ /**
+ * @param wrappedMap
+ */
+ public this(IObservableMap wrappedMap) {
+ super(wrappedMap.getRealm(), wrappedMap);
+ wrappedMap.addMapChangeListener(mapListener);
+ for (Iterator it = wrappedMap.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = cast(Entry) it.next();
+ addMapping(entry.getKey(), entry.getValue());
+ }
+ }
+
+ /**
+ * @param key
+ * @param value
+ */
+ private void addMapping(Object key, Object value) {
+ Object elementOrSet = valueToElements.get(value);
+ if (elementOrSet is null) {
+ valueToElements.put(value, key);
+ return;
+ }
+ if (!( null !is cast(Set)elementOrSet )) {
+ elementOrSet = new HashSet(Collections.singleton(elementOrSet));
+ valueToElements.put(value, elementOrSet);
+ }
+ Set set = cast(Set) elementOrSet;
+ set.add(key);
+ }
+
+ /**
+ * @param functionValue
+ * @param element
+ */
+ private void removeMapping(Object functionValue, Object element) {
+ Object elementOrSet = valueToElements.get(functionValue);
+ if ( null !is cast(Set)elementOrSet ) {
+ Set set = cast(Set) elementOrSet;
+ set.remove(element);
+ if (set.size() is 0) {
+ valueToElements.remove(functionValue);
+ }
+ } else {
+ valueToElements.remove(functionValue);
+ }
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/CompositeMap.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/CompositeMap.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,361 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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
+ *******************************************************************************/
+package org.eclipse.core.databinding.observable.map;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
+import org.eclipse.core.databinding.observable.set.WritableSet;
+import org.eclipse.core.runtime.Assert;
+
+/**
+ * A read-only observable map formed by the composition of two observable maps.
+ * If map1 maps keys a:A to values b1:B, and map2 maps keys b2:B to values c:C,
+ * the composite map maps keys a:A to values c:C. For example, map1 could map
+ * Order objects to their corresponding Customer objects, and map2 could map
+ * Customer objects to their "last name" property of type String. The composite
+ * map of map1 and map2 would then map Order objects to their customers' last
+ * names.
+ *
+ *
+ * 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.
+ *
+ *
+ * @since 1.1
+ *
+ */
+public class CompositeMap : ObservableMap {
+
+ private Map valueToElements = new HashMap();
+
+ // adds that need to go through the second map and thus will be picked up by
+ // secondMapListener.
+ private Set pendingAdds = new HashSet();
+
+ // Removes that need to go through the second map and thus will be picked up
+ // by
+ // secondMapListener. Maps from value being removed to key being removed.
+ private Map pendingRemoves = new HashMap();
+
+ // Changes that need to go through the second map and thus will be picked up
+ // by
+ // secondMapListener. Maps from old value to new value and new value to old
+ // value.
+ private Map pendingChanges = new HashMap();
+
+ private IMapChangeListener firstMapListener = new class() IMapChangeListener {
+
+ public void handleMapChange(MapChangeEvent event) {
+ MapDiff diff = event.diff;
+ Set rangeSetAdditions = new HashSet();
+ Set rangeSetRemovals = new HashSet();
+ final Set adds = new HashSet();
+ final Set changes = new HashSet();
+ final Set removes = new HashSet();
+ final Map oldValues = new HashMap();
+
+ for (Iterator it = diff.getAddedKeys().iterator(); it.hasNext();) {
+ Object addedKey = it.next();
+ Object newValue = diff.getNewValue(addedKey);
+ addMapping(addedKey, newValue);
+ if (!rangeSet.contains(newValue)) {
+ pendingAdds.add(newValue);
+ rangeSetAdditions.add(newValue);
+ } else {
+ adds.add(addedKey);
+ wrappedMap.put(addedKey, secondMap.get(newValue));
+ }
+ }
+ for (Iterator it = diff.getChangedKeys().iterator(); it.hasNext();) {
+ Object changedKey = it.next();
+ Object oldValue = diff.getOldValue(changedKey);
+ Object newValue = diff.getNewValue(changedKey);
+ bool removed = removeMapping(changedKey, oldValue);
+ addMapping(changedKey, newValue);
+ bool added = !rangeSet.contains(newValue);
+ if (removed) {
+ pendingRemoves.put(oldValue, changedKey);
+ rangeSetRemovals.add(oldValue);
+ }
+ if (added) {
+ pendingAdds.add(newValue);
+ rangeSetAdditions.add(newValue);
+ }
+ if (added || removed) {
+ pendingChanges.put(oldValue, newValue);
+ pendingChanges.put(newValue, oldValue);
+ } else {
+ changes.add(changedKey);
+ oldValues.put(changedKey, oldValue);
+ wrappedMap.put(changedKey, secondMap.get(newValue));
+ }
+ }
+ for (Iterator it = diff.getRemovedKeys().iterator(); it.hasNext();) {
+ Object removedKey = it.next();
+ Object oldValue = diff.getOldValue(removedKey);
+ if (removeMapping(removedKey, oldValue)) {
+ pendingRemoves.put(oldValue, removedKey);
+ rangeSetRemovals.add(oldValue);
+ } else {
+ removes.add(removedKey);
+ oldValues.put(removedKey, secondMap.get(oldValue));
+ wrappedMap.remove(removedKey);
+ }
+ }
+
+ if (adds.size() > 0 || removes.size() > 0 || changes.size() > 0) {
+ fireMapChange(new class() MapDiff {
+
+ public Set getAddedKeys() {
+ return adds;
+ }
+
+ public Set getChangedKeys() {
+ return changes;
+ }
+
+ public Object getNewValue(Object key) {
+ return wrappedMap.get(key);
+ }
+
+ public Object getOldValue(Object key) {
+ return oldValues.get(key);
+ }
+
+ public Set getRemovedKeys() {
+ return removes;
+ }
+ });
+ }
+
+ if (rangeSetAdditions.size() > 0 || rangeSetRemovals.size() > 0) {
+ rangeSet.addAndRemove(rangeSetAdditions, rangeSetRemovals);
+ }
+ }
+ };
+
+ private IMapChangeListener secondMapListener = new class() IMapChangeListener {
+
+ public void handleMapChange(MapChangeEvent event) {
+ MapDiff diff = event.diff;
+ final Set adds = new HashSet();
+ final Set changes = new HashSet();
+ final Set removes = new HashSet();
+ final Map oldValues = new HashMap();
+ final Map newValues = new HashMap();
+ Set addedKeys = new HashSet(diff.getAddedKeys());
+ Set removedKeys = new HashSet(diff.getRemovedKeys());
+
+ for (Iterator it = addedKeys.iterator(); it.hasNext();) {
+ Object addedKey = it.next();
+ Set elements = getElementsForValue(addedKey);
+ Object newValue = diff.getNewValue(addedKey);
+ if (pendingChanges.containsKey(addedKey)) {
+ Object oldKey = pendingChanges.remove(addedKey);
+ Object oldValue;
+ if (removedKeys.remove(oldKey)) {
+ oldValue = diff.getOldValue(oldKey);
+ } else {
+ oldValue = secondMap.get(oldKey);
+ }
+ pendingChanges.remove(oldKey);
+ pendingAdds.remove(addedKey);
+ pendingRemoves.remove(oldKey);
+ for (Iterator it2 = elements.iterator(); it2.hasNext();) {
+ Object element = it2.next();
+ changes.add(element);
+ oldValues.put(element, oldValue);
+ newValues.put(element, newValue);
+ wrappedMap.put(element, newValue);
+ }
+ } else if (pendingAdds.remove(addedKey)) {
+ for (Iterator it2 = elements.iterator(); it2.hasNext();) {
+ Object element = it2.next();
+ adds.add(element);
+ newValues.put(element, newValue);
+ wrappedMap.put(element, newValue);
+ }
+ } else {
+ Assert.isTrue(false, "unexpected case"); //$NON-NLS-1$
+ }
+ }
+ for (Iterator it = diff.getChangedKeys().iterator(); it.hasNext();) {
+ Object changedKey = it.next();
+ Set elements = getElementsForValue(changedKey);
+ for (Iterator it2 = elements.iterator(); it2.hasNext();) {
+ Object element = it2.next();
+ changes.add(element);
+ oldValues.put(element, diff.getOldValue(changedKey));
+ Object newValue = diff.getNewValue(changedKey);
+ newValues.put(element, newValue);
+ wrappedMap.put(element, newValue);
+ }
+ }
+ for (Iterator it = removedKeys.iterator(); it.hasNext();) {
+ Object removedKey = it.next();
+ Object element = pendingRemoves.remove(removedKey);
+ if (element !is null) {
+ if (pendingChanges.containsKey(removedKey)) {
+ Object newKey = pendingChanges.remove(removedKey);
+ pendingChanges.remove(newKey);
+ pendingAdds.remove(newKey);
+ pendingRemoves.remove(removedKey);
+ changes.add(element);
+ oldValues.put(element, diff.getOldValue(removedKey));
+ Object newValue = secondMap.get(newKey);
+ newValues.put(element, newValue);
+ wrappedMap.put(element, newValue);
+ } else {
+ removes.add(element);
+ Object oldValue = diff.getOldValue(removedKey);
+ oldValues.put(element, oldValue);
+ wrappedMap.remove(element);
+ }
+ } else {
+ Assert.isTrue(false, "unexpected case"); //$NON-NLS-1$
+ }
+ }
+
+ if (adds.size() > 0 || removes.size() > 0 || changes.size() > 0) {
+ fireMapChange(new class() MapDiff {
+
+ public Set getAddedKeys() {
+ return adds;
+ }
+
+ public Set getChangedKeys() {
+ return changes;
+ }
+
+ public Object getNewValue(Object key) {
+ return newValues.get(key);
+ }
+
+ public Object getOldValue(Object key) {
+ return oldValues.get(key);
+ }
+
+ public Set getRemovedKeys() {
+ return removes;
+ }
+ });
+ }
+ }
+ };
+
+ private IObservableMap firstMap;
+ private IObservableMap secondMap;
+
+ private static class WritableSetPlus : WritableSet {
+ void addAndRemove(Set additions, Set removals) {
+ wrappedSet.removeAll(removals);
+ wrappedSet.addAll(additions);
+ fireSetChange(Diffs.createSetDiff(additions, removals));
+ }
+ }
+
+ private WritableSetPlus rangeSet = new WritableSetPlus();
+
+ /**
+ * Creates a new composite map. Because the key set of the second map is
+ * determined by the value set of the given observable map
+ * firstMap
, it cannot be passed in as an argument. Instead,
+ * the second map will be created by calling
+ * secondMapFactory.createObservable(valueSet())
.
+ *
+ * @param firstMap
+ * the first map
+ * @param secondMapFactory
+ * a factory that creates the second map when given an observable
+ * set representing the value set of firstMap
.
+ */
+ public this(IObservableMap firstMap,
+ IObservableFactory secondMapFactory) {
+ super(firstMap.getRealm(), new HashMap());
+ this.firstMap = firstMap;
+ firstMap.addMapChangeListener(firstMapListener);
+ for (Iterator it = firstMap.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = cast(Entry) it.next();
+ addMapping(entry.getKey(), entry.getValue());
+ rangeSet.add(entry.getValue());
+ }
+ this.secondMap = cast(IObservableMap) secondMapFactory
+ .createObservable(rangeSet);
+ secondMap.addMapChangeListener(secondMapListener);
+ for (Iterator it = firstMap.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = cast(Entry) it.next();
+ wrappedMap.put(entry.getKey(), secondMap.get(entry.getValue()));
+ }
+ }
+
+ /**
+ * @param key
+ * @param value
+ */
+ private void addMapping(Object key, Object value) {
+ Object elementOrSet = valueToElements.get(value);
+ if (elementOrSet is null) {
+ valueToElements.put(value, key);
+ return;
+ }
+ if (!( null !is cast(Set)elementOrSet )) {
+ elementOrSet = new HashSet(Collections.singleton(elementOrSet));
+ valueToElements.put(value, elementOrSet);
+ }
+ Set set = cast(Set) elementOrSet;
+ set.add(key);
+ }
+
+ /**
+ * @param key
+ * @param value
+ */
+ private bool removeMapping(Object key, Object value) {
+ Object elementOrSet = valueToElements.get(value);
+ if ( null !is cast(Set)elementOrSet ) {
+ Set set = cast(Set) elementOrSet;
+ set.remove(key);
+ if (set.size() is 0) {
+ valueToElements.remove(value);
+ return true;
+ }
+ return false;
+ }
+ valueToElements.remove(value);
+ return true;
+ }
+
+ private Set getElementsForValue(Object value) {
+ Object elementOrSet = valueToElements.get(value);
+ if ( null !is cast(Set)elementOrSet ) {
+ return cast(Set) elementOrSet;
+ }
+ return elementOrSet is null ? Collections.EMPTY_SET : Collections
+ .singleton(elementOrSet);
+ }
+
+ public synchronized void dispose() {
+ super.dispose();
+ firstMap.removeMapChangeListener(firstMapListener);
+ firstMap = null;
+ secondMap = null;
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/ComputedObservableMap.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/ComputedObservableMap.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,161 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.map;
+
+import java.util.AbstractSet;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.set.IObservableSet;
+import org.eclipse.core.databinding.observable.set.ISetChangeListener;
+import org.eclipse.core.databinding.observable.set.SetChangeEvent;
+
+/**
+ * Maps objects to one of their attributes. Tracks changes to the underlying
+ * observable set of objects (keys), as well as changes to attribute values.
+ */
+public abstract class ComputedObservableMap : AbstractObservableMap {
+
+ private final IObservableSet keySet;
+
+ private ISetChangeListener setChangeListener = new class() ISetChangeListener {
+ public void handleSetChange(SetChangeEvent event) {
+ Set addedKeys = new HashSet(event.diff.getAdditions());
+ Set removedKeys = new HashSet(event.diff.getRemovals());
+ Map oldValues = new HashMap();
+ Map newValues = new HashMap();
+ for (Iterator it = removedKeys.iterator(); it.hasNext();) {
+ Object removedKey = it.next();
+ Object oldValue = doGet(removedKey);
+ unhookListener(removedKey);
+ if (oldValue !is null) {
+ oldValues.put(removedKey, oldValue);
+ }
+ }
+ for (Iterator it = addedKeys.iterator(); it.hasNext();) {
+ Object addedKey = it.next();
+ hookListener(addedKey);
+ Object newValue = doGet(addedKey);
+ newValues.put(addedKey, newValue);
+ }
+ fireMapChange(Diffs.createMapDiff(addedKeys, removedKeys,
+ Collections.EMPTY_SET, oldValues, newValues));
+ }
+ };
+
+ private Set entrySet = new EntrySet();
+
+ private class EntrySet : AbstractSet {
+
+ public Iterator iterator() {
+ final Iterator keyIterator = keySet.iterator();
+ return new class() Iterator {
+
+ public bool hasNext() {
+ return keyIterator.hasNext();
+ }
+
+ public Object next() {
+ final Object key = keyIterator.next();
+ return new Map.Entry() {
+
+ public Object getKey() {
+ return key;
+ }
+
+ public Object getValue() {
+ return get(getKey());
+ }
+
+ public Object setValue(Object value) {
+ return put(getKey(), value);
+ }
+ };
+ }
+
+ public void remove() {
+ keyIterator.remove();
+ }
+ };
+ }
+
+ public int size() {
+ return keySet.size();
+ }
+
+ }
+
+ /**
+ * @param keySet
+ */
+ public this(IObservableSet keySet) {
+ super(keySet.getRealm());
+ this.keySet = keySet;
+ this.keySet.addSetChangeListener(setChangeListener);
+ }
+
+ protected void init() {
+ for (Iterator it = this.keySet.iterator(); it.hasNext();) {
+ Object key = it.next();
+ hookListener(key);
+ }
+ }
+
+ protected final void fireSingleChange(Object key, Object oldValue,
+ Object newValue) {
+ fireMapChange(Diffs.createMapDiffSingleChange(key, oldValue, newValue));
+ }
+
+ public Set entrySet() {
+ return entrySet;
+ }
+
+ public Set keySet() {
+ return keySet;
+ }
+
+ final public Object get(Object key) {
+ return doGet(key);
+ }
+
+ final public Object put(Object key, Object value) {
+ return doPut(key, value);
+ }
+
+ /**
+ * @param removedKey
+ */
+ protected abstract void unhookListener(Object removedKey);
+
+ /**
+ * @param addedKey
+ */
+ protected abstract void hookListener(Object addedKey);
+
+ /**
+ * @param key
+ * @return the value for the given key
+ */
+ protected abstract Object doGet(Object key);
+
+ /**
+ * @param key
+ * @param value
+ * @return the old value for the given key
+ */
+ protected abstract Object doPut(Object key, Object value);
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/IMapChangeListener.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/IMapChangeListener.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.map;
+
+import org.eclipse.core.databinding.observable.IObservablesListener;
+
+/**
+ * Listener for changes to observable maps.
+ *
+ * @since 1.0
+ *
+ */
+public interface IMapChangeListener : IObservablesListener {
+
+ /**
+ * Handle a change an observable map. The given event object must only be
+ * used locally in this method because it may be reused for other change
+ * notifications. The diff object referenced by the event is immutable and
+ * may be used non-locally.
+ *
+ * @param event
+ * the event
+ */
+ void handleMapChange(MapChangeEvent event);
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/IObservableMap.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/IObservableMap.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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 - bug 164653
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.map;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.IObservable;
+
+/**
+ * Observable Map.
+ *
+ * @noextend This interface is not intended to be extended by clients.
+ * @noimplement This interface is not intended to be implemented by clients.
+ * Clients should instead subclass one of the classes that
+ * implement this interface. Note that direct implementers of this
+ * interface outside of the framework will be broken in future
+ * releases when methods are added to this interface.
+ *
+ * @see AbstractObservableMap
+ * @see ObservableMap
+ *
+ * @since 1.1
+ */
+public interface IObservableMap : Map, IObservable {
+
+ /**
+ * @param listener
+ */
+ public void addMapChangeListener(IMapChangeListener listener);
+
+ /**
+ * @param listener
+ */
+ public void removeMapChangeListener(IMapChangeListener listener);
+
+ /**
+ * @TrackedGetter
+ */
+ public int size();
+
+ /**
+ * @TrackedGetter
+ */
+ public bool isEmpty();
+
+ /**
+ * @TrackedGetter
+ */
+ public bool containsKey(Object key);
+
+ /**
+ * @TrackedGetter
+ */
+ public bool containsValue(Object value);
+
+ /**
+ * @TrackedGetter
+ */
+ public Object get(Object key);
+
+ /**
+ *
+ */
+ public Object put(Object key, Object value);
+
+ /**
+ *
+ */
+ public Object remove(Object key);
+
+ /**
+ * @TrackedGetter
+ */
+ public Set keySet();
+
+ /**
+ * @TrackedGetter
+ */
+ public Collection values();
+
+ /**
+ * @TrackedGetter
+ */
+ public Set entrySet();
+
+ /**
+ * @TrackedGetter
+ */
+ public override equals_t opEquals(Object o);
+
+ /**
+ * @TrackedGetter
+ */
+ public override hash_t toHash();
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/MapChangeEvent.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/MapChangeEvent.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ ******************************************************************************/
+
+package org.eclipse.core.databinding.observable.map;
+
+import org.eclipse.core.databinding.observable.IObservablesListener;
+import org.eclipse.core.databinding.observable.ObservableEvent;
+
+/**
+ * Map change event describing an incremental change of an
+ * {@link IObservableMap} object.
+ *
+ * @since 1.0
+ *
+ */
+public class MapChangeEvent : ObservableEvent {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -8092347212410548463L;
+ static final Object TYPE = new Object();
+
+ /**
+ * Description of the change to the source observable map. Listeners must
+ * not change this field.
+ */
+ public MapDiff diff;
+
+ /**
+ * Creates a new map change event
+ *
+ * @param source
+ * the source observable map
+ * @param diff
+ * the map change
+ */
+ public this(IObservableMap source, MapDiff diff) {
+ super(source);
+ this.diff = diff;
+ }
+
+ /**
+ * Returns the observable map from which this event originated.
+ *
+ * @return the observable map from which this event originated
+ */
+ public IObservableMap getObservableMap() {
+ return cast(IObservableMap) getSource();
+ }
+
+ protected void dispatch(IObservablesListener listener) {
+ (cast(IMapChangeListener) listener).handleMapChange(this);
+ }
+
+ protected Object getListenerType() {
+ return TYPE;
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/MapDiff.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/MapDiff.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.map;
+
+import java.util.Set;
+
+/**
+ * @since 1.1
+ *
+ */
+public abstract class MapDiff {
+
+ /**
+ * @return the set of keys which were added
+ */
+ public abstract Set getAddedKeys();
+
+ /**
+ * @return the set of keys which were removed
+ */
+ public abstract Set getRemovedKeys();
+
+ /**
+ * @return the set of keys for which the value has changed
+ */
+ public abstract Set getChangedKeys();
+
+ /**
+ * Returns the old value for the given key, which must be an element of
+ * {@link #getRemovedKeys()} or {@link #getChangedKeys()}.
+ *
+ * @param key
+ * @return the old value for the given key.
+ */
+ public abstract Object getOldValue(Object key);
+
+ /**
+ * Returns the new value for the given key, which must be an element of
+ * {@link #getChangedKeys()} or {@link #getAddedKeys()}.
+ *
+ * @param key
+ * @return the new value for the given key.
+ */
+ public abstract Object getNewValue(Object key);
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/ObservableMap.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/ObservableMap.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,182 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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 - bug 164653
+ * Matthew Hall - bug 245183
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.map;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.AbstractObservable;
+import org.eclipse.core.databinding.observable.ObservableTracker;
+import org.eclipse.core.databinding.observable.Realm;
+
+/**
+ *
+ *
+ * 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.
+ *
+ * @since 1.0
+ */
+public class ObservableMap : AbstractObservable , IObservableMap {
+
+ protected Map wrappedMap;
+
+ private bool stale = false;
+
+ /**
+ * @param wrappedMap
+ */
+ public this(Map wrappedMap) {
+ this(Realm.getDefault(), wrappedMap);
+ }
+
+ /**
+ * @param realm
+ * @param wrappedMap
+ */
+ public this(Realm realm, Map wrappedMap) {
+ super(realm);
+ this.wrappedMap = wrappedMap;
+ }
+
+ public synchronized void addMapChangeListener(IMapChangeListener listener) {
+ addListener(MapChangeEvent.TYPE, listener);
+ }
+
+ public synchronized void removeMapChangeListener(IMapChangeListener listener) {
+ removeListener(MapChangeEvent.TYPE, listener);
+ }
+
+ protected void getterCalled() {
+ ObservableTracker.getterCalled(this);
+ }
+
+ protected void fireMapChange(MapDiff diff) {
+ checkRealm();
+
+ // fire general change event first
+ super.fireChange();
+
+ fireEvent(new MapChangeEvent(this, diff));
+ }
+
+ public bool containsKey(Object key) {
+ getterCalled();
+ return wrappedMap.containsKey(key);
+ }
+
+ public bool containsValue(Object value) {
+ getterCalled();
+ return wrappedMap.containsValue(value);
+ }
+
+ public Set entrySet() {
+ getterCalled();
+ return wrappedMap.entrySet();
+ }
+
+ public Object get(Object key) {
+ getterCalled();
+ return wrappedMap.get(key);
+ }
+
+ public bool isEmpty() {
+ getterCalled();
+ return wrappedMap.isEmpty();
+ }
+
+ public Set keySet() {
+ getterCalled();
+ return wrappedMap.keySet();
+ }
+
+ public int size() {
+ getterCalled();
+ return wrappedMap.size();
+ }
+
+ public Collection values() {
+ getterCalled();
+ return wrappedMap.values();
+ }
+
+ /**
+ * Returns the stale state. Must be invoked from the current realm.
+ *
+ * @return stale state
+ */
+ public bool isStale() {
+ checkRealm();
+ return stale;
+ }
+
+ /**
+ * Sets the stale state. Must be invoked from the current realm.
+ *
+ * @param stale
+ * The stale state to set. This will fire a stale event if the
+ * given bool is true and this observable set was not already
+ * stale.
+ */
+ public void setStale(bool stale) {
+ checkRealm();
+ bool wasStale = this.stale;
+ this.stale = stale;
+ if (!wasStale && stale) {
+ fireStale();
+ }
+ }
+
+ public Object put(Object key, Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object remove(Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void putAll(Map arg0) {
+ throw new UnsupportedOperationException();
+ }
+
+ public synchronized void dispose() {
+ super.dispose();
+ }
+
+ public override equals_t opEquals(Object obj) {
+ getterCalled();
+
+ if (obj is this)
+ return true;
+ if (obj is null)
+ return false;
+ if (getClass() is obj.getClass()) {
+ return wrappedMap.equals((cast(ObservableMap) obj).wrappedMap);
+ }
+
+ return wrappedMap.equals(obj);
+ }
+
+ public override hash_t toHash() {
+ getterCalled();
+
+ return wrappedMap.hashCode();
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/WritableMap.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/WritableMap.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,117 @@
+/*******************************************************************************
+ * Copyright (c) 2006-2008 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 - bug 164653
+ * Matthew Hall - bug 184830
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.map;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.internal.databinding.Util;
+
+/**
+ *
+ *
+ * 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.
+ *
+ * @since 1.0
+ */
+public class WritableMap : ObservableMap {
+
+ /**
+ * Constructs a new WritableMap on the default realm.
+ */
+ public this() {
+ this(Realm.getDefault());
+ }
+
+ /**
+ * Constructs a new WritableMap on the given realm.
+ *
+ * @param realm
+ * the realm
+ */
+ public this(Realm realm) {
+ super(realm, new HashMap());
+ }
+
+ /**
+ * Associates the provided value
with the key
. Must be invoked from the current realm.
+ */
+ public Object put(Object key, Object value) {
+ checkRealm();
+ Object result = wrappedMap.put(key, value);
+ if (!Util.equals(result, value)) {
+ if (resultisnull) {
+ fireMapChange(Diffs.createMapDiffSingleAdd(key, value));
+ } else {
+ fireMapChange(Diffs.createMapDiffSingleChange(key, result,
+ value));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Removes the value with the provide key
. Must be invoked from the current realm.
+ */
+ public Object remove(Object key) {
+ checkRealm();
+ Object result = wrappedMap.remove(key);
+ if (result!isnull) {
+ fireMapChange(Diffs.createMapDiffSingleRemove(key, result));
+ }
+ return result;
+ }
+
+ /**
+ * Clears the map. Must be invoked from the current realm.
+ */
+ public void clear() {
+ checkRealm();
+ if (!isEmpty()) {
+ Map copy = new HashMap(wrappedMap);
+ wrappedMap.clear();
+ fireMapChange(Diffs.createMapDiffRemoveAll(copy));
+ }
+ }
+
+ /**
+ * Adds the provided map
's contents to this map. Must be invoked from the current realm.
+ */
+ public void putAll(Map map) {
+ checkRealm();
+ Set addedKeys = new HashSet(map.size());
+ Map changes = new HashMap(map.size());
+ for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = cast(Entry) it.next();
+ Object previousValue = wrappedMap.put(entry.getKey(), entry.getValue());
+ if (previousValueisnull) {
+ addedKeys.add(entry.getKey());
+ } else {
+ changes.put(entry.getKey(), previousValue);
+ }
+ }
+ if (!addedKeys.isEmpty() || !changes.isEmpty()) {
+ fireMapChange(Diffs.createMapDiff(addedKeys, Collections.EMPTY_SET, changes.keySet(), changes, wrappedMap));
+ }
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/package.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/package.html Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ Package-level Javadoc
+
+
+Provides classes that can be used to observe changes in maps.
+
+Package Specification
+
+This package provides classes that can be used to observe changes in maps.
+
+
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/IObservableFactory.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/IObservableFactory.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.masterdetail;
+
+import org.eclipse.core.databinding.observable.IObservable;
+
+/**
+ * Generates an {@link IObservable} when passed a target instance.
+ *
+ * @since 1.0
+ */
+public interface IObservableFactory {
+
+ /**
+ * Creates an observable for the given target object.
+ *
+ * @param target
+ * @return the new observable
+ */
+ public IObservable createObservable(Object target);
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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 - bug 147515
+ * Matthew Hall - bug 221704
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.masterdetail;
+
+import org.eclipse.core.databinding.observable.list.IObservableList;
+import org.eclipse.core.databinding.observable.map.IObservableMap;
+import org.eclipse.core.databinding.observable.set.IObservableSet;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableList;
+import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableMap;
+import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableSet;
+import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableValue;
+
+/**
+ * Allows for the observation of an attribute, the detail, of an observable
+ * representing selection or another transient instance, the master.
+ *
+ * @since 1.0
+ */
+public class MasterDetailObservables {
+
+ /**
+ * Creates a detail observable value from a master observable value and a
+ * factory. This can be used to create observable values that represent a
+ * property of a selected object in a table.
+ *
+ * @param master
+ * the observable value to track
+ * @param detailFactory
+ * a factory for creating {@link IObservableValue} instances
+ * given a current value of the master
+ * @param detailType
+ * the value type of the detail observable value, typically of
+ * type java.lang.Class and can be null
+ * @return an observable value of the given value type that, for any current
+ * value of the given master value, behaves like the observable
+ * value created by the factory for that current value.
+ */
+ public static IObservableValue detailValue(IObservableValue master,
+ IObservableFactory detailFactory, Object detailType) {
+ return new DetailObservableValue(master, detailFactory, detailType);
+ }
+
+ /**
+ * Creates a detail observable list from a master observable value and a
+ * factory. This can be used to create observable lists that represent a
+ * list property of a selected object in a table.
+ *
+ * @param master
+ * the observable value to track
+ * @param detailFactory
+ * a factory for creating {@link IObservableList} instances given
+ * a current value of the master
+ * @param detailElementType
+ * the element type of the detail observable list, typically of
+ * type java.lang.Class and can be null
+ * @return an observable list with the given element type that, for any
+ * current value of the given master value, behaves like the
+ * observable list created by the factory for that current value.
+ */
+ public static IObservableList detailList(IObservableValue master,
+ IObservableFactory detailFactory, Object detailElementType) {
+ return new DetailObservableList(detailFactory, master,
+ detailElementType);
+ }
+
+ /**
+ * Creates a detail observable set from a master observable value and a
+ * factory. This can be used to create observable sets that represent a set
+ * property of a selected object in a table.
+ *
+ * @param master
+ * the observable value to track
+ * @param detailFactory
+ * a factory for creating {@link IObservableSet} instances given
+ * a current value of the master
+ * @param detailElementType
+ * the element type of the detail observable set, typically of
+ * type java.lang.Class and can be null
+ * @return an observable set with the given element type that, for any
+ * current value of the given master value, behaves like the
+ * observable set created by the factory for that current value.
+ */
+ public static IObservableSet detailSet(IObservableValue master,
+ IObservableFactory detailFactory, Object detailElementType) {
+ return new DetailObservableSet(detailFactory, master, detailElementType);
+ }
+
+ /**
+ * Creates a detail observable map from a master observable value and a
+ * factory. This can be used to create observable maps that represent a map
+ * property of a selected object in a table.
+ *
+ * @param master
+ * the observable value to track
+ * @param detailFactory
+ * a factory for createing {@link IObservableMap} instances given
+ * a current value of the master
+ * @return an observable map that, for any current value of the given master
+ * value, behaves like the observable map created by the factory for
+ * that current value.
+ * @since 1.1
+ */
+ public static IObservableMap detailMap(IObservableValue master,
+ IObservableFactory detailFactory) {
+ return new DetailObservableMap(detailFactory, master);
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/package.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/package.html Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ Package-level Javadoc
+
+
+Provides classes that can be used to observe a detail of a master object.
+
+Package Specification
+
+This package provides classes that can be used to observe a detail of a master object.
+A common use case for master detail is observing the detail (e.g. name) of a master (e.g. selected Person) of a list of elements.
+
+
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/package.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/package.html Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ Package-level Javadoc
+
+
+Provides the core APIs for observing changes in objects.
+
+Package Specification
+
+This package provides the core APIs for observing changes in objects.
+
+
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/AbstractObservableSet.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/AbstractObservableSet.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,218 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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
+ * Matthew Hall - bug 208332
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.set;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.AbstractObservable;
+import org.eclipse.core.databinding.observable.ChangeSupport;
+import org.eclipse.core.databinding.observable.ObservableTracker;
+import org.eclipse.core.databinding.observable.Realm;
+
+/**
+ *
+ * Abstract implementation of {@link IObservableSet}.
+ *
+ *
+ * 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.
+ *
+ *
+ * @since 1.0
+ */
+public abstract class AbstractObservableSet : AbstractObservable ,
+ IObservableSet {
+
+ private ChangeSupport changeSupport;
+
+ private bool stale = false;
+
+ protected this() {
+ this(Realm.getDefault());
+ }
+
+ protected void firstListenerAdded() {
+ super.firstListenerAdded();
+ }
+
+ protected void lastListenerRemoved() {
+ super.lastListenerRemoved();
+ }
+
+ protected this(Realm realm) {
+ super(realm);
+ changeSupport = new class(realm) ChangeSupport {
+ protected void firstListenerAdded() {
+ this.outer.firstListenerAdded();
+ }
+ protected void lastListenerRemoved() {
+ this.outer.lastListenerRemoved();
+ }
+ };
+ }
+
+ public synchronized void addSetChangeListener(ISetChangeListener listener) {
+ changeSupport.addListener(SetChangeEvent.TYPE, listener);
+ }
+
+ public synchronized void removeSetChangeListener(ISetChangeListener listener) {
+ changeSupport.removeListener(SetChangeEvent.TYPE, listener);
+ }
+
+ protected abstract Set getWrappedSet();
+
+ protected void fireSetChange(SetDiff diff) {
+ // fire general change event first
+ super.fireChange();
+
+ changeSupport.fireEvent(new SetChangeEvent(this, diff));
+ }
+
+ public bool contains(Object o) {
+ getterCalled();
+ return getWrappedSet().contains(o);
+ }
+
+ public bool containsAll(Collection c) {
+ getterCalled();
+ return getWrappedSet().containsAll(c);
+ }
+
+ public override equals_t opEquals(Object o) {
+ getterCalled();
+ return getWrappedSet().equals(o);
+ }
+
+ public override hash_t toHash() {
+ getterCalled();
+ return getWrappedSet().hashCode();
+ }
+
+ public bool isEmpty() {
+ getterCalled();
+ return getWrappedSet().isEmpty();
+ }
+
+ public Iterator iterator() {
+ getterCalled();
+ final Iterator wrappedIterator = getWrappedSet().iterator();
+ return new class() Iterator {
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool hasNext() {
+ ObservableTracker.getterCalled(this.outer);
+ return wrappedIterator.hasNext();
+ }
+
+ public Object next() {
+ ObservableTracker.getterCalled(this.outer);
+ return wrappedIterator.next();
+ }
+ };
+ }
+
+ public int size() {
+ getterCalled();
+ return getWrappedSet().size();
+ }
+
+ public Object[] toArray() {
+ getterCalled();
+ return getWrappedSet().toArray();
+ }
+
+ public Object[] toArray(Object[] a) {
+ getterCalled();
+ return getWrappedSet().toArray(a);
+ }
+
+ public String toString() {
+ getterCalled();
+ return getWrappedSet().toString();
+ }
+
+ protected void getterCalled() {
+ ObservableTracker.getterCalled(this);
+ }
+
+ public bool add(Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool addAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool remove(Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool removeAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool retainAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @return Returns the stale state.
+ */
+ public bool isStale() {
+ getterCalled();
+ return stale;
+ }
+
+ /**
+ * @param stale
+ * The stale state to set. This will fire a stale event if the
+ * given bool is true and this observable set was not already
+ * stale.
+ */
+ public void setStale(bool stale) {
+ checkRealm();
+ bool wasStale = this.stale;
+ this.stale = stale;
+ if (!wasStale && stale) {
+ fireStale();
+ }
+ }
+
+
+ protected void fireChange() {
+ throw new RuntimeException("fireChange should not be called, use fireSetChange() instead"); //$NON-NLS-1$
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.provisional.databinding.observable.AbstractObservable#dispose()
+ */
+ public synchronized void dispose() {
+ super.dispose();
+
+ if (changeSupport !is null) {
+ changeSupport.dispose();
+ changeSupport = null;
+ }
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/IObservableSet.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/IObservableSet.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.set;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.IObservableCollection;
+
+/**
+ * A set whose changes can be tracked by set change listeners.
+ *
+ * @noextend This interface is not intended to be extended by clients.
+ * @noimplement This interface is not intended to be implemented by clients.
+ * Clients should instead subclass one of the classes that
+ * implement this interface. Note that direct implementers of this
+ * interface outside of the framework will be broken in future
+ * releases when methods are added to this interface.
+ *
+ * @see AbstractObservableSet
+ * @see ObservableSet
+ *
+ * @since 1.0
+ *
+ */
+public interface IObservableSet : Set, IObservableCollection {
+
+ /**
+ * @param listener
+ */
+ public void addSetChangeListener(ISetChangeListener listener);
+
+ /**
+ * @param listener
+ */
+ public void removeSetChangeListener(ISetChangeListener listener);
+
+ /**
+ * @return the element type or null
if untyped
+ */
+ public Object getElementType();
+
+ /**
+ * @TrackedGetter
+ */
+ int size();
+
+ /**
+ * @TrackedGetter
+ */
+ bool isEmpty();
+
+ /**
+ * @TrackedGetter
+ */
+ bool contains(Object o);
+
+ /**
+ * @TrackedGetter
+ */
+ Iterator iterator();
+
+ /**
+ * @TrackedGetter
+ */
+ Object[] toArray();
+
+ /**
+ * @TrackedGetter
+ */
+ Object[] toArray(Object a[]);
+
+ // Modification Operations
+
+ /**
+ * @TrackedGetter
+ */
+ bool add(Object o);
+
+ /**
+ * @TrackedGetter
+ */
+ bool remove(Object o);
+
+ // Bulk Operations
+
+ /**
+ * @TrackedGetter
+ */
+ bool containsAll(Collection c);
+
+ /**
+ * @TrackedGetter
+ */
+ bool addAll(Collection c);
+
+ /**
+ * @TrackedGetter
+ */
+ bool retainAll(Collection c);
+
+ /**
+ * @TrackedGetter
+ */
+ bool removeAll(Collection c);
+
+ // Comparison and hashing
+
+ /**
+ * @TrackedGetter
+ */
+ override equals_t opEquals(Object o);
+
+ /**
+ * @TrackedGetter
+ */
+ override hash_t toHash();
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/ISetChangeListener.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/ISetChangeListener.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.set;
+
+import org.eclipse.core.databinding.observable.IObservablesListener;
+
+/**
+ * Listener for changes to observable sets.
+ *
+ * @since 1.0
+ *
+ */
+public interface ISetChangeListener : IObservablesListener {
+
+ /**
+ * Handle a change to an observable set. The given event object must only be
+ * used locally in this method because it may be reused for other change
+ * notifications. The diff object referenced by the event is immutable and
+ * may be used non-locally.
+ *
+ * @param event
+ * the event
+ */
+ void handleSetChange(SetChangeEvent event);
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/ListToSetAdapter.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/ListToSetAdapter.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.set;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.list.IListChangeListener;
+import org.eclipse.core.databinding.observable.list.IObservableList;
+import org.eclipse.core.databinding.observable.list.ListChangeEvent;
+import org.eclipse.core.databinding.observable.list.ListDiffEntry;
+
+/**
+ * Observable set backed by an observable list. The wrapped list must not
+ * contain duplicate elements.
+ *
+ *
+ * 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.
+ *
+ *
+ * @since 1.0
+ *
+ */
+public class ListToSetAdapter : ObservableSet {
+
+ private final IObservableList list;
+
+ private IListChangeListener listener = new class() IListChangeListener {
+
+ public void handleListChange(ListChangeEvent event) {
+ Set added = new HashSet();
+ Set removed = new HashSet();
+ ListDiffEntry[] differences = event.diff.getDifferences();
+ for (int i = 0; i < differences.length; i++) {
+ ListDiffEntry entry = differences[i];
+ Object element = entry.getElement();
+ if (entry.isAddition()) {
+ if (wrappedSet.add(element)) {
+ if (!removed.remove(element))
+ added.add(element);
+ }
+ } else {
+ if (wrappedSet.remove(element)) {
+ removed.add(element);
+ added.remove(element);
+ }
+ }
+ }
+ fireSetChange(Diffs.createSetDiff(added, removed));
+ }
+ };
+
+ /**
+ * @param list
+ */
+ public this(IObservableList list) {
+ super(list.getRealm(), new HashSet(), list.getElementType());
+ this.list = list;
+ wrappedSet.addAll(list);
+ this.list.addListChangeListener(listener);
+ }
+
+ public synchronized void dispose() {
+ super.dispose();
+ if (list !is null && listener !is null) {
+ list.removeListChangeListener(listener);
+ listener = null;
+ }
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/MappedSet.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/MappedSet.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,157 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.set;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.map.IMapChangeListener;
+import org.eclipse.core.databinding.observable.map.IObservableMap;
+import org.eclipse.core.databinding.observable.map.MapChangeEvent;
+import org.eclipse.core.databinding.observable.map.MapDiff;
+
+/**
+ *
+ *
+ * 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.
+ *
+ *
+ * @since 1.0
+ *
+ */
+public class MappedSet : ObservableSet {
+
+ private final IObservableMap wrappedMap;
+
+ /*
+ * Map from values (range elements) to Integer ref counts
+ */
+ private Map valueCounts = new HashMap();
+
+ private ISetChangeListener domainListener = new class() ISetChangeListener {
+ public void handleSetChange(SetChangeEvent event) {
+ Set additions = new HashSet();
+ for (Iterator it = event.diff.getAdditions().iterator(); it.hasNext();) {
+ Object added = it.next();
+ Object mapValue = wrappedMap.get(added);
+ if (handleAddition(mapValue)) {
+ additions.add(mapValue);
+ }
+ }
+ Set removals = new HashSet();
+ for (Iterator it = event.diff.getRemovals().iterator(); it.hasNext();) {
+ Object removed = it.next();
+ Object mapValue = wrappedMap.get(removed);
+ if (handleRemoval(mapValue)) {
+ removals.add(mapValue);
+ }
+ }
+ fireSetChange(Diffs.createSetDiff(additions, removals));
+ }
+ };
+
+ private IMapChangeListener mapChangeListener = new class() IMapChangeListener {
+ public void handleMapChange(MapChangeEvent event) {
+ MapDiff diff = event.diff;
+ Set additions = new HashSet();
+ Set removals = new HashSet();
+ for (Iterator it = diff.getRemovedKeys().iterator(); it.hasNext();) {
+ Object key = it.next();
+ Object oldValue = diff.getOldValue(key);
+ if (handleRemoval(oldValue)) {
+ removals.add(oldValue);
+ }
+ }
+ for (Iterator it = diff.getChangedKeys().iterator(); it.hasNext();) {
+ Object key = it.next();
+ Object oldValue = diff.getOldValue(key);
+ Object newValue = diff.getNewValue(key);
+ if (handleRemoval(oldValue)) {
+ removals.add(oldValue);
+ }
+ if (handleAddition(newValue)) {
+ additions.add(newValue);
+ }
+ }
+ for (Iterator it = diff.getAddedKeys().iterator(); it.hasNext();) {
+ Object key = it.next();
+ Object newValue = diff.getNewValue(key);
+ if (handleAddition(newValue)) {
+ additions.add(newValue);
+ }
+ }
+ fireSetChange(Diffs.createSetDiff(additions, removals));
+ }
+ };
+
+ private IObservableSet input;
+
+ /**
+ * @param input
+ * @param map
+ */
+ public this(IObservableSet input, IObservableMap map) {
+ super(input.getRealm(), Collections.EMPTY_SET, Object.class);
+ setWrappedSet(valueCounts.keySet());
+ this.wrappedMap = map;
+ this.input = input;
+ for (Iterator it = input.iterator(); it.hasNext();) {
+ Object element = it.next();
+ Object functionValue = wrappedMap.get(element);
+ handleAddition(functionValue);
+ }
+ input.addSetChangeListener(domainListener);
+ map.addMapChangeListener(mapChangeListener);
+ }
+
+ /**
+ * @param mapValue
+ * @return true if the given mapValue was an addition
+ */
+ protected bool handleAddition(Object mapValue) {
+ Integer count = cast(Integer) valueCounts.get(mapValue);
+ if (count is null) {
+ valueCounts.put(mapValue, new Integer(1));
+ return true;
+ }
+ valueCounts.put(mapValue, new Integer(count.intValue() + 1));
+ return false;
+ }
+
+ /**
+ * @param mapValue
+ * @return true if the given mapValue has been removed
+ */
+ protected bool handleRemoval(Object mapValue) {
+ Integer count = cast(Integer) valueCounts.get(mapValue);
+ if (count.intValue() <= 1) {
+ valueCounts.remove(mapValue);
+ return true;
+ }
+ valueCounts.put(mapValue, new Integer(count.intValue() - 1));
+ return false;
+ }
+
+ public synchronized void dispose() {
+ wrappedMap.removeMapChangeListener(mapChangeListener);
+ input.removeSetChangeListener(domainListener);
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/ObservableSet.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/ObservableSet.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,217 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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
+ * Matthew Hall - bugs 208332, 245183
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.set;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.AbstractObservable;
+import org.eclipse.core.databinding.observable.ObservableTracker;
+import org.eclipse.core.databinding.observable.Realm;
+
+/**
+ *
+ * Abstract implementation of {@link IObservableSet}.
+ *
+ *
+ * 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.
+ *
+ *
+ * @since 1.0
+ *
+ */
+public abstract class ObservableSet : AbstractObservable ,
+ IObservableSet {
+
+ protected Set wrappedSet;
+
+ private bool stale = false;
+
+ protected Object elementType;
+
+ protected this(Set wrappedSet, Object elementType) {
+ this(Realm.getDefault(), wrappedSet, elementType);
+ }
+
+ protected this(Realm realm, Set wrappedSet, Object elementType) {
+ super(realm);
+ this.wrappedSet = wrappedSet;
+ this.elementType = elementType;
+ }
+
+ public synchronized void addSetChangeListener(ISetChangeListener listener) {
+ addListener(SetChangeEvent.TYPE, listener);
+ }
+
+ public synchronized void removeSetChangeListener(ISetChangeListener listener) {
+ removeListener(SetChangeEvent.TYPE, listener);
+ }
+
+ protected void fireSetChange(SetDiff diff) {
+ // fire general change event first
+ super.fireChange();
+
+ fireEvent(new SetChangeEvent(this, diff));
+ }
+
+ public bool contains(Object o) {
+ getterCalled();
+ return wrappedSet.contains(o);
+ }
+
+ public bool containsAll(Collection c) {
+ getterCalled();
+ return wrappedSet.containsAll(c);
+ }
+
+ public override equals_t opEquals(Object o) {
+ getterCalled();
+
+ if (o is this)
+ return true;
+ if (o is null)
+ return false;
+ if (getClass() is o.getClass()) {
+ return wrappedSet.equals((cast(ObservableSet) o).wrappedSet);
+ }
+
+ return wrappedSet.equals(o);
+ }
+
+ public override hash_t toHash() {
+ getterCalled();
+ return wrappedSet.hashCode();
+ }
+
+ public bool isEmpty() {
+ getterCalled();
+ return wrappedSet.isEmpty();
+ }
+
+ public Iterator iterator() {
+ getterCalled();
+ final Iterator wrappedIterator = wrappedSet.iterator();
+ return new class() Iterator {
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool hasNext() {
+ ObservableTracker.getterCalled(this.outer);
+ return wrappedIterator.hasNext();
+ }
+
+ public Object next() {
+ ObservableTracker.getterCalled(this.outer);
+ return wrappedIterator.next();
+ }
+ };
+ }
+
+ public int size() {
+ getterCalled();
+ return wrappedSet.size();
+ }
+
+ public Object[] toArray() {
+ getterCalled();
+ return wrappedSet.toArray();
+ }
+
+ public Object[] toArray(Object[] a) {
+ getterCalled();
+ return wrappedSet.toArray(a);
+ }
+
+ public String toString() {
+ getterCalled();
+ return wrappedSet.toString();
+ }
+
+ protected void getterCalled() {
+ ObservableTracker.getterCalled(this);
+ }
+
+ public bool add(Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool addAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool remove(Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool removeAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool retainAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * @return Returns the stale state.
+ */
+ public bool isStale() {
+ getterCalled();
+ return stale;
+ }
+
+ /**
+ * @param stale
+ * The stale state to set. This will fire a stale event if the
+ * given bool is true and this observable set was not already
+ * stale.
+ */
+ public void setStale(bool stale) {
+ checkRealm();
+ bool wasStale = this.stale;
+ this.stale = stale;
+ if (!wasStale && stale) {
+ fireStale();
+ }
+ }
+
+ /**
+ * @param wrappedSet The wrappedSet to set.
+ */
+ protected void setWrappedSet(Set wrappedSet) {
+ this.wrappedSet = wrappedSet;
+ }
+
+ protected void fireChange() {
+ throw new RuntimeException("fireChange should not be called, use fireSetChange() instead"); //$NON-NLS-1$
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.jface.provisional.databinding.observable.AbstractObservable#dispose()
+ */
+ public synchronized void dispose() {
+ super.dispose();
+ }
+
+ public Object getElementType() {
+ return elementType;
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/SetChangeEvent.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/SetChangeEvent.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ ******************************************************************************/
+
+package org.eclipse.core.databinding.observable.set;
+
+import org.eclipse.core.databinding.observable.IObservablesListener;
+import org.eclipse.core.databinding.observable.ObservableEvent;
+
+/**
+ * List change event describing an incremental change of an
+ * {@link IObservableSet} object.
+ *
+ * @since 1.0
+ *
+ */
+public class SetChangeEvent : ObservableEvent {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 7436547103857482256L;
+ static final Object TYPE = new Object();
+
+ /**
+ * Description of the change to the source observable set. Listeners must
+ * not change this field.
+ */
+ public SetDiff diff;
+
+ /**
+ * Creates a new set change event.
+ *
+ * @param source
+ * the source observable set
+ * @param diff
+ * the set change
+ */
+ public this(IObservableSet source, SetDiff diff) {
+ super(source);
+ this.diff = diff;
+ }
+
+ /**
+ * Returns the observable set from which this event originated.
+ *
+ * @return the observable set from which this event originated
+ */
+ public IObservableSet getObservableSet() {
+ return cast(IObservableSet) getSource();
+ }
+
+ protected void dispatch(IObservablesListener listener) {
+ (cast(ISetChangeListener) listener).handleSetChange(this);
+ }
+
+ protected Object getListenerType() {
+ return TYPE;
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/SetDiff.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/SetDiff.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.set;
+
+import java.util.Set;
+
+/**
+ * @since 1.0
+ *
+ */
+public abstract class SetDiff {
+
+ /**
+ * @return the set of added elements
+ */
+ public abstract Set getAdditions();
+
+ /**
+ * @return the set of removed elements
+ */
+ public abstract Set getRemovals();
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ buffer
+ .append(getClass().getName())
+ .append("{additions [") //$NON-NLS-1$
+ .append(getAdditions() !is null ? getAdditions().toString() : "null") //$NON-NLS-1$
+ .append("], removals [") //$NON-NLS-1$
+ .append(getRemovals() !is null ? getRemovals().toString() : "null") //$NON-NLS-1$
+ .append("]}"); //$NON-NLS-1$
+
+ return buffer.toString();
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/UnionSet.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/UnionSet.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,210 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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
+ * Matthew Hall - bug 208332
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.set;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.internal.databinding.observable.IStalenessConsumer;
+import org.eclipse.core.internal.databinding.observable.StalenessTracker;
+
+/**
+ * Represents a set consisting of the union of elements from one or more other
+ * sets. This object does not need to be explicitly disposed. If nobody is
+ * listening to the UnionSet, the set will remove its listeners.
+ *
+ *
+ * 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.
+ *
+ *
+ * @since 1.0
+ */
+public final class UnionSet : ObservableSet {
+
+ /**
+ * child sets
+ */
+ private IObservableSet[] childSets;
+
+ private bool stale = false;
+
+ /**
+ * Map of elements onto Integer reference counts. This map is constructed
+ * when the first listener is added to the union set. Null if nobody is
+ * listening to the UnionSet.
+ */
+ private HashMap refCounts = null;
+
+ private StalenessTracker stalenessTracker;
+
+ /**
+ * @param childSets
+ */
+ public this(IObservableSet[] childSets) {
+ super(childSets[0].getRealm(), null, childSets[0].getElementType());
+ System.arraycopy(childSets, 0, this.childSets = new IObservableSet[childSets.length], 0, childSets.length);
+ this.stalenessTracker = new StalenessTracker(childSets,
+ stalenessConsumer);
+ }
+
+ private ISetChangeListener childSetChangeListener = new class() ISetChangeListener {
+ public void handleSetChange(SetChangeEvent event) {
+ processAddsAndRemoves(event.diff.getAdditions(), event.diff.getRemovals());
+ }
+ };
+
+ private IStalenessConsumer stalenessConsumer = new class() IStalenessConsumer {
+ public void setStale(bool stale) {
+ bool oldStale = this.outer.stale;
+ this.outer.stale = stale;
+ if (stale && !oldStale) {
+ fireStale();
+ }
+ }
+ };
+
+ public bool isStale() {
+ getterCalled();
+ if (refCounts !is null) {
+ return stale;
+ }
+
+ for (int i = 0; i < childSets.length; i++) {
+ IObservableSet childSet = childSets[i];
+
+ if (childSet.isStale()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void processAddsAndRemoves(Set adds, Set removes) {
+ Set addsToFire = new HashSet();
+ Set removesToFire = new HashSet();
+
+ for (Iterator iter = adds.iterator(); iter.hasNext();) {
+ Object added = iter.next();
+
+ Integer refCount = cast(Integer) refCounts.get(added);
+ if (refCount is null) {
+ refCounts.put(added, new Integer(1));
+ addsToFire.add(added);
+ } else {
+ int refs = refCount.intValue();
+ refCount = new Integer(refs + 1);
+ refCounts.put(added, refCount);
+ }
+ }
+
+ for (Iterator iter = removes.iterator(); iter.hasNext();) {
+ Object removed = iter.next();
+
+ Integer refCount = cast(Integer) refCounts.get(removed);
+ if (refCount !is null) {
+ int refs = refCount.intValue();
+ if (refs <= 1) {
+ removesToFire.add(removed);
+ refCounts.remove(removed);
+ } else {
+ refCount = new Integer(refCount.intValue() - 1);
+ refCounts.put(removed, refCount);
+ }
+ }
+ }
+
+ // just in case the removes overlapped with the adds
+ addsToFire.removeAll(removesToFire);
+
+ if (addsToFire.size() > 0 || removesToFire.size() > 0) {
+ fireSetChange(Diffs.createSetDiff(addsToFire, removesToFire));
+ }
+ }
+
+ protected void firstListenerAdded() {
+ super.firstListenerAdded();
+
+ refCounts = new HashMap();
+ for (int i = 0; i < childSets.length; i++) {
+ IObservableSet next = childSets[i];
+ next.addSetChangeListener(childSetChangeListener);
+ incrementRefCounts(next);
+ }
+ stalenessTracker = new StalenessTracker(childSets, stalenessConsumer);
+ setWrappedSet(refCounts.keySet());
+ }
+
+ protected void lastListenerRemoved() {
+ super.lastListenerRemoved();
+
+ for (int i = 0; i < childSets.length; i++) {
+ IObservableSet next = childSets[i];
+
+ next.removeSetChangeListener(childSetChangeListener);
+ stalenessTracker.removeObservable(next);
+ }
+ refCounts = null;
+ stalenessTracker = null;
+ setWrappedSet(null);
+ }
+
+ private ArrayList incrementRefCounts(Collection added) {
+ ArrayList adds = new ArrayList();
+
+ for (Iterator iter = added.iterator(); iter.hasNext();) {
+ Object next = iter.next();
+
+ Integer refCount = cast(Integer) refCounts.get(next);
+ if (refCount is null) {
+ adds.add(next);
+ refCount = new Integer(1);
+ refCounts.put(next, refCount);
+ } else {
+ refCount = new Integer(refCount.intValue() + 1);
+ refCounts.put(next, refCount);
+ }
+ }
+ return adds;
+ }
+
+ protected void getterCalled() {
+ super.getterCalled();
+ if (refCounts is null) {
+ // no listeners, recompute
+ setWrappedSet(computeElements());
+ }
+ }
+
+ private Set computeElements() {
+ // If there is no cached value, compute the union from scratch
+ if (refCounts is null) {
+ Set result = new HashSet();
+ for (int i = 0; i < childSets.length; i++) {
+ result.addAll(childSets[i]);
+ }
+ return result;
+ }
+
+ // Else there is a cached value. Return it.
+ return refCounts.keySet();
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/WritableSet.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/WritableSet.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,171 @@
+/*******************************************************************************
+ * Copyright (c) 2006-2008 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 - bug 147515
+ * Matthew Hall - bug 221351
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.set;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.Realm;
+
+/**
+ * Mutable (writable) implementation of {@link IObservableSet}.
+ *
+ *
+ * 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.
+ *
+ *
+ * @since 1.0
+ */
+public class WritableSet : ObservableSet {
+
+ /**
+ * Constructs a new empty instance in the default realm with a
+ * null
element type.
+ *
+ */
+ public this() {
+ this(Realm.getDefault());
+ }
+
+ /**
+ * Constructs a new instance in the default realm containing the
+ * elements of the given collection. Changes to the given collection after
+ * calling this method do not affect the contents of the created WritableSet.
+ *
+ * @param c
+ * @param elementType
+ * can be null
+ */
+ public this(Collection c, Object elementType) {
+ this(Realm.getDefault(), new HashSet(c), elementType);
+ }
+
+ /**
+ * Constructs a new empty instance in the given realm and a
+ * null
element type.
+ *
+ * @param realm
+ */
+ public this(Realm realm) {
+ this(realm, new HashSet(), null);
+ }
+
+ /**
+ * Constructs a new instance in the default realm with the given element
+ * type, containing the elements of the given collection. Changes to the
+ * given collection after calling this method do not affect the contents of
+ * the created WritableSet.
+ *
+ * @param realm
+ * @param c
+ * @param elementType
+ * can be null
+ */
+ public this(Realm realm, Collection c, Object elementType) {
+ super(realm, new HashSet(c), elementType);
+ this.elementType = elementType;
+ }
+
+ public bool add(Object o) {
+ getterCalled();
+ bool added = wrappedSet.add(o);
+ if (added) {
+ fireSetChange(Diffs.createSetDiff(Collections.singleton(o), Collections.EMPTY_SET));
+ }
+ return added;
+ }
+
+ public bool addAll(Collection c) {
+ getterCalled();
+ Set additions = new HashSet();
+ Iterator it = c.iterator();
+ while (it.hasNext()) {
+ Object element = it.next();
+ if (wrappedSet.add(element)) {
+ additions.add(element);
+ }
+ }
+ if (additions.size() > 0) {
+ fireSetChange(Diffs.createSetDiff(additions, Collections.EMPTY_SET));
+ return true;
+ }
+ return false;
+ }
+
+ public bool remove(Object o) {
+ getterCalled();
+ bool removed = wrappedSet.remove(o);
+ if (removed) {
+ fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET, Collections
+ .singleton(o)));
+ }
+ return removed;
+ }
+
+ public bool removeAll(Collection c) {
+ getterCalled();
+ Set removes = new HashSet();
+ Iterator it = c.iterator();
+ while (it.hasNext()) {
+ Object element = it.next();
+ if (wrappedSet.remove(element)) {
+ removes.add(element);
+ }
+ }
+ if (removes.size() > 0) {
+ fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET, removes));
+ return true;
+ }
+ return false;
+ }
+
+ public bool retainAll(Collection c) {
+ getterCalled();
+ Set removes = new HashSet();
+ Iterator it = wrappedSet.iterator();
+ while (it.hasNext()) {
+ Object element = it.next();
+ if (!c.contains(element)) {
+ it.remove();
+ removes.add(element);
+ }
+ }
+ if (removes.size() > 0) {
+ fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET, removes));
+ return true;
+ }
+ return false;
+ }
+
+ public void clear() {
+ getterCalled();
+ Set removes = new HashSet(wrappedSet);
+ wrappedSet.clear();
+ fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET, removes));
+ }
+
+ /**
+ * @param elementType can be null
+ * @return new instance with the default realm
+ */
+ public static WritableSet withElementType(Object elementType) {
+ return new WritableSet(Realm.getDefault(), new HashSet(), elementType);
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/package.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/package.html Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ Package-level Javadoc
+
+
+Provides classes that can be used to observe changes in sets.
+
+Package Specification
+
+This package provides classes that can be used to observe changes in sets.
+
+
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/AbstractObservableValue.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/AbstractObservableValue.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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 - bug 164653
+ * Matthew Hall - bug 208332
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.value;
+
+import org.eclipse.core.databinding.observable.AbstractObservable;
+import org.eclipse.core.databinding.observable.ObservableTracker;
+import org.eclipse.core.databinding.observable.Realm;
+
+/**
+ *
+ *
+ * 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.
+ *
+ * @since 1.0
+ *
+ */
+abstract public class AbstractObservableValue : AbstractObservable , IObservableValue {
+ /**
+ * Constructs a new instance with the default realm.
+ */
+ public this() {
+ this(Realm.getDefault());
+ }
+
+ /**
+ * @param realm
+ */
+ public this(Realm realm) {
+ super(realm);
+ }
+
+ public synchronized void addValueChangeListener(IValueChangeListener listener) {
+ addListener(ValueChangeEvent.TYPE, listener);
+ }
+
+ public synchronized void removeValueChangeListener(IValueChangeListener listener) {
+ removeListener(ValueChangeEvent.TYPE, listener);
+ }
+
+ final public void setValue(Object value) {
+ checkRealm();
+ doSetValue(value);
+ }
+
+ /**
+ * Template method for setting the value of the observable. By default the
+ * method throws an {@link UnsupportedOperationException}.
+ *
+ * @param value
+ */
+ protected void doSetValue(Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ protected void fireValueChange(ValueDiff diff) {
+ // fire general change event first
+ super.fireChange();
+ fireEvent(new ValueChangeEvent(this, diff));
+ }
+
+ public final Object getValue() {
+ getterCalled();
+ return doGetValue();
+ }
+
+ abstract protected Object doGetValue();
+
+ public bool isStale() {
+ getterCalled();
+ return false;
+ }
+
+ private void getterCalled() {
+ ObservableTracker.getterCalled(this);
+ }
+
+ protected void fireChange() {
+ throw new RuntimeException(
+ "fireChange should not be called, use fireValueChange() instead"); //$NON-NLS-1$
+ }
+
+ public synchronized void dispose() {
+ super.dispose();
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/AbstractVetoableValue.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/AbstractVetoableValue.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,95 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2007 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 - bug 164653
+ *******************************************************************************/
+package org.eclipse.core.databinding.observable.value;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.internal.databinding.Util;
+
+/**
+ *
+ *
+ * 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.
+ *
+ * @since 1.0
+ *
+ */
+public abstract class AbstractVetoableValue : AbstractObservableValue
+ , IVetoableValue {
+
+ /**
+ * Creates a new vetoable value.
+ */
+ public this() {
+ this(Realm.getDefault());
+ }
+
+ /**
+ * @param realm
+ */
+ public this(Realm realm) {
+ super(realm);
+ }
+
+ final protected void doSetValue(Object value) {
+ Object currentValue = doGetValue();
+ ValueDiff diff = Diffs.createValueDiff(currentValue, value);
+ bool okToProceed = fireValueChanging(diff);
+ if (!okToProceed) {
+ throw new ChangeVetoException("Change not permitted"); //$NON-NLS-1$
+ }
+ doSetApprovedValue(value);
+
+ if (!Util.equals(diff.getOldValue(), diff.getNewValue())) {
+ fireValueChange(diff);
+ }
+ }
+
+ /**
+ * Sets the value. Invoked after performing veto checks. Should not fire change events.
+ *
+ * @param value
+ */
+ protected abstract void doSetApprovedValue(Object value);
+
+ public synchronized void addValueChangingListener(
+ IValueChangingListener listener) {
+ addListener(ValueChangingEvent.TYPE, listener);
+ }
+
+ public synchronized void removeValueChangingListener(
+ IValueChangingListener listener) {
+ removeListener(ValueChangingEvent.TYPE, listener);
+ }
+
+ /**
+ * Notifies listeners about a pending change, and returns true if no
+ * listener vetoed the change.
+ *
+ * @param diff
+ * @return false if the change was vetoed, true otherwise
+ */
+ protected bool fireValueChanging(ValueDiff diff) {
+ checkRealm();
+
+ ValueChangingEvent event = new ValueChangingEvent(this, diff);
+ fireEvent(event);
+ return !event.veto;
+ }
+
+ public synchronized void dispose() {
+ super.dispose();
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ChangeVetoException.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ChangeVetoException.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2006 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
+ *******************************************************************************/
+package org.eclipse.core.databinding.observable.value;
+
+/**
+ * @since 1.0
+ *
+ */
+public class ChangeVetoException : RuntimeException {
+
+ /**
+ * @param string
+ */
+ public this(String string) {
+ super(string);
+ }
+
+ private static final long serialVersionUID = 1L;
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ComputedValue.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ComputedValue.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,261 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2008 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 - bug 116920
+ * Brad Reynolds - bug 147515
+ *******************************************************************************/
+package org.eclipse.core.databinding.observable.value;
+
+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;
+
+/**
+ * A Lazily calculated value that automatically computes and registers listeners
+ * on its dependencies as long as all of its dependencies are IObservable
+ * objects
+ *
+ * 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.
+ *
+ *
+ * @since 1.0
+ */
+public abstract class ComputedValue : AbstractObservableValue {
+
+ private bool dirty = true;
+
+ private bool stale = false;
+
+ private Object cachedValue = null;
+
+ /**
+ * Array of observables this computed value depends on. This field has a
+ * value of null
if we are not currently listening.
+ */
+ private IObservable[] dependencies = null;
+
+ /**
+ *
+ */
+ public this() {
+ this(Realm.getDefault(), null);
+ }
+
+ /**
+ * @param valueType
+ * can be null
+ */
+ public this(Object valueType) {
+ this(Realm.getDefault(), valueType);
+ }
+
+ /**
+ * @param realm
+ *
+ */
+ public this(Realm realm) {
+ this(realm, null);
+ }
+
+ /**
+ * @param realm
+ * @param valueType
+ */
+ public this(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.
+ *
+ *
+ * The Runnable calls computeValue and stores the result in cachedValue.
+ *
+ *
+ *
+ * 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.
+ *
+ *
+ *
+ * The IChangeListener is attached to every dependency.
+ *
+ *
+ */
+ private class PrivateInterface : Runnable, IChangeListener,
+ IStaleListener {
+ public void run() {
+ cachedValue = calculate();
+ }
+
+ public void handleStale(StaleEvent event) {
+ if (!dirty && !stale) {
+ stale = true;
+ fireStale();
+ }
+ }
+
+ public void handleChange(ChangeEvent event) {
+ makeDirty();
+ }
+ }
+
+ private PrivateInterface privateInterface = new PrivateInterface();
+
+ private Object valueType;
+
+ protected final Object 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 (int i = 0; i < newDependencies.length; i++) {
+ IObservable observable = newDependencies[i];
+ // 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.
+ *
+ * @return the object's value
+ */
+ protected abstract Object calculate();
+
+ protected final void makeDirty() {
+ if (!dirty) {
+ dirty = true;
+
+ stopListening();
+
+ // copy the old value
+ final Object oldValue = cachedValue;
+ // Fire the "dirty" event. This implementation recomputes the new
+ // value lazily.
+ fireValueChange(new class() ValueDiff {
+
+ public Object getOldValue() {
+ return oldValue;
+ }
+
+ public Object getNewValue() {
+ return getValue();
+ }
+ });
+ }
+ }
+
+ /**
+ *
+ */
+ private void stopListening() {
+ // Stop listening for dependency changes.
+ if (dependencies !is null) {
+ for (int i = 0; i < dependencies.length; i++) {
+ IObservable observable = dependencies[i];
+
+ observable.removeChangeListener(privateInterface);
+ observable.removeStaleListener(privateInterface);
+ }
+ dependencies = null;
+ }
+ }
+
+ public bool isStale() {
+ // we need to recompute, otherwise staleness wouldn't mean anything
+ getValue();
+ return stale;
+ }
+
+ public Object getValueType() {
+ return valueType;
+ }
+
+ // this method exists here so that we can call it from the runnable below.
+ /**
+ * @since 1.1
+ */
+ protected bool hasListeners() {
+ return super.hasListeners();
+ }
+
+ 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 class() Runnable {
+ public void run() {
+ if (dependencies is 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();
+ }
+ }
+ }
+ });
+ }
+
+ public synchronized void addValueChangeListener(
+ IValueChangeListener listener) {
+ super.addValueChangeListener(listener);
+ // If somebody is listening, we need to make sure we attach our own
+ // listeners
+ computeValueForListeners();
+ }
+
+ public synchronized void dispose() {
+ super.dispose();
+ stopListening();
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IObservableValue.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IObservableValue.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.value;
+
+import org.eclipse.core.databinding.observable.IObservable;
+import org.eclipse.core.databinding.observable.Realm;
+
+/**
+ * A value whose changes can be tracked by value change listeners.
+ *
+ * @noextend This interface is not intended to be extended by clients.
+ * @noimplement This interface is not intended to be implemented by clients.
+ * Clients should instead subclass one of the classes that
+ * implement this interface. Note that direct implementers of this
+ * interface outside of the framework will be broken in future
+ * releases when methods are added to this interface.
+ *
+ * @see AbstractObservableValue
+ *
+ * @since 1.0
+ */
+public interface IObservableValue : IObservable {
+
+ /**
+ * The value type of this observable value, or null
if this
+ * observable value is untyped.
+ *
+ * @return the value type, or null
+ */
+ public Object getValueType();
+
+ /**
+ * Returns the value. Must be invoked in the {@link Realm} of the observable.
+ *
+ * @return the current value
+ * @TrackedGetter
+ */
+ public Object getValue();
+
+ /**
+ * Sets the value. Must be invoked in the {@link Realm} of the observable.
+ *
+ * @param value
+ * the value to set
+ * @throws UnsupportedOperationException
+ * if this observable value cannot be set.
+ */
+ public void setValue(Object value);
+
+ /**
+ *
+ * @param listener
+ */
+ public void addValueChangeListener(IValueChangeListener listener);
+
+ /**
+ * @param listener
+ */
+ public void removeValueChangeListener(IValueChangeListener listener);
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IValueChangeListener.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IValueChangeListener.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.value;
+
+import org.eclipse.core.databinding.observable.IObservablesListener;
+
+/**
+ * Listener for changes to observable values.
+ *
+ * @since 1.0
+ *
+ */
+public interface IValueChangeListener : IObservablesListener {
+
+ /**
+ * Handles a change to an observable value. The given event object must only
+ * be used locally in this method because it may be reused for other change
+ * notifications. The diff object referenced by the event is immutable and
+ * may be used non-locally.
+ *
+ * @param event
+ * the event
+ */
+ void handleValueChange(ValueChangeEvent event);
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IValueChangingListener.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IValueChangingListener.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2007 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
+ *******************************************************************************/
+package org.eclipse.core.databinding.observable.value;
+
+import org.eclipse.core.databinding.observable.IObservablesListener;
+
+/**
+ * Listener for pre-change events for observable values.
+ *
+ * @since 1.0
+ *
+ */
+public interface IValueChangingListener : IObservablesListener {
+
+ /**
+ * This method is called when the value is about to change and provides an
+ * opportunity to veto the change. The given event object must only be used
+ * locally in this method because it may be reused for other change
+ * notifications. The diff object referenced by the event is immutable and
+ * may be used non-locally.
+ *
+ * @param event
+ */
+ public void handleValueChanging(ValueChangingEvent event);
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IVetoableValue.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IVetoableValue.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2008 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
+ *******************************************************************************/
+package org.eclipse.core.databinding.observable.value;
+
+/**
+ * An observable value whose changes can be vetoed by listeners.
+ *
+ * @noextend This interface is not intended to be extended by clients.
+ * @noimplement This interface is not intended to be implemented by clients.
+ * Clients should instead subclass one of the classes that
+ * implement this interface. Note that direct implementers of this
+ * interface outside of the framework will be broken in future
+ * releases when methods are added to this interface.
+ *
+ * @since 1.0
+ *
+ */
+public interface IVetoableValue : IObservableValue {
+
+ /**
+ * @param listener
+ */
+ public void addValueChangingListener(IValueChangingListener listener);
+
+ /**
+ * @param listener
+ */
+ public void removeValueChangingListener(IValueChangingListener listener);
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ValueChangeEvent.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ValueChangeEvent.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ ******************************************************************************/
+
+package org.eclipse.core.databinding.observable.value;
+
+import org.eclipse.core.databinding.observable.IObservablesListener;
+import org.eclipse.core.databinding.observable.ObservableEvent;
+
+/**
+ * Value change event describing a change of an {@link IObservableValue}
+ * object's current value.
+ *
+ * @since 1.0
+ *
+ */
+public class ValueChangeEvent : ObservableEvent {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 2305345286999701156L;
+
+ static final Object TYPE = new Object();
+
+ /**
+ * Description of the change to the source observable value. Listeners must
+ * not change this field.
+ */
+ public ValueDiff diff;
+
+ /**
+ * Creates a new value change event.
+ *
+ * @param source
+ * the source observable value
+ * @param diff
+ * the value change
+ */
+ public this(IObservableValue source, ValueDiff diff) {
+ super(source);
+ this.diff = diff;
+ }
+
+ /**
+ * Returns the observable value from which this event originated.
+ *
+ * @return returns the observable value from which this event originated
+ */
+ public IObservableValue getObservableValue() {
+ return cast(IObservableValue) source;
+ }
+
+ protected void dispatch(IObservablesListener listener) {
+ (cast(IValueChangeListener) listener).handleValueChange(this);
+ }
+
+ protected Object getListenerType() {
+ return TYPE;
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ValueChangingEvent.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ValueChangingEvent.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,74 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ ******************************************************************************/
+
+package org.eclipse.core.databinding.observable.value;
+
+import org.eclipse.core.databinding.observable.IObservablesListener;
+import org.eclipse.core.databinding.observable.ObservableEvent;
+
+/**
+ * Value changing event describing a pending change of an
+ * {@link IObservableValue} object's current value. Listeners can veto the
+ * pending change by setting {@link #veto} to true
.
+ *
+ * @since 1.0
+ *
+ */
+public class ValueChangingEvent : ObservableEvent {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 2305345286999701156L;
+
+ static final Object TYPE = new Object();
+
+ /**
+ * Description of the change to the source observable value. Listeners must
+ * not change this field.
+ */
+ public ValueDiff diff;
+
+ /**
+ * Flag for vetoing this change. Default value is false
, can
+ * be set to true
by listeners to veto this change.
+ */
+ public bool veto = false;
+
+ /**
+ * Creates a new value changing event.
+ *
+ * @param source
+ * the source observable value
+ * @param diff
+ * the value change
+ */
+ public this(IObservableValue source, ValueDiff diff) {
+ super(source);
+ this.diff = diff;
+ }
+
+ /**
+ * @return the observable value from which this event originated
+ */
+ public IObservableValue getObservableValue() {
+ return cast(IObservableValue) source;
+ }
+
+ protected void dispatch(IObservablesListener listener) {
+ (cast(IValueChangingListener) listener).handleValueChanging(this);
+ }
+
+ protected Object getListenerType() {
+ return TYPE;
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ValueDiff.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ValueDiff.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.value;
+
+import org.eclipse.core.databinding.observable.Diffs;
+
+/**
+ * @since 1.0
+ *
+ */
+public abstract class ValueDiff {
+ /**
+ * Creates a value diff.
+ */
+ public this() {
+ }
+
+ /**
+ * @return the old value
+ */
+ public abstract Object getOldValue();
+
+ /**
+ * @return the new value
+ */
+ public abstract Object getNewValue();
+
+ public override equals_t opEquals(Object obj) {
+ if ( null !is cast(ValueDiff)obj ) {
+ ValueDiff val = cast(ValueDiff) obj;
+
+ return Diffs.equals(val.getNewValue(), getNewValue())
+ && Diffs.equals(val.getOldValue(), getOldValue());
+
+ }
+ return false;
+ }
+
+ public override hash_t toHash() {
+ final int prime = 31;
+ int result = 1;
+ Object nv = getNewValue();
+ Object ov = getOldValue();
+ result = prime * result + ((nv is null) ? 0 : nv.hashCode());
+ result = prime * result + ((ov is null) ? 0 : ov.hashCode());
+ return result;
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ StringBuffer buffer = new StringBuffer();
+ buffer
+ .append(getClass().getName())
+ .append("{oldValue [") //$NON-NLS-1$
+ .append(getOldValue() !is null ? getOldValue().toString() : "null") //$NON-NLS-1$
+ .append("], newValue [") //$NON-NLS-1$
+ .append(getNewValue() !is null ? getNewValue().toString() : "null") //$NON-NLS-1$
+ .append("]}"); //$NON-NLS-1$
+
+ return buffer.toString();
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/WritableValue.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/WritableValue.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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 - bug 158687
+ * Brad Reynolds - bug 164653, 147515
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.observable.value;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.Realm;
+
+/**
+ * Mutable (writable) implementation of {@link IObservableValue} that will maintain a value and fire
+ * change events when the value changes.
+ *
+ * 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.
+ *
+ * @since 1.0
+ */
+public class WritableValue : AbstractObservableValue {
+
+ private final Object valueType;
+
+ /**
+ * Constructs a new instance with the default realm, a null
+ * value type, and a null
value.
+ */
+ public this() {
+ this(null, null);
+ }
+
+ /**
+ * Constructs a new instance with the default realm.
+ *
+ * @param initialValue
+ * can be null
+ * @param valueType
+ * can be null
+ */
+ public this(Object initialValue, Object valueType) {
+ this(Realm.getDefault(), initialValue, valueType);
+ }
+
+ /**
+ * Constructs a new instance with the provided realm
, a
+ * null
value type, and a null
initial value.
+ *
+ * @param realm
+ */
+ public this(Realm realm) {
+ this(realm, null, null);
+ }
+
+ /**
+ * Constructs a new instance.
+ *
+ * @param realm
+ * @param initialValue
+ * can be null
+ * @param valueType
+ * can be null
+ */
+ public this(Realm realm, Object initialValue, Object valueType) {
+ super(realm);
+ this.valueType = valueType;
+ this.value = initialValue;
+ }
+
+ private Object value = null;
+
+ public Object doGetValue() {
+ return value;
+ }
+
+ /**
+ * @param value
+ * The value to set.
+ */
+ public void doSetValue(Object value) {
+ bool changed = false;
+
+ if (this.value is null && value !is null) {
+ changed = true;
+ } else if (this.value !is null && !this.value.equals(value)) {
+ changed = true;
+ }
+
+ if (changed) {
+ fireValueChange(Diffs.createValueDiff(this.value, this.value = value));
+ }
+ }
+
+ public Object getValueType() {
+ return valueType;
+ }
+
+ /**
+ * @param elementType can be null
+ * @return new instance with the default realm and a value of null
+ */
+ public static WritableValue withValueType(Object elementType) {
+ return new WritableValue(Realm.getDefault(), null, elementType);
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/package.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/package.html Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ Package-level Javadoc
+
+
+Provides classes that can be used to observe changes in discrete values.
+
+Package Specification
+
+This package provides classes that can be used to observe changes in discrete values.
+
+
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/util/ILogger.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/util/ILogger.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2007 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:
+ * Chris Gross (schtoo@schtoo.com) - initial API and implementation
+ * (bug 49497 [RCP] JFace dependency on org.eclipse.core.runtime enlarges standalone JFace applications)
+ *******************************************************************************/
+
+package org.eclipse.core.databinding.util;
+
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * A mechanism to log errors throughout JFace Data Binding.
+ *
+ * Clients may provide their own implementation to change how errors are logged
+ * from within JFace Data Binding.
+ *
+ *
+ * @see Policy#getLog()
+ * @see Policy#setLogcast(ILogger)
+ * @since 1.1
+ */
+public interface ILogger {
+
+ /**
+ * Logs the given status.
+ *
+ * @param status
+ * the status to log
+ */
+ public void log(IStatus status);
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/util/Policy.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/util/Policy.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2007 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
+ * Chris Gross (schtoo@schtoo.com) - support for ILogger added
+ * (bug 49497 [RCP] JFace dependency on org.eclipse.core.runtime enlarges standalone JFace applications)
+ * Brad Reynolds - bug 164653
+ * Tom Schindl - bug 194587
+ *******************************************************************************/
+package org.eclipse.core.databinding.util;
+
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * The Policy class handles settings for behaviour, debug flags and logging
+ * within JFace Data Binding.
+ *
+ * @since 1.1
+ */
+public class Policy {
+
+ /**
+ * Constant for the the default setting for debug options.
+ */
+ public static final bool DEFAULT = false;
+
+ /**
+ * The unique identifier of the JFace plug-in.
+ */
+ public static final String JFACE_DATABINDING = "org.eclipse.core.databinding";//$NON-NLS-1$
+
+ private static ILogger log;
+
+ /**
+ * Returns the dummy log to use if none has been set
+ */
+ private static ILogger getDummyLog() {
+ return new class() ILogger {
+ public void log(IStatus status) {
+ System.err.println(status.getPlugin() + " - " + status.getCode() + " - " + status.getMessage()); //$NON-NLS-1$//$NON-NLS-2$
+ if( status.getException() !is null ) {
+ status.getException().printStackTrace(System.err);
+ }
+ }
+ };
+ }
+
+ /**
+ * Sets the logger used by JFace Data Binding to log errors.
+ *
+ * @param logger
+ * the logger to use, or null
to use the default
+ * logger
+ */
+ public static synchronized void setLog(ILogger logger) {
+ log = logger;
+ }
+
+ /**
+ * Returns the logger used by JFace Data Binding to log errors.
+ *
+ * The default logger prints the status to System.err
.
+ *
+ *
+ * @return the logger
+ */
+ public static synchronized ILogger getLog() {
+ if (log is null) {
+ log = getDummyLog();
+ }
+ return log;
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/util/package.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/util/package.html Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ Package-level Javadoc
+
+
+Provides general utilities for data binding.
+
+Package Specification
+
+This package provides general utilities for data binding.
+
+
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ConstantObservableValue.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ConstantObservableValue.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2007-2008 Matt Carter 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:
+ * Matt Carter - initial API and implementation (bug 212518)
+ * Matthew Hall - bug 212518
+ *******************************************************************************/
+package org.eclipse.core.internal.databinding.observable;
+
+import org.eclipse.core.databinding.observable.IChangeListener;
+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.value.IObservableValue;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.core.databinding.observable.value.WritableValue;
+import org.eclipse.core.runtime.Assert;
+
+/**
+ * An immutable {@link IObservableValue}.
+ *
+ * @see WritableValue
+ */
+public class ConstantObservableValue : IObservableValue {
+ final Realm realm;
+ final Object value;
+ final Object type;
+
+ /**
+ * Construct a constant value of the given type, in the default realm.
+ *
+ * @param value
+ * immutable value
+ * @param type
+ * type
+ */
+ public this(Object value, Object type) {
+ this(Realm.getDefault(), value, type);
+ }
+
+ /**
+ * Construct a constant value of the given type, in the given realm.
+ *
+ * @param realm
+ * Realm
+ * @param value
+ * immutable value
+ * @param type
+ * type
+ */
+ public this(Realm realm, Object value, Object type) {
+ Assert.isNotNull(realm, "Realm cannot be null"); //$NON-NLS-1$
+ this.realm = realm;
+ this.value = value;
+ this.type = type;
+ }
+
+ public Object getValueType() {
+ return type;
+ }
+
+ public Object getValue() {
+ ObservableTracker.getterCalled(this);
+ return value;
+ }
+
+ public void setValue(Object value) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void addValueChangeListener(IValueChangeListener listener) {
+ // ignore
+ }
+
+ public void removeValueChangeListener(IValueChangeListener listener) {
+ // ignore
+ }
+
+ public void addChangeListener(IChangeListener listener) {
+ // ignore
+ }
+
+ public void addStaleListener(IStaleListener listener) {
+ // ignore
+ }
+
+ public void dispose() {
+ // nothing to dispose
+ }
+
+ public Realm getRealm() {
+ return realm;
+ }
+
+ public bool isStale() {
+ return false;
+ }
+
+ public void removeChangeListener(IChangeListener listener) {
+ // ignore
+ }
+
+ public void removeStaleListener(IStaleListener listener) {
+ // ignore
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/EmptyObservableList.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/EmptyObservableList.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,224 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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
+ * Matthew Hall - bug 208858
+ * Matthew Hall - bug 208332
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.eclipse.core.databinding.observable.IChangeListener;
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.list.IListChangeListener;
+import org.eclipse.core.databinding.observable.list.IObservableList;
+import org.eclipse.core.runtime.Assert;
+
+/**
+ * Singleton empty list
+ */
+public class EmptyObservableList : IObservableList {
+
+ private static final List emptyList = Collections.EMPTY_LIST;
+
+ private Realm realm;
+ private Object elementType;
+
+ /**
+ * Creates an empty list. This list may be disposed multiple times
+ * without any side-effects.
+ *
+ * @param realm
+ * the realm of the constructed list
+ */
+ public this(Realm realm) {
+ this(realm, null);
+ }
+
+ /**
+ * Creates an empty list. This list may be disposed multiple times
+ * without any side-effects.
+ *
+ * @param realm
+ * the realm of the constructed list
+ * @param elementType
+ * the element type of the constructed list
+ * @since 1.1
+ */
+ public this(Realm realm, Object elementType) {
+ this.realm = realm;
+ this.elementType = elementType;
+ }
+
+ public void addListChangeListener(IListChangeListener listener) {
+ // ignore
+ }
+
+ public void removeListChangeListener(IListChangeListener listener) {
+ // ignore
+ }
+
+ public Object getElementType() {
+ return elementType;
+ }
+
+ public int size() {
+ checkRealm();
+ return 0;
+ }
+
+ void checkRealm() {
+ Assert.isTrue(realm.isCurrent(),
+ "Observable cannot be accessed outside its realm"); //$NON-NLS-1$
+ }
+
+ public bool isEmpty() {
+ checkRealm();
+ return true;
+ }
+
+ public bool contains(Object o) {
+ checkRealm();
+ return false;
+ }
+
+ public Iterator iterator() {
+ checkRealm();
+ return emptyList.iterator();
+ }
+
+ public Object[] toArray() {
+ checkRealm();
+ return emptyList.toArray();
+ }
+
+ public Object[] toArray(Object[] a) {
+ return emptyList.toArray(a);
+ }
+
+ public bool add(Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool remove(Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool containsAll(Collection c) {
+ checkRealm();
+ return c.isEmpty();
+ }
+
+ public bool addAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool retainAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool removeAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void addChangeListener(IChangeListener listener) {
+ }
+
+ public void removeChangeListener(IChangeListener listener) {
+ }
+
+ public void addStaleListener(IStaleListener listener) {
+ }
+
+ public void removeStaleListener(IStaleListener listener) {
+ }
+
+ public bool isStale() {
+ checkRealm();
+ return false;
+ }
+
+ public void dispose() {
+ }
+
+ public bool addAll(int index, Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object get(int index) {
+ return emptyList.get(index);
+ }
+
+ public int indexOf(Object o) {
+ return -1;
+ }
+
+ public int lastIndexOf(Object o) {
+ return -1;
+ }
+
+ public ListIterator listIterator() {
+ return emptyList.listIterator();
+ }
+
+ public ListIterator listIterator(int index) {
+ return emptyList.listIterator(index);
+ }
+
+ public Object remove(int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object set(int index, Object element) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object move(int oldIndex, int newIndex) {
+ throw new UnsupportedOperationException();
+ }
+
+ public List subList(int fromIndex, int toIndex) {
+ return emptyList.subList(fromIndex, toIndex);
+ }
+
+ public void add(int index, Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Realm getRealm() {
+ return realm;
+ }
+
+ public override equals_t opEquals(Object obj) {
+ checkRealm();
+ if (obj is this)
+ return true;
+ if (obj is null)
+ return false;
+ if (!( null !is cast(List)obj ))
+ return false;
+
+ return (cast(List) obj).isEmpty();
+ }
+
+ public override hash_t toHash() {
+ checkRealm();
+ return 1;
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/EmptyObservableSet.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/EmptyObservableSet.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,176 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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
+ * Matthew Hall - bug 208332
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.IChangeListener;
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.set.IObservableSet;
+import org.eclipse.core.databinding.observable.set.ISetChangeListener;
+import org.eclipse.core.runtime.Assert;
+
+/**
+ * Singleton empty set
+ */
+public class EmptyObservableSet : IObservableSet {
+
+ private static final Set emptySet = Collections.EMPTY_SET;
+
+ private Realm realm;
+ private Object elementType;
+
+ /**
+ * Creates a singleton empty set. This set may be disposed multiple times
+ * without any side-effects.
+ *
+ * @param realm
+ * the realm of the constructed set
+ */
+ public this(Realm realm) {
+ this(realm, null);
+ }
+
+ /**
+ * Creates a singleton empty set. This set may be disposed multiple times
+ * without any side-effects.
+ *
+ * @param realm
+ * the realm of the constructed set
+ * @param elementType
+ * the element type of the constructed set
+ * @since 1.1
+ */
+ public this(Realm realm, Object elementType) {
+ this.realm = realm;
+ this.elementType = elementType;
+ }
+
+ public void addSetChangeListener(ISetChangeListener listener) {
+ }
+
+ public void removeSetChangeListener(ISetChangeListener listener) {
+ }
+
+ public Object getElementType() {
+ return elementType;
+ }
+
+ public int size() {
+ checkRealm();
+ return 0;
+ }
+
+ private void checkRealm() {
+ Assert.isTrue(realm.isCurrent(),
+ "Observable cannot be accessed outside its realm"); //$NON-NLS-1$
+ }
+
+ public bool isEmpty() {
+ checkRealm();
+ return true;
+ }
+
+ public bool contains(Object o) {
+ checkRealm();
+ return false;
+ }
+
+ public Iterator iterator() {
+ checkRealm();
+ return emptySet.iterator();
+ }
+
+ public Object[] toArray() {
+ checkRealm();
+ return emptySet.toArray();
+ }
+
+ public Object[] toArray(Object[] a) {
+ return emptySet.toArray(a);
+ }
+
+ public bool add(Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool remove(Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool containsAll(Collection c) {
+ checkRealm();
+ return c.isEmpty();
+ }
+
+ public bool addAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool retainAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool removeAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void addChangeListener(IChangeListener listener) {
+ }
+
+ public void removeChangeListener(IChangeListener listener) {
+ }
+
+ public void addStaleListener(IStaleListener listener) {
+ }
+
+ public void removeStaleListener(IStaleListener listener) {
+ }
+
+ public bool isStale() {
+ checkRealm();
+ return false;
+ }
+
+ public void dispose() {
+ }
+
+ public Realm getRealm() {
+ return realm;
+ }
+
+ public override equals_t opEquals(Object obj) {
+ checkRealm();
+ if (obj is this)
+ return true;
+ if (obj is null)
+ return false;
+ if (!( null !is cast(Set)obj ))
+ return false;
+
+ return (cast(Set) obj).isEmpty();
+ }
+
+ public override hash_t toHash() {
+ checkRealm();
+ return 0;
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/IStalenessConsumer.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/IStalenessConsumer.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable;
+
+/**
+ * @since 1.0
+ *
+ */
+public interface IStalenessConsumer {
+ /**
+ * @param stale
+ *
+ */
+ public void setStale(bool stale);
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/MapEntryObservableValue.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/MapEntryObservableValue.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Marko Topolnik 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:
+ * Marko Topolnik - initial API and implementation (bug 184830)
+ * Matthew Hall - bug 184830
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.ObservableTracker;
+import org.eclipse.core.databinding.observable.StaleEvent;
+import org.eclipse.core.databinding.observable.map.IMapChangeListener;
+import org.eclipse.core.databinding.observable.map.IObservableMap;
+import org.eclipse.core.databinding.observable.map.MapChangeEvent;
+import org.eclipse.core.databinding.observable.value.AbstractObservableValue;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+
+/**
+ * An {@link IObservableValue} that tracks the value of an entry in an
+ * {@link IObservableMap}, identified by the entry's key.
+ *
+ * @since 1.1
+ */
+public class MapEntryObservableValue : AbstractObservableValue {
+ private IObservableMap map;
+ private Object key;
+ private Object valueType;
+
+ private IMapChangeListener changeListener = new class() IMapChangeListener {
+ public void handleMapChange(final MapChangeEvent event) {
+ if (event.diff.getAddedKeys().contains(key)) {
+ final Object newValue = event.diff.getNewValue(key);
+ if (newValue !is null) {
+ fireValueChange(Diffs.createValueDiff(null, newValue));
+ }
+ } else if (event.diff.getChangedKeys().contains(key)) {
+ fireValueChange(Diffs.createValueDiff(event.diff
+ .getOldValue(key), event.diff.getNewValue(key)));
+ } else if (event.diff.getRemovedKeys().contains(key)) {
+ final Object oldValue = event.diff.getOldValue(key);
+ if (oldValue !is null) {
+ fireValueChange(Diffs.createValueDiff(oldValue, null));
+ }
+ }
+ }
+ };
+
+ private IStaleListener staleListener = new class() IStaleListener {
+ public void handleStale(StaleEvent staleEvent) {
+ fireStale();
+ }
+ };
+
+ /**
+ * Creates a map entry observable.
+ *
+ * @param map
+ * the observable map whose entry will be tracked
+ * @param key
+ * the key identifying the entry whose value will be tracked
+ * @param valueType
+ * the type of the value
+ */
+ public this(IObservableMap map, Object key,
+ Object valueType) {
+ super(map.getRealm());
+ this.map = map;
+ this.key = key;
+ this.valueType = valueType;
+
+ map.addMapChangeListener(changeListener);
+ map.addStaleListener(staleListener);
+ }
+
+ public Object getValueType() {
+ return this.valueType;
+ }
+
+ public bool isStale() {
+ ObservableTracker.getterCalled(this);
+ return map.isStale();
+ }
+
+ public synchronized void dispose() {
+ if (map !is null) {
+ map.removeMapChangeListener(changeListener);
+ map.removeStaleListener(staleListener);
+ map = null;
+ changeListener = null;
+ staleListener = null;
+ }
+ super.dispose();
+ }
+
+ protected Object doGetValue() {
+ return this.map.get(this.key);
+ }
+
+ protected void doSetValue(Object value) {
+ this.map.put(this.key, value);
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ProxyObservableList.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ProxyObservableList.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,73 @@
+/*******************************************************************************
+ * 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 (bug 208332)
+ * IBM Corporation - initial API and implementation
+ * (through ProxyObservableSet.java)
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable;
+
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.StaleEvent;
+import org.eclipse.core.databinding.observable.list.*;
+
+/**
+ * Wraps an observable list. This object acts like an exact copy of the original
+ * list, and tracks all the changes in the original. The only difference is that
+ * disposing the wrapper will not dispose the original. You can use this
+ * whenever you need to return an IObservableList from a method that expects the
+ * caller to dispose the list, but you have an IObservableList that you don't
+ * want disposed.
+ *
+ * @since 1.1
+ */
+public class ProxyObservableList : ObservableList {
+ private IListChangeListener listChangelistener = new class() IListChangeListener {
+ public void handleListChange(ListChangeEvent event) {
+ fireListChange(event.diff);
+ }
+ };
+
+ private IStaleListener staleListener = new class() IStaleListener {
+ public void handleStale(StaleEvent event) {
+ fireStale();
+ }
+ };
+
+ private IObservableList wrappedList;
+
+ /**
+ * Constructs a ProxyObservableList that tracks the state of the given list.
+ *
+ * @param wrappedList
+ * the list being wrapped
+ */
+ public this(IObservableList wrappedList) {
+ super(wrappedList.getRealm(), wrappedList, wrappedList.getElementType());
+ this.wrappedList = wrappedList;
+ wrappedList.addListChangeListener(listChangelistener);
+ wrappedList.addStaleListener(staleListener);
+ }
+
+ public bool isStale() {
+ getterCalled();
+ return wrappedList is null ? false : wrappedList.isStale();
+ }
+
+ public void dispose() {
+ if (wrappedList !is null) {
+ wrappedList.removeListChangeListener(listChangelistener);
+ listChangelistener = null;
+ wrappedList.removeStaleListener(staleListener);
+ staleListener = null;
+ wrappedList = null;
+ }
+ super.dispose();
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ProxyObservableSet.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ProxyObservableSet.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 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
+ * Matthew Hall - bug 208332
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.StaleEvent;
+import org.eclipse.core.databinding.observable.set.AbstractObservableSet;
+import org.eclipse.core.databinding.observable.set.IObservableSet;
+import org.eclipse.core.databinding.observable.set.ISetChangeListener;
+import org.eclipse.core.databinding.observable.set.SetChangeEvent;
+
+/**
+ * Wraps an observable set. This object acts like an exact copy of the original
+ * set, and tracks all the changes in the original. The only difference is that
+ * disposing the wrapper will not dispose the original. You can use this
+ * whenever you need to return an IObservableSet from a method that expects the
+ * caller to dispose the set, but you have an IObservableSet that you don't want
+ * disposed.
+ */
+public class ProxyObservableSet : AbstractObservableSet {
+ private IObservableSet wrappedSet;
+ private Object elementType;
+
+ private ISetChangeListener setChangeListener = new class() ISetChangeListener {
+ public void handleSetChange(SetChangeEvent event) {
+ fireSetChange(event.diff);
+ }
+ };
+
+ private IStaleListener staleListener = new class() IStaleListener {
+ public void handleStale(StaleEvent staleEvent) {
+ fireStale();
+ }
+ };
+
+ /**
+ * Constructs a ProxyObservableSet that tracks the state of the given set.
+ *
+ * @param wrappedSet
+ * the set being wrapped
+ */
+ public this(IObservableSet wrappedSet) {
+ super(wrappedSet.getRealm());
+ this.wrappedSet = wrappedSet;
+ this.elementType = wrappedSet.getElementType();
+ wrappedSet.addSetChangeListener(setChangeListener);
+ wrappedSet.addStaleListener(staleListener);
+ }
+
+ protected Set getWrappedSet() {
+ return wrappedSet is null ? Collections.EMPTY_SET : wrappedSet;
+ }
+
+ public Object getElementType() {
+ return elementType;
+ }
+
+ public bool isStale() {
+ getterCalled();
+ return wrappedSet is null ? false : wrappedSet.isStale();
+ }
+
+ public void dispose() {
+ if (wrappedSet !is null) {
+ wrappedSet.removeSetChangeListener(setChangeListener);
+ setChangeListener = null;
+ wrappedSet.removeStaleListener(staleListener);
+ staleListener = null;
+ wrappedSet = null;
+ }
+ elementType = null;
+ super.dispose();
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/StalenessObservableValue.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/StalenessObservableValue.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2008 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:
+ * Boris Bokowski, IBM Corporation - initial API and implementation
+ * Matthew Hall - bug 212468
+ *******************************************************************************/
+package org.eclipse.core.internal.databinding.observable;
+
+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.StaleEvent;
+import org.eclipse.core.databinding.observable.value.AbstractObservableValue;
+
+/**
+ * An observable value that tracks the staleness of an {@link IObservable}.
+ *
+ * @since 1.1
+ */
+public class StalenessObservableValue : AbstractObservableValue {
+
+ private class MyListener : IChangeListener, IStaleListener {
+ public void handleChange(ChangeEvent event) {
+ if (stale && !event.getObservable().isStale()) {
+ stale = false;
+ fireValueChange(Diffs.createValueDiff(Boolean.TRUE,
+ Boolean.FALSE));
+ }
+ }
+
+ public void handleStale(StaleEvent staleEvent) {
+ if (!stale) {
+ stale = true;
+ fireValueChange(Diffs.createValueDiff(Boolean.FALSE,
+ Boolean.TRUE));
+ }
+ }
+ }
+
+ private IObservable tracked;
+ private bool stale;
+ private MyListener listener = new MyListener();
+
+ /**
+ * Constructs a StalenessObservableValue that tracks the staleness of the
+ * given {@link IObservable}.
+ *
+ * @param observable
+ * the observable to track
+ */
+ public this(IObservable observable) {
+ super(observable.getRealm());
+ this.tracked = observable;
+ this.stale = observable.isStale();
+ tracked.addChangeListener(listener);
+ tracked.addStaleListener(listener);
+ }
+
+ protected Object doGetValue() {
+ return tracked.isStale() ? Boolean.TRUE : Boolean.FALSE;
+ }
+
+ public Object getValueType() {
+ return Boolean.TYPE;
+ }
+
+ public synchronized void dispose() {
+ if (tracked !is null) {
+ tracked.removeChangeListener(listener);
+ tracked.removeStaleListener(listener);
+ tracked = null;
+ listener = null;
+ }
+ super.dispose();
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/StalenessTracker.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/StalenessTracker.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * Copyright (c) 2006 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
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable;
+
+import java.util.HashMap;
+import java.util.Map;
+
+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.StaleEvent;
+import org.eclipse.core.internal.databinding.IdentityWrapper;
+
+/**
+ * @since 1.0
+ *
+ */
+public class StalenessTracker {
+
+ private Map staleMap = new HashMap();
+
+ private int staleCount = 0;
+
+ private final IStalenessConsumer stalenessConsumer;
+
+ private class ChildListener : IStaleListener, IChangeListener {
+ public void handleStale(StaleEvent event) {
+ processStalenessChange(cast(IObservable) event.getSource(), true);
+ }
+
+ public void handleChange(ChangeEvent event) {
+ processStalenessChange(cast(IObservable) event.getSource(), true);
+ }
+ }
+
+ private ChildListener childListener = new ChildListener();
+
+ /**
+ * @param observables
+ * @param stalenessConsumer
+ */
+ public this(IObservable[] observables,
+ IStalenessConsumer stalenessConsumer) {
+ this.stalenessConsumer = stalenessConsumer;
+ for (int i = 0; i < observables.length; i++) {
+ IObservable observable = observables[i];
+ doAddObservable(observable, false);
+ }
+ stalenessConsumer.setStale(staleCount > 0);
+ }
+
+ /**
+ * @param child
+ * @param callback
+ */
+ public void processStalenessChange(IObservable child, bool callback) {
+ bool oldStale = staleCount > 0;
+ IdentityWrapper wrappedChild = new IdentityWrapper(child);
+ bool oldChildStale = getOldChildStale(wrappedChild);
+ bool newChildStale = child.isStale();
+ if (oldChildStale !is newChildStale) {
+ if (oldChildStale) {
+ staleCount--;
+ } else {
+ staleCount++;
+ }
+ staleMap.put(wrappedChild, newChildStale ? Boolean.TRUE : Boolean.FALSE);
+ }
+ bool newStale = staleCount > 0;
+ if (callback && (newStale !is oldStale)) {
+ stalenessConsumer.setStale(newStale);
+ }
+ }
+
+ /**
+ * @param wrappedChild
+ */
+ private bool getOldChildStale(IdentityWrapper wrappedChild) {
+ Object oldChildValue = staleMap.get(wrappedChild);
+ bool oldChildStale = oldChildValue is null ? false
+ : (cast(Boolean) oldChildValue).booleanValue();
+ return oldChildStale;
+ }
+
+ /**
+ * @param observable
+ */
+ public void addObservable(IObservable observable) {
+ doAddObservable(observable, true);
+ }
+
+ private void doAddObservable(IObservable observable, bool callback) {
+ processStalenessChange(observable, callback);
+ observable.addChangeListener(childListener);
+ observable.addStaleListener(childListener);
+ }
+
+ /**
+ * @param observable
+ */
+ public void removeObservable(IObservable observable) {
+ bool oldStale = staleCount > 0;
+ IdentityWrapper wrappedChild = new IdentityWrapper(observable);
+ bool oldChildStale = getOldChildStale(wrappedChild);
+ if (oldChildStale) {
+ staleCount--;
+ }
+ staleMap.remove(wrappedChild);
+ observable.removeChangeListener(childListener);
+ observable.removeStaleListener(childListener);
+ bool newStale = staleCount > 0;
+ if (newStale !is oldStale) {
+ stalenessConsumer.setStale(newStale);
+ }
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableList.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableList.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2006-2008 Cerner 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:
+ * Brad Reynolds - initial API and implementation
+ * Matthew Hall - bug 208332
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable;
+
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.StaleEvent;
+import org.eclipse.core.databinding.observable.list.IListChangeListener;
+import org.eclipse.core.databinding.observable.list.IObservableList;
+import org.eclipse.core.databinding.observable.list.ListChangeEvent;
+import org.eclipse.core.databinding.observable.list.ObservableList;
+
+/**
+ * ObservableList implementation that prevents modification by consumers. Events
+ * in the originating wrapped list are propagated and thrown from this instance
+ * when appropriate. All mutators throw an UnsupportedOperationException.
+ *
+ * @since 1.0
+ */
+/*
+ * Implementation makes the assumption that the superclass cast(ObservableList) is
+ * unmodifiable and that all modify methods throw an
+ * UnsupportedOperationException.
+ */
+public class UnmodifiableObservableList : ObservableList {
+ /**
+ * List that is being made unmodifiable.
+ */
+ private IObservableList wrappedList;
+
+ private IListChangeListener listChangeListener = new class() IListChangeListener {
+ public void handleListChange(ListChangeEvent event) {
+ // Fires a Change and then ListChange event.
+ fireListChange(event.diff);
+ }
+ };
+
+ private IStaleListener staleListener = new class() IStaleListener {
+ public void handleStale(StaleEvent event) {
+ fireStale();
+ }
+ };
+
+ /**
+ * @param wrappedList
+ */
+ public this(IObservableList wrappedList) {
+ super(wrappedList.getRealm(), wrappedList, wrappedList.getElementType());
+ this.wrappedList = wrappedList;
+
+ wrappedList.addListChangeListener(listChangeListener);
+
+ wrappedList.addStaleListener(staleListener);
+ }
+
+ /**
+ * Because this instance is immutable staleness cannot be changed.
+ *
+ * @throws UnsupportedOperationException
+ * because this instance is unmodifiable.
+ */
+ public void setStale(bool stale) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool isStale() {
+ getterCalled();
+ return wrappedList is null ? false : wrappedList.isStale();
+ }
+
+ public synchronized void dispose() {
+ if (wrappedList !is null) {
+ wrappedList.removeListChangeListener(listChangeListener);
+ wrappedList.removeStaleListener(staleListener);
+ wrappedList = null;
+ }
+ listChangeListener = null;
+ staleListener = null;
+ super.dispose();
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableSet.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableSet.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * 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 (bug 208332)
+ * Brad Reynolds - initial API and implementation
+ * (through UnmodifiableObservableList.java)
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable;
+
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.StaleEvent;
+import org.eclipse.core.databinding.observable.set.*;
+
+/**
+ * ObservableList implementation that prevents modification by consumers. Events
+ * in the originating wrapped list are propagated and thrown from this instance
+ * when appropriate. All mutators throw an UnsupportedOperationException.
+ *
+ * @since 1.1
+ */
+public class UnmodifiableObservableSet : ObservableSet {
+ private ISetChangeListener setChangeListener = new class() ISetChangeListener {
+ public void handleSetChange(SetChangeEvent event) {
+ fireSetChange(event.diff);
+ }
+ };
+
+ private IStaleListener staleListener = new class() IStaleListener {
+ public void handleStale(StaleEvent event) {
+ fireStale();
+ }
+ };
+
+ private IObservableSet wrappedSet;
+
+ /**
+ * @param wrappedSet
+ */
+ public this(IObservableSet wrappedSet) {
+ super(wrappedSet.getRealm(), wrappedSet, wrappedSet.getElementType());
+
+ this.wrappedSet = wrappedSet;
+
+ wrappedSet.addSetChangeListener(setChangeListener);
+ wrappedSet.addStaleListener(staleListener);
+ }
+
+ /**
+ * Because this instance is immutable staleness cannot be changed.
+ */
+ public void setStale(bool stale) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool isStale() {
+ getterCalled();
+ return wrappedSet is null ? false : wrappedSet.isStale();
+ }
+
+ public synchronized void dispose() {
+ if (wrappedSet !is null) {
+ wrappedSet.removeSetChangeListener(setChangeListener);
+ wrappedSet.removeStaleListener(staleListener);
+ wrappedSet = null;
+ }
+ setChangeListener = null;
+ staleListener = null;
+ super.dispose();
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableValue.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableValue.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * Copyright (c) 2008 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 219909)
+ * Matthew Hall - bug 237884
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable;
+
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.StaleEvent;
+import org.eclipse.core.databinding.observable.value.AbstractObservableValue;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
+
+/**
+ * An unmodifiable wrapper class for IObservableValue instances.
+ * @since 1.1
+ */
+public class UnmodifiableObservableValue : AbstractObservableValue {
+ private IObservableValue wrappedValue;
+
+ /**
+ * Constructs an UnmodifiableObservableValue which wraps the given
+ * observable value
+ *
+ * @param wrappedValue
+ * the observable value to wrap in an unmodifiable instance.
+ */
+ public this(IObservableValue wrappedValue) {
+ super(wrappedValue.getRealm());
+
+ this.wrappedValue = wrappedValue;
+ wrappedValue.addValueChangeListener(new class() IValueChangeListener {
+ public void handleValueChange(ValueChangeEvent event) {
+ fireValueChange(event.diff);
+ }
+ });
+ wrappedValue.addStaleListener(new class() IStaleListener {
+ public void handleStale(StaleEvent staleEvent) {
+ fireStale();
+ }
+ });
+ }
+
+ protected Object doGetValue() {
+ return wrappedValue.getValue();
+ }
+
+ public Object getValueType() {
+ return wrappedValue.getValueType();
+ }
+
+ public bool isStale() {
+ return wrappedValue.isStale();
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableList.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableList.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,393 @@
+/*******************************************************************************
+ * Copyright (c) 2008 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 218269)
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.ObservableTracker;
+import org.eclipse.core.databinding.observable.StaleEvent;
+import org.eclipse.core.databinding.observable.list.IListChangeListener;
+import org.eclipse.core.databinding.observable.list.IObservableList;
+import org.eclipse.core.databinding.observable.list.ListChangeEvent;
+import org.eclipse.core.databinding.observable.list.ListDiff;
+import org.eclipse.core.databinding.observable.list.ListDiffEntry;
+import org.eclipse.core.databinding.observable.list.ListDiffVisitor;
+import org.eclipse.core.databinding.observable.list.ObservableList;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * @since 3.3
+ *
+ */
+public class ValidatedObservableList : ObservableList {
+ private IObservableList target;
+ private IObservableValue validationStatus;
+
+ // Only true when out of sync with target due to validation status
+ private bool stale;
+
+ // True when validaton status changes from invalid to valid.
+ private bool computeNextDiff = false;
+
+ private bool updatingTarget = false;
+
+ private IListChangeListener targetChangeListener = new class() IListChangeListener {
+ public void handleListChange(ListChangeEvent event) {
+ if (updatingTarget)
+ return;
+ IStatus status = cast(IStatus) validationStatus.getValue();
+ if (isValid(status)) {
+ if (stale) {
+ // this.stale means we are out of sync with target,
+ // so reset wrapped list to exactly mirror target
+ stale = false;
+ updateWrappedList(new ArrayList(target));
+ } else {
+ ListDiff diff = event.diff;
+ if (computeNextDiff) {
+ diff = Diffs.computeListDiff(wrappedList, target);
+ computeNextDiff = false;
+ }
+ applyDiff(diff, wrappedList);
+ fireListChange(diff);
+ }
+ } else {
+ makeStale();
+ }
+ }
+ };
+
+ private static bool isValid(IStatus status) {
+ return status.isOK() || status.matches(IStatus.INFO | IStatus.WARNING);
+ }
+
+ private IStaleListener targetStaleListener = new class() IStaleListener {
+ public void handleStale(StaleEvent staleEvent) {
+ fireStale();
+ }
+ };
+
+ private IValueChangeListener validationStatusChangeListener = new class() IValueChangeListener {
+ public void handleValueChange(ValueChangeEvent event) {
+ IStatus oldStatus = cast(IStatus) event.diff.getOldValue();
+ IStatus newStatus = cast(IStatus) event.diff.getNewValue();
+ if (stale && !isValid(oldStatus) && isValid(newStatus)) {
+ // this.stale means we are out of sync with target,
+ // reset wrapped list to exactly mirror target
+ stale = false;
+ updateWrappedList(new ArrayList(target));
+
+ // If the validation status becomes valid because of a change in
+ // target observable
+ computeNextDiff = true;
+ }
+ }
+ };
+
+ /**
+ * @param target
+ * @param validationStatus
+ */
+ public this(final IObservableList target,
+ final IObservableValue validationStatus) {
+ super(target.getRealm(), new ArrayList(target), target.getElementType());
+ Assert.isNotNull(validationStatus,
+ "Validation status observable cannot be null"); //$NON-NLS-1$
+ Assert
+ .isTrue(target.getRealm().equals(validationStatus.getRealm()),
+ "Target and validation status observables must be on the same realm"); //$NON-NLS-1$
+ this.target = target;
+ this.validationStatus = validationStatus;
+ target.addListChangeListener(targetChangeListener);
+ target.addStaleListener(targetStaleListener);
+ validationStatus.addValueChangeListener(validationStatusChangeListener);
+ }
+
+ private void makeStale() {
+ if (!stale) {
+ stale = true;
+ fireStale();
+ }
+ }
+
+ private void updateTargetList(ListDiff diff) {
+ updatingTarget = true;
+ try {
+ if (stale) {
+ stale = false;
+ applyDiff(Diffs.computeListDiff(target, wrappedList), target);
+ } else {
+ applyDiff(diff, target);
+ }
+ } finally {
+ updatingTarget = false;
+ }
+ }
+
+ private void applyDiff(ListDiff diff, final List list) {
+ diff.accept(new class() ListDiffVisitor {
+ public void handleAdd(int index, Object element) {
+ list.add(index, element);
+ }
+
+ public void handleRemove(int index, Object element) {
+ list.remove(index);
+ }
+
+ public void handleReplace(int index, Object oldElement,
+ Object newElement) {
+ list.set(index, newElement);
+ }
+ });
+ }
+
+ public bool isStale() {
+ ObservableTracker.getterCalled(this);
+ return stale || target.isStale();
+ }
+
+ public void add(int index, Object element) {
+ checkRealm();
+ wrappedList.add(index, element);
+ ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(index,
+ true, element));
+ updateTargetList(diff);
+ fireListChange(diff);
+ }
+
+ public bool add(Object o) {
+ checkRealm();
+ add(wrappedList.size(), o);
+ return true;
+ }
+
+ public bool addAll(Collection c) {
+ checkRealm();
+ return addAll(wrappedList.size(), c);
+ }
+
+ public bool addAll(int index, Collection c) {
+ checkRealm();
+ Object[] elements = c.toArray();
+ ListDiffEntry[] entries = new ListDiffEntry[elements.length];
+ for (int i = 0; i < elements.length; i++) {
+ wrappedList.add(index + i, elements[i]);
+ entries[i] = Diffs
+ .createListDiffEntry(index + i, true, elements[i]);
+ }
+ ListDiff diff = Diffs.createListDiff(entries);
+ updateTargetList(diff);
+ fireListChange(diff);
+ return true;
+ }
+
+ public void clear() {
+ checkRealm();
+ if (isEmpty())
+ return;
+ ListDiff diff = Diffs.computeListDiff(wrappedList,
+ Collections.EMPTY_LIST);
+ wrappedList.clear();
+ updateTargetList(diff);
+ fireListChange(diff);
+ }
+
+ public Iterator iterator() {
+ getterCalled();
+ final ListIterator wrappedIterator = wrappedList.listIterator();
+ return new class() Iterator {
+ Object last = null;
+
+ public bool hasNext() {
+ return wrappedIterator.hasNext();
+ }
+
+ public Object next() {
+ return last = wrappedIterator.next();
+ }
+
+ public void remove() {
+ int index = wrappedIterator.previousIndex();
+ wrappedIterator.remove();
+ ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(
+ index, false, last));
+ updateTargetList(diff);
+ fireListChange(diff);
+ }
+ };
+ }
+
+ public ListIterator listIterator() {
+ return listIterator(0);
+ }
+
+ public ListIterator listIterator(int index) {
+ getterCalled();
+ final ListIterator wrappedIterator = wrappedList.listIterator(index);
+ return new class() ListIterator {
+ int lastIndex = -1;
+ Object last = null;
+
+ public void add(Object o) {
+ wrappedIterator.add(o);
+ lastIndex = previousIndex();
+ ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(
+ lastIndex, true, o));
+ updateTargetList(diff);
+ fireListChange(diff);
+ }
+
+ public bool hasNext() {
+ return wrappedIterator.hasNext();
+ }
+
+ public bool hasPrevious() {
+ return wrappedIterator.hasPrevious();
+ }
+
+ public Object next() {
+ last = wrappedIterator.next();
+ lastIndex = previousIndex();
+ return last;
+ }
+
+ public int nextIndex() {
+ return wrappedIterator.nextIndex();
+ }
+
+ public Object previous() {
+ last = wrappedIterator.previous();
+ lastIndex = nextIndex();
+ return last;
+ }
+
+ public int previousIndex() {
+ return wrappedIterator.previousIndex();
+ }
+
+ public void remove() {
+ wrappedIterator.remove();
+ ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(
+ lastIndex, false, last));
+ lastIndex = -1;
+ updateTargetList(diff);
+ fireListChange(diff);
+ }
+
+ public void set(Object o) {
+ wrappedIterator.set(o);
+ ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(
+ lastIndex, false, last), Diffs.createListDiffEntry(
+ lastIndex, true, o));
+ last = o;
+ updateTargetList(diff);
+ fireListChange(diff);
+ }
+ };
+ }
+
+ public Object move(int oldIndex, int newIndex) {
+ checkRealm();
+ int size = wrappedList.size();
+ if (oldIndex >= size)
+ throw new IndexOutOfBoundsException(
+ "oldIndex: " + oldIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$
+ if (newIndex >= size)
+ throw new IndexOutOfBoundsException(
+ "newIndex: " + newIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$
+ if (oldIndex is newIndex)
+ return wrappedList.get(oldIndex);
+ Object element = wrappedList.remove(oldIndex);
+ wrappedList.add(newIndex, element);
+ ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(
+ oldIndex, false, element), Diffs.createListDiffEntry(newIndex,
+ true, element));
+ updateTargetList(diff);
+ fireListChange(diff);
+ return element;
+ }
+
+ public Object remove(int index) {
+ checkRealm();
+ Object element = wrappedList.remove(index);
+ ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(index,
+ false, element));
+ updateTargetList(diff);
+ fireListChange(diff);
+ return element;
+ }
+
+ public bool remove(Object o) {
+ checkRealm();
+ int index = wrappedList.indexOf(o);
+ if (index is -1)
+ return false;
+ remove(index);
+ return true;
+ }
+
+ public bool removeAll(Collection c) {
+ checkRealm();
+ List list = new ArrayList(wrappedList);
+ bool changed = list.removeAll(c);
+ if (changed) {
+ ListDiff diff = Diffs.computeListDiff(wrappedList, list);
+ wrappedList = list;
+ updateTargetList(diff);
+ fireListChange(diff);
+ }
+ return changed;
+ }
+
+ public bool retainAll(Collection c) {
+ checkRealm();
+ List list = new ArrayList(wrappedList);
+ bool changed = list.retainAll(c);
+ if (changed) {
+ ListDiff diff = Diffs.computeListDiff(wrappedList, list);
+ wrappedList = list;
+ updateTargetList(diff);
+ fireListChange(diff);
+ }
+ return changed;
+ }
+
+ public Object set(int index, Object element) {
+ checkRealm();
+ Object oldElement = wrappedList.set(index, element);
+ ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(index,
+ false, oldElement), Diffs.createListDiffEntry(index, true,
+ element));
+ updateTargetList(diff);
+ fireListChange(diff);
+ return oldElement;
+ }
+
+ public synchronized void dispose() {
+ target.removeListChangeListener(targetChangeListener);
+ target.removeStaleListener(targetStaleListener);
+ validationStatus
+ .removeValueChangeListener(validationStatusChangeListener);
+ super.dispose();
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableMap.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableMap.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,228 @@
+/*******************************************************************************
+ * Copyright (c) 2008 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 218269)
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.StaleEvent;
+import org.eclipse.core.databinding.observable.map.IMapChangeListener;
+import org.eclipse.core.databinding.observable.map.IObservableMap;
+import org.eclipse.core.databinding.observable.map.MapChangeEvent;
+import org.eclipse.core.databinding.observable.map.MapDiff;
+import org.eclipse.core.databinding.observable.map.ObservableMap;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * @since 3.3
+ *
+ */
+public class ValidatedObservableMap : ObservableMap {
+ private IObservableMap target;
+ private IObservableValue validationStatus;
+
+ // Only true when out of sync with target due to validation status
+ private bool stale;
+
+ // True when validation status changes from invalid to valid.
+ private bool computeNextDiff = false;
+
+ private bool updatingTarget = false;
+
+ private IMapChangeListener targetChangeListener = new class() IMapChangeListener {
+ public void handleMapChange(MapChangeEvent event) {
+ if (updatingTarget)
+ return;
+ IStatus status = cast(IStatus) validationStatus.getValue();
+ if (isValid(status)) {
+ if (stale) {
+ // this.stale means we are out of sync with target,
+ // so reset wrapped list to exactly mirror target
+ stale = false;
+ updateWrappedMap(new HashMap(target));
+ } else {
+ MapDiff diff = event.diff;
+ if (computeNextDiff) {
+ diff = Diffs.computeMapDiff(wrappedMap, target);
+ computeNextDiff = false;
+ }
+ applyDiff(diff, wrappedMap);
+ fireMapChange(diff);
+ }
+ } else {
+ makeStale();
+ }
+ }
+ };
+
+ private IStaleListener targetStaleListener = new class() IStaleListener {
+ public void handleStale(StaleEvent staleEvent) {
+ fireStale();
+ }
+ };
+
+ private IValueChangeListener validationStatusChangeListener = new class() IValueChangeListener {
+ public void handleValueChange(ValueChangeEvent event) {
+ IStatus oldStatus = cast(IStatus) event.diff.getOldValue();
+ IStatus newStatus = cast(IStatus) event.diff.getNewValue();
+ if (stale && !isValid(oldStatus) && isValid(newStatus)) {
+ // this.stale means we are out of sync with target,
+ // reset wrapped map to exactly mirror target
+ stale = false;
+ updateWrappedMap(new HashMap(target));
+
+ // If the validation status becomes valid because of a change in
+ // target observable
+ computeNextDiff = true;
+ }
+ }
+ };
+
+ /**
+ * @param target
+ * @param validationStatus
+ */
+ public this(final IObservableMap target,
+ final IObservableValue validationStatus) {
+ super(target.getRealm(), new HashMap(target));
+ Assert.isNotNull(validationStatus,
+ "Validation status observable cannot be null"); //$NON-NLS-1$
+ Assert
+ .isTrue(target.getRealm().equals(validationStatus.getRealm()),
+ "Target and validation status observables must be on the same realm"); //$NON-NLS-1$
+ this.target = target;
+ this.validationStatus = validationStatus;
+ target.addMapChangeListener(targetChangeListener);
+ target.addStaleListener(targetStaleListener);
+ validationStatus.addValueChangeListener(validationStatusChangeListener);
+ }
+
+ private void updateWrappedMap(Map newMap) {
+ Map oldMap = wrappedMap;
+ MapDiff diff = Diffs.computeMapDiff(oldMap, newMap);
+ wrappedMap = newMap;
+ fireMapChange(diff);
+ }
+
+ private static bool isValid(IStatus status) {
+ return status.isOK() || status.matches(IStatus.INFO | IStatus.WARNING);
+ }
+
+ private void applyDiff(MapDiff diff, Map map) {
+ for (Iterator iterator = diff.getRemovedKeys().iterator(); iterator
+ .hasNext();)
+ map.remove(iterator.next());
+ for (Iterator iterator = diff.getChangedKeys().iterator(); iterator
+ .hasNext();) {
+ Object key = iterator.next();
+ map.put(key, diff.getNewValue(key));
+ }
+ for (Iterator iterator = diff.getAddedKeys().iterator(); iterator
+ .hasNext();) {
+ Object key = iterator.next();
+ map.put(key, diff.getNewValue(key));
+ }
+ }
+
+ private void makeStale() {
+ if (!stale) {
+ stale = true;
+ fireStale();
+ }
+ }
+
+ private void updateTargetMap(MapDiff diff) {
+ updatingTarget = true;
+ try {
+ if (stale) {
+ stale = false;
+ applyDiff(Diffs.computeMapDiff(target, wrappedMap), target);
+ } else {
+ applyDiff(diff, target);
+ }
+ } finally {
+ updatingTarget = false;
+ }
+ }
+
+ public bool isStale() {
+ getterCalled();
+ return stale || target.isStale();
+ }
+
+ public void clear() {
+ checkRealm();
+ if (isEmpty())
+ return;
+ MapDiff diff = Diffs.computeMapDiff(wrappedMap, Collections.EMPTY_MAP);
+ wrappedMap = new HashMap();
+ updateTargetMap(diff);
+ fireMapChange(diff);
+ }
+
+ public Object put(Object key, Object value) {
+ checkRealm();
+ MapDiff diff;
+ Object oldValue;
+ if (wrappedMap.containsKey(key)) {
+ oldValue = wrappedMap.put(key, value);
+ if (wrappedMap.containsKey(key)) { // Changed
+ diff = Diffs.createMapDiffSingleChange(key, oldValue, value);
+ } else { // Removed
+ diff = Diffs.createMapDiffSingleRemove(key, oldValue);
+ }
+ } else { // Added
+ oldValue = wrappedMap.put(key, value);
+ diff = Diffs.createMapDiffSingleAdd(key, value);
+ }
+ updateTargetMap(diff);
+ fireMapChange(diff);
+ return oldValue;
+ }
+
+ public void putAll(Map m) {
+ checkRealm();
+ Map map = new HashMap(wrappedMap);
+ map.putAll(m);
+ MapDiff diff = Diffs.computeMapDiff(wrappedMap, map);
+ wrappedMap = map;
+ updateTargetMap(diff);
+ fireMapChange(diff);
+ }
+
+ public Object remove(Object key) {
+ checkRealm();
+ if (!wrappedMap.containsKey(key))
+ return null;
+ Object oldValue = wrappedMap.remove(key);
+ MapDiff diff = Diffs.createMapDiffSingleRemove(key, oldValue);
+ updateTargetMap(diff);
+ fireMapChange(diff);
+ return oldValue;
+ }
+
+ public synchronized void dispose() {
+ target.removeMapChangeListener(targetChangeListener);
+ target.removeStaleListener(targetStaleListener);
+ validationStatus
+ .removeValueChangeListener(validationStatusChangeListener);
+ super.dispose();
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableSet.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableSet.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,270 @@
+/*******************************************************************************
+ * Copyright (c) 2008 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 218269)
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.StaleEvent;
+import org.eclipse.core.databinding.observable.set.IObservableSet;
+import org.eclipse.core.databinding.observable.set.ISetChangeListener;
+import org.eclipse.core.databinding.observable.set.ObservableSet;
+import org.eclipse.core.databinding.observable.set.SetChangeEvent;
+import org.eclipse.core.databinding.observable.set.SetDiff;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * @since 3.3
+ *
+ */
+public class ValidatedObservableSet : ObservableSet {
+ private IObservableSet target;
+ private IObservableValue validationStatus;
+
+ // Only true when out of sync with target due to validation status
+ private bool stale;
+
+ // True when validation status changes from invalid to valid.
+ private bool computeNextDiff = false;
+
+ private bool updatingTarget = false;
+
+ private ISetChangeListener targetChangeListener = new class() ISetChangeListener {
+ public void handleSetChange(SetChangeEvent event) {
+ if (updatingTarget)
+ return;
+ IStatus status = cast(IStatus) validationStatus.getValue();
+ if (isValid(status)) {
+ if (stale) {
+ // this.stale means we are out of sync with target,
+ // so reset wrapped list to exactly mirror target
+ stale = false;
+ updateWrappedSet(new HashSet(target));
+ } else {
+ SetDiff diff = event.diff;
+ if (computeNextDiff) {
+ diff = Diffs.computeSetDiff(wrappedSet, target);
+ computeNextDiff = false;
+ }
+ applyDiff(diff, wrappedSet);
+ fireSetChange(diff);
+ }
+ } else {
+ makeStale();
+ }
+ }
+ };
+
+ private IStaleListener targetStaleListener = new class() IStaleListener {
+ public void handleStale(StaleEvent staleEvent) {
+ fireStale();
+ }
+ };
+
+ private IValueChangeListener validationStatusChangeListener = new class() IValueChangeListener {
+ public void handleValueChange(ValueChangeEvent event) {
+ IStatus oldStatus = cast(IStatus) event.diff.getOldValue();
+ IStatus newStatus = cast(IStatus) event.diff.getNewValue();
+ if (stale && !isValid(oldStatus) && isValid(newStatus)) {
+ // this.stale means we are out of sync with target,
+ // reset wrapped set to exactly mirror target
+ stale = false;
+ updateWrappedSet(new HashSet(target));
+
+ // If the validation status becomes valid because of a change in
+ // target observable
+ computeNextDiff = true;
+ }
+ }
+ };
+
+ /**
+ * @param target
+ * @param validationStatus
+ */
+ public this(final IObservableSet target,
+ final IObservableValue validationStatus) {
+ super(target.getRealm(), new HashSet(target), target.getElementType());
+ Assert.isNotNull(validationStatus,
+ "Validation status observable cannot be null"); //$NON-NLS-1$
+ Assert
+ .isTrue(target.getRealm().equals(validationStatus.getRealm()),
+ "Target and validation status observables must be on the same realm"); //$NON-NLS-1$
+ this.target = target;
+ this.validationStatus = validationStatus;
+ target.addSetChangeListener(targetChangeListener);
+ target.addStaleListener(targetStaleListener);
+ validationStatus.addValueChangeListener(validationStatusChangeListener);
+ }
+
+ private void updateWrappedSet(Set newSet) {
+ Set oldSet = wrappedSet;
+ SetDiff diff = Diffs.computeSetDiff(oldSet, newSet);
+ wrappedSet = newSet;
+ fireSetChange(diff);
+ }
+
+ private static bool isValid(IStatus status) {
+ return status.isOK() || status.matches(IStatus.INFO | IStatus.WARNING);
+ }
+
+ private void applyDiff(SetDiff diff, Set set) {
+ for (Iterator iterator = diff.getRemovals().iterator(); iterator
+ .hasNext();) {
+ set.remove(iterator.next());
+ }
+ for (Iterator iterator = diff.getAdditions().iterator(); iterator
+ .hasNext();) {
+ set.add(iterator.next());
+ }
+ }
+
+ private void makeStale() {
+ if (!stale) {
+ stale = true;
+ fireStale();
+ }
+ }
+
+ private void updateTargetSet(SetDiff diff) {
+ updatingTarget = true;
+ try {
+ if (stale) {
+ stale = false;
+ applyDiff(Diffs.computeSetDiff(target, wrappedSet), target);
+ } else {
+ applyDiff(diff, target);
+ }
+ } finally {
+ updatingTarget = false;
+ }
+ }
+
+ public bool isStale() {
+ getterCalled();
+ return stale || target.isStale();
+ }
+
+ public bool add(Object o) {
+ getterCalled();
+ bool changed = wrappedSet.add(o);
+ if (changed) {
+ SetDiff diff = Diffs.createSetDiff(Collections.singleton(o),
+ Collections.EMPTY_SET);
+ updateTargetSet(diff);
+ fireSetChange(diff);
+ }
+ return changed;
+ }
+
+ public bool addAll(Collection c) {
+ getterCalled();
+ HashSet set = new HashSet(wrappedSet);
+ bool changed = set.addAll(c);
+ if (changed) {
+ SetDiff diff = Diffs.computeSetDiff(wrappedSet, set);
+ wrappedSet = set;
+ updateTargetSet(diff);
+ fireSetChange(diff);
+ }
+ return changed;
+ }
+
+ public void clear() {
+ getterCalled();
+ if (isEmpty())
+ return;
+ SetDiff diff = Diffs.createSetDiff(Collections.EMPTY_SET, wrappedSet);
+ wrappedSet = new HashSet();
+ updateTargetSet(diff);
+ fireSetChange(diff);
+ }
+
+ public Iterator iterator() {
+ getterCalled();
+ final Iterator wrappedIterator = wrappedSet.iterator();
+ return new class() Iterator {
+ Object last = null;
+
+ public bool hasNext() {
+ return wrappedIterator.hasNext();
+ }
+
+ public Object next() {
+ return last = wrappedIterator.next();
+ }
+
+ public void remove() {
+ wrappedIterator.remove();
+ SetDiff diff = Diffs.createSetDiff(Collections.EMPTY_SET,
+ Collections.singleton(last));
+ updateTargetSet(diff);
+ fireSetChange(diff);
+ }
+ };
+ }
+
+ public bool remove(Object o) {
+ getterCalled();
+ bool changed = wrappedSet.remove(o);
+ if (changed) {
+ SetDiff diff = Diffs.createSetDiff(Collections.EMPTY_SET,
+ Collections.singleton(o));
+ updateTargetSet(diff);
+ fireSetChange(diff);
+ }
+ return changed;
+ }
+
+ public bool removeAll(Collection c) {
+ getterCalled();
+ Set set = new HashSet(wrappedSet);
+ bool changed = set.removeAll(c);
+ if (changed) {
+ SetDiff diff = Diffs.computeSetDiff(wrappedSet, set);
+ wrappedSet = set;
+ updateTargetSet(diff);
+ fireSetChange(diff);
+ }
+ return changed;
+ }
+
+ public bool retainAll(Collection c) {
+ getterCalled();
+ Set set = new HashSet(wrappedSet);
+ bool changed = set.retainAll(c);
+ if (changed) {
+ SetDiff diff = Diffs.computeSetDiff(wrappedSet, set);
+ wrappedSet = set;
+ updateTargetSet(diff);
+ fireSetChange(diff);
+ }
+ return changed;
+ }
+
+ public synchronized void dispose() {
+ target.removeSetChangeListener(targetChangeListener);
+ target.removeStaleListener(targetStaleListener);
+ validationStatus
+ .removeValueChangeListener(validationStatusChangeListener);
+ super.dispose();
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableValue.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableValue.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,170 @@
+/*******************************************************************************
+ * Copyright (c) 2008 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 218269)
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.ObservableTracker;
+import org.eclipse.core.databinding.observable.StaleEvent;
+import org.eclipse.core.databinding.observable.value.AbstractObservableValue;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.core.databinding.observable.value.IVetoableValue;
+import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
+import org.eclipse.core.databinding.observable.value.ValueChangingEvent;
+import org.eclipse.core.internal.databinding.Util;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * An {@link IObservableValue} wrapper that stays in sync with the target
+ * observable as long as a given validation status is valid.
+ *
+ * - While status is valid, ValidatedObservableValue stays in sync with its
+ * target.
+ *
- When status becomes invalid, ValidatedObservableValue will retain the
+ * last valid value of its target.
+ *
- While status is invalid, changes in the target observable cause
+ * ValidatedObservableValue to fire a stale event, to indicate that changes are
+ * pending.
+ *
- When status becomes valid, pending value changes are performed (if any)
+ * and synchronization resumes.
+ *
+ *
+ * Note:
+ *
+ * - By default, a status is valid if its
+ * {@link IStatus#getSeverity() severity} is {@link IStatus#OK OK},
+ * {@link IStatus#INFO INFO}, or {@link IStatus#WARNING WARNING}
+ *
- Calls to {@link #setValuecast(Object)} on the validated observable changes
+ * the value regardless of the validation status.
+ *
- This class will not forward {@link ValueChangingEvent} events from a
+ * wrapped {@link IVetoableValue}.
+ *
+ *
+ * @since 1.2
+ */
+public class ValidatedObservableValue : AbstractObservableValue {
+ private IObservableValue target;
+ private IObservableValue validationStatus;
+
+ private Object cachedValue;
+ private bool stale;
+ private bool updatingTarget = false;
+
+ private IValueChangeListener targetChangeListener = new class() IValueChangeListener {
+ public void handleValueChange(ValueChangeEvent event) {
+ if (updatingTarget)
+ return;
+ IStatus status = cast(IStatus) validationStatus.getValue();
+ if (isValid(status))
+ internalSetValue(event.diff.getNewValue(), false);
+ else
+ makeStale();
+ }
+ };
+
+ private static bool isValid(IStatus status) {
+ return status.isOK() || status.matches(IStatus.INFO | IStatus.WARNING);
+ }
+
+ private IStaleListener targetStaleListener = new class() IStaleListener {
+ public void handleStale(StaleEvent staleEvent) {
+ fireStale();
+ }
+ };
+
+ private IValueChangeListener validationStatusChangeListener = new class() IValueChangeListener {
+ public void handleValueChange(ValueChangeEvent event) {
+ IStatus oldStatus = cast(IStatus) event.diff.getOldValue();
+ IStatus newStatus = cast(IStatus) event.diff.getNewValue();
+ if (stale && !isValid(oldStatus) && isValid(newStatus)) {
+ internalSetValue(target.getValue(), false);
+ }
+ }
+ };
+
+ /**
+ * Constructs an observable value
+ *
+ * @param target
+ * the observable value to be wrapped
+ * @param validationStatus
+ * an observable value of type {@link IStatus}.class which
+ * contains the current validation status
+ */
+ public this(IObservableValue target,
+ IObservableValue validationStatus) {
+ super(target.getRealm());
+ Assert.isNotNull(validationStatus,
+ "Validation status observable cannot be null"); //$NON-NLS-1$
+ Assert
+ .isTrue(target.getRealm().equals(validationStatus.getRealm()),
+ "Target and validation status observables must be on the same realm"); //$NON-NLS-1$
+ this.target = target;
+ this.validationStatus = validationStatus;
+ this.cachedValue = target.getValue();
+
+ target.addValueChangeListener(targetChangeListener);
+ target.addStaleListener(targetStaleListener);
+ validationStatus.addValueChangeListener(validationStatusChangeListener);
+ }
+
+ private void makeStale() {
+ if (!stale) {
+ stale = true;
+ fireStale();
+ }
+ }
+
+ public bool isStale() {
+ ObservableTracker.getterCalled(this);
+ return stale || target.isStale();
+ }
+
+ protected Object doGetValue() {
+ return cachedValue;
+ }
+
+ private void internalSetValue(Object value, bool updateTarget) {
+ Object oldValue = cachedValue;
+ cachedValue = value;
+ if (updateTarget) {
+ updatingTarget = true;
+ try {
+ target.setValue(value);
+ cachedValue = target.getValue();
+ } finally {
+ updatingTarget = false;
+ }
+ }
+ stale = false;
+ if (!Util.equals(oldValue, cachedValue))
+ fireValueChange(Diffs.createValueDiff(oldValue, cachedValue));
+ }
+
+ protected void doSetValue(Object value) {
+ internalSetValue(value, true);
+ }
+
+ public Object getValueType() {
+ return target.getValueType();
+ }
+
+ public synchronized void dispose() {
+ target.removeValueChangeListener(targetChangeListener);
+ target.removeStaleListener(targetStaleListener);
+ validationStatus
+ .removeValueChangeListener(validationStatusChangeListener);
+ super.dispose();
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableList.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableList.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,175 @@
+/*******************************************************************************
+ * Copyright (c) 2005-2008 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 - bug 147515
+ * Matthew Hall - bug 221351
+ *******************************************************************************/
+package org.eclipse.core.internal.databinding.observable.masterdetail;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.IObserving;
+import org.eclipse.core.databinding.observable.list.IListChangeListener;
+import org.eclipse.core.databinding.observable.list.IObservableList;
+import org.eclipse.core.databinding.observable.list.ListChangeEvent;
+import org.eclipse.core.databinding.observable.list.ObservableList;
+import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
+import org.eclipse.core.runtime.Assert;
+
+/**
+ * @since 3.2
+ *
+ */
+
+public class DetailObservableList : ObservableList , IObserving {
+
+ private bool updating = false;
+
+ private IListChangeListener innerChangeListener = new class() IListChangeListener {
+ public void handleListChange(ListChangeEvent event) {
+ if (!updating) {
+ fireListChange(event.diff);
+ }
+ }
+ };
+
+ private Object currentOuterValue;
+
+ private IObservableList innerObservableList;
+
+ private IObservableFactory factory;
+
+ private IObservableValue outerObservableValue;
+
+ private Object detailType;
+
+ /**
+ * @param factory
+ * @param outerObservableValue
+ * @param detailType
+ */
+ public this(IObservableFactory factory,
+ IObservableValue outerObservableValue, Object detailType) {
+ super(outerObservableValue.getRealm(), Collections.EMPTY_LIST, detailType);
+ this.factory = factory;
+ this.outerObservableValue = outerObservableValue;
+ this.detailType = detailType;
+ updateInnerObservableList(outerObservableValue);
+
+ outerObservableValue.addValueChangeListener(outerChangeListener);
+ }
+
+ IValueChangeListener outerChangeListener = new class() IValueChangeListener {
+ public void handleValueChange(ValueChangeEvent event) {
+ List oldList = new ArrayList(wrappedList);
+ updateInnerObservableList(outerObservableValue);
+ fireListChange(Diffs.computeListDiff(oldList, wrappedList));
+ }
+ };
+
+ private void updateInnerObservableList(IObservableValue outerObservableValue) {
+ if (innerObservableList !is null) {
+ innerObservableList.removeListChangeListener(innerChangeListener);
+ innerObservableList.dispose();
+ }
+ currentOuterValue = outerObservableValue.getValue();
+ if (currentOuterValue is null) {
+ innerObservableList = null;
+ wrappedList = Collections.EMPTY_LIST;
+ } else {
+ this.innerObservableList = cast(IObservableList) factory
+ .createObservable(currentOuterValue);
+ wrappedList = innerObservableList;
+
+ if (detailType !is null) {
+ Object innerValueType = innerObservableList.getElementType();
+ Assert.isTrue(getElementType().equals(innerValueType),
+ "Cannot change value type in a nested observable list"); //$NON-NLS-1$
+ }
+ innerObservableList.addListChangeListener(innerChangeListener);
+ }
+ }
+
+ public bool add(Object o) {
+ return wrappedList.add(o);
+ }
+
+ public void add(int index, Object element) {
+ wrappedList.add(index, element);
+ }
+
+ public bool remove(Object o) {
+ return wrappedList.remove(o);
+ }
+
+ public Object set(int index, Object element) {
+ return wrappedList.set(index, element);
+ }
+
+ public Object move(int oldIndex, int newIndex) {
+ if (innerObservableList !is null)
+ return innerObservableList.move(oldIndex, newIndex);
+ return super.move(oldIndex, newIndex);
+ }
+
+ public Object remove(int index) {
+ return wrappedList.remove(index);
+ }
+
+ public bool addAll(Collection c) {
+ return wrappedList.addAll(c);
+ }
+
+ public bool addAll(int index, Collection c) {
+ return wrappedList.addAll(index, c);
+ }
+
+ public bool removeAll(Collection c) {
+ return wrappedList.removeAll(c);
+ }
+
+ public bool retainAll(Collection c) {
+ return wrappedList.retainAll(c);
+ }
+
+ public void clear() {
+ wrappedList.clear();
+ }
+
+ public void dispose() {
+ super.dispose();
+
+ if (outerObservableValue !is null) {
+ outerObservableValue.removeValueChangeListener(outerChangeListener);
+ outerObservableValue.dispose();
+ }
+ if (innerObservableList !is null) {
+ innerObservableList.removeListChangeListener(innerChangeListener);
+ innerObservableList.dispose();
+ }
+ currentOuterValue = null;
+ factory = null;
+ innerObservableList = null;
+ innerChangeListener = null;
+ }
+
+ public Object getObserved() {
+ if ( null !is cast(IObserving)innerObservableList ) {
+ return (cast(IObserving) innerObservableList).getObserved();
+ }
+ return null;
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableMap.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableMap.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,126 @@
+/*******************************************************************************
+ * Copyright (c) 2008 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 221704)
+ * Matthew Hall - bug 223114
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable.masterdetail;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.map.IMapChangeListener;
+import org.eclipse.core.databinding.observable.map.IObservableMap;
+import org.eclipse.core.databinding.observable.map.MapChangeEvent;
+import org.eclipse.core.databinding.observable.map.ObservableMap;
+import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
+
+/**
+ * @since 1.1
+ *
+ */
+public class DetailObservableMap : ObservableMap {
+ private bool updating = false;
+
+ private IObservableValue master;
+ private IObservableFactory detailFactory;
+
+ private IObservableMap detailMap;
+
+ private IValueChangeListener masterChangeListener = new class() IValueChangeListener {
+ public void handleValueChange(ValueChangeEvent event) {
+ Map oldMap = new HashMap(wrappedMap);
+ updateDetailMap();
+ fireMapChange(Diffs.computeMapDiff(oldMap, wrappedMap));
+ }
+ };
+
+ private IMapChangeListener detailChangeListener = new class() IMapChangeListener {
+ public void handleMapChange(MapChangeEvent event) {
+ if (!updating) {
+ fireMapChange(event.diff);
+ }
+ }
+ };
+
+ /**
+ * Constructs a new DetailObservableMap
+ *
+ * @param detailFactory
+ * observable factory that creates IObservableMap instances given
+ * the current value of master observable value
+ * @param master
+ *
+ */
+ public this(IObservableFactory detailFactory,
+ IObservableValue master) {
+ super(master.getRealm(), Collections.EMPTY_MAP);
+ this.master = master;
+ this.detailFactory = detailFactory;
+
+ updateDetailMap();
+ master.addValueChangeListener(masterChangeListener);
+ }
+
+ private void updateDetailMap() {
+ Object masterValue = master.getValue();
+ if (detailMap !is null) {
+ detailMap.removeMapChangeListener(detailChangeListener);
+ detailMap.dispose();
+ }
+
+ if (masterValue is null) {
+ detailMap = null;
+ wrappedMap = Collections.EMPTY_MAP;
+ } else {
+ detailMap = cast(IObservableMap) detailFactory
+ .createObservable(masterValue);
+ wrappedMap = detailMap;
+ detailMap.addMapChangeListener(detailChangeListener);
+ }
+ }
+
+ public Object put(Object key, Object value) {
+ return detailMap.put(key, value);
+ }
+
+ public void putAll(Map map) {
+ detailMap.putAll(map);
+ }
+
+ public Object remove(Object key) {
+ return detailMap.remove(key);
+ }
+
+ public void clear() {
+ detailMap.clear();
+ }
+
+ public synchronized void dispose() {
+ if (master !is null) {
+ master.removeValueChangeListener(masterChangeListener);
+ master = null;
+ masterChangeListener = null;
+ }
+ detailFactory = null;
+ if (detailMap !is null) {
+ detailMap.removeMapChangeListener(detailChangeListener);
+ detailMap.dispose();
+ detailMap = null;
+ }
+ detailChangeListener = null;
+ super.dispose();
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableSet.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableSet.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,158 @@
+/*******************************************************************************
+ * Copyright (c) 2005-2008 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
+ * Matthew Hall - bug 221351
+ *******************************************************************************/
+package org.eclipse.core.internal.databinding.observable.masterdetail;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.IObserving;
+import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
+import org.eclipse.core.databinding.observable.set.IObservableSet;
+import org.eclipse.core.databinding.observable.set.ISetChangeListener;
+import org.eclipse.core.databinding.observable.set.ObservableSet;
+import org.eclipse.core.databinding.observable.set.SetChangeEvent;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
+import org.eclipse.core.runtime.Assert;
+
+/**
+ * @since 3.2
+ *
+ */
+public class DetailObservableSet : ObservableSet , IObserving {
+
+ private bool updating = false;
+
+ private ISetChangeListener innerChangeListener = new class() ISetChangeListener {
+ public void handleSetChange(SetChangeEvent event) {
+ if (!updating) {
+ fireSetChange(event.diff);
+ }
+ }
+ };
+
+ private Object currentOuterValue;
+
+ private IObservableSet innerObservableSet;
+
+ private IObservableValue outerObservableValue;
+
+ private IObservableFactory factory;
+
+ /**
+ * @param factory
+ * @param outerObservableValue
+ * @param detailType
+ */
+ public this(IObservableFactory factory,
+ IObservableValue outerObservableValue, Object detailType) {
+ super(outerObservableValue.getRealm(), Collections.EMPTY_SET,
+ detailType);
+ this.factory = factory;
+ this.outerObservableValue = outerObservableValue;
+ updateInnerObservableSet(outerObservableValue);
+
+ outerObservableValue.addValueChangeListener(outerChangeListener);
+ }
+
+ IValueChangeListener outerChangeListener = new class() IValueChangeListener {
+ public void handleValueChange(ValueChangeEvent event) {
+ Set oldSet = new HashSet(wrappedSet);
+ updateInnerObservableSet(outerObservableValue);
+ fireSetChange(Diffs.computeSetDiff(oldSet, wrappedSet));
+ }
+ };
+
+ private void updateInnerObservableSet(IObservableValue outerObservableValue) {
+ currentOuterValue = outerObservableValue.getValue();
+ if (innerObservableSet !is null) {
+ innerObservableSet.removeSetChangeListener(innerChangeListener);
+ innerObservableSet.dispose();
+ }
+ if (currentOuterValue is null) {
+ innerObservableSet = null;
+ wrappedSet = Collections.EMPTY_SET;
+ } else {
+ this.innerObservableSet = cast(IObservableSet) factory
+ .createObservable(currentOuterValue);
+ wrappedSet = innerObservableSet;
+
+ if (elementType !is null) {
+ Object innerValueType = innerObservableSet.getElementType();
+
+ Assert.isTrue(elementType.equals(innerValueType),
+ "Cannot change value type in a nested observable set"); //$NON-NLS-1$
+ }
+
+ innerObservableSet.addSetChangeListener(innerChangeListener);
+ }
+ }
+
+ public bool add(Object o) {
+ getterCalled();
+ return wrappedSet.add(o);
+ }
+
+ public bool remove(Object o) {
+ getterCalled();
+ return wrappedSet.remove(o);
+ }
+
+ public bool addAll(Collection c) {
+ getterCalled();
+ return wrappedSet.addAll(c);
+ }
+
+ public bool removeAll(Collection c) {
+ getterCalled();
+ return wrappedSet.removeAll(c);
+ }
+
+ public bool retainAll(Collection c) {
+ getterCalled();
+ return wrappedSet.retainAll(c);
+ }
+
+ public void clear() {
+ getterCalled();
+ wrappedSet.clear();
+ }
+
+ public void dispose() {
+ super.dispose();
+
+ if (outerObservableValue !is null) {
+ outerObservableValue.removeValueChangeListener(outerChangeListener);
+ outerObservableValue.dispose();
+ }
+ if (innerObservableSet !is null) {
+ innerObservableSet.removeSetChangeListener(innerChangeListener);
+ innerObservableSet.dispose();
+ }
+ currentOuterValue = null;
+ factory = null;
+ innerObservableSet = null;
+ innerChangeListener = null;
+ }
+
+ public Object getObserved() {
+ if ( null !is cast(IObserving)innerObservableSet ) {
+ return (cast(IObserving) innerObservableSet).getObserved();
+ }
+ return null;
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableValue.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableValue.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2007 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 - bug 164653
+ * Brad Reynolds - bug 147515
+ *******************************************************************************/
+package org.eclipse.core.internal.databinding.observable.masterdetail;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.IObserving;
+import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
+import org.eclipse.core.databinding.observable.value.AbstractObservableValue;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
+import org.eclipse.core.runtime.Assert;
+
+/**
+ * @since 1.0
+ *
+ */
+public class DetailObservableValue : AbstractObservableValue , IObserving {
+
+ private bool updating = false;
+
+ private IValueChangeListener innerChangeListener = new class() IValueChangeListener {
+ public void handleValueChange(ValueChangeEvent event) {
+ if (!updating) {
+ fireValueChange(event.diff);
+ }
+ }
+ };
+
+ private Object currentOuterValue;
+
+ private IObservableValue innerObservableValue;
+
+ private Object detailType;
+
+ private IObservableValue outerObservableValue;
+
+ private IObservableFactory factory;
+
+ /**
+ * @param outerObservableValue
+ * @param factory
+ * @param detailType
+ */
+ public this(IObservableValue outerObservableValue,
+ IObservableFactory factory, Object detailType) {
+ super(outerObservableValue.getRealm());
+ this.factory = factory;
+ this.detailType = detailType;
+ this.outerObservableValue = outerObservableValue;
+ updateInnerObservableValue(outerObservableValue);
+
+ outerObservableValue.addValueChangeListener(outerChangeListener);
+ }
+
+ IValueChangeListener outerChangeListener = new class() IValueChangeListener {
+ public void handleValueChange(ValueChangeEvent event) {
+ Object oldValue = doGetValue();
+ updateInnerObservableValue(outerObservableValue);
+ fireValueChange(Diffs.createValueDiff(oldValue, doGetValue()));
+ }
+ };
+
+ private void updateInnerObservableValue(
+ IObservableValue outerObservableValue) {
+ currentOuterValue = outerObservableValue.getValue();
+ if (innerObservableValue !is null) {
+ innerObservableValue.removeValueChangeListener(innerChangeListener);
+ innerObservableValue.dispose();
+ }
+ if (currentOuterValue is null) {
+ innerObservableValue = null;
+ } else {
+ this.innerObservableValue = cast(IObservableValue) factory
+ .createObservable(currentOuterValue);
+ Object innerValueType = innerObservableValue.getValueType();
+
+ if (detailType !is null) {
+ Assert
+ .isTrue(
+ detailType.equals(innerValueType),
+ "Cannot change value type in a nested observable value, from " + innerValueType + " to " + detailType); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ innerObservableValue.addValueChangeListener(innerChangeListener);
+ }
+ }
+
+ public void doSetValue(Object value) {
+ if (innerObservableValue !is null)
+ innerObservableValue.setValue(value);
+ }
+
+ public Object doGetValue() {
+ return innerObservableValue is null ? null : innerObservableValue
+ .getValue();
+ }
+
+ public Object getValueType() {
+ return detailType;
+ }
+
+ public void dispose() {
+ super.dispose();
+
+ if (outerObservableValue !is null) {
+ outerObservableValue.removeValueChangeListener(outerChangeListener);
+ outerObservableValue.dispose();
+ }
+ if (innerObservableValue !is null) {
+ innerObservableValue.removeValueChangeListener(innerChangeListener);
+ innerObservableValue.dispose();
+ }
+ currentOuterValue = null;
+ factory = null;
+ innerObservableValue = null;
+ innerChangeListener = null;
+ }
+
+ public Object getObserved() {
+ if ( null !is cast(IObserving)innerObservableValue ) {
+ return (cast(IObserving)innerObservableValue).getObserved();
+ }
+ return null;
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/AbstractObservableTree.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/AbstractObservableTree.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,151 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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 - bug 164134
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable.tree;
+
+import org.eclipse.core.databinding.observable.AbstractObservable;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.util.Policy;
+import org.eclipse.core.internal.databinding.BindingMessages;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.ListenerList;
+import org.eclipse.core.runtime.Status;
+
+/**
+ * @since 3.3
+ *
+ */
+public abstract class AbstractObservableTree : AbstractObservable
+ , IObservableTree {
+
+ private bool stale;
+
+ private ListenerList treeListeners = new ListenerListcast(ListenerList.IDENTITY);
+
+ /**
+ * @param realm
+ */
+ public this(Realm realm) {
+ super(realm);
+ }
+
+ public void addChild(TreePath parentPath, Object childElement) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void addTreeChangeListener(ITreeChangeListener listener) {
+ treeListeners.add(listener);
+ }
+
+ public int getChildCount(TreePath parentPath) {
+ return getChildren(parentPath).length;
+ }
+
+ public bool hasChildren(TreePath parentPath) {
+ return getChildCount(parentPath) > 0;
+ }
+
+ public void insertChild(TreePath parentPath, int index, Object childElement) {
+ throw new UnsupportedOperationException();
+ }
+
+ public bool isLazy() {
+ return false;
+ }
+
+ public bool isOrdered() {
+ return false;
+ }
+
+ public void removeChild(TreePath parentPath, Object childElement) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void removeChild(TreePath parentPath, int index) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void removeTreeChangeListener(ITreeChangeListener listener) {
+ treeListeners.remove(listener);
+ }
+
+ public void setChildCount(TreePath parentPath, int count) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void setChildren(TreePath parentPath, Object[] children) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void updateChildren(IChildrenUpdate update) {
+ TreePath parent = update.getParent();
+ Object[] children = getChildren(parent);
+ for (int i = 0; i < update.getLength(); i++) {
+ int targetIndex = update.getOffset() + i;
+ if (targetIndex < children.length) {
+ update.setChild(children[targetIndex], targetIndex);
+ } else {
+ update
+ .setStatus(new Status(
+ IStatus.WARNING,
+ Policy.JFACE_DATABINDING,
+ IStatus.OK,
+ BindingMessages
+ .getStringcast(BindingMessages.INDEX_OUT_OF_RANGE),
+ null));
+ }
+ }
+ update.done();
+ }
+
+ public void updateChildrenCount(IChildrenCountUpdate update) {
+ TreePath[] parents = update.getParents();
+ for (int i = 0; i < parents.length; i++) {
+ update.setChildCount(parents[i], getChildCount(parents[i]));
+ }
+ update.done();
+ }
+
+ public void updateHasChildren(IHasChildrenUpdate update) {
+ TreePath[] parents = update.getElements();
+ for (int i = 0; i < parents.length; i++) {
+ update.setHasChilren(parents[i], hasChildren(parents[i]));
+ }
+ update.done();
+ }
+
+ public bool isStale() {
+ return stale;
+ }
+
+ /**
+ * @param stale
+ */
+ public void setStale(bool stale) {
+ this.stale = stale;
+ if (stale) {
+ fireStale();
+ }
+ }
+
+ protected void fireTreeChange(TreeDiff diff) {
+ // fire general change event first
+ fireChange();
+
+ Object[] listeners = treeListeners.getListeners();
+ TreeChangeEvent event = new TreeChangeEvent(this, diff);
+ for (int i = 0; i < listeners.length; i++) {
+ (cast(ITreeChangeListener) listeners[i]).handleTreeChange(event);
+ }
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IChildrenCountUpdate.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IChildrenCountUpdate.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+package org.eclipse.core.internal.databinding.observable.tree;
+
+/**
+ * Request monitor used to collect the number of children for an element in a lazy
+ * observable tree.
+ *
+ * @since 3.3
+ */
+public interface IChildrenCountUpdate : IViewerUpdate {
+
+ /**
+ * Returns the parent elements that children counts have been requested for
+ * as tree paths. An empty path identifies the root element.
+ *
+ * @return parent elements as tree paths
+ */
+ public TreePath[] getParents();
+
+ /**
+ * Sets the number of children for the given parent.
+ *
+ * @param parentPath
+ * parent element or empty path for root element
+ * @param numChildren
+ * number of children
+ */
+ public void setChildCount(TreePath parentPath, int numChildren);
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IChildrenUpdate.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IChildrenUpdate.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+package org.eclipse.core.internal.databinding.observable.tree;
+
+/**
+ * Context sensitive children update request for a parent and subrange of its
+ * children.
+ *
+ * @since 3.3
+ */
+public interface IChildrenUpdate : IViewerUpdate {
+
+ /**
+ * Returns the parent element that children are being requested for
+ * as a tree path. An empty path identifies the root element.
+ *
+ * @return parent element as a tree path
+ */
+ public TreePath getParent();
+
+ /**
+ * Returns the offset at which children have been requested for. This is
+ * the index of the first child being requested.
+ *
+ * @return offset at which children have been requested for
+ */
+ public int getOffset();
+
+ /**
+ * Returns the number of children requested.
+ *
+ * @return number of children requested
+ */
+ public int getLength();
+
+ /**
+ * Sets the child for this request's parent at the given offset.
+ *
+ * @param child child
+ * @param index child offset
+ *
+ * TODO: what to do with null
+ */
+ public void setChild(Object child, int index);
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IHasChildrenUpdate.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IHasChildrenUpdate.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+package org.eclipse.core.internal.databinding.observable.tree;
+
+/**
+ * Context sensitive update request for whether elements have children.
+ *
+ * @since 3.3
+ */
+public interface IHasChildrenUpdate : IViewerUpdate {
+
+ /**
+ * The elements this request is for specified as tree paths. An empty path
+ * identifies the root element.
+ *
+ * @return elements as tree paths
+ */
+ public TreePath[] getElements();
+
+ /**
+ * Sets whether the given element has children.
+ *
+ * @param element
+ * tree path to element, or empty for root element
+ * @param hasChildren
+ * whether it has children
+ */
+ public void setHasChilren(TreePath element, bool hasChildren);
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IObservableTree.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IObservableTree.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,145 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable.tree;
+
+import org.eclipse.core.databinding.observable.IObservable;
+
+/**
+ *
+ * A tree whose changes can be tracked by tree change listeners. If the tree is
+ * ordered ({@link #isOrdered()}), the order of children for a given tree path
+ * matters, and tree change notifications will always specify indices. If the
+ * tree is unordered, the children of a tree path are an unordered set and
+ * indices in change notifications are not specified.
+ *
+ *
+ * This interface is not intended to be implemented by clients. Clients should
+ * instead subclass one of the framework classes that implement this interface.
+ * Note that direct implementers of this interface outside of the framework will
+ * be broken in future releases when methods are added to this interface.
+ *
+ *
+ * @since 1.1
+ */
+public interface IObservableTree : IObservable {
+
+ /**
+ * Element that can be returned from synchronous getters if this observable
+ * tree is lazy.
+ */
+ public final static Object UNKNOWN_ELEMENT = new Object();
+
+ /**
+ * @param listener
+ */
+ public void addTreeChangeListener(ITreeChangeListener listener);
+
+ /**
+ * @param listener
+ */
+ public void removeTreeChangeListener(ITreeChangeListener listener);
+
+ /**
+ * Returns whether the order of children for a given parent is important. If
+ * this tree is ordered, tree change notifications will always specify
+ * indices.
+ *
+ * @return true if the order of children for a given parent is important
+ */
+ public bool isOrdered();
+
+ /**
+ * Returns whether this tree is optimized to fetch subsets of children
+ * lazily and possibly asynchronously. Implies {@link #isOrdered()}.
+ *
+ * @return true if this tree
+ */
+ public bool isLazy();
+
+ /**
+ * @param parentPath
+ * @return the children at the given parent path
+ */
+ public Object[] getChildren(TreePath parentPath);
+
+ /**
+ * @param parentPath
+ * @param children
+ */
+ public void setChildren(TreePath parentPath, Object[] children);
+
+ /**
+ * @param parentPath
+ * @param childElement
+ */
+ public void addChild(TreePath parentPath, Object childElement);
+
+ /**
+ * @param parentPath
+ * @param childElement
+ */
+ public void removeChild(TreePath parentPath, Object childElement);
+
+ /**
+ * @param parentPath
+ * @param index
+ * @param childElement
+ */
+ public void insertChild(TreePath parentPath, int index, Object childElement);
+
+ /**
+ * @param parentPath
+ * @param index
+ */
+ public void removeChild(TreePath parentPath, int index);
+
+ /**
+ * @param parentPath
+ * @return true
if the element at the given path has children
+ */
+ public bool hasChildren(TreePath parentPath);
+
+ /**
+ * @param parentPath
+ * @return the number of children of the element at the given path
+ */
+ public int getChildCount(TreePath parentPath);
+
+ /**
+ * @param parentPath
+ * @param count
+ */
+ public void setChildCount(TreePath parentPath, int count);
+
+ /**
+ * Updates the number of children for the given parent elements in the
+ * specified request.
+ *
+ * @param update specifies counts to update and stores result
+ */
+ public void updateChildrenCount(IChildrenCountUpdate update);
+
+ /**
+ * Updates children as requested by the update.
+ *
+ * @param update specifies children to update and stores result
+ */
+ public void updateChildren(IChildrenUpdate update);
+
+ /**
+ * Updates whether elements have children.
+ *
+ * @param update specifies elements to update and stores result
+ */
+ public void updateHasChildren(IHasChildrenUpdate update);
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IOrderedTreeProvider.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IOrderedTreeProvider.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable.tree;
+
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.list.IObservableList;
+
+/**
+ * Objects that implement this interface are capable of describing a tree by
+ * returning the list of children of any given element in the tree.
+ *
+ * @since 3.3
+ */
+public interface IOrderedTreeProvider {
+ /**
+ * Returns the children of the given element, or null if the element is a
+ * leaf node. The caller of this method is expected to dispose the result
+ * list when it is no longer needed.
+ *
+ * @param element
+ * the tree path of the element to query
+ * @return the children of the given element, or null if the element is a
+ * leaf node
+ */
+ IObservableList createChildList(TreePath element);
+
+ /**
+ * @return the realm shared by all child lists
+ */
+ Realm getRealm();
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/ITreeChangeListener.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/ITreeChangeListener.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable.tree;
+
+/**
+ * @since 3.3
+ *
+ */
+public interface ITreeChangeListener {
+ /**
+ * @param event
+ */
+ void handleTreeChange(TreeChangeEvent event);
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IUnorderedTreeProvider.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IUnorderedTreeProvider.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable.tree;
+
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.set.IObservableSet;
+
+/**
+ * @since 1.0
+ *
+ */
+public interface IUnorderedTreeProvider {
+ /**
+ * @return the realm for the createChildSet method
+ */
+ public Realm getRealm();
+
+ /**
+ * Returns the children of the given element, or null if the element is a leaf node.
+ * The caller of this method is expected to dispose the result set when it is no
+ * longer needed.
+ *
+ * @param element element to query
+ * @return the children of the given element, or null if the element is a leaf node
+ */
+ IObservableSet createChildSet(Object element);
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IViewerUpdate.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IViewerUpdate.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+package org.eclipse.core.internal.databinding.observable.tree;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * A context sensitive viewer update request.
+ *
+ * @since 3.3
+ */
+public interface IViewerUpdate : IProgressMonitor {
+
+ /**
+ * Sets the status of this request, possibly null
.
+ * When a request fails, the status indicates why the request failed.
+ * A null
status is considered to be successful.
+ *
+ * @param status request status
+ */
+ public void setStatus(IStatus status);
+
+ /**
+ * Returns the status of this request, or null
.
+ *
+ * @return request status or null
+ */
+ public IStatus getStatus();
+
+ /**
+ * Returns the model element corresponding to the given tree path.
+ * Returns the root element for the empty path.
+ *
+ * @param path viewer tree path
+ * @return corresponding model element
+ */
+ public Object getElement(TreePath path);
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeChangeEvent.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeChangeEvent.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable.tree;
+
+import java.util.EventObject;
+
+/**
+ * @since 3.3
+ *
+ */
+public class TreeChangeEvent : EventObject {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -3198503763995528027L;
+ /**
+ *
+ */
+ public TreeDiff diff;
+
+ /**
+ * @param source
+ * @param diff
+ */
+ public this(IObservableTree source, TreeDiff diff) {
+ super(source);
+ this.diff = diff;
+ }
+
+ /**
+ * @return the observable tree from which this event originated
+ */
+ public IObservableTree getObservable() {
+ return cast(IObservableTree) getSource();
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeDiff.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeDiff.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable.tree;
+
+/**
+ * Describes the difference between two trees as a tree of tree diff nodes.
+ *
+ * @since 1.1
+ *
+ */
+public abstract class TreeDiff : TreeDiffNode {
+
+ /**
+ * Returns the tree path (possibly empty) of the parent, or
+ * null
if the underlying tree is not lazy and never contains
+ * duplicate elements.
+ *
+ * @return the tree path (possibly empty) of the unchanged parent, or
+ * null
+ */
+ public abstract TreePath getParentPath();
+
+ /**
+ * @param visitor
+ */
+ public void accept(TreeDiffVisitor visitor) {
+ doAccept(visitor, getParentPath());
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeDiffNode.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeDiffNode.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,91 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable.tree;
+
+/**
+ * @since 1.1
+ *
+ */
+public abstract class TreeDiffNode {
+
+ /**
+ *
+ */
+ public final static int NO_CHANGE = 0x00;
+
+ /**
+ *
+ */
+ public final static int ADDED = 0x01;
+
+ /**
+ *
+ */
+ public final static int REMOVED = 0x02;
+
+ /**
+ *
+ */
+ public final static int REPLACED = 0x03;
+
+ /**
+ *
+ */
+ public static final TreeDiffNode[] NO_CHILDREN = new TreeDiffNode[0];
+
+ /**
+ *
+ */
+ public static final int INDEX_UNKNOWN = -1;
+
+ /**
+ * @return the change type
+ */
+ public abstract int getChangeType();
+
+ /**
+ * @return the element that was removed, or the replaced element
+ */
+ public abstract Object getOldElement();
+
+ /**
+ * @return the element that was not changed, added, or the replacement
+ * element
+ */
+ public abstract Object getNewElement();
+
+ /**
+ * @return the index at which the element was added, removed, or replaced
+ */
+ public abstract int getIndex();
+
+ /**
+ * Returns the child tree diff objects that describe changes to children. If
+ * the change type is REMOVED, there will be no children.
+ *
+ * @return the nodes representing changes to children
+ */
+ public abstract TreeDiffNode[] getChildren();
+
+ protected void doAccept(TreeDiffVisitor visitor, TreePath parentPath) {
+ TreePath currentPath = parentPath.createChildPath(getNewElement());
+ bool recurse = visitor.visit(this, currentPath);
+ if (recurse) {
+ TreeDiffNode[] children = getChildren();
+ for (int i = 0; i < children.length; i++) {
+ TreeDiffNode child = children[i];
+ child.doAccept(visitor, currentPath);
+ }
+ }
+ }
+
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeDiffVisitor.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeDiffVisitor.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,33 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 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
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable.tree;
+
+/**
+ * @since 3.3
+ *
+ */
+public abstract class TreeDiffVisitor {
+
+ /**
+ * Visits the given tree diff.
+ *
+ * @param diff
+ * the diff to visit
+ * @param currentPath
+ * the current path (the diff's element is the last segment of
+ * the path)
+ *
+ * @return true
if the tree diff's children should be
+ * visited; false
if they should be skipped.
+ */
+ public abstract bool visit(TreeDiffNode diff, TreePath currentPath);
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreePath.d
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreePath.d Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,183 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2008 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
+ * Matthew Hall - bug 118516
+ *******************************************************************************/
+package org.eclipse.core.internal.databinding.observable.tree;
+
+import org.eclipse.core.runtime.Assert;
+
+/**
+ * A tree path denotes a model element in a tree viewer. Tree path objects have
+ * value semantics. A model element is represented by a path of elements in the
+ * tree from the root element to the leaf element.
+ *
+ * Clients may instantiate this class. Not intended to be subclassed.
+ *
+ *
+ * @since 3.2
+ */
+public final class TreePath {
+
+ /**
+ * Constant for representing an empty tree path.
+ */
+ public static final TreePath EMPTY = new TreePath(new Object[0]);
+
+ private Object[] segments;
+
+ private int hash;
+
+ /**
+ * Constructs a path identifying a leaf node in a tree.
+ *
+ * @param segments
+ * path of elements to a leaf node in a tree, starting with the
+ * root element
+ */
+ public this(Object[] segments) {
+ Assert.isNotNull(segments, "Segments array cannot be null"); //$NON-NLS-1$
+ this.segments = new Object[segments.length];
+ for (int i = 0; i < segments.length; i++) {
+ Assert.isNotNull(segments[i], "Segments array cannot contain null"); //$NON-NLS-1$
+ this.segments[i] = segments[i];
+ }
+ }
+
+ /**
+ * Returns the element at the specified index in this path.
+ *
+ * @param index
+ * index of element to return
+ * @return element at the specified index
+ */
+ public Object getSegment(int index) {
+ return segments[index];
+ }
+
+ /**
+ * Returns the number of elements in this path.
+ *
+ * @return the number of elements in this path
+ */
+ public int getSegmentCount() {
+ return segments.length;
+ }
+
+ /**
+ * Returns the first element in this path.
+ *
+ * @return the first element in this path
+ */
+ public Object getFirstSegment() {
+ if (segments.length is 0) {
+ return null;
+ }
+ return segments[0];
+ }
+
+ /**
+ * Returns the last element in this path.
+ *
+ * @return the last element in this path
+ */
+ public Object getLastSegment() {
+ if (segments.length is 0) {
+ return null;
+ }
+ return segments[segments.length - 1];
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public override equals_t opEquals(Object other) {
+ if (!( null !is cast(TreePath)other )) {
+ return false;
+ }
+ TreePath otherPath = cast(TreePath) other;
+ if (segments.length !is otherPath.segments.length) {
+ return false;
+ }
+ for (int i = 0; i < segments.length; i++) {
+ if (!segments[i].equals(otherPath.segments[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public override hash_t toHash() {
+ if (hash is 0) {
+ for (int i = 0; i < segments.length; i++) {
+ hash += segments[i].hashCode();
+ }
+ }
+ return hash;
+ }
+
+ /**
+ * Returns whether this path starts with the same segments as the given
+ * path, using the given comparer to compare segments.
+ *
+ * @param treePath
+ * path to compare to
+ * @return whether the given path is a prefix of this path, or the same as
+ * this path
+ */
+ public bool startsWith(TreePath treePath) {
+ int thisSegmentCount = getSegmentCount();
+ int otherSegmentCount = treePath.getSegmentCount();
+ if (otherSegmentCount is thisSegmentCount) {
+ return equals(treePath);
+ }
+ if (otherSegmentCount > thisSegmentCount) {
+ return false;
+ }
+ for (int i = 0; i < otherSegmentCount; i++) {
+ Object otherSegment = treePath.getSegment(i);
+ if (!otherSegment.equals(segments[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns a copy of this tree path with one segment removed from the end,
+ * or null
if this tree path has no segments.
+ * @return a tree path
+ */
+ public TreePath getParentPath() {
+ int segmentCount = getSegmentCount();
+ if (segmentCount <= 1) {
+ return null;
+ }
+ Object[] parentSegments = new Object[segmentCount - 1];
+ System.arraycopy(segments, 0, parentSegments, 0, segmentCount - 1);
+ return new TreePath(parentSegments);
+ }
+
+ /**
+ * Returns a copy of this tree path with the given segment added at the end.
+ * @param newSegment
+ * @return a tree path
+ */
+ public TreePath createChildPath(Object newSegment) {
+ int segmentCount = getSegmentCount();
+ Object[] childSegments = new Object[segmentCount + 1];
+ if(segmentCount>0) {
+ System.arraycopy(segments, 0, childSegments, 0, segmentCount);
+ }
+ childSegments[segmentCount] = newSegment;
+ return new TreePath(childSegments);
+ }
+}
diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/package.html
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/package.html Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ Package-level Javadoc
+
+
+To be written.
+
+Package Specification
+
+This package provides classes that can be used to ...
+
+
diff -r 1d37a7813832 -r 6208d4f6a277 rakefile
--- a/rakefile Sun Apr 19 17:37:36 2009 +0200
+++ b/rakefile Tue Apr 21 10:55:51 2009 +0200
@@ -78,6 +78,8 @@
LIBNAMES_CORE = [ "org.eclipse.core.runtime",
"org.eclipse.core.commands",
"org.eclipse.core.databinding",
+ "org.eclipse.core.databinding.beans",
+ "org.eclipse.core.databinding.observable",
"org.eclipse.core.jobs" ]
LIBNAMES_JFACE = [ "org.eclipse.jface" ]
@@ -323,6 +325,8 @@
buildTree( "org.eclipse.core.runtime", "src", "res" )
buildTree( "org.eclipse.core.commands", "src", "res" )
buildTree( "org.eclipse.core.databinding", "src", "res" )
+ buildTree( "org.eclipse.core.databinding.beans", "src", "res" )
+ buildTree( "org.eclipse.core.databinding.observable", "src", "res" )
buildTree( "org.eclipse.core.jobs", "src", "res" )
end