View Javadoc

1   package com.atlassian.plugin.event.impl;
2   
3   import com.atlassian.plugin.event.PluginEventManager;
4   import com.atlassian.plugin.util.ClassUtils;
5   
6   import java.lang.reflect.Method;
7   import java.lang.reflect.InvocationTargetException;
8   import java.util.*;
9   
10  import org.apache.commons.collections.map.LazyMap;
11  import org.apache.commons.collections.Factory;
12  import org.apache.commons.logging.Log;
13  import org.apache.commons.logging.LogFactory;
14  import org.apache.commons.lang.Validate;
15  
16  /**
17   * Simple, synchronous event manager that uses one or more method selectors to determine event listeners.  The default
18   * method selectors are {@link MethodNameListenerMethodSelector} and {@link AnnotationListenerMethodSelector}.
19   */
20  public class DefaultPluginEventManager implements PluginEventManager
21  {
22      private final Map<Class,Set<Listener>> eventsToListener;
23      private static final Log log = LogFactory.getLog(DefaultPluginEventManager.class);
24      private final ListenerMethodSelector[] listenerMethodSelectors;
25  
26      /**
27       * Default constructor that looks for methods named "channel" and the @PluginEventListener annotations
28       */
29      public DefaultPluginEventManager()
30      {
31          this(new ListenerMethodSelector[]{new MethodNameListenerMethodSelector(), new AnnotationListenerMethodSelector()});
32      }
33  
34      /**
35       * Constructor that looks for an arbitrary selectors
36       * @param selectors List of selectors that determine which are listener methods
37       */
38      public DefaultPluginEventManager(ListenerMethodSelector[] selectors)
39      {
40          this.listenerMethodSelectors = selectors;
41          eventsToListener = LazyMap.decorate(new HashMap<Class,Set<Listener>>(), new Factory() {
42              public Set<Listener> create() { return new HashSet<Listener>(); }
43          });
44      }
45  
46      /**
47       * Broadcasts an event to all listeners synchronously, logging all exceptions as an ERROR.
48       *
49       * @param event The event object
50       */
51      public synchronized void broadcast(Object event)
52      {
53          Validate.notNull(event, "The event to broadcast must not be null");
54          final Set<Listener> calledListeners = new HashSet<Listener>();
55          for (Class type : ClassUtils.findAllTypes(event.getClass()))
56          {
57              Set<Listener> registrations = eventsToListener.get(type);
58              for (Listener reg : registrations)
59              {
60                  if (calledListeners.contains(reg))
61                      continue;
62                  calledListeners.add(reg);
63                  reg.notify(event);
64              }
65          }
66      }
67  
68      /**
69       * Registers a listener by scanning the object for all listener methods
70       *
71       * @param listener The listener object
72       * @throws IllegalArgumentException If the listener is null or contains a listener method with 0 or 2 or more
73       * arguments
74       */
75      public synchronized void register(Object listener) throws IllegalArgumentException
76      {
77          if (listener == null)
78              throw new IllegalArgumentException("Listener cannot be null");
79  
80          forEveryListenerMethod(listener, new ListenerMethodHandler()
81          {
82              public void handle(Object listener, Method m)
83              {
84                  if (m.getParameterTypes().length != 1)
85                          throw new IllegalArgumentException("Listener methods must only have one argument");
86                  Set<Listener> listeners = eventsToListener.get(m.getParameterTypes()[0]);
87                  listeners.add(new Listener(listener, m));
88              }
89          });
90      }
91  
92      /**
93       * Unregisters the listener
94       * @param listener The listener
95       */
96      public synchronized void unregister(Object listener)
97      {
98          forEveryListenerMethod(listener, new ListenerMethodHandler()
99          {
100             public void handle(Object listener, Method m)
101             {
102                 Set<Listener> listeners = eventsToListener.get(m.getParameterTypes()[0]);
103                 listeners.remove(new Listener(listener, m));
104             }
105         });
106     }
107 
108     /**
109      * Walks an object for every listener method and calls the handler
110      * @param listener The listener object
111      * @param handler The handler
112      */
113     void forEveryListenerMethod(Object listener, ListenerMethodHandler handler)
114     {
115         Method[] methods = listener.getClass().getMethods();
116         for (int x=0; x<methods.length; x++)
117         {
118             Method m = methods[x];
119             for (int s = 0; s<listenerMethodSelectors.length; s++)
120             {
121                 ListenerMethodSelector selector = listenerMethodSelectors[s];
122                 if (selector.isListenerMethod(m))
123                 {
124                     handler.handle(listener, m);
125                 }
126             }
127         }
128     }
129 
130     /**
131      * Records a registration of a listener method
132      */
133     /**
134      * Simple fake closure for logic that needs to execute for every listener method on an object
135      */
136     private static interface ListenerMethodHandler
137     {
138         void handle(Object listener, Method m);
139     }
140 
141     private static class Listener
142     {
143 
144         public final Object listener;
145 
146         public final Method method;
147 
148         public Listener(Object listener, Method method)
149         {
150             Validate.notNull(listener);
151             Validate.notNull(method);
152             this.listener = listener;
153             this.method = method;
154         }
155 
156         public void notify(Object event)
157         {
158             Validate.notNull(event);
159             try
160             {
161                 method.invoke(listener, event);
162             }
163             catch (IllegalAccessException e)
164             {
165                 log.error("Unable to access listener method: "+method, e);
166             }
167             catch (InvocationTargetException e)
168             {
169                 log.error("Exception calling listener method", e.getCause());
170             }
171         }
172 
173         public boolean equals(Object o)
174         {
175             if (this == o) return true;
176             if (o == null || getClass() != o.getClass()) return false;
177 
178             Listener that = (Listener) o;
179 
180             if (!listener.equals(that.listener)) return false;
181             if (!method.equals(that.method)) return false;
182 
183             return true;
184         }
185 
186         public int hashCode()
187         {
188             int result;
189             result = listener.hashCode();
190             result = 31 * result + method.hashCode();
191             return result;
192         }
193     }
194 }