001 /*
002 * Copyright 2008-2015 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 package org.codehaus.griffon.runtime.core.event;
017
018 import griffon.core.CallableWithArgs;
019 import griffon.core.RunnableWithArgs;
020 import griffon.core.event.Event;
021 import griffon.core.event.EventRouter;
022 import griffon.util.GriffonClassUtils;
023 import griffon.util.MethodDescriptor;
024 import griffon.util.MethodUtils;
025 import org.slf4j.Logger;
026 import org.slf4j.LoggerFactory;
027
028 import javax.annotation.Nonnull;
029 import javax.annotation.Nullable;
030 import java.lang.reflect.Method;
031 import java.util.ArrayList;
032 import java.util.LinkedHashMap;
033 import java.util.List;
034 import java.util.Map;
035 import java.util.concurrent.ConcurrentHashMap;
036
037 import static griffon.util.GriffonClassUtils.convertToTypeArray;
038 import static griffon.util.GriffonNameUtils.capitalize;
039 import static griffon.util.GriffonNameUtils.requireNonBlank;
040 import static java.util.Arrays.asList;
041 import static java.util.Collections.EMPTY_LIST;
042 import static java.util.Collections.synchronizedList;
043 import static java.util.Objects.requireNonNull;
044
045 /**
046 * @author Andres Almiray
047 */
048 public abstract class AbstractEventRouter implements EventRouter {
049 protected static final Object[] LOCK = new Object[0];
050 private static final String ERROR_EVENT_NAME_BLANK = "Argument 'eventName' must not be blank";
051 private static final String ERROR_EVENT_HANDLER_BLANK = "Argument 'eventHandler' must not be blank";
052 private static final String ERROR_MODE_BLANK = "Argument 'mode' must not be blank";
053 private static final String ERROR_LISTENER_NULL = "Argument 'listener' must not be null";
054 private static final String ERROR_EVENT_CLASS_NULL = "Argument 'eventClass' must not be null";
055 private static final String ERROR_EVENT_NULL = "Argument 'event' must not be null";
056 private static final String ERROR_CALLABLE_NULL = "Argument 'callable' must not be null";
057 private static final String ERROR_RUNNABLE_NULL = "Argument 'runnable' must not be null";
058 private static final String ERROR_PARAMS_NULL = "Argument 'params' must not be null";
059 private static final String ERROR_INSTANCE_NULL = "Argument 'instance' must not be null";
060 private static final String ERROR_OWNER_NULL = "Argument 'owner' must not be null";
061 private static final Logger LOG = LoggerFactory.getLogger(AbstractEventRouter.class);
062 protected final List<Object> instanceListeners = synchronizedList(new ArrayList<>());
063 protected final Map<String, List<Object>> functionalListeners = new ConcurrentHashMap<>();
064 private final MethodCache methodCache = new MethodCache();
065 private boolean enabled = true;
066
067 @Override
068 public boolean isEventPublishingEnabled() {
069 synchronized (LOCK) {
070 return this.enabled;
071 }
072 }
073
074 @Override
075 public void setEventPublishingEnabled(boolean enabled) {
076 synchronized (LOCK) {
077 this.enabled = enabled;
078 }
079 }
080
081 @Override
082 public void publishEvent(@Nonnull String eventName) {
083 publishEvent(eventName, EMPTY_LIST);
084 }
085
086 @Override
087 public void publishEvent(@Nonnull String eventName, @Nullable List<?> params) {
088 if (!isEventPublishingEnabled()) return;
089 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
090 if (params == null) params = EMPTY_LIST;
091 buildPublisher(eventName, params, "synchronously").run();
092 }
093
094 @Override
095 public void publishEventOutsideUI(@Nonnull String eventName) {
096 publishEventOutsideUI(eventName, EMPTY_LIST);
097 }
098
099 @Override
100 public void publishEventOutsideUI(@Nonnull String eventName, @Nullable List<?> params) {
101 if (!isEventPublishingEnabled()) return;
102 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
103 if (params == null) params = EMPTY_LIST;
104 final Runnable publisher = buildPublisher(eventName, params, "outside UI");
105 doPublishOutsideUI(publisher);
106 }
107
108 protected abstract void doPublishOutsideUI(@Nonnull Runnable publisher);
109
110 @Override
111 public void publishEventAsync(@Nonnull String eventName) {
112 publishEventAsync(eventName, EMPTY_LIST);
113 }
114
115 @Override
116 public void publishEventAsync(@Nonnull String eventName, @Nullable List<?> params) {
117 if (!isEventPublishingEnabled()) return;
118 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
119 if (params == null) params = EMPTY_LIST;
120 final Runnable publisher = buildPublisher(eventName, params, "asynchronously");
121 doPublishAsync(publisher);
122 }
123
124 protected abstract void doPublishAsync(@Nonnull Runnable publisher);
125
126 @Override
127 public void publishEvent(@Nonnull Event event) {
128 requireNonNull(event, ERROR_EVENT_NULL);
129 publishEvent(event.getClass().getSimpleName(), asList(event));
130 }
131
132 @Override
133 public void publishEventOutsideUI(@Nonnull Event event) {
134 requireNonNull(event, ERROR_EVENT_NULL);
135 publishEventOutsideUI(event.getClass().getSimpleName(), asList(event));
136 }
137
138 @Override
139 public void publishEventAsync(@Nonnull Event event) {
140 requireNonNull(event, ERROR_EVENT_NULL);
141 publishEventAsync(event.getClass().getSimpleName(), asList(event));
142 }
143
144 @Override
145 public <E extends Event> void removeEventListener(@Nonnull Class<E> eventClass, @Nonnull CallableWithArgs<?> listener) {
146 requireNonNull(eventClass, ERROR_EVENT_CLASS_NULL);
147 removeEventListener(eventClass.getSimpleName(), listener);
148 }
149
150 @Override
151 public <E extends Event> void removeEventListener(@Nonnull Class<E> eventClass, @Nonnull RunnableWithArgs listener) {
152 requireNonNull(eventClass, ERROR_EVENT_CLASS_NULL);
153 removeEventListener(eventClass.getSimpleName(), listener);
154 }
155
156 protected void fireEvent(@Nonnull RunnableWithArgs runnable, @Nonnull List<?> params) {
157 requireNonNull(runnable, ERROR_RUNNABLE_NULL);
158 requireNonNull(params, ERROR_PARAMS_NULL);
159 runnable.run(asArray(params));
160 }
161
162 protected void fireEvent(@Nonnull CallableWithArgs<?> callable, @Nonnull List<?> params) {
163 requireNonNull(callable, ERROR_CALLABLE_NULL);
164 requireNonNull(params, ERROR_PARAMS_NULL);
165 callable.call(asArray(params));
166 }
167
168 protected void fireEvent(@Nonnull Object instance, @Nonnull String eventHandler, @Nonnull List<?> params) {
169 requireNonNull(instance, ERROR_INSTANCE_NULL);
170 requireNonBlank(eventHandler, ERROR_EVENT_HANDLER_BLANK);
171 requireNonNull(params, ERROR_PARAMS_NULL);
172
173 Class[] argTypes = convertToTypeArray(asArray(params));
174 MethodDescriptor target = new MethodDescriptor(eventHandler, argTypes);
175 Method method = methodCache.findMatchingMethodFor(instance.getClass(), target);
176
177 if (method != null) {
178 MethodUtils.invokeSafe(method, instance, asArray(params));
179 }
180 }
181
182 @Override
183 public <E extends Event> void addEventListener(@Nonnull Class<E> eventClass, @Nonnull CallableWithArgs<?> listener) {
184 requireNonNull(eventClass, ERROR_EVENT_CLASS_NULL);
185 addEventListener(eventClass.getSimpleName(), listener);
186 }
187
188 @Override
189 public <E extends Event> void addEventListener(@Nonnull Class<E> eventClass, @Nonnull RunnableWithArgs listener) {
190 requireNonNull(eventClass, ERROR_EVENT_CLASS_NULL);
191 addEventListener(eventClass.getSimpleName(), listener);
192 }
193
194 @Override
195 @SuppressWarnings("unchecked")
196 public void addEventListener(@Nonnull Object listener) {
197 requireNonNull(listener, ERROR_LISTENER_NULL);
198 if (listener instanceof RunnableWithArgs) {
199 throw new IllegalArgumentException("Cannot add an event listener of type " + RunnableWithArgs.class.getName() +
200 " because the target event name is missing. " + listener);
201 }
202 if (listener instanceof CallableWithArgs) {
203 throw new IllegalArgumentException("Cannot add an event listener of type " + CallableWithArgs.class.getName() +
204 " because the target event name is missing. " + listener);
205 }
206
207 if (listener instanceof Map) {
208 addEventListener((Map) listener);
209 return;
210 }
211
212 if (!methodCache.isEventListener(listener.getClass())) {
213 return;
214 }
215
216 synchronized (instanceListeners) {
217 if (instanceListeners.contains(listener)) return;
218 try {
219 LOG.debug("Adding listener {}", listener);
220 } catch (UnsupportedOperationException uoe) {
221 LOG.debug("Adding listener {}", listener.getClass().getName());
222 }
223 instanceListeners.add(listener);
224 }
225 }
226
227 @Override
228 public void addEventListener(@Nonnull Map<String, Object> listener) {
229 requireNonNull(listener, ERROR_LISTENER_NULL);
230 for (Map.Entry<String, Object> entry : listener.entrySet()) {
231 Object eventHandler = entry.getValue();
232 if (eventHandler instanceof RunnableWithArgs) {
233 addEventListener(entry.getKey(), (RunnableWithArgs) eventHandler);
234 } else if (eventHandler instanceof CallableWithArgs) {
235 addEventListener(entry.getKey(), (CallableWithArgs) eventHandler);
236 } else {
237 throw new IllegalArgumentException("Unsupported functional event listener " + eventHandler);
238 }
239 }
240 }
241
242 @Override
243 @SuppressWarnings("unchecked")
244 public void removeEventListener(@Nonnull Object listener) {
245 requireNonNull(listener, ERROR_LISTENER_NULL);
246 if (listener instanceof RunnableWithArgs) {
247 throw new IllegalArgumentException("Cannot remove an event listener of type " + RunnableWithArgs.class.getName() +
248 " because the target event name is missing. " + listener);
249 }
250 if (listener instanceof CallableWithArgs) {
251 throw new IllegalArgumentException("Cannot remove an event listener of type " + CallableWithArgs.class.getName() +
252 " because the target event name is missing. " + listener);
253 }
254
255 if (listener instanceof Map) {
256 removeEventListener((Map) listener);
257 return;
258 }
259 synchronized (instanceListeners) {
260 try {
261 LOG.debug("Removing listener {}", listener);
262 } catch (UnsupportedOperationException uoe) {
263 LOG.debug("Removing listener {}", listener.getClass().getName());
264 }
265 instanceListeners.remove(listener);
266 removeNestedListeners(listener);
267 }
268 }
269
270 @Override
271 public void removeEventListener(@Nonnull Map<String, Object> listener) {
272 requireNonNull(listener, ERROR_LISTENER_NULL);
273 for (Map.Entry<String, Object> entry : listener.entrySet()) {
274 Object eventHandler = entry.getValue();
275 if (eventHandler instanceof RunnableWithArgs) {
276 removeEventListener(entry.getKey(), (RunnableWithArgs) eventHandler);
277 } else if (eventHandler instanceof CallableWithArgs) {
278 removeEventListener(entry.getKey(), (CallableWithArgs) eventHandler);
279 } else {
280 throw new IllegalArgumentException("Unsupported functional event listener " + eventHandler);
281 }
282 }
283 }
284
285 @Override
286 public void addEventListener(@Nonnull String eventName, @Nonnull CallableWithArgs<?> listener) {
287 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
288 requireNonNull(listener, ERROR_LISTENER_NULL);
289 synchronized (functionalListeners) {
290 List<Object> list = functionalListeners.get(capitalize(eventName));
291 if (list == null) {
292 list = new ArrayList<>();
293 functionalListeners.put(capitalize(eventName), list);
294 }
295 if (list.contains(listener)) return;
296 LOG.debug("Adding listener {} on {}", listener.getClass().getName(), capitalize(eventName));
297 list.add(listener);
298 }
299 }
300
301 @Override
302 public void addEventListener(@Nonnull String eventName, @Nonnull RunnableWithArgs listener) {
303 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
304 requireNonNull(listener, ERROR_LISTENER_NULL);
305 synchronized (functionalListeners) {
306 List<Object> list = functionalListeners.get(capitalize(eventName));
307 if (list == null) {
308 list = new ArrayList<>();
309 functionalListeners.put(capitalize(eventName), list);
310 }
311 if (list.contains(listener)) return;
312 LOG.debug("Adding listener {} on {}", listener.getClass().getName(), capitalize(eventName));
313 list.add(listener);
314 }
315 }
316
317 @Override
318 public void removeEventListener(@Nonnull String eventName, @Nonnull CallableWithArgs<?> listener) {
319 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
320 requireNonNull(listener, ERROR_LISTENER_NULL);
321 synchronized (functionalListeners) {
322 List<Object> list = functionalListeners.get(capitalize(eventName));
323 if (list != null) {
324 LOG.debug("Removing listener {} on {}", listener.getClass().getName(), capitalize(eventName));
325 list.remove(listener);
326 }
327 }
328 }
329
330 @Override
331 public void removeEventListener(@Nonnull String eventName, @Nonnull RunnableWithArgs listener) {
332 requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
333 requireNonNull(listener, ERROR_LISTENER_NULL);
334 synchronized (functionalListeners) {
335 List<Object> list = functionalListeners.get(capitalize(eventName));
336 if (list != null) {
337 LOG.debug("Removing listener {} on {}", listener.getClass().getName(), capitalize(eventName));
338 list.remove(listener);
339 }
340 }
341 }
342
343 protected Runnable buildPublisher(@Nonnull final String event, @Nonnull final List<?> params, @Nonnull final String mode) {
344 requireNonNull(event, ERROR_EVENT_NULL);
345 requireNonNull(params, ERROR_PARAMS_NULL);
346 requireNonBlank(mode, ERROR_MODE_BLANK);
347 return new Runnable() {
348 public void run() {
349 String eventName = capitalize(event);
350 LOG.debug("Triggering event '{}' {}", eventName, mode);
351 String eventHandler = "on" + eventName;
352 // defensive copying to avoid CME during event dispatching
353 // GRIFFON-224
354 List<Object> listenersCopy = new ArrayList<>();
355 synchronized (instanceListeners) {
356 listenersCopy.addAll(instanceListeners);
357 }
358 synchronized (functionalListeners) {
359 List list = functionalListeners.get(eventName);
360 if (list != null) {
361 for (Object listener : list) {
362 listenersCopy.add(listener);
363 }
364 }
365 }
366
367 for (Object listener : listenersCopy) {
368 if (listener instanceof RunnableWithArgs) {
369 fireEvent((RunnableWithArgs) listener, params);
370 } else if (listener instanceof CallableWithArgs) {
371 fireEvent((CallableWithArgs<?>) listener, params);
372 } else {
373 fireEvent(listener, eventHandler, params);
374 }
375 }
376 }
377 };
378 }
379
380 protected void removeNestedListeners(@Nonnull Object owner) {
381 requireNonNull(owner, ERROR_OWNER_NULL);
382 synchronized (functionalListeners) {
383 for (Map.Entry<String, List<Object>> event : functionalListeners.entrySet()) {
384 String eventName = event.getKey();
385 List<Object> listenerList = event.getValue();
386 List<Object> toRemove = new ArrayList<>();
387 for (Object listener : listenerList) {
388 if (isNestedListener(listener, owner)) {
389 toRemove.add(listener);
390 }
391 }
392 for (Object listener : toRemove) {
393 LOG.debug("Removing listener {} on {}", listener.getClass().getName(), capitalize(eventName));
394 listenerList.remove(listener);
395 }
396 }
397 }
398 }
399
400 protected boolean isNestedListener(@Nonnull Object listener, @Nonnull Object owner) {
401 requireNonNull(listener, ERROR_LISTENER_NULL);
402 requireNonNull(owner, ERROR_OWNER_NULL);
403 Class<?> listenerClass = listener.getClass();
404 return listenerClass.isMemberClass() &&
405 listenerClass.getEnclosingClass().equals(owner.getClass()) &&
406 owner.equals(GriffonClassUtils.getFieldValue(listener, "this$0"));
407 }
408
409 protected Object[] asArray(@Nonnull List<?> list) {
410 return list.toArray(new Object[list.size()]);
411 }
412
413 protected static class MethodCache {
414 private final Map<Class<?>, Map<String, List<MethodInfo>>> methodMap = new ConcurrentHashMap<>();
415
416 public boolean isEventListener(@Nonnull Class<?> klass) {
417 Map<String, List<MethodInfo>> methodMetadata = methodMap.get(klass);
418 if (methodMetadata == null) {
419 methodMetadata = fetchMethodMetadata(klass);
420 if (!methodMetadata.isEmpty()) {
421 methodMap.put(klass, methodMetadata);
422 } else {
423 methodMetadata = null;
424 }
425 }
426 return methodMetadata != null;
427 }
428
429 @Nullable
430 public Method findMatchingMethodFor(@Nonnull Class<?> klass, @Nonnull MethodDescriptor target) {
431 Map<String, List<MethodInfo>> methodMetadata = methodMap.get(klass);
432
433 List<MethodInfo> descriptors = methodMetadata.get(target.getName());
434 if (descriptors != null) {
435 for (MethodInfo info : descriptors) {
436 if (info.descriptor.matches(target)) {
437 return info.method;
438 }
439 }
440 }
441
442 return null;
443 }
444
445 private Map<String, List<MethodInfo>> fetchMethodMetadata(Class<?> klass) {
446 Map<String, List<MethodInfo>> methodMetadata = new LinkedHashMap<>();
447
448 for (Method method : klass.getMethods()) {
449 MethodDescriptor descriptor = MethodDescriptor.forMethod(method);
450 if (GriffonClassUtils.isEventHandler(descriptor)) {
451 String methodName = method.getName();
452 List<MethodInfo> descriptors = methodMetadata.get(methodName);
453 if (descriptors == null) {
454 descriptors = new ArrayList<>();
455 methodMetadata.put(methodName, descriptors);
456 }
457 descriptors.add(new MethodInfo(descriptor, method));
458 }
459 }
460
461 return methodMetadata;
462 }
463 }
464
465 protected static class MethodInfo {
466 private final MethodDescriptor descriptor;
467 private final Method method;
468
469 public MethodInfo(MethodDescriptor descriptor, Method method) {
470 this.descriptor = descriptor;
471 this.method = method;
472 }
473
474 public MethodDescriptor getDescriptor() {
475 return descriptor;
476 }
477
478 public Method getMethod() {
479 return method;
480 }
481 }
482 }
|