blob: 2ad36c9b9a2570e98ccb7d778f185d32d14f36d6 [file] [log] [blame]
/*******************************************************************************
* 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(&quot;A says: go!&quot;);
* }
* };
*
* // B extends A and is ESObserver
* 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 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) {
}
}
}
}