View Javadoc

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 }