changeset 95:6208d4f6a277

Added trees for databinding.beans and observable
author Frank Benoit <benoit@tionex.de>
date Tue, 21 Apr 2009 10:55:51 +0200
parents 1d37a7813832
children b74ac5dfcc06
files org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/BeansObservables.d org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/IBeanObservable.d org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/PojoObservables.d org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/package.html org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/BeanObservableListDecorator.d org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/BeanObservableMapDecorator.d org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/BeanObservableSetDecorator.d org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/BeanObservableValueDecorator.d org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/IdentityWrapper.d org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/JavaBeanObservableList.d org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/JavaBeanObservableMap.d org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/JavaBeanObservableSet.d org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/JavaBeanObservableValue.d org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/JavaBeanPropertyObservableMap.d org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/ListenerSupport.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/AbstractObservable.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ChangeEvent.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ChangeManager.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ChangeSupport.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/Diffs.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IChangeListener.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObservable.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObservableCollection.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObservablesListener.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObserving.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IStaleListener.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ObservableEvent.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ObservableTracker.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/Observables.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/Realm.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/StaleEvent.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/AbstractObservableList.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ComputedList.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/IListChangeListener.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/IObservableList.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListChangeEvent.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListDiff.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListDiffEntry.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListDiffVisitor.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ObservableList.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/WritableList.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/package.html org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/AbstractObservableMap.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/BidirectionalMap.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/CompositeMap.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/ComputedObservableMap.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/IMapChangeListener.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/IObservableMap.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/MapChangeEvent.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/MapDiff.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/ObservableMap.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/WritableMap.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/package.html org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/IObservableFactory.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/package.html org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/package.html org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/AbstractObservableSet.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/IObservableSet.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/ISetChangeListener.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/ListToSetAdapter.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/MappedSet.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/ObservableSet.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/SetChangeEvent.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/SetDiff.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/UnionSet.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/WritableSet.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/package.html org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/AbstractObservableValue.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/AbstractVetoableValue.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ChangeVetoException.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ComputedValue.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IObservableValue.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IValueChangeListener.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IValueChangingListener.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IVetoableValue.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ValueChangeEvent.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ValueChangingEvent.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ValueDiff.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/WritableValue.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/package.html org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/util/ILogger.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/util/Policy.d org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/util/package.html org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ConstantObservableValue.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/EmptyObservableList.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/EmptyObservableSet.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/IStalenessConsumer.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/MapEntryObservableValue.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ProxyObservableList.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ProxyObservableSet.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/StalenessObservableValue.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/StalenessTracker.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableList.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableSet.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableValue.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableList.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableMap.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableSet.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableValue.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableList.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableMap.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableSet.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableValue.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/AbstractObservableTree.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IChildrenCountUpdate.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IChildrenUpdate.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IHasChildrenUpdate.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IObservableTree.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IOrderedTreeProvider.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/ITreeChangeListener.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IUnorderedTreeProvider.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IViewerUpdate.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeChangeEvent.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeDiff.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeDiffNode.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeDiffVisitor.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreePath.d org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/package.html rakefile
diffstat 120 files changed, 15254 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/BeansObservables.d	Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,554 @@
+/*******************************************************************************
+ * 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 164268, 171616
+ *     Brad Reynolds - bug 147515
+ *     Matthew Hall - bug 221704
+ *     Thomas Kratz - bug 213787
+ *******************************************************************************/
+package org.eclipse.core.databinding.beans;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+
+import org.eclipse.core.databinding.BindingException;
+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;
+import org.eclipse.core.runtime.Assert;
+
+/**
+ * A factory for creating observable objects of Java objects that conform to the
+ * <a href="http://java.sun.com/products/javabeans/docs/spec.html">JavaBean
+ * specification</a> for bound properties.
+ * 
+ * @since 1.1
+ * 
+ */
+final public class BeansObservables {
+
+    /**
+     * 
+     */
+    public static final bool DEBUG = true;
+
+    /**
+     * Returns an observable value in the default realm tracking the current
+     * value of the named property of the given bean.
+     * 
+     * @param bean
+     *            the object
+     * @param propertyName
+     *            the name of the property
+     * @return an observable value tracking the current value of the named
+     *         property of the given bean
+     */
+    public static IObservableValue observeValue(Object bean, String propertyName) {
+        return observeValue(Realm.getDefault(), bean, propertyName);
+    }
+
+    /**
+     * Returns an observable value in the given realm tracking the current value
+     * of the named property of the given bean.
+     * 
+     * @param realm
+     *            the realm
+     * @param bean
+     *            the object
+     * @param propertyName
+     *            the name of the property
+     * @return an observable value tracking the current value of the named
+     *         property of the given bean
+     */
+    public static IObservableValue observeValue(Realm realm, Object bean,
+            String propertyName) {
+        PropertyDescriptor descriptor = getPropertyDescriptor(bean.getClass(),
+                propertyName);
+        return new JavaBeanObservableValue(realm, bean, descriptor);
+    }
+
+    /**
+     * Returns an observable map in the default realm tracking the current
+     * values of the named property for the beans in the given set.
+     * 
+     * @param domain
+     *            the set of bean objects
+     * @param beanClass
+     *            the common base type of bean 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 beans in the given domain set
+     */
+    public static IObservableMap observeMap(IObservableSet domain,
+            Class beanClass, String propertyName) {
+        PropertyDescriptor descriptor = getPropertyDescriptor(beanClass,
+                propertyName);
+        return new JavaBeanObservableMap(domain, descriptor);
+    }
+
+    /**
+     * Returns an observable map in the given realm tracking the map-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 map tracking the map-typed named property of the
+     *         given bean object
+     * @since 1.1
+     */
+    public static IObservableMap observeMap(Realm realm, Object bean,
+            String propertyName) {
+        PropertyDescriptor descriptor = getPropertyDescriptor(bean.getClass(),
+                propertyName);
+        return new JavaBeanPropertyObservableMap(realm, bean, descriptor);
+    }
+
+    /*package*/ static PropertyDescriptor getPropertyDescriptor(Class beanClass,
+            String propertyName) {
+        BeanInfo beanInfo;
+        try {
+            beanInfo = Introspector.getBeanInfo(beanClass);
+        } catch (IntrospectionException e) {
+            // cannot introspect, give up
+            return null;
+        }
+        PropertyDescriptor[] propertyDescriptors = beanInfo
+                .getPropertyDescriptors();
+        for (int i = 0; i < propertyDescriptors.length; i++) {
+            PropertyDescriptor descriptor = propertyDescriptors[i];
+            if (descriptor.getName().equals(propertyName)) {
+                return descriptor;
+            }
+        }
+        throw new BindingException(
+                "Could not find property with name " + propertyName + " in class " + beanClass); //$NON-NLS-1$ //$NON-NLS-2$
+    }
+
+    /**
+     * Returns an array of observable maps in the default realm tracking the
+     * current values of the named propertys for the beans in the given set.
+     * 
+     * @param domain
+     *            the set of objects
+     * @param beanClass
+     *            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 beans in the given domain set
+     */
+    public static IObservableMap[] observeMaps(IObservableSet domain,
+            Class beanClass, String[] propertyNames) {
+        IObservableMap[] result = new IObservableMap[propertyNames.length];
+        for (int i = 0; i < propertyNames.length; i++) {
+            result[i] = observeMap(domain, beanClass, propertyNames[i]);
+        }
+        return result;
+    }
+
+    /**
+     * Returns an observable list in the given realm tracking the
+     * collection-typed named property of the given bean object. The returned
+     * list is mutable.
+     * 
+     * @param realm
+     *            the realm
+     * @param bean
+     *            the object
+     * @param propertyName
+     *            the name of the collection-typed property
+     * @return an observable list tracking the collection-typed named property
+     *         of the given bean object
+     * @see #observeList(Realm, Object, String, Class)
+     */
+    public static IObservableList observeList(Realm realm, Object bean,
+            String propertyName) {
+        return observeList(realm, bean, 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 <code>PropertyChangeEvents</code>. 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 <code>null</code> and
+     *            the property is an array the type will be inferred. If
+     *            <code>null</code> and the property type cannot be inferred
+     *            element type will be <code>null</code>.
+     * @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
+     * <code>MasterDetailObservables.detailValue(master, valueFactory(realm,
+     propertyName), propertyType)</code>
+     * 
+     * @param realm
+     * @param master
+     * @param propertyName
+     * @param propertyType
+     *            can be <code>null</code>
+     * @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
+     * <code>MasterDetailObservables.detailValue(master, valueFactory(realm,
+     * propertyName), propertyType)</code>.
+     * 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 <code>null</code>
+     * @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
+     * <code>MasterDetailObservables.detailList(master, listFactory(realm,
+     propertyName, propertyType), propertyType)</code>
+     * 
+     * @param realm
+     * @param master
+     * @param propertyName
+     * @param propertyType
+     *            can be <code>null</code>
+     * @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
+     * <code>MasterDetailObservables.detailSet(master, setFactory(realm,
+     propertyName), propertyType)</code>
+     * 
+     * @param realm
+     * @param master
+     * @param propertyName
+     * @param propertyType
+     *            can be <code>null</code>
+     * @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
+     * <code>MasterDetailObservables.detailMap(master, mapFactory(realm, propertyName))</code>
+     * 
+     * @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 <code>null</code>
+     * @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 <code>null</code>
+     * @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 <code>null</code>
+     * @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 <code>null</code>
+     */
+    /* package*/ static PropertyDescriptor getValueTypePropertyDescriptor(
+            IObservableValue observable, String propertyName) {
+        return (observable.getValueType() !is null) ? getPropertyDescriptor(
+                cast(Class) observable.getValueType(), propertyName) : null;
+    }
+}
--- /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.
+ * <p>
+ * This interface is not meant to be implemented by clients.
+ * </p>
+ * 
+ * @since 3.3
+ */
+public interface IBeanObservable : IObserving {
+    /**
+     * @return property descriptor of the property being observed,
+     *         <code>null</code> if the runtime time information was not
+     *         provided on construction of the observable
+     */
+    public PropertyDescriptor getPropertyDescriptor();
+}
--- /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 <code>PropertyChangeEvents</code>. 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 <code>null</code> and
+     *            the property is an array the type will be inferred. If
+     *            <code>null</code> and the property type cannot be inferred
+     *            element type will be <code>null</code>.
+     * @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 <code>null</code>
+     * @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 <code>null</code>
+     * @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
+     * <code>MasterDetailObservables.detailValue(master, valueFactory(realm,
+     propertyName), propertyType)</code>
+     * 
+     * @param realm
+     * @param master
+     * @param propertyName
+     * @param propertyType
+     *            can be <code>null</code>
+     * @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
+     * <code>MasterDetailObservables.detailList(master, listFactory(realm,
+     propertyName, propertyType), propertyType)</code>
+     * 
+     * @param realm
+     * @param master
+     * @param propertyName
+     * @param propertyType
+     *            can be <code>null</code>
+     * @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
+     * <code>MasterDetailObservables.detailSet(master, setFactory(realm,
+     propertyName), propertyType)</code>
+     * 
+     * @param realm
+     * @param master
+     * @param propertyName
+     * @param propertyType
+     *            can be <code>null</code>
+     * @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
+     * <code>MasterDetailObservables.detailMap(master, mapFactory(realm, propertyName))</code>
+     * 
+     * @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;
+    }
+}
--- /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 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+   <meta name="Author" content="IBM">
+   <meta name="GENERATOR" content="Mozilla/4.5 [en] (Win98; I) [Netscape]">
+   <title>Package-level Javadoc</title>
+</head>
+<body>
+Provides classes for observing JavaBeans(tm) objects.
+<h2>
+Package Specification</h2>
+<p>
+This package provides classes that can be used to observe objects that conform to the <a href="http://java.sun.com/products/javabeans/docs/spec.html">JavaBean specification</a> for bound properties.</p>
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/BeanObservableListDecorator.d	Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,229 @@
+/*******************************************************************************
+ * 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
+ *     Matthew Hall - bugs 208858, 245183
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.beans;
+
+import java.beans.PropertyDescriptor;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.eclipse.core.databinding.beans.IBeanObservable;
+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.AbstractObservableList;
+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.internal.databinding.Util;
+
+/**
+ * {@link IBeanObservable} decorator for an {@link IObservableList}.
+ * 
+ * @since 3.3
+ */
+public class BeanObservableListDecorator : AbstractObservableList
+        , IBeanObservable {
+    private IObservableList delegate;
+    private IStaleListener delegateStaleListener;
+    private IListChangeListener delegateListChangeListener;
+
+    private Object observed;
+    private PropertyDescriptor propertyDescriptor;
+
+    /**
+     * @param delegate
+     * @param observed
+     * @param propertyDescriptor
+     */
+    public this(IObservableList delegate,
+            Object observed, PropertyDescriptor propertyDescriptor) {
+        super(delegate.getRealm());
+        this.delegate = delegate;
+        this.observed = observed;
+        this.propertyDescriptor = propertyDescriptor;
+    }
+
+    public void add(int index, Object element) {
+        delegate.add(index, element);
+    }
+
+    public bool add(Object o) {
+        return delegate.add(o);
+    }
+
+    public bool addAll(Collection c) {
+        return delegate.addAll(c);
+    }
+
+    public bool addAll(int index, Collection c) {
+        return delegate.addAll(index, c);
+    }
+
+    public void clear() {
+        delegate.clear();
+    }
+
+    public void dispose() {
+        delegate.dispose();
+        super.dispose();
+    }
+
+    public override equals_t opEquals(Object o) {
+        getterCalled();
+        if (o is this)
+            return true;
+        if (o is null)
+            return true;
+        if (getClass() is o.getClass()) {
+            BeanObservableListDecorator other = cast(BeanObservableListDecorator) o;
+            return Util.equals(other.delegate, delegate);
+        }
+        return delegate.equals(o);
+    }
+
+    public Object get(int index) {
+        getterCalled();
+        return delegate.get(index);
+    }
+
+    public Object getElementType() {
+        return delegate.getElementType();
+    }
+
+    public override hash_t toHash() {
+        getterCalled();
+        return delegate.hashCode();
+    }
+
+    public int indexOf(Object o) {
+        getterCalled();
+        return delegate.indexOf(o);
+    }
+
+    public Iterator iterator() {
+        getterCalled();
+        return delegate.iterator();
+    }
+
+    public int lastIndexOf(Object o) {
+        getterCalled();
+        return delegate.lastIndexOf(o);
+    }
+
+    public ListIterator listIterator() {
+        getterCalled();
+        return delegate.listIterator();
+    }
+
+    public ListIterator listIterator(int index) {
+        getterCalled();
+        return delegate.listIterator(index);
+    }
+
+    public Object move(int oldIndex, int newIndex) {
+        return delegate.move(oldIndex, newIndex);
+    }
+
+    public Object remove(int index) {
+        return delegate.remove(index);
+    }
+
+    public bool remove(Object o) {
+        return delegate.remove(o);
+    }
+
+    public bool removeAll(Collection c) {
+        return delegate.removeAll(c);
+    }
+
+    public bool retainAll(Collection c) {
+        return delegate.retainAll(c);
+    }
+
+    public Object set(int index, Object element) {
+        return delegate.set(index, element);
+    }
+
+    protected int doGetSize() {
+        return delegate.size();
+    }
+
+    public List subList(int fromIndex, int toIndex) {
+        getterCalled();
+        return delegate.subList(fromIndex, toIndex);
+    }
+
+    public Object[] toArray() {
+        getterCalled();
+        return delegate.toArray();
+    }
+
+    public Object[] toArray(Object[] a) {
+        return delegate.toArray(a);
+    }
+
+    protected void firstListenerAdded() {
+        delegateStaleListener = new class() IStaleListener {
+            public void handleStale(StaleEvent staleEvent) {
+                fireStale();
+            }
+        };
+        delegate.addStaleListener(delegateStaleListener);
+
+        delegateListChangeListener = new class() IListChangeListener {
+            public void handleListChange(ListChangeEvent event) {
+                fireListChange(event.diff);
+            }
+        };
+        delegate.addListChangeListener(delegateListChangeListener);
+    }
+
+    protected void lastListenerRemoved() {
+        delegate.removeStaleListener(delegateStaleListener);
+        delegateStaleListener = null;
+
+        delegate.removeListChangeListener(delegateListChangeListener);
+        delegateListChangeListener = null;
+    }
+
+    private void getterCalled() {
+        ObservableTracker.getterCalled(this);
+    }
+
+    /**
+     * @return list being delegated to
+     */
+    public IObservableList getDelegate() {
+        return delegate;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.eclipse.core.databinding.beans.IBeanObservable#getObserved()
+     */
+    public Object getObserved() {
+        return observed;
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.eclipse.core.databinding.beans.IBeanObservable#getPropertyDescriptor()
+     */
+    public PropertyDescriptor getPropertyDescriptor() {
+        return propertyDescriptor;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/BeanObservableMapDecorator.d	Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,165 @@
+/*******************************************************************************
+ * 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 245183
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.beans;
+
+import java.beans.PropertyDescriptor;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.databinding.beans.IBeanObservable;
+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.map.IMapChangeListener;
+import org.eclipse.core.databinding.observable.map.IObservableMap;
+import org.eclipse.core.internal.databinding.Util;
+
+/**
+ * {@link IBeanObservable} decorator for an {@link IObservableMap}.
+ * 
+ * @since 3.3
+ */
+public class BeanObservableMapDecorator : IObservableMap, IBeanObservable {
+    private IObservableMap delegate;
+    private Object observed;
+    private PropertyDescriptor propertyDescriptor;
+
+    /**
+     * @param delegate 
+     * @param observed 
+     * @param propertyDescriptor
+     */
+    public this(IObservableMap delegate,
+            Object observed,
+            PropertyDescriptor propertyDescriptor) {
+        
+        this.delegate = delegate;
+        this.observed = observed;
+        this.propertyDescriptor = propertyDescriptor;
+    }
+
+    public Realm getRealm() {
+        return delegate.getRealm();
+    }
+
+    public bool isStale() {
+        return delegate.isStale();
+    }
+
+    public bool containsKey(Object key) {
+        return delegate.containsKey(key);
+    }
+
+    public bool containsValue(Object value) {
+        return delegate.containsValue(value);
+    }
+
+    public Set entrySet() {
+        return delegate.entrySet();
+    }
+
+    public Object get(Object key) {
+        return delegate.get(key);
+    }
+
+    public Set keySet() {
+        return delegate.keySet();
+    }
+
+    public Object put(Object key, Object value) {
+        return delegate.put(key, value);
+    }
+
+    public Object remove(Object key) {
+        return delegate.remove(key);
+    }
+
+    public Collection values() {
+        return delegate.values();
+    }
+
+    public void putAll(Map map) {
+        delegate.putAll(map);
+    }
+
+    public void clear() {
+        delegate.clear();
+    }
+
+    public bool isEmpty() {
+        return delegate.isEmpty();
+    }
+
+    public int size() {
+        return delegate.size();
+    }
+
+    public Object getObserved() {
+        return observed;
+    }
+
+    public PropertyDescriptor getPropertyDescriptor() {
+        return propertyDescriptor;
+    }
+
+    /**
+     * @return the wrapped map
+     */
+    public IObservableMap getDelegate() {
+        return delegate;
+    }   
+    public void dispose() {
+        delegate.dispose();
+    }
+
+    public void addChangeListener(IChangeListener listener) {
+        delegate.addChangeListener(listener);
+    }
+
+    public void removeChangeListener(IChangeListener listener) {
+        delegate.removeChangeListener(listener);
+    }
+
+    public void addMapChangeListener(IMapChangeListener listener) {
+        delegate.addMapChangeListener(listener);
+    }
+
+    public void removeMapChangeListener(IMapChangeListener listener) {
+        delegate.removeMapChangeListener(listener);
+    }
+
+    public void addStaleListener(IStaleListener listener) {
+        delegate.addStaleListener(listener);
+    }
+
+    public void removeStaleListener(IStaleListener listener) {
+        delegate.removeStaleListener(listener);
+    }
+
+    public override equals_t opEquals(Object obj) {
+        if (obj is this)
+            return true;
+        if (obj is null)
+            return false;
+        if (getClass() is obj.getClass()) {
+            BeanObservableMapDecorator other = cast(BeanObservableMapDecorator) obj;
+            return Util.equals(other.delegate, delegate);
+        }
+        return delegate.equals(obj);
+    }
+
+    public override hash_t toHash() {
+        return delegate.hashCode();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/BeanObservableSetDecorator.d	Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,179 @@
+/*******************************************************************************
+ * 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
+ *     Matthew Hall - bug 245183
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.beans;
+
+import java.beans.PropertyDescriptor;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.eclipse.core.databinding.beans.IBeanObservable;
+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.internal.databinding.Util;
+
+/**
+ * {@link IBeanObservable} decorator for an {@link IObservableSet}.
+ * 
+ * @since 3.3
+ */
+public class BeanObservableSetDecorator : IObservableSet, IBeanObservable {
+    private IObservableSet delegate;
+    private Object observed;
+    private PropertyDescriptor propertyDescriptor;
+
+    /**
+     * @param delegate 
+     * @param observed 
+     * @param propertyDescriptor
+     */
+    public this(IObservableSet delegate,
+            Object observed,
+            PropertyDescriptor propertyDescriptor) {
+        
+        this.delegate = delegate;
+        this.observed = observed;
+        this.propertyDescriptor = propertyDescriptor;
+    }
+
+    public bool add(Object o) {
+        return delegate.add(o);
+    }
+
+    public bool addAll(Collection c) {
+        return delegate.addAll(c);
+    }
+
+    public void addChangeListener(IChangeListener listener) {
+        delegate.addChangeListener(listener);
+    }
+
+    public void addSetChangeListener(ISetChangeListener listener) {
+        delegate.addSetChangeListener(listener);
+    }
+
+    public void addStaleListener(IStaleListener listener) {
+        delegate.addStaleListener(listener);
+    }
+
+    public void clear() {
+        delegate.clear();
+    }
+
+    public bool contains(Object o) {
+        return delegate.contains(o);
+    }
+
+    public bool containsAll(Collection c) {
+        return delegate.containsAll(c);
+    }
+
+    public void dispose() {
+        delegate.dispose();
+    }
+
+    public override equals_t opEquals(Object obj) {
+        if (obj is this)
+            return true;
+        if (obj is null)
+            return false;
+        if (getClass() is obj.getClass()) {
+            BeanObservableSetDecorator other = cast(BeanObservableSetDecorator) obj;
+            return Util.equals(other.delegate, delegate);
+        }
+        return delegate.equals(obj);
+    }
+
+    public Object getElementType() {
+        return delegate.getElementType();
+    }
+
+    public Realm getRealm() {
+        return delegate.getRealm();
+    }
+
+    public override hash_t toHash() {
+        return delegate.hashCode();
+    }
+
+    public bool isEmpty() {
+        return delegate.isEmpty();
+    }
+
+    public bool isStale() {
+        return delegate.isStale();
+    }
+
+    public Iterator iterator() {
+        return delegate.iterator();
+    }
+
+    public bool remove(Object o) {
+        return delegate.remove(o);
+    }
+
+    public bool removeAll(Collection c) {
+        return delegate.removeAll(c);
+    }
+
+    public void removeChangeListener(IChangeListener listener) {
+        delegate.removeChangeListener(listener);
+    }
+
+    public void removeSetChangeListener(ISetChangeListener listener) {
+        delegate.removeSetChangeListener(listener);
+    }
+
+    public void removeStaleListener(IStaleListener listener) {
+        delegate.removeStaleListener(listener);
+    }
+
+    public bool retainAll(Collection c) {
+        return delegate.retainAll(c);
+    }
+
+    public int size() {
+        return delegate.size();
+    }
+
+    public Object[] toArray() {
+        return delegate.toArray();
+    }
+
+    public Object[] toArray(Object[] a) {
+        return delegate.toArray(a);
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.core.databinding.beans.IBeanObservable#getObserved()
+     */
+    public Object getObserved() {
+        return observed;
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.core.databinding.beans.IBeanObservable#getPropertyDescriptor()
+     */
+    public PropertyDescriptor getPropertyDescriptor() {
+        return propertyDescriptor;
+    }
+
+    /**
+     * @return the wrapped set
+     */
+    public IObservableSet getDelegate() {
+        return delegate;
+    }   
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/BeanObservableValueDecorator.d	Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,136 @@
+/*******************************************************************************
+ * 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
+ *     Matthew Hall - bug 245183
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.beans;
+
+import java.beans.PropertyDescriptor;
+
+import org.eclipse.core.databinding.beans.IBeanObservable;
+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.value.IObservableValue;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.core.internal.databinding.Util;
+
+/**
+ * {@link IBeanObservable} decorator for an {@link IObservableValue}.
+ * 
+ * @since 3.3
+ */
+public class BeanObservableValueDecorator : IObservableValue,
+        IBeanObservable {
+    private final IObservableValue delegate;
+    private final PropertyDescriptor descriptor;
+    private final IObservableValue observed;
+
+    /**
+     * @param delegate
+     * @param observed 
+     * @param descriptor
+     */
+    public this(IObservableValue delegate, IObservableValue observed,
+            PropertyDescriptor descriptor) {
+        this.delegate = delegate;
+        this.observed = observed;
+        this.descriptor = descriptor;
+    }
+
+    public void addChangeListener(IChangeListener listener) {
+        delegate.addChangeListener(listener);
+    }
+
+    public void addStaleListener(IStaleListener listener) {
+        delegate.addStaleListener(listener);
+    }
+
+    public void addValueChangeListener(IValueChangeListener listener) {
+        delegate.addValueChangeListener(listener);
+    }
+
+    public void dispose() {
+        delegate.dispose();
+    }
+    
+    public override equals_t opEquals(Object obj) {
+        if (obj is this)
+            return true;
+        if (obj is null)
+            return false;
+        if (getClass() is obj.getClass()) {
+            BeanObservableValueDecorator other = cast(BeanObservableValueDecorator) obj;
+            return Util.equals(other.delegate, delegate);
+        }
+        return delegate.equals(obj);
+    }
+
+    public Realm getRealm() {
+        return delegate.getRealm();
+    }
+
+    public Object getValue() {
+        return delegate.getValue();
+    }
+
+    public Object getValueType() {
+        return delegate.getValueType();
+    }
+    
+    public override hash_t toHash() {
+        return delegate.hashCode();
+    }
+
+    public bool isStale() {
+        return delegate.isStale();
+    }
+
+    public void removeChangeListener(IChangeListener listener) {
+        delegate.removeChangeListener(listener);
+    }
+
+    public void removeStaleListener(IStaleListener listener) {
+        delegate.removeStaleListener(listener);
+    }
+
+    public void removeValueChangeListener(IValueChangeListener listener) {
+        delegate.removeValueChangeListener(listener);
+    }
+
+    public void setValue(Object value) {
+        delegate.setValue(value);
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.eclipse.core.databinding.beans.IBeanObservable#getObserved()
+     */
+    public Object getObserved() {
+        return observed.getValue();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see org.eclipse.core.databinding.beans.IBeanObservable#getPropertyDescriptor()
+     */
+    public PropertyDescriptor getPropertyDescriptor() {
+        return descriptor;
+    }
+    
+    /**
+     * @return observable value delegate
+     */
+    public IObservableValue getDelegate() {
+        return delegate;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/IdentityWrapper.d	Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * 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
+ *     Daniel Kruegler - bug 137435
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.beans;
+
+/**
+ * Used for wrapping objects that define their own implementations of equals()
+ * and hashCode() when putting them in sets or hashmaps to ensure identity
+ * comparison.
+ * 
+ * @since 1.0
+ * 
+ */
+public class IdentityWrapper {
+    final Object o;
+
+    /**
+     * @param o
+     */
+    public this(Object o) {
+        this.o = o;
+    }
+    
+    /**
+     * @return the unwrapped object
+     */
+    public Object unwrap() {
+        return o;
+    }
+
+    public override equals_t opEquals(Object obj) {
+        if (obj is null || obj.getClass() !is IdentityWrapper.class) {
+            return false;
+        }
+        return o is (cast(IdentityWrapper) obj).o;
+    }
+
+    public override hash_t toHash() {
+        return System.identityHashCode(o);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/JavaBeanObservableList.d	Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,399 @@
+/*******************************************************************************
+ * 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 171616
+ *     Matthew Hall - bugs 208858, 221351, 213145, 223164, 244098
+ *     Mike Evans - bug 217558
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.beans;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.core.databinding.BindingException;
+import org.eclipse.core.databinding.beans.IBeanObservable;
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.list.ListDiffEntry;
+import org.eclipse.core.databinding.observable.list.ObservableList;
+
+/**
+ * @since 1.0
+ * 
+ */
+public class JavaBeanObservableList : ObservableList ,
+        IBeanObservable {
+
+    private final Object object;
+
+    private bool updating = false;
+
+    private PropertyDescriptor descriptor;
+
+    private ListenerSupport listenerSupport;
+
+    /**
+     * @param realm
+     * @param object
+     * @param descriptor
+     * @param elementType
+     */
+    public this(Realm realm, Object object,
+            PropertyDescriptor descriptor, Class elementType) {
+        this(realm, object, descriptor, elementType, true);
+    }
+
+    /**
+     * @param realm
+     * @param object
+     * @param descriptor
+     * @param elementType
+     * @param attachListeners
+     */
+    public this(Realm realm, Object object,
+            PropertyDescriptor descriptor, Class elementType,
+            bool attachListeners) {
+
+        super(realm, new ArrayList(), elementType);
+        this.object = object;
+        this.descriptor = descriptor;
+
+        if (attachListeners) {
+            PropertyChangeListener listener = new class() PropertyChangeListener {
+                public void propertyChange(java.beans.PropertyChangeEvent event) {
+                    if (!updating) {
+                        getRealm().exec(new class() Runnable {
+                            public void run() {
+                                updateWrappedList(new ArrayList(Arrays
+                                        .asList(getValues())));
+                            }
+                        });
+                    }
+                }
+            };
+            this.listenerSupport = new ListenerSupport(listener,
+                    descriptor.getName());
+            listenerSupport.hookListener(this.object);
+        }
+
+        // initialize list without firing events
+        wrappedList.addAll(Arrays.asList(getValues()));
+    }
+
+    public void dispose() {
+        if (listenerSupport !is null) {
+            listenerSupport.dispose();
+            listenerSupport = null;
+        }
+        super.dispose();
+    }
+
+    private Object primGetValues() {
+        Exception ex = null;
+        try {
+            Method readMethod = descriptor.getReadMethod();
+            if (!readMethod.isAccessible()) {
+                readMethod.setAccessible(true);
+            }
+            return readMethod.invoke(object, new Object[0]);
+        } catch (IllegalArgumentException e) {
+            ex = e;
+        } catch (IllegalAccessException e) {
+            ex = e;
+        } catch (InvocationTargetException e) {
+            ex = e;
+        }
+        throw new BindingException("Could not read collection values", ex); //$NON-NLS-1$
+    }
+
+    private Object[] getValues() {
+        Object[] values = null;
+
+        Object result = primGetValues();
+        if (descriptor.getPropertyType().isArray())
+            values = cast(Object[]) result;
+        else {
+            // TODO add jUnit for POJO (var. SettableValue) collections
+            Collection list = cast(Collection) result;
+            if (list !is null) {
+                values = list.toArray();
+            }
+        }
+        if (values is null)
+            values = new Object[0];
+        return values;
+    }
+
+    public Object getObserved() {
+        return object;
+    }
+
+    public PropertyDescriptor getPropertyDescriptor() {
+        return descriptor;
+    }
+
+    private void setValues() {
+        if (descriptor.getPropertyType().isArray()) {
+            Class componentType = descriptor.getPropertyType()
+                    .getComponentType();
+            Object[] newArray = cast(Object[]) Array.newInstance(componentType,
+                    wrappedList.size());
+            wrappedList.toArray(newArray);
+            primSetValues(newArray);
+        } else {
+            // assume that it is a java.util.List
+            primSetValues(new ArrayList(wrappedList));
+        }
+    }
+
+    private void primSetValues(Object newValue) {
+        Exception ex = null;
+        try {
+            Method writeMethod = descriptor.getWriteMethod();
+            if (!writeMethod.isAccessible()) {
+                writeMethod.setAccessible(true);
+            }
+            writeMethod.invoke(object, new Object[] { newValue });
+            return;
+        } catch (IllegalArgumentException e) {
+            ex = e;
+        } catch (IllegalAccessException e) {
+            ex = e;
+        } catch (InvocationTargetException e) {
+            ex = e;
+        }
+        throw new BindingException("Could not write collection values", ex); //$NON-NLS-1$
+    }
+
+    public Object set(int index, Object element) {
+        getterCalled();
+        updating = true;
+        try {
+            Object oldElement = wrappedList.set(index, element);
+            setValues();
+            fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(
+                    index, false, oldElement), Diffs.createListDiffEntry(index,
+                    true, element)));
+            return oldElement;
+        } finally {
+            updating = false;
+        }
+    }
+
+    public Object move(int oldIndex, int newIndex) {
+        getterCalled();
+        updating = true;
+        try {
+            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);
+            setValues();
+            fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(
+                    oldIndex, false, element), Diffs.createListDiffEntry(
+                    newIndex, true, element)));
+            return element;
+        } finally {
+            updating = false;
+        }
+    }
+
+    public Object remove(int index) {
+        getterCalled();
+        updating = true;
+        try {
+            Object oldElement = wrappedList.remove(index);
+            setValues();
+            fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(
+                    index, false, oldElement)));
+            return oldElement;
+        } finally {
+            updating = false;
+        }
+    }
+
+    public bool add(Object element) {
+        updating = true;
+        try {
+            int index = wrappedList.size();
+            bool result = wrappedList.add(element);
+            setValues();
+            fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(
+                    index, true, element)));
+            return result;
+        } finally {
+            updating = false;
+        }
+    }
+
+    public void add(int index, Object element) {
+        updating = true;
+        try {
+            wrappedList.add(index, element);
+            setValues();
+            fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(
+                    index, true, element)));
+        } finally {
+            updating = false;
+        }
+    }
+
+    public bool addAll(Collection c) {
+        if (c.isEmpty()) {
+            return false;
+        }
+        updating = true;
+        try {
+            int index = wrappedList.size();
+            bool result = wrappedList.addAll(c);
+            setValues();
+            ListDiffEntry[] entries = new ListDiffEntry[c.size()];
+            int i = 0;
+            for (Iterator it = c.iterator(); it.hasNext();) {
+                Object o = it.next();
+                entries[i++] = Diffs.createListDiffEntry(index++, true, o);
+            }
+            fireListChange(Diffs.createListDiff(entries));
+            return result;
+        } finally {
+            updating = false;
+        }
+    }
+
+    public bool addAll(int index, Collection c) {
+        if (c.isEmpty()) {
+            return false;
+        }
+        updating = true;
+        try {
+            bool result = wrappedList.addAll(index, c);
+            setValues();
+            ListDiffEntry[] entries = new ListDiffEntry[c.size()];
+            int i = 0;
+            for (Iterator it = c.iterator(); it.hasNext();) {
+                Object o = it.next();
+                entries[i++] = Diffs.createListDiffEntry(index++, true, o);
+            }
+            fireListChange(Diffs.createListDiff(entries));
+            return result;
+        } finally {
+            updating = false;
+        }
+    }
+
+    public bool remove(Object o) {
+        getterCalled();
+        int index = wrappedList.indexOf(o);
+        if (index is -1) {
+            return false;
+        }
+        updating = true;
+        try {
+            Object oldElement = wrappedList.remove(index);
+            setValues();
+            fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(
+                    index, false, oldElement)));
+            return true;
+        } finally {
+            updating = false;
+        }
+    }
+
+    public bool removeAll(Collection c) {
+        getterCalled();
+        bool changed = false;
+        updating = true;
+        try {
+            List diffEntries = new ArrayList();
+            for (Iterator it = c.iterator(); it.hasNext();) {
+                Object o = it.next();
+                int index = wrappedList.indexOf(o);
+                if (index !is -1) {
+                    changed = true;
+                    Object oldElement = wrappedList.remove(index);
+                    diffEntries.add(Diffs.createListDiffEntry(index, false,
+                            oldElement));
+                }
+            }
+            if (changed) {
+                setValues();
+                fireListChange(Diffs
+                        .createListDiff(cast(ListDiffEntry[]) diffEntries
+                                .toArray(new ListDiffEntry[diffEntries.size()])));
+            }
+            return changed;
+        } finally {
+            updating = false;
+        }
+    }
+
+    public bool retainAll(Collection c) {
+        getterCalled();
+        bool changed = false;
+        updating = true;
+        try {
+            List diffEntries = new ArrayList();
+            int index = 0;
+            for (Iterator it = wrappedList.iterator(); it.hasNext();) {
+                Object o = it.next();
+                bool retain = c.contains(o);
+                if (retain) {
+                    index++;
+                } else {
+                    changed = true;
+                    it.remove();
+                    diffEntries.add(Diffs.createListDiffEntry(index, false, o));
+                }
+            }
+            if (changed) {
+                setValues();
+                fireListChange(Diffs
+                        .createListDiff(cast(ListDiffEntry[]) diffEntries
+                                .toArray(new ListDiffEntry[diffEntries.size()])));
+            }
+            return changed;
+        } finally {
+            updating = false;
+        }
+    }
+
+    public void clear() {
+        updating = true;
+        try {
+            List diffEntries = new ArrayList();
+            for (Iterator it = wrappedList.iterator(); it.hasNext();) {
+                Object o = it.next();
+                diffEntries.add(Diffs.createListDiffEntry(0, false, o));
+            }
+            wrappedList.clear();
+            setValues();
+            fireListChange(Diffs.createListDiff(cast(ListDiffEntry[]) diffEntries
+                    .toArray(new ListDiffEntry[diffEntries.size()])));
+        } finally {
+            updating = false;
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/JavaBeanObservableMap.d	Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,142 @@
+/*******************************************************************************
+ * 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 171616
+ *     Matthew hall - bug 223164
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.beans;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+
+import org.eclipse.core.databinding.beans.IBeanObservable;
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.map.ComputedObservableMap;
+import org.eclipse.core.databinding.observable.set.IObservableSet;
+import org.eclipse.core.databinding.util.Policy;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+/**
+ * @since 1.0
+ * 
+ */
+public class JavaBeanObservableMap : ComputedObservableMap ,
+        IBeanObservable {
+
+    private PropertyDescriptor propertyDescriptor;
+    
+    private PropertyChangeListener elementListener = new class() PropertyChangeListener {
+        public void propertyChange(final java.beans.PropertyChangeEvent event) {
+            if (!updating) {
+                getRealm().exec(new class() Runnable {
+                    public void run() {
+                        fireMapChange(Diffs.createMapDiffSingleChange(
+                                event.getSource(), event.getOldValue(), event
+                                .getNewValue()));
+                    }
+                });
+            }
+        }
+    };
+
+    private ListenerSupport listenerSupport;
+
+    private bool updating = false;
+
+    private bool attachListeners;
+
+    /**
+     * @param domain
+     * @param propertyDescriptor
+     */
+    public this(IObservableSet domain,
+            PropertyDescriptor propertyDescriptor) {
+        this(domain, propertyDescriptor, true);
+    }
+
+    /**
+     * @param domain
+     * @param propertyDescriptor
+     * @param attachListeners
+     */
+    public this(IObservableSet domain,
+            PropertyDescriptor propertyDescriptor, bool attachListeners) {
+        super(domain);
+
+        this.propertyDescriptor = propertyDescriptor;
+        this.attachListeners = attachListeners;
+        if (attachListeners) {
+            this.listenerSupport = new ListenerSupport(elementListener,
+                    propertyDescriptor.getName());
+        }
+        init();
+    }
+
+    protected void hookListener(Object domainElement) {
+        if (attachListeners && domainElement !is null) {
+            listenerSupport.hookListener(domainElement);
+        }
+    }
+
+    protected void unhookListener(Object domainElement) {
+        if (attachListeners && domainElement !is null) {
+            listenerSupport.unhookListener(domainElement);
+        }
+    }
+
+    protected Object doGet(Object key) {
+        if (key is null) {
+            return null;
+        }
+        try {
+            Method readMethod = propertyDescriptor.getReadMethod();
+            if (!readMethod.isAccessible()) {
+                readMethod.setAccessible(true);
+            }
+            return readMethod.invoke(key, new Object[0]);
+        } catch (Exception e) {
+            Policy.getLog().log(
+                    new Status(IStatus.ERROR, Policy.JFACE_DATABINDING,
+                            IStatus.ERROR, "cannot get value", e)); //$NON-NLS-1$
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected Object doPut(Object key, Object value) {
+        try {
+            Object oldValue = get(key);
+            propertyDescriptor.getWriteMethod().invoke(key,
+                    new Object[] { value });
+            keySet().add(key);
+            return oldValue;
+        } catch (Exception e) {
+            Policy.getLog().log(
+                    new Status(IStatus.ERROR, Policy.JFACE_DATABINDING,
+                            IStatus.ERROR, "cannot set value", e)); //$NON-NLS-1$
+            throw new RuntimeException(e);
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.core.databinding.beans.IBeanObservable#getObserved()
+     */
+    public Object getObserved() {
+        return keySet();
+    }
+
+    /* (non-Javadoc)
+     * @see org.eclipse.core.databinding.beans.IBeanObservable#getPropertyDescriptor()
+     */
+    public PropertyDescriptor getPropertyDescriptor() {
+        return propertyDescriptor;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/JavaBeanObservableSet.d	Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,301 @@
+/*******************************************************************************
+ * 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 171616
+ *     Matthew Hall - bugs 221351, 223164, 244098
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.beans;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+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.BindingException;
+import org.eclipse.core.databinding.beans.IBeanObservable;
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.set.ObservableSet;
+import org.eclipse.core.runtime.Assert;
+
+/**
+ * @since 1.0
+ * 
+ */
+public class JavaBeanObservableSet : ObservableSet , IBeanObservable {
+
+    private final Object object;
+
+    private bool updating = false;
+
+    private PropertyDescriptor descriptor;
+
+    private ListenerSupport listenerSupport;
+
+    /**
+     * @param realm
+     * @param object
+     * @param descriptor
+     * @param elementType
+     */
+    public this(Realm realm, Object object,
+            PropertyDescriptor descriptor, Class elementType) {
+        this(realm, object, descriptor, elementType, true);
+    }
+
+    /**
+     * @param realm
+     * @param object
+     * @param descriptor
+     * @param elementType
+     * @param attachListeners
+     */
+    public this(Realm realm, Object object,
+            PropertyDescriptor descriptor, Class elementType,
+            bool attachListeners) {
+        super(realm, new HashSet(), elementType);
+        this.object = object;
+        this.descriptor = descriptor;
+        if (attachListeners) {
+            PropertyChangeListener listener = new class() PropertyChangeListener {
+                public void propertyChange(java.beans.PropertyChangeEvent event) {
+                    if (!updating) {
+                        getRealm().exec(new class() Runnable {
+                            public void run() {
+                                Set newElements = new HashSet(Arrays
+                                        .asList(getValues()));
+                                Set addedElements = new HashSet(newElements);
+                                Set removedElements = new HashSet(wrappedSet);
+                                // remove all new elements from old elements to
+                                // compute
+                                // the removed elements
+                                removedElements.removeAll(newElements);
+                                addedElements.removeAll(wrappedSet);
+                                wrappedSet = newElements;
+                                fireSetChange(Diffs.createSetDiff(
+                                        addedElements, removedElements));
+                            }
+                        });
+                    }
+                }
+            };
+            this.listenerSupport = new ListenerSupport(listener, descriptor
+                    .getName());
+            listenerSupport.hookListener(this.object);
+        }
+
+        wrappedSet.addAll(Arrays.asList(getValues()));
+    }
+
+    private Object primGetValues() {
+        try {
+            Method readMethod = descriptor.getReadMethod();
+            if (!readMethod.isAccessible()) {
+                readMethod.setAccessible(true);
+            }
+            return readMethod.invoke(object, new Object[0]);
+        } catch (IllegalArgumentException e) {
+        } catch (IllegalAccessException e) {
+        } catch (InvocationTargetException e) {
+        }
+        Assert.isTrue(false, "Could not read collection values"); //$NON-NLS-1$
+        return null;
+    }
+
+    private Object[] getValues() {
+        Object[] values = null;
+
+        Object result = primGetValues();
+        if (descriptor.getPropertyType().isArray())
+            values = cast(Object[]) result;
+        else {
+            // TODO add jUnit for POJO (var. SettableValue) collections
+            Collection list = cast(Collection) result;
+            if (list !is null)
+                values = list.toArray();
+        }
+        if (values is null)
+            values = new Object[0];
+        return values;
+    }
+
+    private void setValues() {
+        if (descriptor.getPropertyType().isArray()) {
+            Class componentType = descriptor.getPropertyType()
+                    .getComponentType();
+            Object[] newArray = cast(Object[]) Array.newInstance(componentType,
+                    wrappedSet.size());
+            wrappedSet.toArray(newArray);
+            primSetValues(newArray);
+        } else {
+            // assume that it is a java.util.Set
+            primSetValues(new HashSet(wrappedSet));
+        }
+    }
+
+    public bool add(Object o) {
+        getterCalled();
+        updating = true;
+        try {
+            bool added = wrappedSet.add(o);
+            if (added) {
+                setValues();
+                fireSetChange(Diffs.createSetDiff(Collections.singleton(o),
+                        Collections.EMPTY_SET));
+            }
+            return added;
+        } finally {
+            updating = false;
+        }
+    }
+
+    public bool remove(Object o) {
+        getterCalled();
+        updating = true;
+        try {
+            bool removed = wrappedSet.remove(o);
+            if (removed) {
+                setValues();
+                fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET,
+                        Collections.singleton(o)));
+            }
+            return removed;
+        } finally {
+            updating = false;
+        }
+    }
+
+    public bool addAll(Collection c) {
+        getterCalled();
+        updating = true;
+        try {
+            Set additions = new HashSet();
+            for (Iterator iterator = c.iterator(); iterator.hasNext();) {
+                Object element = iterator.next();
+                if (wrappedSet.add(element))
+                    additions.add(element);
+            }
+            bool changed = !additions.isEmpty();
+            if (changed) {
+                setValues();
+                fireSetChange(Diffs.createSetDiff(additions,
+                        Collections.EMPTY_SET));
+            }
+            return changed;
+        } finally {
+            updating = false;
+        }
+    }
+
+    public bool removeAll(Collection c) {
+        getterCalled();
+        updating = true;
+        try {
+            Set removals = new HashSet();
+            for (Iterator iterator = c.iterator(); iterator.hasNext();) {
+                Object element = iterator.next();
+                if (wrappedSet.remove(element))
+                    removals.add(element);
+            }
+            bool changed = !removals.isEmpty();
+            if (changed) {
+                setValues();
+                fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET,
+                        removals));
+            }
+            return changed;
+        } finally {
+            updating = false;
+        }
+    }
+
+    public bool retainAll(Collection c) {
+        getterCalled();
+        updating = true;
+        try {
+            Set removals = new HashSet();
+            for (Iterator iterator = wrappedSet.iterator(); iterator.hasNext();) {
+                Object element = iterator.next();
+                if (!c.contains(element)) {
+                    iterator.remove();
+                    removals.add(element);
+                }
+            }
+            bool changed = !removals.isEmpty();
+            if (changed) {
+                setValues();
+                fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET,
+                        removals));
+            }
+            return changed;
+        } finally {
+            updating = false;
+        }
+    }
+
+    public void clear() {
+        getterCalled();
+        if (wrappedSet.isEmpty())
+            return;
+
+        updating = true;
+        try {
+            Set removals = new HashSet(wrappedSet);
+            wrappedSet.clear();
+            setValues();
+            fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET, removals));
+        } finally {
+            updating = false;
+        }
+    }
+
+    private void primSetValues(Object newValue) {
+        Exception ex = null;
+        try {
+            Method writeMethod = descriptor.getWriteMethod();
+            if (!writeMethod.isAccessible()) {
+                writeMethod.setAccessible(true);
+            }
+            writeMethod.invoke(object, new Object[] { newValue });
+            return;
+        } catch (IllegalArgumentException e) {
+            ex = e;
+        } catch (IllegalAccessException e) {
+            ex = e;
+        } catch (InvocationTargetException e) {
+            ex = e;
+        }
+        throw new BindingException("Could not write collection values", ex); //$NON-NLS-1$
+    }
+
+    public Object getObserved() {
+        return object;
+    }
+
+    public PropertyDescriptor getPropertyDescriptor() {
+        return descriptor;
+    }
+
+    public synchronized void dispose() {
+        if (listenerSupport !is null) {
+            listenerSupport.dispose();
+            listenerSupport = null;
+        }
+
+        super.dispose();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/JavaBeanObservableValue.d	Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,190 @@
+/*******************************************************************************
+ * 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 164134, 171616
+ *******************************************************************************/
+package org.eclipse.core.internal.databinding.beans;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.eclipse.core.databinding.BindingException;
+import org.eclipse.core.databinding.beans.BeansObservables;
+import org.eclipse.core.databinding.beans.IBeanObservable;
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.value.AbstractObservableValue;
+import org.eclipse.core.databinding.observable.value.ValueDiff;
+import org.eclipse.core.databinding.util.Policy;
+import org.eclipse.core.internal.databinding.Util;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+/**
+ * @since 1.0
+ * 
+ */
+public class JavaBeanObservableValue : AbstractObservableValue , IBeanObservable {
+    private final Object object;
+    private bool updating = false;
+
+    private final PropertyDescriptor propertyDescriptor;
+    private ListenerSupport listenerSupport;
+
+    private bool attachListeners;
+
+    /**
+     * @param realm
+     * @param object
+     * @param descriptor
+     */
+    public this(Realm realm, Object object,
+            PropertyDescriptor descriptor) {
+        this(realm, object, descriptor, true);
+    }
+
+    /**
+     * @param realm
+     * @param object
+     * @param descriptor
+     * @param attachListeners
+     */
+    public this(Realm realm, Object object,
+            PropertyDescriptor descriptor, bool attachListeners) {
+        super(realm);
+        this.object = object;
+        this.propertyDescriptor = descriptor;
+        this.attachListeners = attachListeners;
+    }
+
+    protected void firstListenerAdded() {
+        if (!attachListeners) {
+            return;
+        }
+            
+        PropertyChangeListener listener = new class() PropertyChangeListener {
+            public void propertyChange(java.beans.PropertyChangeEvent event) {
+                if (!updating) {
+                    final ValueDiff diff = Diffs.createValueDiff(event.getOldValue(),
+                                            event.getNewValue());
+                    getRealm().exec(new class() Runnable {
+                        public void run() {
+                            fireValueChange(diff);
+                        }});
+                }
+            }
+        };
+        
+        if (listenerSupport is null) {
+            listenerSupport = new ListenerSupport(listener, propertyDescriptor.getName());
+        }
+        
+        listenerSupport.hookListener(object);
+    }
+
+    public void doSetValue(Object value) {
+        updating = true;
+        try {
+            Object oldValue = doGetValue();
+            
+            if (Util.equals(oldValue, value)) {
+                return;
+            }
+            
+            Method writeMethod = propertyDescriptor.getWriteMethod();
+            if (!writeMethod.isAccessible()) {
+                writeMethod.setAccessible(true);
+            }
+            writeMethod.invoke(object, new Object[] { value });
+            fireValueChange(Diffs.createValueDiff(oldValue, doGetValue()));
+        } catch (InvocationTargetException e) {
+            /*
+             * InvocationTargetException wraps any exception thrown by the
+             * invoked method.
+             */
+            throw new RuntimeException(e.getCause());
+        } catch (Exception e) {
+            if cast(BeansObservables.DEBUG) {
+                Policy
+                        .getLog()
+                        .log(
+                                new Status(
+                                        IStatus.WARNING,
+                                        Policy.JFACE_DATABINDING,
+                                        IStatus.OK,
+                                        "Could not change value of " + object + "." + propertyDescriptor.getName(), e)); //$NON-NLS-1$ //$NON-NLS-2$
+            }
+        } finally {
+            updating = false;
+        }
+    }
+
+    public Object doGetValue() {
+        try {
+            Method readMethod = propertyDescriptor.getReadMethod();
+            if (readMethod is null) {
+                throw new BindingException(propertyDescriptor.getName()
+                        + " property does not have a read method."); //$NON-NLS-1$
+            }
+            if (!readMethod.isAccessible()) {
+                readMethod.setAccessible(true);
+            }
+            return readMethod.invoke(object, null);
+        } catch (InvocationTargetException e) {
+            /*
+             * InvocationTargetException wraps any exception thrown by the
+             * invoked method.
+             */
+            throw new RuntimeException(e.getCause());
+        } catch (Exception e) {
+            if cast(BeansObservables.DEBUG) {
+                Policy
+                        .getLog()
+                        .log(
+                                new Status(
+                                        IStatus.WARNING,
+                                        Policy.JFACE_DATABINDING,
+                                        IStatus.OK,
+                                        "Could not read value of " + object + "." + propertyDescriptor.getName(), e)); //$NON-NLS-1$ //$NON-NLS-2$
+            }
+            return null;
+        }
+    }
+
+    protected void lastListenerRemoved() {
+        unhookListener();
+    }
+
+    private void unhookListener() {
+        if (listenerSupport !is null) {
+            listenerSupport.dispose();
+            listenerSupport = null;
+        }
+    }
+
+    public Object getValueType() {
+        return propertyDescriptor.getPropertyType();
+    }
+
+    public Object getObserved() {
+        return object;
+    }
+
+    public PropertyDescriptor getPropertyDescriptor() {
+        return propertyDescriptor;
+    }
+
+    public synchronized void dispose() {
+        unhookListener();
+        super.dispose();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/JavaBeanPropertyObservableMap.d	Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,235 @@
+/*******************************************************************************
+ * 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 - bugs 223164, 244098
+ *******************************************************************************/
+
+package org.eclipse.core.internal.databinding.beans;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+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.BindingException;
+import org.eclipse.core.databinding.beans.IBeanObservable;
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.map.ObservableMap;
+import org.eclipse.core.internal.databinding.Util;
+import org.eclipse.core.runtime.Assert;
+
+/**
+ * @since 1.0
+ * 
+ */
+public class JavaBeanPropertyObservableMap : ObservableMap ,
+        IBeanObservable {
+
+    private final Object object;
+
+    private bool updating = false;
+
+    private PropertyDescriptor descriptor;
+
+    private ListenerSupport listenerSupport;
+
+    /**
+     * @param realm
+     * @param object
+     * @param descriptor
+     */
+    public this(Realm realm, Object object,
+            PropertyDescriptor descriptor) {
+        this(realm, object, descriptor, true);
+    }
+
+    /**
+     * @param realm
+     * @param object
+     * @param descriptor
+     * @param attachListeners
+     */
+    public this(Realm realm, Object object,
+            PropertyDescriptor descriptor, bool attachListeners) {
+        super(realm, new HashMap());
+        this.object = object;
+        this.descriptor = descriptor;
+        if (attachListeners) {
+            PropertyChangeListener listener = new class() PropertyChangeListener {
+                public void propertyChange(final PropertyChangeEvent event) {
+                    if (!updating) {
+                        getRealm().exec(new class() Runnable {
+                            public void run() {
+                                Map oldValue = wrappedMap;
+                                Map newValue = cast(Map) event.getNewValue();
+                                wrappedMap = new HashMap(newValue);
+                                
+                                fireMapChange(Diffs.computeMapDiff(oldValue, newValue));
+                            }
+                        });
+                    }
+                }
+            };
+
+            listenerSupport = new ListenerSupport(listener,
+                    descriptor.getName());
+            listenerSupport.hookListener(this.object);
+        }
+
+        wrappedMap.putAll(getMap());
+    }
+
+    private Object primGetMap() {
+        try {
+            Method readMethod = descriptor.getReadMethod();
+            if (!readMethod.isAccessible()) {
+                readMethod.setAccessible(true);
+            }
+            return readMethod.invoke(object, new Object[0]);
+        } catch (IllegalArgumentException e) {
+        } catch (IllegalAccessException e) {
+        } catch (InvocationTargetException e) {
+        }
+        Assert.isTrue(false, "Could not read collection values"); //$NON-NLS-1$
+        return null;
+    }
+
+    private void primSetMap(Object newValue) {
+        Exception ex = null;
+        try {
+            Method writeMethod = descriptor.getWriteMethod();
+            if (!writeMethod.isAccessible()) {
+                writeMethod.setAccessible(true);
+            }
+            writeMethod.invoke(object, new Object[] { newValue });
+            return;
+        } catch (IllegalArgumentException e) {
+            ex = e;
+        } catch (IllegalAccessException e) {
+            ex = e;
+        } catch (InvocationTargetException e) {
+            ex = e;
+        }
+        throw new BindingException("Could not write collection values", ex); //$NON-NLS-1$
+    }
+
+    private Map getMap() {
+        Map result = cast(Map) primGetMap();
+
+        if (result is null)
+            result = new HashMap();
+        return result;
+    }
+
+    private void setMap() {
+        primSetMap(new HashMap(wrappedMap));
+    }
+
+    public Object put(Object key, Object value) {
+        checkRealm();
+        updating = true;
+        try {
+            Object result = wrappedMap.put(key, value);
+            if (!Util.equals(result, value)) {
+                setMap();
+                if (result is null) {
+                    fireMapChange(Diffs.createMapDiffSingleAdd(key, value));
+                } else {
+                    fireMapChange(Diffs.createMapDiffSingleChange(key, result,
+                            value));
+                }
+            }
+            return result;
+        } finally {
+            updating = false;
+        }
+    }
+
+    public void putAll(Map map) {
+        checkRealm();
+        updating = true;
+        try {
+            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 key = entry.getKey();
+                Object newValue = entry.getValue();
+                Object oldValue = wrappedMap.put(key, newValue);
+                if (oldValue is null) {
+                    addedKeys.add(key);
+                } else if (!Util.equals(oldValue, newValue)) {
+                    changes.put(key, oldValue);
+                }
+            }
+            if (!addedKeys.isEmpty() || !changes.isEmpty()) {
+                setMap();
+                fireMapChange(Diffs.createMapDiff(addedKeys,
+                        Collections.EMPTY_SET, changes.keySet(), changes,
+                        wrappedMap));
+            }
+        } finally {
+            updating = false;
+        }
+    }
+
+    public Object remove(Object key) {
+        checkRealm();
+        updating = true;
+        try {
+            Object result = wrappedMap.remove(key);
+            if (result!isnull) {
+                setMap();
+                fireMapChange(Diffs.createMapDiffSingleRemove(key, result));
+            }
+            return result;
+        } finally {
+            updating = false;
+        }
+    }
+
+    public void clear() {
+        checkRealm();
+        if (wrappedMap.isEmpty())
+            return;
+        updating = true;
+        try {
+            Map oldMap = wrappedMap;
+            wrappedMap = new HashMap();
+            setMap();
+            fireMapChange(Diffs.computeMapDiff(oldMap, Collections.EMPTY_MAP));
+        } finally {
+            updating = false;
+        }
+    }
+
+    public Object getObserved() {
+        return object;
+    }
+
+    public PropertyDescriptor getPropertyDescriptor() {
+        return descriptor;
+    }
+
+    public synchronized void dispose() {
+        if (listenerSupport !is null) {
+            listenerSupport.dispose();
+            listenerSupport = null;
+        }
+        super.dispose();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/ListenerSupport.d	Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,216 @@
+/*******************************************************************************
+ * 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.beans;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.eclipse.core.databinding.beans.BeansObservables;
+import org.eclipse.core.databinding.util.Policy;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+/**
+ * This is a helper that will hook up and listen for <code>PropertyChangeEvent</code> 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 <code>PropertyChangeEvent</code> is fired on any
+     *      of the target objects.  Will only receive change events 
+     *      when the provided <code>propertyName</code> 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 of<code>ListenerSupport</code> 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 <code>methodName</code> 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 <code>true</code> 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));
+        }
+    }
+}
--- /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$
+    }
+}
--- /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;
+    }
+
+}
--- /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;
+    }
+
+}
--- /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);
+    }
+    
+}
--- /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; i<size;i++) {
+            Object candidate = list.get(i);
+            if (candidateisnull ? objectisnull : candidate.equals(object)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Checks whether the two objects are <code>null</code> -- allowing for
+     * <code>null</code>.
+     * 
+     * @param left
+     *            The left object to compare; may be <code>null</code>.
+     * @param right
+     *            The right object to compare; may be <code>null</code>.
+     * @return <code>true</code> if the two objects are equivalent;
+     *         <code>false</code> 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;
+            }
+        };
+    }
+}
--- /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);
+
+}
--- /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.
+ * 
+ * <p>
+ * 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}.
+ * </p>
+ * 
+ * @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 in the
+ *              framework 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 IObservable {
+
+    /**
+     * Returns the realm for this observable. Unless otherwise specified,
+     * getters and setters must be accessed from within this realm. Listeners
+     * will be within this realm when they receive events from this observable.
+     * <p>
+     * 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.
+     * </p>
+     * 
+     * @return the realm
+     */
+    public Realm getRealm();
+
+    /**
+     * Adds the given change listener to the list of change listeners. Change
+     * listeners are notified about changes of the state of this observable in a
+     * generic way, without specifying the change that happened. To get the
+     * changed state, a change listener needs to query for the current state of
+     * this observable.
+     * 
+     * @param listener
+     */
+    public void addChangeListener(IChangeListener listener);
+
+    /**
+     * Removes the given change listener from the list of change listeners. Has
+     * no effect if the given listener is not registered as a change listener.
+     * 
+     * @param listener
+     */
+    public void removeChangeListener(IChangeListener listener);
+
+    /**
+     * Adds the given stale listener to the list of stale listeners. Stale
+     * listeners are notified when an observable object becomes stale, not when
+     * is becomes non-stale.
+     * 
+     * @param listener
+     * 
+     * @see #isStale()
+     */
+    public void addStaleListener(IStaleListener listener);
+
+    /**
+     * Removes the given stale listener from the list of stale listeners. Has no
+     * effect if the given listener is not registered as a stale listener.
+     * 
+     * @param listener
+     */
+    public void removeStaleListener(IStaleListener listener);
+
+    /**
+     * Returns whether the state of this observable is stale and is expected to
+     * change soon. A non-stale observable that becomes stale will notify its
+     * stale listeners. A stale object that becomes non-stale does so by
+     * changing its state and notifying its change listeners, it does <b>not</b>
+     * notify its stale listeners about becoming non-stale. Clients that do not
+     * expect asynchronous changes may ignore staleness of observable objects.
+     * 
+     * @return true if this observable's state is stale and will change soon.
+     * 
+     * @TrackedGetter - implementers must call
+     *                {@link ObservableTracker#getterCalledcast(IObservable)}.
+     */
+    public bool isStale();
+
+    /**
+     * Disposes of this observable object, removing all listeners registered
+     * with this object, and all listeners this object might have registered on
+     * other objects.
+     */
+    public void dispose();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObservableCollection.d	Tue Apr 21 10:55:51 2009 +0200
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * 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;
+
+import java.util.Collection;
+
+import org.eclipse.core.databinding.observable.list.IObservableList;
+import org.eclipse.core.databinding.observable.set.IObservableSet;
+
+/**
+ * Interface for observable collections. Only general change listeners can be
+ * added to an observable collection. Listeners interested in incremental
+ * changes have to be added using more concrete subtypes such as
+ * {@link IObservableList} or {@link IObservableSet}.
+ * 
+ * @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.
+ *              </p>
+ * 
+ * @since 1.0
+ */
+public interface IObservableCollection : IObservable, Collection {
+
+    /**
+     * @return the element type of this observable value, or <code>null</code>
+     *         if this observable collection is untyped.
+     */
+    Object getElementType();
+
+}
--- /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 {
+
+}
--- /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 <code>null</code> if this observing
+     * object does not currently observe an object.
+     * 
+     * @return the observed object, or <code>null</code>
+     */
+    public Object getObserved();
+
+}
--- /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);
+
+}
--- /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();
+
+}
--- /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:
+ * 
+ * <p>
+ * 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.
+ * </p>
+ * 
+ * <p>
+ * 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.
+ * </p>
+ * 
+ * <p>
+ * 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.
+ * </p>
+ * 
+ * <p>
+ * 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.
+ * </p>
+ * 
+ * @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
+     *            <code>null</code> 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);
+        }
+    }
+}
--- /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 <code>Boolean.TYPE</code> 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.
+     * <p>
+     * The state where the key does not exist in the map is equivalent to the
+     * state where the key exists and its value is <code>null</code>. 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 <code>null</code>, 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 <code>null</code>, 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 <code>MasterDetailObservables.detailValue(master,
+     * mapEntryValueFactory(map, valueType), valueType)</code>.
+     * 
+     * @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 <code>null</code>, 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);
+    }
+}
--- /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.
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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)}.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * 
+ * @since 1.0
+ * 
+ * @see IObservable
+ */
+public abstract class Realm {
+
+    private static ThreadLocal defaultRealm = new ThreadLocal();
+
+    /**
+     * Returns the default realm for the calling thread, or <code>null</code>
+     * if no default realm has been set.
+     * 
+     * @return the default realm, or <code>null</code>
+     */
+    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 <code>null</code>
+     * @return the previous default realm, or <code>null</code>
+     */
+    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
+     * <code>handleException<code> 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 <code>run()</code> 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.
+     * <p>
+     * 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.
+     * </p>
+     * 
+     * @param runnable
+     */
+    public void exec(Runnable runnable) {
+        if (isCurrent()) {
+            safeRun(runnable);
+        } else {
+            asyncExec(runnable);
+        }
+    }
+
+    /**
+     * Causes the <code>run()</code> 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.
+     * <p>
+     * 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.
+     * </p>
+     * <p>
+     * Subclasses should use {@link #safeRuncast(Runnable)} to run the runnable.
+     * </p>
+     * 
+     * @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 <code>run()</code> 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.
+     * <p>
+     * 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.
+     * </p>
+     * <p>
+     * Subclasses should use {@link #safeRuncast(Runnable)} to run the runnable.
+     * </p>
+     * <p>
+     * Note: This class is not meant to be called by clients and therefore has
+     * only protected access.
+     * </p>
+     * 
+     * @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 <code>realm</code> 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);
+        }
+    }
+}
--- /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;
+    }
+
+}
--- /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().
+ * 
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * 
+ * @since 1.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 <code>oldIndex</code> to
+     * <code>newIndex</code>. This method is equivalent to calling
+     * <code>add(newIndex, remove(oldIndex))</code>.
+     * <p>
+     * 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 <code>0 &lt;= oldIndex &lt; size()</code>.
+     * @param newIndex
+     *            the element's position after the move. Must be within the
+     *            range <code>0 &lt;= newIndex &lt; size()</code>.
+     * @return the element that was moved.
+     * @throws IndexOutOfBoundsException
+     *             if either argument is out of range (<code>0 &lt;= index &lt; size()</code>).
+     * @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$
+    }
+}
--- /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
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * 
+ * @since 1.1
+ */
+public abstract class ComputedList : AbstractObservableList {
+    private List cachedList = new ArrayList();
+
+    private bool dirty = true;
+    private bool stale = false;
+
+    private IObservable[] dependencies = new IObservable[0];
+
+    /**
+     * Creates a computed list in the default realm and with an unknown (null)
+     * element type.
+     */
+    public this() {
+        this(Realm.getDefault(), null);
+    }
+
+    /**
+     * Creates a computed list in the default realm and with the given element
+     * type.
+     * 
+     * @param elementType
+     *            the element type, may be <code>null</code> to indicate
+     *            unknown element type
+     */
+    public this(Object elementType) {
+        this(Realm.getDefault(), elementType);
+    }
+
+    /**
+     * Creates a computed list in given realm and with an unknown (null) element
+     * type.
+     * 
+     * @param realm
+     *            the realm
+     * 
+     */
+    public this(Realm realm) {
+        this(realm, null);
+    }
+
+    /**
+     * Creates a computed list in the given realm and with the given element
+     * type.
+     * 
+     * @param realm
+     *            the realm
+     * @param elementType
+     *            the element type, may be <code>null</code> to indicate
+     *            unknown element type
+     */
+    public this(Realm realm, Object elementType) {
+        super(realm);
+        this.elementType = elementType;
+    }
+
+    /**
+     * Inner class that implements interfaces that we don't want to expose as
+     * public API. Each interface could have been implemented using a separate
+     * anonymous class, but we combine them here to reduce the memory overhead
+     * and number of classes.
+     * 
+     * <p>
+     * The Runnable calls calculate and stores the result in cachedList.
+     * </p>
+     * 
+     * <p>
+     * The IChangeListener stores each observable in the dependencies list. This
+     * is registered as the listener when calling ObservableTracker, to detect
+     * every observable that is used by computeValue.
+     * </p>
+     * 
+     * <p>
+     * The IChangeListener is attached to every dependency.
+     * </p>
+     * 
+     */
+    private class PrivateInterface : Runnable, IChangeListener,
+            IStaleListener {
+        public void run() {
+            cachedList = calculate();
+            if (cachedList is null)
+                cachedList = Collections.EMPTY_LIST;
+        }
+
+        public void handleStale(StaleEvent event) {
+            if (!dirty)
+                makeStale();
+        }
+
+        public void handleChange(ChangeEvent event) {
+            makeDirty();
+        }
+    }
+
+    private PrivateInterface privateInterface = new PrivateInterface();
+
+    private Object elementType;
+
+    protected int doGetSize() {
+        return doGetList().size();
+    }
+
+    public Object get(int index) {
+        getterCalled();
+        return doGetList().get(index);
+    }
+
+    private final List getList() {
+        getterCalled();
+        return doGetList();
+    }
+
+    final List doGetList() {
+        if (dirty) {
+            // This line will do the following:
+            // - Run the calculate method
+            // - While doing so, add any observable that is touched to the
+            // dependencies list
+            IObservable[] newDependencies = ObservableTracker.runAndMonitor(
+                    privateInterface, privateInterface, null);
+
+            // If any dependencies are stale, a stale event will be fired here
+            // even if we were already stale before recomputing. This is in case
+            // clients assume that a list change is indicative of non-staleness.
+            stale = false;
+            for (int i = 0; i < newDependencies.length; i++) {
+                if (newDependencies[i].isStale()) {
+                    makeStale();
+                    break;
+                }
+            }
+
+            if (!stale) {
+                for (int i = 0; i < newDependencies.length; i++) {
+                    newDependencies[i].addStaleListener(privateInterface);
+                }
+            }
+
+            dependencies = newDependencies;
+
+            dirty = false;
+        }
+
+        return cachedList;
+    }
+
+    private void getterCalled() {
+        ObservableTracker.getterCalled(this);
+    }
+
+    /**
+     * Subclasses must override this method to calculate the list contents.
+     * 
+     * @return the object's list.
+     */
+    protected abstract List calculate();
+
+    private void makeDirty() {
+        if (!dirty) {
+            dirty = true;
+
+            makeStale();
+
+            stopListening();
+
+            // copy the old list
+            final List oldList = new ArrayList(cachedList);
+            // Fire the "dirty" event. This implementation recomputes the new
+            // list lazily.
+            fireListChange(new class() ListDiff {
+                ListDiffEntry[] differences;
+
+                public ListDiffEntry[] getDifferences() {
+                    if (differences is null)
+                        differences = Diffs.computeListDiff(oldList, getList())
+                                .getDifferences();
+                    return differences;
+                }
+            });
+        }
+    }
+
+    private void stopListening() {
+        if (dependencies !is null) {
+            for (int i = 0; i < dependencies.length; i++) {
+                IObservable observable = dependencies[i];
+
+                observable.removeChangeListener(privateInterface);
+                observable.removeStaleListener(privateInterface);
+            }
+            dependencies = null;
+        }
+    }
+
+    private void makeStale() {
+        if (!stale) {
+            stale = true;
+            fireStale();
+        }
+    }
+
+    public bool isStale() {
+        // recalculate list if dirty, to ensure staleness is correct.
+        getList();
+        return stale;
+    }
+
+    public Object getElementType() {
+        return elementType;
+    }
+
+    public synchronized void addChangeListener(IChangeListener listener) {
+        super.addChangeListener(listener);
+        // If somebody is listening, we need to make sure we attach our own
+        // listeners
+        computeListForListeners();
+    }
+
+    public synchronized void addListChangeListener(IListChangeListener listener) {
+        super.addListChangeListener(listener);
+        // If somebody is listening, we need to make sure we attach our own
+        // listeners
+        computeListForListeners();
+    }
+
+    private void computeListForListeners() {
+        // Some clients just add a listener and expect to get notified even if
+        // they never called getValue(), so we have to call getValue() ourselves
+        // here to be sure. Need to be careful about realms though, this method
+        // can be called outside of our realm.
+        // See also bug 198211. If a client calls this outside of our realm,
+        // they may receive change notifications before the runnable below has
+        // been executed. It is their job to figure out what to do with those
+        // notifications.
+        getRealm().exec(new class() Runnable {
+            public void run() {
+                if (dependencies is null) {
+                    // We are not currently listening.
+                    // But someone is listening for changes. Call getValue()
+                    // to make sure we start listening to the observables we
+                    // depend on.
+                    getList();
+                }
+            }
+        });
+    }
+
+    public synchronized void dispose() {
+        stopListening();
+        super.dispose();
+    }
+}
--- /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);
+
+}
--- /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 <code>oldIndex</code> to
+     * <code>newIndex</code>. This method is equivalent to calling
+     * <code>add(newIndex, remove(oldIndex))</code>.
+     * <p>
+     * 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 <code>0 &lt;= oldIndex &lt; size()</code>.
+     * @param newIndex
+     *            the element's position after the move. Must be within the
+     *            range <code>0 &lt;= newIndex &lt; size()</code>.
+     * @return the element that was moved.
+     * @throws IndexOutOfBoundsException
+     *             if either argument is out of range (<code>0 &lt;= index &lt; size()</code>).
+     * @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 <code>null</code> if untyped
+     */
+    Object getElementType();
+}
--- /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;
+    }
+
+}
--- /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 <code>visitor</code> for each difference.
+     * <ol>
+     * <li>{@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.
+     * <li>{@link ListDiffVisitor#handleMove(int, int, Object)} is called
+     * whenever a remove entry is immediately followed by an add entry with an
+     * equivalent element.
+     * <li>{@link ListDiffVisitor#handleRemove(int, Object)} is called whenever
+     * a remove entry does not match conditions 1 or 2.
+     * <li>{@link ListDiffVisitor#handleAdd(int, Object)} is called whenever an
+     * add entry does not match conditions in 1 or 2.
+     * </ol>
+     * 
+     * @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();
+    }
+}
--- /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();
+    }
+}
--- /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 <code>element</code> was added to the list at
+     * position <code>index</code>.
+     * 
+     * @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 <code>element</code> was removed from the
+     * list at position <code>index</code>.
+     * 
+     * @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 <code>element</code> was moved in the list
+     * from position <code>oldIndex</code> to position <code>newIndex</code>.
+     * <p>
+     * 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 <code>oldElement</code>, located at position
+     * <code>index</code> in the list, was replaced by <code>newElement</code>.
+     * <p>
+     * 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);
+    }
+}
--- /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. 
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * @since 1.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 <code>oldIndex</code> to
+     * <code>newIndex</code>. This method is equivalent to calling
+     * <code>add(newIndex, remove(oldIndex))</code>.
+     * <p>
+     * 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 <code>0 &lt;= oldIndex &lt; size()</code>.
+     * @param newIndex
+     *            the element's position after the move. Must be within the
+     *            range <code>0 &lt;= newIndex &lt; size()</code>.
+     * @return the element that was moved.
+     * @throws IndexOutOfBoundsException
+     *             if either argument is out of range (<code>0 &lt;= index &lt; size()</code>).
+     * @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);
+    }
+
+}
--- /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.
+ * 
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * 
+ * @since 1.0
+ */
+public class WritableList : ObservableList {
+
+    /**
+     * Creates an empty writable list in the default realm with a
+     * <code>null</code> element type.
+     * 
+     */
+    public this() {
+        this(Realm.getDefault());
+    }
+
+    /**
+     * Creates an empty writable list with a <code>null</code> 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 <code>null</code>
+     */
+    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 <code>null</code>
+     */
+    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 <code>null</code>
+     * @return new list with the default realm.
+     */
+    public static WritableList withElementType(Object elementType) {
+        return new WritableList(Realm.getDefault(), new ArrayList(),
+                elementType);
+    }
+}
--- /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 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+   <meta name="Author" content="IBM">
+   <meta name="GENERATOR" content="Mozilla/4.5 [en] (Win98; I) [Netscape]">
+   <title>Package-level Javadoc</title>
+</head>
+<body>
+Provides classes for observing changes in lists.
+<h2>
+Package Specification</h2>
+<p>
+This package provides classes for observing changes in lists.</p>
+</body>
+</html>
--- /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;
+
+/**
+ * 
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * @since 1.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$
+    }
+}
--- /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;
+
+/**
+ * 
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * @since 1.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);
+        }
+    }
+
+}
--- /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.
+ * 
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * 
+ * @since 1.1
+ * 
+ */
+public 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
+     * <code>firstMap</code>, it cannot be passed in as an argument. Instead,
+     * the second map will be created by calling
+     * <code>secondMapFactory.createObservable(valueSet())</code>.
+     * 
+     * @param firstMap
+     *            the first map
+     * @param secondMapFactory
+     *            a factory that creates the second map when given an observable
+     *            set representing the value set of <code>firstMap</code>.
+     */
+    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;
+    }
+
+}
--- /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);
+}
--- /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);
+
+}
--- /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();
+}
--- /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;
+    }
+
+}
--- /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);
+}
--- /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;
+
+/**
+ * 
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * @since 1.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();
+    }
+}
--- /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;
+
+/**
+ * 
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * @since 1.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 <code>value</code> with the <code>key</code>.  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 <code>key</code>.  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 <code>map</code>'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));
+        }
+    }
+
+}
--- /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 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+   <meta name="Author" content="IBM">
+   <meta name="GENERATOR" content="Mozilla/4.5 [en] (Win98; I) [Netscape]">
+   <title>Package-level Javadoc</title>
+</head>
+<body>
+Provides classes that can be used to observe changes in maps.
+<h2>
+Package Specification</h2>
+<p>
+This package provides classes that can be used to observe changes in maps.</p>
+</body>
+</html>
--- /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);
+
+}
--- /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 <code>null</code>
+     * @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 <code>null</code>
+     * @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 <code>null</code>
+     * @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);
+    }
+}
--- /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 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+   <meta name="Author" content="IBM">
+   <meta name="GENERATOR" content="Mozilla/4.5 [en] (Win98; I) [Netscape]">
+   <title>Package-level Javadoc</title>
+</head>
+<body>
+Provides classes that can be used to observe a detail of a master object.
+<h2>
+Package Specification</h2>
+<p>
+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.</p>
+</body>
+</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 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+   <meta name="Author" content="IBM">
+   <meta name="GENERATOR" content="Mozilla/4.5 [en] (Win98; I) [Netscape]">
+   <title>Package-level Javadoc</title>
+</head>
+<body>
+Provides the core APIs for observing changes in objects.
+<h2>
+Package Specification</h2>
+<p>
+This package provides the core APIs for observing changes in objects.</p>
+</body>
+</html>
--- /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}.
+ * 
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * 
+ * @since 1.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;
+        }
+    }
+    
+}
--- /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 <code>null</code> 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();
+
+}
--- /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);
+
+}
--- /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.
+ * 
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * 
+ * @since 1.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;
+        }
+    }
+
+}
--- /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;
+
+/**
+ * 
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * 
+ * @since 1.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);
+    }
+
+}
--- /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}. 
+ * 
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * 
+ * @since 1.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;
+    }
+}
--- /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;
+    }
+
+}
--- /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();
+    }
+}
--- /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.
+ * 
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * 
+ * @since 1.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();
+    }
+
+}
--- /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}.
+ * 
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * 
+ * @since 1.0
+ */
+public class WritableSet : ObservableSet {
+
+    /**
+     * Constructs a new empty instance in the default realm with a
+     * <code>null</code> 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 <code>null</code>
+     */
+    public this(Collection c, Object elementType) {
+        this(Realm.getDefault(), new HashSet(c), elementType);
+    }
+
+    /**
+     * Constructs a new empty instance in the given realm and a
+     * <code>null</code> 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 <code>null</code>
+     */
+    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 <code>null</code>
+     * @return new instance with the default realm
+     */
+    public static WritableSet withElementType(Object elementType) {
+        return new WritableSet(Realm.getDefault(), new HashSet(), elementType);
+    }
+}
--- /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 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+   <meta name="Author" content="IBM">
+   <meta name="GENERATOR" content="Mozilla/4.5 [en] (Win98; I) [Netscape]">
+   <title>Package-level Javadoc</title>
+</head>
+<body>
+Provides classes that can be used to observe changes in sets.
+<h2>
+Package Specification</h2>
+<p>
+This package provides classes that can be used to observe changes in sets.</p>
+</body>
+</html>
--- /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;
+
+/**
+ * 
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * @since 1.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();
+    }
+}
--- /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;
+
+/**
+ * 
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * @since 1.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();
+    }
+
+}
--- /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;
+
+}
--- /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
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * 
+ * @since 1.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 <code>null</code> if we are not currently listening.
+     */
+    private IObservable[] dependencies = null;
+
+    /**
+     * 
+     */
+    public this() {
+        this(Realm.getDefault(), null);
+    }
+
+    /**
+     * @param valueType
+     *            can be <code>null</code>
+     */
+    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.
+     * 
+     * <p>
+     * The Runnable calls computeValue and stores the result in cachedValue.
+     * </p>
+     * 
+     * <p>
+     * The IChangeListener stores each observable in the dependencies list. This
+     * is registered as the listener when calling ObservableTracker, to detect
+     * every observable that is used by computeValue.
+     * </p>
+     * 
+     * <p>
+     * The IChangeListener is attached to every dependency.
+     * </p>
+     * 
+     */
+    private class PrivateInterface : 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();
+    }
+
+}
--- /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 <code>null</code> if this
+     * observable value is untyped.
+     * 
+     * @return the value type, or <code>null</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);
+}
--- /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);
+
+}
--- /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);
+
+}
--- /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);
+
+}
--- /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;
+    }
+
+}
--- /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 <code>true</code>.
+ * 
+ * @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 <code>false</code>, can
+     * be set to <code>true</code> 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;
+    }
+
+}
--- /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();
+    }
+}
--- /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.
+ * <p>
+ * This class is thread safe. All state accessing methods must be invoked from
+ * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
+ * listeners may be invoked from any thread.
+ * </p>
+ * @since 1.0
+ */
+public class WritableValue : AbstractObservableValue {
+
+    private final Object valueType;
+
+    /**
+     * Constructs a new instance with the default realm, a <code>null</code>
+     * value type, and a <code>null</code> value.
+     */
+    public this() {
+        this(null, null);
+    }
+
+    /**
+     * Constructs a new instance with the default realm.
+     * 
+     * @param initialValue
+     *            can be <code>null</code>
+     * @param valueType
+     *            can be <code>null</code>
+     */
+    public this(Object initialValue, Object valueType) {
+        this(Realm.getDefault(), initialValue, valueType);
+    }
+
+    /**
+     * Constructs a new instance with the provided <code>realm</code>, a
+     * <code>null</code> value type, and a <code>null</code> initial value.
+     * 
+     * @param realm
+     */
+    public this(Realm realm) {
+        this(realm, null, null);
+    }
+
+    /**
+     * Constructs a new instance.
+     * 
+     * @param realm
+     * @param initialValue
+     *            can be <code>null</code>
+     * @param valueType
+     *            can be <code>null</code>
+     */
+    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 <code>null</code>
+     * @return new instance with the default realm and a value of <code>null</code>
+     */
+    public static WritableValue withValueType(Object elementType) {
+        return new WritableValue(Realm.getDefault(), null, elementType);
+    }
+}
--- /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 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+   <meta name="Author" content="IBM">
+   <meta name="GENERATOR" content="Mozilla/4.5 [en] (Win98; I) [Netscape]">
+   <title>Package-level Javadoc</title>
+</head>
+<body>
+Provides classes that can be used to observe changes in discrete values.
+<h2>
+Package Specification</h2>
+<p>
+This package provides classes that can be used to observe changes in discrete values.</p>
+</body>
+</html>
--- /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.
+ * <p>
+ * Clients may provide their own implementation to change how errors are logged
+ * from within JFace Data Binding.
+ * </p>
+ * 
+ * @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);
+
+}
--- /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 <tom.schindl@bestsolution.at> - 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 <code>null</code> 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.
+     * <p>
+     * The default logger prints the status to <code>System.err</code>.
+     * </p>
+     * 
+     * @return the logger
+     */
+    public static synchronized ILogger getLog() {
+        if (log is null) {
+            log = getDummyLog();
+        }
+        return log;
+    }
+
+}
--- /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 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+   <meta name="Author" content="IBM">
+   <meta name="GENERATOR" content="Mozilla/4.5 [en] (Win98; I) [Netscape]">
+   <title>Package-level Javadoc</title>
+</head>
+<body>
+Provides general utilities for data binding.
+<h2>
+Package Specification</h2>
+<p>
+This package provides general utilities for data binding.</p>
+</body>
+</html>
--- /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
+    }
+}
--- /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;
+    }
+}
--- /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;
+    }
+}
--- /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);
+}
--- /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);
+    }
+}
--- /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();
+    }
+}
--- /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();
+    }
+}
--- /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();
+    }
+
+}
--- /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);
+        }
+    }
+
+}
--- /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();
+    }
+}
--- /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();
+    }
+}
--- /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();
+    }
+}
--- /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();
+    }
+}
--- /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();
+    }
+}
--- /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();
+    }
+}
--- /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.
+ * <ul>
+ * <li>While status is valid, ValidatedObservableValue stays in sync with its
+ * target.
+ * <li>When status becomes invalid, ValidatedObservableValue will retain the
+ * last valid value of its target.
+ * <li>While status is invalid, changes in the target observable cause
+ * ValidatedObservableValue to fire a stale event, to indicate that changes are
+ * pending.
+ * <li>When status becomes valid, pending value changes are performed (if any)
+ * and synchronization resumes.
+ * </ul>
+ * <p>
+ * Note:
+ * <ul>
+ * <li>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}
+ * <li>Calls to {@link #setValuecast(Object)} on the validated observable changes
+ * the value regardless of the validation status.
+ * <li>This class will not forward {@link ValueChangingEvent} events from a
+ * wrapped {@link IVetoableValue}.
+ * </ul>
+ * 
+ * @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();
+    }
+}
--- /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;
+    }
+}
--- /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();
+    }
+
+}
--- /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;
+    }
+
+}
--- /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;
+    }
+
+}
--- /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);
+        }
+    }
+
+}
--- /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);
+}
--- /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 <code>null</code>
+     */
+    public void setChild(Object child, int index);  
+}
--- /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);
+}
--- /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.
+ * 
+ * <p>
+ * 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.
+ * </p>
+ * 
+ * @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 <code>true</code> 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);
+
+}
--- /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();
+}
--- /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);
+
+}
--- /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);
+}
--- /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 <code>null</code>.
+     * When a request fails, the status indicates why the request failed.
+     * A <code>null</code> status is considered to be successful.
+     * 
+     * @param status request status
+     */
+    public void setStatus(IStatus status);
+    
+    /**
+     * Returns the status of this request, or <code>null</code>.
+     * 
+     * @return request status or <code>null</code>
+     */
+    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);
+}
--- /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();
+    }
+
+}
--- /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
+     * <code>null</code> if the underlying tree is not lazy and never contains
+     * duplicate elements.
+     * 
+     * @return the tree path (possibly empty) of the unchanged parent, or
+     *         <code>null</code>
+     */
+    public abstract TreePath getParentPath();
+
+    /**
+     * @param visitor
+     */
+    public void accept(TreeDiffVisitor visitor) {
+        doAccept(visitor, getParentPath());
+    }
+
+}
--- /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);
+            }
+        }
+    }
+
+}
--- /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 <code>true</code> if the tree diff's children should be
+     *         visited; <code>false</code> if they should be skipped.
+     */
+    public abstract bool visit(TreeDiffNode diff, TreePath currentPath);
+}
--- /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.
+ * <p>
+ * Clients may instantiate this class. Not intended to be subclassed.
+ * </p>
+ * 
+ * @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 <code>null</code> 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);
+    }
+}
--- /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 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+   <meta name="Author" content="IBM">
+   <meta name="GENERATOR" content="Mozilla/4.5 [en] (Win98; I) [Netscape]">
+   <title>Package-level Javadoc</title>
+</head>
+<body>
+To be written.
+<h2>
+Package Specification</h2>
+<p>
+This package provides classes that can be used to ...</p>
+</body>
+</html>
--- 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