1/* 2 * Copyright (C) 2007 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.google.common.eventbus; 18 19import com.google.common.base.Objects; 20import com.google.common.base.Throwables; 21import com.google.common.cache.CacheBuilder; 22import com.google.common.cache.CacheLoader; 23import com.google.common.cache.LoadingCache; 24import com.google.common.collect.HashMultimap; 25import com.google.common.collect.ImmutableList; 26import com.google.common.collect.Maps; 27import com.google.common.collect.Multimap; 28import com.google.common.reflect.TypeToken; 29import com.google.common.util.concurrent.UncheckedExecutionException; 30 31import java.lang.reflect.Method; 32import java.util.Arrays; 33import java.util.List; 34import java.util.Map; 35import java.util.Set; 36 37import javax.annotation.Nullable; 38 39/** 40 * A {@link SubscriberFindingStrategy} for collecting all event subscriber methods that are marked 41 * with the {@link Subscribe} annotation. 42 * 43 * @author Cliff Biffle 44 * @author Louis Wasserman 45 */ 46class AnnotatedSubscriberFinder implements SubscriberFindingStrategy { 47 /** 48 * A thread-safe cache that contains the mapping from each class to all methods in that class and 49 * all super-classes, that are annotated with {@code @Subscribe}. The cache is shared across all 50 * instances of this class; this greatly improves performance if multiple EventBus instances are 51 * created and objects of the same class are registered on all of them. 52 */ 53 private static final LoadingCache<Class<?>, ImmutableList<Method>> subscriberMethodsCache = 54 CacheBuilder.newBuilder() 55 .weakKeys() 56 .build(new CacheLoader<Class<?>, ImmutableList<Method>>() { 57 @Override 58 public ImmutableList<Method> load(Class<?> concreteClass) throws Exception { 59 return getAnnotatedMethodsInternal(concreteClass); 60 } 61 }); 62 63 /** 64 * {@inheritDoc} 65 * 66 * This implementation finds all methods marked with a {@link Subscribe} annotation. 67 */ 68 @Override 69 public Multimap<Class<?>, EventSubscriber> findAllSubscribers(Object listener) { 70 Multimap<Class<?>, EventSubscriber> methodsInListener = HashMultimap.create(); 71 Class<?> clazz = listener.getClass(); 72 for (Method method : getAnnotatedMethods(clazz)) { 73 Class<?>[] parameterTypes = method.getParameterTypes(); 74 Class<?> eventType = parameterTypes[0]; 75 EventSubscriber subscriber = makeSubscriber(listener, method); 76 methodsInListener.put(eventType, subscriber); 77 } 78 return methodsInListener; 79 } 80 81 private static ImmutableList<Method> getAnnotatedMethods(Class<?> clazz) { 82 try { 83 return subscriberMethodsCache.getUnchecked(clazz); 84 } catch (UncheckedExecutionException e) { 85 throw Throwables.propagate(e.getCause()); 86 } 87 } 88 89 private static final class MethodIdentifier { 90 private final String name; 91 private final List<Class<?>> parameterTypes; 92 93 MethodIdentifier(Method method) { 94 this.name = method.getName(); 95 this.parameterTypes = Arrays.asList(method.getParameterTypes()); 96 } 97 98 @Override 99 public int hashCode() { 100 return Objects.hashCode(name, parameterTypes); 101 } 102 103 @Override 104 public boolean equals(@Nullable Object o) { 105 if (o instanceof MethodIdentifier) { 106 MethodIdentifier ident = (MethodIdentifier) o; 107 return name.equals(ident.name) && parameterTypes.equals(ident.parameterTypes); 108 } 109 return false; 110 } 111 } 112 113 private static ImmutableList<Method> getAnnotatedMethodsInternal(Class<?> clazz) { 114 Set<? extends Class<?>> supers = TypeToken.of(clazz).getTypes().rawTypes(); 115 Map<MethodIdentifier, Method> identifiers = Maps.newHashMap(); 116 for (Class<?> superClazz : supers) { 117 for (Method superClazzMethod : superClazz.getMethods()) { 118 if (superClazzMethod.isAnnotationPresent(Subscribe.class) 119 && !superClazzMethod.isBridge()) { 120 Class<?>[] parameterTypes = superClazzMethod.getParameterTypes(); 121 if (parameterTypes.length != 1) { 122 throw new IllegalArgumentException("Method " + superClazzMethod 123 + " has @Subscribe annotation, but requires " + parameterTypes.length 124 + " arguments. Event subscriber methods must require a single argument."); 125 } 126 127 MethodIdentifier ident = new MethodIdentifier(superClazzMethod); 128 if (!identifiers.containsKey(ident)) { 129 identifiers.put(ident, superClazzMethod); 130 } 131 } 132 } 133 } 134 return ImmutableList.copyOf(identifiers.values()); 135 } 136 137 /** 138 * Creates an {@code EventSubscriber} for subsequently calling {@code method} on 139 * {@code listener}. 140 * Selects an EventSubscriber implementation based on the annotations on 141 * {@code method}. 142 * 143 * @param listener object bearing the event subscriber method. 144 * @param method the event subscriber method to wrap in an EventSubscriber. 145 * @return an EventSubscriber that will call {@code method} on {@code listener} 146 * when invoked. 147 */ 148 private static EventSubscriber makeSubscriber(Object listener, Method method) { 149 EventSubscriber wrapper; 150 if (methodIsDeclaredThreadSafe(method)) { 151 wrapper = new EventSubscriber(listener, method); 152 } else { 153 wrapper = new SynchronizedEventSubscriber(listener, method); 154 } 155 return wrapper; 156 } 157 158 /** 159 * Checks whether {@code method} is thread-safe, as indicated by the 160 * {@link AllowConcurrentEvents} annotation. 161 * 162 * @param method subscriber method to check. 163 * @return {@code true} if {@code subscriber} is marked as thread-safe, 164 * {@code false} otherwise. 165 */ 166 private static boolean methodIsDeclaredThreadSafe(Method method) { 167 return method.getAnnotation(AllowConcurrentEvents.class) != null; 168 } 169} 170