| /* 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; | |
| } | |
| } |