1 /***
2 * Ambient - A music player for the Android platform
3 Copyright (C) 2007 Martin Vysny
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 package sk.baka.ambient.commons;
19
20 import java.lang.reflect.InvocationHandler;
21 import java.lang.reflect.Method;
22 import java.lang.reflect.Proxy;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.Map;
26 import java.util.concurrent.ConcurrentHashMap;
27 import java.util.concurrent.ConcurrentMap;
28
29 import sk.baka.ambient.AmbientApplication;
30 import android.os.Handler;
31 import android.util.Log;
32
33 /***
34 * A simple message delivery bus. All events are invoked asynchronously in the
35 * message loop.
36 *
37 * @author Martin Vysny
38 */
39 public final class SimpleBus {
40 /***
41 * The handlers map - maps interface class to a list of handlers of given
42 * event type.
43 */
44 private final ConcurrentMap<Class<?>, ConcurrentMap<Class<?>, Object>> handlerMap = new ConcurrentHashMap<Class<?>, ConcurrentMap<Class<?>, Object>>();
45
46 /***
47 * Caches asynchronous invocator instances.
48 */
49 private final ConcurrentMap<Class<?>, Object> invocatorCacheAsync = new ConcurrentHashMap<Class<?>, Object>();
50
51 /***
52 * Caches synchronous invocator instances.
53 */
54 private final ConcurrentMap<Class<?>, Object> invocatorCacheSync = new ConcurrentHashMap<Class<?>, Object>();
55
56 /***
57 * Allows event invocation in the message loop.
58 */
59 private final Handler handler = AmbientApplication.getHandler();
60
61 /***
62 * Registers this handler. The handler is registered to all message types
63 * that given object implements. If this is not desired, use
64 * {@link #addHandler(Object, Class)}.
65 *
66 * @param handler
67 * the handler to register.
68 */
69 public void addHandler(final Object handler) {
70 for (final Class<?> intf : MiscUtils.getInterfaces(handler.getClass())) {
71 addHandler(handler, intf);
72 }
73 }
74
75 /***
76 * Registers given handler only to given message type. The handler must
77 * implement the <code>messageType</code> interface otherwise
78 * {@link ClassCastException} is thrown.
79 *
80 * @param messageType
81 * the message interface
82 * @param handler
83 * handler that implements given interface.
84 */
85 public void addHandler(final Object handler, final Class<?> messageType) {
86 messageType.cast(handler);
87 synchronized (handlerMap) {
88 ConcurrentMap<Class<?>, Object> handlers = handlerMap
89 .get(messageType);
90 if (handlers == null) {
91 handlers = new ConcurrentHashMap<Class<?>, Object>();
92 handlerMap.put(messageType, handlers);
93 }
94 handlers.put(handler.getClass(), handler);
95 }
96 }
97
98 /***
99 * Unregisters this handler. The handler is unregistered from all message
100 * types that given object implements. If this is not desired, use
101 * {@link #removeHandler(Object, Class)}.
102 *
103 * @param handler
104 * the handler to unregister.
105 */
106 public void removeHandler(final Object handler) {
107 for (final Class<?> intf : MiscUtils.getInterfaces(handler.getClass())) {
108 removeHandler(handler, intf);
109 }
110 }
111
112 /***
113 * Unregisters given handler only to given message type. The handler must
114 * implement the <code>messageType</code> interface otherwise
115 * {@link ClassCastException} is thrown.
116 *
117 * @param messageType
118 * the message interface
119 * @param handler
120 * handler that implements given interface.
121 */
122 public void removeHandler(final Object handler, final Class<?> messageType) {
123 messageType.cast(handler);
124 synchronized (handlerMap) {
125 final ConcurrentMap<Class<?>, Object> handlers = handlerMap.get(messageType);
126 if (handlers == null) {
127 return;
128 }
129 handlers.remove(handler.getClass());
130 }
131 }
132
133 /***
134 * Returns an event invocator. Any call to a method causes appropriate
135 * handlers to be invoked.
136 *
137 * @param <T>
138 * the interface type
139 * @param messageType
140 * the interface class
141 * @param async
142 * if <code>true</code> then the invocation is dispatched using
143 * a Handler. If <code>false</code> then the invocation is
144 * forced immediately. In this case, the invocator methods
145 * <strong>should</strong> be called in the handler event
146 * thread.
147 * @return invocator instance, must not be <code>null</code>.
148 */
149 public <T> T getInvocator(final Class<T> messageType, final boolean async) {
150 final Map<Class<?>, Object> cache = async ? invocatorCacheAsync
151 : invocatorCacheSync;
152 Object proxy = cache.get(messageType);
153 if (proxy == null) {
154 final ClassLoader loader = messageType.getClassLoader();
155 proxy = Proxy.newProxyInstance(loader,
156 new Class<?>[] { messageType }, async ? eventInvocatorAsync
157 : eventInvocatorSync);
158 cache.put(messageType, proxy);
159 }
160 return messageType.cast(proxy);
161 }
162
163 /***
164 * Handles invocation calls from the proxy. The asynchronous version.
165 */
166 private final InvocationHandler eventInvocatorAsync = new InvocationHandler() {
167 public Object invoke(Object proxy, Method method, Object[] args)
168 throws Throwable {
169 handler.post(new InvokeHandler(method, args));
170 return null;
171 }
172 };
173 /***
174 * Handles invocation calls from the proxy. The synchronous version.
175 */
176 private final InvocationHandler eventInvocatorSync = new InvocationHandler() {
177 public Object invoke(Object proxy, Method method, Object[] args)
178 throws Throwable {
179 new InvokeHandler(method, args).run();
180 return null;
181 }
182 };
183
184 /***
185 * Invokes given method on a list of objects.
186 *
187 * @author Martin Vysny
188 */
189 private final class InvokeHandler implements Runnable {
190 private final String methodName;
191 private final Class<?>[] argTypes;
192 private final Object[] args;
193 private final Class<?> messageType;
194
195 /***
196 * Invokes given method on a list of instances.
197 *
198 * @param args
199 * arguments to take
200 * @param method
201 * the method to run
202 */
203 public InvokeHandler(final Method method, final Object[] args) {
204 super();
205 this.methodName = method.getName();
206 this.argTypes = method.getParameterTypes();
207 this.args = args;
208 messageType = method.getDeclaringClass();
209 }
210
211 public void run() {
212 final Map<Class<?>, Object> handlers = handlerMap.get(messageType);
213 final Collection<Object> instances = handlers == null ? Collections.emptyList()
214 : handlers.values();
215 if (instances == null) {
216 return;
217 }
218 for (final Object instance : instances) {
219 try {
220 final Method method = instance.getClass().getMethod(
221 methodName, argTypes);
222 method.invoke(instance, args);
223 } catch (Exception ex) {
224 Log.e(SimpleBus.class.getSimpleName(), "Error in handler",
225 ex);
226 }
227 }
228 }
229 }
230
231 /***
232 * Removes all handlers and cleans up the object.
233 */
234 public void clear() {
235 handlerMap.clear();
236 invocatorCacheAsync.clear();
237 invocatorCacheSync.clear();
238 }
239 }