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 }