/*
* @(#)GenericListener.java
*
* Copyright (c) 1996-2010 The authors and contributors of JHotDraw.
* You may not use, copy or modify this file, except in compliance with the
* accompanying license terms.
*/
package org.jhotdraw.gui.event;
import javax.annotation.Nullable;
import java.lang.reflect.*;
/**
* The GenericListener creates anonymous listener classes at runtime.
* <p>
* Usage:
* <pre>
* public class Demo {
* JPanel root = new JPanel(new BorderLayout());
* JLabel label = new JLabel(" ");
*
* public void myButtonAction(ActionEvent e) {
* label.setText("buttonAction");
* }
*
* public void myMouseEntered(MouseEvent e) {
* label.setText("mouseEntered: "+e.toString());
* }
*
* Demo() {
* JButton button = new JButton("Button with Dynamic Listener");
*
* //This listener will be generated at run-time, for example at run-time
* // an ActionListener class will be code-generated and then
* // class-loaded. Only one of these is actually created, even
* // if many calls to GenericListener.create(ActionListener.class ...)
* // are made.
* ActionListener actionListener = (ActionListener)(GenericListener.create(
* ActionListener.class,
* "actionPerformed",
* this,
* "myButtonAction")
* );
* button.addActionListener(actionListener);
*
* // Here's another dynamically generated listener. This one is
* // a little different because the listenerMethod argument actually
* // specifies one of many listener methods. In the previous example
* // "actionPerformed" named the one and only ActionListener method.
* MouseListener mouseListener = (MouseListener)(GenericListener.create(
* MouseListener.class,
* "mouseEntered",
* this,
* "myMouseEntered")
* );
* button.addMouseListener(mouseListener);
* </pre>
*
* @author Werner Randelshofer
* @version $Id$
*/
public abstract class GenericListener {
/**
* A convenient version of <code>create(listenerMethod, targetObject, targetMethod)</code>.
* This version looks up the listener and target Methods, so you don't have to.
*/
public static Object create(
Class<?> listenerInterface,
String listenerMethodName,
Object target,
String targetMethodName) {
Method listenerMethod = getListenerMethod(listenerInterface, listenerMethodName);
// Search a target method with the same parameter types as the listener method.
Method targetMethod =
getTargetMethod(target, targetMethodName, listenerMethod.getParameterTypes());
// Nothing found? Search a target method with no parameters
if (targetMethod == null) {
targetMethod = getTargetMethod(target, targetMethodName, new Class<?>[0]);
}
// Still nothing found? We give up.
if (targetMethod == null) {
throw new RuntimeException("no such method " + targetMethodName + " in " + target.getClass());
}
return create(listenerMethod, target, targetMethod);
}
/**
* Return an instance of a class that implements the interface that contains
* the declaration for <code>listenerMethod</code>. In this new class,
* <code>listenerMethod</code> will apply <code>target.targetMethod</code>
* to the incoming Event.
*/
public static Object create(
final Method listenerMethod,
final Object target,
final Method targetMethod) {
/**
* The implementation of the create method uses the Dynamic Proxy API
* introduced in JDK 1.3.
*
* Create an instance of the DefaultInvoker and override the invoke
* method to handle the invoking the targetMethod on the target.
*/
InvocationHandler handler = new DefaultInvoker() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// Send all methods except for the targetMethod to
// the superclass for handling.
if (listenerMethod.equals(method)) {
if (targetMethod.getParameterTypes().length == 0) {
// Special treatment for parameterless target methods:
return targetMethod.invoke(target, new Object[0]);
} else {
// Regular treatment for target methods having the same
// argument list as the listener method.
return targetMethod.invoke(target, args);
}
} else {
return super.invoke(proxy, method, args);
}
}
};
Class<?> cls = listenerMethod.getDeclaringClass();
ClassLoader cl = cls.getClassLoader();
return Proxy.newProxyInstance(cl, new Class<?>[]{cls}, handler);
}
/**
* Implementation of the InvocationHandler which handles the basic
* object methods.
*/
private static class DefaultInvoker implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
String methodName = method.getName();
if ("hashCode".equals(methodName)) {
return proxyHashCode(proxy);
} else if ("equals".equals(methodName)) {
return proxyEquals(proxy, args[0]);
} else if ("toString".equals(methodName)) {
return proxyToString(proxy);
}
}
// Although listener methods are supposed to be void, we
// allow for any return type here and produce null/0/false
// as appropriate.
return nullValueOf(method.getReturnType());
}
protected Integer proxyHashCode(Object proxy) {
return System.identityHashCode(proxy);
}
protected Boolean proxyEquals(Object proxy, Object other) {
return (proxy == other ? Boolean.TRUE : Boolean.FALSE);
}
protected String proxyToString(Object proxy) {
return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode());
}
private static final Character char_0 = (char) 0;
private static final Byte byte_0 = (byte) 0;
@Nullable
private static final Object nullValueOf(Class<?> rt) {
if (!rt.isPrimitive()) {
return null;
} else if (rt == void.class) {
return null;
} else if (rt == boolean.class) {
return Boolean.FALSE;
} else if (rt == char.class) {
return char_0;
} else {
// this will convert to any other kind of number
return byte_0;
}
}
}
/* Helper methods for "EZ" version of create(): */
private static Method getListenerMethod(Class<?> listenerInterface,
String listenerMethodName) {
// given the arguments to create(), find out which listener is desired:
Method[] m = listenerInterface.getMethods();
Method result = null;
for (Method m1 : m) {
if (listenerMethodName.equals(m1.getName())) {
if (result != null) {
throw new RuntimeException("ambiguous method: " + m1 + " vs. " + result);
}
result = m1;
}
}
if (result == null) {
throw new RuntimeException("no such method " + listenerMethodName + " in " + listenerInterface);
}
return result;
}
@SuppressWarnings("unchecked")
@Nullable
private static Method getTargetMethod(Object target,
String targetMethodName,
Class<?>[] parameterTypes) {
Method[] m = target.getClass().getMethods();
Method result = null;
eachMethod:
for (Method m1 : m) {
if (!targetMethodName.equals(m1.getName())) {
continue eachMethod;
}
Class<?>[] p = m1.getParameterTypes();
if (p.length != parameterTypes.length) {
continue eachMethod;
}
for (int j = 0; j < p.length; j++) {
if (!p[j].isAssignableFrom(parameterTypes[j])) {
continue eachMethod;
}
}
if (result != null) {
throw new RuntimeException("ambiguous method: " + m1 + " vs. " + result);
}
result = m1;
}
/*
if (result == null) {
throw new RuntimeException("no such method "+targetMethodName+" in "+target.getClass());
}*/
if (result == null) {
return null;
}
Method publicResult = raiseToPublicClass(result);
if (publicResult != null) {
result = publicResult;
}
return result;
}
@Nullable
private static Method raiseToPublicClass(Method m) {
Class<?> c = m.getDeclaringClass();
if (Modifier.isPublic(m.getModifiers())
&& Modifier.isPublic(c.getModifiers())) {
return m; // yes!
} // search for a public version which m overrides
Class<?> sc = c.getSuperclass();
if (sc != null) {
Method sm = raiseToPublicClass(m, sc);
if (sm != null) {
return sm;
}
}
Class<?>[] ints = c.getInterfaces();
for (Class<?> int1 : ints) {
Method im = raiseToPublicClass(m, int1);
if (im != null) {
return im;
}
}
// no public version of m here
return null;
}
@SuppressWarnings("unchecked")
@Nullable
private static Method raiseToPublicClass(Method m, Class<?> c) {
try {
Method sm = c.getMethod(m.getName(), m.getParameterTypes());
return raiseToPublicClass(sm);
} catch (NoSuchMethodException ee) {
return null;
}
}
private GenericListener() {
}
}