Skip to content

Content of file ECPObserverBusImpl.java

/*******************************************************************************
 * Copyright (c) 2011-2012 EclipseSource Muenchen GmbH and others.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 * wesendon - initial API and implementation
 *
 *******************************************************************************/

package org.eclipse.emf.ecp.internal.core.util.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.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.eclipse.emf.ecp.core.util.observer.ECPObserver;
import org.eclipse.emf.ecp.core.util.observer.ECPObserverBus;

/**
 * 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 ECPObserver} 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 ECPObserverCall}, 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(&quot;A says: go!&quot;);
 * 	}
 * };
 *
 * // B extends A and is IObserver
 * B b = new B() {
 *
 * 	public void say(String ja) {
 * 		System.out.println(&quot;B says: &quot; + ja);
 * 	}
 *
 * 	public void foo() {
 * 		System.out.println(&quot;B says: h??&quot;);
 * 	}
 * };
 *
 * // B is registered first
 * ObserverBus.register(b);
 * ObserverBus.register(a);
 *
 * ObserverBus.notify(A.class).foo();
 *
 * ObserverBus.notify(B.class).say(&quot;w00t&quot;);
 *
 * // Output:
 *
 * // B says: h??
 * // A says: go!
 * //
 * // B says: w00t
 *
 * </pre>
 *
 * @author wesendon
 */
public class ECPObserverBusImpl implements ECPObserverBus {

	private final HashMap<Class<? extends ECPObserver>, List<ECPObserver>> observerMap;

	/**
	 * Default constructor.
	 */
	public ECPObserverBusImpl() {
		observerMap = new HashMap<Class<? extends ECPObserver>, List<ECPObserver>>();
	}

	/**
	 * This method allows you to notify all observers.
	 *
	 * @param <T> class of observer
	 * @param clazz class of observer
	 * @return call object
	 */
	@Override
	public <T extends ECPObserver> T notify(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 ECPPrioritizedIObserver}
	 *
	 * @return call object
	 */
	public <T extends ECPObserver> T notify(Class<T> clazz, boolean prioritized) {
		if (clazz == null) {
			return null;
		}
		return createProxy(clazz, false);
	}

	/**
	 * Registers an observer for all observer interfaces implemented by the object or its super classes.
	 *
	 * @param observer observer object
	 */
	@Override
	public void register(ECPObserver 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(ECPObserver observer, Class<? extends ECPObserver>... classes) {
		for (final Class<? extends ECPObserver> 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
	 */
	@Override
	public void unregister(ECPObserver 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(ECPObserver observer, Class<? extends ECPObserver>... classes) {
for (final Class<? extends ECPObserver> iface : classes) { if (iface.isInstance(observer)) { removeObserver(observer, iface); } } } private void addObserver(ECPObserver observer, Class<? extends ECPObserver> iface) { final List<ECPObserver> observers = initObserverList(iface); observers.add(observer); } private void removeObserver(ECPObserver observer, Class<? extends ECPObserver> iface) { final List<ECPObserver> observers = initObserverList(iface); observers.remove(observer); } private List<ECPObserver> initObserverList(Class<? extends ECPObserver> iface) { List<ECPObserver> list = observerMap.get(iface); if (list == null) { list = new ArrayList<ECPObserver>(); observerMap.put(iface, list); } return list; } private List<ECPObserver> getObserverByClass(Class<? extends ECPObserver> clazz) { List<ECPObserver> list = observerMap.get(clazz); if (list == null) { list = Collections.emptyList(); } return new ArrayList<ECPObserver>(list); } private boolean isPrioritizedObserver(Class<?> clazz, Method method) { // Only prioritize if requested class extends PrioritizedIObserver and method is part of this class and not part // of some super class if (!clazz.equals(method.getDeclaringClass())) { return false; } for (final Class<?> interfaceClass : clazz.getInterfaces()) { if (ECPPrioritizedIObserver.class.equals(interfaceClass)) { return true; } } return false; } @SuppressWarnings("unchecked") private <T extends ECPObserver> T createProxy(Class<T> clazz, boolean prioritized) { final ProxyHandler handler = new ProxyHandler((Class<ECPObserver>) clazz, prioritized); return (T) Proxy .newProxyInstance(clazz.getClassLoader(), new Class[] { clazz, ECPObserverCall.class }, handler); } /** * Proxyobserver which notifies all observers. * * @author wesendon */ private final class ProxyHandler implements InvocationHandler, ECPObserverCall { private final Class<ECPObserver> clazz; private List<ECPObserverCall.Result> lastResults; private final boolean prioritized; ProxyHandler(Class<ECPObserver> clazz, boolean prioritized) { this.clazz = clazz; this.prioritized = prioritized; lastResults = new ArrayList<ECPObserverCall.Result>(); } // BEGIN SUPRESS CATCH EXCEPTION @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // END SUPRESS CATCH EXCEPTION // fork for calls to ObserverCall.class if (ECPObserverCall.class.equals(method.getDeclaringClass())) { return accessObserverCall(method, args); } final List<ECPObserver> observers = getObserverByClass(clazz); if (prioritized && isPrioritizedObserver(clazz, method)) { sortObservers(observers); } // return default value if no observers are registered if (observers.size() == 0) { lastResults = new ArrayList<ECPObserverCall.Result>(); return Result.getDefaultValue(method); } lastResults = notifiyObservers(observers, method, args); final List<Object> result = new ArrayList<Object>(); for (final Result resultObject : lastResults) { final Object res = resultObject.getResultOrDefaultValue(); if (res instanceof Object[]) { final Object[] arrayRes = (Object[]) res; result.addAll(Arrays.asList(arrayRes)); } else if (res instanceof Collection) { result.addAll((Collection) res); } else { result.add(res); } } return result; } private Object accessObserverCall(Method method, Object[] args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { return method.invoke(this, args); } private List<ECPObserverCall.Result> notifiyObservers(List<ECPObserver> observers, Method method, Object[] args) { final List<ECPObserverCall.Result> results = new ArrayList<ECPObserverCall.Result>(observers.size()); for (final ECPObserver observer : observers) { try { results.add(new Result(observer, method, method.invoke(observer, args))); // BEGIN SUPRESS CATCH EXCEPTION } catch (final Throwable e) { // END SUPRESS CATCH EXCEPTION results.add(new Result(observer, e, method)); } } return results; } @Override public List<Result> getObserverCallResults() { return lastResults; } // END SUPRESS CATCH EXCEPTION } /** * Sorts Observers. Make sure they are {@link ECPPrioritizedIObserver}!! * * @param observers list of observers */ private void sortObservers(List<ECPObserver> observers) { Collections.sort(observers, new Comparator<ECPObserver>() { @Override public int compare(ECPObserver o1, ECPObserver o2) { final int prio1 = ((ECPPrioritizedIObserver) o1).getPriority(); final int prio2 = ((ECPPrioritizedIObserver) o2).getPriority(); if (prio1 == prio2) { return 0; } return prio1 > prio2 ? 1 : -1; } }); } @SuppressWarnings("unchecked") private Class<? extends ECPObserver>[] getObserverInterfaces(ECPObserver observer) { final HashSet<Class<? extends ECPObserver>> observerInterfacsFound = new HashSet<Class<? extends ECPObserver>>(); getClasses(observer.getClass(), observerInterfacsFound); return observerInterfacsFound.toArray(new Class[observerInterfacsFound.size()]); } @SuppressWarnings("unchecked") private boolean getClasses(Class<?> clazz, HashSet<Class<? extends ECPObserver>> result) { for (final Class<?> iface : getAllInterfaces(clazz, new HashSet<Class<?>>())) { if (iface.equals(ECPObserver.class) && clazz.isInterface()) { result.add((Class<? extends ECPObserver>) clazz); return true; } if (getClasses(iface, result) && clazz.isInterface()) { result.add((Class<? extends ECPObserver>) 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); } }