| /* java.beans.EventHandler |
| Copyright (C) 2004, 2005 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package java.beans; |
| |
| import java.lang.reflect.InvocationHandler; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Proxy; |
| |
| /** |
| * <p>EventHandler forms a bridge between dynamically created listeners and |
| * arbitrary properties and methods.</p> |
| * |
| * <p>You can use this class to easily create listener implementations for |
| * some basic interactions between an event source and its target. Using |
| * the three static methods named <code>create</code> you can create |
| * these listener implementations.</p> |
| * |
| * <p>See the documentation of each method for usage examples.</p> |
| * |
| * @author Jerry Quinn (jlquinn@optonline.net) |
| * @author Robert Schuster (thebohemian@gmx.net) |
| * @since 1.4 |
| */ |
| public class EventHandler implements InvocationHandler |
| { |
| // The name of the method that will be implemented. If null, any method. |
| private String listenerMethod; |
| |
| // The object to call action on. |
| private Object target; |
| |
| // The name of the method or property setter in target. |
| private String action; |
| |
| // The property to extract from an event passed to listenerMethod. |
| private String property; |
| |
| // The target objects Class. |
| private Class targetClass; |
| |
| // String class doesn't already have a capitalize routine. |
| private String capitalize(String s) |
| { |
| return s.substring(0, 1).toUpperCase() + s.substring(1); |
| } |
| |
| /** |
| * Creates a new <code>EventHandler</code> instance. |
| * |
| * <p>Typical creation is done with the create method, not by knewing an |
| * EventHandler.</p> |
| * |
| * <p>This constructs an EventHandler that will connect the method |
| * listenerMethodName to target.action, extracting eventPropertyName from |
| * the first argument of listenerMethodName. and sending it to action.</p> |
| * |
| * <p>Throws a <code>NullPointerException</code> if the <code>target</code> |
| * argument is <code>null</code>. |
| * |
| * @param target Object that will perform the action. |
| * @param action A property or method of the target. |
| * @param eventPropertyName A readable property of the inbound event. |
| * @param listenerMethodName The listener method name triggering the action. |
| */ |
| public EventHandler(Object target, String action, String eventPropertyName, |
| String listenerMethodName) |
| { |
| this.target = target; |
| |
| // Retrieving the class is done for two reasons: |
| // 1) The class object is needed very frequently in the invoke() method. |
| // 2) The constructor should throw a NullPointerException if target is null. |
| targetClass = target.getClass(); |
| |
| this.action = action; // Turn this into a method or do we wait till |
| // runtime |
| property = eventPropertyName; |
| listenerMethod = listenerMethodName; |
| } |
| |
| /** |
| * Returns the event property name. |
| */ |
| public String getEventPropertyName() |
| { |
| return property; |
| } |
| |
| /** |
| * Returns the listener's method name. |
| */ |
| public String getListenerMethodName() |
| { |
| return listenerMethod; |
| } |
| |
| /** |
| * Returns the target object. |
| */ |
| public Object getTarget() |
| { |
| return target; |
| } |
| |
| /** |
| * Returns the action method name. |
| */ |
| public String getAction() |
| { |
| return action; |
| } |
| |
| // Fetch a qualified property like a.b.c from object o. The properties can |
| // be boolean isProp or object getProp properties. |
| // |
| // Returns a length 2 array with the first entry containing the value |
| // extracted from the property, and the second entry contains the class of |
| // the method return type. |
| // |
| // We play this game because if the method returns a native type, the return |
| // value will be a wrapper. If we then take the type of the wrapper and use |
| // it to locate the action method that takes the native type, it won't match. |
| private Object[] getProperty(Object o, String prop) |
| { |
| // Isolate the first property name from a.b.c. |
| int pos; |
| String rest = null; |
| if ((pos = prop.indexOf('.')) != -1) |
| { |
| rest = prop.substring(pos + 1); |
| prop = prop.substring(0, pos); |
| } |
| |
| // Find a method named getProp. It could be isProp instead. |
| Method getter; |
| try |
| { |
| // Look for boolean property getter isProperty |
| getter = o.getClass().getMethod("is" + capitalize(prop), |
| null); |
| } |
| catch (NoSuchMethodException nsme1) |
| { |
| try { |
| // Look for regular property getter getProperty |
| getter = o.getClass().getMethod("get" + capitalize(prop), |
| null); |
| } catch(NoSuchMethodException nsme2) { |
| try { |
| // Finally look for a method of the name prop |
| getter = o.getClass().getMethod(prop, null); |
| } catch(NoSuchMethodException nsme3) { |
| // Ok, give up with an intelligent hint for the user. |
| throw new RuntimeException("Method not called: Could not find a property or method '" + prop |
| + "' in " + o.getClass() + " while following the property argument '" + property + "'."); |
| } |
| } |
| } |
| try { |
| Object val = getter.invoke(o, null); |
| |
| if (rest != null) |
| return getProperty(val, rest); |
| |
| return new Object[] {val, getter.getReturnType()}; |
| } catch(InvocationTargetException ite) { |
| throw new RuntimeException("Method not called: Property or method '" + prop + "' has thrown an exception.", ite); |
| } catch(IllegalAccessException iae) { |
| // This cannot happen because we looked up method with Class.getMethod() |
| // which returns public methods only. |
| throw (InternalError) new InternalError("Non-public method was invoked.").initCause(iae); |
| } |
| } |
| |
| /** |
| * Invokes the <code>EventHandler</code>. |
| * |
| * <p>This method is normally called by the listener's proxy implementation.</p> |
| * |
| * @param proxy The listener interface that is implemented using |
| * the proxy mechanism. |
| * @param method The method that was called on the proxy instance. |
| * @param arguments The arguments which where given to the method. |
| * @throws Throwable <code>NoSuchMethodException</code> is thrown when the EventHandler's |
| * action method or property cannot be found. |
| */ |
| public Object invoke(Object proxy, Method method, Object[] arguments) |
| { |
| try { |
| // The method instance of the target object. We have to find out which |
| // one we have to invoke. |
| Method actionMethod = null; |
| |
| // Listener methods that weren't specified are ignored. If listenerMethod |
| // is null, then all listener methods are processed. |
| if (listenerMethod != null && !method.getName().equals(listenerMethod)) |
| return null; |
| |
| // If a property is defined we definitely need a valid object at |
| // arguments[0] that can be used to retrieve a value to which the |
| // property of the target gets set. |
| if(property != null) { |
| // Extracts the argument. We will let it fail with a NullPointerException |
| // the caller used a listener method that has no arguments. |
| Object event = arguments[0]; |
| |
| // Obtains the property XXX propertyType keeps showing up null - why? |
| // because the object inside getProperty changes, but the ref variable |
| // can't change this way, dolt! need a better way to get both values out |
| // - need method and object to do the invoke and get return type |
| Object v[] = getProperty(event, property); |
| Object[] args = new Object[] { v[0] }; |
| |
| // Changes the class array that controls which method signature we are going |
| // to look up in the target object. |
| Class[] argTypes = new Class[] { initClass((Class) v[1]) }; |
| |
| // Tries to find a setter method to which we can apply the |
| while(argTypes[0] != null) { |
| try |
| { |
| // Look for a property setter for action. |
| actionMethod = targetClass.getMethod("set" + capitalize(action), argTypes); |
| |
| return actionMethod.invoke(target, args); |
| } |
| catch (NoSuchMethodException e) |
| { |
| // If action as property didn't work, try as method later. |
| } |
| |
| argTypes[0] = nextClass(argTypes[0]); |
| } |
| |
| // We could not find a suitable setter method. Now we try again interpreting |
| // action as the method name itself. |
| // Since we probably have changed the block local argTypes array |
| // we need to rebuild it. |
| argTypes = new Class[] { initClass((Class) v[1]) }; |
| |
| // Tries to find a setter method to which we can apply the |
| while(argTypes[0] != null) { |
| try |
| { |
| actionMethod = targetClass.getMethod(action, argTypes); |
| |
| return actionMethod.invoke(target, args); |
| } |
| catch (NoSuchMethodException e) |
| { |
| } |
| |
| argTypes[0] = nextClass(argTypes[0]); |
| } |
| |
| throw new RuntimeException("Method not called: Could not find a public method named '" |
| + action + "' in target " + targetClass + " which takes a '" |
| + v[1] + "' argument or a property of this type."); |
| } |
| |
| // If property was null we will search for a no-argument method here. |
| // Note: The ordering of method lookups is important because we want to prefer no-argument |
| // calls like the JDK does. This means if we have actionMethod() and actionMethod(Event) we will |
| // call the first *EVEN* if we have a valid argument for the second method. This is behavior compliant |
| // to the JDK. |
| // If actionMethod() is not available but there is a actionMethod(Event) we take this. That makes us |
| // more specification compliant than the JDK itself because this one will fail in such a case. |
| try |
| { |
| actionMethod = targetClass.getMethod(action, null); |
| } |
| catch(NoSuchMethodException nsme) |
| { |
| // Note: If we want to be really strict the specification says that a no-argument method should |
| // accept an EventObject (or subclass I guess). However since the official implementation is broken |
| // anyways, it's more flexible without the EventObject restriction and we are compatible on everything |
| // else this can stay this way. |
| if(arguments != null && arguments.length >= 1/* && arguments[0] instanceof EventObject*/) { |
| Class[] targetArgTypes = new Class[] { initClass(arguments[0].getClass()) }; |
| |
| while(targetArgTypes[0] != null) { |
| try |
| { |
| // If no property exists we expect the first element of the arguments to be |
| // an EventObject which is then applied to the target method. |
| |
| actionMethod = targetClass.getMethod(action, targetArgTypes); |
| |
| return actionMethod.invoke(target, new Object[] { arguments[0] }); |
| } |
| catch(NoSuchMethodException nsme2) |
| { |
| |
| } |
| |
| targetArgTypes[0] = nextClass(targetArgTypes[0]); |
| } |
| |
| } |
| } |
| |
| // If we do not have a Method instance at this point this means that all our tries |
| // failed. The JDK throws an ArrayIndexOutOfBoundsException in this case. |
| if(actionMethod == null) |
| throw new ArrayIndexOutOfBoundsException(0); |
| |
| // Invoke target.action(property) |
| return actionMethod.invoke(target, null); |
| } catch(InvocationTargetException ite) { |
| throw new RuntimeException(ite.getCause()); |
| } catch(IllegalAccessException iae) { |
| // Cannot happen because we always use getMethod() which returns public |
| // methods only. Otherwise there is something seriously broken in |
| // GNU Classpath. |
| throw (InternalError) new InternalError("Non-public method was invoked.").initCause(iae); |
| } |
| } |
| |
| /** |
| * <p>Returns the primitive type for every wrapper class or the |
| * class itself if it is no wrapper class.</p> |
| * |
| * <p>This is needed because to be able to find both kinds of methods: |
| * One that takes a wrapper class as the first argument and one that |
| * accepts a primitive instead.</p> |
| */ |
| private Class initClass(Class klass) { |
| if(klass == Boolean.class) { |
| return Boolean.TYPE; |
| } else if(klass == Byte.class) { |
| return Byte.TYPE; |
| } else if(klass == Short.class) { |
| return Short.TYPE; |
| } else if(klass == Integer.class) { |
| return Integer.TYPE; |
| } else if(klass == Long.class) { |
| return Long.TYPE; |
| } else if(klass == Float.class) { |
| return Float.TYPE; |
| } else if(klass == Double.class) { |
| return Double.TYPE; |
| } else { |
| return klass; |
| } |
| } |
| |
| /** |
| * |
| * |
| * @param klass |
| * @return |
| */ |
| private Class nextClass(Class klass) { |
| if(klass == Boolean.TYPE) { |
| return Boolean.class; |
| } else if(klass == Byte.TYPE) { |
| return Byte.class; |
| } else if(klass == Short.TYPE) { |
| return Short.class; |
| } else if(klass == Integer.TYPE) { |
| return Integer.class; |
| } else if(klass == Long.TYPE) { |
| return Long.class; |
| } else if(klass == Float.TYPE) { |
| return Float.class; |
| } else if(klass == Double.TYPE) { |
| return Double.class; |
| } else { |
| return klass.getSuperclass(); |
| } |
| } |
| |
| /** |
| * <p>Constructs an implementation of <code>listenerInterface</code> |
| * to dispatch events.</p> |
| * |
| * <p>You can use such an implementation to simply call a public |
| * no-argument method of an arbitrary target object or to forward |
| * the first argument of the listener method to the target method.</p> |
| * |
| * <p>Call this method like:</p> |
| * <code> |
| * button.addActionListener((ActionListener) |
| * EventHandler.create(ActionListener.class, target, "dispose")); |
| * </code> |
| * |
| * <p>to achieve the following behavior:</p> |
| * <code> |
| * button.addActionListener(new ActionListener() { |
| * public void actionPerformed(ActionEvent ae) { |
| * target.dispose(); |
| * } |
| * }); |
| * </code> |
| * |
| * <p>That means if you need a listener implementation that simply calls a |
| * a no-argument method on a given instance for <strong>each</strong> |
| * method of the listener interface.</p> |
| * |
| * <p>Note: The <code>action</code> is interpreted as a method name. If your target object |
| * has no no-argument method of the given name the EventHandler tries to find |
| * a method with the same name but which can accept the first argument of the |
| * listener method. Usually this will be an event object but any other object |
| * will be forwarded, too. Keep in mind that using a property name instead of a |
| * real method here is wrong and will throw an <code>ArrayIndexOutOfBoundsException</code> |
| * whenever one of the listener methods is called.<p/> |
| * |
| * <p>The <code>EventHandler</code> will automatically convert primitives |
| * to their wrapper class and vice versa. Furthermore it will call |
| * a target method if it accepts a superclass of the type of the |
| * first argument of the listener method.</p> |
| * |
| * <p>In case that the method of the target object throws an exception |
| * it will be wrapped in a <code>RuntimeException</code> and thrown out |
| * of the listener method.</p> |
| * |
| * <p>In case that the method of the target object cannot be found an |
| * <code>ArrayIndexOutOfBoundsException</code> will be thrown when the |
| * listener method is invoked.</p> |
| * |
| * <p>A call to this method is equivalent to: |
| * <code>create(listenerInterface, target, action, null, null)</code></p> |
| * |
| * @param listenerInterface Listener interface to implement. |
| * @param target Object to invoke action on. |
| * @param action Target property or method to invoke. |
| * @return A constructed proxy object. |
| */ |
| public static Object create(Class listenerInterface, Object target, String action) |
| { |
| return create(listenerInterface, target, action, null, null); |
| } |
| |
| /** |
| * <p>Constructs an implementation of <code>listenerInterface</code> |
| * to dispatch events.</p> |
| * |
| * <p>Use this method if you want to create an implementation that retrieves |
| * a property value from the <b>first</b> argument of the listener method |
| * and applies it to the target's property or method. This first argument |
| * of the listener is usually an event object but any other object is |
| * valid, too.</p> |
| * |
| * <p>You can set the value of <code>eventPropertyName</code> to "prop" |
| * to denote the retrieval of a property named "prop" from the event |
| * object. In case that no such property exists the <code>EventHandler</code> |
| * will try to find a method with that name.</p> |
| * |
| * <p>If you set <code>eventPropertyName</code> to a value like this "a.b.c" |
| * <code>EventHandler</code> will recursively evaluate the properties "a", "b" |
| * and "c". Again if no property can be found the <code>EventHandler</code> |
| * tries a method name instead. This allows mixing the names, too: "a.toString" |
| * will retrieve the property "a" from the event object and will then call |
| * the method "toString" on it.</p> |
| * |
| * <p>An exception thrown in any of these methods will provoke a |
| * <code>RuntimeException</code> to be thrown which contains an |
| * <code>InvocationTargetException</code> containing the triggering exception.</p> |
| * |
| * <p>If you set <code>eventPropertyName</code> to a non-null value the |
| * <code>action</code> parameter will be interpreted as a property name |
| * or a method name of the target object.</p> |
| * |
| * <p>Any object retrieved from the event object and applied to the |
| * target will converted from primitives to their wrapper class or |
| * vice versa or applied to a method that accepts a superclass |
| * of the object.</p> |
| * |
| * <p>Examples:</p> |
| * <p>The following code:</p><code> |
| * button.addActionListener( |
| * new ActionListener() { |
| * public void actionPerformed(ActionEvent ae) { |
| * Object o = ae.getSource().getClass().getName(); |
| * textField.setText((String) o); |
| * } |
| * }); |
| * </code> |
| * |
| * <p>Can be expressed using the <code>EventHandler</code> like this:</p> |
| * <p> |
| * <code>button.addActionListener((ActionListener) |
| * EventHandler.create(ActionListener.class, textField, "text", "source.class.name"); |
| * <code> |
| * </p> |
| * |
| * <p>As said above you can specify the target as a method, too:</p> |
| * <p> |
| * <code>button.addActionListener((ActionListener) |
| * EventHandler.create(ActionListener.class, textField, "setText", "source.class.name"); |
| * <code> |
| * </p> |
| * |
| * <p>Furthermore you can use method names in the property:</p> |
| * <p> |
| * <code>button.addActionListener((ActionListener) |
| * EventHandler.create(ActionListener.class, textField, "setText", "getSource.getClass.getName"); |
| * <code> |
| * </p> |
| * |
| * <p>Finally you can mix names:</p> |
| * <p> |
| * <code>button.addActionListener((ActionListener) |
| * EventHandler.create(ActionListener.class, textField, "setText", "source.getClass.name"); |
| * <code> |
| * </p> |
| * |
| * <p>A call to this method is equivalent to: |
| * <code>create(listenerInterface, target, action, null, null)</code> |
| * </p> |
| * |
| * @param listenerInterface Listener interface to implement. |
| * @param target Object to invoke action on. |
| * @param action Target property or method to invoke. |
| * @param eventPropertyName Name of property to extract from event. |
| * @return A constructed proxy object. |
| */ |
| public static Object create(Class listenerInterface, Object target, |
| String action, String eventPropertyName) |
| { |
| return create(listenerInterface, target, action, eventPropertyName, null); |
| } |
| |
| /** |
| * <p>Constructs an implementation of <code>listenerInterface</code> |
| * to dispatch events.</p> |
| * |
| * <p>Besides the functionality described for {@link create(Class, Object, String)} |
| * and {@link create(Class, Object, String, String)} this method allows you |
| * to filter the listener method that should have an effect. Look at these |
| * method's documentation for more information about the <code>EventHandler</code>'s |
| * usage.</p> |
| * |
| * <p>If you want to call <code>dispose</code> on a <code>JFrame</code> instance |
| * when the <code>WindowListener.windowClosing()</code> method was invoked use |
| * the following code:</p> |
| * <p> |
| * <code> |
| * EventHandler.create(WindowListener.class, jframeInstance, "dispose", null, "windowClosing"); |
| * </code> |
| * </p> |
| * |
| * <p>A <code>NullPointerException</code> is thrown if the <code>listenerInterface</code> |
| * or <code>target</code> argument are <code>null</code>. |
| * |
| * @param listenerInterface Listener interface to implement. |
| * @param target Object to invoke action on. |
| * @param action Target method name to invoke. |
| * @param eventPropertyName Name of property to extract from event. |
| * @param listenerMethodName Listener method to implement. |
| * @return A constructed proxy object. |
| */ |
| public static Object create(Class listenerInterface, Object target, |
| String action, String eventPropertyName, |
| String listenerMethodName) |
| { |
| // Create EventHandler instance |
| EventHandler eh = new EventHandler(target, action, eventPropertyName, |
| listenerMethodName); |
| |
| // Create proxy object passing in the event handler |
| Object proxy = Proxy.newProxyInstance(listenerInterface.getClassLoader(), |
| new Class[] {listenerInterface}, |
| eh); |
| |
| return proxy; |
| } |
| |
| } |