001: // ============================================================================
002: // $Id: FunctorProxy.java,v 1.7 2006/11/30 05:06:10 davidahall Exp $
003: // Copyright (c) 2005 David A. Hall
004: // ============================================================================
005: // The contents of this file are subject to the Common Development and
006: // Distribution License (CDDL), Version 1.0 (the License); you may not use this
007: // file except in compliance with the License. You should have received a copy
008: // of the the License along with this file: if not, a copy of the License is
009: // available from Sun Microsystems, Inc.
010: //
011: // http://www.sun.com/cddl/cddl.html
012: //
013: // From time to time, the license steward (initially Sun Microsystems, Inc.) may
014: // publish revised and/or new versions of the License. You may not use,
015: // distribute, or otherwise make this file available under subsequent versions
016: // of the License.
017: //
018: // Alternatively, the contents of this file may be used under the terms of the
019: // GNU Lesser General Public License Version 2.1 or later (the "LGPL"), in which
020: // case the provisions of the LGPL are applicable instead of those above. If you
021: // wish to allow use of your version of this file only under the terms of the
022: // LGPL, and not to allow others to use your version of this file under the
023: // terms of the CDDL, indicate your decision by deleting the provisions above
024: // and replace them with the notice and other provisions required by the LGPL.
025: // If you do not delete the provisions above, a recipient may use your version
026: // of this file under the terms of either the CDDL or the LGPL.
027: //
028: // This library is distributed in the hope that it will be useful,
029: // but WITHOUT ANY WARRANTY; without even the implied warranty of
030: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
031: // ============================================================================
032:
033: package net.sf.jga.swing;
034:
035: import java.awt.Event;
036: import java.io.Serializable;
037: import java.lang.ref.Reference;
038: import java.lang.ref.WeakReference;
039: import java.lang.reflect.InvocationHandler;
040: import java.lang.reflect.InvocationTargetException;
041: import java.lang.reflect.Method;
042: import java.lang.reflect.Proxy;
043: import java.util.Collections;
044: import java.util.EventListener;
045: import java.util.HashMap;
046: import java.util.HashSet;
047: import java.util.Iterator;
048: import java.util.Map;
049: import java.util.Set;
050: import java.util.WeakHashMap;
051: import net.sf.jga.algorithms.Filter;
052: import net.sf.jga.fn.BinaryFunctor;
053: import net.sf.jga.fn.UnaryFunctor;
054: import net.sf.jga.parser.GenericParser;
055: import net.sf.jga.parser.ParseException;
056: import net.sf.jga.parser.UncheckedParseException;
057:
058: /**
059: * Factory that builds Proxy objects that map Functors to event notification methods.
060: * Using this proxy is a two step process: first build the proxy instance calling
061: * <tt>makeListenerFor</tt> or <tt>newProxyInstance</tt>; second, register the
062: * desired functors with the various registration methods.
063: * <p>
064: * Copyright © 2005 David A. Hall
065: */
066:
067: public class FunctorProxy {
068:
069: /**
070: * Manufactures a Proxy that can listen to any/all events that the given
071: * object may fire. The generated Proxy's invocation handler will be an
072: * instance FunctorInvocationHandler.
073: */
074: static public Proxy makeListenerFor(Object obj) {
075: return newProxyInstance(obj.getClass().getClassLoader(),
076: identifyListeners(obj.getClass()));
077: }
078:
079: /**
080: * Returns an instance of a proxy class for the specified interfaces that dispatches
081: * method invocations to functors registed via a FunctorInvocationHandler.
082: */
083: static public Proxy newProxyInstance(ClassLoader loader,
084: Class[] interfaces) {
085: return (Proxy) Proxy.newProxyInstance(loader, interfaces,
086: new FunctorInvocationHandler(interfaces));
087: }
088:
089: // ------------
090: // registration
091: // ------------
092:
093: /**
094: * Registers the functor described by the given expression, to be invoked by the proxy
095: * when a method with the given method is called. If the proxy supports more than one
096: * method with this name, then which one will be invoked is undefined.
097: *
098: * @throws NoSuchMethodException if no method with the given name is found
099: * @throws ParseException if the expression cannot be parsed
100: * @throws ClassCastException if the given proxy's handler is not a FunctorInvocationHandler
101: */
102: static public void register(Proxy proxy, String methodName,
103: String expression) throws ParseException,
104: NoSuchMethodException {
105: getFunctorInvocationHandler(proxy).register(methodName,
106: expression);
107: }
108:
109: /**
110: * Registers the given functor to be invoked by the proxy when a method with the given
111: * method is called. If the proxy supports more than one method with this name, then
112: * which one will be invoked is undefined.
113: *
114: * @throws NoSuchMethodException if no method with the given name is found
115: * @throws ClassCastException if the given proxy's handler is not a FunctorInvocationHandler
116: */
117: static public void register(Proxy proxy, String methodName,
118: UnaryFunctor functor) throws NoSuchMethodException {
119: getFunctorInvocationHandler(proxy)
120: .register(methodName, functor);
121: }
122:
123: /**
124: * Registers the given functor to be invoked by the proxy when the given method is called.
125: * @throws ClassCastException if the given proxy's handler is not a FunctorInvocationHandler
126: */
127: static public void register(Proxy proxy, Method method,
128: UnaryFunctor functor) {
129: getFunctorInvocationHandler(proxy).register(method, functor);
130: }
131:
132: /**
133: * Registers a functor to be evaluated when the given method is invoked.
134: * @throws IllegalArgumentException if the proxy does not implement the listenerClass
135: * @throws NoSuchMethodException if the proxy does not have a method with the given
136: * name that takes an argument of class eventClass
137: * @throws ClassCastException if the given proxy's handler is not a FunctorInvocationHandler
138: */
139: static public void register(Proxy proxy, Class listenerClass,
140: Class eventClass, String methodName, UnaryFunctor functor)
141: throws NoSuchMethodException, IllegalArgumentException {
142: if (listenerClass.isInstance(proxy)) {
143: Method m = listenerClass.getMethod(methodName,
144: new Class[] { eventClass });
145: getFunctorInvocationHandler(proxy).register(m, functor);
146: return;
147: }
148:
149: throw new IllegalArgumentException("This does not implement "
150: + listenerClass.getName());
151: }
152:
153: // ----------------------
154: // implementation details
155: // ----------------------
156:
157: // Maps 'Listenee' classes to the arrays of Listener interfaces they support
158: static private Map<Class<?>, WeakReference> proxyClasses = Collections
159: .synchronizedMap(new WeakHashMap<Class<?>, WeakReference>());
160:
161: // A predicate that is true when a method is an add${FOO}Listener
162: static private final UnaryFunctor<Method, Boolean> isAddListenerMethod = GenericParser
163: .parse("x.getName().matches(\"^add.*Listener$\")",
164: Method.class, Boolean.class);
165:
166: /**
167: * Returns a list of the ${FOO}Listener interfaces which an object of Class clasz
168: * may fire events (ie, the Listeners which such an object can add).
169: */
170: static private Class[] identifyListeners(Class<?> clasz) {
171: synchronized (proxyClasses) {
172: // See if we've already generated such a list: if so, return it.
173: Object value = proxyClasses.get(clasz);
174: if (value instanceof Reference) {
175: return (Class[]) ((Reference) value).get();
176: }
177:
178: // Otherwise, we'll need to build the list. For now, we can find all methods
179: // whose names are of the form "add${FOO}Listener", and that take a single
180: // argument of a type derived from EventListener.
181: Set<Class<?>> interfaceSet = new HashSet<Class<?>>();
182:
183: // for(Method m : Filter.filter(clasz.getMethods(), isAddListenerMethod)) {
184: Iterator<Method> iter = Filter.filter(clasz.getMethods(),
185: isAddListenerMethod).iterator();
186: while (iter.hasNext()) {
187: Method m = iter.next();
188: Class[] argtypes = m.getParameterTypes();
189: if (argtypes.length == 1) {
190: Class c = m.getParameterTypes()[0];
191: if (EventListener.class.isAssignableFrom(c)) {
192: interfaceSet.add(c);
193: }
194: }
195: }
196:
197: // Save ourselves the work next time around.
198: Class[] interfaceArray = interfaceSet
199: .toArray(new Class[interfaceSet.size()]);
200: proxyClasses.put(clasz, new WeakReference(interfaceArray));
201: return interfaceArray;
202: }
203: }
204:
205: /**
206: * @returns the FunctorInvocationHandler associated with the given proxy
207: * @throws ClassCastException if the given proxy's handler is not a FunctorInvocationHandler
208: */
209: static private FunctorInvocationHandler getFunctorInvocationHandler(
210: Proxy proxy) {
211: return (FunctorInvocationHandler) Proxy
212: .getInvocationHandler(proxy);
213: }
214:
215: /**
216: * The InvocationHandler for FunctorProxy instances. This handler allows individual
217: * methods to be mapped to Functors of the form UnaryFunctor<Event>. When the method
218: * (which must be an event notification method of one of the listener interfaces that
219: * the proxy object implements) is invoked, the event is passed to the appropriate
220: * functor for evaluation.
221: */
222: static public class FunctorInvocationHandler implements
223: InvocationHandler, Serializable {
224:
225: static final long serialVersionUID = -6537167128759416788L;
226:
227: // The interfaces for which this handler can route method invocations
228: private Class[] _interfaces;
229:
230: // Maps methods to the functors that will be invoked
231: private Map<Method, UnaryFunctor> _functors = new HashMap<Method, UnaryFunctor>();
232:
233: // Functor that returns true if a given method has the given name
234: static private final BinaryFunctor<Method, String, Boolean> isNameEq = GenericParser
235: .parse("x.getName() == y", Method.class, String.class,
236: Boolean.class);
237:
238: private FunctorInvocationHandler(Class[] interfaces) {
239: _interfaces = interfaces;
240: }
241:
242: /**
243: * Returns the first method found with the given name
244: * @throws NoSuchMethodException if no method with the given name is found
245: */
246: private Method getMethod(String name)
247: throws NoSuchMethodException {
248: UnaryFunctor<Method, Boolean> hasGivenName = isNameEq
249: .bind2nd(name);
250:
251: // Loop through the interfaces that this handler supports to find
252: // the one with the named method, and return the method if found
253:
254: // for (Class iface : _interfaces)
255: for (int i = 0; i < _interfaces.length; ++i) {
256: Class iface = _interfaces[i];
257:
258: // for(Method method : Iterables.filter(iface.getMethods(), hasGivenName))
259: Iterator<Method> iter = Filter.filter(
260: iface.getMethods(), hasGivenName).iterator();
261: if (iter.hasNext()) {
262: return iter.next();
263: }
264: }
265:
266: throw new NoSuchMethodException(name);
267: }
268:
269: /**
270: * Registers the functor described by the given expression, to be invoked a
271: * method with the given method is called. If this handler has more than one
272: * method with this name, then which one will be invoked is undefined.
273: *
274: * @throws NoSuchMethodException if no method with the given name is found
275: * @throws ParseException if the expression cannot be parsed
276: */
277: private void register(String methodName, String expression)
278: throws ParseException, NoSuchMethodException {
279: Method m = getMethod(methodName);
280: UnaryFunctor fn = GenericParser.parse(expression, m
281: .getParameterTypes()[0], m.getReturnType());
282:
283: register(m, fn);
284: }
285:
286: /**
287: * Registers the given functor, to be invoked a method with the given method
288: * is called. If this handler has more than one method with this name, then
289: * which one will be invoked is undefined.
290: *
291: * @throws NoSuchMethodException if no method with the given name is found
292: */
293: private void register(String methodName, UnaryFunctor functor)
294: throws NoSuchMethodException {
295: Method m = getMethod(methodName);
296: register(m, functor);
297: }
298:
299: /**
300: * Registers the given functor, to be invoked when the given method is called.
301: */
302: private void register(Method method, UnaryFunctor functor) {
303: _functors.put(method, functor);
304: }
305:
306: /**
307: */
308: private void unregister(Method method) {
309: _functors.remove(method);
310: }
311:
312: /**
313: */
314: private UnaryFunctor getFunctor(Method method) {
315: return _functors.get(method);
316: }
317:
318: // --------------------------------
319: // InvocationHandler Implementation
320: // --------------------------------
321:
322: /**
323: */
324: public Object invoke(Object object, Method method,
325: Object[] objectArray) throws Throwable {
326: UnaryFunctor functor = getFunctor(method);
327: if (functor != null) {
328: functor.fn(objectArray[0]);
329: }
330:
331: return null;
332: }
333: }
334: }
|