| /******************************************************************************* |
| * Copyright (c) 2008-2011 Chair for Applied Software Engineering, |
| * Technische Universitaet Muenchen. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Otto von Wesendonk - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.emf.emfstore.internal.common.observer; |
| |
| 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.Collections; |
| import java.util.Comparator; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.emf.emfstore.common.ESObserver; |
| import org.eclipse.emf.emfstore.common.extensionpoint.ESExtensionElement; |
| import org.eclipse.emf.emfstore.common.extensionpoint.ESExtensionPoint; |
| import org.eclipse.emf.emfstore.common.extensionpoint.ESExtensionPointException; |
| import org.eclipse.emf.emfstore.internal.common.Activator; |
| import org.eclipse.emf.emfstore.internal.common.observer.ObserverCall.Result; |
| |
| /** |
| * This is a universal observer bus. This class follows the publish/subscribe pattern, it is a central dispatcher for |
| * observers and makes use of generics in order to allow type safety. It can be used as singleton or be injected through |
| * DI. |
| * Observers have to implement the {@link ESObserver} interface, which is only used as a marker. Future use of |
| * Annotations is possible. |
| * by using {@link #notify(Class)} (e.g. <code>bus.notify(MyObserver.class).myObserverMethod()</code>) all registered |
| * Observers are notified. |
| * This is implemented by using the java {@link Proxy} class. By calling {@link #notify(Class)} a proxy is returned, |
| * which then calls all registered observers. |
| * The proxy can also be casted into {@link ObserverCall}, which allows to access all results by the different |
| * observers. |
| * |
| * |
| * Example code: |
| * |
| * <pre> |
| * // A is ESObserver |
| * A a = new A() { |
| * |
| * public void foo() { |
| * System.out.println("A says: go!"); |
| * } |
| * }; |
| * |
| * // B extends A and is ESObserver |
| * B b = new B() { |
| * |
| * public void say(String ja) { |
| * System.out.println("B says: " + ja); |
| * } |
| * |
| * public void foo() { |
| * System.out.println("B says: h??"); |
| * } |
| * }; |
| * |
| * // B is registered first |
| * ObserverBus.register(b); |
| * ObserverBus.register(a); |
| * |
| * ObserverBus.notify(A.class).foo(); |
| * |
| * ObserverBus.notify(B.class).say("w00t"); |
| * |
| * // Output: |
| * |
| * // B says: h?? |
| * // A says: go! |
| * // |
| * // B says: w00t |
| * |
| * </pre> |
| * |
| * @author wesendon |
| */ |
| public class ObserverBus { |
| |
| private static final String EXTENSION_POINT_ID = "org.eclipse.emf.emfstore.common.observerBusExtensionPointRegistration"; //$NON-NLS-1$ |
| private final transient Map<Class<? extends ESObserver>, List<ESObserver>> observerMap; |
| |
| /** |
| * Default constructor. |
| */ |
| public ObserverBus() { |
| observerMap = new LinkedHashMap<Class<? extends ESObserver>, List<ESObserver>>(); |
| collectionExtensionPoints(); |
| } |
| |
| /** |
| * This method allows you to notify all observers. |
| * |
| * @param <T> class of observer |
| * @param clazz class of observer |
| * @return call object |
| */ |
| public <T extends ESObserver> T notify(final Class<T> clazz) { |
| return notify(clazz, false); |
| } |
| |
| /** |
| * This method allows you to notify all observers. |
| * |
| * @param <T> class of observer |
| * @param clazz class of observer |
| * @param prioritized sort observer after {@link ESPrioritizedObserver} |
| * |
| * @return call object |
| */ |
| public <T extends ESObserver> T notify(final Class<T> clazz, final boolean prioritized) { |
| if (clazz == null) { |
| return null; |
| } |
| return createProxy(clazz, prioritized); |
| } |
| |
| /** |
| * Registers an observer for all observer interfaces implemented by the object or its super classes. |
| * |
| * @param observer observer object |
| */ |
| public void register(final ESObserver observer) { |
| register(observer, getObserverInterfaces(observer)); |
| } |
| |
| /** |
| * Registers an observer for the specified observer interfaces. |
| * |
| * @param observer observer object |
| * @param classes set of classes |
| */ |
| public void register(final ESObserver observer, final Class<? extends ESObserver>... classes) { |
| for (final Class<? extends ESObserver> iface : classes) { |
| if (iface.isInstance(observer)) { |
| addObserver(observer, iface); |
| } |
| } |
| } |
| |
| /** |
| * Unregisters an observer for all observer interfaces implemented by the object or its super classes. |
| * |
| * @param observer observer object |
| */ |
| public void unregister(final ESObserver observer) { |
| unregister(observer, getObserverInterfaces(observer)); |
| } |
| |
| /** |
| * Unregisters an observer for the specified observer interfaces. |
| * |
| * @param observer observer object |
| * @param classes set of classes |
| */ |
| public void unregister(final ESObserver observer, final Class<? extends ESObserver>... classes) { |
| for (final Class<? extends ESObserver> iface : classes) { |
| if (iface.isInstance(observer)) { |
| removeObserver(observer, iface); |
| } |
| } |
| } |
| |
| private void addObserver(ESObserver observer, final Class<? extends ESObserver> iface) { |
| final List<ESObserver> observers = initObserverList(iface); |
| observers.add(observer); |
| } |
| |
| private void removeObserver(ESObserver observer, Class<? extends ESObserver> iface) { |
| final List<ESObserver> observers = initObserverList(iface); |
| observers.remove(observer); |
| } |
| |
| private List<ESObserver> initObserverList(Class<? extends ESObserver> iface) { |
| List<ESObserver> list = observerMap.get(iface); |
| if (list == null) { |
| list = new ArrayList<ESObserver>(); |
| observerMap.put(iface, list); |
| } |
| return list; |
| } |
| |
| private List<ESObserver> getObserverByClass(Class<? extends ESObserver> clazz) { |
| List<ESObserver> list = observerMap.get(clazz); |
| if (list == null) { |
| list = Collections.emptyList(); |
| } |
| return new ArrayList<ESObserver>(list); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private <T extends ESObserver> T createProxy(Class<T> clazz, boolean prioritized) { |
| final ProxyHandler handler = new ProxyHandler((Class<ESObserver>) clazz, prioritized); |
| return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz, ObserverCall.class }, handler); |
| } |
| |
| private static void logExceptions(List<Result> results) { |
| for (final Result result : results) { |
| final Throwable throwable = result.getException(); |
| if (throwable != null) { |
| Activator.getDefault().getLog().log( |
| new Status( |
| IStatus.ERROR, "org.eclipse.emf.emfstore.common", //$NON-NLS-1$ |
| throwable.getMessage(), |
| throwable)); |
| } |
| } |
| } |
| |
| /** |
| * Proxyobserver which notifies all observers. |
| * |
| * @author wesendon |
| */ |
| private final class ProxyHandler implements InvocationHandler, ObserverCall { |
| |
| private final Class<ESObserver> clazz; |
| private final boolean prioritized; |
| private List<ObserverCall.Result> lastResults; |
| |
| public ProxyHandler(Class<ESObserver> clazz, boolean prioritized) { |
| this.clazz = clazz; |
| this.prioritized = prioritized; |
| lastResults = new ArrayList<ObserverCall.Result>(); |
| } |
| |
| // BEGIN SUPRESS CATCH EXCEPTION |
| public Object invoke(Object proxy, Method method, Object[] args) throws Exception { |
| // END SUPRESS CATCH EXCEPTION |
| // fork for calls to ObserverCall.class |
| if (ObserverCall.class.equals(method.getDeclaringClass())) { |
| return accessObserverCall(method, args); |
| } |
| |
| final List<ESObserver> observers = getObserverByClass(clazz); |
| |
| if (prioritized) { |
| final List<ESObserver> nonPrioritizedObservers = filterNonPrioritizedObservers(observers); |
| observers.removeAll(nonPrioritizedObservers); |
| sortObservers(observers); |
| |
| // return default value if no observers are registered |
| if (observers.size() == 0 && nonPrioritizedObservers.size() == 0) { |
| lastResults = new ArrayList<ObserverCall.Result>(); |
| return Result.getDefaultValue(method); |
| } |
| |
| lastResults = notifiyObservers(observers, method, args); |
| final List<Result> nonPrioritizedResults = notifiyObservers(nonPrioritizedObservers, method, args); |
| if (lastResults.size() == 0) { |
| lastResults = nonPrioritizedResults; |
| } |
| return lastResults.get(0).getResult(); |
| } |
| |
| // return default value if no observers are registered |
| if (observers.size() == 0) { |
| lastResults = new ArrayList<ObserverCall.Result>(); |
| return Result.getDefaultValue(method); |
| } |
| |
| lastResults = notifiyObservers(observers, method, args); |
| logExceptions(lastResults); |
| return lastResults.get(0).getResultOrDefaultValue(); |
| } |
| |
| /** |
| * Returns all observers that are not an instance of {@link ESPrioritizedObserver}. |
| * |
| * @param observers |
| * the list of observers to be filtered |
| * @return a list of non prioritized observers |
| */ |
| private List<ESObserver> filterNonPrioritizedObservers(List<ESObserver> observers) { |
| final List<ESObserver> nonPrioritizedObservers = new ArrayList<ESObserver>(); |
| for (final ESObserver o : observers) { |
| if (!ESPrioritizedObserver.class.isInstance(o)) { |
| nonPrioritizedObservers.add(o); |
| } |
| } |
| return nonPrioritizedObservers; |
| } |
| |
| private Object accessObserverCall(Method method, Object[] args) throws IllegalArgumentException, |
| IllegalAccessException, InvocationTargetException { |
| return method.invoke(this, args); |
| |
| } |
| |
| private List<ObserverCall.Result> notifiyObservers(List<ESObserver> observers, Method method, Object[] args) { |
| final List<ObserverCall.Result> results = new ArrayList<ObserverCall.Result>(observers.size()); |
| for (final ESObserver observer : observers) { |
| try { |
| results.add(new Result(observer, method, method.invoke(observer, args))); |
| // BEGIN SUPRESS CATCH EXCEPTION |
| } catch (final Exception exceptin) { |
| // END SUPRESS CATCH EXCEPTION |
| results.add(new Result(observer, method, exceptin)); |
| } |
| } |
| return results; |
| } |
| |
| public List<Result> getObserverCallResults() { |
| return lastResults; |
| } |
| |
| // END SUPRESS CATCH EXCEPTION |
| } |
| |
| /** |
| * Sorts Observers. Make sure they are {@link ESPrioritizedObserver}!! |
| * |
| * @param observers list of observers |
| */ |
| private void sortObservers(List<ESObserver> observers) { |
| Collections.sort(observers, new Comparator<ESObserver>() { |
| public int compare(ESObserver observer1, ESObserver observer2) { |
| final int prio1 = ((ESPrioritizedObserver) observer1).getPriority(); |
| final int prio2 = ((ESPrioritizedObserver) observer2).getPriority(); |
| if (prio1 == prio2) { |
| return 0; |
| } |
| return prio1 < prio2 ? 1 : -1; |
| } |
| }); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Class<? extends ESObserver>[] getObserverInterfaces(ESObserver observer) { |
| final HashSet<Class<? extends ESObserver>> observerIFacesFound = new LinkedHashSet<Class<? extends ESObserver>>(); |
| getClasses(observer.getClass(), observerIFacesFound); |
| return observerIFacesFound.toArray(new Class[observerIFacesFound.size()]); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private boolean getClasses(Class<?> clazz, Set<Class<? extends ESObserver>> result) { |
| for (final Class<?> iface : getAllInterfaces(clazz, new LinkedHashSet<Class<?>>())) { |
| if (iface.equals(ESObserver.class) && clazz.isInterface()) { |
| result.add((Class<? extends ESObserver>) clazz); |
| return true; |
| } |
| if (getClasses(iface, result) && clazz.isInterface()) { |
| result.add((Class<? extends ESObserver>) clazz); |
| } |
| } |
| return false; |
| } |
| |
| private Set<Class<?>> getAllInterfaces(final Class<?> clazz, final Set<Class<?>> interfacesFound) { |
| |
| for (final Class<?> iface : clazz.getInterfaces()) { |
| interfacesFound.add(iface); |
| } |
| |
| if (clazz.getSuperclass() == null) { |
| return interfacesFound; |
| } |
| |
| return getAllInterfaces(clazz.getSuperclass(), interfacesFound); |
| } |
| |
| /** |
| * Pulls observers from an extension point and registers them. |
| */ |
| public void collectionExtensionPoints() { |
| for (final ESExtensionElement outer : new ESExtensionPoint( |
| EXTENSION_POINT_ID, true) |
| .getExtensionElements()) { |
| try { |
| for (final ESExtensionElement inner : new ESExtensionPoint(outer.getAttribute("extensionPointName"), //$NON-NLS-1$ |
| true) |
| .getExtensionElements()) { |
| register(inner.getClass(outer.getAttribute("observerAttributeName"), ESObserver.class)); //$NON-NLS-1$ |
| } |
| } catch (final ESExtensionPointException e) { |
| } |
| } |
| } |
| } |