AbstractEventRouter.java
001 /*
002  * Copyright 2009-2013 the original author or authors.
003  *
004  * Licensed under the Apache License, Version 2.0 (the "License");
005  * you may not use this file except in compliance with the License.
006  * You may obtain a copy of the License at
007  *
008  *      http://www.apache.org/licenses/LICENSE-2.0
009  *
010  * Unless required by applicable law or agreed to in writing, software
011  * distributed under the License is distributed on an "AS IS" BASIS,
012  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013  * See the License for the specific language governing permissions and
014  * limitations under the License.
015  */
016 
017 package org.codehaus.griffon.runtime.core;
018 
019 import griffon.core.EventRouter;
020 import griffon.core.GriffonArtifact;
021 import griffon.util.RunnableWithArgs;
022 import groovy.lang.*;
023 import org.codehaus.groovy.runtime.InvokerHelper;
024 import org.slf4j.Logger;
025 import org.slf4j.LoggerFactory;
026 
027 import java.util.*;
028 
029 import static griffon.util.GriffonNameUtils.capitalize;
030 import static griffon.util.GriffonNameUtils.isBlank;
031 import static java.util.Collections.EMPTY_LIST;
032 import static java.util.Collections.synchronizedList;
033 import static org.codehaus.groovy.runtime.MetaClassHelper.convertToTypeArray;
034 
035 /**
036  @author Andres Almiray
037  */
038 public abstract class AbstractEventRouter implements EventRouter {
039     private static final Logger LOG = LoggerFactory.getLogger(AbstractEventRouter.class);
040     protected static final Object[] LOCK = new Object[0];
041     private boolean enabled = true;
042     protected final List listeners = synchronizedList(new ArrayList());
043     private final Map<Script, Binding> scriptBindings = new LinkedHashMap<Script, Binding>();
044     protected final Map<String, List> closureListeners = Collections.synchronizedMap(new LinkedHashMap<String, List>());
045 
046     @Override
047     public boolean isEnabled() {
048         synchronized (LOCK) {
049             return this.enabled;
050         }
051     }
052 
053     @Override
054     public void setEnabled(boolean enabled) {
055         synchronized (LOCK) {
056             this.enabled = enabled;
057         }
058     }
059 
060     @Override
061     public void publish(String eventName) {
062         publish(eventName, EMPTY_LIST);
063     }
064 
065     @Override
066     public void publish(String eventName, List params) {
067         if (!isEnabled()) return;
068         if (isBlank(eventName)) return;
069         if (params == nullparams = EMPTY_LIST;
070         buildPublisher(eventName, params, "synchronously").run();
071     }
072 
073     @Override
074     public void publishOutsideUI(String eventName) {
075         publishOutsideUI(eventName, EMPTY_LIST);
076     }
077 
078     @Override
079     public void publishOutsideUI(String eventName, List params) {
080         if (!isEnabled()) return;
081         if (isBlank(eventName)) return;
082         if (params == nullparams = EMPTY_LIST;
083         final Runnable publisher = buildPublisher(eventName, params, "outside UI");
084         doPublishOutsideUI(publisher);
085     }
086 
087     protected abstract void doPublishOutsideUI(Runnable publisher);
088 
089     @Override
090     public void publishAsync(String eventName) {
091         publishAsync(eventName, EMPTY_LIST);
092     }
093 
094     @Override
095     public void publishAsync(String eventName, List params) {
096         if (!isEnabled()) return;
097         if (isBlank(eventName)) return;
098         if (params == nullparams = EMPTY_LIST;
099         final Runnable publisher = buildPublisher(eventName, params, "asynchronously");
100         doPublishAsync(publisher);
101     }
102 
103     protected abstract void doPublishAsync(Runnable publisher);
104 
105     private void invokeHandler(Object handler, List params) {
106         if (handler instanceof Closure) {
107             ((Closurehandler).call(asArray(params));
108         else if (handler instanceof RunnableWithArgs) {
109             ((RunnableWithArgshandler).run(asArray(params));
110         }
111     }
112 
113     protected void fireEvent(Script script, String eventHandler, List params) {
114         Binding binding = scriptBindings.get(script);
115         if (binding == null) {
116             binding = new Binding();
117             script.setBinding(binding);
118             script.run();
119             scriptBindings.put(script, binding);
120         }
121 
122         Object handler = binding.getVariables().get(eventHandler);
123         if (handler != null) {
124             invokeHandler(handler, params);
125         }
126     }
127 
128     protected void fireEvent(Closure closure, String eventHandler, List params) {
129         closure.call(asArray(params));
130     }
131 
132     protected void fireEvent(RunnableWithArgs runnable, String eventHandler, List params) {
133         runnable.run(asArray(params));
134     }
135 
136     protected void fireEvent(Object instance, String eventHandler, List params) {
137         MetaClass mc = metaClassOf(instance);
138         MetaProperty mp = mc.getMetaProperty(eventHandler);
139         if (mp != null && mp.getProperty(instance!= null) {
140             invokeHandler(mp.getProperty(instance), params);
141             return;
142         }
143 
144         Class[] argTypes = convertToTypeArray(asArray(params));
145         MetaMethod mm = mc.pickMethod(eventHandler, argTypes);
146         if (mm != null) {
147             mm.invoke(instance, asArray(params));
148         }
149     }
150 
151     @Override
152     public void addEventListener(Object listener) {
153         if (listener == null || listener instanceof Closure || listener instanceof RunnableWithArgsreturn;
154         if (listener instanceof Map) {
155             addEventListener((Maplistener);
156             return;
157         }
158         synchronized (listeners) {
159             if (listeners.contains(listener)) return;
160             try {
161                 LOG.debug("Adding listener " + listener);
162             catch (UnsupportedOperationException uoe) {
163                 LOG.debug("Adding listener " + listener.getClass().getName());
164             }
165             listeners.add(listener);
166         }
167     }
168 
169     @Override
170     public void addEventListener(Map<String, Object> listener) {
171         if (listener == nullreturn;
172         for (Map.Entry<String, Object> entry : listener.entrySet()) {
173             Object value = entry.getValue();
174             if (value instanceof Closure) {
175                 addEventListener(entry.getKey()(Closurevalue);
176             else if (value instanceof RunnableWithArgs) {
177                 addEventListener(entry.getKey()(RunnableWithArgsvalue);
178             }
179         }
180     }
181 
182     @Override
183     public void removeEventListener(Object listener) {
184         if (listener == null || listener instanceof Closure || listener instanceof RunnableWithArgsreturn;
185         if (listener instanceof Map) {
186             removeEventListener((Maplistener);
187             return;
188         }
189         synchronized (listeners) {
190             if (LOG.isDebugEnabled()) {
191                 try {
192                     LOG.debug("Removing listener " + listener);
193                 catch (UnsupportedOperationException uoe) {
194                     LOG.debug("Removing listener " + listener.getClass().getName());
195                 }
196             }
197             listeners.remove(listener);
198             removeNestedListeners(listener);
199         }
200     }
201 
202     @Override
203     public void removeEventListener(Map<String, Object> listener) {
204         if (listener == nullreturn;
205         for (Map.Entry<String, Object> entry : listener.entrySet()) {
206             Object value = entry.getValue();
207             if (value instanceof Closure) {
208                 removeEventListener(entry.getKey()(Closurevalue);
209             else if (value instanceof RunnableWithArgs) {
210                 removeEventListener(entry.getKey()(RunnableWithArgsvalue);
211             }
212         }
213     }
214 
215     @Override
216     public void addEventListener(String eventName, Closure listener) {
217         if (isBlank(eventName|| listener == nullreturn;
218         synchronized (closureListeners) {
219             List list = closureListeners.get(capitalize(eventName));
220             if (list == null) {
221                 list = new ArrayList();
222                 closureListeners.put(capitalize(eventName), list);
223             }
224             if (list.contains(listener)) return;
225             if (LOG.isDebugEnabled()) {
226                 LOG.debug("Adding listener " + listener.getClass().getName() " on " + capitalize(eventName));
227             }
228             list.add(listener);
229         }
230     }
231 
232     @Override
233     public void addEventListener(String eventName, RunnableWithArgs listener) {
234         if (isBlank(eventName|| listener == nullreturn;
235         synchronized (closureListeners) {
236             List list = closureListeners.get(capitalize(eventName));
237             if (list == null) {
238                 list = new ArrayList();
239                 closureListeners.put(capitalize(eventName), list);
240             }
241             if (list.contains(listener)) return;
242             if (LOG.isDebugEnabled()) {
243                 LOG.debug("Adding listener " + listener.getClass().getName() " on " + capitalize(eventName));
244             }
245             list.add(listener);
246         }
247     }
248 
249     @Override
250     public void removeEventListener(String eventName, Closure listener) {
251         if (isBlank(eventName|| listener == nullreturn;
252         synchronized (closureListeners) {
253             List list = closureListeners.get(capitalize(eventName));
254             if (list != null) {
255                 if (LOG.isDebugEnabled()) {
256                     LOG.debug("Removing listener " + listener.getClass().getName() " on " + capitalize(eventName));
257                 }
258                 list.remove(listener);
259             }
260         }
261     }
262 
263     @Override
264     public void removeEventListener(String eventName, RunnableWithArgs listener) {
265         if (isBlank(eventName|| listener == nullreturn;
266         synchronized (closureListeners) {
267             List list = closureListeners.get(capitalize(eventName));
268             if (list != null) {
269                 if (LOG.isDebugEnabled()) {
270                     LOG.debug("Removing listener " + listener.getClass().getName() " on " + capitalize(eventName));
271                 }
272                 list.remove(listener);
273             }
274         }
275     }
276 
277     protected Runnable buildPublisher(final String event, final List params, final String mode) {
278         return new Runnable() {
279             public void run() {
280                 String eventName = capitalize(event);
281                 if (LOG.isTraceEnabled()) {
282                     LOG.trace("Triggering event '" + eventName + "' " + mode);
283                 }
284                 String eventHandler = "on" + eventName;
285                 // defensive copying to avoid CME during event dispatching
286                 // GRIFFON-224
287                 List listenersCopy = new ArrayList();
288                 synchronized (listeners) {
289                     listenersCopy.addAll(listeners);
290                 }
291                 synchronized (closureListeners) {
292                     List list = closureListeners.get(eventName);
293                     if (list != null) {
294                         for (Object listener : list) {
295                             listenersCopy.add(listener);
296                         }
297                     }
298                 }
299 
300                 for (Object listener : listenersCopy) {
301                     if (listener instanceof Script) {
302                         fireEvent((Scriptlistener, eventHandler, params);
303                     else if (listener instanceof Closure) {
304                         fireEvent((Closurelistener, eventHandler, params);
305                     else if (listener instanceof RunnableWithArgs) {
306                         fireEvent((RunnableWithArgslistener, eventHandler, params);
307                     else {
308                         fireEvent(listener, eventHandler, params);
309                     }
310                 }
311             }
312         };
313     }
314 
315     protected void removeNestedListeners(Object subject) {
316         synchronized (closureListeners) {
317             for (Map.Entry<String, List> event : closureListeners.entrySet()) {
318                 String eventName = event.getKey();
319                 List listenerList = event.getValue();
320                 List toRemove = new ArrayList();
321                 for (Object listener : listenerList) {
322                     if (isNestedListener(listener, subject)) {
323                         toRemove.add(listener);
324                     }
325                 }
326                 for (Object listener : toRemove) {
327                     if (LOG.isDebugEnabled()) {
328                         LOG.debug("Removing listener " + listener.getClass().getName() " on " + capitalize(eventName));
329                     }
330                     listenerList.remove(listener);
331                 }
332             }
333         }
334     }
335 
336     protected boolean isNestedListener(Object listener, Object subject) {
337         if (listener instanceof Closure) {
338             return ((Closurelistener).getOwner().equals(subject);
339         else if (listener instanceof RunnableWithArgs) {
340             Class listenerClass = listener.getClass();
341             if (listenerClass.isMemberClass() && listenerClass.getEnclosingClass().equals(subject.getClass())) {
342                 return subject.equals(InvokerHelper.getProperty(listener, "this$0"));
343             }
344         }
345         return false;
346     }
347 
348     protected Object[] asArray(List list) {
349         return list.toArray(new Object[list.size()]);
350     }
351 
352     protected MetaClass metaClassOf(Object obj) {
353         if (obj instanceof GriffonArtifact) {
354             return ((GriffonArtifactobj).getGriffonClass().getMetaClass();
355         else if (obj instanceof GroovyObject) {
356             return ((GroovyObjectobj).getMetaClass();
357         }
358         return GroovySystem.getMetaClassRegistry().getMetaClass(obj.getClass());
359     }
360 }