• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.checkNotNull;
18 
19 import com.google.common.annotations.VisibleForTesting;
20 import com.google.j2objc.annotations.Weak;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.util.concurrent.Executor;
24 import javax.annotation.CheckForNull;
25 
26 /**
27  * A subscriber method on a specific object, plus the executor that should be used for dispatching
28  * events to it.
29  *
30  * <p>Two subscribers are equivalent when they refer to the same method on the same object (not
31  * class). This property is used to ensure that no subscriber method is registered more than once.
32  *
33  * @author Colin Decker
34  */
35 @ElementTypesAreNonnullByDefault
36 class Subscriber {
37 
38   /** Creates a {@code Subscriber} for {@code method} on {@code listener}. */
create(EventBus bus, Object listener, Method method)39   static Subscriber create(EventBus bus, Object listener, Method method) {
40     return isDeclaredThreadSafe(method)
41         ? new Subscriber(bus, listener, method)
42         : new SynchronizedSubscriber(bus, listener, method);
43   }
44 
45   /** The event bus this subscriber belongs to. */
46   @Weak private EventBus bus;
47 
48   /** The object with the subscriber method. */
49   @VisibleForTesting final Object target;
50 
51   /** Subscriber method. */
52   private final Method method;
53 
54   /** Executor to use for dispatching events to this subscriber. */
55   private final Executor executor;
56 
Subscriber(EventBus bus, Object target, Method method)57   private Subscriber(EventBus bus, Object target, Method method) {
58     this.bus = bus;
59     this.target = checkNotNull(target);
60     this.method = method;
61     method.setAccessible(true);
62 
63     this.executor = bus.executor();
64   }
65 
66   /** Dispatches {@code event} to this subscriber using the proper executor. */
dispatchEvent(final Object event)67   final void dispatchEvent(final Object event) {
68     executor.execute(
69         new Runnable() {
70           @Override
71           public void run() {
72             try {
73               invokeSubscriberMethod(event);
74             } catch (InvocationTargetException e) {
75               bus.handleSubscriberException(e.getCause(), context(event));
76             }
77           }
78         });
79   }
80 
81   /**
82    * Invokes the subscriber method. This method can be overridden to make the invocation
83    * synchronized.
84    */
85   @VisibleForTesting
invokeSubscriberMethod(Object event)86   void invokeSubscriberMethod(Object event) throws InvocationTargetException {
87     try {
88       method.invoke(target, checkNotNull(event));
89     } catch (IllegalArgumentException e) {
90       throw new Error("Method rejected target/argument: " + event, e);
91     } catch (IllegalAccessException e) {
92       throw new Error("Method became inaccessible: " + event, e);
93     } catch (InvocationTargetException e) {
94       if (e.getCause() instanceof Error) {
95         throw (Error) e.getCause();
96       }
97       throw e;
98     }
99   }
100 
101   /** Gets the context for the given event. */
context(Object event)102   private SubscriberExceptionContext context(Object event) {
103     return new SubscriberExceptionContext(bus, event, target, method);
104   }
105 
106   @Override
hashCode()107   public final int hashCode() {
108     return (31 + method.hashCode()) * 31 + System.identityHashCode(target);
109   }
110 
111   @Override
equals(@heckForNull Object obj)112   public final boolean equals(@CheckForNull Object obj) {
113     if (obj instanceof Subscriber) {
114       Subscriber that = (Subscriber) obj;
115       // Use == so that different equal instances will still receive events.
116       // We only guard against the case that the same object is registered
117       // multiple times
118       return target == that.target && method.equals(that.method);
119     }
120     return false;
121   }
122 
123   /**
124    * Checks whether {@code method} is thread-safe, as indicated by the presence of the {@link
125    * AllowConcurrentEvents} annotation.
126    */
isDeclaredThreadSafe(Method method)127   private static boolean isDeclaredThreadSafe(Method method) {
128     return method.getAnnotation(AllowConcurrentEvents.class) != null;
129   }
130 
131   /**
132    * Subscriber that synchronizes invocations of a method to ensure that only one thread may enter
133    * the method at a time.
134    */
135   @VisibleForTesting
136   static final class SynchronizedSubscriber extends Subscriber {
137 
SynchronizedSubscriber(EventBus bus, Object target, Method method)138     private SynchronizedSubscriber(EventBus bus, Object target, Method method) {
139       super(bus, target, method);
140     }
141 
142     @Override
invokeSubscriberMethod(Object event)143     void invokeSubscriberMethod(Object event) throws InvocationTargetException {
144       synchronized (this) {
145         super.invokeSubscriberMethod(event);
146       }
147     }
148   }
149 }
150