| 
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.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 }
 |