blob: ddc4963c9609e0126300492c5c3bbe1f487e869b [file] [log] [blame]
/*
* Copyright (C) 2014 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.common.eventbus;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.VisibleForTesting;
import com.google.j2objc.annotations.Weak;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.Executor;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* A subscriber method on a specific object, plus the executor that should be used for dispatching
* events to it.
*
* <p>Two subscribers are equivalent when they refer to the same method on the same object (not
* class). This property is used to ensure that no subscriber method is registered more than once.
*
* @author Colin Decker
*/
class Subscriber {
/** Creates a {@code Subscriber} for {@code method} on {@code listener}. */
static Subscriber create(EventBus bus, Object listener, Method method) {
return isDeclaredThreadSafe(method)
? new Subscriber(bus, listener, method)
: new SynchronizedSubscriber(bus, listener, method);
}
/** The event bus this subscriber belongs to. */
@Weak private EventBus bus;
/** The object with the subscriber method. */
@VisibleForTesting final Object target;
/** Subscriber method. */
private final Method method;
/** Executor to use for dispatching events to this subscriber. */
private final Executor executor;
private Subscriber(EventBus bus, Object target, Method method) {
this.bus = bus;
this.target = checkNotNull(target);
this.method = method;
method.setAccessible(true);
this.executor = bus.executor();
}
/** Dispatches {@code event} to this subscriber using the proper executor. */
final void dispatchEvent(final Object event) {
executor.execute(
new Runnable() {
@Override
public void run() {
try {
invokeSubscriberMethod(event);
} catch (InvocationTargetException e) {
bus.handleSubscriberException(e.getCause(), context(event));
}
}
});
}
/**
* Invokes the subscriber method. This method can be overridden to make the invocation
* synchronized.
*/
@VisibleForTesting
void invokeSubscriberMethod(Object event) throws InvocationTargetException {
try {
method.invoke(target, checkNotNull(event));
} catch (IllegalArgumentException e) {
throw new Error("Method rejected target/argument: " + event, e);
} catch (IllegalAccessException e) {
throw new Error("Method became inaccessible: " + event, e);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof Error) {
throw (Error) e.getCause();
}
throw e;
}
}
/** Gets the context for the given event. */
private SubscriberExceptionContext context(Object event) {
return new SubscriberExceptionContext(bus, event, target, method);
}
@Override
public final int hashCode() {
return (31 + method.hashCode()) * 31 + System.identityHashCode(target);
}
@Override
public final boolean equals(@Nullable Object obj) {
if (obj instanceof Subscriber) {
Subscriber that = (Subscriber) obj;
// Use == so that different equal instances will still receive events.
// We only guard against the case that the same object is registered
// multiple times
return target == that.target && method.equals(that.method);
}
return false;
}
/**
* Checks whether {@code method} is thread-safe, as indicated by the presence of the {@link
* AllowConcurrentEvents} annotation.
*/
private static boolean isDeclaredThreadSafe(Method method) {
return method.getAnnotation(AllowConcurrentEvents.class) != null;
}
/**
* Subscriber that synchronizes invocations of a method to ensure that only one thread may enter
* the method at a time.
*/
@VisibleForTesting
static final class SynchronizedSubscriber extends Subscriber {
private SynchronizedSubscriber(EventBus bus, Object target, Method method) {
super(bus, target, method);
}
@Override
void invokeSubscriberMethod(Object event) throws InvocationTargetException {
synchronized (this) {
super.invokeSubscriberMethod(event);
}
}
}
}