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 == null) params = 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 == null) params = 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 == null) params = 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 ((Closure) handler).call(asArray(params));
108 } else if (handler instanceof RunnableWithArgs) {
109 ((RunnableWithArgs) handler).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 RunnableWithArgs) return;
154 if (listener instanceof Map) {
155 addEventListener((Map) listener);
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 == null) return;
172 for (Map.Entry<String, Object> entry : listener.entrySet()) {
173 Object value = entry.getValue();
174 if (value instanceof Closure) {
175 addEventListener(entry.getKey(), (Closure) value);
176 } else if (value instanceof RunnableWithArgs) {
177 addEventListener(entry.getKey(), (RunnableWithArgs) value);
178 }
179 }
180 }
181
182 @Override
183 public void removeEventListener(Object listener) {
184 if (listener == null || listener instanceof Closure || listener instanceof RunnableWithArgs) return;
185 if (listener instanceof Map) {
186 removeEventListener((Map) listener);
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 == null) return;
205 for (Map.Entry<String, Object> entry : listener.entrySet()) {
206 Object value = entry.getValue();
207 if (value instanceof Closure) {
208 removeEventListener(entry.getKey(), (Closure) value);
209 } else if (value instanceof RunnableWithArgs) {
210 removeEventListener(entry.getKey(), (RunnableWithArgs) value);
211 }
212 }
213 }
214
215 @Override
216 public void addEventListener(String eventName, Closure listener) {
217 if (isBlank(eventName) || listener == null) return;
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 == null) return;
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 == null) return;
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 == null) return;
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((Script) listener, eventHandler, params);
303 } else if (listener instanceof Closure) {
304 fireEvent((Closure) listener, eventHandler, params);
305 } else if (listener instanceof RunnableWithArgs) {
306 fireEvent((RunnableWithArgs) listener, 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 ((Closure) listener).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 ((GriffonArtifact) obj).getGriffonClass().getMetaClass();
355 } else if (obj instanceof GroovyObject) {
356 return ((GroovyObject) obj).getMetaClass();
357 }
358 return GroovySystem.getMetaClassRegistry().getMetaClass(obj.getClass());
359 }
360 }
|