From 12e8e7ea6db5d330599ac86ec971d228e2c2a59b Mon Sep 17 00:00:00 2001 From: Jerry Quinn Date: Wed, 14 Jul 2004 22:58:30 +0000 Subject: [PATCH] EventHandler.java: New file. 2004-07-14 Jerry Quinn * java/beans/EventHandler.java: New file. * Makefile.am (awt_java_source_files): Add EventHandler.java. * Makefile.in, gcj/Makefile.in, include/Makefile.in, testsuite/Makefile.in: Regenerate. From-SVN: r84714 --- libjava/ChangeLog | 9 +- libjava/Makefile.am | 1 + libjava/Makefile.in | 6 +- libjava/gcj/Makefile.in | 8 +- libjava/include/Makefile.in | 8 +- libjava/java/beans/EventHandler.java | 393 +++++++++++++++++++++++++++++++++++ libjava/testsuite/Makefile.in | 2 +- 7 files changed, 415 insertions(+), 12 deletions(-) create mode 100644 libjava/java/beans/EventHandler.java diff --git a/libjava/ChangeLog b/libjava/ChangeLog index 2ef5d03..172c372 100644 --- a/libjava/ChangeLog +++ b/libjava/ChangeLog @@ -1,3 +1,10 @@ +2004-07-14 Jerry Quinn + + * java/beans/EventHandler.java: New file. + * Makefile.am (awt_java_source_files): Add EventHandler.java. + * Makefile.in, gcj/Makefile.in, include/Makefile.in, + testsuite/Makefile.in: Regenerate. + 2004-07-14 Andreas Tobler * testsuite/libjava.jacks/jacks.exp (gcj_jacks_write): Add deprecation @@ -20,7 +27,7 @@ 2004-07-14 Michael Koch Matthias Klose - * java/awt/im/InputContext.java: Initialze in, line. + * java/awt/im/InputContext.java: Initialize in, line. 2004-07-13 Ulrich Weigand diff --git a/libjava/Makefile.am b/libjava/Makefile.am index dea851d..fb77561 100644 --- a/libjava/Makefile.am +++ b/libjava/Makefile.am @@ -1284,6 +1284,7 @@ java/beans/BeanInfo.java \ java/beans/Beans.java \ java/beans/Customizer.java \ java/beans/DesignMode.java \ +java/beans/EventHandler.java \ java/beans/EventSetDescriptor.java \ java/beans/ExceptionListener.java \ java/beans/Expression.java \ diff --git a/libjava/Makefile.in b/libjava/Makefile.in index 1e90c35..d9c8a5f 100644 --- a/libjava/Makefile.in +++ b/libjava/Makefile.in @@ -963,6 +963,7 @@ java/beans/BeanInfo.java \ java/beans/Beans.java \ java/beans/Customizer.java \ java/beans/DesignMode.java \ +java/beans/EventHandler.java \ java/beans/EventSetDescriptor.java \ java/beans/ExceptionListener.java \ java/beans/Expression.java \ @@ -3075,7 +3076,7 @@ libgcj-test.spec.in libgcj.pc.in libgcj.spec.in DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST) -TAR = gtar +TAR = tar GZIP_ENV = --best DIST_SUBDIRS = @DIRLTDL@ testsuite gcj include @DIRLTDL@ gcj include DEP_FILES = .deps/$(srcdir)/$(CONVERT_DIR)/gen-from-JIS.P \ @@ -3732,7 +3733,8 @@ DEP_FILES = .deps/$(srcdir)/$(CONVERT_DIR)/gen-from-JIS.P \ .deps/java/awt/print/PrinterJob.P .deps/java/beans/AppletInitializer.P \ .deps/java/beans/BeanDescriptor.P .deps/java/beans/BeanInfo.P \ .deps/java/beans/Beans.P .deps/java/beans/Customizer.P \ -.deps/java/beans/DesignMode.P .deps/java/beans/EventSetDescriptor.P \ +.deps/java/beans/DesignMode.P .deps/java/beans/EventHandler.P \ +.deps/java/beans/EventSetDescriptor.P \ .deps/java/beans/ExceptionListener.P .deps/java/beans/Expression.P \ .deps/java/beans/FeatureDescriptor.P \ .deps/java/beans/IndexedPropertyDescriptor.P \ diff --git a/libjava/gcj/Makefile.in b/libjava/gcj/Makefile.in index 9a262ce..1e4e84a 100644 --- a/libjava/gcj/Makefile.in +++ b/libjava/gcj/Makefile.in @@ -1,6 +1,6 @@ -# Makefile.in generated automatically by automake 1.4-p6 from Makefile.am +# Makefile.in generated automatically by automake 1.4 from Makefile.am -# Copyright (C) 1994, 1995-8, 1999, 2001 Free Software Foundation, Inc. +# Copyright (C) 1994, 1995-8, 1999 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. @@ -172,7 +172,7 @@ DIST_COMMON = ./stamp-h2.in Makefile.am Makefile.in libgcj-config.h.in DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST) -TAR = gtar +TAR = tar GZIP_ENV = --best all: all-redirect .SUFFIXES: @@ -260,7 +260,7 @@ TAGS: $(HEADERS) $(SOURCES) libgcj-config.h.in $(TAGS_DEPENDENCIES) $(LISP) awk ' { files[$$0] = 1; } \ END { for (i in files) print i; }'`; \ test -z "$(ETAGS_ARGS)libgcj-config.h.in$$unique$(LISP)$$tags" \ - || (cd $(srcdir) && etags -o $$here/TAGS $(ETAGS_ARGS) $$tags libgcj-config.h.in $$unique $(LISP)) + || (cd $(srcdir) && etags $(ETAGS_ARGS) $$tags libgcj-config.h.in $$unique $(LISP) -o $$here/TAGS) mostlyclean-tags: diff --git a/libjava/include/Makefile.in b/libjava/include/Makefile.in index e1e0955..72e0a25 100644 --- a/libjava/include/Makefile.in +++ b/libjava/include/Makefile.in @@ -1,6 +1,6 @@ -# Makefile.in generated automatically by automake 1.4-p6 from Makefile.am +# Makefile.in generated automatically by automake 1.4 from Makefile.am -# Copyright (C) 1994, 1995-8, 1999, 2001 Free Software Foundation, Inc. +# Copyright (C) 1994, 1995-8, 1999 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. @@ -168,7 +168,7 @@ DIST_COMMON = ./stamp-h1.in Makefile.am Makefile.in config.h.in DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST) -TAR = gtar +TAR = tar GZIP_ENV = --best all: all-redirect .SUFFIXES: @@ -241,7 +241,7 @@ TAGS: $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) $(LISP) awk ' { files[$$0] = 1; } \ END { for (i in files) print i; }'`; \ test -z "$(ETAGS_ARGS)config.h.in$$unique$(LISP)$$tags" \ - || (cd $(srcdir) && etags -o $$here/TAGS $(ETAGS_ARGS) $$tags config.h.in $$unique $(LISP)) + || (cd $(srcdir) && etags $(ETAGS_ARGS) $$tags config.h.in $$unique $(LISP) -o $$here/TAGS) mostlyclean-tags: diff --git a/libjava/java/beans/EventHandler.java b/libjava/java/beans/EventHandler.java new file mode 100644 index 0000000..6a7d185 --- /dev/null +++ b/libjava/java/beans/EventHandler.java @@ -0,0 +1,393 @@ +/* java.beans.EventHandler + Copyright (C) 2004 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., 59 Temple Place, Suite 330, Boston, MA +02111-1307 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.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * class EventHandler + * + * EventHandler forms a bridge between dynamically created listeners and + * arbitrary properties and methods. The idea is that a Proxy that implements + * a listener class calls the EventHandler when a listener method is called. + * The Proxy calls invoke(), which dispatches the event to a method, called + * the action, in another object, called the target. + * + * The event passed to the listener method is used to access a prespecified + * property, which in turn is passed to the action method. + * + * Normally, call EventHandler.create(), which constructs an EventHandler and + * a Proxy for the listener interface. When the listenerMethod gets called on + * the proxy, it in turn calls invoke on the attached EventHandler. The + * invoke call extracts the bean property from the event object and passes it + * to the action method of target object. + * + * TODO: Add examples of using this thing. + * + * @author Jerry Quinn (jlquinn@optonline.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; + + // String class doesn't already have a capitalize routine. + final private String capitalize(String s) + { + return s.substring(0, 1).toUpperCase() + s.substring(1); + } + + /** + * Creates a new EventHandler instance. + * + * Typical creation is done with the create method, not by newing an + * EventHandler. + * + * 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. + * + * + * + * @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; + this.action = action; // Turn this into a method or do we wait till + // runtime + property = eventPropertyName; + listenerMethod = listenerMethodName; + } + + /** + * Return the event property name. + */ + public String getEventPropertyName() + { + return property; + } + + /** + * Return the listener's method name. + */ + public String getListenerMethodName() + { + return listenerMethod; + } + + /** + * Return the target object. + */ + public Object getTarget() + { + return target; + } + + /** + * Return 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) + throws NoSuchMethodException, IllegalAccessException, InvocationTargetException + { + // Use the event object when the property name to extract is null. + if (prop == null) + return new Object[] {o, o.getClass()}; + + // 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 e) + { + // Look for regular property getter getProperty + getter = o.getClass().getMethod("get" + capitalize(prop), + null); + } + Object val = getter.invoke(o, null); + + if (rest != null) + return getProperty(val, rest); + + return new Object[] {val, getter.getReturnType()}; + } + + + /** + * Invoke the event handler. + * + * Proxy is the object that was used, method is the method that was invoked + * on object, and arguments is the set of arguments passed to this method. + * We assume that the first argument is the event to extract a property + * from. + * + * Assuming that method matches the listener method specified when creating + * this EventHandler, the desired property is extracted from this argument. + * The property is passed to target.setAction(), if possible. Otherwise + * target.action() is called, where action is the string fed to the + * constructor. + * + * For now we punt on indexed properties. Sun docs are not clear to me + * about this. + * + * @param proxy The proxy object that had method invoked on it. + * @param method The method that was invoked. + * @param arguments Arguments to method. + * @return Result of invoking target.action on the event property + */ + public Object invoke(Object proxy, Method method, Object[] arguments) + throws Exception + { + // Do we actually need the proxy? + if (method == null) + throw new RuntimeException("Invoking null method"); + + // 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; + + // Extract the first arg from arguments and do getProperty on arg + if (arguments == null || arguments.length == 0) + return null; + Object event = arguments[0]; // We hope :-) + + // Obtain 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 val = v[0]; + Class propertyType = (Class) v[1]; + + System.out.println("ptype="+propertyType.getName()); + System.out.println(" val="+((val==null)?"null":val.toString())); + + // Find the actual method of target to invoke. We can't do this in the + // constructor since we don't know the type of the property we extracted + // from the event then. + // + // action can be either a property or a method. Sun's docs seem to imply + // that action should be treated as a property first, and then a method, + // but don't specifically say it. + // + // XXX check what happens with native type wrappers. The better thing to + // do is look at the return type of the method + Method actionMethod; + try + { + // Look for a property setter for action. + actionMethod = + target.getClass().getMethod("set" + capitalize(action), + new Class[] {propertyType}); + } + catch (NoSuchMethodException e) + { + // If action as property didn't work, try as method. + try + { + actionMethod = + target.getClass().getMethod(action, new Class[] {propertyType}); + } + catch (NoSuchMethodException e1) + { + // When event property is null, we may call action with no args + if (property == null) + { + actionMethod = + target.getClass().getMethod(action, null); + return actionMethod.invoke(target, null); + } + else + throw e1; + } + } + + // Invoke target.action(property) + return actionMethod.invoke(target, new Object[] {val}); + } + + /** + * Construct a new object to dispatch events. + * + * Equivalent to: + * create(listenerInterface, target, action, null, null) + * + * I.e. all listenerInterface methods are mapped to + * target.action(EventObject) or target.action(), if the first doesn't + * exist. + * + * @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); + } + + /** + * Construct a new object to dispatch events. + * + * Equivalent to: + * create(listenerInterface, target, action, eventPropertyName, null) + * + * I.e. all listenerInterface methods are mapped to + * target.action(event.getEventPropertyName) + * + * + * @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); + } + + + /** + * Construct a new object to dispatch events. + * + * This creates an object that acts as a proxy for the method + * listenerMethodName in listenerInterface. When the listener method is + * activated, the object extracts eventPropertyName from the event. Then it + * passes the property to the method target.setAction, or target.action if + * action is not a property with a setter. + * + * For example, EventHandler.create(MouseListener.class, test, "pushed", + * "button", "mouseClicked") generates a proxy object that implements + * MouseListener, at least for the method mouseClicked(). The other methods + * of MouseListener are null operations. When mouseClicked is invoked, the + * generated object extracts the button property from the MouseEvent, + * i.e. event.getButton(), and calls test.setPushed() with the result. So under + * the covers the following happens: + * + * + * object.mouseClicked(MouseEvent e) { test.setPushed(e.getButton()); } + * + * + * The Sun spec specifies a hierarchical property naming scheme. Generally + * if the property is a.b.c, this corresponds to event.getA().getB().getC() + * or event.getA().getB().isC(). I don't see how you specify an indexed + * property, though. This may be a limitation of the Sun implementation as + * well. The spec doesn't seem to address it. + * + * If eventPropertyName is null, EventHandler instead uses the event object + * in place of a property, i.e. it calls target.action(EventObject). If + * there is no method named action taking an EventObject argument, + * EventHandler looks for a method target.action() taking no arguments. + * + * If listenerMethodName is null, every method in listenerInterface gets + * mapped to target.action, rather than the specified listener method. + * + * @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; + } + +} diff --git a/libjava/testsuite/Makefile.in b/libjava/testsuite/Makefile.in index 21c86dd..79a8283 100644 --- a/libjava/testsuite/Makefile.in +++ b/libjava/testsuite/Makefile.in @@ -177,7 +177,7 @@ DIST_COMMON = Makefile.am Makefile.in DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST) -TAR = gtar +TAR = tar GZIP_ENV = --best all: all-redirect .SUFFIXES: -- 2.7.4