001/* 002 * Copyright (C) 2014 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License 010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 011 * or implied. See the License for the specific language governing permissions and limitations under 012 * the License. 013 */ 014 015package org.openstreetmap.josm.eventbus; 016 017import java.lang.reflect.InvocationTargetException; 018import java.lang.reflect.Method; 019import java.util.Objects; 020import java.util.concurrent.Executor; 021 022/** 023 * A subscriber method on a specific object, plus the executor that should be used for dispatching 024 * events to it. 025 * 026 * <p>Two subscribers are equivalent when they refer to the same method on the same object (not 027 * class). This property is used to ensure that no subscriber method is registered more than once. 028 * 029 * @author Colin Decker 030 */ 031class Subscriber { 032 033 /** 034 * Creates a {@code Subscriber} for {@code method} on {@code listener}. 035 * @param bus event bus 036 * @param listener listener 037 * @param method method 038 * @return subscriber 039 */ 040 static Subscriber create(EventBus bus, Object listener, Method method) { 041 return isDeclaredThreadSafe(method) 042 ? new Subscriber(bus, listener, method) 043 : new SynchronizedSubscriber(bus, listener, method); 044 } 045 046 /** The event bus this subscriber belongs to. */ 047 private EventBus bus; 048 049 /** The object with the subscriber method. */ 050 final Object target; 051 052 /** Subscriber method. */ 053 private final Method method; 054 055 /** Executor to use for dispatching events to this subscriber. */ 056 private final Executor executor; 057 058 private Subscriber(EventBus bus, Object target, Method method) { 059 this.bus = bus; 060 this.target = Objects.requireNonNull(target); 061 this.method = method; 062 method.setAccessible(true); 063 064 this.executor = bus.executor(); 065 } 066 067 /** 068 * Dispatches {@code event} to this subscriber using the proper executor. 069 * @param event event to dispatch 070 */ 071 final void dispatchEvent(final Object event) { 072 executor.execute( 073 () -> { 074 try { 075 invokeSubscriberMethod(event); 076 } catch (InvocationTargetException e) { 077 bus.handleSubscriberException(e.getCause(), context(event)); 078 } 079 }); 080 } 081 082 /** 083 * Invokes the subscriber method. This method can be overridden to make the invocation 084 * synchronized. 085 * @param event event to dispatch 086 * @throws InvocationTargetException if the invocation fails 087 */ 088 void invokeSubscriberMethod(Object event) throws InvocationTargetException { 089 try { 090 method.invoke(target, Objects.requireNonNull(event)); 091 } catch (IllegalArgumentException e) { 092 throw new Error("Method rejected target/argument: " + event, e); 093 } catch (IllegalAccessException e) { 094 throw new Error("Method became inaccessible: " + event, e); 095 } catch (InvocationTargetException e) { 096 if (e.getCause() instanceof Error) { 097 throw (Error) e.getCause(); 098 } 099 throw e; 100 } 101 } 102 103 /** 104 * Gets the context for the given event. 105 * @param event event 106 * @return context for the given event 107 */ 108 private SubscriberExceptionContext context(Object event) { 109 return new SubscriberExceptionContext(bus, event, target, method); 110 } 111 112 @Override 113 public final int hashCode() { 114 return (31 + method.hashCode()) * 31 + System.identityHashCode(target); 115 } 116 117 @Override 118 public final boolean equals(Object obj) { 119 if (obj instanceof Subscriber) { 120 Subscriber that = (Subscriber) obj; 121 // Use == so that different equal instances will still receive events. 122 // We only guard against the case that the same object is registered 123 // multiple times 124 return target == that.target && method.equals(that.method); 125 } 126 return false; 127 } 128 129 /** 130 * Checks whether {@code method} is thread-safe, as indicated by the presence of the {@link 131 * AllowConcurrentEvents} annotation. 132 * @param method method to check 133 * @return {@code true} if {@code method} is thread-safe 134 */ 135 private static boolean isDeclaredThreadSafe(Method method) { 136 return method.getAnnotation(AllowConcurrentEvents.class) != null; 137 } 138 139 /** 140 * Subscriber that synchronizes invocations of a method to ensure that only one thread may enter 141 * the method at a time. 142 */ 143 static final class SynchronizedSubscriber extends Subscriber { 144 145 private SynchronizedSubscriber(EventBus bus, Object target, Method method) { 146 super(bus, target, method); 147 } 148 149 @Override 150 void invokeSubscriberMethod(Object event) throws InvocationTargetException { 151 synchronized (this) { 152 super.invokeSubscriberMethod(event); 153 } 154 } 155 } 156}