This is the mail archive of the java-patches@gcc.gnu.org mailing list for the Java project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[patch] java.beans.EventHandler


Here's an implementation for java.beans.EventHandler.  As with other stuff,
there's a bit of fuzz in Sun's docs.  I did the best I could.

Mauve test committed and passed.

OK for mainline?


2004-07-13  Jerry Quinn  <jlquinn@optonline.net>

	* 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.

--- /dev/null   2004-06-16 22:59:06.000000000 -0400
+++ java/beans/EventHandler.java        2004-07-13 23:11:27.000000000 -0400
@@ -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 <code>EventHandler</code> 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:
+   *
+   * <CODE>
+   * object.mouseClicked(MouseEvent e) { test.setPushed(e.getButton()); }
+   * </CODE>
+   *
+   * 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;
+  }
+
+}
cvs diff: Diffing .
Index: Makefile.am
===================================================================
RCS file: /cvs/gcc/gcc/libjava/Makefile.am,v
retrieving revision 1.387
diff -u -r1.387 Makefile.am
--- Makefile.am	9 Jul 2004 21:00:08 -0000	1.387
+++ Makefile.am	14 Jul 2004 03:42:27 -0000
@@ -1284,6 +1284,7 @@
 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 \
Index: Makefile.in
===================================================================
RCS file: /cvs/gcc/gcc/libjava/Makefile.in,v
retrieving revision 1.411
diff -u -r1.411 Makefile.in
--- Makefile.in	9 Jul 2004 21:00:09 -0000	1.411
+++ Makefile.in	14 Jul 2004 03:42:30 -0000
@@ -963,6 +963,7 @@
 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 @@
 
 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 @@
 .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 \
cvs diff: Diffing gcj
Index: gcj/Makefile.in
===================================================================
RCS file: /cvs/gcc/gcc/libjava/gcj/Makefile.in,v
retrieving revision 1.44
diff -u -r1.44 Makefile.in
--- gcj/Makefile.in	17 Mar 2004 21:28:56 -0000	1.44
+++ gcj/Makefile.in	14 Jul 2004 03:42:30 -0000
@@ -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 @@
 
 DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST)
 
-TAR = gtar
+TAR = tar
 GZIP_ENV = --best
 all: all-redirect
 .SUFFIXES:
@@ -260,7 +260,7 @@
 	  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:
 
Index: include/Makefile.in
===================================================================
RCS file: /cvs/gcc/gcc/libjava/include/Makefile.in,v
retrieving revision 1.45
diff -u -r1.45 Makefile.in
--- include/Makefile.in	17 Mar 2004 21:28:57 -0000	1.45
+++ include/Makefile.in	14 Jul 2004 03:42:30 -0000
@@ -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 @@
 
 DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST)
 
-TAR = gtar
+TAR = tar
 GZIP_ENV = --best
 all: all-redirect
 .SUFFIXES:
@@ -241,7 +241,7 @@
 	  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:
 
Index: testsuite/Makefile.in
===================================================================
RCS file: /cvs/gcc/gcc/libjava/testsuite/Makefile.in,v
retrieving revision 1.55
diff -u -r1.55 Makefile.in
--- testsuite/Makefile.in	14 Apr 2004 17:45:19 -0000	1.55
+++ testsuite/Makefile.in	14 Jul 2004 03:42:31 -0000
@@ -177,7 +177,7 @@
 
 DISTFILES = $(DIST_COMMON) $(SOURCES) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST)
 
-TAR = gtar
+TAR = tar
 GZIP_ENV = --best
 all: all-redirect
 .SUFFIXES:


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]