package org.goko.core.common.event; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; /** * A generic implementation of an Observable object as defined by the <a * href="http://en.wikipedia.org/wiki/Observer_pattern">Observer pattern</a>. * As a type parameter the interface for the Observer needs to be specified. * * @author Steven Jeuris * @param <T> * The interface which should be implemented by the observers. */ public abstract class AbstractObservable<T> implements IObservable<T> { private Class<T> m_class; private T m_eventDispatcher = null; /** * The list of observers to which the event dispatcher forwards it calls. */ private final ObserverPool<T> m_observers = new ObserverPool<T>(); /** * @param m_class */ public AbstractObservable(Class<T> m_class) { super(); this.m_class = m_class; } /** * Get the event dispatcher through which you can notify the observers. * * @return The event dispatcher through which you can notify the observers. */ protected T getEventDispatcher() { // Only create one instance of the dispatcher. if ( m_eventDispatcher == null ) { // // Use reflection to get the generic parameter type. // // Find the super class after 'AbstractObservable'. // Class<?> superClass = this.getClass(); // while ( superClass.getSuperclass() != AbstractObservable.class ) { // superClass = superClass.getSuperclass(); // } // // // Get the generic class for AbstractObservable, so that parameter types // // can be extracted. // Type genericClass = superClass.getGenericSuperclass(); // if ( genericClass instanceof Class<?> ) { // new RuntimeException( "Observable requires a parameter type!" ); // } // else { // // Get the parameter type. // ParameterizedType genericType = (ParameterizedType)genericClass; // Type[] typeArguments = genericType.getActualTypeArguments(); m_eventDispatcher = m_observers.createEventDispatcher( m_class ); // } } return m_eventDispatcher; } /* * (non-Javadoc) * * @see be.hyp3.patterns.observer.IObservable#addObserver(T) */ public void addObserver( T observer ) { m_observers.addObserver( observer ); } /* * (non-Javadoc) * * @see be.hyp3.patterns.observer.IObservable#removeObserver(T) */ public boolean removeObserver( T observer ) { return m_observers.removeObserver( observer ); } } /** * The ObserverPool is a proxy which allows calls to an interface to be forwarded to a set of listeners. * * @author Steven Jeuris * * @param <T> * The interface which defines which calls can be made to the listeners. */ class ObserverPool<T> implements InvocationHandler { /** * The list of listeners. Additions and removals greatly outnumber traversals, * so a CopyOnWriteArrayList is the most efficient solution. */ private List<T> m_pool = new CopyOnWriteArrayList<T>(); /** * Add an observer to which the calls will be made. * * @param observer * The observer to add. */ public void addObserver( T observer ) { m_pool.add( observer ); } /** * Remove an observer to which calls where being made. * * @param observer * The observer to remove. * * @return True, when the observer was found and removed, false otherwise. */ public boolean removeObserver( T observer ) { return m_pool.remove( observer ); } /** * Create the proxy which allows to dispatch all calls to the observers. * * @param observerClass * The interface class of the observers. * * @return The dispatcher which can be used to make calls to all added observers. */ @SuppressWarnings( "unchecked" ) public T createEventDispatcher( Class observerClass ) { Object dispatcher = Proxy.newProxyInstance( observerClass.getClassLoader(), new Class[] { observerClass }, this ); return (T)dispatcher; } /** * invoke() implementation of InvocationHandler. * This is called whenever a call is made to an event dispatcher. */ @Override public Object invoke( Object object, Method method, Object[] args ) throws Throwable { // Forward the call to all observers. for ( T observer : m_pool ) { method.invoke( observer, args ); } // No return object available. return null; } }