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