Coverage Summary for Class: SubscriberRegistry (com.google.common.eventbus)
| Class | Method, % | Line, % |
|---|---|---|
| SubscriberRegistry | 0% (0/11) | 0% (0/71) |
| SubscriberRegistry$1 | 0% (0/2) | 0% (0/2) |
| SubscriberRegistry$2 | 0% (0/2) | 0% (0/3) |
| SubscriberRegistry$MethodIdentifier | 0% (0/3) | 0% (0/8) |
| Total | 0% (0/18) | 0% (0/84) |
1 /* 2 * Copyright (C) 2014 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15 package com.google.common.eventbus; 16 17 import static com.google.common.base.Preconditions.checkArgument; 18 import static com.google.common.base.Preconditions.checkNotNull; 19 import static com.google.common.base.Throwables.throwIfUnchecked; 20 21 import com.google.common.annotations.VisibleForTesting; 22 import com.google.common.base.MoreObjects; 23 import com.google.common.base.Objects; 24 import com.google.common.base.Throwables; 25 import com.google.common.cache.CacheBuilder; 26 import com.google.common.cache.CacheLoader; 27 import com.google.common.cache.LoadingCache; 28 import com.google.common.collect.HashMultimap; 29 import com.google.common.collect.ImmutableList; 30 import com.google.common.collect.ImmutableSet; 31 import com.google.common.collect.Iterators; 32 import com.google.common.collect.Lists; 33 import com.google.common.collect.Maps; 34 import com.google.common.collect.Multimap; 35 import com.google.common.primitives.Primitives; 36 import com.google.common.reflect.TypeToken; 37 import com.google.common.util.concurrent.UncheckedExecutionException; 38 import com.google.j2objc.annotations.Weak; 39 import java.lang.reflect.Method; 40 import java.util.Arrays; 41 import java.util.Collection; 42 import java.util.Iterator; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.Map.Entry; 46 import java.util.Set; 47 import java.util.concurrent.ConcurrentMap; 48 import java.util.concurrent.CopyOnWriteArraySet; 49 import javax.annotation.CheckForNull; 50 51 /** 52 * Registry of subscribers to a single event bus. 53 * 54 * @author Colin Decker 55 */ 56 @ElementTypesAreNonnullByDefault 57 final class SubscriberRegistry { 58 59 /** 60 * All registered subscribers, indexed by event type. 61 * 62 * <p>The {@link CopyOnWriteArraySet} values make it easy and relatively lightweight to get an 63 * immutable snapshot of all current subscribers to an event without any locking. 64 */ 65 private final ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>> subscribers = 66 Maps.newConcurrentMap(); 67 68 /** The event bus this registry belongs to. */ 69 @Weak private final EventBus bus; 70 71 SubscriberRegistry(EventBus bus) { 72 this.bus = checkNotNull(bus); 73 } 74 75 /** Registers all subscriber methods on the given listener object. */ 76 void register(Object listener) { 77 Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener); 78 79 for (Entry<Class<?>, Collection<Subscriber>> entry : listenerMethods.asMap().entrySet()) { 80 Class<?> eventType = entry.getKey(); 81 Collection<Subscriber> eventMethodsInListener = entry.getValue(); 82 83 CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType); 84 85 if (eventSubscribers == null) { 86 CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<>(); 87 eventSubscribers = 88 MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet); 89 } 90 91 eventSubscribers.addAll(eventMethodsInListener); 92 } 93 } 94 95 /** Unregisters all subscribers on the given listener object. */ 96 void unregister(Object listener) { 97 Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener); 98 99 for (Entry<Class<?>, Collection<Subscriber>> entry : listenerMethods.asMap().entrySet()) { 100 Class<?> eventType = entry.getKey(); 101 Collection<Subscriber> listenerMethodsForType = entry.getValue(); 102 103 CopyOnWriteArraySet<Subscriber> currentSubscribers = subscribers.get(eventType); 104 if (currentSubscribers == null || !currentSubscribers.removeAll(listenerMethodsForType)) { 105 // if removeAll returns true, all we really know is that at least one subscriber was 106 // removed... however, barring something very strange we can assume that if at least one 107 // subscriber was removed, all subscribers on listener for that event type were... after 108 // all, the definition of subscribers on a particular class is totally static 109 throw new IllegalArgumentException( 110 "missing event subscriber for an annotated method. Is " + listener + " registered?"); 111 } 112 113 // don't try to remove the set if it's empty; that can't be done safely without a lock 114 // anyway, if the set is empty it'll just be wrapping an array of length 0 115 } 116 } 117 118 @VisibleForTesting 119 Set<Subscriber> getSubscribersForTesting(Class<?> eventType) { 120 return MoreObjects.firstNonNull(subscribers.get(eventType), ImmutableSet.<Subscriber>of()); 121 } 122 123 /** 124 * Gets an iterator representing an immutable snapshot of all subscribers to the given event at 125 * the time this method is called. 126 */ 127 Iterator<Subscriber> getSubscribers(Object event) { 128 ImmutableSet<Class<?>> eventTypes = flattenHierarchy(event.getClass()); 129 130 List<Iterator<Subscriber>> subscriberIterators = 131 Lists.newArrayListWithCapacity(eventTypes.size()); 132 133 for (Class<?> eventType : eventTypes) { 134 CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType); 135 if (eventSubscribers != null) { 136 // eager no-copy snapshot 137 subscriberIterators.add(eventSubscribers.iterator()); 138 } 139 } 140 141 return Iterators.concat(subscriberIterators.iterator()); 142 } 143 144 /** 145 * A thread-safe cache that contains the mapping from each class to all methods in that class and 146 * all super-classes, that are annotated with {@code @Subscribe}. The cache is shared across all 147 * instances of this class; this greatly improves performance if multiple EventBus instances are 148 * created and objects of the same class are registered on all of them. 149 */ 150 private static final LoadingCache<Class<?>, ImmutableList<Method>> subscriberMethodsCache = 151 CacheBuilder.newBuilder() 152 .weakKeys() 153 .build( 154 new CacheLoader<Class<?>, ImmutableList<Method>>() { 155 @Override 156 public ImmutableList<Method> load(Class<?> concreteClass) throws Exception { 157 return getAnnotatedMethodsNotCached(concreteClass); 158 } 159 }); 160 161 /** 162 * Returns all subscribers for the given listener grouped by the type of event they subscribe to. 163 */ 164 private Multimap<Class<?>, Subscriber> findAllSubscribers(Object listener) { 165 Multimap<Class<?>, Subscriber> methodsInListener = HashMultimap.create(); 166 Class<?> clazz = listener.getClass(); 167 for (Method method : getAnnotatedMethods(clazz)) { 168 Class<?>[] parameterTypes = method.getParameterTypes(); 169 Class<?> eventType = parameterTypes[0]; 170 methodsInListener.put(eventType, Subscriber.create(bus, listener, method)); 171 } 172 return methodsInListener; 173 } 174 175 private static ImmutableList<Method> getAnnotatedMethods(Class<?> clazz) { 176 try { 177 return subscriberMethodsCache.getUnchecked(clazz); 178 } catch (UncheckedExecutionException e) { 179 throwIfUnchecked(e.getCause()); 180 throw e; 181 } 182 } 183 184 private static ImmutableList<Method> getAnnotatedMethodsNotCached(Class<?> clazz) { 185 Set<? extends Class<?>> supertypes = TypeToken.of(clazz).getTypes().rawTypes(); 186 Map<MethodIdentifier, Method> identifiers = Maps.newHashMap(); 187 for (Class<?> supertype : supertypes) { 188 for (Method method : supertype.getDeclaredMethods()) { 189 if (method.isAnnotationPresent(Subscribe.class) && !method.isSynthetic()) { 190 // TODO(cgdecker): Should check for a generic parameter type and error out 191 Class<?>[] parameterTypes = method.getParameterTypes(); 192 checkArgument( 193 parameterTypes.length == 1, 194 "Method %s has @Subscribe annotation but has %s parameters. " 195 + "Subscriber methods must have exactly 1 parameter.", 196 method, 197 parameterTypes.length); 198 199 checkArgument( 200 !parameterTypes[0].isPrimitive(), 201 "@Subscribe method %s's parameter is %s. " 202 + "Subscriber methods cannot accept primitives. " 203 + "Consider changing the parameter to %s.", 204 method, 205 parameterTypes[0].getName(), 206 Primitives.wrap(parameterTypes[0]).getSimpleName()); 207 208 MethodIdentifier ident = new MethodIdentifier(method); 209 if (!identifiers.containsKey(ident)) { 210 identifiers.put(ident, method); 211 } 212 } 213 } 214 } 215 return ImmutableList.copyOf(identifiers.values()); 216 } 217 218 /** Global cache of classes to their flattened hierarchy of supertypes. */ 219 private static final LoadingCache<Class<?>, ImmutableSet<Class<?>>> flattenHierarchyCache = 220 CacheBuilder.newBuilder() 221 .weakKeys() 222 .build( 223 new CacheLoader<Class<?>, ImmutableSet<Class<?>>>() { 224 // <Class<?>> is actually needed to compile 225 @SuppressWarnings("RedundantTypeArguments") 226 @Override 227 public ImmutableSet<Class<?>> load(Class<?> concreteClass) { 228 return ImmutableSet.<Class<?>>copyOf( 229 TypeToken.of(concreteClass).getTypes().rawTypes()); 230 } 231 }); 232 233 /** 234 * Flattens a class's type hierarchy into a set of {@code Class} objects including all 235 * superclasses (transitively) and all interfaces implemented by these superclasses. 236 */ 237 @VisibleForTesting 238 static ImmutableSet<Class<?>> flattenHierarchy(Class<?> concreteClass) { 239 try { 240 return flattenHierarchyCache.getUnchecked(concreteClass); 241 } catch (UncheckedExecutionException e) { 242 throw Throwables.propagate(e.getCause()); 243 } 244 } 245 246 private static final class MethodIdentifier { 247 248 private final String name; 249 private final List<Class<?>> parameterTypes; 250 251 MethodIdentifier(Method method) { 252 this.name = method.getName(); 253 this.parameterTypes = Arrays.asList(method.getParameterTypes()); 254 } 255 256 @Override 257 public int hashCode() { 258 return Objects.hashCode(name, parameterTypes); 259 } 260 261 @Override 262 public boolean equals(@CheckForNull Object o) { 263 if (o instanceof MethodIdentifier) { 264 MethodIdentifier ident = (MethodIdentifier) o; 265 return name.equals(ident.name) && parameterTypes.equals(ident.parameterTypes); 266 } 267 return false; 268 } 269 } 270 }