Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian W. Damus2014-05-23 02:44:04 +0000
committerCamille Letavernier2014-08-07 09:17:38 +0000
commitec9e7064748f00f1cbaea710103064b1738356b8 (patch)
treee14655f44e32bb9127a06e1ea2618297d9bed062 /plugins/infra/org.eclipse.papyrus.infra.tools
parentb0896efe2e99b9eb52b80d5caea3bf3693c9d661 (diff)
downloadorg.eclipse.papyrus-ec9e7064748f00f1cbaea710103064b1738356b8.tar.gz
org.eclipse.papyrus-ec9e7064748f00f1cbaea710103064b1738356b8.tar.xz
org.eclipse.papyrus-ec9e7064748f00f1cbaea710103064b1738356b8.zip
417409: [Performances - Properties view] Delay in UI when reorganizing diagram layout.
https://bugs.eclipse.org/bugs/show_bug.cgi?id=417409 Make property sheet views reusable, with updating of the bound selection when the selection changes to another element that shows the same views. This employs new capability of the DataSource to update the selection that it encapsulates, pushing the new selection into the ModelElements that it creates, using a new delegating observable framework. Property sheet controls are re-used on a per-tab basis. Because of the new delegation pattern introduced here, we need to be able to ensure that delegate observables are disposed of when they are no longer needed. This includes not only the delegates of the new DelegatingObservables, but also the delegates of MultipleObservableValue and similar aggregates. As these delegates can be shared amongst multiple wrappers of different kinds, we use a simple reference counting scheme to ensure that observables are not disposed while they are still in use. This averts the exceptions discovered in multi-observable (multiple selection) scenarios on a previous iteration of this patch set. Change-Id: Ide8f3fcea4228083a68bc9d5d39dc5a50217af62
Diffstat (limited to 'plugins/infra/org.eclipse.papyrus.infra.tools')
-rw-r--r--plugins/infra/org.eclipse.papyrus.infra.tools/META-INF/MANIFEST.MF3
-rw-r--r--plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingInvocationHandler.java174
-rw-r--r--plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservable.java348
-rw-r--r--plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableCollection.java131
-rw-r--r--plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableList.java205
-rw-r--r--plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableSet.java128
-rw-r--r--plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableValue.java140
-rw-r--r--plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/IDelegatingObservable.java53
-rw-r--r--plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/MultipleObservableValue.java14
-rw-r--r--plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/ReferenceCountedObservable.java372
10 files changed, 1563 insertions, 5 deletions
diff --git a/plugins/infra/org.eclipse.papyrus.infra.tools/META-INF/MANIFEST.MF b/plugins/infra/org.eclipse.papyrus.infra.tools/META-INF/MANIFEST.MF
index 69b756e3f75..b2b0b9839a1 100644
--- a/plugins/infra/org.eclipse.papyrus.infra.tools/META-INF/MANIFEST.MF
+++ b/plugins/infra/org.eclipse.papyrus.infra.tools/META-INF/MANIFEST.MF
@@ -13,7 +13,8 @@ Require-Bundle: org.eclipse.ui,
org.eclipse.emf.ecore;bundle-version="2.9.0",
org.eclipse.emf.ecore.xmi;bundle-version="2.9.0",
org.eclipse.emf.edit;bundle-version="2.9.0",
- org.eclipse.core.expressions;bundle-version="3.4.500"
+ org.eclipse.core.expressions;bundle-version="3.4.500",
+ com.google.guava;bundle-version="11.0.0"
Bundle-Vendor: %Bundle-Vendor
Bundle-ActivationPolicy: lazy
Bundle-Version: 1.0.0.qualifier
diff --git a/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingInvocationHandler.java b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingInvocationHandler.java
new file mode 100644
index 00000000000..6dd91f9e37b
--- /dev/null
+++ b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingInvocationHandler.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2014 CEA and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Christian W. Damus (CEA) - Initial API and implementation
+ *
+ */
+package org.eclipse.papyrus.infra.tools.databinding;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.IObservable;
+
+
+/**
+ * An invocation handler for dynamic proxies that wrap {@link DelegatingObservable}s to implement other interfaces of those delegators' delegates,
+ * such as the papyrus {@code ICommitListener} interface from the Widgets API.
+ */
+class DelegatingInvocationHandler implements InvocationHandler {
+
+ private final IDelegatingObservable delegator;
+
+ private final Class<? extends IObservable> delegatedInterface;
+
+ private DelegatingInvocationHandler(IDelegatingObservable delegator, Class<? extends IObservable> delegatedInterface) {
+ super();
+
+ this.delegator = delegator;
+ this.delegatedInterface = delegatedInterface;
+ }
+
+ public static <T extends IObservable> T wrap(IDelegatingObservable delegator, Class<T> delegatedInterface) {
+ T result;
+
+ List<Class<?>> mixins = null;
+
+ IObservable delegate = delegator.getDelegate();
+
+ for(Class<?> next : allInterfaces(delegate.getClass())) {
+ // Already have the core observable interfaces covered
+ if(!next.isAssignableFrom(delegatedInterface)) {
+ if(mixins == null) {
+ mixins = new ArrayList<Class<?>>(1);
+ }
+ mixins.add(next);
+ }
+ }
+
+ if(mixins == null) {
+ result = delegatedInterface.cast(delegator);
+ } else {
+ // This class loader is sure to be able to see all of the interfaces implemented by the delegate.
+ // But the question is, can it see the IDelegatingObservable interface?
+ ClassLoader loader = delegator.getDelegate().getClass().getClassLoader();
+ try {
+ if(loader.loadClass(IDelegatingObservable.class.getName()) != IDelegatingObservable.class) {
+ // This loader can't see the same class. Use my loader, instead
+ loader = DelegatingInvocationHandler.class.getClassLoader();
+ }
+ } catch (Exception e) {
+ // This loader can't see the class. Use my loader, instead
+ loader = DelegatingInvocationHandler.class.getClassLoader();
+ }
+
+ result = wrap(delegator, delegatedInterface, loader, mixins.toArray(new Class<?>[mixins.size()]));
+ }
+
+ return result;
+ }
+
+ static Set<Class<?>> allInterfaces(Class<?> clazz) {
+ Set<Class<?>> result = new HashSet<Class<?>>();
+ collectAllInterfaces(clazz, result);
+ return result;
+ }
+
+ private static void collectAllInterfaces(Class<?> clazz, Collection<Class<?>> result) {
+ Class<?>[] interfaces = clazz.getInterfaces();
+ for(int i = 0; i < interfaces.length; i++) {
+ // Don't need to collect super-interfaces because they are inherited
+ result.add(interfaces[i]);
+ }
+
+ // Climb the type hierarchy to get interfaces of superclasses (which may be unrelated to direct interfaces)
+ Class<?> zuper = clazz.getSuperclass();
+ if(zuper != null) {
+ collectAllInterfaces(zuper, result);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T extends IObservable> T wrap(IDelegatingObservable delegator, Class<T> delegatedInterface, ClassLoader loader, Class<?>... mixins) {
+ T result;
+
+ if((loader == null) || (mixins.length == 0)) {
+ // Nothing to wrap
+ result = delegatedInterface.cast(delegator);
+ } else {
+ List<Class<?>> interfaces = new ArrayList<Class<?>>(mixins.length + 2);
+ interfaces.add(delegatedInterface);
+ interfaces.add(IDelegatingObservable.class);
+ interfaces.addAll(Arrays.asList(mixins));
+ InvocationHandler handler = new DelegatingInvocationHandler(delegator, delegatedInterface);
+
+ result = delegatedInterface.cast(Proxy.newProxyInstance(loader, interfaces.toArray(new Class<?>[interfaces.size()]), handler));
+ ((DelegatingObservable<T>)delegator).setRealObservable(result);
+ }
+
+ return result;
+ }
+
+ /**
+ * The interesting case of wrapping an observable that is already one of our delegating dynamic proxies.
+ *
+ * @param proxy
+ * a dynamic proxy implementing the {@link IDelegatingObservable} interface
+ *
+ * @return another dynamic proxy of the same class, which delegates to the supplied {@code proxy}
+ *
+ * @throws Exception
+ * on failure to create a new dynamic proxy of the same kind as the delegate {@code proxy}
+ */
+ @SuppressWarnings("unchecked")
+ static <T extends IObservable> T wrapDynamicProxy(T proxy) throws Exception {
+ final DelegatingInvocationHandler proxyHandler = (DelegatingInvocationHandler)Proxy.getInvocationHandler(proxy);
+
+ // Create a new delegator of the appropriate class
+ DelegatingObservable<T> proxyDelegator = (DelegatingObservable<T>)proxyHandler.delegator;
+ DelegatingObservable<T> delegator = proxyDelegator.getClass().getDeclaredConstructor(proxyHandler.delegatedInterface).newInstance(proxy);
+
+ // Create an invocation handler for the same delegated interface as the wrapped proxy
+ DelegatingInvocationHandler handler = new DelegatingInvocationHandler(delegator, proxyHandler.delegatedInterface);
+
+ // And create a new delegating proxy of the same class
+ return (T)proxy.getClass().getDeclaredConstructor(InvocationHandler.class).newInstance(handler);
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ Object result = null;
+
+ Class<?> owner = method.getDeclaringClass();
+
+ try {
+ if((owner == delegatedInterface) || (owner == IDelegatingObservable.class) || (owner == ReferenceCountedObservable.class) || (owner == Object.class) || owner.isAssignableFrom(delegatedInterface)) {
+ // Refer this to our delegate
+ result = method.invoke(delegator, args);
+ } else {
+ // Refer this to the delegator's delegate
+ result = method.invoke(delegator.getDelegate(), args);
+ }
+ } catch (InvocationTargetException e) {
+ // Don't just re-throw this because chances are it's triggered by a run-time exception (doesn't need to
+ // be declared) or by a declared exception. The ITE type is not usually declared in API signatures
+ // (in fact, it really should only be declared by the Method::invoke(...) API!)
+ throw e.getTargetException();
+ }
+
+ return result;
+ }
+}
diff --git a/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservable.java b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservable.java
new file mode 100644
index 00000000000..55393927999
--- /dev/null
+++ b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservable.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (c) 2014 CEA and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Christian W. Damus (CEA) - Initial API and implementation
+ *
+ */
+package org.eclipse.papyrus.infra.tools.databinding;
+
+import java.lang.reflect.Proxy;
+
+import org.eclipse.core.databinding.observable.ChangeEvent;
+import org.eclipse.core.databinding.observable.DisposeEvent;
+import org.eclipse.core.databinding.observable.IChangeListener;
+import org.eclipse.core.databinding.observable.IDisposeListener;
+import org.eclipse.core.databinding.observable.IObservable;
+import org.eclipse.core.databinding.observable.IObserving;
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.ObservableEvent;
+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.databinding.observable.list.IObservableList;
+import org.eclipse.core.databinding.observable.set.IObservableSet;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+
+
+/**
+ * Abstract implementation of the {@link IDelegatingObservable} protocol with factory methods for creation of delegators.
+ *
+ * @see #wrap(IObservable)
+ * @see #create(Realm, Class)
+ * @see #create(Realm, Class, ClassLoader, Class...)
+ */
+public abstract class DelegatingObservable<T extends IObservable> extends ReferenceCountedObservable.Abstract implements IDelegatingObservable {
+
+ private final Class<T> delegateType;
+
+ private T delegate;
+
+ @SuppressWarnings("unchecked")
+ private T realObservable = (T)this;
+
+ private IChangeListener forwardingChangeListener;
+
+ private IStaleListener forwardingStaleListener;
+
+ private IDisposeListener delegateDisposeListener;
+
+ DelegatingObservable(T delegate, Class<T> delegateType) {
+ super(delegate.getRealm());
+
+ this.delegateType = delegateType;
+
+ setDelegate(delegate);
+ }
+
+ DelegatingObservable(Realm realm, Class<T> delegateType) {
+ super(realm);
+
+ this.delegateType = delegateType;
+ }
+
+ /**
+ * Wraps an {@code observable} in a delegator, returning an {@link IDelegatingObservable} of the appropriate kind.
+ *
+ * @param observable
+ * an observable to wrap in a delegator
+ * @return the delegator, which will be an instance of the {@link IDelegatingObservable} interface
+ *
+ * @throws IllegalArgumentException
+ * if the {@code observable} is of a kind for which no delegator is currently implemented
+ */
+ public static IObservable wrap(IObservable observable) {
+ IObservable result;
+
+ if(Proxy.isProxyClass(observable.getClass()) && (Proxy.getInvocationHandler(observable) instanceof DelegatingInvocationHandler)) {
+ // Already have a delegator and it's a dynamic proxy. Just create another like it
+ try {
+ result = DelegatingInvocationHandler.wrapDynamicProxy(observable);
+ } catch (Exception e) {
+ // Seems unlikely as I must have created the observable in the first place
+ throw new IllegalArgumentException("observable is an invalid implementation of IDelegatingObservable", e); //$NON-NLS-1$
+ }
+ } else if(observable instanceof IObservableList) {
+ result = DelegatingObservableList.wrap((IObservableList)observable);
+ } else if(observable instanceof IObservableSet) {
+ result = DelegatingObservableSet.wrap((IObservableSet)observable);
+ } else if(observable instanceof IObservableValue) {
+ result = DelegatingObservableValue.wrap((IObservableValue)observable);
+ } else {
+ throw new IllegalArgumentException("no delegating wrapper implementation available for observable"); //$NON-NLS-1$
+ }
+
+ return result;
+ }
+
+ /**
+ * Creates a new empty delegator suitable for observables of the specified type without any other mix-in interfaces such as {@link IObserving}.
+ * Observable types must be specified by their abstract interface, and currently the following types are supported:
+ * <ul>
+ * <li>{@link IObservableValue}</li>
+ * <li>{@link IObservableList}</li>
+ * <li>{@link IObservableSet}</li>
+ * </ul>
+ *
+ * @param observableType
+ * the kind of observable that will be the new delegator's delegate
+ * @return the delegator, which will be an instance of the {@link IDelegatingObservable} interface
+ *
+ * @throws IllegalArgumentException
+ * if the {@code observable} is of a kind for which no delegator is currently implemented
+ *
+ * @see #create(Realm, Class, ClassLoader, Class...)
+ */
+ public static <T extends IObservable> T create(Realm realm, Class<T> observableType) {
+ return create(realm, observableType, null); // Class loader not needed without any mix-ins
+ }
+
+ /**
+ * Creates a new empty delegator suitable for observables of the specified type with optional mix-in interfaces such as {@link IObserving},
+ * which is implemented by detail observables in a master/detail relationship. Observable types must be specified
+ * by their abstract interface, and currently the following types are supported:
+ * <ul>
+ * <li>{@link IObservableValue}</li>
+ * <li>{@link IObservableList}</li>
+ * <li>{@link IObservableSet}</li>
+ * </ul>
+ *
+ * @param observableType
+ * the kind of observable that will be the new delegator's delegate
+ * @param loader
+ * a class loader that can see all of the {@code mixins}, if any
+ * @param mixins
+ * optional mix-in interfaces that the resulting observable should refer to its delegate. These must all have
+ * {@linkplain #registerMixinHandler handlers already registered}
+ * @return the delegator, which will be an instance of the {@link IDelegatingObservable} interface
+ *
+ * @throws IllegalArgumentException
+ * if the {@code observable} is of a kind for which no delegator is currently implemented
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends IObservable> T create(Realm realm, Class<T> observableType, ClassLoader loader, Class<?>... mixins) {
+ if(observableType == IObservableList.class) {
+ return (T)DelegatingObservableList.create(realm, loader, mixins);
+ } else if(observableType == IObservableSet.class) {
+ return (T)DelegatingObservableSet.create(realm, loader, mixins);
+ } else if(observableType == IObservableValue.class) {
+ return (T)DelegatingObservableValue.create(realm, loader, mixins);
+ } else {
+ throw new IllegalArgumentException("observableType"); //$NON-NLS-1$
+ }
+ }
+
+ public final void setDelegate(final IObservable delegate) {
+ if(isDisposed()) {
+ throw new IllegalStateException("disposed"); //$NON-NLS-1$
+ }
+
+ final T oldDelegate = this.delegate;
+
+ if(delegate != oldDelegate) {
+ final T newDelegate = (delegate == null) ? null : delegateType.cast(delegate);
+
+ if(oldDelegate != null) {
+ unhookDelegate(oldDelegate);
+
+ // Release it only after this iteration of the event loop so that UI refreshes can still access it for now
+ // in case its retain count will go to zero and it will be disposed
+ ReferenceCountedObservable.Util.autorelease(oldDelegate);
+ }
+
+ this.delegate = newDelegate;
+
+ if(newDelegate != null) {
+ ReferenceCountedObservable.Util.retain(newDelegate);
+ hookDelegate(newDelegate);
+ }
+
+ delegateChanged(oldDelegate, newDelegate);
+ }
+ }
+
+ final void clearDelegate() {
+ // Can do this even if disposed
+
+ if(delegate != null) {
+ unhookDelegate(delegate);
+
+ delegate = null;
+
+ // Let listeners know that we've changed. We cannot fire an accurate value change event
+ // because we can no longer access the old delegate's value, as it is now disposed
+ fireChange();
+ }
+ }
+
+ public final T getDelegate() {
+ return delegate;
+ }
+
+ /**
+ * Notifies of a change from one delegate to another. Subclasses overriding this must call {@code super}.
+ *
+ * @param oldDelegate
+ * the previous delegate, or {@code null} if there was none
+ * @param newDelegate
+ * the new delegate, or {@code null} if now I have none
+ */
+ protected void delegateChanged(T oldDelegate, T newDelegate) {
+ fireChange();
+ }
+
+ protected void hookDelegate(T delegate) {
+ delegate.addChangeListener(getForwardingChangeListener());
+ delegate.addStaleListener(getForwardingStaleListener());
+
+ // Don't forward dispose events because the delegate has its own lifecycle. However, when our delegate
+ // is disposed, we forget about it
+ delegate.addDisposeListener(getDelegateDisposeListener());
+ }
+
+ protected void unhookDelegate(T delegate) {
+ delegate.removeChangeListener(getForwardingChangeListener());
+ delegate.removeStaleListener(getForwardingStaleListener());
+ delegate.removeDisposeListener(getDelegateDisposeListener());
+ }
+
+ public boolean isStale() {
+ getterCalled();
+
+ return (delegate != null) && delegate.isStale();
+ }
+
+ protected void getterCalled() {
+ ObservableTracker.getterCalled(getRealObservable());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return (obj == this) || ((delegate == null) ? false : delegate.equals(obj));
+ }
+
+ @Override
+ public int hashCode() {
+ return (delegate == null) ? 0 : delegate.hashCode();
+ }
+
+ /**
+ * Sets the real observable (which may be a dynamic proxy) to report as the source of events and the target of getter calls in the
+ * {@link ObservableTracker}.
+ *
+ * @param realObservable
+ * my event source
+ */
+ final void setRealObservable(T realObservable) {
+ this.realObservable = realObservable;
+ }
+
+ /**
+ * Gets the source to report for events (which may be a dynamic proxy).
+ *
+ * @return my event source
+ */
+ final T getRealObservable() {
+ return realObservable;
+ }
+
+ @Override
+ protected final void fireChange() {
+ fireEvent(new ChangeEvent(getRealObservable()));
+ }
+
+ @Override
+ protected final void fireStale() {
+ fireEvent(new StaleEvent(getRealObservable()));
+ }
+
+ public void dispose() {
+ if(!isDisposed()) {
+ if(delegate != null) {
+ unhookDelegate(delegate);
+
+ // Release it only after this iteration of the event loop so that UI refreshes can still access it for now
+ // in case its retain count will go to zero and it will be disposed
+ ReferenceCountedObservable.Util.autorelease(delegate);
+ delegate = null;
+ }
+ super.dispose();
+ }
+ }
+
+ @Override
+ protected void fireEvent(ObservableEvent event) {
+ // ensure the correct source for events fired by the superclass
+ if((event instanceof DisposeEvent) && (event.getSource() != getRealObservable())) {
+ event = new DisposeEvent(getRealObservable());
+ }
+
+ super.fireEvent(event);
+ }
+
+ private IChangeListener getForwardingChangeListener() {
+ if(forwardingChangeListener == null) {
+ forwardingChangeListener = new IChangeListener() {
+
+ public void handleChange(ChangeEvent event) {
+ DelegatingObservable.this.fireChange();
+ }
+ };
+ }
+
+ return forwardingChangeListener;
+ }
+
+ private IStaleListener getForwardingStaleListener() {
+ if(forwardingStaleListener == null) {
+ forwardingStaleListener = new IStaleListener() {
+
+ public void handleStale(StaleEvent staleEvent) {
+ DelegatingObservable.this.fireStale();
+ }
+ };
+ }
+
+ return forwardingStaleListener;
+ }
+
+ private IDisposeListener getDelegateDisposeListener() {
+ if(delegateDisposeListener == null) {
+ delegateDisposeListener = new IDisposeListener() {
+
+ public void handleDispose(DisposeEvent event) {
+ if(event.getObservable() == getDelegate()) {
+ clearDelegate();
+ }
+ }
+ };
+ }
+
+ return delegateDisposeListener;
+ }
+}
diff --git a/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableCollection.java b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableCollection.java
new file mode 100644
index 00000000000..f4597404f19
--- /dev/null
+++ b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableCollection.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2014 CEA and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Christian W. Damus (CEA) - Initial API and implementation
+ *
+ */
+package org.eclipse.papyrus.infra.tools.databinding;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.eclipse.core.databinding.observable.IObservableCollection;
+import org.eclipse.core.databinding.observable.Realm;
+
+
+/**
+ * This is the DelegatingObservableCollection type. Enjoy.
+ */
+public abstract class DelegatingObservableCollection<T extends IObservableCollection> extends DelegatingObservable<T> implements IObservableCollection {
+
+ private static final Object[] EMPTY_ARRAY = {};
+
+ DelegatingObservableCollection(T delegate, Class<T> delegateType) {
+ super(delegate, delegateType);
+ }
+
+ DelegatingObservableCollection(Realm realm, Class<T> delegateType) {
+ super(realm, delegateType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ getterCalled();
+
+ return super.equals(obj);
+ }
+
+ @Override
+ public int hashCode() {
+ getterCalled();
+
+ return super.hashCode();
+ }
+
+ public int size() {
+ getterCalled();
+
+ return (getDelegate() == null) ? 0 : getDelegate().size();
+ }
+
+ public boolean isEmpty() {
+ getterCalled();
+
+ return (getDelegate() == null) ? true : getDelegate().isEmpty();
+ }
+
+ public boolean contains(Object o) {
+ getterCalled();
+
+ return (getDelegate() == null) ? false : getDelegate().contains(o);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public Iterator iterator() {
+ getterCalled();
+
+ return (getDelegate() == null) ? Collections.EMPTY_LIST.iterator() : getDelegate().iterator();
+ }
+
+ public Object[] toArray() {
+ getterCalled();
+
+ return (getDelegate() == null) ? EMPTY_ARRAY : getDelegate().toArray();
+ }
+
+ @SuppressWarnings("unchecked")
+ public Object[] toArray(Object[] a) {
+ getterCalled();
+
+ return (getDelegate() == null) ? Collections.EMPTY_LIST.toArray(a) : getDelegate().toArray(a);
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean add(Object e) {
+ return (getDelegate() == null) ? false : getDelegate().add(e);
+ }
+
+ public boolean remove(Object o) {
+ return (getDelegate() == null) ? false : getDelegate().remove(o);
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public boolean containsAll(Collection c) {
+ getterCalled();
+
+ return (getDelegate() == null) ? false : getDelegate().containsAll(c);
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public boolean addAll(Collection c) {
+ return (getDelegate() == null) ? false : getDelegate().addAll(c);
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public boolean removeAll(Collection c) {
+ return (getDelegate() == null) ? false : getDelegate().removeAll(c);
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public boolean retainAll(Collection c) {
+ return (getDelegate() == null) ? false : getDelegate().retainAll(c);
+ }
+
+ public void clear() {
+ if(getDelegate() != null) {
+ getDelegate().clear();
+ }
+ }
+
+ public Object getElementType() {
+ return (getDelegate() == null) ? null : getDelegate().getElementType();
+ }
+
+}
diff --git a/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableList.java b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableList.java
new file mode 100644
index 00000000000..2c7e7807aaa
--- /dev/null
+++ b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableList.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2014 CEA and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Christian W. Damus (CEA) - Initial API and implementation
+ *
+ */
+package org.eclipse.papyrus.infra.tools.databinding;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
+
+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.ListDiff;
+
+
+/**
+ * This is the DelegatingObservableList type. Enjoy.
+ */
+public class DelegatingObservableList extends DelegatingObservableCollection<IObservableList> implements IObservableList {
+
+ private static final Object LIST_EVENT_TYPE = new Object();
+
+ private IListChangeListener forwardingListChangeListener;
+
+ DelegatingObservableList(IObservableList delegate) {
+ super(delegate, IObservableList.class);
+ }
+
+ DelegatingObservableList(Realm realm) {
+ super(realm, IObservableList.class);
+ }
+
+ public static IObservableList wrap(IObservableList observable) {
+ IObservableList result;
+
+ if(observable instanceof IDelegatingObservable) {
+ // Already have a delegator. Just create another like it
+ try {
+ result = (IObservableList)observable.getClass().getDeclaredConstructor(IObservableList.class).newInstance(observable);
+ } catch (Exception e) {
+ // Seems unlikely as I must have created the observable in the first place
+ throw new IllegalArgumentException("observable is an invalid implementation of IDelegatingObservable", e); //$NON-NLS-1$
+ }
+ } else {
+ result = DelegatingInvocationHandler.wrap(new DelegatingObservableList(observable), IObservableList.class);
+ }
+
+ return result;
+ }
+
+ public static IObservableList create(Realm realm, ClassLoader loader, Class<?>... mixins) {
+ return DelegatingInvocationHandler.wrap(new DelegatingObservableList(realm), IObservableList.class, loader, mixins);
+ }
+
+ public void addListChangeListener(IListChangeListener listener) {
+ addListener(LIST_EVENT_TYPE, listener);
+ }
+
+ public void removeListChangeListener(IListChangeListener listener) {
+ removeListener(LIST_EVENT_TYPE, listener);
+ }
+
+ @Override
+ protected void hookDelegate(IObservableList delegate) {
+ super.hookDelegate(delegate);
+ delegate.addListChangeListener(getForwardingListChangeListener());
+ }
+
+ @Override
+ protected void unhookDelegate(IObservableList delegate) {
+ delegate.removeListChangeListener(getForwardingListChangeListener());
+ super.unhookDelegate(delegate);
+ }
+
+ @Override
+ protected void delegateChanged(IObservableList oldDelegate, IObservableList newDelegate) {
+ super.delegateChanged(oldDelegate, newDelegate);
+
+ List<?> oldList = ((oldDelegate == null) || oldDelegate.isDisposed()) ? Collections.EMPTY_LIST : oldDelegate;
+ List<?> newList = (newDelegate == null) ? Collections.EMPTY_LIST : newDelegate;
+
+ fireEvent(new MyListChangeEvent(Diffs.computeListDiff(oldList, newList)));
+ }
+
+ @SuppressWarnings("unchecked")
+ public void add(int index, Object element) {
+ if(getDelegate() == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ getDelegate().add(index, element);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public boolean addAll(int index, Collection c) {
+ if(getDelegate() == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return getDelegate().addAll(index, c);
+ }
+
+ public Object get(int index) {
+ getterCalled();
+
+ if(getDelegate() == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return getDelegate().get(index);
+ }
+
+ public Object set(int index, Object element) {
+ if(getDelegate() == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return getDelegate().set(index, element);
+ }
+
+ public Object move(int oldIndex, int newIndex) {
+ if(getDelegate() == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return getDelegate().move(oldIndex, newIndex);
+ }
+
+ public Object remove(int index) {
+ return (getDelegate() == null) ? Collections.EMPTY_LIST.remove(index) : getDelegate().remove(index);
+ }
+
+ public int indexOf(Object o) {
+ getterCalled();
+
+ return (getDelegate() == null) ? -1 : getDelegate().indexOf(o);
+ }
+
+ public int lastIndexOf(Object o) {
+ getterCalled();
+
+ return (getDelegate() == null) ? -1 : getDelegate().lastIndexOf(o);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public ListIterator listIterator() {
+ getterCalled();
+
+ return (getDelegate() == null) ? Collections.EMPTY_LIST.listIterator() : getDelegate().listIterator();
+ }
+
+ @SuppressWarnings("rawtypes")
+ public ListIterator listIterator(int index) {
+ getterCalled();
+
+ return (getDelegate() == null) ? Collections.EMPTY_LIST.listIterator(index) : getDelegate().listIterator(index);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public List subList(int fromIndex, int toIndex) {
+ getterCalled();
+
+ return (getDelegate() == null) ? Collections.EMPTY_LIST.subList(fromIndex, toIndex) : getDelegate().subList(fromIndex, toIndex);
+ }
+
+ private IListChangeListener getForwardingListChangeListener() {
+ if(forwardingListChangeListener == null) {
+ forwardingListChangeListener = new IListChangeListener() {
+
+ public void handleListChange(ListChangeEvent event) {
+ ListChangeEvent myEvent = new MyListChangeEvent(event.diff);
+ fireEvent(myEvent);
+ }
+ };
+ }
+
+ return forwardingListChangeListener;
+ }
+
+ //
+ // Nested types
+ //
+
+ class MyListChangeEvent extends ListChangeEvent {
+
+ private static final long serialVersionUID = 1L;
+
+ MyListChangeEvent(ListDiff diff) {
+ super(getRealObservable(), diff);
+ }
+
+ @Override
+ protected Object getListenerType() {
+ // We implement our own listener type because the type from the core framework is not accessible
+ return LIST_EVENT_TYPE;
+ }
+ }
+}
diff --git a/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableSet.java b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableSet.java
new file mode 100644
index 00000000000..4111577f027
--- /dev/null
+++ b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableSet.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2014 CEA and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Christian W. Damus (CEA) - Initial API and implementation
+ *
+ */
+package org.eclipse.papyrus.infra.tools.databinding;
+
+import java.util.Collections;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.Diffs;
+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.databinding.observable.set.SetChangeEvent;
+import org.eclipse.core.databinding.observable.set.SetDiff;
+
+
+/**
+ * This is the DelegatingObservableSet type. Enjoy.
+ */
+public class DelegatingObservableSet extends DelegatingObservableCollection<IObservableSet> implements IObservableSet {
+
+ private static final Object SET_EVENT_TYPE = new Object();
+
+ private ISetChangeListener forwardingSetChangeListener;
+
+ DelegatingObservableSet(IObservableSet delegate) {
+ super(delegate, IObservableSet.class);
+ }
+
+ DelegatingObservableSet(Realm realm) {
+ super(realm, IObservableSet.class);
+ }
+
+ public static IObservableSet wrap(IObservableSet observable) {
+ IObservableSet result;
+
+ if(observable instanceof IDelegatingObservable) {
+ // Already have a delegator. Just create another like it
+ try {
+ result = (IObservableSet)observable.getClass().getDeclaredConstructor(IObservableSet.class).newInstance(observable);
+ } catch (Exception e) {
+ // Seems unlikely as I must have created the observable in the first place
+ throw new IllegalArgumentException("observable is an invalid implementation of IDelegatingObservable", e); //$NON-NLS-1$
+ }
+ } else {
+ result = DelegatingInvocationHandler.wrap(new DelegatingObservableSet(observable), IObservableSet.class);
+ }
+
+ return result;
+ }
+
+ public static IObservableSet create(Realm realm, ClassLoader loader, Class<?>... mixins) {
+ return DelegatingInvocationHandler.wrap(new DelegatingObservableSet(realm), IObservableSet.class, loader, mixins);
+ }
+
+ public void addSetChangeListener(ISetChangeListener listener) {
+ addListener(SET_EVENT_TYPE, listener);
+ }
+
+ public void removeSetChangeListener(ISetChangeListener listener) {
+ removeListener(SET_EVENT_TYPE, listener);
+ }
+
+ @Override
+ protected void hookDelegate(IObservableSet delegate) {
+ super.hookDelegate(delegate);
+ delegate.addSetChangeListener(getForwardingSetChangeListener());
+ }
+
+ @Override
+ protected void unhookDelegate(IObservableSet delegate) {
+ delegate.removeSetChangeListener(getForwardingSetChangeListener());
+ super.unhookDelegate(delegate);
+ }
+
+ @Override
+ protected void delegateChanged(IObservableSet oldDelegate, IObservableSet newDelegate) {
+ super.delegateChanged(oldDelegate, newDelegate);
+
+ Set<?> oldSet = ((oldDelegate == null) || oldDelegate.isDisposed()) ? Collections.EMPTY_SET : oldDelegate;
+ Set<?> newSet = (newDelegate == null) ? Collections.EMPTY_SET : newDelegate;
+
+ fireEvent(new MySetChangeEvent(Diffs.computeSetDiff(oldSet, newSet)));
+ }
+
+ private ISetChangeListener getForwardingSetChangeListener() {
+ if(forwardingSetChangeListener == null) {
+ forwardingSetChangeListener = new ISetChangeListener() {
+
+ public void handleSetChange(SetChangeEvent event) {
+ SetChangeEvent myEvent = new MySetChangeEvent(event.diff);
+ fireEvent(myEvent);
+ }
+ };
+ }
+
+ return forwardingSetChangeListener;
+ }
+
+ //
+ // Nested types
+ //
+
+ class MySetChangeEvent extends SetChangeEvent {
+
+ private static final long serialVersionUID = 1L;
+
+ MySetChangeEvent(SetDiff diff) {
+ super(getRealObservable(), diff);
+ }
+
+ @Override
+ protected Object getListenerType() {
+ // We implement our own listener type because the type from the core framework is not accessible
+ return SET_EVENT_TYPE;
+ }
+ }
+
+}
diff --git a/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableValue.java b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableValue.java
new file mode 100644
index 00000000000..6c75786a1aa
--- /dev/null
+++ b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableValue.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2014 CEA and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Christian W. Damus (CEA) - Initial API and implementation
+ *
+ */
+package org.eclipse.papyrus.infra.tools.databinding;
+
+import org.eclipse.core.databinding.observable.Diffs;
+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.ValueChangeEvent;
+import org.eclipse.core.databinding.observable.value.ValueDiff;
+
+
+/**
+ * This is the DelegatingObservableValue type. Enjoy.
+ */
+public class DelegatingObservableValue extends DelegatingObservable<IObservableValue> implements IObservableValue {
+
+ private static final Object VALUE_EVENT_TYPE = new Object();
+
+ private IValueChangeListener forwardingValueChangeListener;
+
+ DelegatingObservableValue(IObservableValue delegate) {
+ super(delegate, IObservableValue.class);
+ }
+
+ DelegatingObservableValue(Realm realm) {
+ super(realm, IObservableValue.class);
+ }
+
+ public static IObservableValue wrap(IObservableValue observable) {
+ IObservableValue result;
+
+ if(observable instanceof IDelegatingObservable) {
+ // Already have a delegator. Just create another like it
+ try {
+ result = (IObservableValue)observable.getClass().getDeclaredConstructor(IObservableValue.class).newInstance(observable);
+ } catch (Exception e) {
+ // Seems unlikely as I must have created the observable in the first place
+ throw new IllegalArgumentException("observable is an invalid implementation of IDelegatingObservable", e); //$NON-NLS-1$
+ }
+ } else {
+ result = DelegatingInvocationHandler.wrap(new DelegatingObservableValue(observable), IObservableValue.class);
+ }
+
+ return result;
+ }
+
+ public static IObservableValue create(Realm realm, ClassLoader loader, Class<?>... mixins) {
+ return DelegatingInvocationHandler.wrap(new DelegatingObservableValue(realm), IObservableValue.class, loader, mixins);
+ }
+
+ public void addValueChangeListener(IValueChangeListener listener) {
+ addListener(VALUE_EVENT_TYPE, listener);
+ }
+
+ public void removeValueChangeListener(IValueChangeListener listener) {
+ removeListener(VALUE_EVENT_TYPE, listener);
+ }
+
+ @Override
+ protected void hookDelegate(IObservableValue delegate) {
+ super.hookDelegate(delegate);
+ delegate.addValueChangeListener(getForwardingValueChangeListener());
+ }
+
+ @Override
+ protected void unhookDelegate(IObservableValue delegate) {
+ delegate.removeValueChangeListener(getForwardingValueChangeListener());
+ super.unhookDelegate(delegate);
+ }
+
+ @Override
+ protected void delegateChanged(IObservableValue oldDelegate, IObservableValue newDelegate) {
+ super.delegateChanged(oldDelegate, newDelegate);
+
+ Object oldValue = ((oldDelegate == null) || oldDelegate.isDisposed()) ? null : oldDelegate.getValue();
+ Object newValue = (newDelegate == null) ? null : newDelegate.getValue();
+
+ fireEvent(new MyValueChangeEvent(Diffs.createValueDiff(oldValue, newValue)));
+ }
+
+ public Object getValueType() {
+ return (getDelegate() == null) ? Void.class : getDelegate().getValueType();
+ }
+
+ public Object getValue() {
+ getterCalled();
+
+ return (getDelegate() == null) ? null : getDelegate().getValue();
+ }
+
+ public void setValue(Object value) {
+ if(getDelegate() != null) {
+ getDelegate().setValue(value);
+ }
+ }
+
+ private IValueChangeListener getForwardingValueChangeListener() {
+ if(forwardingValueChangeListener == null) {
+ forwardingValueChangeListener = new IValueChangeListener() {
+
+ public void handleValueChange(ValueChangeEvent event) {
+ ValueChangeEvent myEvent = new MyValueChangeEvent(event.diff);
+ fireEvent(myEvent);
+ }
+ };
+ }
+
+ return forwardingValueChangeListener;
+ }
+
+ //
+ // Nested types
+ //
+
+ class MyValueChangeEvent extends ValueChangeEvent {
+
+ private static final long serialVersionUID = 1L;
+
+ MyValueChangeEvent(ValueDiff diff) {
+ super(getRealObservable(), diff);
+ }
+
+ @Override
+ protected Object getListenerType() {
+ // We implement our own listener type because the type from the core framework is not accessible
+ return VALUE_EVENT_TYPE;
+ }
+ }
+}
diff --git a/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/IDelegatingObservable.java b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/IDelegatingObservable.java
new file mode 100644
index 00000000000..2fa0e07bc86
--- /dev/null
+++ b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/IDelegatingObservable.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2014 CEA and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Christian W. Damus (CEA) - Initial API and implementation
+ *
+ */
+package org.eclipse.papyrus.infra.tools.databinding;
+
+import org.eclipse.core.databinding.observable.IObservable;
+
+
+
+/**
+ * <p>
+ * An {@linkplain IObservable observable} that delegates its function (including notification of state changes) to a wrapped instance. The delegate
+ * may be {@linkplain #setDelegate(IObservable) replaced} at any time. Thus, the lifecycle of a delegator is independent of its delegate and
+ * {@link #dispose() disposing} a delegator only disposes it, not its delegate if it has one at the time.
+ * </p>
+ * <p>
+ * Delegating observables may be created via factory methods provided by the {@link DelegatingObservable} class.
+ * </p>
+ *
+ * @see DelegatingObservable
+ */
+public interface IDelegatingObservable extends IObservable, ReferenceCountedObservable {
+
+ /**
+ * Assigns me a new delegate, or at least forgets my delegate if {@code null}.
+ *
+ * @param delegate
+ * my delegate (may be {@code null})
+ */
+ void setDelegate(IObservable delegate);
+
+ /**
+ * Obtains my current delegate, if any.
+ *
+ * @return my delegate, or {@code null} if I have none
+ */
+ IObservable getDelegate();
+
+ /**
+ * Disposes of me and my own resources only. In particular, if I have a {@linkplain #getDelegate() delegate}, it is <em>not</em> disposed, but
+ * must be disposed independently.
+ */
+ void dispose();
+}
diff --git a/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/MultipleObservableValue.java b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/MultipleObservableValue.java
index bd8dadf1e11..a326d80dab1 100644
--- a/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/MultipleObservableValue.java
+++ b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/MultipleObservableValue.java
@@ -1,5 +1,5 @@
/*****************************************************************************
- * Copyright (c) 2010 CEA LIST.
+ * Copyright (c) 2010, 2014 CEA LIST and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
@@ -8,6 +8,8 @@
*
* Contributors:
* Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation
+ * Christian W. Damus (CEA) - bug 417409
+ *
*****************************************************************************/
package org.eclipse.papyrus.infra.tools.databinding;
@@ -18,7 +20,6 @@ import java.util.List;
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.value.AbstractObservableValue;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.ValueDiff;
@@ -32,7 +33,7 @@ import org.eclipse.core.databinding.observable.value.ValueDiff;
* All sub-elements will be edited at the same time, with the same value.
*/
//TODO : Add listeners on sub-observables, and remove them on dispose
-public class MultipleObservableValue extends AbstractObservableValue implements AggregatedObservable, IChangeListener {
+public class MultipleObservableValue extends ReferenceCountedObservable.Value implements AggregatedObservable, IChangeListener {
/**
*
@@ -98,6 +99,7 @@ public class MultipleObservableValue extends AbstractObservableValue implements
public AggregatedObservable aggregate(IObservable observable) {
if(observable instanceof IObservableValue) {
+ ReferenceCountedObservable.Util.retain(observable);
observableValues.add((IObservableValue)observable);
observable.addChangeListener(this);
return this;
@@ -128,8 +130,12 @@ public class MultipleObservableValue extends AbstractObservableValue implements
super.dispose();
for(IObservableValue observable : observableValues) {
observable.removeChangeListener(this);
- observable.dispose();
+
+ // I don't own my observables, so I just release them
+ ReferenceCountedObservable.Util.release(observable);
}
+
+ observableValues.clear();
}
/**
diff --git a/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/ReferenceCountedObservable.java b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/ReferenceCountedObservable.java
new file mode 100644
index 00000000000..41a9ebf5ee9
--- /dev/null
+++ b/plugins/infra/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/ReferenceCountedObservable.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 2014 CEA and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Christian W. Damus (CEA) - Initial API and implementation
+ *
+ */
+package org.eclipse.papyrus.infra.tools.databinding;
+
+import java.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.eclipse.core.databinding.observable.AbstractObservable;
+import org.eclipse.core.databinding.observable.DisposeEvent;
+import org.eclipse.core.databinding.observable.IDisposeListener;
+import org.eclipse.core.databinding.observable.IObservable;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.value.AbstractObservableValue;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.MapMaker;
+
+
+
+/**
+ * A mix-in interface for {@link IObservable}s that support reference counting as a means to automatically dispose of them while ensuring that
+ * they may be freely shared without any client finding itself interacting with a disposed observable.
+ */
+public interface ReferenceCountedObservable extends IObservable {
+
+ /**
+ * Retains me for use by the caller, which should be sure to {@linkplain #release() release me} when I am no longer needed.
+ * As long as I have been retained by at least one client, I shall not {@linkplain IObservable#dispose() dispose} myself.
+ *
+ * @see #release()
+ */
+ void retain();
+
+ /**
+ * Releases me, indicating that I am no longer being used by the caller. When my {@linkplain #retain() retain count} reaches zero,
+ * I automatically {@linkplain IObservable#dispose() dispose} myself, because no client needs me.
+ *
+ * @see #retain()
+ * @see #autorelease()
+ * @see IObservable#dispose()
+ */
+ void release();
+
+ /**
+ * Automatically releases me some time after the current iteration of the UI event loop. This is useful when it is
+ * expected that the UI will still need to be able to access the observable for refreshes while processing the
+ * current event.
+ *
+ * @see #release()
+ */
+ void autorelease();
+
+ /**
+ * A composable assistant to implement the {@link ReferenceCountedObservable} protocol by delegating of its API.
+ */
+ class Support {
+
+ private final IObservable observable;
+
+ private final AtomicInteger refCount = new AtomicInteger();
+
+ /**
+ * Creates a new instance to support reference counting on behalf of an {@code observable}.
+ *
+ * @param observable
+ * the observable for which to provide reference counting
+ */
+ public Support(IObservable observable) {
+ this.observable = observable;
+ }
+
+ public void retain() {
+ refCount.incrementAndGet();
+ }
+
+ public void release() {
+ if((refCount.decrementAndGet() <= 0) && !observable.isDisposed()) {
+ observable.dispose();
+ }
+ }
+
+ public void autorelease() {
+ AutoReleasePool.get(observable.getRealm()).add(observable);
+ }
+ }
+
+ /**
+ * A convenient superclass for reference-counted observables that don't need any other more specific superclass.
+ */
+ abstract class Abstract extends AbstractObservable implements ReferenceCountedObservable {
+
+ private final Support refCount = new Support(this);
+
+ public Abstract(Realm realm) {
+ super(realm);
+ }
+
+ public void retain() {
+ refCount.retain();
+ }
+
+ public void release() {
+ refCount.release();
+ }
+
+ public void autorelease() {
+ refCount.autorelease();
+ }
+ }
+
+ /**
+ * A convenient superclass for reference-counted observable values that don't need any other more specific superclass.
+ */
+ abstract class Value extends AbstractObservableValue implements ReferenceCountedObservable {
+
+ private final Support refCount = new Support(this);
+
+ public Value() {
+ super();
+ }
+
+ public Value(Realm realm) {
+ super(realm);
+ }
+
+ public void retain() {
+ refCount.retain();
+ }
+
+ public void release() {
+ refCount.release();
+ }
+
+ public void autorelease() {
+ refCount.autorelease();
+ }
+ }
+
+ /**
+ * A pool of {@link IObservable}s to be released automatically after the completion of the current iteration
+ * of the UI event loop (or whatever determines the asynchronous execution of tasks in a given {@link Realm}).
+ */
+ final class AutoReleasePool {
+
+ private static final ConcurrentMap<Realm, AutoReleasePool> pools = new MapMaker().concurrencyLevel(1).makeMap();
+
+ private final Realm realm;
+
+ private Collection<IObservable> pool = Lists.newArrayList();
+
+ private AutoReleasePool(Realm realm) {
+ this.realm = realm;
+
+ realm.asyncExec(new ReleaseRunnable());
+ }
+
+ public static AutoReleasePool get(Realm realm) {
+ AutoReleasePool result = pools.get(realm);
+ if(result == null) {
+ result = new AutoReleasePool(realm);
+
+ // Double-check
+ AutoReleasePool oops = pools.putIfAbsent(realm, result);
+ if(oops != null) {
+ result = oops;
+ }
+ }
+
+ return result;
+ }
+
+ public synchronized void add(IObservable observable) {
+ if(pool == null) {
+ pool = Lists.newArrayList();
+ }
+
+ pool.add(observable);
+ }
+
+ public void release() {
+ pools.remove(realm);
+
+ for(;;) {
+ Iterable<IObservable> toDrain;
+
+ synchronized(this) {
+ toDrain = pool;
+ pool = null;
+ }
+
+ if(toDrain != null) {
+ // Drain this pool
+ for(IObservable next : toDrain) {
+ Util.release(next);
+ }
+ } else {
+ // Done. No more pools to drain
+ break;
+ }
+ }
+ }
+
+ private final class ReleaseRunnable implements Runnable {
+
+ public void run() {
+ release();
+ }
+ }
+ }
+
+ /**
+ * Utility APIs for working with reference-counted observables. In particular, this provides external reference-counting
+ * for observables that don't implement it internally via the {@link ReferenceCountedObservable} protocol.
+ */
+ final class Util {
+
+ // Use the Guava weak map because that uses object identity for comparisons, which is critical
+ // to avoid using equals() which will often fail on an assertion violation for accessing a
+ // getter of a disposed observable
+ private static final Map<IObservable, WeakRefCount> adapters = new MapMaker().concurrencyLevel(1).weakKeys().makeMap();
+
+ private Util() {
+ super();
+ }
+
+ /**
+ * Provides a unified interface to retaining observables, delegating to the {@link ReferenceCountedObservable} protocol
+ * for observables that implement it, otherwise providing an external reference-count (which is GC-safe).
+ *
+ * @param observable
+ * an observable to retain
+ *
+ * @return the same {@code observable} (useful for call chaining)
+ *
+ * @see ReferenceCountedObservable#retain()
+ */
+ public static <T extends IObservable> T retain(T observable) {
+ if(observable instanceof ReferenceCountedObservable) {
+ ((ReferenceCountedObservable)observable).retain();
+ } else if(!observable.isDisposed()) { // Don't bother counting if already disposed
+ WeakRefCount adapter = adapt(observable, true);
+ adapter.retain();
+ }
+
+ return observable;
+ }
+
+ /**
+ * Provides a unified interface to releasing observables, delegating to the {@link ReferenceCountedObservable} protocol
+ * for observables that implement it, otherwise providing an external reference-count (which is GC-safe). Note that
+ * for externally reference-counted observables, they are automatically disposed as usual when the retain count drops
+ * to zero, just as though they implemented reference counting internally.
+ *
+ * @param observable
+ * an observable to release. If its retain count is zero as a result (whether intrinsic or extrinsic), it will be disposed
+ *
+ * @return the same {@code observable} (useful for call chaining)
+ *
+ * @see ReferenceCountedObservable#release()
+ */
+ public static <T extends IObservable> T release(T observable) {
+ if(observable instanceof ReferenceCountedObservable) {
+ ((ReferenceCountedObservable)observable).release();
+ } else if(!observable.isDisposed()) { // Don't bother counting if already disposed
+ WeakRefCount adapter = adapt(observable, false);
+
+ // There won't be an adapter if there was no prior retain (of course) or if it was already disposed
+ if(adapter != null) {
+ adapter.release();
+ }
+ }
+
+ return observable;
+ }
+
+ /**
+ * Provides a unified interface to auto-releasing observables, delegating to the {@link ReferenceCountedObservable} protocol
+ * for observables that implement it, otherwise providing an external reference-count (which is GC-safe). Note that
+ * for externally reference-counted observables, they are automatically disposed as usual when the retain count drops
+ * to zero, just as though they implemented reference counting internally.
+ *
+ * @param observable
+ * an observable to release. If its retain count is zero as a result (whether intrinsic or extrinsic), it will be disposed
+ *
+ * @return the same {@code observable} (useful for call chaining)
+ *
+ * @see ReferenceCountedObservable#autorelease()
+ */
+ public static <T extends IObservable> T autorelease(T observable) {
+ if(observable instanceof ReferenceCountedObservable) {
+ ((ReferenceCountedObservable)observable).autorelease();
+ } else if(!observable.isDisposed()) { // Don't bother counting if already disposed
+ WeakRefCount adapter = adapt(observable, false);
+
+ // There won't be an adapter if there was no prior retain (of course) or if it was already disposed
+ if(adapter != null) {
+ adapter.autorelease();
+ }
+ }
+
+ return observable;
+ }
+
+ private static WeakRefCount adapt(IObservable observable, boolean create) {
+ WeakRefCount result = adapters.get(observable);
+
+ if((result == null) && create) {
+ result = new WeakRefCount(observable);
+ adapters.put(observable, result);
+ }
+
+ return result;
+ }
+
+ private static final class WeakRefCount extends WeakReference<IObservable> implements IDisposeListener {
+
+ private final AtomicInteger refCount = new AtomicInteger();
+
+ WeakRefCount(IObservable observable) {
+ super(observable);
+
+ observable.addDisposeListener(this);
+ }
+
+ public void retain() {
+ refCount.incrementAndGet();
+ }
+
+ public void release() {
+ if(refCount.decrementAndGet() <= 0) {
+ IObservable observable = get();
+
+ if(observable != null) {
+ if(!observable.isDisposed()) {
+ observable.dispose();
+ }
+
+ clear();
+ }
+ }
+ }
+
+ public void autorelease() {
+ IObservable observable = get();
+
+ // If it's null, then it's already disposed, so auto-release is meaningless
+ if(observable != null) {
+ AutoReleasePool.get(observable.getRealm()).add(observable);
+ }
+ }
+
+ public void handleDispose(DisposeEvent event) {
+ if(event.getObservable() == get()) {
+ clear();
+ }
+ }
+ }
+ }
+}

Back to the top