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