diff options
Diffstat (limited to 'plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus')
46 files changed, 5296 insertions, 0 deletions
diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/Activator.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/Activator.java new file mode 100644 index 00000000000..4c598486f70 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/Activator.java @@ -0,0 +1,93 @@ +/***************************************************************************** + * Copyright (c) 2011, 2016 CEA LIST, Christian W. Damus, and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * CEA LIST - Initial API and implementation + * Christian W. Damus = bug 485220 + * + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools; + +import org.eclipse.core.runtime.Plugin; +import org.eclipse.papyrus.infra.core.log.LogHelper; +import org.eclipse.papyrus.infra.tools.spi.INotificationBuilderFactory; +import org.eclipse.papyrus.infra.tools.util.IExecutorService; +import org.osgi.framework.BundleContext; +import org.osgi.util.tracker.ServiceTracker; + +/** + * The activator class controls the plug-in life cycle + */ +public class Activator extends Plugin { + + /** + * The plug-in ID + */ + public static final String PLUGIN_ID = "org.eclipse.papyrus.infra.tools"; //$NON-NLS-1$ + + // The shared instance + private static Activator plugin; + + /** + * The plug-in's logger + */ + public static LogHelper log; + + private DelegatingUIExecutorService uiExecutorService; + + private ServiceTracker<INotificationBuilderFactory, INotificationBuilderFactory> notificationBuilderTracker; + + /** + * The constructor + */ + public Activator() { + } + + @Override + public void start(BundleContext context) throws Exception { + super.start(context); + plugin = this; + log = new LogHelper(this); + + notificationBuilderTracker = new ServiceTracker<>(context, INotificationBuilderFactory.class, null); + notificationBuilderTracker.open(); + } + + @Override + public void stop(BundleContext context) throws Exception { + notificationBuilderTracker.close(); + + if (uiExecutorService != null) { + uiExecutorService.shutdown(context); + uiExecutorService = null; + } + + plugin = null; + super.stop(context); + } + + /** + * Returns the shared instance + * + * @return the shared instance + */ + public static Activator getDefault() { + return plugin; + } + + public synchronized IExecutorService getUIExecutorService() { + if (uiExecutorService == null) { + uiExecutorService = new DelegatingUIExecutorService(getBundle().getBundleContext()); + } + return uiExecutorService; + } + + public INotificationBuilderFactory getNotificationBuilderFactory() { + return notificationBuilderTracker.getService(); + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/DelegatingUIExecutorService.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/DelegatingUIExecutorService.java new file mode 100644 index 00000000000..2efa8fc0ede --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/DelegatingUIExecutorService.java @@ -0,0 +1,235 @@ +/***************************************************************************** + * Copyright (c) 2016 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms 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 - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.papyrus.infra.tools.spi.IExecutorServiceFactory; +import org.eclipse.papyrus.infra.tools.util.IExecutorService; +import org.eclipse.papyrus.infra.tools.util.IProgressCallable; +import org.eclipse.papyrus.infra.tools.util.IProgressRunnable; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; + +/** + * An executor service that delegates to the UI executor provided by the + * registered factory service, if available. Otherwise, it delegates to + * a default single-thread executor. Reacts to registration changes in + * the {@link IExecutorServiceFactory} by replacing the default executor + * with a "real" UI executor when available and replacing the "real" UI + * executor with a default executor when no longer available. + */ +class DelegatingUIExecutorService implements IExecutorService, ServiceTrackerCustomizer<IExecutorServiceFactory, IExecutorService> { + private final BundleContext context; + private final ServiceTracker<IExecutorServiceFactory, IExecutorService> tracker; + private ExecutorService delegate; + + DelegatingUIExecutorService(BundleContext context) { + this.context = context; + this.tracker = new ServiceTracker<>(context, IExecutorServiceFactory.class, this); + + tracker.open(); + + delegate = tracker.getService(); + if (delegate == null) { + delegate = Executors.newSingleThreadExecutor(); + } + } + + synchronized void shutdown(BundleContext context) { + if (context == this.context) { + if (delegate != null) { + delegate.shutdown(); + delegate = null; + } + + tracker.close(); + } + } + + @Override + public synchronized IExecutorService addingService(ServiceReference<IExecutorServiceFactory> reference) { + IExecutorService result = context.getService(reference).createExecutor(); + + if (delegate != null) { + delegate.shutdown(); + } + + delegate = result; + + return result; + } + + @Override + public synchronized void removedService(ServiceReference<IExecutorServiceFactory> reference, IExecutorService service) { + context.ungetService(reference); + + if (service == delegate) { + delegate.shutdown(); + delegate = Executors.newSingleThreadExecutor(); + } + } + + @Override + public void modifiedService(ServiceReference<IExecutorServiceFactory> reference, IExecutorService service) { + // Pass + } + + // + // ExecutorService protocol + // + + @Override + public void shutdown() { + throw new IllegalStateException("Executor is shared"); //$NON-NLS-1$ + } + + @Override + public List<Runnable> shutdownNow() { + throw new IllegalStateException("Executor is shared"); //$NON-NLS-1$ + } + + @Override + public void execute(Runnable command) { + delegate.execute(command); + } + + @Override + public boolean isShutdown() { + return delegate.isShutdown(); + } + + @Override + public boolean isTerminated() { + return delegate.isTerminated(); + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return delegate.awaitTermination(timeout, unit); + } + + @Override + public <T> Future<T> submit(Callable<T> task) { + return delegate.submit(task); + } + + @Override + public <T> Future<T> submit(Runnable task, T result) { + return delegate.submit(task, result); + } + + @Override + public Future<?> submit(Runnable task) { + return delegate.submit(task); + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException { + return delegate.invokeAll(tasks); + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException { + return delegate.invokeAll(tasks, timeout, unit); + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException { + return delegate.invokeAny(tasks); + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return delegate.invokeAny(tasks, timeout, unit); + } + + // + // IExecutorService protocol + // + + @Override + public void syncExec(Runnable task) throws InterruptedException, ExecutionException { + if (delegate instanceof IExecutorService) { + ((IExecutorService) delegate).syncExec(task); + } else { + Future<?> future = delegate.submit(task); + // Wait for it + future.get(); + } + } + + @Override + public <V> V syncCall(Callable<V> callable) throws InterruptedException, ExecutionException { + if (delegate instanceof IExecutorService) { + return ((IExecutorService) delegate).syncCall(callable); + } else { + Future<V> future = delegate.submit(callable); + // Wait for it + return future.get(); + } + } + + @Override + public Future<?> submit(IProgressRunnable task) { + if (delegate instanceof IExecutorService) { + return ((IExecutorService) delegate).submit(task); + } else { + return delegate.submit(() -> task.run(new NullProgressMonitor())); + } + } + + @Override + public <V> Future<V> submit(IProgressCallable<V> callable) { + if (delegate instanceof IExecutorService) { + return ((IExecutorService) delegate).submit(callable); + } else { + return delegate.submit(() -> callable.call(new NullProgressMonitor())); + } + } + + @Override + public void syncExec(IProgressRunnable task) throws InterruptedException, ExecutionException { + if (delegate instanceof IExecutorService) { + ((IExecutorService) delegate).syncExec(task); + } else { + Future<?> future = delegate.submit(() -> task.run(new NullProgressMonitor())); + // Wait for it + future.get(); + } + } + + @Override + public <V> V syncCall(IProgressCallable<V> callable) throws InterruptedException, ExecutionException { + if (delegate instanceof IExecutorService) { + return ((IExecutorService) delegate).syncCall(callable); + } else { + Future<V> future = delegate.submit(() -> callable.call(new NullProgressMonitor())); + // Wait for it + return future.get(); + } + } + +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/comparator/CompositeComparator.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/comparator/CompositeComparator.java new file mode 100644 index 00000000000..535294eb0df --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/comparator/CompositeComparator.java @@ -0,0 +1,63 @@ +/***************************************************************************** + * Copyright (c) 2013 CEA LIST. + * + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Vincent Lorenzo (CEA LIST) vincent.lorenzo@cea.fr - Initial API and implementation + * + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.comparator; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +/** + * This class allows to compare elements on several levels + * + * @author vl222926 + * @param <T> + * + */ + +public class CompositeComparator<T> implements Comparator<T> { + + /** + * the list of the comparator + */ + private final List<Comparator<T>> comparators; + + /** + * + * Constructor. + * + * @param comparators + */ + public CompositeComparator(final List<Comparator<T>> comparators) { + this.comparators = comparators; + } + + /** + * + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + * + * @param o1 + * @param o2 + * @return + */ + public int compare(T o1, T o2) { + int res = 0; + final Iterator<Comparator<T>> iter = comparators.iterator(); + while (iter.hasNext() && res == 0) { + final Comparator<T> current = iter.next(); + res = current.compare(o1, o2); + } + return res; + } + +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/AggregatedObservable.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/AggregatedObservable.java new file mode 100644 index 00000000000..7b59541b4f0 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/AggregatedObservable.java @@ -0,0 +1,47 @@ +/***************************************************************************** + * Copyright (c) 2011 CEA LIST. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.databinding; + +import org.eclipse.core.databinding.observable.IObservable; + +/** + * An interface to aggregate many IObservables in a single one. + * A modification on the aggregated observable should be dispatched to all + * the encapsulated observables. + * + * @author Camille Letavernier + */ +public interface AggregatedObservable extends IObservable { + + /** + * Aggregates the current Observable with the given observable. + * Returns the aggregated Observable, or null if the aggregation is not + * possible + * + * Typically, only IObservable with similar ValueTypes can be + * aggregated. + * + * @param observable + * The IObservable to aggregate to the current IObservable + * @return + * The aggregated IObservable, or null if the aggregation is not + * possible + */ + public AggregatedObservable aggregate(IObservable observable); + + /** + * Tests if the sub-observables have different values + * + * @return true if the sub-observables have different values + */ + public boolean hasDifferentValues(); +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/CommandBasedObservable.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/CommandBasedObservable.java new file mode 100644 index 00000000000..1e10dec5a1e --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/CommandBasedObservable.java @@ -0,0 +1,27 @@ +/***************************************************************************** + * Copyright (c) 2011 CEA LIST. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.databinding; + +import org.eclipse.core.databinding.observable.IObservable; +import org.eclipse.emf.common.command.Command; + + +public interface CommandBasedObservable extends IObservable { + + /** + * Returns the EMF Command for modifying this Observable's value + * + * @param value + * @return + */ + public Command getCommand(Object value); +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/CommandBasedObservableValue.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/CommandBasedObservableValue.java new file mode 100644 index 00000000000..06450cf6856 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/CommandBasedObservableValue.java @@ -0,0 +1,19 @@ +/***************************************************************************** + * Copyright (c) 2011 CEA LIST. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.databinding; + +import org.eclipse.core.databinding.observable.value.IObservableValue; + + +public interface CommandBasedObservableValue extends CommandBasedObservable, IObservableValue { + +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingInvocationHandler.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingInvocationHandler.java new file mode 100644 index 00000000000..838c6a36a93 --- /dev/null +++ b/plugins/infra/core/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/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservable.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservable.java new file mode 100644 index 00000000000..7f2f0206e20 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservable.java @@ -0,0 +1,347 @@ +/* + * 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())); + } + + @Override + 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/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableCollection.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableCollection.java new file mode 100644 index 00000000000..88c1079029b --- /dev/null +++ b/plugins/infra/core/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/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableList.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableList.java new file mode 100644 index 00000000000..f7e9fc0e752 --- /dev/null +++ b/plugins/infra/core/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 = 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/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableSet.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableSet.java new file mode 100644 index 00000000000..c91aa4f2ed4 --- /dev/null +++ b/plugins/infra/core/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 = 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/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableValue.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/DelegatingObservableValue.java new file mode 100644 index 00000000000..9b935c26f36 --- /dev/null +++ b/plugins/infra/core/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 = 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/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/IDelegatingObservable.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/IDelegatingObservable.java new file mode 100644 index 00000000000..81bc3f9cd2b --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/IDelegatingObservable.java @@ -0,0 +1,52 @@ +/* + * 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/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/IMultipleObservableValue.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/IMultipleObservableValue.java new file mode 100644 index 00000000000..b2fd4b1cfeb --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/IMultipleObservableValue.java @@ -0,0 +1,37 @@ +/***************************************************************************** + * Copyright (c) 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 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * CEA LIST - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.databinding; + +import java.util.List; + +import org.eclipse.core.databinding.observable.value.IObservableValue; + + + +/** + * Interface for a multiple selection of observable values. + */ +public interface IMultipleObservableValue extends AggregatedObservable, IObservableValue { + + /** + * @return the list of sub-observable values + */ + List<IObservableValue> getObservableValues(); + + /** + * @return the list of observed values + */ + List<Object> getObservedValues(); + +}
\ No newline at end of file diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/MultipleObservableValue.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/MultipleObservableValue.java new file mode 100644 index 00000000000..fb683dbc4c2 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/MultipleObservableValue.java @@ -0,0 +1,181 @@ +/***************************************************************************** + * 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 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + * Christian W. Damus (CEA) - bug 417409 + * Christian W. Damus (CEA) - bug 444227 + * + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.databinding; + +import java.util.Collection; +import java.util.LinkedList; +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.IObservableValue; +import org.eclipse.core.databinding.observable.value.ValueDiff; + +/** + * MultipleObservableValue is used to map a single element + * to a collection of model elements. + * + * It is especially used when displaying a Property View for multiple elements, + * when we want to edit the same property for all of them. + * + * 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 ReferenceCountedObservable.Value implements IMultipleObservableValue, IChangeListener { + + /** + * + * Constructor. + * + * @param values + * The collection of sub-elements for this MultipleObservableValue + * + */ + public MultipleObservableValue(Collection<IObservableValue> values) { + if (values != null) { + observableValues.addAll(values); + } + } + + /** + * + * Constructor. + * + */ + public MultipleObservableValue() { + + } + + public Object getValueType() { + if (observableValues.isEmpty()) { + return null; + } + + return observableValues.get(0).getValueType(); + } + + /** + * If all objects have the same value, returns this value + * Otherwise, returns the defaultGetValue + * If the defaultGetValue hasn't been set, returns null + */ + @Override + protected Object doGetValue() { + if (hasDifferentValues() || observableValues.isEmpty()) { + return null; + } + + return observableValues.get(0).getValue(); + } + + private boolean equals(Object value, Object currentValue) { + if (value == currentValue) { + return true; + } + if (value == null) { + return false; + } + return value.equals(currentValue); + } + + @Override + protected void doSetValue(Object value) { + for (IObservableValue observable : observableValues) { + observable.setValue(value); + } + } + + public AggregatedObservable aggregate(IObservable observable) { + if (observable instanceof IObservableValue) { + ReferenceCountedObservable.Util.retain(observable); + observableValues.add((IObservableValue) observable); + observable.addChangeListener(this); + return this; + } + return null; + } + + public List<IObservableValue> getObservableValues() { + return observableValues; + } + + public List<Object> getObservedValues() { + List<Object> result = new LinkedList<Object>(); + for (IObservableValue value : getObservableValues()) { + result.add(value.getValue()); + } + return result; + } + + @Override + public synchronized void dispose() { + super.dispose(); + for (IObservableValue observable : observableValues) { + observable.removeChangeListener(this); + + // I don't own my observables, so I just release them + ReferenceCountedObservable.Util.release(observable); + } + + observableValues.clear(); + } + + /** + * The {@link IObservableValue}s aggregated by this Observable + */ + protected List<IObservableValue> observableValues = new LinkedList<IObservableValue>(); + + public boolean hasDifferentValues() { + if (observableValues.isEmpty()) { + return false; + } + + Object currentValue = null; + boolean firstValue = true; + for (IObservableValue observable : observableValues) { + if (firstValue) { + firstValue = false; + currentValue = observable.getValue(); + } else { + Object value = observable.getValue(); + if (equals(value, currentValue)) { + continue; + } + return true; + } + } + + return false; + } + + public void handleChange(ChangeEvent event) { + // We're not interested in the old and new values + // We just return two different values so that a change event is fired + super.fireValueChange(new ValueDiff() { + + @Override + public Object getOldValue() { + return true; + } + + @Override + public Object getNewValue() { + return false; + } + }); + } + +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/ReferenceCountedObservable.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/ReferenceCountedObservable.java new file mode 100644 index 00000000000..d96f2694f0c --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/ReferenceCountedObservable.java @@ -0,0 +1,381 @@ +/* + * Copyright (c) 2014, 2016 CEA, Christian W. Damus, and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms 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 + * Christian W. Damus - bug 487027 + * + */ +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); + } + + @Override + public void retain() { + refCount.retain(); + } + + @Override + public void release() { + refCount.release(); + } + + @Override + 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<T> extends AbstractObservableValue<T> implements ReferenceCountedObservable { + + private final Support refCount = new Support(this); + + public Value() { + super(); + } + + public Value(Realm realm) { + super(realm); + } + + @Override + public void retain() { + refCount.retain(); + } + + @Override + public void release() { + refCount.release(); + } + + @Override + 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 { + + @Override + 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); + } + } + + @Override + public void handleDispose(DisposeEvent event) { + if (event.getObservable() == get()) { + clear(); + } + } + } + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/TouchableValue.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/TouchableValue.java new file mode 100644 index 00000000000..438f111b664 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/TouchableValue.java @@ -0,0 +1,73 @@ +/***************************************************************************** + * Copyright (c) 2015, 2016 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms 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 - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.databinding; + +import java.util.Objects; + +import org.eclipse.core.databinding.observable.ChangeEvent; +import org.eclipse.core.databinding.observable.Diffs; +import org.eclipse.core.databinding.observable.Realm; +import org.eclipse.core.databinding.observable.value.WritableValue; + +/** + * An analogue of the {@link WritableValue} that supports "touches" to send + * change events even though the value is not replaced. + */ +public class TouchableValue<T> extends ReferenceCountedObservable.Value<T> { + private final Class<? extends T> type; + + private T value; + + public TouchableValue(Realm realm, Class<? extends T> type) { + super(realm); + + this.type = type; + } + + public TouchableValue(Realm realm, Class<? extends T> type, T initialValue) { + super(realm); + + this.type = type; + this.value = initialValue; + } + + @Override + public Object getValueType() { + return type; + } + + @Override + protected T doGetValue() { + return value; + } + + @Override + protected void doSetValue(T value) { + if (!Objects.equals(this.value, value)) { + T oldValue = this.value; + this.value = value; + fireValueChange(Diffs.createValueDiff(oldValue, value)); + } + } + + /** + * Indicates that some kind of change has happened to the observable's value + * that observers should know about, but for which specific change details + * are not available. + */ + public void touch() { + checkRealm(); + fireEvent(new ChangeEvent(this)); + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/WritableListWithIterator.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/WritableListWithIterator.java new file mode 100644 index 00000000000..236d80cfcef --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/databinding/WritableListWithIterator.java @@ -0,0 +1,288 @@ +/***************************************************************************** + * Copyright (c) 2015, 2016 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms 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 - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.databinding; + +import static org.eclipse.core.databinding.observable.Diffs.createListDiff; + +import java.util.ArrayList; +import java.util.Collection; +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.Realm; +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.WritableList; + +/** + * A specialization of the core Databindings {@link WritableList} providing + * iterators that support modification. + */ +public class WritableListWithIterator<E> extends WritableList<E> implements ReferenceCountedObservable { + + private final ReferenceCountedObservable.Support refCount = new ReferenceCountedObservable.Support(this); + + private final ListDiffVisitor<E> mutationHook = createMutationHook(); + + public WritableListWithIterator() { + super(); + } + + public WritableListWithIterator(Realm realm) { + super(realm); + } + + public WritableListWithIterator(List<E> toWrap, Object elementType) { + super(toWrap, elementType); + } + + public WritableListWithIterator(Collection<E> collection, Object elementType) { + super(collection, elementType); + } + + public WritableListWithIterator(Realm realm, List<E> toWrap, Object elementType) { + super(realm, toWrap, elementType); + } + + public WritableListWithIterator(Realm realm, Collection<E> collection, Object elementType) { + super(realm, collection, elementType); + } + + // + // Mutation hooks + // + + void didAdd(E element) { + // Pass + } + + void didRemove(E element) { + // Pass + } + + private ListDiffVisitor<E> createMutationHook() { + return new ListDiffVisitor<E>() { + @Override + public void handleAdd(int index, E element) { + didAdd(element); + } + + @Override + public void handleRemove(int index, E element) { + didRemove(element); + } + }; + } + + @Override + protected void fireListChange(ListDiff<E> diff) { + diff.accept(mutationHook); + + super.fireListChange(diff); + } + + // + // Reference counting + // + + @Override + public void retain() { + refCount.retain(); + } + + @Override + public void release() { + refCount.release(); + } + + @Override + public void autorelease() { + refCount.autorelease(); + } + + // + // Iteration + // + + @Override + public Iterator<E> iterator() { + getterCalled(); + return new Iter(); + } + + @Override + public ListIterator<E> listIterator() { + getterCalled(); + return new ListIter(0); + } + + @Override + public ListIterator<E> listIterator(int index) { + getterCalled(); + return new ListIter(index); + } + + static <E> ListDiffEntry<E> added(E element, int position) { + return Diffs.createListDiffEntry(position, true, element); + } + + static <E> ListDiffEntry<E> removed(E element, int position) { + return Diffs.createListDiffEntry(position, false, element); + } + + // + // Nested types + // + + private class Iter implements Iterator<E> { + + final ListIterator<E> delegate; + int lastReturned = -1; + + Iter() { + this(0); + } + + Iter(int index) { + super(); + + this.delegate = wrappedList.listIterator(index); + } + + @Override + public boolean hasNext() { + return delegate.hasNext(); + } + + @Override + public E next() { + E result = delegate.next(); + lastReturned = delegate.previousIndex(); + return result; + } + + E lastReturned() { + return ((lastReturned >= 0) && (lastReturned < size())) + ? get(lastReturned) + : null; + } + + @Override + public void remove() { + E removed = lastReturned(); + + delegate.remove(); + + // We only get this far if remove succeeded + fireListChange(createListDiff(removed(removed, lastReturned))); + } + } + + private class ListIter extends Iter implements ListIterator<E> { + + ListIter(int index) { + super(index); + } + + @Override + public boolean hasPrevious() { + return delegate.hasPrevious(); + } + + @Override + public int nextIndex() { + return delegate.nextIndex(); + } + + @Override + public int previousIndex() { + return delegate.previousIndex(); + } + + @Override + public E previous() { + E result = delegate.previous(); + lastReturned = delegate.nextIndex(); + return result; + } + + @Override + public void set(E e) { + E removed = lastReturned(); + + delegate.set(e); + + // We only get this far if remove succeeded + fireListChange(createListDiff(removed(removed, lastReturned), added(e, lastReturned))); + } + + @Override + public void add(E e) { + delegate.add(e); + + // We only get this far if add succeeded + fireListChange(createListDiff(added(e, previousIndex()))); + } + + } + + /** + * A specialized writable list that owns its elements via strong (and counted) references. + * It does not support wrapping an externally-provided list. + */ + public static class Containment<E extends ReferenceCountedObservable> extends WritableListWithIterator<E> { + + public Containment() { + super(); + } + + public Containment(Object elementType) { + super(new ArrayList<E>(), elementType); + } + + public Containment(Realm realm, Object elementType) { + super(realm, new ArrayList<E>(), elementType); + } + + public Containment(Realm realm) { + super(realm); + } + + @Override + public synchronized void dispose() { + super.dispose(); + + // Release my contained elements + wrappedList.forEach(ReferenceCountedObservable::release); + wrappedList.clear(); + } + + @Override + void didAdd(E element) { + if (element != null) { + element.retain(); + } + } + + @Override + void didRemove(E element) { + if (element != null) { + element.release(); + } + } + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/IContext.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/IContext.java new file mode 100644 index 00000000000..93d0e10e3dc --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/IContext.java @@ -0,0 +1,65 @@ +/***************************************************************************** + * Copyright (c) 2010, 2016 ATOS ORIGIN, Christian W. Damus, and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tristan Faure (ATOS ORIGIN INTEGRATION) tristan.faure@atosorigin.com - Initial API and implementation + * Christian W. Damus - bug 485220 + * + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.notify; + +import java.util.HashMap; +import java.util.Map; + + +/** + * The Class Context. + */ +public interface IContext { + + /** + * This constant identifies developper strings to indicate the code location + */ + public static String STRING_FOR_DEVELOPER = "_please_check_IContext_Constant"; + + /** + * This constant allows the user to retrieve the composite created if he filled a ICreationComposite + */ + public static String COMPOSITE_CREATED = "composite_created" + STRING_FOR_DEVELOPER; + + /** + * This constant allows the user to retrieve the notification if he needs to close it + */ + public static String NOTIFICATION_OBJECT = "notification_object" + STRING_FOR_DEVELOPER; + + /** + * This constant determines an action id to the current context + */ + public static String ACTION_ID = "action_id"; + + public void put(String s, Object o); + + public Object get(String s); + + public static class Context implements IContext { + + private Map<String, Object> objects = new HashMap<String, Object>();; + + @Override + public void put(String s, Object o) { + objects.put(s, o); + } + + @Override + public Object get(String s) { + return objects.get(s); + } + + } + +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/INotification.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/INotification.java new file mode 100644 index 00000000000..55bc0f2f8cf --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/INotification.java @@ -0,0 +1,32 @@ +/***************************************************************************** + * Copyright (c) 2010, 2016 ATOS ORIGIN, Christian W. Damus, and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tristan Faure (ATOS ORIGIN INTEGRATION) tristan.faure@atosorigin.com - Initial API and implementation + * Christian W. Damus - bug 485220 + * + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.notify; + + +/** + * The Interface INotification. + * offers some services for a notification + */ +public interface INotification { + + /** delete the current notification */ + void delete(); + + /** + * whether the current notification is deleted + * + * @return true if notification is deleted + */ + boolean isDeleted(); +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/INotificationBuilder.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/INotificationBuilder.java new file mode 100644 index 00000000000..84bb3b4ace6 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/INotificationBuilder.java @@ -0,0 +1,132 @@ +/***************************************************************************** + * Copyright (c) 2010, 2016 ATOS ORIGIN, Christian W. Damus, and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tristan Faure (ATOS ORIGIN INTEGRATION) tristan.faure@atosorigin.com - Initial API and implementation + * Christian W. Damus - bug 485220 + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.notify; + +/** + * Protocol of a pluggable notification builder. + */ +public interface INotificationBuilder { + /** asynchronous, determines if the message needs or not to be synchronous with the notification */ + String ASYNCHRONOUS = "asynchronous"; + + /** a message displayed in the notification */ + String MESSAGE = "message"; + + /** a default action in the notification */ + String ACTION = "default_action"; + + /** a delay to display if it is a temporary notification */ + String DELAY = "delay"; + + /** determines if the notification is temporary */ + String TEMPORARY = "temporary"; + + /** a title displayed in the notification */ + String TITLE = "title"; + + /** determines if there is html content in the notification */ + String HTML = "html"; + + /** determines the type according to {@link Type} */ + String TYPE = "type"; + + /** + * Set a message for the notification + * + * @param message + * , the message to display + * @return this + */ + INotificationBuilder setMessage(String message); + + /** + * Determines if the notification is asynchronous (don't force the user to read the notification immediately) + * + * @param asynchronous + * , true if it asynchronous + * @return this + */ + INotificationBuilder setAsynchronous(boolean asynchronous); + + /** + * Set a default action for the notification + * + * @param runnable + * , a runnable triggered when default action of the notification is selected + * The first action added is the default One + * @return this + */ + INotificationBuilder addAction(NotificationRunnable runnable); + + /** + * Set a delay if the notification is temporary + * + * @param delayMs + * , the delay in ms for visibility + * @return this + */ + INotificationBuilder setDelay(long delayMs); + + /** + * Set true if the notification is temporary + * + * @param temporary + * @return this + */ + INotificationBuilder setTemporary(boolean temporary); + + /** + * Set a title for the notification + * + * @param title + * , the title + * @return this + */ + INotificationBuilder setTitle(String title); + + /** + * Set if the notification has to understand HTML + * + * @param useHTML + * @return this + */ + INotificationBuilder setHTML(boolean useHTML); + + /** + * Set the type of the notification according to {@link Type} + * + * @param type + * , the desired type + * @return this + */ + INotificationBuilder setType(Type type); + + /** + * Allows the developer to use a specific parameter + * + * @param name + * , the key of the parameter + * @param value + * , the value + * @return this + */ + INotificationBuilder setParameter(String name, Object value); + + /** + * Creates a notification according to different parameters + */ + INotification run(); + +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/LogNotification.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/LogNotification.java new file mode 100644 index 00000000000..a4554cb06ed --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/LogNotification.java @@ -0,0 +1,56 @@ +/***************************************************************************** + * Copyright (c) 2016 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms 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 - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.notify; + +import org.eclipse.papyrus.infra.tools.Activator; + +/** + * A simple notification that just emits a message to the log. + */ +public class LogNotification implements INotification { + + public LogNotification(Type type, String message) { + super(); + + // No message? No log + if (message != null) { + if (type == null) { + type = Type.WARNING; + } + switch (type) { + case ERROR: + Activator.log.error(message, null); + break; + case WARNING: + Activator.log.warn(message); + break; + default: + Activator.log.info(message); + break; + } + } + } + + @Override + public void delete() { + // A log message is not presented in the UI, so it is always deleted + } + + @Override + public boolean isDeleted() { + // A log message is not presented in the UI, so it is always deleted + return true; + } + +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/NotificationBuilder.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/NotificationBuilder.java new file mode 100644 index 00000000000..9d54d4204d9 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/NotificationBuilder.java @@ -0,0 +1,388 @@ +/***************************************************************************** + * Copyright (c) 2010, 2016 ATOS ORIGIN, Christian W. Damus, and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tristan Faure (ATOS ORIGIN INTEGRATION) tristan.faure@atosorigin.com - Initial API and implementation + * Christian W. Damus - bug 485220 + * + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.notify; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import org.eclipse.papyrus.infra.tools.Activator; +import org.eclipse.papyrus.infra.tools.spi.INotificationBuilderFactory; + + +/** + * A class creating a notification, + * the run method launch the message according to the value of the attributes + * + * @author tristan faure + * + */ +public class NotificationBuilder implements INotificationBuilder { + + /** The parameters of the notification with the corresponding values */ + protected Map<String, Object> parameters = new HashMap<String, Object>(); + + private static final int YES = 1 << 6; // SWT.YES + + private static final int NO = 1 << 7; // SWT.NO + + /** + * Set a message for the notification + * + * @param message + * , the message to display + * @return this + */ + @Override + public NotificationBuilder setMessage(String message) { + parameters.put(MESSAGE, message); + return this; + } + + /** + * Determines if the notification is asynchronous (don't force the user to read the notification immediately) + * + * @param asynchronous + * , true if it asynchronous + * @return this + */ + @Override + public NotificationBuilder setAsynchronous(boolean asynchronous) { + parameters.put(ASYNCHRONOUS, asynchronous); + return this; + } + + /** + * Set a default action for the notification + * + * @param runnable + * , a runnable triggered when default action of the notification is selected + * The first action added is the default One + * @return this + */ + @Override + @SuppressWarnings("unchecked") + public NotificationBuilder addAction(NotificationRunnable runnable) { + Collection<NotificationRunnable> runnables = (Collection<NotificationRunnable>) parameters.get(ACTION); + if (runnables == null) { + runnables = new LinkedList<NotificationRunnable>(); + parameters.put(ACTION, runnables); + } + runnables.add(runnable); + return this; + } + + /** + * Set a delay if the notification is temporary + * + * @param delayMs + * , the delay in ms for visibility + * @return this + */ + @Override + public NotificationBuilder setDelay(long delayMs) { + parameters.put(DELAY, delayMs); + return this; + } + + /** + * Set true if the notification is temporary + * + * @param temporary + * @return this + */ + @Override + public NotificationBuilder setTemporary(boolean temporary) { + parameters.put(TEMPORARY, temporary); + return this; + } + + /** + * Set a title for the notification + * + * @param title + * , the title + * @return this + */ + @Override + public NotificationBuilder setTitle(String title) { + parameters.put(TITLE, title); + return this; + } + + /** + * Set if the notification has to understand HTML + * + * @param useHTML + * @return this + */ + @Override + public NotificationBuilder setHTML(boolean useHTML) { + parameters.put(HTML, useHTML); + return this; + } + + /** + * Set the type of the notification according to {@link Type} + * + * @param type + * , the desired type + * @return this + */ + @Override + public NotificationBuilder setType(Type type) { + parameters.put(TYPE, type); + return this; + } + + /** + * Allows the developer to use a specific parameter + * + * @param name + * , the key of the parameter + * @param value + * , the value + * @return this + */ + @Override + public NotificationBuilder setParameter(String name, Object value) { + parameters.put(name, value); + return this; + } + + /** + * Creates a notification according to different parameters + */ + @Override + public INotification run() { + INotification result; + + INotificationBuilderFactory delegator = Activator.getDefault().getNotificationBuilderFactory(); + if (delegator != null) { + // Create the delegate + INotificationBuilder delegate = delegator.createNotificationBuilder(); + + // Fill it up + parameters.forEach(delegate::setParameter); + + // And run it + result = delegate.run(); + } else { + // Just a simple log notification + result = new LogNotification( + (Type) parameters.get(TYPE), + (String) parameters.get(MESSAGE)); + } + + return result; + } + + /** + * Creates a notification builder already configured to display an information builder + * + * @return a notification builder + */ + public static NotificationBuilder createInformationBuilder() { + NotificationBuilder builder = new NotificationBuilder(); + return builder; + } + + /** + * Creates a notification builder already configured to display an asynchronous popup + * + * @param text + * , the text to display + * @return a notification builder + */ + public static NotificationBuilder createAsyncPopup(String text) { + return new NotificationBuilder().setAsynchronous(true).setTemporary(true).setMessage(text).setDelay(2000); + } + + /** + * Creates a notification builder already configured to display an asynchronous popup with a specified title + * + * @param text + * , the text to display + * @param title + * , the title of the popup + * @return a notification builder + */ + public static NotificationBuilder createAsyncPopup(String title, String text) { + return new NotificationBuilder().setAsynchronous(true).setTemporary(true).setMessage(text).setTitle(title).setDelay(2000); + } + + /** + * Creates a notification builder already configured to display an information popup + * + * @param text + * , the text to display + * @return a notification builder + */ + public static NotificationBuilder createInfoPopup(String text) { + return new NotificationBuilder().setAsynchronous(false).setTemporary(false).setMessage(text).setType(Type.INFO); + } + + /** + * Creates a notification builder already configured to display an warning popup + * + * @param text + * , the text to display + * @return a notification builder + */ + public static NotificationBuilder createWarningPopup(String text) { + return new NotificationBuilder().setAsynchronous(false).setTemporary(false).setMessage(text).setType(Type.WARNING); + } + + /** + * Creates a notification builder already configured to display a popup with question icon + * + * @param text + * , the text to display + * @return a notification builder + */ + public static NotificationBuilder createQuestionPopup(String text) { + return new NotificationBuilder().setAsynchronous(false).setTemporary(false).setMessage(text).setType(Type.QUESTION); + } + + /** + * Creates a notification builder already configured to display a popup with error icon + * + * @param text + * , the text to display + * @return a notification builder + */ + public static NotificationBuilder createErrorPopup(String text) { + return new NotificationBuilder().setAsynchronous(false).setTemporary(false).setMessage(text).setType(Type.ERROR); + } + + /** + * Creates a notification builder already configured to display a yes no question + * + * @param yes + * , the action to launch if yes is selected + * @param no + * , the action to launch if no is selected + * @return a notification builder + */ + public static NotificationBuilder createYesNo(String message, final Runnable yes, final Runnable no) { + return new NotificationBuilder().setType(Type.QUESTION).setAsynchronous(false).setTemporary(false).setMessage(message).addAction(new NotificationRunnable() { + + @Override + public void run(IContext context) { + if (yes != null) { + context.put(IContext.ACTION_ID, YES); + yes.run(); + } + } + + @Override + public String getLabel() { + return "Yes"; + } + }).addAction(new NotificationRunnable() { + + @Override + public void run(IContext context) { + if (no != null) { + context.put(IContext.ACTION_ID, NO); + no.run(); + } + } + + @Override + public String getLabel() { + return "No"; + } + }); + } + + /** + * Creates a notification builder already configured to display a yes no question, no runnables are necesary as the user just want the + * PopupNotification result + * This NotificationRunnable is not intended to be changed to an asynchronous notification for example + * When the run method is called use getRsult method in {@link PopupNotification} and test if the value is SWT.YES or SWT.NO + * + * @param message + * , the message to display + * + * @return a notification builder + */ + public static NotificationBuilder createYesNo(String message) { + return new NotificationBuilder().setType(Type.QUESTION).setAsynchronous(false).setTemporary(false).setMessage(message).addAction(new NotificationRunnable() { + + @Override + public void run(IContext context) { + context.put(IContext.ACTION_ID, YES); + } + + @Override + public String getLabel() { + return "Yes"; + } + }).addAction(new NotificationRunnable() { + + @Override + public void run(IContext context) { + context.put(IContext.ACTION_ID, NO); + } + + @Override + public String getLabel() { + return "No"; + } + }); + } + + /** + * Creates a notification builder already configured to display a yes no question + * + * @param yes + * , the action to launch if yes is selected + * @param no + * , the action to launch if no is selected + * @return a notification builder + */ + public static NotificationBuilder createYesNo(String message, final NotificationRunnable yes, final NotificationRunnable no) { + return new NotificationBuilder().setType(Type.QUESTION).setAsynchronous(false).setTemporary(false).setMessage(message).addAction(new NotificationRunnable() { + + @Override + public void run(IContext context) { + if (yes != null) { + context.put(IContext.ACTION_ID, YES); + yes.run(context); + } + } + + @Override + public String getLabel() { + return "Yes"; + } + }).addAction(new NotificationRunnable() { + + @Override + public void run(IContext context) { + if (no != null) { + context.put(IContext.ACTION_ID, NO); + no.run(context); + } + } + + @Override + public String getLabel() { + return "No"; + } + }); + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/NotificationRunnable.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/NotificationRunnable.java new file mode 100644 index 00000000000..3df50c005ab --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/NotificationRunnable.java @@ -0,0 +1,36 @@ +/***************************************************************************** + * Copyright (c) 2010, 2016 ATOS ORIGIN, Christian W. Damus, and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tristan Faure (ATOS ORIGIN INTEGRATION) tristan.faure@atosorigin.com - Initial API and implementation + * Christian W. Damus - bug 485220 + * + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.notify; + +/** + * a runnable + * + */ +public interface NotificationRunnable { + + /** + * Run the runnable + * + * @param context + * , used to fill properties, can contain data + */ + void run(IContext context); + + /** + * The label of the runnable + * + * @return the label + */ + String getLabel(); +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/Type.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/Type.java new file mode 100644 index 00000000000..61c73981778 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/notify/Type.java @@ -0,0 +1,29 @@ +/***************************************************************************** + * Copyright (c) 2010, 2016 ATOS ORIGIN, Christian W. Damus, and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Tristan Faure (ATOS ORIGIN INTEGRATION) tristan.faure@atosorigin.com - Initial API and implementation + * Christian W. Damus - bug 485220 + * + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.notify; + + +/** + * The different types of Notifications + * <li>INFO</i> + * <li>WARNING</i> + * <li>ERROR</i> + * <li>QUESTION</i> + * + * @author tristan faure + * + */ +public enum Type { + INFO, WARNING, ERROR, QUESTION +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/spi/IExecutorServiceFactory.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/spi/IExecutorServiceFactory.java new file mode 100644 index 00000000000..cc2ff542665 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/spi/IExecutorServiceFactory.java @@ -0,0 +1,25 @@ +/***************************************************************************** + * Copyright (c) 2016 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms 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 - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.spi; + +import org.eclipse.papyrus.infra.tools.util.IExecutorService; + +/** + * An OSGi service protocol for creation of an executor service on the UI thread. + */ +@FunctionalInterface +public interface IExecutorServiceFactory { + /** Creates an executor service that posts runnables on the SWT UI thread. */ + IExecutorService createExecutor(); +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/spi/INotificationBuilderFactory.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/spi/INotificationBuilderFactory.java new file mode 100644 index 00000000000..a527ec3c592 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/spi/INotificationBuilderFactory.java @@ -0,0 +1,26 @@ +/***************************************************************************** + * Copyright (c) 2016 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms 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 - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.spi; + +import org.eclipse.papyrus.infra.tools.notify.INotificationBuilder; + +/** + * An OSGi service protocol for creation of a notification builder + * (preferably for UI presentation). + */ +@FunctionalInterface +public interface INotificationBuilderFactory { + /** Creates notification builder that presents notifications in the UI. */ + INotificationBuilder createNotificationBuilder(); +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/BooleanHelper.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/BooleanHelper.java new file mode 100644 index 00000000000..296c4751c96 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/BooleanHelper.java @@ -0,0 +1,38 @@ +/***************************************************************************** + * Copyright (c) 2012 CEA LIST. + * + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Vincent Lorenzo (CEA LIST) Vincent.Lorenzo@cea.fr - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.util; + +/** + * + * This class provides an useful methods for boolean + * + */ +public class BooleanHelper { + + private BooleanHelper() { + // to prevent instanciation + } + + /** + * + * @param str + * a string + * @return + * <code>true</code> if the string represents a boolean value + */ + public static final boolean isBoolean(final String str) { + return "true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str); //$NON-NLS-1$ //$NON-NLS-2$ + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/ClassLoaderHelper.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/ClassLoaderHelper.java new file mode 100644 index 00000000000..46751c90c81 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/ClassLoaderHelper.java @@ -0,0 +1,152 @@ +/***************************************************************************** + * Copyright (c) 2010 CEA LIST. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.util; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.papyrus.infra.tools.Activator; + +/** + * A Helper class for Class Loading. + * + * @author Camille Letavernier + */ +// This class needs the "BuddyPolicy" set to "dependent" in the Manifest.MF, +// in order to be able to retrieve the classes it loads +// +// This is the org.eclipse.papyrus.infra.tools class loader which is used for loading +// a class, instead of each caller's ClassLoader +// +// Plug-ins using this class should also either set their Buddy-policy to dependent or +// reexport the dependency to oep.infra.tools +public class ClassLoaderHelper { + + /** + * Usually, there are few classes with many different accesses. Using a cache, we can improve + * the performances between 10 and 20 times, with really few memory consumption + */ + private static final Map<String, Class<?>> classes = new HashMap<String, Class<?>>(); + + /** + * Loads the class matching the given className. Exceptions are caught and sent + * to the Logger. + * + * @param className + * The qualified name of the Class to load. + * @return + * The loaded Class, or null if an error occured + */ + public static Class<?> loadClass(String className) { + try { + Class<?> result = classes.get(className); + if (result == null) { + result = Activator.getDefault().getBundle().loadClass(className); + classes.put(className, result); + } + return result; + } catch (ClassNotFoundException ex) { + Activator.log.error(String.format("The class %s doesn't exist", className), ex); //$NON-NLS-1$ + } catch (NullPointerException ex) { + Activator.log.error("Cannot load class " + className, ex); //$NON-NLS-1$ + } + + return null; + } + + /** + * Loads and returns the class denoted by the given className. + * Checks that the loaded class is a subtype of the given Class. + * + * @param className + * The qualified name of the class to be loaded + * @param asSubClass + * The interface or class that the loaded class must implement or extend + * @return + * The loaded class, or null if the class doesn't exist or is invalid. + * In such a case, the exception is logged. + */ + public static <T> Class<? extends T> loadClass(String className, Class<T> asSubClass) { + Class<?> theClass = loadClass(className); + if (theClass == null) { + return null; + } + + try { + Class<? extends T> typedClass = theClass.asSubclass(asSubClass); + return typedClass; + } catch (ClassCastException ex) { + Activator.log.error(String.format("The class %1$s doesn't extend or implement %2$s", className, asSubClass.getName()), ex); //$NON-NLS-1$ + } + + return null; + } + + /** + * Creates a new instance of class denoted by the given className. + * Checks that the instantiated class is a subtype of the given class + * + * @param className + * The qualified name of the class to be instantiated + * @param asSubclass + * The interface or class that the loaded class must implement or extend + * @return + * An instance of the loaded class, or null if a valid instance + * cannot be created. In such a case, the exception is logged. + */ + public static <T> T newInstance(String className, Class<T> asSubclass) { + Class<? extends T> typedClass = loadClass(className, asSubclass); + if (typedClass == null) { + return null; + } + + return newInstance(typedClass); + } + + /** + * Returns a new Instance of the given class + * + * @param className + * The qualified name of the Class to instantiate + * @return + * A new instance of the given class, or null if the class couldn't be + * instantiated + */ + public static Object newInstance(String className) { + return newInstance(loadClass(className)); + } + + /** + * Returns a new Instance of the given class + * + * @param theClass + * The Class to instantiate + * @return + * A new instance of the given class, or null if the class couldn't be + * instantiated + */ + public static <T extends Object> T newInstance(Class<T> theClass) { + if (theClass == null) { + return null; + } + + try { + return theClass.newInstance(); + } catch (IllegalAccessException ex) { + Activator.log.error("Cannot find a valid public constructor for the class " + theClass.getName(), ex); //$NON-NLS-1$ + } catch (InstantiationException ex) { + Activator.log.error(String.format("The class %s cannot be instantiated.", theClass.getName()), ex); //$NON-NLS-1$ + } + + return null; + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/CompositeServiceTracker.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/CompositeServiceTracker.java new file mode 100644 index 00000000000..2dab30d9eba --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/CompositeServiceTracker.java @@ -0,0 +1,92 @@ +/***************************************************************************** + * Copyright (c) 2016 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms 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 - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.util; + +import java.lang.reflect.Array; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BinaryOperator; +import java.util.stream.Stream; + +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; + +/** + * A service tracker that provides a single service as a composite of + * registered service implementations. + */ +public class CompositeServiceTracker<S> extends ServiceTracker<S, S> { + private final AtomicReference<S> delegate = new AtomicReference<>(); + + private final Class<S> serviceType; + private final S identity; + private final BinaryOperator<S> composer; + + /** + * Initializes me with the bundle context in which I track resolver services, + * an identity service that generally performs trivially (e.g., no-ops or default behaviour), + * and an operator that composes two service instances. + * + * @param context + * the bundle context + * @param serviceType + * the service protocol type + * @param identity + * the basic no-op or default service instance + * @param composer + * an operator that composes two services instances into one + */ + public CompositeServiceTracker(BundleContext context, Class<S> serviceType, S identity, BinaryOperator<S> composer) { + super(context, serviceType, null); + + this.serviceType = serviceType; + this.identity = identity; + this.composer = composer; + } + + @Override + public final S getService() { + S result = this.delegate.get(); + if (result == null) { + // Recompute + @SuppressWarnings("unchecked") + S[] services = (S[]) Array.newInstance(serviceType, getTrackingCount()); + result = Stream.of(getServices(services)) + .filter(Objects::nonNull) // If the array has more slots than we have services + .reduce(identity, composer); + this.delegate.set(result); + } + + return result; + } + + @Override + public S addingService(ServiceReference<S> reference) { + S result = super.addingService(reference); + + // We will have to recompute our delegates + delegate.set(null); + + return result; + } + + @Override + public void removedService(ServiceReference<S> reference, S service) { + super.removedService(reference, service); + + // We will have to recompute our delegates + delegate.set(null); + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/CoreExecutors.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/CoreExecutors.java new file mode 100644 index 00000000000..14604007bc2 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/CoreExecutors.java @@ -0,0 +1,43 @@ +/***************************************************************************** + * Copyright (c) 2016 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms 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 - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.util; + +import java.util.concurrent.Executor; + +import org.eclipse.papyrus.infra.tools.Activator; + +/** + * A provider of {@link Executor}s offering various synchronous and asynchronous + * execution characteristics. + */ +public class CoreExecutors { + + // Not instantiable by clients + private CoreExecutors() { + super(); + } + + /** + * Obtains a service that posts tasks for asynchronous execution on the + * SWT display thread, if there is one. If there is no display, then + * a default background-thread executor is supplied by the Java platform. + * + * @return an executor service on the UI thread (if there is a UI). Never + * {@code null} and always the same instance. Clients may not shut down + * this executor; attempting to do so will result in {@link IllegalStateException}s + */ + public static IExecutorService getUIExecutorService() { + return Activator.getDefault().getUIExecutorService(); + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/FileUtils.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/FileUtils.java new file mode 100644 index 00000000000..ca804c1bc0f --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/FileUtils.java @@ -0,0 +1,129 @@ +/***************************************************************************** + * Copyright (c) 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 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * CEA LIST - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.papyrus.infra.tools.Activator; + +/** + * @author VL222926 + * + */ +public class FileUtils { + + public static final String PLATFORM_STRING = "platform"; //$NON-NLS-1$ + + public static final String PLUGIN_STRING = "plugin"; //$NON-NLS-1$ + + public static final String SLASH_STRING = "/"; //$NON-NLS-1$ + + public static final String COLON_STRING = ":"; //$NON-NLS-1$ + + public static final String DOT_STRING = ".";//$NON-NLS-1$ + + public static final String TEXT_EXTENSION = "txt";//$NON-NLS-1$ + + public static final String CSV_EXTENSIOn = "csv";//$NON-NLS-1$ + + public static final String UNDERSCORE = "_";//$NON-NLS-1$ + + public static final String LINE_SEPARATOR = "line.separator";//$NON-NLS-1$ + + private FileUtils() { + // to prevent instanciation + } + + /** + * return the system property line seperator + */ + public static final String getSystemPropertyLineSeparator(){ + return System.getProperty(LINE_SEPARATOR); + } + + /** + * this method read a file and return a string, the line separator used will we System.getProperty("line.separator") + * + * @param pluginName + * the name of the plugin owning the file + * @param filePath + * the path of the file + * @param fileNameWithExtension + * the name fo the file with its extension + * @return + */ + public static final String getStringFromPlatformFile(final String pluginName, final String filePath, final String fileNameWithExtension) { + return getStringFromPlatformFile(pluginName, filePath, fileNameWithExtension, System.getProperty("line.separator")); //$NON-NLS-1$ + } + + /** + * + * @param pluginName + * the name of the plugin owning the file + * @param filePath + * the path of the file + * @param fileNameWithExtension + * the name fo the file with its extension + * @param lineSeparator + * the line separator to use + * @return + */ + public static final String getStringFromPlatformFile(final String pluginName, final String filePath, final String fileNameWithExtension, final String lineSeparator) { + Assert.isNotNull(pluginName); + Assert.isNotNull(filePath); + Assert.isNotNull(fileNameWithExtension); + StringBuilder pathBuilder = new StringBuilder(); + pathBuilder.append(PLATFORM_STRING); + pathBuilder.append(COLON_STRING); + pathBuilder.append(SLASH_STRING); + pathBuilder.append(PLUGIN_STRING); + pathBuilder.append(SLASH_STRING); + pathBuilder.append(pluginName); + if (!filePath.startsWith(SLASH_STRING)) { + pathBuilder.append(SLASH_STRING); + } + pathBuilder.append(filePath); + if (!filePath.endsWith(SLASH_STRING)) { + pathBuilder.append(SLASH_STRING); + } + pathBuilder.append(fileNameWithExtension); + StringBuilder builder = new StringBuilder(); + URL url; + try { + url = new URL(pathBuilder.toString()); + InputStream inputStream = url.openConnection().getInputStream(); + BufferedReader in = new BufferedReader(new InputStreamReader(inputStream)); + String inputLine = in.readLine(); + + while (inputLine != null) { + builder.append(inputLine); + inputLine = in.readLine(); + if (inputLine != null) { + builder.append(lineSeparator); // $NON-NLS-1$ + } + } + + in.close(); + + } catch (IOException e) { + Activator.log.error(e); + } + return builder.toString(); + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/IExecutorService.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/IExecutorService.java new file mode 100644 index 00000000000..e816ade859c --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/IExecutorService.java @@ -0,0 +1,107 @@ +/***************************************************************************** + * Copyright (c) 2016 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms 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 - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.util; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +/** + * A specialized {@link ExecutorService} that also provides for synchronous + * execution of tasks, with the possibility that these could be optimized + * where appropriate (such as in a UI-thread executor to use a {@code syncExec} + * call). + */ +public interface IExecutorService extends ExecutorService { + /** + * Synchronously executes a {code task}. The task will run on the + * appropriate executor thread, as usual, but control will return + * to the caller only when its execution is complete. + * + * @param task + * the task to execute + * + * @throws InterruptedException + * on interruption, as per {@link Future#get()} + * @throws ExecutionException + * on failed execution, as per {@link Future#get()} + */ + void syncExec(Runnable task) throws InterruptedException, ExecutionException; + + /** + * Synchronously invokes a {code callable}. The callable will run on the + * appropriate executor thread, as usual, but control will return + * to the caller only when its execution is complete. + * + * @param callable + * the task to execute + * + * @throws InterruptedException + * on interruption, as per {@link Future#get()} + * @throws ExecutionException + * on failed execution, as per {@link Future#get()} + */ + <V> V syncCall(Callable<V> callable) throws InterruptedException, ExecutionException; + + /** + * Submits a {@code task} with support for progress reporting. + * + * @param task + * the progress-metered task to execute + * + * @return a future of undefined type that can be used, for example, to wait for the {@code task} to complete + */ + Future<?> submit(IProgressRunnable task); + + /** + * Submits a {@code callable} with support for progress reporting. + * + * @param task + * the progress-metered task to execute + * + * @return the future result of the {@code callable} + */ + <V> Future<V> submit(IProgressCallable<V> callable); + + /** + * Synchronously executes a progress-monitored {code task}. The task will run on the + * appropriate executor thread, as usual, but control will return + * to the caller only when its execution is complete. + * + * @param task + * the task to execute + * + * @throws InterruptedException + * on interruption, as per {@link Future#get()} + * @throws ExecutionException + * on failed execution, as per {@link Future#get()} + */ + void syncExec(IProgressRunnable task) throws InterruptedException, ExecutionException; + + /** + * Synchronously invokes a progress-monitored {code callable}. The callable will run on the + * appropriate executor thread, as usual, but control will return + * to the caller only when its execution is complete. + * + * @param callable + * the task to execute + * + * @throws InterruptedException + * on interruption, as per {@link Future#get()} + * @throws ExecutionException + * on failed execution, as per {@link Future#get()} + */ + <V> V syncCall(IProgressCallable<V> callable) throws InterruptedException, ExecutionException; +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/IProgressCallable.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/IProgressCallable.java new file mode 100644 index 00000000000..ff2601ca8ad --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/IProgressCallable.java @@ -0,0 +1,68 @@ +/***************************************************************************** + * Copyright (c) 2016 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms 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 - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.util; + +import java.util.concurrent.Callable; + +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * An analogue of the Eclipse JFace {@code IRunnableWithProgress} interface, + * a protocol for executable computations that can report measurable progress. + * Implementations of the {@link IExecutorService} can supply suitable progress + * reporting to these callables. + * + * @see IExecutorService + */ +@FunctionalInterface +public interface IProgressCallable<V> { + /** + * Computes a result. + * + * @param monitor + * for reporting of progress of the task + * + * @return the result of the computation + * + * @throws Exception + * if the computation fails unrecoverably + */ + V call(IProgressMonitor monitor) throws Exception; + + /** + * Adapts a plain Java {@code runnable} task to a progress-runnable task. + * + * @param label + * an user-presentable label for the task + * @param runnable + * a plain runnable + * + * @return a progress runnable decorating the plain {@code runnable} + */ + static <V> IProgressCallable<V> convert(String label, Callable<V> callable) { + return progress -> { + if (progress != null) { + progress.beginTask(label, IProgressMonitor.UNKNOWN); + } + + try { + return callable.call(); + } finally { + if (progress != null) { + progress.done(); + } + } + }; + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/IProgressRunnable.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/IProgressRunnable.java new file mode 100644 index 00000000000..124099a64fc --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/IProgressRunnable.java @@ -0,0 +1,61 @@ +/***************************************************************************** + * Copyright (c) 2016 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms 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 - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.util; + +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * An analogue of the Eclipse JFace {@code IRunnableWithProgress} interface, + * a protocol for executable tasks that can report measurable progress. + * Implementations of the {@link IExecutorService} can supply suitable progress + * reporting to these runnables. + * + * @see IExecutorService + */ +@FunctionalInterface +public interface IProgressRunnable { + /** + * Executes the task. + * + * @param monitor + * for reporting of progress of the task + */ + void run(IProgressMonitor monitor); + + /** + * Adapts a plain Java {@code runnable} task to a progress-runnable task. + * + * @param label + * an user-presentable label for the task + * @param runnable + * a plain runnable + * + * @return a progress runnable decorating the plain {@code runnable} + */ + static IProgressRunnable convert(String label, Runnable runnable) { + return progress -> { + if (progress != null) { + progress.beginTask(label, IProgressMonitor.UNKNOWN); + } + + try { + runnable.run(); + } finally { + if (progress != null) { + progress.done(); + } + } + }; + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/IntegerAndSpreadsheetNumberConverter.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/IntegerAndSpreadsheetNumberConverter.java new file mode 100644 index 00000000000..3c4f5f265c6 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/IntegerAndSpreadsheetNumberConverter.java @@ -0,0 +1,90 @@ +/***************************************************************************** + * Copyright (c) 2012 CEA LIST. + * + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Vincent Lorenzo (CEA LIST) vincent.lorenzo@cea.fr - Initial API and implementation + * + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.util; + +/** + * + * This class allows to converter an int into a String like a spreadsheet numerotation and vice-versa. Fox example : + * <ul> + * <li>1 <-> A</li> + * <li>26 <-> Z</li> + * <li>27 <-> AA</li> + * <li>28<-> AB</li> + * </ul> + * + * + * + * + */ +// adapted code from http://www.developpez.net/forums/d1197058/dotnet/general-dotnet/contribuez/extensions-types-int-string-conversion-format-colonne-excel/ +public class IntegerAndSpreadsheetNumberConverter { + + /** + * + * Constructor. + * + */ + private IntegerAndSpreadsheetNumberConverter() { + // to prevent instanciation + } + + /** + * + * @param number + * an integer + * @return + * the string representing this integer in a spreedsheet + */ + public static String toString(int number) { + if (number <= 0) { + throw new NumberFormatException(); + } + int tmp = number; + String string = ""; //$NON-NLS-1$ + while (tmp > 0) { + final int r = (tmp - 1) % 26; + string = (char) ('A' + r) + string; + tmp = (tmp - r) / 26; + } + + return string; + } + + /** + * + * @unused + * @param string + * a string + * @return + * the number corresponding to the string + */ + public static int toInt(String string) { + if (string == null || string.length() == 0) { + throw new NumberFormatException(); + } + string = string.toUpperCase(); + int multiplier = 1; + int columnNumber = 0; + for (int i = string.length() - 1; i >= 0; i--) { + final char c = string.charAt(i); + if (c < 'A' || c > 'Z') { + throw new NumberFormatException(); + } + final int value = (c - 'A' + 1) * multiplier; + columnNumber += value; + multiplier *= 26; + } + return columnNumber; + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/Iterables2.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/Iterables2.java new file mode 100644 index 00000000000..826a0fbf094 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/Iterables2.java @@ -0,0 +1,66 @@ +/***************************************************************************** + * Copyright (c) 2015 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms 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 - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.util; + +import java.util.Comparator; +import java.util.List; +import java.util.ListIterator; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +/** + * Utilities for working with iterables that are not provided by {@linkplain Iterables Guava}. + */ +public class Iterables2 { + /** + * Not instantiable by clients. + */ + private Iterables2() { + super(); + } + + /** + * Brute-force topological sort of objects by a partial ordering relation. + * + * @param items + * the items to be sorted + * @param partOrder + * a partial ordering relation on the items + * @return the topologically sorted {@code items} as a new mutable list + */ + public static <T> List<T> topoSort(Iterable<T> items, Comparator<? super T> partOrder) { + List<T> unsorted = Lists.newLinkedList(items); + List<T> result = Lists.newArrayListWithCapacity(unsorted.size()); + + while (!unsorted.isEmpty()) { + T min = unsorted.remove(0); + + for (ListIterator<T> iter = unsorted.listIterator(); iter.hasNext();) { + T next = iter.next(); + if (partOrder.compare(next, min) < 0) { + // Found a new minimum. Put the old one back for next pass + iter.set(min); + min = next; + } + } + + // Whatever's the minimum now is the next in our partial ordering + result.add(min); + } + + return result; + } + +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/Iterators2.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/Iterators2.java new file mode 100644 index 00000000000..bd10ef4e9d7 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/Iterators2.java @@ -0,0 +1,59 @@ +/***************************************************************************** + * Copyright (c) 2014 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms 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 - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.util; + +import java.util.Iterator; + +import org.eclipse.emf.common.util.TreeIterator; + +import com.google.common.collect.AbstractIterator; +import com.google.common.collect.Iterators; + +/** + * Utilities for working with iterators that are not provided by {@linkplain Iterators Guava}. + */ +public class Iterators2 { + /** + * Not instantiable by clients. + */ + private Iterators2() { + super(); + } + + /** + * Filters an EMF tree iterator for elements of a particular {@code type}. + * + * @param treeIterator + * the tree iterator to filter + * @param type + * the type of elements to include in the filtered tree iterator + * @return the filtered tree iterator + */ + public static <T> TreeIterator<T> filter(final TreeIterator<?> treeIterator, final Class<T> type) { + class FilteredTreeIterator extends AbstractIterator<T> implements TreeIterator<T> { + final Iterator<T> delegate = Iterators.filter(treeIterator, type); + + @Override + protected T computeNext() { + return delegate.hasNext() ? delegate.next() : endOfData(); + } + + public void prune() { + treeIterator.prune(); + } + } + + return new FilteredTreeIterator(); + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/ListHelper.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/ListHelper.java new file mode 100644 index 00000000000..382c964daa5 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/ListHelper.java @@ -0,0 +1,81 @@ +/***************************************************************************** + * Copyright (c) 2012 CEA LIST. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.util; + +import java.util.ArrayList; +import java.util.List; + +public class ListHelper { + + /** + * Converts an array to a List + * + * This method is similar to Arrays.asList, except that it returns + * a writeable list + * + * @param array + * The array to transform into a List + * @return + * A List containing the same elements as the array + */ + public static <T> List<T> asList(T[] array) { + if (array == null) { + return new ArrayList<T>(); + } + + List<T> result = new ArrayList<T>(array.length); + for (T t : array) { + result.add(t); + } + return result; + } + + /** + * Invokes the toString() method recursively on this list's elements. + * The values are separated by ", " + * + * @param list + * The list whose string representation to return + * @return + * + * @see #deepToString(List, String) + */ + public static String deepToString(List<?> list) { + return deepToString(list, ", "); + } + + /** + * Invokes the toString() method recursively on this list's elements. + * The values are separated by the given separator + * + * @param list + * The list whose string representation to return + * @param separator + * The string to insert between each element's string representation + * @return + * + * @see #deepToString(List) + */ + public static String deepToString(List<?> list, String separator) { + boolean firstElement = true; + String result = ""; + for (Object item : list) { + if (firstElement) { + firstElement = false; + } else { + result += separator; + } + result += item == null ? null : item.toString(); + } + return result; + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/PlatformHelper.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/PlatformHelper.java new file mode 100644 index 00000000000..316b4683f9a --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/PlatformHelper.java @@ -0,0 +1,141 @@ +/***************************************************************************** + * Copyright (c) 2013, 2015 CEA LIST, Christian W. Damus, and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * CEA LIST - Initial API and implementation + * Christian W. Damus - bug 479999 + * Christian W. Damus - bug 469188 + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.util; + +import static com.google.common.collect.Iterables.filter; +import static com.google.common.collect.Iterables.find; + +import java.util.function.Supplier; + +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.Platform; +import org.eclipse.emf.common.notify.Notifier; + +import com.google.common.base.Predicates; + + +public class PlatformHelper { + + /** + * Attempt to get an adapter of the specified target {@code type} from an {@code object} + * by any means available. + * + * @param object + * an object to adapt + * @param type + * the type of adapter to get + * + * @return the best-effort adapter of the given {@code type} or {@code null} if no + * adapter is available + */ + public static <T> T getAdapter(Object object, Class<T> type) { + T result = null; + + // Don't provide adapters for null + if (object != null) { + if (type.isInstance(object)) { + result = type.cast(object); + } else if (object instanceof IAdaptable) { + result = getIntrinsicAdapter((IAdaptable) object, type); + } + + if (result == null) { + result = getExtrinsicAdapter(object, type); + + if ((result == null) && (object instanceof Notifier)) { + result = getEMFAdapter((Notifier) object, type); + } + } + } + + return result; + } + + private static <T> T getIntrinsicAdapter(IAdaptable adaptable, Class<T> type) { + T result = null; + + Object attempt = adaptable.getAdapter(type); + if (type.isInstance(attempt)) { + result = type.cast(attempt); + } + + return result; + } + + private static <T> T getExtrinsicAdapter(Object object, Class<T> type) { + T result = null; + + Object attempt = Platform.getAdapterManager().getAdapter(object, type); + if (type.isInstance(attempt)) { + result = type.cast(attempt); + } + + return result; + } + + private static <T> T getEMFAdapter(Notifier notifier, Class<T> type) { + return find(filter(notifier.eAdapters(), type), Predicates.alwaysTrue(), null); + } + + /** + * Get an adapter of the specified target {@code type} from an {@code object} by any means available. + * + * @param object + * an object to adapt. May be {@code null}, in which case the {@code defaultAdapter} is returned + * @param type + * the type of adapter to get + * @param defaultAdapter + * a default adapter to return if none can be obtained (may be {@code null} + * + * @return the best-effort adapter of the given {@code type}, else the {@code defaultAdapter} + */ + public static <T> T getAdapter(Object object, Class<T> type, T defaultAdapter) { + T result = defaultAdapter; + + if (object != null) { + T adapter = getAdapter(object, type); + if (adapter != null) { + result = adapter; + } + } + + return result; + } + + /** + * Get an adapter of the specified target {@code type} from an {@code object} by any means available. + * + * @param object + * an object to adapt. May be {@code null}, in which case the {@code defaultAdapter} is returned + * @param type + * the type of adapter to get + * @param defaultSupplier + * a supplier to consult for a default adapter in the case that none can be + * obtained by the usual means (may be {@code null} + * + * @return the best-effort adapter of the given {@code type}, else the {@code defaultAdapter} + */ + public static <T> T getAdapter(Object object, Class<T> type, Supplier<T> defaultAdapter) { + T result = null; + + if (object != null) { + T adapter = getAdapter(object, type); + if (adapter != null) { + result = adapter; + } + } + + return (result != null) ? result : defaultAdapter.get(); + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/ReferenceCounted.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/ReferenceCounted.java new file mode 100644 index 00000000000..6c0444c3086 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/ReferenceCounted.java @@ -0,0 +1,175 @@ +/***************************************************************************** + * Copyright (c) 2014 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms 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 - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.util; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A convenient reference-counting utility with automatic disposal and asynchronous auto-disposal (intended + * for use with the UI event-loop executor service). Sub-classes leveraging the self-disposing behaviour of + * the self-owning instance must override the {@link #dispose()} method. + */ +public class ReferenceCounted<T> { + private final T owner; + private final AtomicInteger refCount = new AtomicInteger(); + private Runnable disposeAction; + private final ExecutorService autoReleaseExecutor; + + /** + * Initializes me with my owner for which I count references. + * <p> + * I do not support {@link #autoRelease()}. + * + * @param owner + * my owner + * @param disposeAction + * action to run when the reference count reaches zero to dispose my {@code owner} + * + * @throws IllegalArgumentException + * if the {@code disposeAction} is {@code null} because, really, + * why would you need reference counting for an object that doesn't need to be disposed? + */ + public ReferenceCounted(T owner, Runnable disposeAction) { + this(owner, null, disposeAction); + } + + /** + * Initializes me with my owner for which I count references. + * <p> + * I support {@link #autoRelease()} using the given executor. + * + * @param owner + * my owner + * @param autoReleaseExecutor + * the executor on which to schedule auto-release invocations + * @param disposeAction + * action to run when the reference count reaches zero to dispose my {@code owner} + * + * @throws IllegalArgumentException + * if the {@code disposeAction} is {@code null} because, really, + * why would you need reference counting for an object that doesn't need to be disposed? + */ + public ReferenceCounted(T owner, ExecutorService autoReleaseExecutor, Runnable disposeAction) { + super(); + + if (disposeAction == null) { + throw new IllegalArgumentException("null disposeAction"); //$NON-NLS-1$ + } + + this.owner = owner; + this.autoReleaseExecutor = autoReleaseExecutor; + this.disposeAction = disposeAction; + } + + /** + * Initializes me as a self-disposing reference-counted instance. This constructor is only suitable + * for chaining from subclasses. + * <p> + * I do not support {@link #autoRelease()}. + */ + protected ReferenceCounted() { + this((ExecutorService) null); + } + + /** + * Initializes me as a self-disposing reference-counted instance. This constructor is only suitable + * for chaining from subclasses. + * <p> + * I support {@link #autoRelease()} using the given executor. + * + * @param autoReleaseExecutor + * the executor on which to schedule auto-release invocations + */ + @SuppressWarnings("unchecked") + protected ReferenceCounted(ExecutorService autoReleaseExecutor) { + super(); + + this.owner = (T) this; + this.disposeAction = new SelfDisposeAction(); + this.autoReleaseExecutor = autoReleaseExecutor; + } + + /** + * Retains me, incrementing my retain count. The caller must eventually {@link #release()} me (even if via the {@link #autoRelease()} method) + * if I am to become disposable. + * + * @return my owner, for convenience of call chaining + */ + public final T retain() { + refCount.incrementAndGet(); + return owner; + } + + /** + * Releases me, decrementing my retain count. When my retain count reaches zero, I dispose myself using the + * configured dispose-action runnable. + */ + public final void release() { + if (refCount.decrementAndGet() <= 0) { + if (disposeAction != null) { + try { + disposeAction.run(); + } finally { + disposeAction = null; + } + } + } + } + + /** + * Automatically releases me some time in the future, as determined by the auto-release executor with which + * I was configured. A particularly convenient executor is one that posts an asynchronous execution on the + * display thread, to automatically dispose my owner (or me, as the case may be) when the display thread + * returns to the event loop. + * + * @return my owner, for convenience of call chaining + * + * @throws IllegalStateException + * if I have no auto-release executor + */ + public final T autoRelease() { + if (autoReleaseExecutor == null) { + throw new IllegalStateException("no auto-release executor available"); //$NON-NLS-1$ + } + + // Don't submit the dispose action because we could still be retained! + autoReleaseExecutor.execute(new Runnable() { + + public void run() { + release(); + } + }); + return owner; + } + + /** + * For classes that extend the {@code ReferenceCount}, this must be overridden to implement disposal. + * The default implementation throws {@link UnsupportedOperationException} to ensure that subclasses + * override it. + */ + protected void dispose() { + throw new UnsupportedOperationException("dispose is unimplemented"); //$NON-NLS-1$ + } + + // + // Nested types + // + + private class SelfDisposeAction implements Runnable { + public void run() { + dispose(); + } + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/ReflectHelper.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/ReflectHelper.java new file mode 100644 index 00000000000..da91097bbc2 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/ReflectHelper.java @@ -0,0 +1,55 @@ +/***************************************************************************** + * Copyright (c) 2012 CEA LIST. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Vincent Lorenzo (CEA LIST) vincent.lorenzo@cea.fr - Initial API and implementation + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.util; + +import java.lang.reflect.Method; + +/** + * + * This helper provides methods to get methods reflectively + * It is not the better way to access to method, but sometimes it can be interested to avoid to duplicate + * lot of code + * + */ +public class ReflectHelper { + + /** + * + * Should not be instantiated + * + */ + private ReflectHelper() { + // prevents instantiation + } + + /** + * Warning : each call of this method should be tested with a JUnit test, in order to know + * when the API has changed + * + * @param aClass + * a class + * @param methodName + * the name of the method to find + * @param parameterTypes + * an array owning the type of the parameters of the called method + * @return + * the wanted method + * @throws NoSuchMethodException + * @throws SecurityException + */ + public static Method getMethod(final Class<?> aClass, final String methodName, Class<?>[] parameterTypes) throws SecurityException, NoSuchMethodException { + Method m = null; + m = aClass.getDeclaredMethod(methodName, parameterTypes); + m.setAccessible(true); + return m; + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/StringHelper.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/StringHelper.java new file mode 100644 index 00000000000..9513da778c5 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/StringHelper.java @@ -0,0 +1,174 @@ +/***************************************************************************** + * Copyright (c) 2008-2013 CEA LIST. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.util; + +/** + * A library of static helpers for string-related operations + */ +public class StringHelper { + + /** + * Compares two strings. Two Strings are equal if they are both null, + * or if s1.equals(s2) + * + * @param s1 + * @param s2 + * @return + */ + public static boolean equals(String s1, String s2) { + if (s1 == s2) { + return true; + } + + if (s1 == null) { + return false; + } + + return s1.equals(s2); + } + + /** + * Converts a camelCase name to a human-readable Label + * + * Example: aUMLElement -> A UML element + * + * @param camelCaseName + * @return + * A formatted version of the given variable name + */ + public static String camelCaseToLabel(String camelCaseName) { + // "CamelCase" to "Natural case" + String formattedValue = camelCaseName; + + // replace fooBar by foo Bar + formattedValue = formattedValue.replaceAll("([a-z])([A-Z])", "$1 $2"); //$NON-NLS-1$ //$NON-NLS-2$ + + // replace FOOAndBar by FOO And Bar + formattedValue = formattedValue.replaceAll("([A-Z]+)([A-Z])([a-z])", "$1 $2$3"); //$NON-NLS-1$ //$NON-NLS-2$ + + // Capitalize the first word and lower the other ones : foo Bar -> Foo bar + // Keep the upper case for acronyms FOO Bar -> FOO bar + String[] words = formattedValue.split("\\s+"); //$NON-NLS-1$ + formattedValue = firstToUpper(words[0]); + for (int i = 1; i < words.length; i++) { + formattedValue += " "; //$NON-NLS-1$ + if (words[i].matches("^[A-Z]{2,}")) { //$NON-NLS-1$ + formattedValue += words[i]; + } else { + formattedValue += firstToLower(words[i]); + } + } + + //Activator.log.debug("\"" + formattedValue + "\""); //$NON-NLS-1$ //$NON-NLS-2$ + return formattedValue; + } + + /** + * @param source + * @return + * the given String with the first letter capitalized + */ + public static String firstToUpper(String source) { + if (source.length() == 0) { + return source; + } + return source.substring(0, 1).toUpperCase() + source.substring(1); + } + + /** + * @param source + * @return + * the given String with the first letter lowered + */ + public static String firstToLower(String source) { + if (source.length() == 0) { + return source; + } + return source.substring(0, 1).toLowerCase() + source.substring(1); + } + + /** + * Returns the same string, except for "null" which is converted to the empty string + * + * @param str + * @return + */ + public static String trimToEmpty(String str) { + return str == null ? "" : str; //$NON-NLS-1$ + } + + + /* + * + * The following methods have been copied from UML2Util (org.eclipse.uml2.common.util/UML2Util) + */ + + + /** + * Obtains a valid Java identifier based on the specified name. + * + * @param name + * The name from which to obtain a valid identifier. + * @return A valid (Java) identifier. + */ + public static String toJavaIdentifier(String label) { + return getValidJavaIdentifier(label, new StringBuffer()).toString(); + } + + /** + * Appends a valid Java identifier based on the specified name to the + * specified buffer. + * + * @param name + * The name from which to obtain the valid identifier. + * @param validJavaIdentifier + * The buffer to which to append the valid identifier. + * @return The buffer. + */ + protected static StringBuffer getValidJavaIdentifier(String name, StringBuffer validJavaIdentifier) { + + if (isEmpty(name)) { + validJavaIdentifier.append('_'); + } else { + char char_0 = name.charAt(0); + + if (Character.isJavaIdentifierStart(char_0)) { + validJavaIdentifier.append(char_0); + } else { + validJavaIdentifier.append('_'); + + if (Character.isJavaIdentifierPart(char_0)) { + validJavaIdentifier.append(char_0); + } + } + + for (int i = 1; i < name.length(); i++) { + char char_i = name.charAt(i); + + if (Character.isJavaIdentifierPart(char_i)) { + validJavaIdentifier.append(char_i); + } + } + } + + return validJavaIdentifier; + } + + /** + * Determines whether the specified string is empty, i.e. is <code>null</code> or has a length of zero. + * + * @param string + * The string in question. + * @return <code>true</code> if the string is empty; <code>false</code> otherwise. + */ + public static boolean isEmpty(String string) { + return string == null || string.length() == 0; + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/Suppliers2.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/Suppliers2.java new file mode 100644 index 00000000000..4dbfd0ecebf --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/Suppliers2.java @@ -0,0 +1,132 @@ +/***************************************************************************** + * Copyright (c) 2015 Christian W. Damus and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms 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 - Initial API and implementation + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.util; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.papyrus.infra.tools.Activator; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Utilities for working with suppliers that are not provided by {@linkplain Suppliers Guava}. + */ +public class Suppliers2 { + /** + * Not instantiable by clients. + */ + private Suppliers2() { + super(); + } + + /** + * Obtain a supplier that eventually provides the value of a {@code future} result. + * Accessing the supplier returns {@code null} until the {@code future}'s value is + * available, after which it returns that value. If the future completes with an + * exception, then the supplier will always provide {@code null} and the exception + * will not be accessible. + * + * @param future + * a future result + * @return a supplier of the eventual value of the {@code future} + */ + public static <V> Supplier<V> eventualSupplier(Future<V> future) { + return eventualSupplier(future, null); + } + + /** + * Obtain a supplier that eventually provides the value of a {@code future} result. + * Accessing the supplier returns the given {@code defaultValue} until the {@code future}'s value is + * available, after which it returns that value. If the future completes with an + * exception, then the supplier will always provide the default and the exception + * will not be accessible. + * + * @param future + * a future result + * @param defaultValue + * the default value to provide until the future is done + * @return a supplier of the eventual value of the {@code future} + */ + public static <V> Supplier<V> eventualSupplier(Future<V> future, V defaultValue) { + return (future instanceof ListenableFuture<?>) + ? new ListenableFutureSupplier<V>((ListenableFuture<V>) future, defaultValue) + : new FutureSupplier<V>(future, defaultValue); + } + + // + // Nested types + // + + private static class FutureSupplier<V> implements Supplier<V> { + private Future<V> future; + private V value; + + FutureSupplier(Future<V> future, V defaultValue) { + this.future = future; + this.value = defaultValue; + } + + public V get() { + if ((value == null) && (future != null) && future.isDone()) { + try { + if (future.isCancelled()) { + Activator.log.warn("Future of EventualSupplier was cancelled: " + future); //$NON-NLS-1$ + } else { + value = future.get(); + } + } catch (InterruptedException e) { + // Shouldn't happen on a done future + Activator.log.error("Interrupted on a done future.", e); //$NON-NLS-1$ + } catch (ExecutionException e) { + // Normal case. There will never be a value + Activator.log.error("Future execution failed", e.getCause()); + } finally { + future = null; + } + } + + return value; + } + } + + private static class ListenableFutureSupplier<V> implements Supplier<V> { + private AtomicReference<V> value; + + ListenableFutureSupplier(ListenableFuture<V> future, V defaultValue) { + value = new AtomicReference<V>(defaultValue); + + Futures.addCallback(future, new FutureCallback<V>() { + public void onSuccess(V result) { + value.set(result); + } + + public void onFailure(Throwable t) { + // Normal case. There will never be a value + Activator.log.error("Future execution failed", t); + } + }); + } + + public V get() { + return value.get(); + } + } + +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/TypeUtils.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/TypeUtils.java new file mode 100644 index 00000000000..05439dcf825 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/TypeUtils.java @@ -0,0 +1,185 @@ +/***************************************************************************** + * Copyright (c) 2014, 2015 CEA LIST, Christian W. Damus, and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * CEA LIST - Initial API and implementation + * Christian W. Damus - bug 433206 + * + *****************************************************************************/ + +package org.eclipse.papyrus.infra.tools.util; + +import java.math.BigDecimal; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author VL222926 + * + */ +public class TypeUtils { + + /** + * Constructor. + * + */ + private TypeUtils() { + // to prevent instanciation + } + + /** + * + * @param str + * a string representing a boolean + * @return + * <code>true</code> if the string represents a valid boolean + */ + public static final boolean isBooleanValue(String str) { + return "true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * + * @param str + * a string representing a boolean + * @return + * <code>true</code> if the string represents a valid boolean + */ + public static final boolean isIntegerValue(String str) { + /** the pattern that checks visual ids are valid integers */ + Pattern digit = Pattern.compile("-?\\d+"); //$NON-NLS-1$ + boolean result = false; + Matcher matcher = digit.matcher(str); + if (matcher != null) { + result = matcher.matches(); + } + return result; + } + + /** + * + * @param str + * a string + * @return + * <code>true</code> if the string represents a double + */ + public static final boolean isDoubleValue(String str) { + try { + new BigDecimal(str); + } catch (Exception e) { + return false; + } + return true; + } + + /** + * + * @param str + * a string + * @return + * <code>true</code> if the string represents a double + */ + public static final boolean isNaturalValue(String str) { + boolean res = isIntegerValue(str); + if (res) { + int tmp = Integer.parseInt(str); + return tmp >= 0; + } + return res; + } + + /** + * + * @param object + * an object + * @return + * <code>true</code> if the object represents a numeric value + */ + public static final boolean isNumericValue(Object object) { + if (object instanceof String) { + try { + new BigDecimal((String) object); + } catch (Exception e) { + return false; + } + return true; + } + if (object instanceof Integer || object instanceof Double || object instanceof Float) { + return true; + } + return false; + + } + + /** + * Attempts to cast an {@code object} as the required {@code type}. + * + * @param object + * an object to cast + * @param type + * the type to cast it to + * + * @return the {@code object} or {@code null} if it is not of the required {@code type} + */ + public static <T> T as(Object object, Class<T> type) { + T result = null; + + if (type.isInstance(object)) { + result = type.cast(object); + } + + return result; + } + + /** + * Attempts to cast an {@code object} as an instance of the type implied by the given {@code default_}. + * + * @param object + * an object to cast + * @param default_ + * the default value to return if it is not of the required type. May not be {@code null} + * + * @return the {@code object} or {@code default_} if it is not of the required type + * + * @throws NullPointerException + * if {@code default_} is {@code null} + */ + @SuppressWarnings("unchecked") + public static <T> T as(Object object, T default_) { + T result = default_; + + if (default_.getClass().isInstance(object)) { + result = (T) object; + } + + return result; + } + + /** + * Attempts to cast the object at an {@code index} of an {@code array} as the required {@code type}. + * + * @param array + * an array of objects + * @param index + * the position of an object in the {@code array} + * @param type + * the type to cast it to + * + * @return the {@code index}-th object in the {@code array} or {@code null} if it is not of the required {@code type} or the {@code array} has no such {@code index} + */ + public static <T> T as(Object[] array, int index, Class<T> type) { + Object object = ((index >= 0) && (index < array.length)) ? array[index] : null; + T result = null; + + if (type.isInstance(object)) { + result = type.cast(object); + } + + return result; + } +} diff --git a/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/TypesConstants.java b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/TypesConstants.java new file mode 100644 index 00000000000..b4c256c14a5 --- /dev/null +++ b/plugins/infra/core/org.eclipse.papyrus.infra.tools/src/org/eclipse/papyrus/infra/tools/util/TypesConstants.java @@ -0,0 +1,38 @@ +/***************************************************************************** + * Copyright (c) 2013 CEA LIST. + * + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Vincent Lorenzo (CEA LIST) vincent.lorenzo@cea.fr - Initial API and implementation + * + *****************************************************************************/ +package org.eclipse.papyrus.infra.tools.util; + +/** + * + * This class provides some constants used to identify java types + * + * @author vl222926 + * + */ +public class TypesConstants { + + + private TypesConstants() { + // to prevent instanciation + } + + public static final String STRING = "String"; //$NON-NLS-1$ + + public static final String BOOLEAN = "Boolean"; //$NON-NLS-1$ + + public static final String INTEGER = "Integer"; //$NON-NLS-1$ + + public static final String DOUBLE = "Double"; //$NON-NLS-1$ + +} |