# HG changeset patch # User Frank Benoit # Date 1240304151 -7200 # Node ID 6208d4f6a277cc326d82dd539eec562b93187576 # Parent 1d37a78138324ea702ab5d7e10db45bb452c5478 Added trees for databinding.beans and observable diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/BeansObservables.d --- /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 + * JavaBean + * specification 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 PropertyChangeEvents. This is done to + * provide the same behavior as is expected from arrays as specified in the + * bean spec in section 7.2. + * + * @param realm + * the realm + * @param bean + * the bean object + * @param propertyName + * the name of the property + * @param elementType + * type of the elements in the list. If null and + * the property is an array the type will be inferred. If + * null and the property type cannot be inferred + * element type will be null. + * @return an observable list tracking the collection-typed named property + * of the given bean object + */ + public static IObservableList observeList(Realm realm, Object bean, + String propertyName, Class elementType) { + PropertyDescriptor propertyDescriptor = getPropertyDescriptor(bean + .getClass(), propertyName); + elementType = getCollectionElementType(elementType, propertyDescriptor); + + return new JavaBeanObservableList(realm, bean, propertyDescriptor, + elementType); + } + + /** + * Returns an observable set in the given realm tracking the + * collection-typed named property of the given bean object + * + * @param realm + * the realm + * @param bean + * the bean object + * @param propertyName + * the name of the property + * @return an observable set tracking the collection-typed named property of + * the given bean object + */ + public static IObservableSet observeSet(Realm realm, Object bean, + String propertyName) { + return observeSet(realm, bean, propertyName, null); + } + + /** + * Returns a factory for creating obervable values tracking the given + * property of a particular bean object + * + * @param realm + * the realm to use + * @param propertyName + * the name of the property + * @return an observable value factory + */ + public static IObservableFactory valueFactory(final Realm realm, + final String propertyName) { + return new class() IObservableFactory { + public IObservable createObservable(Object target) { + return observeValue(realm, target, propertyName); + } + }; + } + + /** + * Returns a factory for creating obervable lists tracking the given + * property of a particular bean object + * + * @param realm + * the realm to use + * @param propertyName + * the name of the property + * @param elementType + * @return an observable list factory + */ + public static IObservableFactory listFactory(final Realm realm, + final String propertyName, final Class elementType) { + return new class() IObservableFactory { + public IObservable createObservable(Object target) { + return observeList(realm, target, propertyName, elementType); + } + }; + } + + /** + * Returns a factory for creating obervable sets tracking the given property + * of a particular bean object + * + * @param realm + * the realm to use + * @param propertyName + * the name of the property + * @return an observable set factory + */ + public static IObservableFactory setFactory(final Realm realm, + final String propertyName) { + return new class() IObservableFactory { + public IObservable createObservable(Object target) { + return observeSet(realm, target, propertyName); + } + }; + } + + /** + * Helper method for + * MasterDetailObservables.detailValue(master, valueFactory(realm, + propertyName), propertyType) + * + * @param realm + * @param master + * @param propertyName + * @param propertyType + * can be null + * @return an observable value that tracks the current value of the named + * property for the current value of the master observable value + * + * @see MasterDetailObservables + */ + public static IObservableValue observeDetailValue(Realm realm, + IObservableValue master, String propertyName, Class propertyType) { + + IObservableValue value = MasterDetailObservables.detailValue(master, + valueFactory(realm, propertyName), propertyType); + BeanObservableValueDecorator decorator = new BeanObservableValueDecorator( + value, master, getValueTypePropertyDescriptor(master, + propertyName)); + + return decorator; + } + + /** + * Helper method for + * MasterDetailObservables.detailValue(master, valueFactory(realm, + * propertyName), propertyType). + * This method returns an {@link IBeanObservable} with a + * {@link PropertyDescriptor} based on the given master type and property + * name. + * + * @param realm + * the realm + * @param master + * the master observable value, for example tracking the + * selection in a list + * @param masterType + * the type of the master observable value + * @param propertyName + * the property name + * @param propertyType + * can be null + * @return an observable value that tracks the current value of the named + * property for the current value of the master observable value + * + * @see MasterDetailObservables + * @since 1.1 + */ + public static IObservableValue observeDetailValue(Realm realm, + IObservableValue master, Class masterType, String propertyName, Class propertyType) { + Assert.isNotNull(masterType, "masterType cannot be null"); //$NON-NLS-1$ + IObservableValue value = MasterDetailObservables.detailValue(master, + valueFactory(realm, propertyName), propertyType); + BeanObservableValueDecorator decorator = new BeanObservableValueDecorator( + value, master, getPropertyDescriptor(masterType, + propertyName)); + + return decorator; + } + + /** + * Helper method for + * MasterDetailObservables.detailList(master, listFactory(realm, + propertyName, propertyType), propertyType) + * + * @param realm + * @param master + * @param propertyName + * @param propertyType + * can be null + * @return an observable list that tracks the named property for the current + * value of the master observable value + * + * @see MasterDetailObservables + */ + public static IObservableList observeDetailList(Realm realm, + IObservableValue master, String propertyName, Class propertyType) { + IObservableList observableList = MasterDetailObservables.detailList( + master, listFactory(realm, propertyName, propertyType), + propertyType); + BeanObservableListDecorator decorator = new BeanObservableListDecorator( + observableList, master, getValueTypePropertyDescriptor(master, + propertyName)); + + return decorator; + } + + /** + * Helper method for + * MasterDetailObservables.detailSet(master, setFactory(realm, + propertyName), propertyType) + * + * @param realm + * @param master + * @param propertyName + * @param propertyType + * can be null + * @return an observable set that tracks the named property for the current + * value of the master observable value + * + * @see MasterDetailObservables + */ + public static IObservableSet observeDetailSet(Realm realm, + IObservableValue master, String propertyName, Class propertyType) { + + IObservableSet observableSet = MasterDetailObservables.detailSet( + master, setFactory(realm, propertyName, propertyType), + propertyType); + BeanObservableSetDecorator decorator = new BeanObservableSetDecorator( + observableSet, master, getValueTypePropertyDescriptor(master, + propertyName)); + + return decorator; + } + + /** + * Helper method for + * MasterDetailObservables.detailMap(master, mapFactory(realm, propertyName)) + * + * @param realm + * the realm + * @param master + * @param propertyName + * @return an observable map that tracks the map-type named property for the + * current value of the master observable value. + * @since 1.1 + */ + public static IObservableMap observeDetailMap(Realm realm, + IObservableValue master, String propertyName) { + IObservableMap observableMap = MasterDetailObservables.detailMap( + master, mapPropertyFactory(realm, propertyName)); + BeanObservableMapDecorator decorator = new BeanObservableMapDecorator( + observableMap, master, getValueTypePropertyDescriptor(master, + propertyName)); + return decorator; + } + + /** + * @param realm + * @param bean + * @param propertyName + * @param elementType + * can be null + * @return an observable set that tracks the current value of the named + * property for given bean object + */ + public static IObservableSet observeSet(Realm realm, Object bean, + String propertyName, Class elementType) { + PropertyDescriptor propertyDescriptor = getPropertyDescriptor(bean + .getClass(), propertyName); + elementType = getCollectionElementType(elementType, propertyDescriptor); + + return new JavaBeanObservableSet(realm, bean, propertyDescriptor, + elementType); + } + + /** + * @param realm + * @param propertyName + * @param elementType + * can be null + * @return an observable set factory for creating observable sets + */ + public static IObservableFactory setFactory(final Realm realm, + final String propertyName, final Class elementType) { + return new class() IObservableFactory { + public IObservable createObservable(Object target) { + return observeSet(realm, target, propertyName, elementType); + } + }; + } + + /** + * Returns a factory for creating an observable map. The factory, when + * provided with an {@link IObservableSet}, will create an + * {@link IObservableMap} in the same realm as the underlying set that + * tracks the current values of the named property for the beans in the + * given set. + * + * @param beanClass + * the common base type of bean objects that may be in the set + * @param propertyName + * the name of the property + * @return a factory for creating {@link IObservableMap} objects + * + * @since 1.1 + */ + public static IObservableFactory setToMapFactory(final Class beanClass, final String propertyName) { + return new class() IObservableFactory { + public IObservable createObservable(Object target) { + return observeMap(cast(IObservableSet) target, beanClass, propertyName); + } + }; + } + + /** + * Returns a factory for creating an observable map. The factory, when + * provided with a bean object, will create an {@link IObservableMap} in the + * given realm that tracks the map-typed named property for the specified + * bean. + * + * @param realm + * the realm assigned to observables created by the returned + * factory. + * @param propertyName + * the name of the property + * @return a factory for creating {@link IObservableMap} objects. + * @since 1.1 + */ + public static IObservableFactory mapPropertyFactory(final Realm realm, + final String propertyName) { + return new class() IObservableFactory { + public IObservable createObservable(Object target) { + return observeMap(realm, target, propertyName); + } + }; + } + + /** + * @param elementType + * can be null + * @param propertyDescriptor + * @return type of the items in a collection/array property + */ + /*package*/ static Class getCollectionElementType(Class elementType, + PropertyDescriptor propertyDescriptor) { + if (elementType is null) { + Class propertyType = propertyDescriptor.getPropertyType(); + elementType = propertyType.isArray() ? propertyType + .getComponentType() : Object.class; + } + + return elementType; + } + + /** + * @param observable + * @param propertyName + * @return property descriptor or null + */ + /* package*/ static PropertyDescriptor getValueTypePropertyDescriptor( + IObservableValue observable, String propertyName) { + return (observable.getValueType() !is null) ? getPropertyDescriptor( + cast(Class) observable.getValueType(), propertyName) : null; + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/IBeanObservable.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/IBeanObservable.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2007 Brad Reynolds and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Brad Reynolds - initial API and implementation + * Brad Reynolds - bug 147515 + ******************************************************************************/ + +package org.eclipse.core.databinding.beans; + +import java.beans.PropertyDescriptor; + +import org.eclipse.core.databinding.observable.IObserving; + +/** + * Provides access to details of bean observables. + *

+ * This interface is not meant to be implemented by clients. + *

+ * + * @since 3.3 + */ +public interface IBeanObservable : IObserving { + /** + * @return property descriptor of the property being observed, + * null if the runtime time information was not + * provided on construction of the observable + */ + public PropertyDescriptor getPropertyDescriptor(); +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/PojoObservables.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/PojoObservables.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,433 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matthew Hall - bug 221704 + *******************************************************************************/ + +package org.eclipse.core.databinding.beans; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyDescriptor; + +import org.eclipse.core.databinding.observable.IObservable; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.map.IObservableMap; +import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; +import org.eclipse.core.databinding.observable.masterdetail.MasterDetailObservables; +import org.eclipse.core.databinding.observable.set.IObservableSet; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.internal.databinding.beans.BeanObservableListDecorator; +import org.eclipse.core.internal.databinding.beans.BeanObservableMapDecorator; +import org.eclipse.core.internal.databinding.beans.BeanObservableSetDecorator; +import org.eclipse.core.internal.databinding.beans.BeanObservableValueDecorator; +import org.eclipse.core.internal.databinding.beans.JavaBeanObservableList; +import org.eclipse.core.internal.databinding.beans.JavaBeanObservableMap; +import org.eclipse.core.internal.databinding.beans.JavaBeanObservableSet; +import org.eclipse.core.internal.databinding.beans.JavaBeanObservableValue; +import org.eclipse.core.internal.databinding.beans.JavaBeanPropertyObservableMap; + +/** + * A factory for creating observable objects for POJOs (plain old java objects) + * that conform to idea of an object with getters and setters but does not + * provide {@link PropertyChangeEvent property change events} on change. This + * factory is identical to {@link BeansObservables} except for this fact. + * + * @since 1.1 + */ +final public class PojoObservables { + + /** + * Returns an observable value in the default realm tracking the current + * value of the named property of the given pojo. + * + * @param pojo + * the object + * @param propertyName + * the name of the property + * @return an observable value tracking the current value of the named + * property of the given pojo + */ + public static IObservableValue observeValue(Object pojo, String propertyName) { + return observeValue(Realm.getDefault(), pojo, propertyName); + } + + /** + * Returns an observable value in the given realm tracking the current value + * of the named property of the given pojo. + * + * @param realm + * the realm + * @param pojo + * the object + * @param propertyName + * the name of the property + * @return an observable value tracking the current value of the named + * property of the given pojo + */ + public static IObservableValue observeValue(Realm realm, Object pojo, + String propertyName) { + + PropertyDescriptor descriptor = BeansObservables.getPropertyDescriptor( + pojo.getClass(), propertyName); + return new JavaBeanObservableValue(realm, pojo, descriptor, false); + } + + /** + * Returns an observable map in the default realm tracking the current + * values of the named property for the pojos in the given set. + * + * @param domain + * the set of pojo objects + * @param pojoClass + * the common base type of pojo objects that may be in the set + * @param propertyName + * the name of the property + * @return an observable map tracking the current values of the named + * property for the pojos in the given domain set + */ + public static IObservableMap observeMap(IObservableSet domain, + Class pojoClass, String propertyName) { + PropertyDescriptor descriptor = BeansObservables.getPropertyDescriptor( + pojoClass, propertyName); + return new JavaBeanObservableMap(domain, descriptor, false); + } + + /** + * Returns an array of observable maps in the default realm tracking the + * current values of the named propertys for the pojos in the given set. + * + * @param domain + * the set of objects + * @param pojoClass + * the common base type of objects that may be in the set + * @param propertyNames + * the array of property names + * @return an array of observable maps tracking the current values of the + * named propertys for the pojos in the given domain set + */ + public static IObservableMap[] observeMaps(IObservableSet domain, + Class pojoClass, String[] propertyNames) { + IObservableMap[] result = new IObservableMap[propertyNames.length]; + for (int i = 0; i < propertyNames.length; i++) { + result[i] = observeMap(domain, pojoClass, propertyNames[i]); + } + return result; + } + + /** + * Returns an observable map in the given realm tracking the map-typed named + * property of the given pojo object. + * + * @param realm + * the realm + * @param pojo + * the pojo object + * @param propertyName + * the name of the property + * @return an observable map tracking the map-typed named property of the + * given pojo object + */ + public static IObservableMap observeMap(Realm realm, Object pojo, + String propertyName) { + PropertyDescriptor descriptor = BeansObservables.getPropertyDescriptor( + pojo.getClass(), propertyName); + return new JavaBeanPropertyObservableMap(realm, pojo, descriptor, false); + } + + /** + * Returns an observable list in the given realm tracking the + * collection-typed named property of the given pojo object. The returned + * list is mutable. + * + * @param realm + * the realm + * @param pojo + * the object + * @param propertyName + * the name of the collection-typed property + * @return an observable list tracking the collection-typed named property + * of the given pojo object + * @see #observeList(Realm, Object, String, Class) + */ + public static IObservableList observeList(Realm realm, Object pojo, + String propertyName) { + return observeList(realm, pojo, propertyName, null); + } + + /** + * Returns an observable list in the given realm tracking the + * collection-typed named property of the given bean object. The returned + * list is mutable. When an item is added or removed the setter is invoked + * for the list on the parent bean to provide notification to other + * listeners via PropertyChangeEvents. This is done to + * provide the same behavior as is expected from arrays as specified in the + * bean spec in section 7.2. + * + * @param realm + * the realm + * @param pojo + * the bean object + * @param propertyName + * the name of the property + * @param elementType + * type of the elements in the list. If null and + * the property is an array the type will be inferred. If + * null and the property type cannot be inferred + * element type will be null. + * @return an observable list tracking the collection-typed named property + * of the given bean object + */ + public static IObservableList observeList(Realm realm, Object pojo, + String propertyName, Class elementType) { + PropertyDescriptor propertyDescriptor = BeansObservables + .getPropertyDescriptor(pojo.getClass(), propertyName); + elementType = BeansObservables.getCollectionElementType(elementType, + propertyDescriptor); + + return new JavaBeanObservableList(realm, pojo, propertyDescriptor, + elementType, false); + } + + /** + * Returns an observable set in the given realm tracking the + * collection-typed named property of the given pojo object. + * + * @param realm + * the realm + * @param pojo + * the pojo object + * @param propertyName + * the name of the property + * @return an observable set tracking the collection-typed named property of + * the given pojo object + */ + public static IObservableSet observeSet(Realm realm, Object pojo, + String propertyName) { + return observeSet(realm, pojo, propertyName, null); + } + + /** + * @param realm + * @param pojo + * @param propertyName + * @param elementType + * can be null + * @return an observable set that tracks the current value of the named + * property for given pojo object + */ + public static IObservableSet observeSet(Realm realm, Object pojo, + String propertyName, Class elementType) { + PropertyDescriptor propertyDescriptor = BeansObservables + .getPropertyDescriptor(pojo.getClass(), propertyName); + elementType = BeansObservables.getCollectionElementType(elementType, + propertyDescriptor); + + return new JavaBeanObservableSet(realm, pojo, propertyDescriptor, + elementType, false); + } + + /** + * Returns a factory for creating obervable values tracking the given + * property of a particular pojo object + * + * @param realm + * the realm to use + * @param propertyName + * the name of the property + * @return an observable value factory + */ + public static IObservableFactory valueFactory(final Realm realm, + final String propertyName) { + return new class() IObservableFactory { + public IObservable createObservable(Object target) { + return observeValue(realm, target, propertyName); + } + }; + } + + /** + * Returns a factory for creating obervable lists tracking the given + * property of a particular pojo object + * + * @param realm + * the realm to use + * @param propertyName + * the name of the property + * @param elementType + * @return an observable list factory + */ + public static IObservableFactory listFactory(final Realm realm, + final String propertyName, final Class elementType) { + return new class() IObservableFactory { + public IObservable createObservable(Object target) { + return observeList(realm, target, propertyName, elementType); + } + }; + } + + /** + * Returns a factory for creating obervable sets tracking the given property + * of a particular pojo object + * + * @param realm + * the realm to use + * @param propertyName + * the name of the property + * @return an observable set factory + */ + public static IObservableFactory setFactory(final Realm realm, + final String propertyName) { + return new class() IObservableFactory { + public IObservable createObservable(Object target) { + return observeSet(realm, target, propertyName); + } + }; + } + + /** + * @param realm + * @param propertyName + * @param elementType + * can be null + * @return an observable set factory for creating observable sets + */ + public static IObservableFactory setFactory(final Realm realm, + final String propertyName, final Class elementType) { + return new class() IObservableFactory { + public IObservable createObservable(Object target) { + return observeSet(realm, target, propertyName, elementType); + } + }; + } + + /** + * Returns a factory for creating an observable map. The factory, when + * provided with a pojo object, will create an {@link IObservableMap} in the + * given realm that tracks the map-typed named property for the specified + * pojo. + * + * @param realm + * the realm assigned to observables created by the returned + * factory. + * @param propertyName + * the name of the property + * @return a factory for creating {@link IObservableMap} objects. + */ + public static IObservableFactory mapPropertyFactory(final Realm realm, + final String propertyName) { + return new class() IObservableFactory { + public IObservable createObservable(Object target) { + return observeMap(realm, target, propertyName); + } + }; + } + + /** + * Helper method for + * MasterDetailObservables.detailValue(master, valueFactory(realm, + propertyName), propertyType) + * + * @param realm + * @param master + * @param propertyName + * @param propertyType + * can be null + * @return an observable value that tracks the current value of the named + * property for the current value of the master observable value + * + * @see MasterDetailObservables + */ + public static IObservableValue observeDetailValue(Realm realm, + IObservableValue master, String propertyName, Class propertyType) { + + IObservableValue value = MasterDetailObservables.detailValue(master, + valueFactory(realm, propertyName), propertyType); + BeanObservableValueDecorator decorator = new BeanObservableValueDecorator( + value, master, BeansObservables.getValueTypePropertyDescriptor( + master, propertyName)); + + return decorator; + } + + /** + * Helper method for + * MasterDetailObservables.detailList(master, listFactory(realm, + propertyName, propertyType), propertyType) + * + * @param realm + * @param master + * @param propertyName + * @param propertyType + * can be null + * @return an observable list that tracks the named property for the current + * value of the master observable value + * + * @see MasterDetailObservables + */ + public static IObservableList observeDetailList(Realm realm, + IObservableValue master, String propertyName, Class propertyType) { + IObservableList observableList = MasterDetailObservables.detailList( + master, listFactory(realm, propertyName, propertyType), + propertyType); + BeanObservableListDecorator decorator = new BeanObservableListDecorator( + observableList, master, BeansObservables + .getValueTypePropertyDescriptor(master, propertyName)); + + return decorator; + } + + /** + * Helper method for + * MasterDetailObservables.detailSet(master, setFactory(realm, + propertyName), propertyType) + * + * @param realm + * @param master + * @param propertyName + * @param propertyType + * can be null + * @return an observable set that tracks the named property for the current + * value of the master observable value + * + * @see MasterDetailObservables + */ + public static IObservableSet observeDetailSet(Realm realm, + IObservableValue master, String propertyName, Class propertyType) { + + IObservableSet observableSet = MasterDetailObservables.detailSet( + master, setFactory(realm, propertyName, propertyType), + propertyType); + BeanObservableSetDecorator decorator = new BeanObservableSetDecorator( + observableSet, master, BeansObservables + .getValueTypePropertyDescriptor(master, propertyName)); + + return decorator; + } + + /** + * Helper method for + * MasterDetailObservables.detailMap(master, mapFactory(realm, propertyName)) + * + * @param realm + * @param master + * @param propertyName + * @return an observable map that tracks the map-type named property for the + * current value of the master observable value. + */ + public static IObservableMap observeDetailMap(Realm realm, + IObservableValue master, String propertyName) { + IObservableMap observableMap = MasterDetailObservables.detailMap( + master, mapPropertyFactory(realm, propertyName)); + BeanObservableMapDecorator decorator = new BeanObservableMapDecorator( + observableMap, master, BeansObservables + .getValueTypePropertyDescriptor(master, propertyName)); + return decorator; + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/package.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/package.html Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,16 @@ + + + + + + + Package-level Javadoc + + +Provides classes for observing JavaBeans(tm) objects. +

+Package Specification

+

+This package provides classes that can be used to observe objects that conform to the JavaBean specification for bound properties.

+ + diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/BeanObservableListDecorator.d --- /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; + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/BeanObservableMapDecorator.d --- /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(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/BeanObservableSetDecorator.d --- /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; + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/BeanObservableValueDecorator.d --- /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; + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/IdentityWrapper.d --- /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); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/JavaBeanObservableList.d --- /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; + } + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/JavaBeanObservableMap.d --- /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; + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/JavaBeanObservableSet.d --- /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(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/JavaBeanObservableValue.d --- /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(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/JavaBeanPropertyObservableMap.d --- /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(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/beans/ListenerSupport.d --- /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 PropertyChangeEvent events + * for a set of target JavaBeans + * + * @since 1.0 + */ +public class ListenerSupport { + + private Set elementsListenedTo = new HashSet(); + + private PropertyChangeListener listener; + + private String propertyName; + + /** + * Constructs a new instance. + * + * @param listener is the callback that will be called + * when a PropertyChangeEvent is fired on any + * of the target objects. Will only receive change events + * when the provided propertyName changes. + * @param propertyName + */ + public this(final PropertyChangeListener listener, + final String propertyName) { + Assert.isNotNull(listener, "Listener cannot be null"); //$NON-NLS-1$ + Assert.isNotNull(propertyName, "Property name cannot be null"); //$NON-NLS-1$ + + this.propertyName = propertyName; + this.listener = new class() PropertyChangeListener { + public void propertyChange(PropertyChangeEvent evt) { + if (propertyName.equals(evt.getPropertyName())) { + listener.propertyChange(evt); + } + } + }; + } + + /** + * Start listen to target (if it supports the JavaBean property change listener pattern) + * + * @param target + */ + public void hookListener(Object target) { + if (processListener( + "addPropertyChangeListener", "Could not attach listener to ", target)) { //$NON-NLS-1$ //$NON-NLS-2$ + elementsListenedTo.add(new IdentityWrapper(target)); + } + } + + /** + * Add listeners for new targets (those this instance ofListenerSupport does not + * already listen to), + * Stop to listen to those object that this instance listen to and is one of the object in targets + * + * @param targets + */ + public void setHookTargets(Object[] targets) { + Set elementsToUnhook = new HashSet(elementsListenedTo); + if (targets!isnull) { + for (int i = 0; i < targets.length; i++) { + Object newValue = targets[i]; + IdentityWrapper identityWrapper = new IdentityWrapper(newValue); + if(!elementsToUnhook.remove(identityWrapper)) + hookListener(newValue); + } + } + + for (Iterator it = elementsToUnhook.iterator(); it.hasNext();) { + Object o = it.next(); + if (o.getClass()!isIdentityWrapper.class) + o = new IdentityWrapper(o); + elementsListenedTo.remove(o); + unhookListener(o); + } + } + + /** + * Stop listen to target + * + * @param target + */ + public void unhookListener(Object target) { + if (target.getClass() is IdentityWrapper.class) + target = (cast(IdentityWrapper) target).unwrap(); + + if (processListener( + "removePropertyChangeListener", "Cound not remove listener from ", target)) { //$NON-NLS-1$//$NON-NLS-2$ + elementsListenedTo.remove(new IdentityWrapper(target)); + } + } + + + /** + * + */ + public void dispose() { + if (elementsListenedTo!isnull) { + Object[] targets = elementsListenedTo.toArray(); + for (int i = 0; i < targets.length; i++) { + unhookListener(targets[i]); + } + elementsListenedTo=null; + listener=null; + } + } + + /** + * @return elements that were registred to + */ + public Object[] getHookedTargets() { + Object[] targets = null; + if (elementsListenedTo!isnull && elementsListenedTo.size()>0) { + Object[] identityList = elementsListenedTo.toArray(); + targets = new Object[identityList.length]; + for (int i = 0; i < identityList.length; i++) + targets[i]=(cast(IdentityWrapper)identityList[i]).unwrap(); + } + return targets; + } + + /** + * Invokes the method for the provided methodName attempting + * to first use the method with the property name and then the unnamed + * version. + * + * @param methodName + * either addPropertyChangeListener or + * removePropertyChangeListener + * @param message + * string that will be prefixed to the target in an error message + * @param target + * object to invoke the method on + * @return true if the method was invoked successfully + */ + private bool processListener(String methodName, String message, + Object target) { + Method method = null; + Object[] parameters = null; + + try { + try { + method = target.getClass().getMethod( + methodName, + new Class[] { String.class, + PropertyChangeListener.class }); + + parameters = new Object[] { propertyName, listener }; + } catch (NoSuchMethodException e) { + method = target.getClass().getMethod(methodName, + new Class[] { PropertyChangeListener.class }); + + parameters = new Object[] { listener }; + } + } catch (SecurityException e) { + // ignore + } catch (NoSuchMethodException e) { + log(IStatus.WARNING, message + target, e); + } + + if (method !is null) { + if (!method.isAccessible()) { + method.setAccessible(true); + } + try { + method.invoke(target, parameters); + return true; + } catch (IllegalArgumentException e) { + log(IStatus.WARNING, message + target, e); + } catch (IllegalAccessException e) { + log(IStatus.WARNING, message + target, e); + } catch (InvocationTargetException e) { + log(IStatus.WARNING, message + target, e); + } + } + return false; + } + + /** + * Logs a message to the Data Binding logger. + */ + private void log(int severity, String message, Throwable throwable) { + if cast(BeansObservables.DEBUG) { + Policy.getLog().log( + new Status(severity, Policy.JFACE_DATABINDING, IStatus.OK, + message, throwable)); + } + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/AbstractObservable.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/AbstractObservable.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 164653 + * Matthew Hall - bug 118516 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.AssertionFailedException; + +/** + * @since 1.0 + */ +public abstract class AbstractObservable : ChangeManager , IObservable { + + /** + * @param realm + */ + public this(Realm realm) { + super(realm); + } + + public synchronized void addChangeListener(IChangeListener listener) { + addListener(ChangeEvent.TYPE, listener); + } + + public synchronized void removeChangeListener(IChangeListener listener) { + removeListener(ChangeEvent.TYPE, listener); + } + + public synchronized void addStaleListener(IStaleListener listener) { + addListener(StaleEvent.TYPE, listener); + } + + public synchronized void removeStaleListener(IStaleListener listener) { + removeListener(StaleEvent.TYPE, listener); + } + + protected void fireChange() { + checkRealm(); + fireEvent(new ChangeEvent(this)); + } + + protected void fireStale() { + checkRealm(); + fireEvent(new StaleEvent(this)); + } + + /** + * + */ + public synchronized void dispose() { + super.dispose(); + } + + /** + * Asserts that the realm is the current realm. + * + * @see Realm#isCurrent() + * @throws AssertionFailedException if the realm is not the current realm + */ + protected void checkRealm() { + Assert.isTrue(getRealm().isCurrent(), + "This operation must be run within the observable's realm"); //$NON-NLS-1$ + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ChangeEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ChangeEvent.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable; + +/** + * Generic change event denoting that the state of an {@link IObservable} object + * has changed. This event does not carry information about the kind of change + * that occurred. + * + * @since 1.0 + * + */ +public class ChangeEvent : ObservableEvent { + + /** + * + */ + private static final long serialVersionUID = -3241193109844979384L; + static final Object TYPE = new Object(); + + /** + * Creates a new change event object. + * + * @param source + * the observable that changed state + */ + public this(IObservable source) { + super(source); + } + + protected void dispatch(IObservablesListener listener) { + (cast(IChangeListener) listener).handleChange(this); + } + + protected Object getListenerType() { + return TYPE; + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ChangeManager.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ChangeManager.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,162 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matthew Hall - bug 118516 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.ListenerList; + +/** + * Listener management implementation. Exposed to subclasses in form of + * {@link AbstractObservable} and {@link ChangeSupport}. + * + * @since 1.0 + * + */ +/* package */ class ChangeManager { + + ListenerList[] listenerLists = null; + Object listenerTypes[] = null; + private Realm realm; + + /** + * @param realm + * + */ + /* package */ ChangeManager(Realm realm) { + Assert.isNotNull(realm, "Realm cannot be null"); //$NON-NLS-1$ + this.realm = realm; + } + + /** + * @param listenerType + * @param listener + */ + protected void addListener(Object listenerType, + IObservablesListener listener) { + int listenerTypeIndex = findListenerTypeIndex(listenerType); + if (listenerTypeIndex is -1) { + int length; + if (listenerTypes is null) { + length = 0; + listenerTypes = new Object[1]; + listenerLists = new ListenerList[1]; + } else { + length = listenerTypes.length; + System.arraycopy(listenerTypes, 0, + listenerTypes = new Object[length + 1], 0, length); + System + .arraycopy(listenerLists, 0, + listenerLists = new ListenerList[length + 1], + 0, length); + } + listenerTypes[length] = listenerType; + listenerLists[length] = new ListenerList(); + bool hadListeners = hasListeners(); + listenerLists[length].add(listener); + if (!hadListeners) { + this.firstListenerAdded(); + } + return; + } + ListenerList listenerList = listenerLists[listenerTypeIndex]; + bool hadListeners = true; + if (listenerList.size() is 0) { + hadListeners = hasListeners(); + } + listenerList.add(listener); + if (!hadListeners) { + firstListenerAdded(); + } + } + + /** + * @param listenerType + * @param listener + */ + protected void removeListener(Object listenerType, + IObservablesListener listener) { + int listenerTypeIndex = findListenerTypeIndex(listenerType); + if (listenerTypeIndex !is -1) { + listenerLists[listenerTypeIndex].remove(listener); + if (listenerLists[listenerTypeIndex].size() is 0) { + if (!hasListeners()) { + this.lastListenerRemoved(); + } + } + } + } + + protected bool hasListeners() { + if (listenerTypes is null) { + return false; + } + for (int i = 0; i < listenerTypes.length; i++) { + if (listenerLists[i].size() > 0) { + return true; + } + } + return false; + } + + private int findListenerTypeIndex(Object listenerType) { + if (listenerTypes !is null) { + for (int i = 0; i < listenerTypes.length; i++) { + if (listenerTypes[i] is listenerType) { + return i; + } + } + } + return -1; + } + + protected void fireEvent(ObservableEvent event) { + Object listenerType = event.getListenerType(); + int listenerTypeIndex = findListenerTypeIndex(listenerType); + if (listenerTypeIndex !is -1) { + Object[] listeners = listenerLists[listenerTypeIndex] + .getListeners(); + for (int i = 0; i < listeners.length; i++) { + event.dispatch(cast(IObservablesListener) listeners[i]); + } + } + } + + /** + * + */ + protected void firstListenerAdded() { + } + + /** + * + */ + protected void lastListenerRemoved() { + } + + /** + * + */ + public void dispose() { + listenerLists = null; + listenerTypes = null; + realm = null; + } + + /** + * @return Returns the realm. + */ + public Realm getRealm() { + return realm; + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ChangeSupport.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ChangeSupport.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable; + +/** + * @since 1.0 + * + */ +public abstract class ChangeSupport : ChangeManager { + + /** + * @param realm + */ + public this(Realm realm) { + super(realm); + } + + public void addListener(Object listenerType, + IObservablesListener listener) { + super.addListener(listenerType, listener); + } + + public void removeListener(Object listenerType, + IObservablesListener listener) { + super.removeListener(listenerType, listener); + } + + public void fireEvent(ObservableEvent event) { + super.fireEvent(event); + } + + /** + * + */ + protected abstract void firstListenerAdded(); + + /** + * + */ + protected abstract void lastListenerRemoved(); + + /** + * @param listener + */ + public void addChangeListener(IChangeListener listener) { + addListener(ChangeEvent.TYPE, listener); + } + + /** + * @param listener + */ + public void removeChangeListener(IChangeListener listener) { + removeListener(ChangeEvent.TYPE, listener); + } + + /** + * @param listener + */ + public void addStaleListener(IStaleListener listener) { + addListener(StaleEvent.TYPE, listener); + } + + /** + * @param listener + */ + public void removeStaleListener(IStaleListener listener) { + removeListener(StaleEvent.TYPE, listener); + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/Diffs.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/Diffs.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,470 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matthew Hall - bug 226216 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import org.eclipse.core.databinding.observable.list.ListDiff; +import org.eclipse.core.databinding.observable.list.ListDiffEntry; +import org.eclipse.core.databinding.observable.map.MapDiff; +import org.eclipse.core.databinding.observable.set.SetDiff; +import org.eclipse.core.databinding.observable.value.ValueDiff; +import org.eclipse.core.internal.databinding.Util; + +/** + * @since 1.0 + * + */ +public class Diffs { + + /** + * @param oldList + * @param newList + * @return the differences between oldList and newList + */ + public static ListDiff computeListDiff(List oldList, List newList) { + List diffEntries = new ArrayList(); + createListDiffs(new ArrayList(oldList), newList, diffEntries); + ListDiff listDiff = createListDiff(cast(ListDiffEntry[]) diffEntries + .toArray(new ListDiffEntry[diffEntries.size()])); + return listDiff; + } + + /** + * adapted from EMF's ListDifferenceAnalyzer + */ + private static void createListDiffs(List oldList, List newList, + List listDiffs) { + int index = 0; + for (Iterator it = newList.iterator(); it.hasNext();) { + Object newValue = it.next(); + if (oldList.size() <= index) { + // append newValue to newList + listDiffs.add(createListDiffEntry(index, true, newValue)); + } else { + bool done; + do { + done = true; + Object oldValue = oldList.get(index); + if (oldValue is null ? newValue !is null : !oldValue.equals(newValue)) { + int oldIndexOfNewValue = listIndexOf(oldList, newValue, index); + if (oldIndexOfNewValue !is -1) { + int newIndexOfOldValue = listIndexOf(newList, oldValue, index); + if (newIndexOfOldValue is -1) { + // removing oldValue from list[index] + listDiffs.add(createListDiffEntry(index, false, oldValue)); + oldList.remove(index); + done = false; + } else if (newIndexOfOldValue > oldIndexOfNewValue) { + // moving oldValue from list[index] to [newIndexOfOldValue] + if (oldList.size() <= newIndexOfOldValue) { + // The element cannot be moved to the correct index + // now, however later iterations will insert elements + // in front of it, eventually moving it into the + // correct spot. + newIndexOfOldValue = oldList.size() - 1; + } + listDiffs.add(createListDiffEntry(index, false, oldValue)); + oldList.remove(index); + listDiffs.add(createListDiffEntry(newIndexOfOldValue, true, oldValue)); + oldList.add(newIndexOfOldValue, oldValue); + done = false; + } else { + // move newValue from list[oldIndexOfNewValue] to [index] + listDiffs.add(createListDiffEntry(oldIndexOfNewValue, false, newValue)); + oldList.remove(oldIndexOfNewValue); + listDiffs.add(createListDiffEntry(index, true, newValue)); + oldList.add(index, newValue); + } + } else { + // add newValue at list[index] + oldList.add(index, newValue); + listDiffs.add(createListDiffEntry(index, true, newValue)); + } + } + } while (!done); + } + ++index; + } + for (int i = oldList.size(); i > index;) { + // remove excess trailing elements not present in newList + listDiffs.add(createListDiffEntry(--i, false, oldList.get(i))); + } + } + + /** + * @param list + * @param object + * @param index + * @return the index, or -1 if not found + */ + private static int listIndexOf(List list, Object object, int index) { + int size = list.size(); + for (int i=index; inull -- allowing for + * null. + * + * @param left + * The left object to compare; may be null. + * @param right + * The right object to compare; may be null. + * @return true if the two objects are equivalent; + * false otherwise. + */ + public static final override equals_t opEquals(final Object left, final Object right) { + return left is null ? right is null : ((right !is null) && left + .equals(right)); + } + + /** + * @param oldSet + * @param newSet + * @return a set diff + */ + public static SetDiff computeSetDiff(Set oldSet, Set newSet) { + Set additions = new HashSet(newSet); + additions.removeAll(oldSet); + Set removals = new HashSet(oldSet); + removals.removeAll(newSet); + return createSetDiff(additions, removals); + } + + /** + * Computes the difference between two maps. + * + * @param oldMap + * @param newMap + * @return a map diff representing the changes needed to turn oldMap into + * newMap + */ + public static MapDiff computeMapDiff(Map oldMap, Map newMap) { + // starts out with all keys from the new map, we will remove keys from + // the old map as we go + final Set addedKeys = new HashSet(newMap.keySet()); + final Set removedKeys = new HashSet(); + final Set changedKeys = new HashSet(); + final Map oldValues = new HashMap(); + final Map newValues = new HashMap(); + for (Iterator it = oldMap.entrySet().iterator(); it.hasNext();) { + Map.Entry oldEntry = cast(Entry) it.next(); + Object oldKey = oldEntry.getKey(); + if (addedKeys.remove(oldKey)) { + // potentially changed key since it is in oldMap and newMap + Object oldValue = oldEntry.getValue(); + Object newValue = newMap.get(oldKey); + if (!Util.equals(oldValue, newValue)) { + changedKeys.add(oldKey); + oldValues.put(oldKey, oldValue); + newValues.put(oldKey, newValue); + } + } else { + removedKeys.add(oldKey); + oldValues.put(oldKey, oldEntry.getValue()); + } + } + for (Iterator it = addedKeys.iterator(); it.hasNext();) { + Object newKey = it.next(); + newValues.put(newKey, newMap.get(newKey)); + } + return new class() MapDiff { + public Set getAddedKeys() { + return addedKeys; + } + + public Set getChangedKeys() { + return changedKeys; + } + + public Set getRemovedKeys() { + return removedKeys; + } + + public Object getNewValue(Object key) { + return newValues.get(key); + } + + public Object getOldValue(Object key) { + return oldValues.get(key); + } + }; + } + + /** + * @param oldValue + * @param newValue + * @return a value diff + */ + public static ValueDiff createValueDiff(final Object oldValue, + final Object newValue) { + return new class() ValueDiff { + + public Object getOldValue() { + return oldValue; + } + + public Object getNewValue() { + return newValue; + } + }; + } + + /** + * @param additions + * @param removals + * @return a set diff + */ + public static SetDiff createSetDiff(Set additions, Set removals) { + final Set unmodifiableAdditions = Collections + .unmodifiableSet(additions); + final Set unmodifiableRemovals = Collections.unmodifiableSet(removals); + return new class() SetDiff { + + public Set getAdditions() { + return unmodifiableAdditions; + } + + public Set getRemovals() { + return unmodifiableRemovals; + } + }; + } + + /** + * @param difference + * @return a list diff with one differing entry + */ + public static ListDiff createListDiff(ListDiffEntry difference) { + return createListDiff(new ListDiffEntry[] { difference }); + } + + /** + * @param difference1 + * @param difference2 + * @return a list diff with two differing entries + */ + public static ListDiff createListDiff(ListDiffEntry difference1, + ListDiffEntry difference2) { + return createListDiff(new ListDiffEntry[] { difference1, difference2 }); + } + + /** + * @param differences + * @return a list diff with the given entries + */ + public static ListDiff createListDiff(final ListDiffEntry[] differences) { + return new class() ListDiff { + public ListDiffEntry[] getDifferences() { + return differences; + } + }; + } + + /** + * @param position + * @param isAddition + * @param element + * @return a list diff entry + */ + public static ListDiffEntry createListDiffEntry(final int position, + final bool isAddition, final Object element) { + return new class() ListDiffEntry { + + public int getPosition() { + return position; + } + + public bool isAddition() { + return isAddition; + } + + public Object getElement() { + return element; + } + }; + } + + /** + * @param addedKey + * @param newValue + * @return a map diff + */ + public static MapDiff createMapDiffSingleAdd(final Object addedKey, + final Object newValue) { + return new class() MapDiff { + + public Set getAddedKeys() { + return Collections.singleton(addedKey); + } + + public Set getChangedKeys() { + return Collections.EMPTY_SET; + } + + public Object getNewValue(Object key) { + return newValue; + } + + public Object getOldValue(Object key) { + return null; + } + + public Set getRemovedKeys() { + return Collections.EMPTY_SET; + } + }; + } + + /** + * @param existingKey + * @param oldValue + * @param newValue + * @return a map diff + */ + public static MapDiff createMapDiffSingleChange(final Object existingKey, + final Object oldValue, final Object newValue) { + return new class() MapDiff { + + public Set getAddedKeys() { + return Collections.EMPTY_SET; + } + + public Set getChangedKeys() { + return Collections.singleton(existingKey); + } + + public Object getNewValue(Object key) { + return newValue; + } + + public Object getOldValue(Object key) { + return oldValue; + } + + public Set getRemovedKeys() { + return Collections.EMPTY_SET; + } + }; + } + + /** + * @param removedKey + * @param oldValue + * @return a map diff + */ + public static MapDiff createMapDiffSingleRemove(final Object removedKey, + final Object oldValue) { + return new class() MapDiff { + + public Set getAddedKeys() { + return Collections.EMPTY_SET; + } + + public Set getChangedKeys() { + return Collections.EMPTY_SET; + } + + public Object getNewValue(Object key) { + return null; + } + + public Object getOldValue(Object key) { + return oldValue; + } + + public Set getRemovedKeys() { + return Collections.singleton(removedKey); + } + }; + } + + /** + * @param copyOfOldMap + * @return a map diff + */ + public static MapDiff createMapDiffRemoveAll(final Map copyOfOldMap) { + return new class() MapDiff { + + public Set getAddedKeys() { + return Collections.EMPTY_SET; + } + + public Set getChangedKeys() { + return Collections.EMPTY_SET; + } + + public Object getNewValue(Object key) { + return null; + } + + public Object getOldValue(Object key) { + return copyOfOldMap.get(key); + } + + public Set getRemovedKeys() { + return copyOfOldMap.keySet(); + } + }; + } + + /** + * @param addedKeys + * @param removedKeys + * @param changedKeys + * @param oldValues + * @param newValues + * @return a map diff + */ + public static MapDiff createMapDiff(final Set addedKeys, + final Set removedKeys, final Set changedKeys, final Map oldValues, + final Map newValues) { + return new class() MapDiff { + + public Set getAddedKeys() { + return addedKeys; + } + + public Set getChangedKeys() { + return changedKeys; + } + + public Object getNewValue(Object key) { + return newValues.get(key); + } + + public Object getOldValue(Object key) { + return oldValues.get(key); + } + + public Set getRemovedKeys() { + return removedKeys; + } + }; + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IChangeListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IChangeListener.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable; + +import org.eclipse.core.databinding.observable.list.IListChangeListener; +import org.eclipse.core.databinding.observable.map.IMapChangeListener; +import org.eclipse.core.databinding.observable.set.ISetChangeListener; +import org.eclipse.core.databinding.observable.value.IValueChangeListener; + +/** + * Listener for generic change events. Note that the change events do not carry + * information about the change, they only specify the affected observable. To + * listen for specific change events, use more specific change listeners. + * + * @see IValueChangeListener + * @see IListChangeListener + * @see ISetChangeListener + * @see IMapChangeListener + * + * @since 1.0 + */ +public interface IChangeListener : IObservablesListener { + + /** + * Handle a generic change to the given observable. The given event object + * must only be used locally in this method because it may be reused for + * other change notifications. + * + * @param event + */ + public void handleChange(ChangeEvent event); + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObservable.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObservable.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2005, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.databinding.observable; + +/** + * An object with state that allows to listen for state changes. + * + *

+ * 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}. + *

+ * + * @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. + *

+ * 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. + *

+ * + * @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 not + * 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(); +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObservableCollection.d --- /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. + *

+ * + * @since 1.0 + */ +public interface IObservableCollection : IObservable, Collection { + + /** + * @return the element type of this observable value, or null + * if this observable collection is untyped. + */ + Object getElementType(); + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObservablesListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObservablesListener.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable; + +/** + * Marker interface for all listener types in the observables framework. + * + * @noimplement This interface is not intended to be implemented by clients. + * + * @since 1.0 + */ +public interface IObservablesListener { + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObserving.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IObserving.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.core.databinding.observable; + +/** + * + * Mixin interface for IObservables that observe other objects. + * + * @since 1.0 + * + */ +public interface IObserving { + + /** + * Returns the observed object, or null if this observing + * object does not currently observe an object. + * + * @return the observed object, or null + */ + public Object getObserved(); + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IStaleListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/IStaleListener.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable; + +/** + * Listener for staleness events. An observable object is stale if its state + * will change eventually. + * + * @since 1.0 + */ +public interface IStaleListener : IObservablesListener { + + /** + * Handle the event that the given observable object is now stale. The given + * event object must only be used locally in this method because it may be + * reused for other change notifications. + * + * @param staleEvent + */ + public void handleStale(StaleEvent staleEvent); + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ObservableEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ObservableEvent.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable; + +import java.util.EventObject; + +/** + * Abstract event object for events fired by {@link IObservable} objects. All + * events fired by observables must be derived from this class so that the way + * of dispatching events can be improved in later versions of the framework. + * + * @since 1.0 + * + */ +public abstract class ObservableEvent : EventObject { + + /** + * Creates a new observable event. + * + * @param source + */ + public this(IObservable source) { + super(source); + } + + /** + * + */ + private static final long serialVersionUID = 7693906965267871813L; + + /** + * Returns the observable that generated this event. + * + * @return the observable that generated this event + */ + public IObservable getObservable() { + return cast(IObservable) getSource(); + } + + /** + * Dispatch this event to the given listener. Subclasses must implement this + * method by calling the appropriate type-safe event handling method on the + * given listener according to the type of this event. + * + * @param listener + * the listener that should handle the event + */ + protected abstract void dispatch(IObservablesListener listener); + + /** + * Returns a unique object used for distinguishing this event type from + * others. + * + * @return a unique object representing the concrete type of this event. + */ + protected abstract Object getListenerType(); + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ObservableTracker.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/ObservableTracker.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,200 @@ +/******************************************************************************* + * Copyright (c) 2005, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matthew Hall - Fix NPE, more detailed assert messages (bug 210115) + *******************************************************************************/ +package org.eclipse.core.databinding.observable; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.eclipse.core.internal.databinding.IdentityWrapper; +import org.eclipse.core.runtime.Assert; + +/** + * This class makes it possible to monitor whenever an IObservable is read from. + * This can be used to automatically attach and remove listeners. How to use it: + * + *

+ * If you are implementing an IObservable, invoke getterCalled(this) whenever a + * getter is called - that is, whenever your observable is read from. You only + * need to do this once per method call. If one getter delegates to another, the + * outer getter doesn't need to call the method since the inner one will. + *

+ * + *

+ * If you want to determine what observables were used in a particular block of + * code, call runAndMonitorcast(Runnable). This will execute the given runnable and + * return the set of observables that were read from. + *

+ * + *

+ * This can be used to automatically attach listeners. For example, imagine you + * have a block of code that updates some widget by reading from a bunch of + * observables. Whenever one of those observables changes, you want to re-run + * the code and cause the widget to be refreshed. You could do this in the + * traditional manner by attaching one listener to each observable and + * re-running your widget update code whenever one of them changes, but this + * code is repetitive and requires updating the listener code whenever you + * refactor the widget updating code. + *

+ * + *

+ * Alternatively, you could use a utility class that runs the code in a + * runAndMonitor block and automatically attach listeners to any observable used + * in updating the widget. The advantage of the latter approach is that it, + * eliminates the code for attaching and detaching listeners and will always + * stay in synch with changes to the widget update logic. + *

+ * + * @since 1.0 + */ +public class ObservableTracker { + + /** + * Threadlocal storage pointing to the current Set of IObservables, or null + * if none. Note that this is actually the top of a stack. Whenever a method + * changes the current value, it remembers the old value as a local variable + * and restores the old value when the method exits. + */ + private static ThreadLocal currentChangeListener = new ThreadLocal(); + + private static ThreadLocal currentStaleListener = new ThreadLocal(); + + private static ThreadLocal currentObservableSet = new ThreadLocal(); + + /** + * Invokes the given runnable, and returns the set of IObservables that were + * read by the runnable. If the runnable calls this method recursively, the + * result will not contain IObservables that were used within the inner + * runnable. + * + * @param runnable + * runnable to execute + * @param changeListener + * listener to register with all accessed observables + * @param staleListener + * listener to register with all accessed observables, or + * null if no stale listener is to be registered + * @return an array of unique observable objects + */ + public static IObservable[] runAndMonitor(Runnable runnable, + IChangeListener changeListener, IStaleListener staleListener) { + // Remember the previous value in the listener stack + Set lastObservableSet = cast(Set) currentObservableSet.get(); + IChangeListener lastChangeListener = cast(IChangeListener) currentChangeListener + .get(); + IStaleListener lastStaleListener = cast(IStaleListener) currentStaleListener + .get(); + + Set observableSet = new HashSet(); + // Push the new listeners to the top of the stack + currentObservableSet.set(observableSet); + currentChangeListener.set(changeListener); + currentStaleListener.set(staleListener); + try { + runnable.run(); + } finally { + // Pop the new listener off the top of the stack (by restoring the + // previous listener) + currentObservableSet.set(lastObservableSet); + currentChangeListener.set(lastChangeListener); + currentStaleListener.set(lastStaleListener); + } + + int i = 0; + IObservable[] result = new IObservable[observableSet.size()]; + for (Iterator it = observableSet.iterator(); it.hasNext();) { + IdentityWrapper wrapper = cast(IdentityWrapper) it.next(); + result[i++] = cast(IObservable) wrapper.unwrap(); + } + + return result; + } + + /** + * Runs the given runnable without tracking dependencies. + * @param runnable + * + * @since 1.1 + */ + public static void runAndIgnore(Runnable runnable) { + // Remember the previous value in the listener stack + Set lastObservableSet = cast(Set) currentObservableSet.get(); + IChangeListener lastChangeListener = cast(IChangeListener) currentChangeListener + .get(); + IStaleListener lastStaleListener = cast(IStaleListener) currentStaleListener + .get(); + currentObservableSet.set(null); + currentChangeListener.set(null); + currentStaleListener.set(null); + try { + runnable.run(); + } finally { + // Pop the new listener off the top of the stack (by restoring the + // previous listener) + currentObservableSet.set(lastObservableSet); + currentChangeListener.set(lastChangeListener); + currentStaleListener.set(lastStaleListener); + } + } + + /* + * Returns the same string as the default Object.toString() implementation. + * getterCalled() uses this method IObservable.toString() to avoid infinite + * recursion and stack overflow. + */ + private static String toString(IObservable observable) { + return observable.getClass().getName() + "@" //$NON-NLS-1$ + + Integer.toHexString(System.identityHashCode(observable)); + } + + /** + * Notifies the ObservableTracker that an observable was read from. The + * JavaDoc for methods that invoke this method should include the following + * tag: "@TrackedGetter This method will notify ObservableTracker that the + * receiver has been read from". This lets callers know that they can rely + * on automatic updates from the object without explicitly attaching a + * listener. + * + * @param observable + */ + public static void getterCalled(IObservable observable) { + Realm realm = observable.getRealm(); + if (realm is null) // observable.isDisposed() would be more appropriate if it existed + Assert.isTrue(false, "Getter called on disposed observable " //$NON-NLS-1$ + + toString(observable)); + if (!realm.isCurrent()) + Assert.isTrue(false, "Getter called outside realm of observable " //$NON-NLS-1$ + + toString(observable)); + + Set lastObservableSet = cast(Set) currentObservableSet.get(); + if (lastObservableSet is null) { + return; + } + IChangeListener lastChangeListener = cast(IChangeListener) currentChangeListener + .get(); + IStaleListener lastStaleListener = cast(IStaleListener) currentStaleListener + .get(); + + bool added = false; + if (lastObservableSet !is null) { + added = lastObservableSet.add(new IdentityWrapper(observable)); + } + + // If anyone is listening for observable usage... + if (added && lastChangeListener !is null) { + observable.addChangeListener(lastChangeListener); + } + if (added && lastStaleListener !is null) { + observable.addStaleListener(lastStaleListener); + } + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/Observables.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/Observables.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,514 @@ +/******************************************************************************* + * Copyright (c) 2006-2008 Cerner Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Brad Reynolds - initial API and implementation + * Matt Carter - bug 212518 (constantObservableValue) + * Matthew Hall - bugs 208332, 212518, 219909, 184830 + * Marko Topolnik - bug 184830 + ******************************************************************************/ + +package org.eclipse.core.databinding.observable; + +import java.util.List; +import java.util.Set; + +import org.eclipse.core.databinding.observable.list.IListChangeListener; +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.list.ObservableList; +import org.eclipse.core.databinding.observable.map.IObservableMap; +import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; +import org.eclipse.core.databinding.observable.masterdetail.MasterDetailObservables; +import org.eclipse.core.databinding.observable.set.IObservableSet; +import org.eclipse.core.databinding.observable.set.ISetChangeListener; +import org.eclipse.core.databinding.observable.set.ObservableSet; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.internal.databinding.observable.ConstantObservableValue; +import org.eclipse.core.internal.databinding.observable.EmptyObservableList; +import org.eclipse.core.internal.databinding.observable.EmptyObservableSet; +import org.eclipse.core.internal.databinding.observable.MapEntryObservableValue; +import org.eclipse.core.internal.databinding.observable.ProxyObservableList; +import org.eclipse.core.internal.databinding.observable.ProxyObservableSet; +import org.eclipse.core.internal.databinding.observable.StalenessObservableValue; +import org.eclipse.core.internal.databinding.observable.UnmodifiableObservableList; +import org.eclipse.core.internal.databinding.observable.UnmodifiableObservableSet; +import org.eclipse.core.internal.databinding.observable.UnmodifiableObservableValue; +import org.eclipse.core.runtime.Assert; + +/** + * Contains static methods to operate on or return + * {@link IObservable Observables}. + * + * @since 1.0 + */ +public class Observables { + /** + * Returns an unmodifiable observable value backed by the given observable + * value. + * + * @param value + * the value to wrap in an unmodifiable value + * @return an unmodifiable observable value backed by the given observable + * value + * @since 1.1 + */ + public static IObservableValue unmodifiableObservableValue( + IObservableValue value) { + Assert.isNotNull(value, "Argument 'value' cannot be null"); //$NON-NLS-1$ + return new UnmodifiableObservableValue(value); + } + + /** + * Returns an observable value with the given constant value. + * + * @param realm + * the observable's realm + * @param value + * the observable's constant value + * @param valueType + * the observable's value type + * @return an immutable observable value with the given constant value + * @since 1.1 + */ + public static IObservableValue constantObservableValue(Realm realm, + Object value, Object valueType) { + return new ConstantObservableValue(realm, value, valueType); + } + + /** + * Returns an observable value with the given constant value. + * + * @param realm + * the observable's realm + * @param value + * the observable's constant value + * @return an immutable observable value with the given constant value + * @since 1.1 + */ + public static IObservableValue constantObservableValue(Realm realm, + Object value) { + return constantObservableValue(realm, value, null); + } + + /** + * Returns an observable value with the given constant value. + * + * @param value + * the observable's constant value + * @param valueType + * the observable's value type + * @return an immutable observable value with the given constant value + * @since 1.1 + */ + public static IObservableValue constantObservableValue(Object value, + Object valueType) { + return constantObservableValue(Realm.getDefault(), value, valueType); + } + + /** + * Returns an observable value with the given constant value. + * + * @param value + * the observable's constant value + * @return an immutable observable value with the given constant value + * @since 1.1 + */ + public static IObservableValue constantObservableValue(Object value) { + return constantObservableValue(Realm.getDefault(), value, null); + } + + /** + * Returns an unmodifiable observable list backed by the given observable + * list. + * + * @param list + * the list to wrap in an unmodifiable list + * @return an unmodifiable observable list backed by the given observable + * list + */ + public static IObservableList unmodifiableObservableList( + IObservableList list) { + if (list is null) { + throw new IllegalArgumentException("List parameter cannot be null."); //$NON-NLS-1$ + } + + return new UnmodifiableObservableList(list); + } + + /** + * Returns an unmodifiable observable set backed by the given observable + * set. + * + * @param set + * the set to wrap in an unmodifiable set + * @return an unmodifiable observable set backed by the given observable set + * @since 1.1 + */ + public static IObservableSet unmodifiableObservableSet(IObservableSet set) { + if (set is null) { + throw new IllegalArgumentException("Set parameter cannot be null"); //$NON-NLS-1$ + } + + return new UnmodifiableObservableSet(set); + } + + /** + * Returns an empty observable list. The returned list continues to work + * after it has been disposed of and can be disposed of multiple times. + * + * @return an empty observable list. + */ + public static IObservableList emptyObservableList() { + return emptyObservableList(Realm.getDefault(), null); + } + + /** + * Returns an empty observable list of the given element type. The returned + * list continues to work after it has been disposed of and can be disposed + * of multiple times. + * + * @param elementType + * the element type of the returned list + * @return an empty observable list + * @since 1.1 + */ + public static IObservableList emptyObservableList(Object elementType) { + return emptyObservableList(Realm.getDefault(), elementType); + } + + /** + * Returns an empty observable list belonging to the given realm. The + * returned list continues to work after it has been disposed of and can be + * disposed of multiple times. + * + * @param realm + * the realm of the returned list + * @return an empty observable list. + */ + public static IObservableList emptyObservableList(Realm realm) { + return emptyObservableList(realm, null); + } + + /** + * Returns an empty observable list of the given element type and belonging + * to the given realm. The returned list continues to work after it has been + * disposed of and can be disposed of multiple times. + * + * @param realm + * the realm of the returned list + * @param elementType + * the element type of the returned list + * @return an empty observable list + * @since 1.1 + */ + public static IObservableList emptyObservableList(Realm realm, + Object elementType) { + return new EmptyObservableList(realm, elementType); + } + + /** + * Returns an empty observable set. The returned set continues to work after + * it has been disposed of and can be disposed of multiple times. + * + * @return an empty observable set. + */ + public static IObservableSet emptyObservableSet() { + return emptyObservableSet(Realm.getDefault(), null); + } + + /** + * Returns an empty observable set of the given element type. The returned + * set continues to work after it has been disposed of and can be disposed + * of multiple times. + * + * @param elementType + * the element type of the returned set + * @return an empty observable set + * @since 1.1 + */ + public static IObservableSet emptyObservableSet(Object elementType) { + return emptyObservableSet(Realm.getDefault(), elementType); + } + + /** + * Returns an empty observable set belonging to the given realm. The + * returned set continues to work after it has been disposed of and can be + * disposed of multiple times. + * + * @param realm + * the realm of the returned set + * @return an empty observable set. + */ + public static IObservableSet emptyObservableSet(Realm realm) { + return emptyObservableSet(realm, null); + } + + /** + * Returns an empty observable set of the given element type and belonging + * to the given realm. The returned set continues to work after it has been + * disposed of and can be disposed of multiple times. + * + * @param realm + * the realm of the returned set + * @param elementType + * the element type of the returned set + * @return an empty observable set + * @since 1.1 + */ + public static IObservableSet emptyObservableSet(Realm realm, + Object elementType) { + return new EmptyObservableSet(realm, elementType); + } + + /** + * Returns an observable set backed by the given set. + * + * @param set + * the set to wrap in an IObservableSet + * @return an observable set backed by the given set + */ + public static IObservableSet staticObservableSet(Set set) { + return staticObservableSet(Realm.getDefault(), set, Object.class); + } + + /** + * Returns an observable set of the given element type, backed by the given + * set. + * + * @param set + * the set to wrap in an IObservableSet + * @param elementType + * the element type of the returned set + * @return Returns an observable set backed by the given unchanging set + * @since 1.1 + */ + public static IObservableSet staticObservableSet(Set set, Object elementType) { + return staticObservableSet(Realm.getDefault(), set, elementType); + } + + /** + * Returns an observable set belonging to the given realm, backed by the + * given set. + * + * @param realm + * the realm of the returned set + * @param set + * the set to wrap in an IObservableSet + * @return an observable set backed by the given unchanging set + */ + public static IObservableSet staticObservableSet(Realm realm, Set set) { + return staticObservableSet(realm, set, Object.class); + } + + /** + * Returns an observable set of the given element type and belonging to the + * given realm, backed by the given set. + * + * @param realm + * the realm of the returned set + * @param set + * the set to wrap in an IObservableSet + * @param elementType + * the element type of the returned set + * @return an observable set backed by the given set + * @since 1.1 + */ + public static IObservableSet staticObservableSet(Realm realm, Set set, + Object elementType) { + return new class(realm, set, elementType) ObservableSet { + public void addChangeListener(IChangeListener listener) { + } + + public void addStaleListener(IStaleListener listener) { + } + + public void addSetChangeListener(ISetChangeListener listener) { + } + }; + } + + /** + * Returns an observable set that contains the same elements as the given + * set, and fires the same events as the given set, but can be disposed of + * without disposing of the wrapped set. + * + * @param target + * the set to wrap + * @return a disposable proxy for the given observable set + */ + public static IObservableSet proxyObservableSet(IObservableSet target) { + return new ProxyObservableSet(target); + } + + /** + * Returns an observable list that contains the same elements as the given + * list, and fires the same events as the given list, but can be disposed of + * without disposing of the wrapped list. + * + * @param target + * the list to wrap + * @return a disposable proxy for the given observable list + * @since 1.1 + */ + public static IObservableList proxyObservableList(IObservableList target) { + return new ProxyObservableList(target); + } + + /** + * Returns an observable list backed by the given list. + * + * @param list + * the list to wrap in an IObservableList + * @return an observable list backed by the given unchanging list + */ + public static IObservableList staticObservableList(List list) { + return staticObservableList(Realm.getDefault(), list, Object.class); + } + + /** + * Returns an observable list of the given element type, backed by the given + * list. + * + * @param list + * the list to wrap in an IObservableList + * @param elementType + * the element type of the returned list + * @return an observable list backed by the given unchanging list + * @since 1.1 + */ + public static IObservableList staticObservableList(List list, + Object elementType) { + return staticObservableList(Realm.getDefault(), list, elementType); + } + + /** + * Returns an observable list belonging to the given realm, backed by the + * given list. + * + * @param realm + * the realm of the returned list + * @param list + * the list to wrap in an IObservableList + * @return an observable list backed by the given unchanging list + */ + public static IObservableList staticObservableList(Realm realm, List list) { + return staticObservableList(realm, list, Object.class); + } + + /** + * Returns an observable list of the given element type and belonging to the + * given realm, backed by the given list. + * + * @param realm + * the realm of the returned list + * @param list + * the list to wrap in an IObservableList + * @param elementType + * the element type of the returned list + * @return an observable list backed by the given unchanging list + * @since 1.1 + */ + public static IObservableList staticObservableList(Realm realm, List list, + Object elementType) { + return new class(realm, list, elementType) ObservableList { + public void addChangeListener(IChangeListener listener) { + } + + public void addStaleListener(IStaleListener listener) { + } + + public void addListChangeListener(IListChangeListener listener) { + } + }; + } + + /** + * Returns an observable value of type Boolean.TYPE which + * tracks whether the given observable is stale. + * + * @param observable + * the observable to track + * @return an observable value which tracks whether the given observable is + * stale + * + * @since 1.1 + */ + public static IObservableValue observeStale(IObservable observable) { + return new StalenessObservableValue(observable); + } + + /** + * Returns an observable value that tracks changes to the value of an + * observable map's entry specified by its key. + *

+ * The state where the key does not exist in the map is equivalent to the + * state where the key exists and its value is null. The + * transition between these two states is not considered a value change and + * no event is fired. + * + * @param map + * the observable map whose entry will be tracked. + * @param key + * the key identifying the map entry to track. + * @param valueType + * the type of the value. May be null, meaning + * the value is untyped. + * @return an observable value that tracks the value associated with the + * specified key in the given map + * @since 1.1 + */ + public static IObservableValue observeMapEntry(IObservableMap map, + Object key, Object valueType) { + return new MapEntryObservableValue(map, key, valueType); + } + + /** + * Returns a factory for creating obervable values tracking the value of the + * {@link IObservableMap observable map} entry identified by a particular + * key. + * + * @param map + * the observable map whose entry will be tracked. + * @param valueType + * the type of the value. May be null, meaning + * the value is untyped. + * @return a factory for creating observable values tracking the value of + * the observable map entry identified by a particular key object. + * @since 1.1 + */ + public static IObservableFactory mapEntryValueFactory( + final IObservableMap map, final Object valueType) { + return new class() IObservableFactory { + public IObservable createObservable(Object key) { + return observeMapEntry(map, key, valueType); + } + }; + } + + /** + * Helper method for MasterDetailObservables.detailValue(master, + * mapEntryValueFactory(map, valueType), valueType). + * + * @param map + * the observable map whose entry will be tracked. + * @param master + * the observable value that identifies which map entry to track. + * @param valueType + * the type of the value. May be null, meaning + * the value is untyped. + * @return an observable value tracking the current value of the specified + * key in the given map an observable value that tracks the current + * value of the named property for the current value of the master + * observable value + * @since 1.1 + */ + public static IObservableValue observeDetailMapEntry(IObservableMap map, + IObservableValue master, Object valueType) { + return MasterDetailObservables.detailValue(master, + mapEntryValueFactory(map, valueType), valueType); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/Realm.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/Realm.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,293 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 168153 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable; + +import org.eclipse.core.databinding.Binding; +import org.eclipse.core.databinding.util.Policy; +import org.eclipse.core.internal.databinding.Queue; +import org.eclipse.core.runtime.ISafeRunnable; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.SafeRunner; +import org.eclipse.core.runtime.Status; + +/** + * A realm defines a context from which objects implementing {@link IObservable} + * must be accessed, and on which these objects will notify their listeners. To + * bridge between observables from different realms, subclasses of + * {@link Binding} can be used. + *

+ * A block of code is said to be executing within a realm if calling + * {@link #isCurrent()} from that block returns true. Code reached by calling + * methods from that block will execute within the same realm, with the + * exception of methods on this class that can be used to execute code within a + * specific realm. Clients can use {@link #syncExeccast(Runnable)}, + * {@link #asyncExeccast(Runnable)}, or {@link #execcast(Runnable)} to execute a + * runnable within this realm. Note that using {@link #syncExeccast(Runnable)} can + * lead to deadlocks and should be avoided if the current thread holds any + * locks. + *

+ *

+ * It is instructive to think about possible implementations of Realm: It can be + * based on executing on a designated thread such as a UI thread, or based on + * holding a lock. In the former case, calling syncExec on a realm that is not + * the current realm will execute the given runnable on a different thread (the + * designated thread). In the latter case, calling syncExec may execute the + * given runnable on the calling thread, but calling + * {@link #asyncExeccast(Runnable)} will execute the given runnable on a different + * thread. Therefore, no assumptions can be made about the thread that will + * execute arguments to {@link #asyncExeccast(Runnable)}, + * {@link #syncExeccast(Runnable)}, or {@link #execcast(Runnable)}. + *

+ *

+ * It is possible that a block of code is executing within more than one realm. + * This can happen for implementations of Realm that are based on holding a lock + * but don't use a separate thread to run runnables given to + * {@link #syncExeccast(Runnable)}. Realm implementations of this kind should be + * appropriately documented because it increases the opportunity for deadlock. + *

+ *

+ * Some implementations of {@link IObservable} provide constructors which do not + * take a Realm argument and are specified to create the observable instance + * with the current default realm. The default realm can be set for the + * currently executing thread by using {@link #runWithDefault(Realm, Runnable)}. + * Note that the default realm does not have to be the current realm. + *

+ *

+ * Subclasses must override at least one of asyncExec()/syncExec(). For realms + * based on a designated thread, it may be easier to implement asyncExec and + * keep the default implementation of syncExec. For realms based on holding a + * lock, it may be easier to implement syncExec and keep the default + * implementation of asyncExec. + *

+ * + * @since 1.0 + * + * @see IObservable + */ +public abstract class Realm { + + private static ThreadLocal defaultRealm = new ThreadLocal(); + + /** + * Returns the default realm for the calling thread, or null + * if no default realm has been set. + * + * @return the default realm, or null + */ + public static Realm getDefault() { + return cast(Realm) defaultRealm.get(); + } + + /** + * Sets the default realm for the calling thread, returning the current + * default thread. This method is inherently unsafe, it is recommended to + * use {@link #runWithDefault(Realm, Runnable)} instead. This method is + * exposed to subclasses to facilitate testing. + * + * @param realm + * the new default realm, or null + * @return the previous default realm, or null + */ + protected static Realm setDefault(Realm realm) { + Realm oldValue = getDefault(); + defaultRealm.set(realm); + return oldValue; + } + + /** + * @return true if the caller is executing in this realm. This method must + * not have side-effects (such as, for example, implicitly placing + * the caller in this realm). + */ + abstract public bool isCurrent(); + + private Thread workerThread; + + Queue workQueue = new Queue(); + + /** + * Runs the given runnable. If an exception occurs within the runnable, it + * is logged and not re-thrown. If the runnable implements + * {@link ISafeRunnable}, the exception is passed to its + * handleException method. + * + * @param runnable + */ + protected static void safeRun(final Runnable runnable) { + ISafeRunnable safeRunnable; + if ( null !is cast(ISafeRunnable)runnable ) { + safeRunnable = cast(ISafeRunnable) runnable; + } else { + safeRunnable = new class() ISafeRunnable { + public void handleException(Throwable exception) { + Policy + .getLog() + .log( + new Status( + IStatus.ERROR, + Policy.JFACE_DATABINDING, + IStatus.OK, + "Unhandled exception: " + exception.getMessage(), exception)); //$NON-NLS-1$ + } + public void run() { + runnable.run(); + } + }; + } + SafeRunner.run(safeRunnable); + } + + /** + * Causes the run() method of the runnable to be invoked from + * within this realm. If the caller is executing in this realm, the + * runnable's run method is invoked directly, otherwise it is run at the + * next reasonable opportunity using asyncExec. + *

+ * If the given runnable is an instance of {@link ISafeRunnable}, its + * exception handler method will be called if any exceptions occur while + * running it. Otherwise, the exception will be logged. + *

+ * + * @param runnable + */ + public void exec(Runnable runnable) { + if (isCurrent()) { + safeRun(runnable); + } else { + asyncExec(runnable); + } + } + + /** + * Causes the run() method of the runnable to be invoked from + * within this realm at the next reasonable opportunity. The caller of this + * method continues to run in parallel, and is not notified when the + * runnable has completed. + *

+ * If the given runnable is an instance of {@link ISafeRunnable}, its + * exception handler method will be called if any exceptions occur while + * running it. Otherwise, the exception will be logged. + *

+ *

+ * Subclasses should use {@link #safeRuncast(Runnable)} to run the runnable. + *

+ * + * @param runnable + */ + public void asyncExec(Runnable runnable) { + synchronized (workQueue) { + ensureWorkerThreadIsRunning(); + workQueue.enqueue(runnable); + workQueue.notifyAll(); + } + } + + /** + * + */ + private void ensureWorkerThreadIsRunning() { + if (workerThread is null) { + workerThread = new class() Thread { + public void run() { + try { + while (true) { + Runnable work = null; + synchronized (workQueue) { + while (workQueue.isEmpty()) { + workQueue.wait(); + } + work = cast(Runnable) workQueue.dequeue(); + } + syncExec(work); + } + } catch (InterruptedException e) { + // exit + } + } + }; + workerThread.start(); + } + } + + /** + * Causes the run() method of the runnable to be invoked from + * within this realm at the next reasonable opportunity. This method is + * blocking the caller until the runnable completes. + *

+ * If the given runnable is an instance of {@link ISafeRunnable}, its + * exception handler method will be called if any exceptions occur while + * running it. Otherwise, the exception will be logged. + *

+ *

+ * Subclasses should use {@link #safeRuncast(Runnable)} to run the runnable. + *

+ *

+ * Note: This class is not meant to be called by clients and therefore has + * only protected access. + *

+ * + * @param runnable + */ + protected void syncExec(Runnable runnable) { + SyncRunnable syncRunnable = new SyncRunnable(runnable); + asyncExec(syncRunnable); + synchronized (syncRunnable) { + while (!syncRunnable.hasRun) { + try { + syncRunnable.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + } + + static class SyncRunnable : Runnable { + bool hasRun = false; + + private Runnable runnable; + + this(Runnable runnable) { + this.runnable = runnable; + } + + public void run() { + try { + safeRun(runnable); + } finally { + synchronized (this) { + hasRun = true; + this.notifyAll(); + } + } + } + } + + /** + * Sets the provided realm as the default for the duration of + * {@link Runnable#run()} and resets the previous realm after completion. + * Note that this will not set the given realm as the current realm. + * + * @param realm + * @param runnable + */ + public static void runWithDefault(Realm realm, Runnable runnable) { + Realm oldRealm = Realm.getDefault(); + try { + defaultRealm.set(realm); + runnable.run(); + } finally { + defaultRealm.set(oldRealm); + } + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/StaleEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/StaleEvent.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable; + +/** + * Generic event denoting that the state of an {@link IObservable} object is + * about to change. Note that this event is only fired when an observable + * becomes stale, not when it becomes unstale; an observable that becomes + * unstale should always fire a change event. Staleness can be used (for + * example) to notify listeners when an observable has started a background + * thread for updating its state. Clients can safely ignore staleness. + * + * @see IObservable#isStale() + * + * @since 1.0 + * + */ +public class StaleEvent : ObservableEvent { + + /** + * Creates a new stale event. + * + * @param source + * the source observable + */ + public this(IObservable source) { + super(source); + } + + /** + * + */ + private static final long serialVersionUID = 3491012225431471077L; + + static final Object TYPE = new Object(); + + protected void dispatch(IObservablesListener listener) { + (cast(IStaleListener) listener).handleStale(this); + } + + protected Object getListenerType() { + return TYPE; + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/AbstractObservableList.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/AbstractObservableList.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,312 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 164653 + * Brad Reynolds - bug 167204 + * Matthew Hall - bug 118516 + * Matthew Hall - bug 208858 + * Matthew Hall - bug 208332 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.list; + +import java.util.AbstractList; +import java.util.Collection; +import java.util.Iterator; + +import org.eclipse.core.databinding.observable.ChangeEvent; +import org.eclipse.core.databinding.observable.ChangeSupport; +import org.eclipse.core.databinding.observable.IChangeListener; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.AssertionFailedException; + +/** + * Subclasses should override at least get(int index) and size(). + * + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * + * @since 1.0 + * + */ +public abstract class AbstractObservableList : AbstractList , + IObservableList { + + private ChangeSupport changeSupport; + + /** + * @param realm + * + */ + public this(Realm realm) { + Assert.isNotNull(realm, "Realm cannot be null"); //$NON-NLS-1$ + changeSupport = new class(realm) ChangeSupport { + protected void firstListenerAdded() { + this.outer.firstListenerAdded(); + } + protected void lastListenerRemoved() { + this.outer.lastListenerRemoved(); + } + }; + } + + /** + * + */ + public this() { + this(Realm.getDefault()); + } + + public bool isStale() { + getterCalled(); + return false; + } + + public synchronized void addListChangeListener(IListChangeListener listener) { + changeSupport.addListener(ListChangeEvent.TYPE, listener); + } + + public synchronized void removeListChangeListener(IListChangeListener listener) { + changeSupport.removeListener(ListChangeEvent.TYPE, listener); + } + + protected void fireListChange(ListDiff diff) { + // fire general change event first + fireChange(); + changeSupport.fireEvent(new ListChangeEvent(this, diff)); + } + + public synchronized void addChangeListener(IChangeListener listener) { + changeSupport.addChangeListener(listener); + } + + public synchronized void removeChangeListener(IChangeListener listener) { + changeSupport.removeChangeListener(listener); + } + + public synchronized void addStaleListener(IStaleListener listener) { + changeSupport.addStaleListener(listener); + } + + public synchronized void removeStaleListener(IStaleListener listener) { + changeSupport.removeStaleListener(listener); + } + + /** + * Fires change event. Must be invoked from the current realm. + */ + protected void fireChange() { + checkRealm(); + changeSupport.fireEvent(new ChangeEvent(this)); + } + + /** + * Fires stale event. Must be invoked from the current realm. + */ + protected void fireStale() { + checkRealm(); + changeSupport.fireEvent(new StaleEvent(this)); + } + + /** + * + */ + protected void firstListenerAdded() { + } + + /** + * + */ + protected void lastListenerRemoved() { + } + + /** + * + */ + public synchronized void dispose() { + changeSupport = null; + lastListenerRemoved(); + } + + public final int size() { + getterCalled(); + return doGetSize(); + } + + /** + * @return the size + */ + protected abstract int doGetSize(); + + /** + * + */ + private void getterCalled() { + ObservableTracker.getterCalled(this); + } + + public bool isEmpty() { + getterCalled(); + return super.isEmpty(); + } + + public bool contains(Object o) { + getterCalled(); + return super.contains(o); + } + + public Iterator iterator() { + getterCalled(); + final Iterator wrappedIterator = super.iterator(); + return new class() Iterator { + public void remove() { + wrappedIterator.remove(); + } + + public bool hasNext() { + return wrappedIterator.hasNext(); + } + + public Object next() { + return wrappedIterator.next(); + } + }; + } + + public Object[] toArray() { + getterCalled(); + return super.toArray(); + } + + public Object[] toArray(Object a[]) { + getterCalled(); + return super.toArray(a); + } + + // Modification Operations + + public bool add(Object o) { + getterCalled(); + return super.add(o); + } + + /** + * Moves the element located at oldIndex to + * newIndex. This method is equivalent to calling + * add(newIndex, remove(oldIndex)). + *

+ * Subclasses should override this method to deliver list change + * notification for the remove and add operations in the same + * ListChangeEvent, as this allows {@link ListDiff#acceptcast(ListDiffVisitor)} + * to recognize the operation as a move. + * + * @param oldIndex + * the element's position before the move. Must be within the + * range 0 <= oldIndex < size(). + * @param newIndex + * the element's position after the move. Must be within the + * range 0 <= newIndex < size(). + * @return the element that was moved. + * @throws IndexOutOfBoundsException + * if either argument is out of range (0 <= index < size()). + * @see ListDiffVisitor#handleMove(int, int, Object) + * @see ListDiff#acceptcast(ListDiffVisitor) + * @since 1.1 + */ + public Object move(int oldIndex, int newIndex) { + checkRealm(); + int size = doGetSize(); + if (oldIndex < 0 || oldIndex >= size) + throw new IndexOutOfBoundsException( + "oldIndex: " + oldIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$ + if (newIndex < 0 || newIndex >= size) + throw new IndexOutOfBoundsException( + "newIndex: " + newIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$ + Object element = remove(oldIndex); + add(newIndex, element); + return element; + } + + public bool remove(Object o) { + getterCalled(); + return super.remove(o); + } + + // Bulk Modification Operations + + public bool containsAll(Collection c) { + getterCalled(); + return super.containsAll(c); + } + + public bool addAll(Collection c) { + getterCalled(); + return super.addAll(c); + } + + public bool addAll(int index, Collection c) { + getterCalled(); + return super.addAll(c); + } + + public bool removeAll(Collection c) { + getterCalled(); + return super.removeAll(c); + } + + public bool retainAll(Collection c) { + getterCalled(); + return super.retainAll(c); + } + + // Comparison and hashing + + public override equals_t opEquals(Object o) { + getterCalled(); + return super.equals(o); + } + + public override hash_t toHash() { + getterCalled(); + return super.hashCode(); + } + + public int indexOf(Object o) { + getterCalled(); + return super.indexOf(o); + } + + public int lastIndexOf(Object o) { + getterCalled(); + return super.lastIndexOf(o); + } + + public Realm getRealm() { + return changeSupport.getRealm(); + } + + /** + * Asserts that the realm is the current realm. + * + * @see Realm#isCurrent() + * @throws AssertionFailedException + * if the realm is not the current realm + */ + protected void checkRealm() { + Assert.isTrue(getRealm().isCurrent(), + "This operation must be run within the observable's realm"); //$NON-NLS-1$ + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ComputedList.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ComputedList.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,290 @@ +/************************************************************************************************************ + * Copyright (c) 2007 Matthew Hall and others. All rights reserved. This program and the + * accompanying materials are made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Matthew Hall - initial API and implementation + * IBM Corporation - initial API and implementation + * Brad Reynolds - initial API and implementation (through bug 116920 and bug 147515) + * Matthew Hall - bug 211786 + ***********************************************************************************************************/ +package org.eclipse.core.databinding.observable.list; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.core.databinding.observable.ChangeEvent; +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.IChangeListener; +import org.eclipse.core.databinding.observable.IObservable; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.StaleEvent; + +/** + * A Lazily calculated list that automatically computes and registers listeners + * on its dependencies as long as all of its dependencies are IObservable + * objects + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * + * @since 1.1 + */ +public abstract class ComputedList : AbstractObservableList { + private List cachedList = new ArrayList(); + + private bool dirty = true; + private bool stale = false; + + private IObservable[] dependencies = new IObservable[0]; + + /** + * Creates a computed list in the default realm and with an unknown (null) + * element type. + */ + public this() { + this(Realm.getDefault(), null); + } + + /** + * Creates a computed list in the default realm and with the given element + * type. + * + * @param elementType + * the element type, may be null to indicate + * unknown element type + */ + public this(Object elementType) { + this(Realm.getDefault(), elementType); + } + + /** + * Creates a computed list in given realm and with an unknown (null) element + * type. + * + * @param realm + * the realm + * + */ + public this(Realm realm) { + this(realm, null); + } + + /** + * Creates a computed list in the given realm and with the given element + * type. + * + * @param realm + * the realm + * @param elementType + * the element type, may be null to indicate + * unknown element type + */ + public this(Realm realm, Object elementType) { + super(realm); + this.elementType = elementType; + } + + /** + * Inner class that implements interfaces that we don't want to expose as + * public API. Each interface could have been implemented using a separate + * anonymous class, but we combine them here to reduce the memory overhead + * and number of classes. + * + *

+ * The Runnable calls calculate and stores the result in cachedList. + *

+ * + *

+ * The IChangeListener stores each observable in the dependencies list. This + * is registered as the listener when calling ObservableTracker, to detect + * every observable that is used by computeValue. + *

+ * + *

+ * The IChangeListener is attached to every dependency. + *

+ * + */ + private class PrivateInterface : Runnable, IChangeListener, + IStaleListener { + public void run() { + cachedList = calculate(); + if (cachedList is null) + cachedList = Collections.EMPTY_LIST; + } + + public void handleStale(StaleEvent event) { + if (!dirty) + makeStale(); + } + + public void handleChange(ChangeEvent event) { + makeDirty(); + } + } + + private PrivateInterface privateInterface = new PrivateInterface(); + + private Object elementType; + + protected int doGetSize() { + return doGetList().size(); + } + + public Object get(int index) { + getterCalled(); + return doGetList().get(index); + } + + private final List getList() { + getterCalled(); + return doGetList(); + } + + final List doGetList() { + if (dirty) { + // This line will do the following: + // - Run the calculate method + // - While doing so, add any observable that is touched to the + // dependencies list + IObservable[] newDependencies = ObservableTracker.runAndMonitor( + privateInterface, privateInterface, null); + + // If any dependencies are stale, a stale event will be fired here + // even if we were already stale before recomputing. This is in case + // clients assume that a list change is indicative of non-staleness. + stale = false; + for (int i = 0; i < newDependencies.length; i++) { + if (newDependencies[i].isStale()) { + makeStale(); + break; + } + } + + if (!stale) { + for (int i = 0; i < newDependencies.length; i++) { + newDependencies[i].addStaleListener(privateInterface); + } + } + + dependencies = newDependencies; + + dirty = false; + } + + return cachedList; + } + + private void getterCalled() { + ObservableTracker.getterCalled(this); + } + + /** + * Subclasses must override this method to calculate the list contents. + * + * @return the object's list. + */ + protected abstract List calculate(); + + private void makeDirty() { + if (!dirty) { + dirty = true; + + makeStale(); + + stopListening(); + + // copy the old list + final List oldList = new ArrayList(cachedList); + // Fire the "dirty" event. This implementation recomputes the new + // list lazily. + fireListChange(new class() ListDiff { + ListDiffEntry[] differences; + + public ListDiffEntry[] getDifferences() { + if (differences is null) + differences = Diffs.computeListDiff(oldList, getList()) + .getDifferences(); + return differences; + } + }); + } + } + + private void stopListening() { + if (dependencies !is null) { + for (int i = 0; i < dependencies.length; i++) { + IObservable observable = dependencies[i]; + + observable.removeChangeListener(privateInterface); + observable.removeStaleListener(privateInterface); + } + dependencies = null; + } + } + + private void makeStale() { + if (!stale) { + stale = true; + fireStale(); + } + } + + public bool isStale() { + // recalculate list if dirty, to ensure staleness is correct. + getList(); + return stale; + } + + public Object getElementType() { + return elementType; + } + + public synchronized void addChangeListener(IChangeListener listener) { + super.addChangeListener(listener); + // If somebody is listening, we need to make sure we attach our own + // listeners + computeListForListeners(); + } + + public synchronized void addListChangeListener(IListChangeListener listener) { + super.addListChangeListener(listener); + // If somebody is listening, we need to make sure we attach our own + // listeners + computeListForListeners(); + } + + private void computeListForListeners() { + // Some clients just add a listener and expect to get notified even if + // they never called getValue(), so we have to call getValue() ourselves + // here to be sure. Need to be careful about realms though, this method + // can be called outside of our realm. + // See also bug 198211. If a client calls this outside of our realm, + // they may receive change notifications before the runnable below has + // been executed. It is their job to figure out what to do with those + // notifications. + getRealm().exec(new class() Runnable { + public void run() { + if (dependencies is null) { + // We are not currently listening. + // But someone is listening for changes. Call getValue() + // to make sure we start listening to the observables we + // depend on. + getList(); + } + } + }); + } + + public synchronized void dispose() { + stopListening(); + super.dispose(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/IListChangeListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/IListChangeListener.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.list; + +import org.eclipse.core.databinding.observable.IObservablesListener; + +/** + * Listener for changes to observable lists. + * + * @since 1.0 + */ +public interface IListChangeListener : IObservablesListener { + + /** + * Handle a change to an observable list. The change is described by the + * diff object. The given event object must only be used locally in this + * method because it may be reused for other change notifications. The diff + * object referenced by the event is immutable and may be used non-locally. + * + * @param event + */ + void handleListChange(ListChangeEvent event); + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/IObservableList.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/IObservableList.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,198 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 167204 + * Matthew Hall - bug 208858 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.list; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.core.databinding.observable.IObservableCollection; + +/** + * A list whose changes can be tracked by list change listeners. + * + * @noextend This interface is not intended to be extended by clients. + * @noimplement This interface is not intended to be implemented by clients. + * Clients should instead subclass one of the framework classes + * that implement this interface. Note that direct implementers of + * this interface outside of the framework will be broken in future + * releases when methods are added to this interface. + * + * @see AbstractObservableList + * @see ObservableList + * + * @since 1.0 + */ +public interface IObservableList : List, IObservableCollection { + + /** + * Adds the given list change listener to the list of list change listeners. + * @param listener + */ + public void addListChangeListener(IListChangeListener listener); + + /** + * Removes the given list change listener from the list of list change listeners. + * Has no effect if the given listener is not registered as a list change listener. + * + * @param listener + */ + public void removeListChangeListener(IListChangeListener listener); + + /** + * @TrackedGetter + */ + public int size(); + + /** + * @TrackedGetter + */ + public bool isEmpty(); + + /** + * @TrackedGetter + */ + public bool contains(Object o); + + /** + * @TrackedGetter + */ + public Iterator iterator(); + + /** + * @TrackedGetter + */ + public Object[] toArray(); + + /** + * @TrackedGetter + */ + public Object[] toArray(Object a[]); + + /** + * + */ + public bool add(Object o); + + /** + * + */ + public bool remove(Object o); + + /** + * @TrackedGetter + */ + public bool containsAll(Collection c); + + /** + * + */ + public bool addAll(Collection c); + + /** + * + */ + public bool addAll(int index, Collection c); + + /** + * + */ + public bool removeAll(Collection c); + + /** + * + */ + public bool retainAll(Collection c); + + /** + * @TrackedGetter + */ + public override equals_t opEquals(Object o); + + /** + * @TrackedGetter + */ + public override hash_t toHash(); + + /** + * @TrackedGetter + */ + public Object get(int index); + + /** + * + */ + public Object set(int index, Object element); + + /** + * Moves the element located at oldIndex to + * newIndex. This method is equivalent to calling + * add(newIndex, remove(oldIndex)). + *

+ * Implementors should deliver list change notification for the remove and + * add operations in the same ListChangeEvent, as this allows + * {@link ListDiff#acceptcast(ListDiffVisitor)} to recognize the operation as a + * move. + * + * @param oldIndex + * the element's position before the move. Must be within the + * range 0 <= oldIndex < size(). + * @param newIndex + * the element's position after the move. Must be within the + * range 0 <= newIndex < size(). + * @return the element that was moved. + * @throws IndexOutOfBoundsException + * if either argument is out of range (0 <= index < size()). + * @see ListDiffVisitor#handleMove(int, int, Object) + * @see ListDiff#acceptcast(ListDiffVisitor) + * @since 1.1 + */ + public Object move(int oldIndex, int newIndex); + + /** + * + */ + public Object remove(int index); + + /** + * @TrackedGetter + */ + public int indexOf(Object o); + + /** + * @TrackedGetter + */ + public int lastIndexOf(Object o); + + /** + * @TrackedGetter + */ + public ListIterator listIterator(); + + /** + * @TrackedGetter + */ + public ListIterator listIterator(int index); + + /** + * @TrackedGetter + */ + public List subList(int fromIndex, int toIndex); + + /** + * @return the type of the elements or null if untyped + */ + Object getElementType(); +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListChangeEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListChangeEvent.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.list; + +import org.eclipse.core.databinding.observable.IObservablesListener; +import org.eclipse.core.databinding.observable.ObservableEvent; + +/** + * List change event describing an incremental change of an + * {@link IObservableList} object. + * + * @since 1.0 + */ +public class ListChangeEvent : ObservableEvent { + + /** + * + */ + private static final long serialVersionUID = -9154315534258776672L; + + static final Object TYPE = new Object(); + + /** + * Description of the change to the source observable list. Listeners must + * not change this field. + */ + public ListDiff diff; + + /** + * Creates a new list change event. + * + * @param source + * the source observable list + * @param diff + * the list change + */ + public this(IObservableList source, ListDiff diff) { + super(source); + this.diff = diff; + } + + /** + * Returns the observable list from which this event originated. + * + * @return the observable list from which this event originated + */ + public IObservableList getObservableList() { + return cast(IObservableList) getSource(); + } + + protected void dispatch(IObservablesListener listener) { + (cast(IListChangeListener) listener).handleListChange(this); + } + + protected Object getListenerType() { + return TYPE; + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListDiff.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListDiff.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matthew Hall - bug 208858 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.list; + +import org.eclipse.core.internal.databinding.Util; + +/** + * Object describing a diff between two lists. + * + * @since 1.0 + */ +public abstract class ListDiff { + + /** + * Returns a ListDiffEntry array representing the differences in the list, + * in the order they are to be processed. + * + * @return a ListDiffEntry array representing the differences in the list, + * in the order they are to be processed. + */ + public abstract ListDiffEntry[] getDifferences(); + + /** + * Traverses the {@link #getDifferences()} array, calling the appropriate + * method in visitor for each difference. + *

    + *
  1. {@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. + *
  2. {@link ListDiffVisitor#handleMove(int, int, Object)} is called + * whenever a remove entry is immediately followed by an add entry with an + * equivalent element. + *
  3. {@link ListDiffVisitor#handleRemove(int, Object)} is called whenever + * a remove entry does not match conditions 1 or 2. + *
  4. {@link ListDiffVisitor#handleAdd(int, Object)} is called whenever an + * add entry does not match conditions in 1 or 2. + *
+ * + * @param visitor + * the visitor to receive callbacks. + * @see ListDiffVisitor + * @since 1.1 + */ + public void accept(ListDiffVisitor visitor) { + ListDiffEntry[] differences = getDifferences(); + for (int i = 0; i < differences.length; i++) { + ListDiffEntry entry = differences[i]; + int position = entry.getPosition(); + Object element = entry.getElement(); + bool addition = entry.isAddition(); + + if (!addition && i + 1 < differences.length) { + ListDiffEntry entry2 = differences[i + 1]; + if (entry2.isAddition()) { + int position2 = entry2.getPosition(); + Object element2 = entry2.getElement(); + if (position is position2) { + visitor.handleReplace(position, element, element2); + i++; + continue; + } + if (Util.equals(element, element2)) { + visitor.handleMove(position, position2, element); + i++; + continue; + } + } + } + if (addition) + visitor.handleAdd(position, element); + else + visitor.handleRemove(position, element); + } + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + ListDiffEntry[] differences = getDifferences(); + StringBuffer buffer = new StringBuffer(); + buffer.append(getClass().getName()); + + if (differences is null || differences.length is 0) { + buffer + .append("{}"); //$NON-NLS-1$ + } else { + buffer + .append("{"); //$NON-NLS-1$ + + for (int i = 0; i < differences.length; i++) { + if (i > 0) + buffer.append(", "); //$NON-NLS-1$ + + buffer + .append("difference[") //$NON-NLS-1$ + .append(i) + .append("] [") //$NON-NLS-1$ + .append(differences[i] !is null ? differences[i].toString() : "null") //$NON-NLS-1$ + .append("]"); //$NON-NLS-1$ + } + buffer.append("}"); //$NON-NLS-1$ + } + + return buffer.toString(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListDiffEntry.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListDiffEntry.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.core.databinding.observable.list; + +/** + * A single addition of an element to a list or removal of an element from a list. + * + * @since 1.0 + */ +public abstract class ListDiffEntry { + + /** + * @return the 0-based position of the addition or removal + */ + public abstract int getPosition(); + + /** + * @return true if this represents an addition, false if this represents a removal + */ + public abstract bool isAddition(); + + /** + * @return the element that was added or removed + */ + public abstract Object getElement(); + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer + .append(this.getClass().getName()) + .append("{position [") //$NON-NLS-1$ + .append(getPosition()) + .append("], isAddition [") //$NON-NLS-1$ + .append(isAddition()) + .append("], element [") //$NON-NLS-1$ + .append(getElement() !is null ? getElement().toString() : "null") //$NON-NLS-1$ + .append("]}"); //$NON-NLS-1$ + + return buffer.toString(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListDiffVisitor.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ListDiffVisitor.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2007 Matthew Hall and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Matthew Hall - initial API and implementation (bug 208858) + ******************************************************************************/ + +package org.eclipse.core.databinding.observable.list; + +import java.util.List; + +/** + * A visitor for processing differences in a ListDiff. + * + * @see ListDiff#acceptcast(ListDiffVisitor) + * @since 1.1 + */ +public abstract class ListDiffVisitor { + /** + * Notifies the visitor that element was added to the list at + * position index. + * + * @param index + * the index where the element was added + * @param element + * the element that was added + */ + public abstract void handleAdd(int index, Object element); + + /** + * Notifies the visitor that element was removed from the + * list at position index. + * + * @param index + * the index where the element was removed + * @param element + * the element that was removed + */ + public abstract void handleRemove(int index, Object element); + + /** + * Notifies the visitor that element was moved in the list + * from position oldIndex to position newIndex. + *

+ * The default implementation of this method calls + * {@link #handleRemove(int, Object)} with the old position, then + * {@link #handleAdd(int, Object)} with the new position. Clients which are + * interested in recognizing "moves" in a list (i.e. calls to + * {@link IObservableList#move(int, int)}) should override this method. + * + * @param oldIndex + * the index that the element was moved from. + * @param newIndex + * the index that the element was moved to. + * @param element + * the element that was moved + * @see IObservableList#move(int, int) + */ + public void handleMove(int oldIndex, int newIndex, Object element) { + handleRemove(oldIndex, element); + handleAdd(newIndex, element); + } + + /** + * Notifies the visitor that oldElement, located at position + * index in the list, was replaced by newElement. + *

+ * The default implementation of this method calls + * {@link #handleRemove(int, Object)} with the old element, then + * {@link #handleAdd(int, Object)} with the new element. Clients which are + * interested in recognizing "replaces" in a list (i.e. calls to + * {@link List#set(int, Object)}) should override this method. + * + * @param index + * the index where the element was replaced. + * @param oldElement + * the element being replaced. + * @param newElement + * the element that replaced oldElement. + * @see List#set(int, Object) + */ + public void handleReplace(int index, Object oldElement, Object newElement) { + handleRemove(index, oldElement); + handleAdd(index, newElement); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ObservableList.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/ObservableList.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,379 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bugs 164653, 167204 + * Matthew Hall - bugs 208858, 208332, 245183 + * Tom Schindl - bug 245183 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.list; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.core.databinding.observable.AbstractObservable; +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.Realm; + +/** + * + * Abstract implementation of {@link IObservableList}, based on an underlying regular list. + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * @since 1.0 + * + */ +public abstract class ObservableList : AbstractObservable , + IObservableList { + + protected List wrappedList; + + /** + * Stale state of the list. Access must occur in the current realm. + */ + private bool stale = false; + + private Object elementType; + + protected this(List wrappedList, Object elementType) { + this(Realm.getDefault(), wrappedList, elementType); + } + + protected this(Realm realm, List wrappedList, Object elementType) { + super(realm); + this.wrappedList = wrappedList; + this.elementType = elementType; + } + + public synchronized void addListChangeListener(IListChangeListener listener) { + addListener(ListChangeEvent.TYPE, listener); + } + + public synchronized void removeListChangeListener(IListChangeListener listener) { + removeListener(ListChangeEvent.TYPE, listener); + } + + protected void fireListChange(ListDiff diff) { + // fire general change event first + super.fireChange(); + fireEvent(new ListChangeEvent(this, diff)); + } + + public bool contains(Object o) { + getterCalled(); + return wrappedList.contains(o); + } + + public bool containsAll(Collection c) { + getterCalled(); + return wrappedList.containsAll(c); + } + + public override equals_t opEquals(Object o) { + getterCalled(); + + if (o is this) + return true; + if (o is null) + return false; + if (getClass() is o.getClass()) { + return wrappedList.equals((cast(ObservableList) o).wrappedList); + } + + return wrappedList.equals(o); + } + + public override hash_t toHash() { + getterCalled(); + return wrappedList.hashCode(); + } + + public bool isEmpty() { + getterCalled(); + return wrappedList.isEmpty(); + } + + public Iterator iterator() { + getterCalled(); + final Iterator wrappedIterator = wrappedList.iterator(); + return new class() Iterator { + + public void remove() { + throw new UnsupportedOperationException(); + } + + public bool hasNext() { + return wrappedIterator.hasNext(); + } + + public Object next() { + return wrappedIterator.next(); + } + }; + } + + public int size() { + getterCalled(); + return wrappedList.size(); + } + + public Object[] toArray() { + getterCalled(); + return wrappedList.toArray(); + } + + public Object[] toArray(Object[] a) { + getterCalled(); + return wrappedList.toArray(a); + } + + public String toString() { + getterCalled(); + return wrappedList.toString(); + } + + /** + * @TrackedGetter + */ + public Object get(int index) { + getterCalled(); + return wrappedList.get(index); + } + + /** + * @TrackedGetter + */ + public int indexOf(Object o) { + getterCalled(); + return wrappedList.indexOf(o); + } + + /** + * @TrackedGetter + */ + public int lastIndexOf(Object o) { + getterCalled(); + return wrappedList.lastIndexOf(o); + } + + // List Iterators + + /** + * @TrackedGetter + */ + public ListIterator listIterator() { + return listIterator(0); + } + + /** + * @TrackedGetter + */ + public ListIterator listIterator(int index) { + getterCalled(); + final ListIterator wrappedIterator = wrappedList.listIterator(index); + return new class() ListIterator { + + public int nextIndex() { + return wrappedIterator.nextIndex(); + } + + public int previousIndex() { + return wrappedIterator.previousIndex(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public bool hasNext() { + return wrappedIterator.hasNext(); + } + + public bool hasPrevious() { + return wrappedIterator.hasPrevious(); + } + + public Object next() { + return wrappedIterator.next(); + } + + public Object previous() { + return wrappedIterator.previous(); + } + + public void add(Object o) { + throw new UnsupportedOperationException(); + } + + public void set(Object o) { + throw new UnsupportedOperationException(); + } + }; + } + + + public List subList(final int fromIndex, final int toIndex) { + getterCalled(); + if (fromIndex < 0 || fromIndex > toIndex || toIndex > size()) { + throw new IndexOutOfBoundsException(); + } + return new AbstractObservableList(getRealm()) { + + public Object getElementType() { + return this.outer.getElementType(); + } + + public Object get(int location) { + return this.outer.get(fromIndex + location); + } + + protected int doGetSize() { + return toIndex - fromIndex; + } + }; + } + + protected void getterCalled() { + ObservableTracker.getterCalled(this); + } + + public Object set(int index, Object element) { + throw new UnsupportedOperationException(); + } + + /** + * Moves the element located at oldIndex to + * newIndex. This method is equivalent to calling + * add(newIndex, remove(oldIndex)). + *

+ * Subclasses should override this method to deliver list change + * notification for the remove and add operations in the same + * ListChangeEvent, as this allows {@link ListDiff#acceptcast(ListDiffVisitor)} + * to recognize the operation as a move. + * + * @param oldIndex + * the element's position before the move. Must be within the + * range 0 <= oldIndex < size(). + * @param newIndex + * the element's position after the move. Must be within the + * range 0 <= newIndex < size(). + * @return the element that was moved. + * @throws IndexOutOfBoundsException + * if either argument is out of range (0 <= index < size()). + * @see ListDiffVisitor#handleMove(int, int, Object) + * @see ListDiff#acceptcast(ListDiffVisitor) + * @since 1.1 + */ + public Object move(int oldIndex, int newIndex) { + checkRealm(); + int size = wrappedList.size(); + if (oldIndex < 0 || oldIndex >= size) + throw new IndexOutOfBoundsException( + "oldIndex: " + oldIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$ + if (newIndex < 0 || newIndex >= size) + throw new IndexOutOfBoundsException( + "newIndex: " + newIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$ + Object element = remove(oldIndex); + add(newIndex, element); + return element; + } + + public Object remove(int index) { + throw new UnsupportedOperationException(); + } + + public bool add(Object o) { + throw new UnsupportedOperationException(); + } + + public void add(int index, Object element) { + throw new UnsupportedOperationException(); + } + + public bool addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public bool addAll(int index, Collection c) { + throw new UnsupportedOperationException(); + } + + public bool remove(Object o) { + throw new UnsupportedOperationException(); + } + + public bool removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public bool retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Returns the stale state. Must be invoked from the current realm. + * + * @return stale state + */ + public bool isStale() { + getterCalled(); + return stale; + } + + /** + * Sets the stale state. Must be invoked from the current realm. + * + * @param stale + * The stale state to list. This will fire a stale event if the + * given bool is true and this observable list was not already + * stale. + */ + public void setStale(bool stale) { + checkRealm(); + + bool wasStale = this.stale; + this.stale = stale; + if (!wasStale && stale) { + fireStale(); + } + } + + protected void fireChange() { + throw new RuntimeException("fireChange should not be called, use fireListChange() instead"); //$NON-NLS-1$ + } + + /* (non-Javadoc) + * @see org.eclipse.jface.provisional.databinding.observable.AbstractObservable#dispose() + */ + public synchronized void dispose() { + super.dispose(); + } + + public Object getElementType() { + return elementType; + } + + protected void updateWrappedList(List newList) { + List oldList = wrappedList; + ListDiff listDiff = Diffs.computeListDiff(oldList, newList); + wrappedList = newList; + fireListChange(listDiff); + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/WritableList.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/WritableList.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,239 @@ +/******************************************************************************* + * Copyright (c) 2005, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 164653 + * Brad Reynolds - bug 167204 + * Gautam Saggar - bug 169529 + * Brad Reynolds - bug 147515 + * Matthew Hall - bug 208858, 213145 + *******************************************************************************/ +package org.eclipse.core.databinding.observable.list; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.Realm; + +/** + * Mutable observable list backed by an ArrayList. + * + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * + * @since 1.0 + */ +public class WritableList : ObservableList { + + /** + * Creates an empty writable list in the default realm with a + * null element type. + * + */ + public this() { + this(Realm.getDefault()); + } + + /** + * Creates an empty writable list with a null element type. + * + * @param realm + */ + public this(Realm realm) { + this(realm, new ArrayList(), null); + } + + /** + * Constructs a new instance with the default realm. + * + * @param toWrap + * @param elementType + * can be null + */ + public this(List toWrap, Object elementType) { + this(Realm.getDefault(), toWrap, elementType); + } + + /** + * Creates a writable list containing elements of the given type, wrapping + * an existing client-supplied list. + * + * @param realm + * @param toWrap + * The java.utilList to wrap + * @param elementType + * can be null + */ + public this(Realm realm, List toWrap, Object elementType) { + super(realm, toWrap, elementType); + } + + public Object set(int index, Object element) { + checkRealm(); + Object oldElement = wrappedList.set(index, element); + fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(index, + false, oldElement), Diffs.createListDiffEntry(index, true, + element))); + return oldElement; + } + + /** + * @since 1.1 + */ + public Object move(int oldIndex, int newIndex) { + checkRealm(); + int size = wrappedList.size(); + if (oldIndex < 0 || oldIndex >= size) + throw new IndexOutOfBoundsException( + "oldIndex: " + oldIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$ + if (newIndex < 0 || newIndex >= size) + throw new IndexOutOfBoundsException( + "newIndex: " + newIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$ + if (oldIndex is newIndex) + return wrappedList.get(oldIndex); + Object element = wrappedList.remove(oldIndex); + wrappedList.add(newIndex, element); + fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(oldIndex, + false, element), Diffs.createListDiffEntry(newIndex, true, + element))); + return element; + } + + public Object remove(int index) { + checkRealm(); + Object oldElement = wrappedList.remove(index); + fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(index, + false, oldElement))); + return oldElement; + } + + public bool add(Object element) { + checkRealm(); + bool added = wrappedList.add(element); + if (added) { + fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry( + wrappedList.size() - 1, true, element))); + } + return added; + } + + public void add(int index, Object element) { + checkRealm(); + wrappedList.add(index, element); + fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(index, + true, element))); + } + + public bool addAll(Collection c) { + checkRealm(); + ListDiffEntry[] entries = new ListDiffEntry[c.size()]; + int i = 0; + int addIndex = wrappedList.size(); + for (Iterator it = c.iterator(); it.hasNext();) { + Object element = it.next(); + entries[i++] = Diffs.createListDiffEntry(addIndex++, true, element); + } + bool added = wrappedList.addAll(c); + fireListChange(Diffs.createListDiff(entries)); + return added; + } + + public bool addAll(int index, Collection c) { + checkRealm(); + ListDiffEntry[] entries = new ListDiffEntry[c.size()]; + int i = 0; + int addIndex = index; + for (Iterator it = c.iterator(); it.hasNext();) { + Object element = it.next(); + entries[i++] = Diffs.createListDiffEntry(addIndex++, true, element); + } + bool added = wrappedList.addAll(index, c); + fireListChange(Diffs.createListDiff(entries)); + return added; + } + + public bool remove(Object o) { + checkRealm(); + int index = wrappedList.indexOf(o); + if (index is -1) { + return false; + } + wrappedList.remove(index); + fireListChange(Diffs.createListDiff(Diffs.createListDiffEntry(index, + false, o))); + return true; + } + + public bool removeAll(Collection c) { + checkRealm(); + List entries = new ArrayList(); + for (Iterator it = c.iterator(); it.hasNext();) { + Object element = it.next(); + int removeIndex = wrappedList.indexOf(element); + if (removeIndex !is -1) { + wrappedList.remove(removeIndex); + entries.add(Diffs.createListDiffEntry(removeIndex, false, + element)); + } + } + if (entries.size() > 0) + fireListChange(Diffs.createListDiff(cast(ListDiffEntry[]) entries + .toArray(new ListDiffEntry[entries.size()]))); + return entries.size() > 0; + } + + public bool retainAll(Collection c) { + checkRealm(); + List entries = new ArrayList(); + int removeIndex = 0; + for (Iterator it = wrappedList.iterator(); it.hasNext();) { + Object element = it.next(); + if (!c.contains(element)) { + entries.add(Diffs.createListDiffEntry(removeIndex, false, + element)); + it.remove(); + } else { + // only increment if we haven't removed the current element + removeIndex++; + } + } + if (entries.size() > 0) + fireListChange(Diffs.createListDiff(cast(ListDiffEntry[]) entries + .toArray(new ListDiffEntry[entries.size()]))); + return entries.size() > 0; + } + + public void clear() { + checkRealm(); + List entries = new ArrayList(); + for (Iterator it = wrappedList.iterator(); it.hasNext();) { + Object element = it.next(); + // always report 0 as the remove index + entries.add(Diffs.createListDiffEntry(0, false, element)); + it.remove(); + } + fireListChange(Diffs.createListDiff(cast(ListDiffEntry[]) entries + .toArray(new ListDiffEntry[entries.size()]))); + } + + /** + * @param elementType + * can be null + * @return new list with the default realm. + */ + public static WritableList withElementType(Object elementType) { + return new WritableList(Realm.getDefault(), new ArrayList(), + elementType); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/package.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/list/package.html Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,16 @@ + + + + + + + Package-level Javadoc + + +Provides classes for observing changes in lists. +

+Package Specification

+

+This package provides classes for observing changes in lists.

+ + diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/AbstractObservableMap.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/AbstractObservableMap.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,181 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 164653 + * Matthew Hall - bugs 118516, 240931 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.map; + +import java.util.AbstractMap; + +import org.eclipse.core.databinding.observable.ChangeEvent; +import org.eclipse.core.databinding.observable.ChangeSupport; +import org.eclipse.core.databinding.observable.IChangeListener; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.AssertionFailedException; + +/** + * + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * @since 1.0 + */ +public abstract class AbstractObservableMap : AbstractMap , + IObservableMap { + + private ChangeSupport changeSupport; + + private bool stale; + + /** + */ + public this() { + this(Realm.getDefault()); + } + + /** + * + */ + protected void lastListenerRemoved() { + } + + /** + * + */ + protected void firstListenerAdded() { + } + + /** + * @param realm + */ + public this(Realm realm) { + Assert.isNotNull(realm, "Realm cannot be null"); //$NON-NLS-1$ + changeSupport = new class(realm) ChangeSupport { + protected void firstListenerAdded() { + this.outer.firstListenerAdded(); + } + protected void lastListenerRemoved() { + this.outer.lastListenerRemoved(); + } + }; + } + + public synchronized void addMapChangeListener(IMapChangeListener listener) { + if (changeSupport !is null) { + changeSupport.addListener(MapChangeEvent.TYPE, listener); + } + } + + public synchronized void removeMapChangeListener(IMapChangeListener listener) { + if (changeSupport !is null) { + changeSupport.removeListener(MapChangeEvent.TYPE, listener); + } + } + + public synchronized void addChangeListener(IChangeListener listener) { + if (changeSupport !is null) { + changeSupport.addChangeListener(listener); + } + } + + public synchronized void addStaleListener(IStaleListener listener) { + if (changeSupport !is null) { + changeSupport.addStaleListener(listener); + } + } + + public synchronized void dispose() { + if (changeSupport !is null) { + changeSupport.dispose(); + changeSupport = null; + } + } + + public Realm getRealm() { + if (changeSupport !is null) { + return changeSupport.getRealm(); + } + return null; + } + + public bool isStale() { + checkRealm(); + return stale; + } + + public synchronized void removeChangeListener(IChangeListener listener) { + if (changeSupport !is null) { + changeSupport.removeChangeListener(listener); + } + } + + public synchronized void removeStaleListener(IStaleListener listener) { + if (changeSupport !is null) { + changeSupport.removeStaleListener(listener); + } + } + + /** + * Sets the stale state. Must be invoked from the current realm. + * + * @param stale + */ + public void setStale(bool stale) { + checkRealm(); + this.stale = stale; + if (stale) { + fireStale(); + } + } + + /** + * Fires stale events. Must be invoked from current realm. + */ + protected void fireStale() { + checkRealm(); + changeSupport.fireEvent(new StaleEvent(this)); + } + + /** + * Fires change events. Must be invoked from current realm. + */ + protected void fireChange() { + checkRealm(); + changeSupport.fireEvent(new ChangeEvent(this)); + } + + /** + * Fires map change events. Must be invoked from current realm. + * + * @param diff + */ + protected void fireMapChange(MapDiff diff) { + checkRealm(); + changeSupport.fireEvent(new MapChangeEvent(this, diff)); + } + + /** + * Asserts that the realm is the current realm. + * + * @see Realm#isCurrent() + * @throws AssertionFailedException + * if the realm is not the current realm + */ + protected void checkRealm() { + Assert.isTrue(getRealm().isCurrent(), + "This operation must be run within the observable's realm"); //$NON-NLS-1$ + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/BidirectionalMap.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/BidirectionalMap.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,104 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.databinding.observable.map; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.databinding.observable.Realm; + +/** + * + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * @since 1.0 + * + */ +public class BidirectionalMap : ObservableMap { + + private Map valueToElements = new HashMap(); + + private IMapChangeListener mapListener = new class() IMapChangeListener { + + public void handleMapChange(MapChangeEvent event) { + MapDiff diff = event.diff; + for (Iterator it = diff.getAddedKeys().iterator(); it.hasNext();) { + Object addedKey = it.next(); + addMapping(addedKey, diff.getNewValue(addedKey)); + } + for (Iterator it = diff.getChangedKeys().iterator(); it.hasNext();) { + Object changedKey = it.next(); + removeMapping(changedKey, diff.getOldValue(changedKey)); + addMapping(changedKey, diff.getNewValue(changedKey)); + } + for (Iterator it = diff.getRemovedKeys().iterator(); it.hasNext();) { + Object removedKey = it.next(); + removeMapping(removedKey, diff.getOldValue(removedKey)); + } + fireMapChange(diff); + } + }; + + /** + * @param wrappedMap + */ + public this(IObservableMap wrappedMap) { + super(wrappedMap.getRealm(), wrappedMap); + wrappedMap.addMapChangeListener(mapListener); + for (Iterator it = wrappedMap.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = cast(Entry) it.next(); + addMapping(entry.getKey(), entry.getValue()); + } + } + + /** + * @param key + * @param value + */ + private void addMapping(Object key, Object value) { + Object elementOrSet = valueToElements.get(value); + if (elementOrSet is null) { + valueToElements.put(value, key); + return; + } + if (!( null !is cast(Set)elementOrSet )) { + elementOrSet = new HashSet(Collections.singleton(elementOrSet)); + valueToElements.put(value, elementOrSet); + } + Set set = cast(Set) elementOrSet; + set.add(key); + } + + /** + * @param functionValue + * @param element + */ + private void removeMapping(Object functionValue, Object element) { + Object elementOrSet = valueToElements.get(functionValue); + if ( null !is cast(Set)elementOrSet ) { + Set set = cast(Set) elementOrSet; + set.remove(element); + if (set.size() is 0) { + valueToElements.remove(functionValue); + } + } else { + valueToElements.remove(functionValue); + } + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/CompositeMap.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/CompositeMap.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,361 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.databinding.observable.map; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; +import org.eclipse.core.databinding.observable.set.WritableSet; +import org.eclipse.core.runtime.Assert; + +/** + * A read-only observable map formed by the composition of two observable maps. + * If map1 maps keys a:A to values b1:B, and map2 maps keys b2:B to values c:C, + * the composite map maps keys a:A to values c:C. For example, map1 could map + * Order objects to their corresponding Customer objects, and map2 could map + * Customer objects to their "last name" property of type String. The composite + * map of map1 and map2 would then map Order objects to their customers' last + * names. + * + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * + * @since 1.1 + * + */ +public class CompositeMap : ObservableMap { + + private Map valueToElements = new HashMap(); + + // adds that need to go through the second map and thus will be picked up by + // secondMapListener. + private Set pendingAdds = new HashSet(); + + // Removes that need to go through the second map and thus will be picked up + // by + // secondMapListener. Maps from value being removed to key being removed. + private Map pendingRemoves = new HashMap(); + + // Changes that need to go through the second map and thus will be picked up + // by + // secondMapListener. Maps from old value to new value and new value to old + // value. + private Map pendingChanges = new HashMap(); + + private IMapChangeListener firstMapListener = new class() IMapChangeListener { + + public void handleMapChange(MapChangeEvent event) { + MapDiff diff = event.diff; + Set rangeSetAdditions = new HashSet(); + Set rangeSetRemovals = new HashSet(); + final Set adds = new HashSet(); + final Set changes = new HashSet(); + final Set removes = new HashSet(); + final Map oldValues = new HashMap(); + + for (Iterator it = diff.getAddedKeys().iterator(); it.hasNext();) { + Object addedKey = it.next(); + Object newValue = diff.getNewValue(addedKey); + addMapping(addedKey, newValue); + if (!rangeSet.contains(newValue)) { + pendingAdds.add(newValue); + rangeSetAdditions.add(newValue); + } else { + adds.add(addedKey); + wrappedMap.put(addedKey, secondMap.get(newValue)); + } + } + for (Iterator it = diff.getChangedKeys().iterator(); it.hasNext();) { + Object changedKey = it.next(); + Object oldValue = diff.getOldValue(changedKey); + Object newValue = diff.getNewValue(changedKey); + bool removed = removeMapping(changedKey, oldValue); + addMapping(changedKey, newValue); + bool added = !rangeSet.contains(newValue); + if (removed) { + pendingRemoves.put(oldValue, changedKey); + rangeSetRemovals.add(oldValue); + } + if (added) { + pendingAdds.add(newValue); + rangeSetAdditions.add(newValue); + } + if (added || removed) { + pendingChanges.put(oldValue, newValue); + pendingChanges.put(newValue, oldValue); + } else { + changes.add(changedKey); + oldValues.put(changedKey, oldValue); + wrappedMap.put(changedKey, secondMap.get(newValue)); + } + } + for (Iterator it = diff.getRemovedKeys().iterator(); it.hasNext();) { + Object removedKey = it.next(); + Object oldValue = diff.getOldValue(removedKey); + if (removeMapping(removedKey, oldValue)) { + pendingRemoves.put(oldValue, removedKey); + rangeSetRemovals.add(oldValue); + } else { + removes.add(removedKey); + oldValues.put(removedKey, secondMap.get(oldValue)); + wrappedMap.remove(removedKey); + } + } + + if (adds.size() > 0 || removes.size() > 0 || changes.size() > 0) { + fireMapChange(new class() MapDiff { + + public Set getAddedKeys() { + return adds; + } + + public Set getChangedKeys() { + return changes; + } + + public Object getNewValue(Object key) { + return wrappedMap.get(key); + } + + public Object getOldValue(Object key) { + return oldValues.get(key); + } + + public Set getRemovedKeys() { + return removes; + } + }); + } + + if (rangeSetAdditions.size() > 0 || rangeSetRemovals.size() > 0) { + rangeSet.addAndRemove(rangeSetAdditions, rangeSetRemovals); + } + } + }; + + private IMapChangeListener secondMapListener = new class() IMapChangeListener { + + public void handleMapChange(MapChangeEvent event) { + MapDiff diff = event.diff; + final Set adds = new HashSet(); + final Set changes = new HashSet(); + final Set removes = new HashSet(); + final Map oldValues = new HashMap(); + final Map newValues = new HashMap(); + Set addedKeys = new HashSet(diff.getAddedKeys()); + Set removedKeys = new HashSet(diff.getRemovedKeys()); + + for (Iterator it = addedKeys.iterator(); it.hasNext();) { + Object addedKey = it.next(); + Set elements = getElementsForValue(addedKey); + Object newValue = diff.getNewValue(addedKey); + if (pendingChanges.containsKey(addedKey)) { + Object oldKey = pendingChanges.remove(addedKey); + Object oldValue; + if (removedKeys.remove(oldKey)) { + oldValue = diff.getOldValue(oldKey); + } else { + oldValue = secondMap.get(oldKey); + } + pendingChanges.remove(oldKey); + pendingAdds.remove(addedKey); + pendingRemoves.remove(oldKey); + for (Iterator it2 = elements.iterator(); it2.hasNext();) { + Object element = it2.next(); + changes.add(element); + oldValues.put(element, oldValue); + newValues.put(element, newValue); + wrappedMap.put(element, newValue); + } + } else if (pendingAdds.remove(addedKey)) { + for (Iterator it2 = elements.iterator(); it2.hasNext();) { + Object element = it2.next(); + adds.add(element); + newValues.put(element, newValue); + wrappedMap.put(element, newValue); + } + } else { + Assert.isTrue(false, "unexpected case"); //$NON-NLS-1$ + } + } + for (Iterator it = diff.getChangedKeys().iterator(); it.hasNext();) { + Object changedKey = it.next(); + Set elements = getElementsForValue(changedKey); + for (Iterator it2 = elements.iterator(); it2.hasNext();) { + Object element = it2.next(); + changes.add(element); + oldValues.put(element, diff.getOldValue(changedKey)); + Object newValue = diff.getNewValue(changedKey); + newValues.put(element, newValue); + wrappedMap.put(element, newValue); + } + } + for (Iterator it = removedKeys.iterator(); it.hasNext();) { + Object removedKey = it.next(); + Object element = pendingRemoves.remove(removedKey); + if (element !is null) { + if (pendingChanges.containsKey(removedKey)) { + Object newKey = pendingChanges.remove(removedKey); + pendingChanges.remove(newKey); + pendingAdds.remove(newKey); + pendingRemoves.remove(removedKey); + changes.add(element); + oldValues.put(element, diff.getOldValue(removedKey)); + Object newValue = secondMap.get(newKey); + newValues.put(element, newValue); + wrappedMap.put(element, newValue); + } else { + removes.add(element); + Object oldValue = diff.getOldValue(removedKey); + oldValues.put(element, oldValue); + wrappedMap.remove(element); + } + } else { + Assert.isTrue(false, "unexpected case"); //$NON-NLS-1$ + } + } + + if (adds.size() > 0 || removes.size() > 0 || changes.size() > 0) { + fireMapChange(new class() MapDiff { + + public Set getAddedKeys() { + return adds; + } + + public Set getChangedKeys() { + return changes; + } + + public Object getNewValue(Object key) { + return newValues.get(key); + } + + public Object getOldValue(Object key) { + return oldValues.get(key); + } + + public Set getRemovedKeys() { + return removes; + } + }); + } + } + }; + + private IObservableMap firstMap; + private IObservableMap secondMap; + + private static class WritableSetPlus : WritableSet { + void addAndRemove(Set additions, Set removals) { + wrappedSet.removeAll(removals); + wrappedSet.addAll(additions); + fireSetChange(Diffs.createSetDiff(additions, removals)); + } + } + + private WritableSetPlus rangeSet = new WritableSetPlus(); + + /** + * Creates a new composite map. Because the key set of the second map is + * determined by the value set of the given observable map + * firstMap, it cannot be passed in as an argument. Instead, + * the second map will be created by calling + * secondMapFactory.createObservable(valueSet()). + * + * @param firstMap + * the first map + * @param secondMapFactory + * a factory that creates the second map when given an observable + * set representing the value set of firstMap. + */ + public this(IObservableMap firstMap, + IObservableFactory secondMapFactory) { + super(firstMap.getRealm(), new HashMap()); + this.firstMap = firstMap; + firstMap.addMapChangeListener(firstMapListener); + for (Iterator it = firstMap.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = cast(Entry) it.next(); + addMapping(entry.getKey(), entry.getValue()); + rangeSet.add(entry.getValue()); + } + this.secondMap = cast(IObservableMap) secondMapFactory + .createObservable(rangeSet); + secondMap.addMapChangeListener(secondMapListener); + for (Iterator it = firstMap.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = cast(Entry) it.next(); + wrappedMap.put(entry.getKey(), secondMap.get(entry.getValue())); + } + } + + /** + * @param key + * @param value + */ + private void addMapping(Object key, Object value) { + Object elementOrSet = valueToElements.get(value); + if (elementOrSet is null) { + valueToElements.put(value, key); + return; + } + if (!( null !is cast(Set)elementOrSet )) { + elementOrSet = new HashSet(Collections.singleton(elementOrSet)); + valueToElements.put(value, elementOrSet); + } + Set set = cast(Set) elementOrSet; + set.add(key); + } + + /** + * @param key + * @param value + */ + private bool removeMapping(Object key, Object value) { + Object elementOrSet = valueToElements.get(value); + if ( null !is cast(Set)elementOrSet ) { + Set set = cast(Set) elementOrSet; + set.remove(key); + if (set.size() is 0) { + valueToElements.remove(value); + return true; + } + return false; + } + valueToElements.remove(value); + return true; + } + + private Set getElementsForValue(Object value) { + Object elementOrSet = valueToElements.get(value); + if ( null !is cast(Set)elementOrSet ) { + return cast(Set) elementOrSet; + } + return elementOrSet is null ? Collections.EMPTY_SET : Collections + .singleton(elementOrSet); + } + + public synchronized void dispose() { + super.dispose(); + firstMap.removeMapChangeListener(firstMapListener); + firstMap = null; + secondMap = null; + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/ComputedObservableMap.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/ComputedObservableMap.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.map; + +import java.util.AbstractSet; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.set.IObservableSet; +import org.eclipse.core.databinding.observable.set.ISetChangeListener; +import org.eclipse.core.databinding.observable.set.SetChangeEvent; + +/** + * Maps objects to one of their attributes. Tracks changes to the underlying + * observable set of objects (keys), as well as changes to attribute values. + */ +public abstract class ComputedObservableMap : AbstractObservableMap { + + private final IObservableSet keySet; + + private ISetChangeListener setChangeListener = new class() ISetChangeListener { + public void handleSetChange(SetChangeEvent event) { + Set addedKeys = new HashSet(event.diff.getAdditions()); + Set removedKeys = new HashSet(event.diff.getRemovals()); + Map oldValues = new HashMap(); + Map newValues = new HashMap(); + for (Iterator it = removedKeys.iterator(); it.hasNext();) { + Object removedKey = it.next(); + Object oldValue = doGet(removedKey); + unhookListener(removedKey); + if (oldValue !is null) { + oldValues.put(removedKey, oldValue); + } + } + for (Iterator it = addedKeys.iterator(); it.hasNext();) { + Object addedKey = it.next(); + hookListener(addedKey); + Object newValue = doGet(addedKey); + newValues.put(addedKey, newValue); + } + fireMapChange(Diffs.createMapDiff(addedKeys, removedKeys, + Collections.EMPTY_SET, oldValues, newValues)); + } + }; + + private Set entrySet = new EntrySet(); + + private class EntrySet : AbstractSet { + + public Iterator iterator() { + final Iterator keyIterator = keySet.iterator(); + return new class() Iterator { + + public bool hasNext() { + return keyIterator.hasNext(); + } + + public Object next() { + final Object key = keyIterator.next(); + return new Map.Entry() { + + public Object getKey() { + return key; + } + + public Object getValue() { + return get(getKey()); + } + + public Object setValue(Object value) { + return put(getKey(), value); + } + }; + } + + public void remove() { + keyIterator.remove(); + } + }; + } + + public int size() { + return keySet.size(); + } + + } + + /** + * @param keySet + */ + public this(IObservableSet keySet) { + super(keySet.getRealm()); + this.keySet = keySet; + this.keySet.addSetChangeListener(setChangeListener); + } + + protected void init() { + for (Iterator it = this.keySet.iterator(); it.hasNext();) { + Object key = it.next(); + hookListener(key); + } + } + + protected final void fireSingleChange(Object key, Object oldValue, + Object newValue) { + fireMapChange(Diffs.createMapDiffSingleChange(key, oldValue, newValue)); + } + + public Set entrySet() { + return entrySet; + } + + public Set keySet() { + return keySet; + } + + final public Object get(Object key) { + return doGet(key); + } + + final public Object put(Object key, Object value) { + return doPut(key, value); + } + + /** + * @param removedKey + */ + protected abstract void unhookListener(Object removedKey); + + /** + * @param addedKey + */ + protected abstract void hookListener(Object addedKey); + + /** + * @param key + * @return the value for the given key + */ + protected abstract Object doGet(Object key); + + /** + * @param key + * @param value + * @return the old value for the given key + */ + protected abstract Object doPut(Object key, Object value); +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/IMapChangeListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/IMapChangeListener.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.map; + +import org.eclipse.core.databinding.observable.IObservablesListener; + +/** + * Listener for changes to observable maps. + * + * @since 1.0 + * + */ +public interface IMapChangeListener : IObservablesListener { + + /** + * Handle a change an observable map. The given event object must only be + * used locally in this method because it may be reused for other change + * notifications. The diff object referenced by the event is immutable and + * may be used non-locally. + * + * @param event + * the event + */ + void handleMapChange(MapChangeEvent event); + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/IObservableMap.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/IObservableMap.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 164653 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.map; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.databinding.observable.IObservable; + +/** + * Observable Map. + * + * @noextend This interface is not intended to be extended by clients. + * @noimplement This interface is not intended to be implemented by clients. + * Clients should instead subclass one of the classes that + * implement this interface. Note that direct implementers of this + * interface outside of the framework will be broken in future + * releases when methods are added to this interface. + * + * @see AbstractObservableMap + * @see ObservableMap + * + * @since 1.1 + */ +public interface IObservableMap : Map, IObservable { + + /** + * @param listener + */ + public void addMapChangeListener(IMapChangeListener listener); + + /** + * @param listener + */ + public void removeMapChangeListener(IMapChangeListener listener); + + /** + * @TrackedGetter + */ + public int size(); + + /** + * @TrackedGetter + */ + public bool isEmpty(); + + /** + * @TrackedGetter + */ + public bool containsKey(Object key); + + /** + * @TrackedGetter + */ + public bool containsValue(Object value); + + /** + * @TrackedGetter + */ + public Object get(Object key); + + /** + * + */ + public Object put(Object key, Object value); + + /** + * + */ + public Object remove(Object key); + + /** + * @TrackedGetter + */ + public Set keySet(); + + /** + * @TrackedGetter + */ + public Collection values(); + + /** + * @TrackedGetter + */ + public Set entrySet(); + + /** + * @TrackedGetter + */ + public override equals_t opEquals(Object o); + + /** + * @TrackedGetter + */ + public override hash_t toHash(); +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/MapChangeEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/MapChangeEvent.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.core.databinding.observable.map; + +import org.eclipse.core.databinding.observable.IObservablesListener; +import org.eclipse.core.databinding.observable.ObservableEvent; + +/** + * Map change event describing an incremental change of an + * {@link IObservableMap} object. + * + * @since 1.0 + * + */ +public class MapChangeEvent : ObservableEvent { + + /** + * + */ + private static final long serialVersionUID = -8092347212410548463L; + static final Object TYPE = new Object(); + + /** + * Description of the change to the source observable map. Listeners must + * not change this field. + */ + public MapDiff diff; + + /** + * Creates a new map change event + * + * @param source + * the source observable map + * @param diff + * the map change + */ + public this(IObservableMap source, MapDiff diff) { + super(source); + this.diff = diff; + } + + /** + * Returns the observable map from which this event originated. + * + * @return the observable map from which this event originated + */ + public IObservableMap getObservableMap() { + return cast(IObservableMap) getSource(); + } + + protected void dispatch(IObservablesListener listener) { + (cast(IMapChangeListener) listener).handleMapChange(this); + } + + protected Object getListenerType() { + return TYPE; + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/MapDiff.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/MapDiff.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.map; + +import java.util.Set; + +/** + * @since 1.1 + * + */ +public abstract class MapDiff { + + /** + * @return the set of keys which were added + */ + public abstract Set getAddedKeys(); + + /** + * @return the set of keys which were removed + */ + public abstract Set getRemovedKeys(); + + /** + * @return the set of keys for which the value has changed + */ + public abstract Set getChangedKeys(); + + /** + * Returns the old value for the given key, which must be an element of + * {@link #getRemovedKeys()} or {@link #getChangedKeys()}. + * + * @param key + * @return the old value for the given key. + */ + public abstract Object getOldValue(Object key); + + /** + * Returns the new value for the given key, which must be an element of + * {@link #getChangedKeys()} or {@link #getAddedKeys()}. + * + * @param key + * @return the new value for the given key. + */ + public abstract Object getNewValue(Object key); +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/ObservableMap.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/ObservableMap.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,182 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 164653 + * Matthew Hall - bug 245183 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.map; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.databinding.observable.AbstractObservable; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.Realm; + +/** + * + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * @since 1.0 + */ +public class ObservableMap : AbstractObservable , IObservableMap { + + protected Map wrappedMap; + + private bool stale = false; + + /** + * @param wrappedMap + */ + public this(Map wrappedMap) { + this(Realm.getDefault(), wrappedMap); + } + + /** + * @param realm + * @param wrappedMap + */ + public this(Realm realm, Map wrappedMap) { + super(realm); + this.wrappedMap = wrappedMap; + } + + public synchronized void addMapChangeListener(IMapChangeListener listener) { + addListener(MapChangeEvent.TYPE, listener); + } + + public synchronized void removeMapChangeListener(IMapChangeListener listener) { + removeListener(MapChangeEvent.TYPE, listener); + } + + protected void getterCalled() { + ObservableTracker.getterCalled(this); + } + + protected void fireMapChange(MapDiff diff) { + checkRealm(); + + // fire general change event first + super.fireChange(); + + fireEvent(new MapChangeEvent(this, diff)); + } + + public bool containsKey(Object key) { + getterCalled(); + return wrappedMap.containsKey(key); + } + + public bool containsValue(Object value) { + getterCalled(); + return wrappedMap.containsValue(value); + } + + public Set entrySet() { + getterCalled(); + return wrappedMap.entrySet(); + } + + public Object get(Object key) { + getterCalled(); + return wrappedMap.get(key); + } + + public bool isEmpty() { + getterCalled(); + return wrappedMap.isEmpty(); + } + + public Set keySet() { + getterCalled(); + return wrappedMap.keySet(); + } + + public int size() { + getterCalled(); + return wrappedMap.size(); + } + + public Collection values() { + getterCalled(); + return wrappedMap.values(); + } + + /** + * Returns the stale state. Must be invoked from the current realm. + * + * @return stale state + */ + public bool isStale() { + checkRealm(); + return stale; + } + + /** + * Sets the stale state. Must be invoked from the current realm. + * + * @param stale + * The stale state to set. This will fire a stale event if the + * given bool is true and this observable set was not already + * stale. + */ + public void setStale(bool stale) { + checkRealm(); + bool wasStale = this.stale; + this.stale = stale; + if (!wasStale && stale) { + fireStale(); + } + } + + public Object put(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + public Object remove(Object key) { + throw new UnsupportedOperationException(); + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + public void putAll(Map arg0) { + throw new UnsupportedOperationException(); + } + + public synchronized void dispose() { + super.dispose(); + } + + public override equals_t opEquals(Object obj) { + getterCalled(); + + if (obj is this) + return true; + if (obj is null) + return false; + if (getClass() is obj.getClass()) { + return wrappedMap.equals((cast(ObservableMap) obj).wrappedMap); + } + + return wrappedMap.equals(obj); + } + + public override hash_t toHash() { + getterCalled(); + + return wrappedMap.hashCode(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/WritableMap.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/WritableMap.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2006-2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 164653 + * Matthew Hall - bug 184830 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.map; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.internal.databinding.Util; + +/** + * + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * @since 1.0 + */ +public class WritableMap : ObservableMap { + + /** + * Constructs a new WritableMap on the default realm. + */ + public this() { + this(Realm.getDefault()); + } + + /** + * Constructs a new WritableMap on the given realm. + * + * @param realm + * the realm + */ + public this(Realm realm) { + super(realm, new HashMap()); + } + + /** + * Associates the provided value with the key. Must be invoked from the current realm. + */ + public Object put(Object key, Object value) { + checkRealm(); + Object result = wrappedMap.put(key, value); + if (!Util.equals(result, value)) { + if (resultisnull) { + fireMapChange(Diffs.createMapDiffSingleAdd(key, value)); + } else { + fireMapChange(Diffs.createMapDiffSingleChange(key, result, + value)); + } + } + return result; + } + + /** + * Removes the value with the provide key. Must be invoked from the current realm. + */ + public Object remove(Object key) { + checkRealm(); + Object result = wrappedMap.remove(key); + if (result!isnull) { + fireMapChange(Diffs.createMapDiffSingleRemove(key, result)); + } + return result; + } + + /** + * Clears the map. Must be invoked from the current realm. + */ + public void clear() { + checkRealm(); + if (!isEmpty()) { + Map copy = new HashMap(wrappedMap); + wrappedMap.clear(); + fireMapChange(Diffs.createMapDiffRemoveAll(copy)); + } + } + + /** + * Adds the provided map's contents to this map. Must be invoked from the current realm. + */ + public void putAll(Map map) { + checkRealm(); + Set addedKeys = new HashSet(map.size()); + Map changes = new HashMap(map.size()); + for (Iterator it = map.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = cast(Entry) it.next(); + Object previousValue = wrappedMap.put(entry.getKey(), entry.getValue()); + if (previousValueisnull) { + addedKeys.add(entry.getKey()); + } else { + changes.put(entry.getKey(), previousValue); + } + } + if (!addedKeys.isEmpty() || !changes.isEmpty()) { + fireMapChange(Diffs.createMapDiff(addedKeys, Collections.EMPTY_SET, changes.keySet(), changes, wrappedMap)); + } + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/package.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/map/package.html Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,16 @@ + + + + + + + Package-level Javadoc + + +Provides classes that can be used to observe changes in maps. +

+Package Specification

+

+This package provides classes that can be used to observe changes in maps.

+ + diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/IObservableFactory.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/IObservableFactory.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.masterdetail; + +import org.eclipse.core.databinding.observable.IObservable; + +/** + * Generates an {@link IObservable} when passed a target instance. + * + * @since 1.0 + */ +public interface IObservableFactory { + + /** + * Creates an observable for the given target object. + * + * @param target + * @return the new observable + */ + public IObservable createObservable(Object target); + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 147515 + * Matthew Hall - bug 221704 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.masterdetail; + +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.map.IObservableMap; +import org.eclipse.core.databinding.observable.set.IObservableSet; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableList; +import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableMap; +import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableSet; +import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableValue; + +/** + * Allows for the observation of an attribute, the detail, of an observable + * representing selection or another transient instance, the master. + * + * @since 1.0 + */ +public class MasterDetailObservables { + + /** + * Creates a detail observable value from a master observable value and a + * factory. This can be used to create observable values that represent a + * property of a selected object in a table. + * + * @param master + * the observable value to track + * @param detailFactory + * a factory for creating {@link IObservableValue} instances + * given a current value of the master + * @param detailType + * the value type of the detail observable value, typically of + * type java.lang.Class and can be null + * @return an observable value of the given value type that, for any current + * value of the given master value, behaves like the observable + * value created by the factory for that current value. + */ + public static IObservableValue detailValue(IObservableValue master, + IObservableFactory detailFactory, Object detailType) { + return new DetailObservableValue(master, detailFactory, detailType); + } + + /** + * Creates a detail observable list from a master observable value and a + * factory. This can be used to create observable lists that represent a + * list property of a selected object in a table. + * + * @param master + * the observable value to track + * @param detailFactory + * a factory for creating {@link IObservableList} instances given + * a current value of the master + * @param detailElementType + * the element type of the detail observable list, typically of + * type java.lang.Class and can be null + * @return an observable list with the given element type that, for any + * current value of the given master value, behaves like the + * observable list created by the factory for that current value. + */ + public static IObservableList detailList(IObservableValue master, + IObservableFactory detailFactory, Object detailElementType) { + return new DetailObservableList(detailFactory, master, + detailElementType); + } + + /** + * Creates a detail observable set from a master observable value and a + * factory. This can be used to create observable sets that represent a set + * property of a selected object in a table. + * + * @param master + * the observable value to track + * @param detailFactory + * a factory for creating {@link IObservableSet} instances given + * a current value of the master + * @param detailElementType + * the element type of the detail observable set, typically of + * type java.lang.Class and can be null + * @return an observable set with the given element type that, for any + * current value of the given master value, behaves like the + * observable set created by the factory for that current value. + */ + public static IObservableSet detailSet(IObservableValue master, + IObservableFactory detailFactory, Object detailElementType) { + return new DetailObservableSet(detailFactory, master, detailElementType); + } + + /** + * Creates a detail observable map from a master observable value and a + * factory. This can be used to create observable maps that represent a map + * property of a selected object in a table. + * + * @param master + * the observable value to track + * @param detailFactory + * a factory for createing {@link IObservableMap} instances given + * a current value of the master + * @return an observable map that, for any current value of the given master + * value, behaves like the observable map created by the factory for + * that current value. + * @since 1.1 + */ + public static IObservableMap detailMap(IObservableValue master, + IObservableFactory detailFactory) { + return new DetailObservableMap(detailFactory, master); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/package.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/package.html Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,17 @@ + + + + + + + Package-level Javadoc + + +Provides classes that can be used to observe a detail of a master object. +

+Package Specification

+

+This package provides classes that can be used to observe a detail of a master object. +A common use case for master detail is observing the detail (e.g. name) of a master (e.g. selected Person) of a list of elements.

+ + diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/package.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/package.html Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,16 @@ + + + + + + + Package-level Javadoc + + +Provides the core APIs for observing changes in objects. +

+Package Specification

+

+This package provides the core APIs for observing changes in objects.

+ + diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/AbstractObservableSet.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/AbstractObservableSet.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,218 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matthew Hall - bug 208332 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.set; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import org.eclipse.core.databinding.observable.AbstractObservable; +import org.eclipse.core.databinding.observable.ChangeSupport; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.Realm; + +/** + * + * Abstract implementation of {@link IObservableSet}. + * + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * + * @since 1.0 + */ +public abstract class AbstractObservableSet : AbstractObservable , + IObservableSet { + + private ChangeSupport changeSupport; + + private bool stale = false; + + protected this() { + this(Realm.getDefault()); + } + + protected void firstListenerAdded() { + super.firstListenerAdded(); + } + + protected void lastListenerRemoved() { + super.lastListenerRemoved(); + } + + protected this(Realm realm) { + super(realm); + changeSupport = new class(realm) ChangeSupport { + protected void firstListenerAdded() { + this.outer.firstListenerAdded(); + } + protected void lastListenerRemoved() { + this.outer.lastListenerRemoved(); + } + }; + } + + public synchronized void addSetChangeListener(ISetChangeListener listener) { + changeSupport.addListener(SetChangeEvent.TYPE, listener); + } + + public synchronized void removeSetChangeListener(ISetChangeListener listener) { + changeSupport.removeListener(SetChangeEvent.TYPE, listener); + } + + protected abstract Set getWrappedSet(); + + protected void fireSetChange(SetDiff diff) { + // fire general change event first + super.fireChange(); + + changeSupport.fireEvent(new SetChangeEvent(this, diff)); + } + + public bool contains(Object o) { + getterCalled(); + return getWrappedSet().contains(o); + } + + public bool containsAll(Collection c) { + getterCalled(); + return getWrappedSet().containsAll(c); + } + + public override equals_t opEquals(Object o) { + getterCalled(); + return getWrappedSet().equals(o); + } + + public override hash_t toHash() { + getterCalled(); + return getWrappedSet().hashCode(); + } + + public bool isEmpty() { + getterCalled(); + return getWrappedSet().isEmpty(); + } + + public Iterator iterator() { + getterCalled(); + final Iterator wrappedIterator = getWrappedSet().iterator(); + return new class() Iterator { + + public void remove() { + throw new UnsupportedOperationException(); + } + + public bool hasNext() { + ObservableTracker.getterCalled(this.outer); + return wrappedIterator.hasNext(); + } + + public Object next() { + ObservableTracker.getterCalled(this.outer); + return wrappedIterator.next(); + } + }; + } + + public int size() { + getterCalled(); + return getWrappedSet().size(); + } + + public Object[] toArray() { + getterCalled(); + return getWrappedSet().toArray(); + } + + public Object[] toArray(Object[] a) { + getterCalled(); + return getWrappedSet().toArray(a); + } + + public String toString() { + getterCalled(); + return getWrappedSet().toString(); + } + + protected void getterCalled() { + ObservableTracker.getterCalled(this); + } + + public bool add(Object o) { + throw new UnsupportedOperationException(); + } + + public bool addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public bool remove(Object o) { + throw new UnsupportedOperationException(); + } + + public bool removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public bool retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + /** + * @return Returns the stale state. + */ + public bool isStale() { + getterCalled(); + return stale; + } + + /** + * @param stale + * The stale state to set. This will fire a stale event if the + * given bool is true and this observable set was not already + * stale. + */ + public void setStale(bool stale) { + checkRealm(); + bool wasStale = this.stale; + this.stale = stale; + if (!wasStale && stale) { + fireStale(); + } + } + + + protected void fireChange() { + throw new RuntimeException("fireChange should not be called, use fireSetChange() instead"); //$NON-NLS-1$ + } + + /* (non-Javadoc) + * @see org.eclipse.jface.provisional.databinding.observable.AbstractObservable#dispose() + */ + public synchronized void dispose() { + super.dispose(); + + if (changeSupport !is null) { + changeSupport.dispose(); + changeSupport = null; + } + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/IObservableSet.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/IObservableSet.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.set; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import org.eclipse.core.databinding.observable.IObservableCollection; + +/** + * A set whose changes can be tracked by set change listeners. + * + * @noextend This interface is not intended to be extended by clients. + * @noimplement This interface is not intended to be implemented by clients. + * Clients should instead subclass one of the classes that + * implement this interface. Note that direct implementers of this + * interface outside of the framework will be broken in future + * releases when methods are added to this interface. + * + * @see AbstractObservableSet + * @see ObservableSet + * + * @since 1.0 + * + */ +public interface IObservableSet : Set, IObservableCollection { + + /** + * @param listener + */ + public void addSetChangeListener(ISetChangeListener listener); + + /** + * @param listener + */ + public void removeSetChangeListener(ISetChangeListener listener); + + /** + * @return the element type or null if untyped + */ + public Object getElementType(); + + /** + * @TrackedGetter + */ + int size(); + + /** + * @TrackedGetter + */ + bool isEmpty(); + + /** + * @TrackedGetter + */ + bool contains(Object o); + + /** + * @TrackedGetter + */ + Iterator iterator(); + + /** + * @TrackedGetter + */ + Object[] toArray(); + + /** + * @TrackedGetter + */ + Object[] toArray(Object a[]); + + // Modification Operations + + /** + * @TrackedGetter + */ + bool add(Object o); + + /** + * @TrackedGetter + */ + bool remove(Object o); + + // Bulk Operations + + /** + * @TrackedGetter + */ + bool containsAll(Collection c); + + /** + * @TrackedGetter + */ + bool addAll(Collection c); + + /** + * @TrackedGetter + */ + bool retainAll(Collection c); + + /** + * @TrackedGetter + */ + bool removeAll(Collection c); + + // Comparison and hashing + + /** + * @TrackedGetter + */ + override equals_t opEquals(Object o); + + /** + * @TrackedGetter + */ + override hash_t toHash(); + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/ISetChangeListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/ISetChangeListener.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.set; + +import org.eclipse.core.databinding.observable.IObservablesListener; + +/** + * Listener for changes to observable sets. + * + * @since 1.0 + * + */ +public interface ISetChangeListener : IObservablesListener { + + /** + * Handle a change to an observable set. The given event object must only be + * used locally in this method because it may be reused for other change + * notifications. The diff object referenced by the event is immutable and + * may be used non-locally. + * + * @param event + * the event + */ + void handleSetChange(SetChangeEvent event); + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/ListToSetAdapter.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/ListToSetAdapter.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.set; + +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.list.IListChangeListener; +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.list.ListChangeEvent; +import org.eclipse.core.databinding.observable.list.ListDiffEntry; + +/** + * Observable set backed by an observable list. The wrapped list must not + * contain duplicate elements. + * + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * + * @since 1.0 + * + */ +public class ListToSetAdapter : ObservableSet { + + private final IObservableList list; + + private IListChangeListener listener = new class() IListChangeListener { + + public void handleListChange(ListChangeEvent event) { + Set added = new HashSet(); + Set removed = new HashSet(); + ListDiffEntry[] differences = event.diff.getDifferences(); + for (int i = 0; i < differences.length; i++) { + ListDiffEntry entry = differences[i]; + Object element = entry.getElement(); + if (entry.isAddition()) { + if (wrappedSet.add(element)) { + if (!removed.remove(element)) + added.add(element); + } + } else { + if (wrappedSet.remove(element)) { + removed.add(element); + added.remove(element); + } + } + } + fireSetChange(Diffs.createSetDiff(added, removed)); + } + }; + + /** + * @param list + */ + public this(IObservableList list) { + super(list.getRealm(), new HashSet(), list.getElementType()); + this.list = list; + wrappedSet.addAll(list); + this.list.addListChangeListener(listener); + } + + public synchronized void dispose() { + super.dispose(); + if (list !is null && listener !is null) { + list.removeListChangeListener(listener); + listener = null; + } + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/MappedSet.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/MappedSet.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,157 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.set; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.map.IMapChangeListener; +import org.eclipse.core.databinding.observable.map.IObservableMap; +import org.eclipse.core.databinding.observable.map.MapChangeEvent; +import org.eclipse.core.databinding.observable.map.MapDiff; + +/** + * + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * + * @since 1.0 + * + */ +public class MappedSet : ObservableSet { + + private final IObservableMap wrappedMap; + + /* + * Map from values (range elements) to Integer ref counts + */ + private Map valueCounts = new HashMap(); + + private ISetChangeListener domainListener = new class() ISetChangeListener { + public void handleSetChange(SetChangeEvent event) { + Set additions = new HashSet(); + for (Iterator it = event.diff.getAdditions().iterator(); it.hasNext();) { + Object added = it.next(); + Object mapValue = wrappedMap.get(added); + if (handleAddition(mapValue)) { + additions.add(mapValue); + } + } + Set removals = new HashSet(); + for (Iterator it = event.diff.getRemovals().iterator(); it.hasNext();) { + Object removed = it.next(); + Object mapValue = wrappedMap.get(removed); + if (handleRemoval(mapValue)) { + removals.add(mapValue); + } + } + fireSetChange(Diffs.createSetDiff(additions, removals)); + } + }; + + private IMapChangeListener mapChangeListener = new class() IMapChangeListener { + public void handleMapChange(MapChangeEvent event) { + MapDiff diff = event.diff; + Set additions = new HashSet(); + Set removals = new HashSet(); + for (Iterator it = diff.getRemovedKeys().iterator(); it.hasNext();) { + Object key = it.next(); + Object oldValue = diff.getOldValue(key); + if (handleRemoval(oldValue)) { + removals.add(oldValue); + } + } + for (Iterator it = diff.getChangedKeys().iterator(); it.hasNext();) { + Object key = it.next(); + Object oldValue = diff.getOldValue(key); + Object newValue = diff.getNewValue(key); + if (handleRemoval(oldValue)) { + removals.add(oldValue); + } + if (handleAddition(newValue)) { + additions.add(newValue); + } + } + for (Iterator it = diff.getAddedKeys().iterator(); it.hasNext();) { + Object key = it.next(); + Object newValue = diff.getNewValue(key); + if (handleAddition(newValue)) { + additions.add(newValue); + } + } + fireSetChange(Diffs.createSetDiff(additions, removals)); + } + }; + + private IObservableSet input; + + /** + * @param input + * @param map + */ + public this(IObservableSet input, IObservableMap map) { + super(input.getRealm(), Collections.EMPTY_SET, Object.class); + setWrappedSet(valueCounts.keySet()); + this.wrappedMap = map; + this.input = input; + for (Iterator it = input.iterator(); it.hasNext();) { + Object element = it.next(); + Object functionValue = wrappedMap.get(element); + handleAddition(functionValue); + } + input.addSetChangeListener(domainListener); + map.addMapChangeListener(mapChangeListener); + } + + /** + * @param mapValue + * @return true if the given mapValue was an addition + */ + protected bool handleAddition(Object mapValue) { + Integer count = cast(Integer) valueCounts.get(mapValue); + if (count is null) { + valueCounts.put(mapValue, new Integer(1)); + return true; + } + valueCounts.put(mapValue, new Integer(count.intValue() + 1)); + return false; + } + + /** + * @param mapValue + * @return true if the given mapValue has been removed + */ + protected bool handleRemoval(Object mapValue) { + Integer count = cast(Integer) valueCounts.get(mapValue); + if (count.intValue() <= 1) { + valueCounts.remove(mapValue); + return true; + } + valueCounts.put(mapValue, new Integer(count.intValue() - 1)); + return false; + } + + public synchronized void dispose() { + wrappedMap.removeMapChangeListener(mapChangeListener); + input.removeSetChangeListener(domainListener); + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/ObservableSet.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/ObservableSet.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,217 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matthew Hall - bugs 208332, 245183 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.set; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +import org.eclipse.core.databinding.observable.AbstractObservable; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.Realm; + +/** + * + * Abstract implementation of {@link IObservableSet}. + * + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * + * @since 1.0 + * + */ +public abstract class ObservableSet : AbstractObservable , + IObservableSet { + + protected Set wrappedSet; + + private bool stale = false; + + protected Object elementType; + + protected this(Set wrappedSet, Object elementType) { + this(Realm.getDefault(), wrappedSet, elementType); + } + + protected this(Realm realm, Set wrappedSet, Object elementType) { + super(realm); + this.wrappedSet = wrappedSet; + this.elementType = elementType; + } + + public synchronized void addSetChangeListener(ISetChangeListener listener) { + addListener(SetChangeEvent.TYPE, listener); + } + + public synchronized void removeSetChangeListener(ISetChangeListener listener) { + removeListener(SetChangeEvent.TYPE, listener); + } + + protected void fireSetChange(SetDiff diff) { + // fire general change event first + super.fireChange(); + + fireEvent(new SetChangeEvent(this, diff)); + } + + public bool contains(Object o) { + getterCalled(); + return wrappedSet.contains(o); + } + + public bool containsAll(Collection c) { + getterCalled(); + return wrappedSet.containsAll(c); + } + + public override equals_t opEquals(Object o) { + getterCalled(); + + if (o is this) + return true; + if (o is null) + return false; + if (getClass() is o.getClass()) { + return wrappedSet.equals((cast(ObservableSet) o).wrappedSet); + } + + return wrappedSet.equals(o); + } + + public override hash_t toHash() { + getterCalled(); + return wrappedSet.hashCode(); + } + + public bool isEmpty() { + getterCalled(); + return wrappedSet.isEmpty(); + } + + public Iterator iterator() { + getterCalled(); + final Iterator wrappedIterator = wrappedSet.iterator(); + return new class() Iterator { + + public void remove() { + throw new UnsupportedOperationException(); + } + + public bool hasNext() { + ObservableTracker.getterCalled(this.outer); + return wrappedIterator.hasNext(); + } + + public Object next() { + ObservableTracker.getterCalled(this.outer); + return wrappedIterator.next(); + } + }; + } + + public int size() { + getterCalled(); + return wrappedSet.size(); + } + + public Object[] toArray() { + getterCalled(); + return wrappedSet.toArray(); + } + + public Object[] toArray(Object[] a) { + getterCalled(); + return wrappedSet.toArray(a); + } + + public String toString() { + getterCalled(); + return wrappedSet.toString(); + } + + protected void getterCalled() { + ObservableTracker.getterCalled(this); + } + + public bool add(Object o) { + throw new UnsupportedOperationException(); + } + + public bool addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public bool remove(Object o) { + throw new UnsupportedOperationException(); + } + + public bool removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public bool retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + /** + * @return Returns the stale state. + */ + public bool isStale() { + getterCalled(); + return stale; + } + + /** + * @param stale + * The stale state to set. This will fire a stale event if the + * given bool is true and this observable set was not already + * stale. + */ + public void setStale(bool stale) { + checkRealm(); + bool wasStale = this.stale; + this.stale = stale; + if (!wasStale && stale) { + fireStale(); + } + } + + /** + * @param wrappedSet The wrappedSet to set. + */ + protected void setWrappedSet(Set wrappedSet) { + this.wrappedSet = wrappedSet; + } + + protected void fireChange() { + throw new RuntimeException("fireChange should not be called, use fireSetChange() instead"); //$NON-NLS-1$ + } + + /* (non-Javadoc) + * @see org.eclipse.jface.provisional.databinding.observable.AbstractObservable#dispose() + */ + public synchronized void dispose() { + super.dispose(); + } + + public Object getElementType() { + return elementType; + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/SetChangeEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/SetChangeEvent.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.core.databinding.observable.set; + +import org.eclipse.core.databinding.observable.IObservablesListener; +import org.eclipse.core.databinding.observable.ObservableEvent; + +/** + * List change event describing an incremental change of an + * {@link IObservableSet} object. + * + * @since 1.0 + * + */ +public class SetChangeEvent : ObservableEvent { + + /** + * + */ + private static final long serialVersionUID = 7436547103857482256L; + static final Object TYPE = new Object(); + + /** + * Description of the change to the source observable set. Listeners must + * not change this field. + */ + public SetDiff diff; + + /** + * Creates a new set change event. + * + * @param source + * the source observable set + * @param diff + * the set change + */ + public this(IObservableSet source, SetDiff diff) { + super(source); + this.diff = diff; + } + + /** + * Returns the observable set from which this event originated. + * + * @return the observable set from which this event originated + */ + public IObservableSet getObservableSet() { + return cast(IObservableSet) getSource(); + } + + protected void dispatch(IObservablesListener listener) { + (cast(ISetChangeListener) listener).handleSetChange(this); + } + + protected Object getListenerType() { + return TYPE; + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/SetDiff.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/SetDiff.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.set; + +import java.util.Set; + +/** + * @since 1.0 + * + */ +public abstract class SetDiff { + + /** + * @return the set of added elements + */ + public abstract Set getAdditions(); + + /** + * @return the set of removed elements + */ + public abstract Set getRemovals(); + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer + .append(getClass().getName()) + .append("{additions [") //$NON-NLS-1$ + .append(getAdditions() !is null ? getAdditions().toString() : "null") //$NON-NLS-1$ + .append("], removals [") //$NON-NLS-1$ + .append(getRemovals() !is null ? getRemovals().toString() : "null") //$NON-NLS-1$ + .append("]}"); //$NON-NLS-1$ + + return buffer.toString(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/UnionSet.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/UnionSet.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,210 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matthew Hall - bug 208332 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.set; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.internal.databinding.observable.IStalenessConsumer; +import org.eclipse.core.internal.databinding.observable.StalenessTracker; + +/** + * Represents a set consisting of the union of elements from one or more other + * sets. This object does not need to be explicitly disposed. If nobody is + * listening to the UnionSet, the set will remove its listeners. + * + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * + * @since 1.0 + */ +public final class UnionSet : ObservableSet { + + /** + * child sets + */ + private IObservableSet[] childSets; + + private bool stale = false; + + /** + * Map of elements onto Integer reference counts. This map is constructed + * when the first listener is added to the union set. Null if nobody is + * listening to the UnionSet. + */ + private HashMap refCounts = null; + + private StalenessTracker stalenessTracker; + + /** + * @param childSets + */ + public this(IObservableSet[] childSets) { + super(childSets[0].getRealm(), null, childSets[0].getElementType()); + System.arraycopy(childSets, 0, this.childSets = new IObservableSet[childSets.length], 0, childSets.length); + this.stalenessTracker = new StalenessTracker(childSets, + stalenessConsumer); + } + + private ISetChangeListener childSetChangeListener = new class() ISetChangeListener { + public void handleSetChange(SetChangeEvent event) { + processAddsAndRemoves(event.diff.getAdditions(), event.diff.getRemovals()); + } + }; + + private IStalenessConsumer stalenessConsumer = new class() IStalenessConsumer { + public void setStale(bool stale) { + bool oldStale = this.outer.stale; + this.outer.stale = stale; + if (stale && !oldStale) { + fireStale(); + } + } + }; + + public bool isStale() { + getterCalled(); + if (refCounts !is null) { + return stale; + } + + for (int i = 0; i < childSets.length; i++) { + IObservableSet childSet = childSets[i]; + + if (childSet.isStale()) { + return true; + } + } + return false; + } + + private void processAddsAndRemoves(Set adds, Set removes) { + Set addsToFire = new HashSet(); + Set removesToFire = new HashSet(); + + for (Iterator iter = adds.iterator(); iter.hasNext();) { + Object added = iter.next(); + + Integer refCount = cast(Integer) refCounts.get(added); + if (refCount is null) { + refCounts.put(added, new Integer(1)); + addsToFire.add(added); + } else { + int refs = refCount.intValue(); + refCount = new Integer(refs + 1); + refCounts.put(added, refCount); + } + } + + for (Iterator iter = removes.iterator(); iter.hasNext();) { + Object removed = iter.next(); + + Integer refCount = cast(Integer) refCounts.get(removed); + if (refCount !is null) { + int refs = refCount.intValue(); + if (refs <= 1) { + removesToFire.add(removed); + refCounts.remove(removed); + } else { + refCount = new Integer(refCount.intValue() - 1); + refCounts.put(removed, refCount); + } + } + } + + // just in case the removes overlapped with the adds + addsToFire.removeAll(removesToFire); + + if (addsToFire.size() > 0 || removesToFire.size() > 0) { + fireSetChange(Diffs.createSetDiff(addsToFire, removesToFire)); + } + } + + protected void firstListenerAdded() { + super.firstListenerAdded(); + + refCounts = new HashMap(); + for (int i = 0; i < childSets.length; i++) { + IObservableSet next = childSets[i]; + next.addSetChangeListener(childSetChangeListener); + incrementRefCounts(next); + } + stalenessTracker = new StalenessTracker(childSets, stalenessConsumer); + setWrappedSet(refCounts.keySet()); + } + + protected void lastListenerRemoved() { + super.lastListenerRemoved(); + + for (int i = 0; i < childSets.length; i++) { + IObservableSet next = childSets[i]; + + next.removeSetChangeListener(childSetChangeListener); + stalenessTracker.removeObservable(next); + } + refCounts = null; + stalenessTracker = null; + setWrappedSet(null); + } + + private ArrayList incrementRefCounts(Collection added) { + ArrayList adds = new ArrayList(); + + for (Iterator iter = added.iterator(); iter.hasNext();) { + Object next = iter.next(); + + Integer refCount = cast(Integer) refCounts.get(next); + if (refCount is null) { + adds.add(next); + refCount = new Integer(1); + refCounts.put(next, refCount); + } else { + refCount = new Integer(refCount.intValue() + 1); + refCounts.put(next, refCount); + } + } + return adds; + } + + protected void getterCalled() { + super.getterCalled(); + if (refCounts is null) { + // no listeners, recompute + setWrappedSet(computeElements()); + } + } + + private Set computeElements() { + // If there is no cached value, compute the union from scratch + if (refCounts is null) { + Set result = new HashSet(); + for (int i = 0; i < childSets.length; i++) { + result.addAll(childSets[i]); + } + return result; + } + + // Else there is a cached value. Return it. + return refCounts.keySet(); + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/WritableSet.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/WritableSet.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,171 @@ +/******************************************************************************* + * Copyright (c) 2006-2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 147515 + * Matthew Hall - bug 221351 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.set; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.Realm; + +/** + * Mutable (writable) implementation of {@link IObservableSet}. + * + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * + * @since 1.0 + */ +public class WritableSet : ObservableSet { + + /** + * Constructs a new empty instance in the default realm with a + * null element type. + * + */ + public this() { + this(Realm.getDefault()); + } + + /** + * Constructs a new instance in the default realm containing the + * elements of the given collection. Changes to the given collection after + * calling this method do not affect the contents of the created WritableSet. + * + * @param c + * @param elementType + * can be null + */ + public this(Collection c, Object elementType) { + this(Realm.getDefault(), new HashSet(c), elementType); + } + + /** + * Constructs a new empty instance in the given realm and a + * null element type. + * + * @param realm + */ + public this(Realm realm) { + this(realm, new HashSet(), null); + } + + /** + * Constructs a new instance in the default realm with the given element + * type, containing the elements of the given collection. Changes to the + * given collection after calling this method do not affect the contents of + * the created WritableSet. + * + * @param realm + * @param c + * @param elementType + * can be null + */ + public this(Realm realm, Collection c, Object elementType) { + super(realm, new HashSet(c), elementType); + this.elementType = elementType; + } + + public bool add(Object o) { + getterCalled(); + bool added = wrappedSet.add(o); + if (added) { + fireSetChange(Diffs.createSetDiff(Collections.singleton(o), Collections.EMPTY_SET)); + } + return added; + } + + public bool addAll(Collection c) { + getterCalled(); + Set additions = new HashSet(); + Iterator it = c.iterator(); + while (it.hasNext()) { + Object element = it.next(); + if (wrappedSet.add(element)) { + additions.add(element); + } + } + if (additions.size() > 0) { + fireSetChange(Diffs.createSetDiff(additions, Collections.EMPTY_SET)); + return true; + } + return false; + } + + public bool remove(Object o) { + getterCalled(); + bool removed = wrappedSet.remove(o); + if (removed) { + fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET, Collections + .singleton(o))); + } + return removed; + } + + public bool removeAll(Collection c) { + getterCalled(); + Set removes = new HashSet(); + Iterator it = c.iterator(); + while (it.hasNext()) { + Object element = it.next(); + if (wrappedSet.remove(element)) { + removes.add(element); + } + } + if (removes.size() > 0) { + fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET, removes)); + return true; + } + return false; + } + + public bool retainAll(Collection c) { + getterCalled(); + Set removes = new HashSet(); + Iterator it = wrappedSet.iterator(); + while (it.hasNext()) { + Object element = it.next(); + if (!c.contains(element)) { + it.remove(); + removes.add(element); + } + } + if (removes.size() > 0) { + fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET, removes)); + return true; + } + return false; + } + + public void clear() { + getterCalled(); + Set removes = new HashSet(wrappedSet); + wrappedSet.clear(); + fireSetChange(Diffs.createSetDiff(Collections.EMPTY_SET, removes)); + } + + /** + * @param elementType can be null + * @return new instance with the default realm + */ + public static WritableSet withElementType(Object elementType) { + return new WritableSet(Realm.getDefault(), new HashSet(), elementType); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/package.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/set/package.html Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,16 @@ + + + + + + + Package-level Javadoc + + +Provides classes that can be used to observe changes in sets. +

+Package Specification

+

+This package provides classes that can be used to observe changes in sets.

+ + diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/AbstractObservableValue.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/AbstractObservableValue.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 164653 + * Matthew Hall - bug 208332 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.value; + +import org.eclipse.core.databinding.observable.AbstractObservable; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.Realm; + +/** + * + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * @since 1.0 + * + */ +abstract public class AbstractObservableValue : AbstractObservable , IObservableValue { + /** + * Constructs a new instance with the default realm. + */ + public this() { + this(Realm.getDefault()); + } + + /** + * @param realm + */ + public this(Realm realm) { + super(realm); + } + + public synchronized void addValueChangeListener(IValueChangeListener listener) { + addListener(ValueChangeEvent.TYPE, listener); + } + + public synchronized void removeValueChangeListener(IValueChangeListener listener) { + removeListener(ValueChangeEvent.TYPE, listener); + } + + final public void setValue(Object value) { + checkRealm(); + doSetValue(value); + } + + /** + * Template method for setting the value of the observable. By default the + * method throws an {@link UnsupportedOperationException}. + * + * @param value + */ + protected void doSetValue(Object value) { + throw new UnsupportedOperationException(); + } + + protected void fireValueChange(ValueDiff diff) { + // fire general change event first + super.fireChange(); + fireEvent(new ValueChangeEvent(this, diff)); + } + + public final Object getValue() { + getterCalled(); + return doGetValue(); + } + + abstract protected Object doGetValue(); + + public bool isStale() { + getterCalled(); + return false; + } + + private void getterCalled() { + ObservableTracker.getterCalled(this); + } + + protected void fireChange() { + throw new RuntimeException( + "fireChange should not be called, use fireValueChange() instead"); //$NON-NLS-1$ + } + + public synchronized void dispose() { + super.dispose(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/AbstractVetoableValue.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/AbstractVetoableValue.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 164653 + *******************************************************************************/ +package org.eclipse.core.databinding.observable.value; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.internal.databinding.Util; + +/** + * + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * @since 1.0 + * + */ +public abstract class AbstractVetoableValue : AbstractObservableValue + , IVetoableValue { + + /** + * Creates a new vetoable value. + */ + public this() { + this(Realm.getDefault()); + } + + /** + * @param realm + */ + public this(Realm realm) { + super(realm); + } + + final protected void doSetValue(Object value) { + Object currentValue = doGetValue(); + ValueDiff diff = Diffs.createValueDiff(currentValue, value); + bool okToProceed = fireValueChanging(diff); + if (!okToProceed) { + throw new ChangeVetoException("Change not permitted"); //$NON-NLS-1$ + } + doSetApprovedValue(value); + + if (!Util.equals(diff.getOldValue(), diff.getNewValue())) { + fireValueChange(diff); + } + } + + /** + * Sets the value. Invoked after performing veto checks. Should not fire change events. + * + * @param value + */ + protected abstract void doSetApprovedValue(Object value); + + public synchronized void addValueChangingListener( + IValueChangingListener listener) { + addListener(ValueChangingEvent.TYPE, listener); + } + + public synchronized void removeValueChangingListener( + IValueChangingListener listener) { + removeListener(ValueChangingEvent.TYPE, listener); + } + + /** + * Notifies listeners about a pending change, and returns true if no + * listener vetoed the change. + * + * @param diff + * @return false if the change was vetoed, true otherwise + */ + protected bool fireValueChanging(ValueDiff diff) { + checkRealm(); + + ValueChangingEvent event = new ValueChangingEvent(this, diff); + fireEvent(event); + return !event.veto; + } + + public synchronized void dispose() { + super.dispose(); + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ChangeVetoException.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ChangeVetoException.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2005, 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.databinding.observable.value; + +/** + * @since 1.0 + * + */ +public class ChangeVetoException : RuntimeException { + + /** + * @param string + */ + public this(String string) { + super(string); + } + + private static final long serialVersionUID = 1L; + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ComputedValue.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ComputedValue.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,261 @@ +/******************************************************************************* + * Copyright (c) 2005, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 116920 + * Brad Reynolds - bug 147515 + *******************************************************************************/ +package org.eclipse.core.databinding.observable.value; + +import org.eclipse.core.databinding.observable.ChangeEvent; +import org.eclipse.core.databinding.observable.IChangeListener; +import org.eclipse.core.databinding.observable.IObservable; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.StaleEvent; + +/** + * A Lazily calculated value that automatically computes and registers listeners + * on its dependencies as long as all of its dependencies are IObservable + * objects + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * + * @since 1.0 + */ +public abstract class ComputedValue : AbstractObservableValue { + + private bool dirty = true; + + private bool stale = false; + + private Object cachedValue = null; + + /** + * Array of observables this computed value depends on. This field has a + * value of null if we are not currently listening. + */ + private IObservable[] dependencies = null; + + /** + * + */ + public this() { + this(Realm.getDefault(), null); + } + + /** + * @param valueType + * can be null + */ + public this(Object valueType) { + this(Realm.getDefault(), valueType); + } + + /** + * @param realm + * + */ + public this(Realm realm) { + this(realm, null); + } + + /** + * @param realm + * @param valueType + */ + public this(Realm realm, Object valueType) { + super(realm); + this.valueType = valueType; + } + + /** + * Inner class that implements interfaces that we don't want to expose as + * public API. Each interface could have been implemented using a separate + * anonymous class, but we combine them here to reduce the memory overhead + * and number of classes. + * + *

+ * The Runnable calls computeValue and stores the result in cachedValue. + *

+ * + *

+ * The IChangeListener stores each observable in the dependencies list. This + * is registered as the listener when calling ObservableTracker, to detect + * every observable that is used by computeValue. + *

+ * + *

+ * The IChangeListener is attached to every dependency. + *

+ * + */ + private class PrivateInterface : Runnable, IChangeListener, + IStaleListener { + public void run() { + cachedValue = calculate(); + } + + public void handleStale(StaleEvent event) { + if (!dirty && !stale) { + stale = true; + fireStale(); + } + } + + public void handleChange(ChangeEvent event) { + makeDirty(); + } + } + + private PrivateInterface privateInterface = new PrivateInterface(); + + private Object valueType; + + protected final Object doGetValue() { + if (dirty) { + // This line will do the following: + // - Run the calculate method + // - While doing so, add any observable that is touched to the + // dependencies list + IObservable[] newDependencies = ObservableTracker.runAndMonitor( + privateInterface, privateInterface, null); + + stale = false; + for (int i = 0; i < newDependencies.length; i++) { + IObservable observable = newDependencies[i]; + // Add a change listener to the new dependency. + if (observable.isStale()) { + stale = true; + } else { + observable.addStaleListener(privateInterface); + } + } + + dependencies = newDependencies; + + dirty = false; + } + + return cachedValue; + } + + /** + * Subclasses must override this method to provide the object's value. + * + * @return the object's value + */ + protected abstract Object calculate(); + + protected final void makeDirty() { + if (!dirty) { + dirty = true; + + stopListening(); + + // copy the old value + final Object oldValue = cachedValue; + // Fire the "dirty" event. This implementation recomputes the new + // value lazily. + fireValueChange(new class() ValueDiff { + + public Object getOldValue() { + return oldValue; + } + + public Object getNewValue() { + return getValue(); + } + }); + } + } + + /** + * + */ + private void stopListening() { + // Stop listening for dependency changes. + if (dependencies !is null) { + for (int i = 0; i < dependencies.length; i++) { + IObservable observable = dependencies[i]; + + observable.removeChangeListener(privateInterface); + observable.removeStaleListener(privateInterface); + } + dependencies = null; + } + } + + public bool isStale() { + // we need to recompute, otherwise staleness wouldn't mean anything + getValue(); + return stale; + } + + public Object getValueType() { + return valueType; + } + + // this method exists here so that we can call it from the runnable below. + /** + * @since 1.1 + */ + protected bool hasListeners() { + return super.hasListeners(); + } + + public synchronized void addChangeListener(IChangeListener listener) { + super.addChangeListener(listener); + // If somebody is listening, we need to make sure we attach our own + // listeners + computeValueForListeners(); + } + + /** + * Some clients just add a listener and expect to get notified even if they + * never called getValue(), so we have to call getValue() ourselves here to + * be sure. Need to be careful about realms though, this method can be + * called outside of our realm. See also bug 198211. If a client calls this + * outside of our realm, they may receive change notifications before the + * runnable below has been executed. It is their job to figure out what to + * do with those notifications. + */ + private void computeValueForListeners() { + getRealm().exec(new class() Runnable { + public void run() { + if (dependencies is null) { + // We are not currently listening. + if (hasListeners()) { + // But someone is listening for changes. Call getValue() + // to make sure we start listening to the observables we + // depend on. + getValue(); + } + } + } + }); + } + + public synchronized void addValueChangeListener( + IValueChangeListener listener) { + super.addValueChangeListener(listener); + // If somebody is listening, we need to make sure we attach our own + // listeners + computeValueForListeners(); + } + + public synchronized void dispose() { + super.dispose(); + stopListening(); + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IObservableValue.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IObservableValue.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.value; + +import org.eclipse.core.databinding.observable.IObservable; +import org.eclipse.core.databinding.observable.Realm; + +/** + * A value whose changes can be tracked by value change listeners. + * + * @noextend This interface is not intended to be extended by clients. + * @noimplement This interface is not intended to be implemented by clients. + * Clients should instead subclass one of the classes that + * implement this interface. Note that direct implementers of this + * interface outside of the framework will be broken in future + * releases when methods are added to this interface. + * + * @see AbstractObservableValue + * + * @since 1.0 + */ +public interface IObservableValue : IObservable { + + /** + * The value type of this observable value, or null if this + * observable value is untyped. + * + * @return the value type, or null + */ + public Object getValueType(); + + /** + * Returns the value. Must be invoked in the {@link Realm} of the observable. + * + * @return the current value + * @TrackedGetter + */ + public Object getValue(); + + /** + * Sets the value. Must be invoked in the {@link Realm} of the observable. + * + * @param value + * the value to set + * @throws UnsupportedOperationException + * if this observable value cannot be set. + */ + public void setValue(Object value); + + /** + * + * @param listener + */ + public void addValueChangeListener(IValueChangeListener listener); + + /** + * @param listener + */ + public void removeValueChangeListener(IValueChangeListener listener); +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IValueChangeListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IValueChangeListener.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.value; + +import org.eclipse.core.databinding.observable.IObservablesListener; + +/** + * Listener for changes to observable values. + * + * @since 1.0 + * + */ +public interface IValueChangeListener : IObservablesListener { + + /** + * Handles a change to an observable value. The given event object must only + * be used locally in this method because it may be reused for other change + * notifications. The diff object referenced by the event is immutable and + * may be used non-locally. + * + * @param event + * the event + */ + void handleValueChange(ValueChangeEvent event); + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IValueChangingListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IValueChangingListener.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.databinding.observable.value; + +import org.eclipse.core.databinding.observable.IObservablesListener; + +/** + * Listener for pre-change events for observable values. + * + * @since 1.0 + * + */ +public interface IValueChangingListener : IObservablesListener { + + /** + * This method is called when the value is about to change and provides an + * opportunity to veto the change. The given event object must only be used + * locally in this method because it may be reused for other change + * notifications. The diff object referenced by the event is immutable and + * may be used non-locally. + * + * @param event + */ + public void handleValueChanging(ValueChangingEvent event); + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IVetoableValue.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/IVetoableValue.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2005, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.databinding.observable.value; + +/** + * An observable value whose changes can be vetoed by listeners. + * + * @noextend This interface is not intended to be extended by clients. + * @noimplement This interface is not intended to be implemented by clients. + * Clients should instead subclass one of the classes that + * implement this interface. Note that direct implementers of this + * interface outside of the framework will be broken in future + * releases when methods are added to this interface. + * + * @since 1.0 + * + */ +public interface IVetoableValue : IObservableValue { + + /** + * @param listener + */ + public void addValueChangingListener(IValueChangingListener listener); + + /** + * @param listener + */ + public void removeValueChangingListener(IValueChangingListener listener); + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ValueChangeEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ValueChangeEvent.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.core.databinding.observable.value; + +import org.eclipse.core.databinding.observable.IObservablesListener; +import org.eclipse.core.databinding.observable.ObservableEvent; + +/** + * Value change event describing a change of an {@link IObservableValue} + * object's current value. + * + * @since 1.0 + * + */ +public class ValueChangeEvent : ObservableEvent { + + /** + * + */ + private static final long serialVersionUID = 2305345286999701156L; + + static final Object TYPE = new Object(); + + /** + * Description of the change to the source observable value. Listeners must + * not change this field. + */ + public ValueDiff diff; + + /** + * Creates a new value change event. + * + * @param source + * the source observable value + * @param diff + * the value change + */ + public this(IObservableValue source, ValueDiff diff) { + super(source); + this.diff = diff; + } + + /** + * Returns the observable value from which this event originated. + * + * @return returns the observable value from which this event originated + */ + public IObservableValue getObservableValue() { + return cast(IObservableValue) source; + } + + protected void dispatch(IObservablesListener listener) { + (cast(IValueChangeListener) listener).handleValueChange(this); + } + + protected Object getListenerType() { + return TYPE; + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ValueChangingEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ValueChangingEvent.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.core.databinding.observable.value; + +import org.eclipse.core.databinding.observable.IObservablesListener; +import org.eclipse.core.databinding.observable.ObservableEvent; + +/** + * Value changing event describing a pending change of an + * {@link IObservableValue} object's current value. Listeners can veto the + * pending change by setting {@link #veto} to true. + * + * @since 1.0 + * + */ +public class ValueChangingEvent : ObservableEvent { + + /** + * + */ + private static final long serialVersionUID = 2305345286999701156L; + + static final Object TYPE = new Object(); + + /** + * Description of the change to the source observable value. Listeners must + * not change this field. + */ + public ValueDiff diff; + + /** + * Flag for vetoing this change. Default value is false, can + * be set to true by listeners to veto this change. + */ + public bool veto = false; + + /** + * Creates a new value changing event. + * + * @param source + * the source observable value + * @param diff + * the value change + */ + public this(IObservableValue source, ValueDiff diff) { + super(source); + this.diff = diff; + } + + /** + * @return the observable value from which this event originated + */ + public IObservableValue getObservableValue() { + return cast(IObservableValue) source; + } + + protected void dispatch(IObservablesListener listener) { + (cast(IValueChangingListener) listener).handleValueChanging(this); + } + + protected Object getListenerType() { + return TYPE; + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ValueDiff.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/ValueDiff.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.value; + +import org.eclipse.core.databinding.observable.Diffs; + +/** + * @since 1.0 + * + */ +public abstract class ValueDiff { + /** + * Creates a value diff. + */ + public this() { + } + + /** + * @return the old value + */ + public abstract Object getOldValue(); + + /** + * @return the new value + */ + public abstract Object getNewValue(); + + public override equals_t opEquals(Object obj) { + if ( null !is cast(ValueDiff)obj ) { + ValueDiff val = cast(ValueDiff) obj; + + return Diffs.equals(val.getNewValue(), getNewValue()) + && Diffs.equals(val.getOldValue(), getOldValue()); + + } + return false; + } + + public override hash_t toHash() { + final int prime = 31; + int result = 1; + Object nv = getNewValue(); + Object ov = getOldValue(); + result = prime * result + ((nv is null) ? 0 : nv.hashCode()); + result = prime * result + ((ov is null) ? 0 : ov.hashCode()); + return result; + } + + /** + * @see java.lang.Object#toString() + */ + public String toString() { + StringBuffer buffer = new StringBuffer(); + buffer + .append(getClass().getName()) + .append("{oldValue [") //$NON-NLS-1$ + .append(getOldValue() !is null ? getOldValue().toString() : "null") //$NON-NLS-1$ + .append("], newValue [") //$NON-NLS-1$ + .append(getNewValue() !is null ? getNewValue().toString() : "null") //$NON-NLS-1$ + .append("]}"); //$NON-NLS-1$ + + return buffer.toString(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/WritableValue.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/WritableValue.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 158687 + * Brad Reynolds - bug 164653, 147515 + *******************************************************************************/ + +package org.eclipse.core.databinding.observable.value; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.Realm; + +/** + * Mutable (writable) implementation of {@link IObservableValue} that will maintain a value and fire + * change events when the value changes. + *

+ * This class is thread safe. All state accessing methods must be invoked from + * the {@link Realm#isCurrent() current realm}. Methods for adding and removing + * listeners may be invoked from any thread. + *

+ * @since 1.0 + */ +public class WritableValue : AbstractObservableValue { + + private final Object valueType; + + /** + * Constructs a new instance with the default realm, a null + * value type, and a null value. + */ + public this() { + this(null, null); + } + + /** + * Constructs a new instance with the default realm. + * + * @param initialValue + * can be null + * @param valueType + * can be null + */ + public this(Object initialValue, Object valueType) { + this(Realm.getDefault(), initialValue, valueType); + } + + /** + * Constructs a new instance with the provided realm, a + * null value type, and a null initial value. + * + * @param realm + */ + public this(Realm realm) { + this(realm, null, null); + } + + /** + * Constructs a new instance. + * + * @param realm + * @param initialValue + * can be null + * @param valueType + * can be null + */ + public this(Realm realm, Object initialValue, Object valueType) { + super(realm); + this.valueType = valueType; + this.value = initialValue; + } + + private Object value = null; + + public Object doGetValue() { + return value; + } + + /** + * @param value + * The value to set. + */ + public void doSetValue(Object value) { + bool changed = false; + + if (this.value is null && value !is null) { + changed = true; + } else if (this.value !is null && !this.value.equals(value)) { + changed = true; + } + + if (changed) { + fireValueChange(Diffs.createValueDiff(this.value, this.value = value)); + } + } + + public Object getValueType() { + return valueType; + } + + /** + * @param elementType can be null + * @return new instance with the default realm and a value of null + */ + public static WritableValue withValueType(Object elementType) { + return new WritableValue(Realm.getDefault(), null, elementType); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/package.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/value/package.html Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,16 @@ + + + + + + + Package-level Javadoc + + +Provides classes that can be used to observe changes in discrete values. +

+Package Specification

+

+This package provides classes that can be used to observe changes in discrete values.

+ + diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/util/ILogger.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/util/ILogger.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Chris Gross (schtoo@schtoo.com) - initial API and implementation + * (bug 49497 [RCP] JFace dependency on org.eclipse.core.runtime enlarges standalone JFace applications) + *******************************************************************************/ + +package org.eclipse.core.databinding.util; + +import org.eclipse.core.runtime.IStatus; + +/** + * A mechanism to log errors throughout JFace Data Binding. + *

+ * Clients may provide their own implementation to change how errors are logged + * from within JFace Data Binding. + *

+ * + * @see Policy#getLog() + * @see Policy#setLogcast(ILogger) + * @since 1.1 + */ +public interface ILogger { + + /** + * Logs the given status. + * + * @param status + * the status to log + */ + public void log(IStatus status); + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/util/Policy.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/util/Policy.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2004, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Chris Gross (schtoo@schtoo.com) - support for ILogger added + * (bug 49497 [RCP] JFace dependency on org.eclipse.core.runtime enlarges standalone JFace applications) + * Brad Reynolds - bug 164653 + * Tom Schindl - bug 194587 + *******************************************************************************/ +package org.eclipse.core.databinding.util; + +import org.eclipse.core.runtime.IStatus; + +/** + * The Policy class handles settings for behaviour, debug flags and logging + * within JFace Data Binding. + * + * @since 1.1 + */ +public class Policy { + + /** + * Constant for the the default setting for debug options. + */ + public static final bool DEFAULT = false; + + /** + * The unique identifier of the JFace plug-in. + */ + public static final String JFACE_DATABINDING = "org.eclipse.core.databinding";//$NON-NLS-1$ + + private static ILogger log; + + /** + * Returns the dummy log to use if none has been set + */ + private static ILogger getDummyLog() { + return new class() ILogger { + public void log(IStatus status) { + System.err.println(status.getPlugin() + " - " + status.getCode() + " - " + status.getMessage()); //$NON-NLS-1$//$NON-NLS-2$ + if( status.getException() !is null ) { + status.getException().printStackTrace(System.err); + } + } + }; + } + + /** + * Sets the logger used by JFace Data Binding to log errors. + * + * @param logger + * the logger to use, or null to use the default + * logger + */ + public static synchronized void setLog(ILogger logger) { + log = logger; + } + + /** + * Returns the logger used by JFace Data Binding to log errors. + *

+ * The default logger prints the status to System.err. + *

+ * + * @return the logger + */ + public static synchronized ILogger getLog() { + if (log is null) { + log = getDummyLog(); + } + return log; + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/util/package.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/util/package.html Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,16 @@ + + + + + + + Package-level Javadoc + + +Provides general utilities for data binding. +

+Package Specification

+

+This package provides general utilities for data binding.

+ + diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ConstantObservableValue.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ConstantObservableValue.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007-2008 Matt Carter and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Matt Carter - initial API and implementation (bug 212518) + * Matthew Hall - bug 212518 + *******************************************************************************/ +package org.eclipse.core.internal.databinding.observable; + +import org.eclipse.core.databinding.observable.IChangeListener; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.observable.value.IValueChangeListener; +import org.eclipse.core.databinding.observable.value.WritableValue; +import org.eclipse.core.runtime.Assert; + +/** + * An immutable {@link IObservableValue}. + * + * @see WritableValue + */ +public class ConstantObservableValue : IObservableValue { + final Realm realm; + final Object value; + final Object type; + + /** + * Construct a constant value of the given type, in the default realm. + * + * @param value + * immutable value + * @param type + * type + */ + public this(Object value, Object type) { + this(Realm.getDefault(), value, type); + } + + /** + * Construct a constant value of the given type, in the given realm. + * + * @param realm + * Realm + * @param value + * immutable value + * @param type + * type + */ + public this(Realm realm, Object value, Object type) { + Assert.isNotNull(realm, "Realm cannot be null"); //$NON-NLS-1$ + this.realm = realm; + this.value = value; + this.type = type; + } + + public Object getValueType() { + return type; + } + + public Object getValue() { + ObservableTracker.getterCalled(this); + return value; + } + + public void setValue(Object value) { + throw new UnsupportedOperationException(); + } + + public void addValueChangeListener(IValueChangeListener listener) { + // ignore + } + + public void removeValueChangeListener(IValueChangeListener listener) { + // ignore + } + + public void addChangeListener(IChangeListener listener) { + // ignore + } + + public void addStaleListener(IStaleListener listener) { + // ignore + } + + public void dispose() { + // nothing to dispose + } + + public Realm getRealm() { + return realm; + } + + public bool isStale() { + return false; + } + + public void removeChangeListener(IChangeListener listener) { + // ignore + } + + public void removeStaleListener(IStaleListener listener) { + // ignore + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/EmptyObservableList.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/EmptyObservableList.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,224 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matthew Hall - bug 208858 + * Matthew Hall - bug 208332 + *******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.core.databinding.observable.IChangeListener; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.list.IListChangeListener; +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.runtime.Assert; + +/** + * Singleton empty list + */ +public class EmptyObservableList : IObservableList { + + private static final List emptyList = Collections.EMPTY_LIST; + + private Realm realm; + private Object elementType; + + /** + * Creates an empty list. This list may be disposed multiple times + * without any side-effects. + * + * @param realm + * the realm of the constructed list + */ + public this(Realm realm) { + this(realm, null); + } + + /** + * Creates an empty list. This list may be disposed multiple times + * without any side-effects. + * + * @param realm + * the realm of the constructed list + * @param elementType + * the element type of the constructed list + * @since 1.1 + */ + public this(Realm realm, Object elementType) { + this.realm = realm; + this.elementType = elementType; + } + + public void addListChangeListener(IListChangeListener listener) { + // ignore + } + + public void removeListChangeListener(IListChangeListener listener) { + // ignore + } + + public Object getElementType() { + return elementType; + } + + public int size() { + checkRealm(); + return 0; + } + + void checkRealm() { + Assert.isTrue(realm.isCurrent(), + "Observable cannot be accessed outside its realm"); //$NON-NLS-1$ + } + + public bool isEmpty() { + checkRealm(); + return true; + } + + public bool contains(Object o) { + checkRealm(); + return false; + } + + public Iterator iterator() { + checkRealm(); + return emptyList.iterator(); + } + + public Object[] toArray() { + checkRealm(); + return emptyList.toArray(); + } + + public Object[] toArray(Object[] a) { + return emptyList.toArray(a); + } + + public bool add(Object o) { + throw new UnsupportedOperationException(); + } + + public bool remove(Object o) { + throw new UnsupportedOperationException(); + } + + public bool containsAll(Collection c) { + checkRealm(); + return c.isEmpty(); + } + + public bool addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public bool retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public bool removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + public void addChangeListener(IChangeListener listener) { + } + + public void removeChangeListener(IChangeListener listener) { + } + + public void addStaleListener(IStaleListener listener) { + } + + public void removeStaleListener(IStaleListener listener) { + } + + public bool isStale() { + checkRealm(); + return false; + } + + public void dispose() { + } + + public bool addAll(int index, Collection c) { + throw new UnsupportedOperationException(); + } + + public Object get(int index) { + return emptyList.get(index); + } + + public int indexOf(Object o) { + return -1; + } + + public int lastIndexOf(Object o) { + return -1; + } + + public ListIterator listIterator() { + return emptyList.listIterator(); + } + + public ListIterator listIterator(int index) { + return emptyList.listIterator(index); + } + + public Object remove(int index) { + throw new UnsupportedOperationException(); + } + + public Object set(int index, Object element) { + throw new UnsupportedOperationException(); + } + + public Object move(int oldIndex, int newIndex) { + throw new UnsupportedOperationException(); + } + + public List subList(int fromIndex, int toIndex) { + return emptyList.subList(fromIndex, toIndex); + } + + public void add(int index, Object o) { + throw new UnsupportedOperationException(); + } + + public Realm getRealm() { + return realm; + } + + public override equals_t opEquals(Object obj) { + checkRealm(); + if (obj is this) + return true; + if (obj is null) + return false; + if (!( null !is cast(List)obj )) + return false; + + return (cast(List) obj).isEmpty(); + } + + public override hash_t toHash() { + checkRealm(); + return 1; + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/EmptyObservableSet.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/EmptyObservableSet.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,176 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matthew Hall - bug 208332 + *******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.Set; + +import org.eclipse.core.databinding.observable.IChangeListener; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.set.IObservableSet; +import org.eclipse.core.databinding.observable.set.ISetChangeListener; +import org.eclipse.core.runtime.Assert; + +/** + * Singleton empty set + */ +public class EmptyObservableSet : IObservableSet { + + private static final Set emptySet = Collections.EMPTY_SET; + + private Realm realm; + private Object elementType; + + /** + * Creates a singleton empty set. This set may be disposed multiple times + * without any side-effects. + * + * @param realm + * the realm of the constructed set + */ + public this(Realm realm) { + this(realm, null); + } + + /** + * Creates a singleton empty set. This set may be disposed multiple times + * without any side-effects. + * + * @param realm + * the realm of the constructed set + * @param elementType + * the element type of the constructed set + * @since 1.1 + */ + public this(Realm realm, Object elementType) { + this.realm = realm; + this.elementType = elementType; + } + + public void addSetChangeListener(ISetChangeListener listener) { + } + + public void removeSetChangeListener(ISetChangeListener listener) { + } + + public Object getElementType() { + return elementType; + } + + public int size() { + checkRealm(); + return 0; + } + + private void checkRealm() { + Assert.isTrue(realm.isCurrent(), + "Observable cannot be accessed outside its realm"); //$NON-NLS-1$ + } + + public bool isEmpty() { + checkRealm(); + return true; + } + + public bool contains(Object o) { + checkRealm(); + return false; + } + + public Iterator iterator() { + checkRealm(); + return emptySet.iterator(); + } + + public Object[] toArray() { + checkRealm(); + return emptySet.toArray(); + } + + public Object[] toArray(Object[] a) { + return emptySet.toArray(a); + } + + public bool add(Object o) { + throw new UnsupportedOperationException(); + } + + public bool remove(Object o) { + throw new UnsupportedOperationException(); + } + + public bool containsAll(Collection c) { + checkRealm(); + return c.isEmpty(); + } + + public bool addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public bool retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public bool removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + public void addChangeListener(IChangeListener listener) { + } + + public void removeChangeListener(IChangeListener listener) { + } + + public void addStaleListener(IStaleListener listener) { + } + + public void removeStaleListener(IStaleListener listener) { + } + + public bool isStale() { + checkRealm(); + return false; + } + + public void dispose() { + } + + public Realm getRealm() { + return realm; + } + + public override equals_t opEquals(Object obj) { + checkRealm(); + if (obj is this) + return true; + if (obj is null) + return false; + if (!( null !is cast(Set)obj )) + return false; + + return (cast(Set) obj).isEmpty(); + } + + public override hash_t toHash() { + checkRealm(); + return 0; + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/IStalenessConsumer.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/IStalenessConsumer.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable; + +/** + * @since 1.0 + * + */ +public interface IStalenessConsumer { + /** + * @param stale + * + */ + public void setStale(bool stale); +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/MapEntryObservableValue.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/MapEntryObservableValue.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2008 Marko Topolnik and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Marko Topolnik - initial API and implementation (bug 184830) + * Matthew Hall - bug 184830 + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.databinding.observable.map.IMapChangeListener; +import org.eclipse.core.databinding.observable.map.IObservableMap; +import org.eclipse.core.databinding.observable.map.MapChangeEvent; +import org.eclipse.core.databinding.observable.value.AbstractObservableValue; +import org.eclipse.core.databinding.observable.value.IObservableValue; + +/** + * An {@link IObservableValue} that tracks the value of an entry in an + * {@link IObservableMap}, identified by the entry's key. + * + * @since 1.1 + */ +public class MapEntryObservableValue : AbstractObservableValue { + private IObservableMap map; + private Object key; + private Object valueType; + + private IMapChangeListener changeListener = new class() IMapChangeListener { + public void handleMapChange(final MapChangeEvent event) { + if (event.diff.getAddedKeys().contains(key)) { + final Object newValue = event.diff.getNewValue(key); + if (newValue !is null) { + fireValueChange(Diffs.createValueDiff(null, newValue)); + } + } else if (event.diff.getChangedKeys().contains(key)) { + fireValueChange(Diffs.createValueDiff(event.diff + .getOldValue(key), event.diff.getNewValue(key))); + } else if (event.diff.getRemovedKeys().contains(key)) { + final Object oldValue = event.diff.getOldValue(key); + if (oldValue !is null) { + fireValueChange(Diffs.createValueDiff(oldValue, null)); + } + } + } + }; + + private IStaleListener staleListener = new class() IStaleListener { + public void handleStale(StaleEvent staleEvent) { + fireStale(); + } + }; + + /** + * Creates a map entry observable. + * + * @param map + * the observable map whose entry will be tracked + * @param key + * the key identifying the entry whose value will be tracked + * @param valueType + * the type of the value + */ + public this(IObservableMap map, Object key, + Object valueType) { + super(map.getRealm()); + this.map = map; + this.key = key; + this.valueType = valueType; + + map.addMapChangeListener(changeListener); + map.addStaleListener(staleListener); + } + + public Object getValueType() { + return this.valueType; + } + + public bool isStale() { + ObservableTracker.getterCalled(this); + return map.isStale(); + } + + public synchronized void dispose() { + if (map !is null) { + map.removeMapChangeListener(changeListener); + map.removeStaleListener(staleListener); + map = null; + changeListener = null; + staleListener = null; + } + super.dispose(); + } + + protected Object doGetValue() { + return this.map.get(this.key); + } + + protected void doSetValue(Object value) { + this.map.put(this.key, value); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ProxyObservableList.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ProxyObservableList.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2007 Matthew Hall and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Matthew Hall - initial API and implementation (bug 208332) + * IBM Corporation - initial API and implementation + * (through ProxyObservableSet.java) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable; + +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.databinding.observable.list.*; + +/** + * Wraps an observable list. This object acts like an exact copy of the original + * list, and tracks all the changes in the original. The only difference is that + * disposing the wrapper will not dispose the original. You can use this + * whenever you need to return an IObservableList from a method that expects the + * caller to dispose the list, but you have an IObservableList that you don't + * want disposed. + * + * @since 1.1 + */ +public class ProxyObservableList : ObservableList { + private IListChangeListener listChangelistener = new class() IListChangeListener { + public void handleListChange(ListChangeEvent event) { + fireListChange(event.diff); + } + }; + + private IStaleListener staleListener = new class() IStaleListener { + public void handleStale(StaleEvent event) { + fireStale(); + } + }; + + private IObservableList wrappedList; + + /** + * Constructs a ProxyObservableList that tracks the state of the given list. + * + * @param wrappedList + * the list being wrapped + */ + public this(IObservableList wrappedList) { + super(wrappedList.getRealm(), wrappedList, wrappedList.getElementType()); + this.wrappedList = wrappedList; + wrappedList.addListChangeListener(listChangelistener); + wrappedList.addStaleListener(staleListener); + } + + public bool isStale() { + getterCalled(); + return wrappedList is null ? false : wrappedList.isStale(); + } + + public void dispose() { + if (wrappedList !is null) { + wrappedList.removeListChangeListener(listChangelistener); + listChangelistener = null; + wrappedList.removeStaleListener(staleListener); + staleListener = null; + wrappedList = null; + } + super.dispose(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ProxyObservableSet.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ProxyObservableSet.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2006, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matthew Hall - bug 208332 + *******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable; + +import java.util.Collections; +import java.util.Set; + +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.databinding.observable.set.AbstractObservableSet; +import org.eclipse.core.databinding.observable.set.IObservableSet; +import org.eclipse.core.databinding.observable.set.ISetChangeListener; +import org.eclipse.core.databinding.observable.set.SetChangeEvent; + +/** + * Wraps an observable set. This object acts like an exact copy of the original + * set, and tracks all the changes in the original. The only difference is that + * disposing the wrapper will not dispose the original. You can use this + * whenever you need to return an IObservableSet from a method that expects the + * caller to dispose the set, but you have an IObservableSet that you don't want + * disposed. + */ +public class ProxyObservableSet : AbstractObservableSet { + private IObservableSet wrappedSet; + private Object elementType; + + private ISetChangeListener setChangeListener = new class() ISetChangeListener { + public void handleSetChange(SetChangeEvent event) { + fireSetChange(event.diff); + } + }; + + private IStaleListener staleListener = new class() IStaleListener { + public void handleStale(StaleEvent staleEvent) { + fireStale(); + } + }; + + /** + * Constructs a ProxyObservableSet that tracks the state of the given set. + * + * @param wrappedSet + * the set being wrapped + */ + public this(IObservableSet wrappedSet) { + super(wrappedSet.getRealm()); + this.wrappedSet = wrappedSet; + this.elementType = wrappedSet.getElementType(); + wrappedSet.addSetChangeListener(setChangeListener); + wrappedSet.addStaleListener(staleListener); + } + + protected Set getWrappedSet() { + return wrappedSet is null ? Collections.EMPTY_SET : wrappedSet; + } + + public Object getElementType() { + return elementType; + } + + public bool isStale() { + getterCalled(); + return wrappedSet is null ? false : wrappedSet.isStale(); + } + + public void dispose() { + if (wrappedSet !is null) { + wrappedSet.removeSetChangeListener(setChangeListener); + setChangeListener = null; + wrappedSet.removeStaleListener(staleListener); + staleListener = null; + wrappedSet = null; + } + elementType = null; + super.dispose(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/StalenessObservableValue.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/StalenessObservableValue.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2007, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Boris Bokowski, IBM Corporation - initial API and implementation + * Matthew Hall - bug 212468 + *******************************************************************************/ +package org.eclipse.core.internal.databinding.observable; + +import org.eclipse.core.databinding.observable.ChangeEvent; +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.IChangeListener; +import org.eclipse.core.databinding.observable.IObservable; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.databinding.observable.value.AbstractObservableValue; + +/** + * An observable value that tracks the staleness of an {@link IObservable}. + * + * @since 1.1 + */ +public class StalenessObservableValue : AbstractObservableValue { + + private class MyListener : IChangeListener, IStaleListener { + public void handleChange(ChangeEvent event) { + if (stale && !event.getObservable().isStale()) { + stale = false; + fireValueChange(Diffs.createValueDiff(Boolean.TRUE, + Boolean.FALSE)); + } + } + + public void handleStale(StaleEvent staleEvent) { + if (!stale) { + stale = true; + fireValueChange(Diffs.createValueDiff(Boolean.FALSE, + Boolean.TRUE)); + } + } + } + + private IObservable tracked; + private bool stale; + private MyListener listener = new MyListener(); + + /** + * Constructs a StalenessObservableValue that tracks the staleness of the + * given {@link IObservable}. + * + * @param observable + * the observable to track + */ + public this(IObservable observable) { + super(observable.getRealm()); + this.tracked = observable; + this.stale = observable.isStale(); + tracked.addChangeListener(listener); + tracked.addStaleListener(listener); + } + + protected Object doGetValue() { + return tracked.isStale() ? Boolean.TRUE : Boolean.FALSE; + } + + public Object getValueType() { + return Boolean.TYPE; + } + + public synchronized void dispose() { + if (tracked !is null) { + tracked.removeChangeListener(listener); + tracked.removeStaleListener(listener); + tracked = null; + listener = null; + } + super.dispose(); + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/StalenessTracker.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/StalenessTracker.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,127 @@ +/******************************************************************************* + * Copyright (c) 2006 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.databinding.observable.ChangeEvent; +import org.eclipse.core.databinding.observable.IChangeListener; +import org.eclipse.core.databinding.observable.IObservable; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.internal.databinding.IdentityWrapper; + +/** + * @since 1.0 + * + */ +public class StalenessTracker { + + private Map staleMap = new HashMap(); + + private int staleCount = 0; + + private final IStalenessConsumer stalenessConsumer; + + private class ChildListener : IStaleListener, IChangeListener { + public void handleStale(StaleEvent event) { + processStalenessChange(cast(IObservable) event.getSource(), true); + } + + public void handleChange(ChangeEvent event) { + processStalenessChange(cast(IObservable) event.getSource(), true); + } + } + + private ChildListener childListener = new ChildListener(); + + /** + * @param observables + * @param stalenessConsumer + */ + public this(IObservable[] observables, + IStalenessConsumer stalenessConsumer) { + this.stalenessConsumer = stalenessConsumer; + for (int i = 0; i < observables.length; i++) { + IObservable observable = observables[i]; + doAddObservable(observable, false); + } + stalenessConsumer.setStale(staleCount > 0); + } + + /** + * @param child + * @param callback + */ + public void processStalenessChange(IObservable child, bool callback) { + bool oldStale = staleCount > 0; + IdentityWrapper wrappedChild = new IdentityWrapper(child); + bool oldChildStale = getOldChildStale(wrappedChild); + bool newChildStale = child.isStale(); + if (oldChildStale !is newChildStale) { + if (oldChildStale) { + staleCount--; + } else { + staleCount++; + } + staleMap.put(wrappedChild, newChildStale ? Boolean.TRUE : Boolean.FALSE); + } + bool newStale = staleCount > 0; + if (callback && (newStale !is oldStale)) { + stalenessConsumer.setStale(newStale); + } + } + + /** + * @param wrappedChild + */ + private bool getOldChildStale(IdentityWrapper wrappedChild) { + Object oldChildValue = staleMap.get(wrappedChild); + bool oldChildStale = oldChildValue is null ? false + : (cast(Boolean) oldChildValue).booleanValue(); + return oldChildStale; + } + + /** + * @param observable + */ + public void addObservable(IObservable observable) { + doAddObservable(observable, true); + } + + private void doAddObservable(IObservable observable, bool callback) { + processStalenessChange(observable, callback); + observable.addChangeListener(childListener); + observable.addStaleListener(childListener); + } + + /** + * @param observable + */ + public void removeObservable(IObservable observable) { + bool oldStale = staleCount > 0; + IdentityWrapper wrappedChild = new IdentityWrapper(observable); + bool oldChildStale = getOldChildStale(wrappedChild); + if (oldChildStale) { + staleCount--; + } + staleMap.remove(wrappedChild); + observable.removeChangeListener(childListener); + observable.removeStaleListener(childListener); + bool newStale = staleCount > 0; + if (newStale !is oldStale) { + stalenessConsumer.setStale(newStale); + } + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableList.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableList.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2006-2008 Cerner Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Brad Reynolds - initial API and implementation + * Matthew Hall - bug 208332 + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable; + +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.databinding.observable.list.IListChangeListener; +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.list.ListChangeEvent; +import org.eclipse.core.databinding.observable.list.ObservableList; + +/** + * ObservableList implementation that prevents modification by consumers. Events + * in the originating wrapped list are propagated and thrown from this instance + * when appropriate. All mutators throw an UnsupportedOperationException. + * + * @since 1.0 + */ +/* + * Implementation makes the assumption that the superclass cast(ObservableList) is + * unmodifiable and that all modify methods throw an + * UnsupportedOperationException. + */ +public class UnmodifiableObservableList : ObservableList { + /** + * List that is being made unmodifiable. + */ + private IObservableList wrappedList; + + private IListChangeListener listChangeListener = new class() IListChangeListener { + public void handleListChange(ListChangeEvent event) { + // Fires a Change and then ListChange event. + fireListChange(event.diff); + } + }; + + private IStaleListener staleListener = new class() IStaleListener { + public void handleStale(StaleEvent event) { + fireStale(); + } + }; + + /** + * @param wrappedList + */ + public this(IObservableList wrappedList) { + super(wrappedList.getRealm(), wrappedList, wrappedList.getElementType()); + this.wrappedList = wrappedList; + + wrappedList.addListChangeListener(listChangeListener); + + wrappedList.addStaleListener(staleListener); + } + + /** + * Because this instance is immutable staleness cannot be changed. + * + * @throws UnsupportedOperationException + * because this instance is unmodifiable. + */ + public void setStale(bool stale) { + throw new UnsupportedOperationException(); + } + + public bool isStale() { + getterCalled(); + return wrappedList is null ? false : wrappedList.isStale(); + } + + public synchronized void dispose() { + if (wrappedList !is null) { + wrappedList.removeListChangeListener(listChangeListener); + wrappedList.removeStaleListener(staleListener); + wrappedList = null; + } + listChangeListener = null; + staleListener = null; + super.dispose(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableSet.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableSet.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2007 Matthew Hall and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Matthew Hall - initial API and implementation (bug 208332) + * Brad Reynolds - initial API and implementation + * (through UnmodifiableObservableList.java) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable; + +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.databinding.observable.set.*; + +/** + * ObservableList implementation that prevents modification by consumers. Events + * in the originating wrapped list are propagated and thrown from this instance + * when appropriate. All mutators throw an UnsupportedOperationException. + * + * @since 1.1 + */ +public class UnmodifiableObservableSet : ObservableSet { + private ISetChangeListener setChangeListener = new class() ISetChangeListener { + public void handleSetChange(SetChangeEvent event) { + fireSetChange(event.diff); + } + }; + + private IStaleListener staleListener = new class() IStaleListener { + public void handleStale(StaleEvent event) { + fireStale(); + } + }; + + private IObservableSet wrappedSet; + + /** + * @param wrappedSet + */ + public this(IObservableSet wrappedSet) { + super(wrappedSet.getRealm(), wrappedSet, wrappedSet.getElementType()); + + this.wrappedSet = wrappedSet; + + wrappedSet.addSetChangeListener(setChangeListener); + wrappedSet.addStaleListener(staleListener); + } + + /** + * Because this instance is immutable staleness cannot be changed. + */ + public void setStale(bool stale) { + throw new UnsupportedOperationException(); + } + + public bool isStale() { + getterCalled(); + return wrappedSet is null ? false : wrappedSet.isStale(); + } + + public synchronized void dispose() { + if (wrappedSet !is null) { + wrappedSet.removeSetChangeListener(setChangeListener); + wrappedSet.removeStaleListener(staleListener); + wrappedSet = null; + } + setChangeListener = null; + staleListener = null; + super.dispose(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableValue.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/UnmodifiableObservableValue.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2008 Matthew Hall and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Matthew Hall - initial API and implementation (bug 219909) + * Matthew Hall - bug 237884 + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable; + +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.databinding.observable.value.AbstractObservableValue; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.observable.value.IValueChangeListener; +import org.eclipse.core.databinding.observable.value.ValueChangeEvent; + +/** + * An unmodifiable wrapper class for IObservableValue instances. + * @since 1.1 + */ +public class UnmodifiableObservableValue : AbstractObservableValue { + private IObservableValue wrappedValue; + + /** + * Constructs an UnmodifiableObservableValue which wraps the given + * observable value + * + * @param wrappedValue + * the observable value to wrap in an unmodifiable instance. + */ + public this(IObservableValue wrappedValue) { + super(wrappedValue.getRealm()); + + this.wrappedValue = wrappedValue; + wrappedValue.addValueChangeListener(new class() IValueChangeListener { + public void handleValueChange(ValueChangeEvent event) { + fireValueChange(event.diff); + } + }); + wrappedValue.addStaleListener(new class() IStaleListener { + public void handleStale(StaleEvent staleEvent) { + fireStale(); + } + }); + } + + protected Object doGetValue() { + return wrappedValue.getValue(); + } + + public Object getValueType() { + return wrappedValue.getValueType(); + } + + public bool isStale() { + return wrappedValue.isStale(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableList.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableList.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,393 @@ +/******************************************************************************* + * Copyright (c) 2008 Matthew Hall and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Matthew Hall - initial API and implementation (bug 218269) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.databinding.observable.list.IListChangeListener; +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.list.ListChangeEvent; +import org.eclipse.core.databinding.observable.list.ListDiff; +import org.eclipse.core.databinding.observable.list.ListDiffEntry; +import org.eclipse.core.databinding.observable.list.ListDiffVisitor; +import org.eclipse.core.databinding.observable.list.ObservableList; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.observable.value.IValueChangeListener; +import org.eclipse.core.databinding.observable.value.ValueChangeEvent; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IStatus; + +/** + * @since 3.3 + * + */ +public class ValidatedObservableList : ObservableList { + private IObservableList target; + private IObservableValue validationStatus; + + // Only true when out of sync with target due to validation status + private bool stale; + + // True when validaton status changes from invalid to valid. + private bool computeNextDiff = false; + + private bool updatingTarget = false; + + private IListChangeListener targetChangeListener = new class() IListChangeListener { + public void handleListChange(ListChangeEvent event) { + if (updatingTarget) + return; + IStatus status = cast(IStatus) validationStatus.getValue(); + if (isValid(status)) { + if (stale) { + // this.stale means we are out of sync with target, + // so reset wrapped list to exactly mirror target + stale = false; + updateWrappedList(new ArrayList(target)); + } else { + ListDiff diff = event.diff; + if (computeNextDiff) { + diff = Diffs.computeListDiff(wrappedList, target); + computeNextDiff = false; + } + applyDiff(diff, wrappedList); + fireListChange(diff); + } + } else { + makeStale(); + } + } + }; + + private static bool isValid(IStatus status) { + return status.isOK() || status.matches(IStatus.INFO | IStatus.WARNING); + } + + private IStaleListener targetStaleListener = new class() IStaleListener { + public void handleStale(StaleEvent staleEvent) { + fireStale(); + } + }; + + private IValueChangeListener validationStatusChangeListener = new class() IValueChangeListener { + public void handleValueChange(ValueChangeEvent event) { + IStatus oldStatus = cast(IStatus) event.diff.getOldValue(); + IStatus newStatus = cast(IStatus) event.diff.getNewValue(); + if (stale && !isValid(oldStatus) && isValid(newStatus)) { + // this.stale means we are out of sync with target, + // reset wrapped list to exactly mirror target + stale = false; + updateWrappedList(new ArrayList(target)); + + // If the validation status becomes valid because of a change in + // target observable + computeNextDiff = true; + } + } + }; + + /** + * @param target + * @param validationStatus + */ + public this(final IObservableList target, + final IObservableValue validationStatus) { + super(target.getRealm(), new ArrayList(target), target.getElementType()); + Assert.isNotNull(validationStatus, + "Validation status observable cannot be null"); //$NON-NLS-1$ + Assert + .isTrue(target.getRealm().equals(validationStatus.getRealm()), + "Target and validation status observables must be on the same realm"); //$NON-NLS-1$ + this.target = target; + this.validationStatus = validationStatus; + target.addListChangeListener(targetChangeListener); + target.addStaleListener(targetStaleListener); + validationStatus.addValueChangeListener(validationStatusChangeListener); + } + + private void makeStale() { + if (!stale) { + stale = true; + fireStale(); + } + } + + private void updateTargetList(ListDiff diff) { + updatingTarget = true; + try { + if (stale) { + stale = false; + applyDiff(Diffs.computeListDiff(target, wrappedList), target); + } else { + applyDiff(diff, target); + } + } finally { + updatingTarget = false; + } + } + + private void applyDiff(ListDiff diff, final List list) { + diff.accept(new class() ListDiffVisitor { + public void handleAdd(int index, Object element) { + list.add(index, element); + } + + public void handleRemove(int index, Object element) { + list.remove(index); + } + + public void handleReplace(int index, Object oldElement, + Object newElement) { + list.set(index, newElement); + } + }); + } + + public bool isStale() { + ObservableTracker.getterCalled(this); + return stale || target.isStale(); + } + + public void add(int index, Object element) { + checkRealm(); + wrappedList.add(index, element); + ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(index, + true, element)); + updateTargetList(diff); + fireListChange(diff); + } + + public bool add(Object o) { + checkRealm(); + add(wrappedList.size(), o); + return true; + } + + public bool addAll(Collection c) { + checkRealm(); + return addAll(wrappedList.size(), c); + } + + public bool addAll(int index, Collection c) { + checkRealm(); + Object[] elements = c.toArray(); + ListDiffEntry[] entries = new ListDiffEntry[elements.length]; + for (int i = 0; i < elements.length; i++) { + wrappedList.add(index + i, elements[i]); + entries[i] = Diffs + .createListDiffEntry(index + i, true, elements[i]); + } + ListDiff diff = Diffs.createListDiff(entries); + updateTargetList(diff); + fireListChange(diff); + return true; + } + + public void clear() { + checkRealm(); + if (isEmpty()) + return; + ListDiff diff = Diffs.computeListDiff(wrappedList, + Collections.EMPTY_LIST); + wrappedList.clear(); + updateTargetList(diff); + fireListChange(diff); + } + + public Iterator iterator() { + getterCalled(); + final ListIterator wrappedIterator = wrappedList.listIterator(); + return new class() Iterator { + Object last = null; + + public bool hasNext() { + return wrappedIterator.hasNext(); + } + + public Object next() { + return last = wrappedIterator.next(); + } + + public void remove() { + int index = wrappedIterator.previousIndex(); + wrappedIterator.remove(); + ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry( + index, false, last)); + updateTargetList(diff); + fireListChange(diff); + } + }; + } + + public ListIterator listIterator() { + return listIterator(0); + } + + public ListIterator listIterator(int index) { + getterCalled(); + final ListIterator wrappedIterator = wrappedList.listIterator(index); + return new class() ListIterator { + int lastIndex = -1; + Object last = null; + + public void add(Object o) { + wrappedIterator.add(o); + lastIndex = previousIndex(); + ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry( + lastIndex, true, o)); + updateTargetList(diff); + fireListChange(diff); + } + + public bool hasNext() { + return wrappedIterator.hasNext(); + } + + public bool hasPrevious() { + return wrappedIterator.hasPrevious(); + } + + public Object next() { + last = wrappedIterator.next(); + lastIndex = previousIndex(); + return last; + } + + public int nextIndex() { + return wrappedIterator.nextIndex(); + } + + public Object previous() { + last = wrappedIterator.previous(); + lastIndex = nextIndex(); + return last; + } + + public int previousIndex() { + return wrappedIterator.previousIndex(); + } + + public void remove() { + wrappedIterator.remove(); + ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry( + lastIndex, false, last)); + lastIndex = -1; + updateTargetList(diff); + fireListChange(diff); + } + + public void set(Object o) { + wrappedIterator.set(o); + ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry( + lastIndex, false, last), Diffs.createListDiffEntry( + lastIndex, true, o)); + last = o; + updateTargetList(diff); + fireListChange(diff); + } + }; + } + + public Object move(int oldIndex, int newIndex) { + checkRealm(); + int size = wrappedList.size(); + if (oldIndex >= size) + throw new IndexOutOfBoundsException( + "oldIndex: " + oldIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$ + if (newIndex >= size) + throw new IndexOutOfBoundsException( + "newIndex: " + newIndex + ", size:" + size); //$NON-NLS-1$ //$NON-NLS-2$ + if (oldIndex is newIndex) + return wrappedList.get(oldIndex); + Object element = wrappedList.remove(oldIndex); + wrappedList.add(newIndex, element); + ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry( + oldIndex, false, element), Diffs.createListDiffEntry(newIndex, + true, element)); + updateTargetList(diff); + fireListChange(diff); + return element; + } + + public Object remove(int index) { + checkRealm(); + Object element = wrappedList.remove(index); + ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(index, + false, element)); + updateTargetList(diff); + fireListChange(diff); + return element; + } + + public bool remove(Object o) { + checkRealm(); + int index = wrappedList.indexOf(o); + if (index is -1) + return false; + remove(index); + return true; + } + + public bool removeAll(Collection c) { + checkRealm(); + List list = new ArrayList(wrappedList); + bool changed = list.removeAll(c); + if (changed) { + ListDiff diff = Diffs.computeListDiff(wrappedList, list); + wrappedList = list; + updateTargetList(diff); + fireListChange(diff); + } + return changed; + } + + public bool retainAll(Collection c) { + checkRealm(); + List list = new ArrayList(wrappedList); + bool changed = list.retainAll(c); + if (changed) { + ListDiff diff = Diffs.computeListDiff(wrappedList, list); + wrappedList = list; + updateTargetList(diff); + fireListChange(diff); + } + return changed; + } + + public Object set(int index, Object element) { + checkRealm(); + Object oldElement = wrappedList.set(index, element); + ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(index, + false, oldElement), Diffs.createListDiffEntry(index, true, + element)); + updateTargetList(diff); + fireListChange(diff); + return oldElement; + } + + public synchronized void dispose() { + target.removeListChangeListener(targetChangeListener); + target.removeStaleListener(targetStaleListener); + validationStatus + .removeValueChangeListener(validationStatusChangeListener); + super.dispose(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableMap.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableMap.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,228 @@ +/******************************************************************************* + * Copyright (c) 2008 Matthew Hall and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Matthew Hall - initial API and implementation (bug 218269) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.databinding.observable.map.IMapChangeListener; +import org.eclipse.core.databinding.observable.map.IObservableMap; +import org.eclipse.core.databinding.observable.map.MapChangeEvent; +import org.eclipse.core.databinding.observable.map.MapDiff; +import org.eclipse.core.databinding.observable.map.ObservableMap; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.observable.value.IValueChangeListener; +import org.eclipse.core.databinding.observable.value.ValueChangeEvent; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IStatus; + +/** + * @since 3.3 + * + */ +public class ValidatedObservableMap : ObservableMap { + private IObservableMap target; + private IObservableValue validationStatus; + + // Only true when out of sync with target due to validation status + private bool stale; + + // True when validation status changes from invalid to valid. + private bool computeNextDiff = false; + + private bool updatingTarget = false; + + private IMapChangeListener targetChangeListener = new class() IMapChangeListener { + public void handleMapChange(MapChangeEvent event) { + if (updatingTarget) + return; + IStatus status = cast(IStatus) validationStatus.getValue(); + if (isValid(status)) { + if (stale) { + // this.stale means we are out of sync with target, + // so reset wrapped list to exactly mirror target + stale = false; + updateWrappedMap(new HashMap(target)); + } else { + MapDiff diff = event.diff; + if (computeNextDiff) { + diff = Diffs.computeMapDiff(wrappedMap, target); + computeNextDiff = false; + } + applyDiff(diff, wrappedMap); + fireMapChange(diff); + } + } else { + makeStale(); + } + } + }; + + private IStaleListener targetStaleListener = new class() IStaleListener { + public void handleStale(StaleEvent staleEvent) { + fireStale(); + } + }; + + private IValueChangeListener validationStatusChangeListener = new class() IValueChangeListener { + public void handleValueChange(ValueChangeEvent event) { + IStatus oldStatus = cast(IStatus) event.diff.getOldValue(); + IStatus newStatus = cast(IStatus) event.diff.getNewValue(); + if (stale && !isValid(oldStatus) && isValid(newStatus)) { + // this.stale means we are out of sync with target, + // reset wrapped map to exactly mirror target + stale = false; + updateWrappedMap(new HashMap(target)); + + // If the validation status becomes valid because of a change in + // target observable + computeNextDiff = true; + } + } + }; + + /** + * @param target + * @param validationStatus + */ + public this(final IObservableMap target, + final IObservableValue validationStatus) { + super(target.getRealm(), new HashMap(target)); + Assert.isNotNull(validationStatus, + "Validation status observable cannot be null"); //$NON-NLS-1$ + Assert + .isTrue(target.getRealm().equals(validationStatus.getRealm()), + "Target and validation status observables must be on the same realm"); //$NON-NLS-1$ + this.target = target; + this.validationStatus = validationStatus; + target.addMapChangeListener(targetChangeListener); + target.addStaleListener(targetStaleListener); + validationStatus.addValueChangeListener(validationStatusChangeListener); + } + + private void updateWrappedMap(Map newMap) { + Map oldMap = wrappedMap; + MapDiff diff = Diffs.computeMapDiff(oldMap, newMap); + wrappedMap = newMap; + fireMapChange(diff); + } + + private static bool isValid(IStatus status) { + return status.isOK() || status.matches(IStatus.INFO | IStatus.WARNING); + } + + private void applyDiff(MapDiff diff, Map map) { + for (Iterator iterator = diff.getRemovedKeys().iterator(); iterator + .hasNext();) + map.remove(iterator.next()); + for (Iterator iterator = diff.getChangedKeys().iterator(); iterator + .hasNext();) { + Object key = iterator.next(); + map.put(key, diff.getNewValue(key)); + } + for (Iterator iterator = diff.getAddedKeys().iterator(); iterator + .hasNext();) { + Object key = iterator.next(); + map.put(key, diff.getNewValue(key)); + } + } + + private void makeStale() { + if (!stale) { + stale = true; + fireStale(); + } + } + + private void updateTargetMap(MapDiff diff) { + updatingTarget = true; + try { + if (stale) { + stale = false; + applyDiff(Diffs.computeMapDiff(target, wrappedMap), target); + } else { + applyDiff(diff, target); + } + } finally { + updatingTarget = false; + } + } + + public bool isStale() { + getterCalled(); + return stale || target.isStale(); + } + + public void clear() { + checkRealm(); + if (isEmpty()) + return; + MapDiff diff = Diffs.computeMapDiff(wrappedMap, Collections.EMPTY_MAP); + wrappedMap = new HashMap(); + updateTargetMap(diff); + fireMapChange(diff); + } + + public Object put(Object key, Object value) { + checkRealm(); + MapDiff diff; + Object oldValue; + if (wrappedMap.containsKey(key)) { + oldValue = wrappedMap.put(key, value); + if (wrappedMap.containsKey(key)) { // Changed + diff = Diffs.createMapDiffSingleChange(key, oldValue, value); + } else { // Removed + diff = Diffs.createMapDiffSingleRemove(key, oldValue); + } + } else { // Added + oldValue = wrappedMap.put(key, value); + diff = Diffs.createMapDiffSingleAdd(key, value); + } + updateTargetMap(diff); + fireMapChange(diff); + return oldValue; + } + + public void putAll(Map m) { + checkRealm(); + Map map = new HashMap(wrappedMap); + map.putAll(m); + MapDiff diff = Diffs.computeMapDiff(wrappedMap, map); + wrappedMap = map; + updateTargetMap(diff); + fireMapChange(diff); + } + + public Object remove(Object key) { + checkRealm(); + if (!wrappedMap.containsKey(key)) + return null; + Object oldValue = wrappedMap.remove(key); + MapDiff diff = Diffs.createMapDiffSingleRemove(key, oldValue); + updateTargetMap(diff); + fireMapChange(diff); + return oldValue; + } + + public synchronized void dispose() { + target.removeMapChangeListener(targetChangeListener); + target.removeStaleListener(targetStaleListener); + validationStatus + .removeValueChangeListener(validationStatusChangeListener); + super.dispose(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableSet.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableSet.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,270 @@ +/******************************************************************************* + * Copyright (c) 2008 Matthew Hall and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Matthew Hall - initial API and implementation (bug 218269) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.databinding.observable.set.IObservableSet; +import org.eclipse.core.databinding.observable.set.ISetChangeListener; +import org.eclipse.core.databinding.observable.set.ObservableSet; +import org.eclipse.core.databinding.observable.set.SetChangeEvent; +import org.eclipse.core.databinding.observable.set.SetDiff; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.observable.value.IValueChangeListener; +import org.eclipse.core.databinding.observable.value.ValueChangeEvent; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IStatus; + +/** + * @since 3.3 + * + */ +public class ValidatedObservableSet : ObservableSet { + private IObservableSet target; + private IObservableValue validationStatus; + + // Only true when out of sync with target due to validation status + private bool stale; + + // True when validation status changes from invalid to valid. + private bool computeNextDiff = false; + + private bool updatingTarget = false; + + private ISetChangeListener targetChangeListener = new class() ISetChangeListener { + public void handleSetChange(SetChangeEvent event) { + if (updatingTarget) + return; + IStatus status = cast(IStatus) validationStatus.getValue(); + if (isValid(status)) { + if (stale) { + // this.stale means we are out of sync with target, + // so reset wrapped list to exactly mirror target + stale = false; + updateWrappedSet(new HashSet(target)); + } else { + SetDiff diff = event.diff; + if (computeNextDiff) { + diff = Diffs.computeSetDiff(wrappedSet, target); + computeNextDiff = false; + } + applyDiff(diff, wrappedSet); + fireSetChange(diff); + } + } else { + makeStale(); + } + } + }; + + private IStaleListener targetStaleListener = new class() IStaleListener { + public void handleStale(StaleEvent staleEvent) { + fireStale(); + } + }; + + private IValueChangeListener validationStatusChangeListener = new class() IValueChangeListener { + public void handleValueChange(ValueChangeEvent event) { + IStatus oldStatus = cast(IStatus) event.diff.getOldValue(); + IStatus newStatus = cast(IStatus) event.diff.getNewValue(); + if (stale && !isValid(oldStatus) && isValid(newStatus)) { + // this.stale means we are out of sync with target, + // reset wrapped set to exactly mirror target + stale = false; + updateWrappedSet(new HashSet(target)); + + // If the validation status becomes valid because of a change in + // target observable + computeNextDiff = true; + } + } + }; + + /** + * @param target + * @param validationStatus + */ + public this(final IObservableSet target, + final IObservableValue validationStatus) { + super(target.getRealm(), new HashSet(target), target.getElementType()); + Assert.isNotNull(validationStatus, + "Validation status observable cannot be null"); //$NON-NLS-1$ + Assert + .isTrue(target.getRealm().equals(validationStatus.getRealm()), + "Target and validation status observables must be on the same realm"); //$NON-NLS-1$ + this.target = target; + this.validationStatus = validationStatus; + target.addSetChangeListener(targetChangeListener); + target.addStaleListener(targetStaleListener); + validationStatus.addValueChangeListener(validationStatusChangeListener); + } + + private void updateWrappedSet(Set newSet) { + Set oldSet = wrappedSet; + SetDiff diff = Diffs.computeSetDiff(oldSet, newSet); + wrappedSet = newSet; + fireSetChange(diff); + } + + private static bool isValid(IStatus status) { + return status.isOK() || status.matches(IStatus.INFO | IStatus.WARNING); + } + + private void applyDiff(SetDiff diff, Set set) { + for (Iterator iterator = diff.getRemovals().iterator(); iterator + .hasNext();) { + set.remove(iterator.next()); + } + for (Iterator iterator = diff.getAdditions().iterator(); iterator + .hasNext();) { + set.add(iterator.next()); + } + } + + private void makeStale() { + if (!stale) { + stale = true; + fireStale(); + } + } + + private void updateTargetSet(SetDiff diff) { + updatingTarget = true; + try { + if (stale) { + stale = false; + applyDiff(Diffs.computeSetDiff(target, wrappedSet), target); + } else { + applyDiff(diff, target); + } + } finally { + updatingTarget = false; + } + } + + public bool isStale() { + getterCalled(); + return stale || target.isStale(); + } + + public bool add(Object o) { + getterCalled(); + bool changed = wrappedSet.add(o); + if (changed) { + SetDiff diff = Diffs.createSetDiff(Collections.singleton(o), + Collections.EMPTY_SET); + updateTargetSet(diff); + fireSetChange(diff); + } + return changed; + } + + public bool addAll(Collection c) { + getterCalled(); + HashSet set = new HashSet(wrappedSet); + bool changed = set.addAll(c); + if (changed) { + SetDiff diff = Diffs.computeSetDiff(wrappedSet, set); + wrappedSet = set; + updateTargetSet(diff); + fireSetChange(diff); + } + return changed; + } + + public void clear() { + getterCalled(); + if (isEmpty()) + return; + SetDiff diff = Diffs.createSetDiff(Collections.EMPTY_SET, wrappedSet); + wrappedSet = new HashSet(); + updateTargetSet(diff); + fireSetChange(diff); + } + + public Iterator iterator() { + getterCalled(); + final Iterator wrappedIterator = wrappedSet.iterator(); + return new class() Iterator { + Object last = null; + + public bool hasNext() { + return wrappedIterator.hasNext(); + } + + public Object next() { + return last = wrappedIterator.next(); + } + + public void remove() { + wrappedIterator.remove(); + SetDiff diff = Diffs.createSetDiff(Collections.EMPTY_SET, + Collections.singleton(last)); + updateTargetSet(diff); + fireSetChange(diff); + } + }; + } + + public bool remove(Object o) { + getterCalled(); + bool changed = wrappedSet.remove(o); + if (changed) { + SetDiff diff = Diffs.createSetDiff(Collections.EMPTY_SET, + Collections.singleton(o)); + updateTargetSet(diff); + fireSetChange(diff); + } + return changed; + } + + public bool removeAll(Collection c) { + getterCalled(); + Set set = new HashSet(wrappedSet); + bool changed = set.removeAll(c); + if (changed) { + SetDiff diff = Diffs.computeSetDiff(wrappedSet, set); + wrappedSet = set; + updateTargetSet(diff); + fireSetChange(diff); + } + return changed; + } + + public bool retainAll(Collection c) { + getterCalled(); + Set set = new HashSet(wrappedSet); + bool changed = set.retainAll(c); + if (changed) { + SetDiff diff = Diffs.computeSetDiff(wrappedSet, set); + wrappedSet = set; + updateTargetSet(diff); + fireSetChange(diff); + } + return changed; + } + + public synchronized void dispose() { + target.removeSetChangeListener(targetChangeListener); + target.removeStaleListener(targetStaleListener); + validationStatus + .removeValueChangeListener(validationStatusChangeListener); + super.dispose(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableValue.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableValue.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,170 @@ +/******************************************************************************* + * Copyright (c) 2008 Matthew Hall and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Matthew Hall - initial API and implementation (bug 218269) + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.IStaleListener; +import org.eclipse.core.databinding.observable.ObservableTracker; +import org.eclipse.core.databinding.observable.StaleEvent; +import org.eclipse.core.databinding.observable.value.AbstractObservableValue; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.observable.value.IValueChangeListener; +import org.eclipse.core.databinding.observable.value.IVetoableValue; +import org.eclipse.core.databinding.observable.value.ValueChangeEvent; +import org.eclipse.core.databinding.observable.value.ValueChangingEvent; +import org.eclipse.core.internal.databinding.Util; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.IStatus; + +/** + * An {@link IObservableValue} wrapper that stays in sync with the target + * observable as long as a given validation status is valid. + *
    + *
  • While status is valid, ValidatedObservableValue stays in sync with its + * target. + *
  • When status becomes invalid, ValidatedObservableValue will retain the + * last valid value of its target. + *
  • While status is invalid, changes in the target observable cause + * ValidatedObservableValue to fire a stale event, to indicate that changes are + * pending. + *
  • When status becomes valid, pending value changes are performed (if any) + * and synchronization resumes. + *
+ *

+ * Note: + *

    + *
  • By default, a status is valid if its + * {@link IStatus#getSeverity() severity} is {@link IStatus#OK OK}, + * {@link IStatus#INFO INFO}, or {@link IStatus#WARNING WARNING} + *
  • Calls to {@link #setValuecast(Object)} on the validated observable changes + * the value regardless of the validation status. + *
  • This class will not forward {@link ValueChangingEvent} events from a + * wrapped {@link IVetoableValue}. + *
+ * + * @since 1.2 + */ +public class ValidatedObservableValue : AbstractObservableValue { + private IObservableValue target; + private IObservableValue validationStatus; + + private Object cachedValue; + private bool stale; + private bool updatingTarget = false; + + private IValueChangeListener targetChangeListener = new class() IValueChangeListener { + public void handleValueChange(ValueChangeEvent event) { + if (updatingTarget) + return; + IStatus status = cast(IStatus) validationStatus.getValue(); + if (isValid(status)) + internalSetValue(event.diff.getNewValue(), false); + else + makeStale(); + } + }; + + private static bool isValid(IStatus status) { + return status.isOK() || status.matches(IStatus.INFO | IStatus.WARNING); + } + + private IStaleListener targetStaleListener = new class() IStaleListener { + public void handleStale(StaleEvent staleEvent) { + fireStale(); + } + }; + + private IValueChangeListener validationStatusChangeListener = new class() IValueChangeListener { + public void handleValueChange(ValueChangeEvent event) { + IStatus oldStatus = cast(IStatus) event.diff.getOldValue(); + IStatus newStatus = cast(IStatus) event.diff.getNewValue(); + if (stale && !isValid(oldStatus) && isValid(newStatus)) { + internalSetValue(target.getValue(), false); + } + } + }; + + /** + * Constructs an observable value + * + * @param target + * the observable value to be wrapped + * @param validationStatus + * an observable value of type {@link IStatus}.class which + * contains the current validation status + */ + public this(IObservableValue target, + IObservableValue validationStatus) { + super(target.getRealm()); + Assert.isNotNull(validationStatus, + "Validation status observable cannot be null"); //$NON-NLS-1$ + Assert + .isTrue(target.getRealm().equals(validationStatus.getRealm()), + "Target and validation status observables must be on the same realm"); //$NON-NLS-1$ + this.target = target; + this.validationStatus = validationStatus; + this.cachedValue = target.getValue(); + + target.addValueChangeListener(targetChangeListener); + target.addStaleListener(targetStaleListener); + validationStatus.addValueChangeListener(validationStatusChangeListener); + } + + private void makeStale() { + if (!stale) { + stale = true; + fireStale(); + } + } + + public bool isStale() { + ObservableTracker.getterCalled(this); + return stale || target.isStale(); + } + + protected Object doGetValue() { + return cachedValue; + } + + private void internalSetValue(Object value, bool updateTarget) { + Object oldValue = cachedValue; + cachedValue = value; + if (updateTarget) { + updatingTarget = true; + try { + target.setValue(value); + cachedValue = target.getValue(); + } finally { + updatingTarget = false; + } + } + stale = false; + if (!Util.equals(oldValue, cachedValue)) + fireValueChange(Diffs.createValueDiff(oldValue, cachedValue)); + } + + protected void doSetValue(Object value) { + internalSetValue(value, true); + } + + public Object getValueType() { + return target.getValueType(); + } + + public synchronized void dispose() { + target.removeValueChangeListener(targetChangeListener); + target.removeStaleListener(targetStaleListener); + validationStatus + .removeValueChangeListener(validationStatusChangeListener); + super.dispose(); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableList.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableList.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,175 @@ +/******************************************************************************* + * Copyright (c) 2005-2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 147515 + * Matthew Hall - bug 221351 + *******************************************************************************/ +package org.eclipse.core.internal.databinding.observable.masterdetail; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.IObserving; +import org.eclipse.core.databinding.observable.list.IListChangeListener; +import org.eclipse.core.databinding.observable.list.IObservableList; +import org.eclipse.core.databinding.observable.list.ListChangeEvent; +import org.eclipse.core.databinding.observable.list.ObservableList; +import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.observable.value.IValueChangeListener; +import org.eclipse.core.databinding.observable.value.ValueChangeEvent; +import org.eclipse.core.runtime.Assert; + +/** + * @since 3.2 + * + */ + +public class DetailObservableList : ObservableList , IObserving { + + private bool updating = false; + + private IListChangeListener innerChangeListener = new class() IListChangeListener { + public void handleListChange(ListChangeEvent event) { + if (!updating) { + fireListChange(event.diff); + } + } + }; + + private Object currentOuterValue; + + private IObservableList innerObservableList; + + private IObservableFactory factory; + + private IObservableValue outerObservableValue; + + private Object detailType; + + /** + * @param factory + * @param outerObservableValue + * @param detailType + */ + public this(IObservableFactory factory, + IObservableValue outerObservableValue, Object detailType) { + super(outerObservableValue.getRealm(), Collections.EMPTY_LIST, detailType); + this.factory = factory; + this.outerObservableValue = outerObservableValue; + this.detailType = detailType; + updateInnerObservableList(outerObservableValue); + + outerObservableValue.addValueChangeListener(outerChangeListener); + } + + IValueChangeListener outerChangeListener = new class() IValueChangeListener { + public void handleValueChange(ValueChangeEvent event) { + List oldList = new ArrayList(wrappedList); + updateInnerObservableList(outerObservableValue); + fireListChange(Diffs.computeListDiff(oldList, wrappedList)); + } + }; + + private void updateInnerObservableList(IObservableValue outerObservableValue) { + if (innerObservableList !is null) { + innerObservableList.removeListChangeListener(innerChangeListener); + innerObservableList.dispose(); + } + currentOuterValue = outerObservableValue.getValue(); + if (currentOuterValue is null) { + innerObservableList = null; + wrappedList = Collections.EMPTY_LIST; + } else { + this.innerObservableList = cast(IObservableList) factory + .createObservable(currentOuterValue); + wrappedList = innerObservableList; + + if (detailType !is null) { + Object innerValueType = innerObservableList.getElementType(); + Assert.isTrue(getElementType().equals(innerValueType), + "Cannot change value type in a nested observable list"); //$NON-NLS-1$ + } + innerObservableList.addListChangeListener(innerChangeListener); + } + } + + public bool add(Object o) { + return wrappedList.add(o); + } + + public void add(int index, Object element) { + wrappedList.add(index, element); + } + + public bool remove(Object o) { + return wrappedList.remove(o); + } + + public Object set(int index, Object element) { + return wrappedList.set(index, element); + } + + public Object move(int oldIndex, int newIndex) { + if (innerObservableList !is null) + return innerObservableList.move(oldIndex, newIndex); + return super.move(oldIndex, newIndex); + } + + public Object remove(int index) { + return wrappedList.remove(index); + } + + public bool addAll(Collection c) { + return wrappedList.addAll(c); + } + + public bool addAll(int index, Collection c) { + return wrappedList.addAll(index, c); + } + + public bool removeAll(Collection c) { + return wrappedList.removeAll(c); + } + + public bool retainAll(Collection c) { + return wrappedList.retainAll(c); + } + + public void clear() { + wrappedList.clear(); + } + + public void dispose() { + super.dispose(); + + if (outerObservableValue !is null) { + outerObservableValue.removeValueChangeListener(outerChangeListener); + outerObservableValue.dispose(); + } + if (innerObservableList !is null) { + innerObservableList.removeListChangeListener(innerChangeListener); + innerObservableList.dispose(); + } + currentOuterValue = null; + factory = null; + innerObservableList = null; + innerChangeListener = null; + } + + public Object getObserved() { + if ( null !is cast(IObserving)innerObservableList ) { + return (cast(IObserving) innerObservableList).getObserved(); + } + return null; + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableMap.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableMap.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,126 @@ +/******************************************************************************* + * Copyright (c) 2008 Matthew Hall and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Matthew Hall - initial API and implementation (bug 221704) + * Matthew Hall - bug 223114 + ******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable.masterdetail; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.map.IMapChangeListener; +import org.eclipse.core.databinding.observable.map.IObservableMap; +import org.eclipse.core.databinding.observable.map.MapChangeEvent; +import org.eclipse.core.databinding.observable.map.ObservableMap; +import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.observable.value.IValueChangeListener; +import org.eclipse.core.databinding.observable.value.ValueChangeEvent; + +/** + * @since 1.1 + * + */ +public class DetailObservableMap : ObservableMap { + private bool updating = false; + + private IObservableValue master; + private IObservableFactory detailFactory; + + private IObservableMap detailMap; + + private IValueChangeListener masterChangeListener = new class() IValueChangeListener { + public void handleValueChange(ValueChangeEvent event) { + Map oldMap = new HashMap(wrappedMap); + updateDetailMap(); + fireMapChange(Diffs.computeMapDiff(oldMap, wrappedMap)); + } + }; + + private IMapChangeListener detailChangeListener = new class() IMapChangeListener { + public void handleMapChange(MapChangeEvent event) { + if (!updating) { + fireMapChange(event.diff); + } + } + }; + + /** + * Constructs a new DetailObservableMap + * + * @param detailFactory + * observable factory that creates IObservableMap instances given + * the current value of master observable value + * @param master + * + */ + public this(IObservableFactory detailFactory, + IObservableValue master) { + super(master.getRealm(), Collections.EMPTY_MAP); + this.master = master; + this.detailFactory = detailFactory; + + updateDetailMap(); + master.addValueChangeListener(masterChangeListener); + } + + private void updateDetailMap() { + Object masterValue = master.getValue(); + if (detailMap !is null) { + detailMap.removeMapChangeListener(detailChangeListener); + detailMap.dispose(); + } + + if (masterValue is null) { + detailMap = null; + wrappedMap = Collections.EMPTY_MAP; + } else { + detailMap = cast(IObservableMap) detailFactory + .createObservable(masterValue); + wrappedMap = detailMap; + detailMap.addMapChangeListener(detailChangeListener); + } + } + + public Object put(Object key, Object value) { + return detailMap.put(key, value); + } + + public void putAll(Map map) { + detailMap.putAll(map); + } + + public Object remove(Object key) { + return detailMap.remove(key); + } + + public void clear() { + detailMap.clear(); + } + + public synchronized void dispose() { + if (master !is null) { + master.removeValueChangeListener(masterChangeListener); + master = null; + masterChangeListener = null; + } + detailFactory = null; + if (detailMap !is null) { + detailMap.removeMapChangeListener(detailChangeListener); + detailMap.dispose(); + detailMap = null; + } + detailChangeListener = null; + super.dispose(); + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableSet.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableSet.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,158 @@ +/******************************************************************************* + * Copyright (c) 2005-2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matthew Hall - bug 221351 + *******************************************************************************/ +package org.eclipse.core.internal.databinding.observable.masterdetail; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.IObserving; +import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; +import org.eclipse.core.databinding.observable.set.IObservableSet; +import org.eclipse.core.databinding.observable.set.ISetChangeListener; +import org.eclipse.core.databinding.observable.set.ObservableSet; +import org.eclipse.core.databinding.observable.set.SetChangeEvent; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.observable.value.IValueChangeListener; +import org.eclipse.core.databinding.observable.value.ValueChangeEvent; +import org.eclipse.core.runtime.Assert; + +/** + * @since 3.2 + * + */ +public class DetailObservableSet : ObservableSet , IObserving { + + private bool updating = false; + + private ISetChangeListener innerChangeListener = new class() ISetChangeListener { + public void handleSetChange(SetChangeEvent event) { + if (!updating) { + fireSetChange(event.diff); + } + } + }; + + private Object currentOuterValue; + + private IObservableSet innerObservableSet; + + private IObservableValue outerObservableValue; + + private IObservableFactory factory; + + /** + * @param factory + * @param outerObservableValue + * @param detailType + */ + public this(IObservableFactory factory, + IObservableValue outerObservableValue, Object detailType) { + super(outerObservableValue.getRealm(), Collections.EMPTY_SET, + detailType); + this.factory = factory; + this.outerObservableValue = outerObservableValue; + updateInnerObservableSet(outerObservableValue); + + outerObservableValue.addValueChangeListener(outerChangeListener); + } + + IValueChangeListener outerChangeListener = new class() IValueChangeListener { + public void handleValueChange(ValueChangeEvent event) { + Set oldSet = new HashSet(wrappedSet); + updateInnerObservableSet(outerObservableValue); + fireSetChange(Diffs.computeSetDiff(oldSet, wrappedSet)); + } + }; + + private void updateInnerObservableSet(IObservableValue outerObservableValue) { + currentOuterValue = outerObservableValue.getValue(); + if (innerObservableSet !is null) { + innerObservableSet.removeSetChangeListener(innerChangeListener); + innerObservableSet.dispose(); + } + if (currentOuterValue is null) { + innerObservableSet = null; + wrappedSet = Collections.EMPTY_SET; + } else { + this.innerObservableSet = cast(IObservableSet) factory + .createObservable(currentOuterValue); + wrappedSet = innerObservableSet; + + if (elementType !is null) { + Object innerValueType = innerObservableSet.getElementType(); + + Assert.isTrue(elementType.equals(innerValueType), + "Cannot change value type in a nested observable set"); //$NON-NLS-1$ + } + + innerObservableSet.addSetChangeListener(innerChangeListener); + } + } + + public bool add(Object o) { + getterCalled(); + return wrappedSet.add(o); + } + + public bool remove(Object o) { + getterCalled(); + return wrappedSet.remove(o); + } + + public bool addAll(Collection c) { + getterCalled(); + return wrappedSet.addAll(c); + } + + public bool removeAll(Collection c) { + getterCalled(); + return wrappedSet.removeAll(c); + } + + public bool retainAll(Collection c) { + getterCalled(); + return wrappedSet.retainAll(c); + } + + public void clear() { + getterCalled(); + wrappedSet.clear(); + } + + public void dispose() { + super.dispose(); + + if (outerObservableValue !is null) { + outerObservableValue.removeValueChangeListener(outerChangeListener); + outerObservableValue.dispose(); + } + if (innerObservableSet !is null) { + innerObservableSet.removeSetChangeListener(innerChangeListener); + innerObservableSet.dispose(); + } + currentOuterValue = null; + factory = null; + innerObservableSet = null; + innerChangeListener = null; + } + + public Object getObserved() { + if ( null !is cast(IObserving)innerObservableSet ) { + return (cast(IObserving) innerObservableSet).getObserved(); + } + return null; + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableValue.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/masterdetail/DetailObservableValue.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2005, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 164653 + * Brad Reynolds - bug 147515 + *******************************************************************************/ +package org.eclipse.core.internal.databinding.observable.masterdetail; + +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.IObserving; +import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; +import org.eclipse.core.databinding.observable.value.AbstractObservableValue; +import org.eclipse.core.databinding.observable.value.IObservableValue; +import org.eclipse.core.databinding.observable.value.IValueChangeListener; +import org.eclipse.core.databinding.observable.value.ValueChangeEvent; +import org.eclipse.core.runtime.Assert; + +/** + * @since 1.0 + * + */ +public class DetailObservableValue : AbstractObservableValue , IObserving { + + private bool updating = false; + + private IValueChangeListener innerChangeListener = new class() IValueChangeListener { + public void handleValueChange(ValueChangeEvent event) { + if (!updating) { + fireValueChange(event.diff); + } + } + }; + + private Object currentOuterValue; + + private IObservableValue innerObservableValue; + + private Object detailType; + + private IObservableValue outerObservableValue; + + private IObservableFactory factory; + + /** + * @param outerObservableValue + * @param factory + * @param detailType + */ + public this(IObservableValue outerObservableValue, + IObservableFactory factory, Object detailType) { + super(outerObservableValue.getRealm()); + this.factory = factory; + this.detailType = detailType; + this.outerObservableValue = outerObservableValue; + updateInnerObservableValue(outerObservableValue); + + outerObservableValue.addValueChangeListener(outerChangeListener); + } + + IValueChangeListener outerChangeListener = new class() IValueChangeListener { + public void handleValueChange(ValueChangeEvent event) { + Object oldValue = doGetValue(); + updateInnerObservableValue(outerObservableValue); + fireValueChange(Diffs.createValueDiff(oldValue, doGetValue())); + } + }; + + private void updateInnerObservableValue( + IObservableValue outerObservableValue) { + currentOuterValue = outerObservableValue.getValue(); + if (innerObservableValue !is null) { + innerObservableValue.removeValueChangeListener(innerChangeListener); + innerObservableValue.dispose(); + } + if (currentOuterValue is null) { + innerObservableValue = null; + } else { + this.innerObservableValue = cast(IObservableValue) factory + .createObservable(currentOuterValue); + Object innerValueType = innerObservableValue.getValueType(); + + if (detailType !is null) { + Assert + .isTrue( + detailType.equals(innerValueType), + "Cannot change value type in a nested observable value, from " + innerValueType + " to " + detailType); //$NON-NLS-1$ //$NON-NLS-2$ + } + innerObservableValue.addValueChangeListener(innerChangeListener); + } + } + + public void doSetValue(Object value) { + if (innerObservableValue !is null) + innerObservableValue.setValue(value); + } + + public Object doGetValue() { + return innerObservableValue is null ? null : innerObservableValue + .getValue(); + } + + public Object getValueType() { + return detailType; + } + + public void dispose() { + super.dispose(); + + if (outerObservableValue !is null) { + outerObservableValue.removeValueChangeListener(outerChangeListener); + outerObservableValue.dispose(); + } + if (innerObservableValue !is null) { + innerObservableValue.removeValueChangeListener(innerChangeListener); + innerObservableValue.dispose(); + } + currentOuterValue = null; + factory = null; + innerObservableValue = null; + innerChangeListener = null; + } + + public Object getObserved() { + if ( null !is cast(IObserving)innerObservableValue ) { + return (cast(IObserving)innerObservableValue).getObserved(); + } + return null; + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/AbstractObservableTree.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/AbstractObservableTree.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Brad Reynolds - bug 164134 + *******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable.tree; + +import org.eclipse.core.databinding.observable.AbstractObservable; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.util.Policy; +import org.eclipse.core.internal.databinding.BindingMessages; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.core.runtime.Status; + +/** + * @since 3.3 + * + */ +public abstract class AbstractObservableTree : AbstractObservable + , IObservableTree { + + private bool stale; + + private ListenerList treeListeners = new ListenerListcast(ListenerList.IDENTITY); + + /** + * @param realm + */ + public this(Realm realm) { + super(realm); + } + + public void addChild(TreePath parentPath, Object childElement) { + throw new UnsupportedOperationException(); + } + + public void addTreeChangeListener(ITreeChangeListener listener) { + treeListeners.add(listener); + } + + public int getChildCount(TreePath parentPath) { + return getChildren(parentPath).length; + } + + public bool hasChildren(TreePath parentPath) { + return getChildCount(parentPath) > 0; + } + + public void insertChild(TreePath parentPath, int index, Object childElement) { + throw new UnsupportedOperationException(); + } + + public bool isLazy() { + return false; + } + + public bool isOrdered() { + return false; + } + + public void removeChild(TreePath parentPath, Object childElement) { + throw new UnsupportedOperationException(); + } + + public void removeChild(TreePath parentPath, int index) { + throw new UnsupportedOperationException(); + } + + public void removeTreeChangeListener(ITreeChangeListener listener) { + treeListeners.remove(listener); + } + + public void setChildCount(TreePath parentPath, int count) { + throw new UnsupportedOperationException(); + } + + public void setChildren(TreePath parentPath, Object[] children) { + throw new UnsupportedOperationException(); + } + + public void updateChildren(IChildrenUpdate update) { + TreePath parent = update.getParent(); + Object[] children = getChildren(parent); + for (int i = 0; i < update.getLength(); i++) { + int targetIndex = update.getOffset() + i; + if (targetIndex < children.length) { + update.setChild(children[targetIndex], targetIndex); + } else { + update + .setStatus(new Status( + IStatus.WARNING, + Policy.JFACE_DATABINDING, + IStatus.OK, + BindingMessages + .getStringcast(BindingMessages.INDEX_OUT_OF_RANGE), + null)); + } + } + update.done(); + } + + public void updateChildrenCount(IChildrenCountUpdate update) { + TreePath[] parents = update.getParents(); + for (int i = 0; i < parents.length; i++) { + update.setChildCount(parents[i], getChildCount(parents[i])); + } + update.done(); + } + + public void updateHasChildren(IHasChildrenUpdate update) { + TreePath[] parents = update.getElements(); + for (int i = 0; i < parents.length; i++) { + update.setHasChilren(parents[i], hasChildren(parents[i])); + } + update.done(); + } + + public bool isStale() { + return stale; + } + + /** + * @param stale + */ + public void setStale(bool stale) { + this.stale = stale; + if (stale) { + fireStale(); + } + } + + protected void fireTreeChange(TreeDiff diff) { + // fire general change event first + fireChange(); + + Object[] listeners = treeListeners.getListeners(); + TreeChangeEvent event = new TreeChangeEvent(this, diff); + for (int i = 0; i < listeners.length; i++) { + (cast(ITreeChangeListener) listeners[i]).handleTreeChange(event); + } + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IChildrenCountUpdate.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IChildrenCountUpdate.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.databinding.observable.tree; + +/** + * Request monitor used to collect the number of children for an element in a lazy + * observable tree. + * + * @since 3.3 + */ +public interface IChildrenCountUpdate : IViewerUpdate { + + /** + * Returns the parent elements that children counts have been requested for + * as tree paths. An empty path identifies the root element. + * + * @return parent elements as tree paths + */ + public TreePath[] getParents(); + + /** + * Sets the number of children for the given parent. + * + * @param parentPath + * parent element or empty path for root element + * @param numChildren + * number of children + */ + public void setChildCount(TreePath parentPath, int numChildren); +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IChildrenUpdate.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IChildrenUpdate.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.databinding.observable.tree; + +/** + * Context sensitive children update request for a parent and subrange of its + * children. + * + * @since 3.3 + */ +public interface IChildrenUpdate : IViewerUpdate { + + /** + * Returns the parent element that children are being requested for + * as a tree path. An empty path identifies the root element. + * + * @return parent element as a tree path + */ + public TreePath getParent(); + + /** + * Returns the offset at which children have been requested for. This is + * the index of the first child being requested. + * + * @return offset at which children have been requested for + */ + public int getOffset(); + + /** + * Returns the number of children requested. + * + * @return number of children requested + */ + public int getLength(); + + /** + * Sets the child for this request's parent at the given offset. + * + * @param child child + * @param index child offset + * + * TODO: what to do with null + */ + public void setChild(Object child, int index); +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IHasChildrenUpdate.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IHasChildrenUpdate.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.databinding.observable.tree; + +/** + * Context sensitive update request for whether elements have children. + * + * @since 3.3 + */ +public interface IHasChildrenUpdate : IViewerUpdate { + + /** + * The elements this request is for specified as tree paths. An empty path + * identifies the root element. + * + * @return elements as tree paths + */ + public TreePath[] getElements(); + + /** + * Sets whether the given element has children. + * + * @param element + * tree path to element, or empty for root element + * @param hasChildren + * whether it has children + */ + public void setHasChilren(TreePath element, bool hasChildren); +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IObservableTree.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IObservableTree.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable.tree; + +import org.eclipse.core.databinding.observable.IObservable; + +/** + * + * A tree whose changes can be tracked by tree change listeners. If the tree is + * ordered ({@link #isOrdered()}), the order of children for a given tree path + * matters, and tree change notifications will always specify indices. If the + * tree is unordered, the children of a tree path are an unordered set and + * indices in change notifications are not specified. + * + *

+ * This interface is not intended to be implemented by clients. Clients should + * instead subclass one of the framework classes that implement this interface. + * Note that direct implementers of this interface outside of the framework will + * be broken in future releases when methods are added to this interface. + *

+ * + * @since 1.1 + */ +public interface IObservableTree : IObservable { + + /** + * Element that can be returned from synchronous getters if this observable + * tree is lazy. + */ + public final static Object UNKNOWN_ELEMENT = new Object(); + + /** + * @param listener + */ + public void addTreeChangeListener(ITreeChangeListener listener); + + /** + * @param listener + */ + public void removeTreeChangeListener(ITreeChangeListener listener); + + /** + * Returns whether the order of children for a given parent is important. If + * this tree is ordered, tree change notifications will always specify + * indices. + * + * @return true if the order of children for a given parent is important + */ + public bool isOrdered(); + + /** + * Returns whether this tree is optimized to fetch subsets of children + * lazily and possibly asynchronously. Implies {@link #isOrdered()}. + * + * @return true if this tree + */ + public bool isLazy(); + + /** + * @param parentPath + * @return the children at the given parent path + */ + public Object[] getChildren(TreePath parentPath); + + /** + * @param parentPath + * @param children + */ + public void setChildren(TreePath parentPath, Object[] children); + + /** + * @param parentPath + * @param childElement + */ + public void addChild(TreePath parentPath, Object childElement); + + /** + * @param parentPath + * @param childElement + */ + public void removeChild(TreePath parentPath, Object childElement); + + /** + * @param parentPath + * @param index + * @param childElement + */ + public void insertChild(TreePath parentPath, int index, Object childElement); + + /** + * @param parentPath + * @param index + */ + public void removeChild(TreePath parentPath, int index); + + /** + * @param parentPath + * @return true if the element at the given path has children + */ + public bool hasChildren(TreePath parentPath); + + /** + * @param parentPath + * @return the number of children of the element at the given path + */ + public int getChildCount(TreePath parentPath); + + /** + * @param parentPath + * @param count + */ + public void setChildCount(TreePath parentPath, int count); + + /** + * Updates the number of children for the given parent elements in the + * specified request. + * + * @param update specifies counts to update and stores result + */ + public void updateChildrenCount(IChildrenCountUpdate update); + + /** + * Updates children as requested by the update. + * + * @param update specifies children to update and stores result + */ + public void updateChildren(IChildrenUpdate update); + + /** + * Updates whether elements have children. + * + * @param update specifies elements to update and stores result + */ + public void updateHasChildren(IHasChildrenUpdate update); + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IOrderedTreeProvider.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IOrderedTreeProvider.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable.tree; + +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.list.IObservableList; + +/** + * Objects that implement this interface are capable of describing a tree by + * returning the list of children of any given element in the tree. + * + * @since 3.3 + */ +public interface IOrderedTreeProvider { + /** + * Returns the children of the given element, or null if the element is a + * leaf node. The caller of this method is expected to dispose the result + * list when it is no longer needed. + * + * @param element + * the tree path of the element to query + * @return the children of the given element, or null if the element is a + * leaf node + */ + IObservableList createChildList(TreePath element); + + /** + * @return the realm shared by all child lists + */ + Realm getRealm(); +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/ITreeChangeListener.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/ITreeChangeListener.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable.tree; + +/** + * @since 3.3 + * + */ +public interface ITreeChangeListener { + /** + * @param event + */ + void handleTreeChange(TreeChangeEvent event); + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IUnorderedTreeProvider.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IUnorderedTreeProvider.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable.tree; + +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.set.IObservableSet; + +/** + * @since 1.0 + * + */ +public interface IUnorderedTreeProvider { + /** + * @return the realm for the createChildSet method + */ + public Realm getRealm(); + + /** + * Returns the children of the given element, or null if the element is a leaf node. + * The caller of this method is expected to dispose the result set when it is no + * longer needed. + * + * @param element element to query + * @return the children of the given element, or null if the element is a leaf node + */ + IObservableSet createChildSet(Object element); +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IViewerUpdate.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/IViewerUpdate.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.core.internal.databinding.observable.tree; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; + +/** + * A context sensitive viewer update request. + * + * @since 3.3 + */ +public interface IViewerUpdate : IProgressMonitor { + + /** + * Sets the status of this request, possibly null. + * When a request fails, the status indicates why the request failed. + * A null status is considered to be successful. + * + * @param status request status + */ + public void setStatus(IStatus status); + + /** + * Returns the status of this request, or null. + * + * @return request status or null + */ + public IStatus getStatus(); + + /** + * Returns the model element corresponding to the given tree path. + * Returns the root element for the empty path. + * + * @param path viewer tree path + * @return corresponding model element + */ + public Object getElement(TreePath path); +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeChangeEvent.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeChangeEvent.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable.tree; + +import java.util.EventObject; + +/** + * @since 3.3 + * + */ +public class TreeChangeEvent : EventObject { + + /** + * + */ + private static final long serialVersionUID = -3198503763995528027L; + /** + * + */ + public TreeDiff diff; + + /** + * @param source + * @param diff + */ + public this(IObservableTree source, TreeDiff diff) { + super(source); + this.diff = diff; + } + + /** + * @return the observable tree from which this event originated + */ + public IObservableTree getObservable() { + return cast(IObservableTree) getSource(); + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeDiff.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeDiff.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable.tree; + +/** + * Describes the difference between two trees as a tree of tree diff nodes. + * + * @since 1.1 + * + */ +public abstract class TreeDiff : TreeDiffNode { + + /** + * Returns the tree path (possibly empty) of the parent, or + * null if the underlying tree is not lazy and never contains + * duplicate elements. + * + * @return the tree path (possibly empty) of the unchanged parent, or + * null + */ + public abstract TreePath getParentPath(); + + /** + * @param visitor + */ + public void accept(TreeDiffVisitor visitor) { + doAccept(visitor, getParentPath()); + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeDiffNode.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeDiffNode.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable.tree; + +/** + * @since 1.1 + * + */ +public abstract class TreeDiffNode { + + /** + * + */ + public final static int NO_CHANGE = 0x00; + + /** + * + */ + public final static int ADDED = 0x01; + + /** + * + */ + public final static int REMOVED = 0x02; + + /** + * + */ + public final static int REPLACED = 0x03; + + /** + * + */ + public static final TreeDiffNode[] NO_CHILDREN = new TreeDiffNode[0]; + + /** + * + */ + public static final int INDEX_UNKNOWN = -1; + + /** + * @return the change type + */ + public abstract int getChangeType(); + + /** + * @return the element that was removed, or the replaced element + */ + public abstract Object getOldElement(); + + /** + * @return the element that was not changed, added, or the replacement + * element + */ + public abstract Object getNewElement(); + + /** + * @return the index at which the element was added, removed, or replaced + */ + public abstract int getIndex(); + + /** + * Returns the child tree diff objects that describe changes to children. If + * the change type is REMOVED, there will be no children. + * + * @return the nodes representing changes to children + */ + public abstract TreeDiffNode[] getChildren(); + + protected void doAccept(TreeDiffVisitor visitor, TreePath parentPath) { + TreePath currentPath = parentPath.createChildPath(getNewElement()); + bool recurse = visitor.visit(this, currentPath); + if (recurse) { + TreeDiffNode[] children = getChildren(); + for (int i = 0; i < children.length; i++) { + TreeDiffNode child = children[i]; + child.doAccept(visitor, currentPath); + } + } + } + +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeDiffVisitor.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreeDiffVisitor.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2006, 2007 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.core.internal.databinding.observable.tree; + +/** + * @since 3.3 + * + */ +public abstract class TreeDiffVisitor { + + /** + * Visits the given tree diff. + * + * @param diff + * the diff to visit + * @param currentPath + * the current path (the diff's element is the last segment of + * the path) + * + * @return true if the tree diff's children should be + * visited; false if they should be skipped. + */ + public abstract bool visit(TreeDiffNode diff, TreePath currentPath); +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreePath.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/TreePath.d Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,183 @@ +/******************************************************************************* + * Copyright (c) 2005, 2008 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Matthew Hall - bug 118516 + *******************************************************************************/ +package org.eclipse.core.internal.databinding.observable.tree; + +import org.eclipse.core.runtime.Assert; + +/** + * A tree path denotes a model element in a tree viewer. Tree path objects have + * value semantics. A model element is represented by a path of elements in the + * tree from the root element to the leaf element. + *

+ * Clients may instantiate this class. Not intended to be subclassed. + *

+ * + * @since 3.2 + */ +public final class TreePath { + + /** + * Constant for representing an empty tree path. + */ + public static final TreePath EMPTY = new TreePath(new Object[0]); + + private Object[] segments; + + private int hash; + + /** + * Constructs a path identifying a leaf node in a tree. + * + * @param segments + * path of elements to a leaf node in a tree, starting with the + * root element + */ + public this(Object[] segments) { + Assert.isNotNull(segments, "Segments array cannot be null"); //$NON-NLS-1$ + this.segments = new Object[segments.length]; + for (int i = 0; i < segments.length; i++) { + Assert.isNotNull(segments[i], "Segments array cannot contain null"); //$NON-NLS-1$ + this.segments[i] = segments[i]; + } + } + + /** + * Returns the element at the specified index in this path. + * + * @param index + * index of element to return + * @return element at the specified index + */ + public Object getSegment(int index) { + return segments[index]; + } + + /** + * Returns the number of elements in this path. + * + * @return the number of elements in this path + */ + public int getSegmentCount() { + return segments.length; + } + + /** + * Returns the first element in this path. + * + * @return the first element in this path + */ + public Object getFirstSegment() { + if (segments.length is 0) { + return null; + } + return segments[0]; + } + + /** + * Returns the last element in this path. + * + * @return the last element in this path + */ + public Object getLastSegment() { + if (segments.length is 0) { + return null; + } + return segments[segments.length - 1]; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public override equals_t opEquals(Object other) { + if (!( null !is cast(TreePath)other )) { + return false; + } + TreePath otherPath = cast(TreePath) other; + if (segments.length !is otherPath.segments.length) { + return false; + } + for (int i = 0; i < segments.length; i++) { + if (!segments[i].equals(otherPath.segments[i])) { + return false; + } + } + return true; + } + + public override hash_t toHash() { + if (hash is 0) { + for (int i = 0; i < segments.length; i++) { + hash += segments[i].hashCode(); + } + } + return hash; + } + + /** + * Returns whether this path starts with the same segments as the given + * path, using the given comparer to compare segments. + * + * @param treePath + * path to compare to + * @return whether the given path is a prefix of this path, or the same as + * this path + */ + public bool startsWith(TreePath treePath) { + int thisSegmentCount = getSegmentCount(); + int otherSegmentCount = treePath.getSegmentCount(); + if (otherSegmentCount is thisSegmentCount) { + return equals(treePath); + } + if (otherSegmentCount > thisSegmentCount) { + return false; + } + for (int i = 0; i < otherSegmentCount; i++) { + Object otherSegment = treePath.getSegment(i); + if (!otherSegment.equals(segments[i])) { + return false; + } + } + return true; + } + + /** + * Returns a copy of this tree path with one segment removed from the end, + * or null if this tree path has no segments. + * @return a tree path + */ + public TreePath getParentPath() { + int segmentCount = getSegmentCount(); + if (segmentCount <= 1) { + return null; + } + Object[] parentSegments = new Object[segmentCount - 1]; + System.arraycopy(segments, 0, parentSegments, 0, segmentCount - 1); + return new TreePath(parentSegments); + } + + /** + * Returns a copy of this tree path with the given segment added at the end. + * @param newSegment + * @return a tree path + */ + public TreePath createChildPath(Object newSegment) { + int segmentCount = getSegmentCount(); + Object[] childSegments = new Object[segmentCount + 1]; + if(segmentCount>0) { + System.arraycopy(segments, 0, childSegments, 0, segmentCount); + } + childSegments[segmentCount] = newSegment; + return new TreePath(childSegments); + } +} diff -r 1d37a7813832 -r 6208d4f6a277 org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/package.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/org.eclipse.core.databinding.observable/src/org/eclipse/core/internal/databinding/observable/tree/package.html Tue Apr 21 10:55:51 2009 +0200 @@ -0,0 +1,16 @@ + + + + + + + Package-level Javadoc + + +To be written. +

+Package Specification

+

+This package provides classes that can be used to ...

+ + diff -r 1d37a7813832 -r 6208d4f6a277 rakefile --- a/rakefile Sun Apr 19 17:37:36 2009 +0200 +++ b/rakefile Tue Apr 21 10:55:51 2009 +0200 @@ -78,6 +78,8 @@ LIBNAMES_CORE = [ "org.eclipse.core.runtime", "org.eclipse.core.commands", "org.eclipse.core.databinding", + "org.eclipse.core.databinding.beans", + "org.eclipse.core.databinding.observable", "org.eclipse.core.jobs" ] LIBNAMES_JFACE = [ "org.eclipse.jface" ] @@ -323,6 +325,8 @@ buildTree( "org.eclipse.core.runtime", "src", "res" ) buildTree( "org.eclipse.core.commands", "src", "res" ) buildTree( "org.eclipse.core.databinding", "src", "res" ) + buildTree( "org.eclipse.core.databinding.beans", "src", "res" ) + buildTree( "org.eclipse.core.databinding.observable", "src", "res" ) buildTree( "org.eclipse.core.jobs", "src", "res" ) end