View Javadoc

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