- /*
 - * @(#)Statement.java 1.18 03/01/23
 - *
 - * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
 - * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 - */
 - package java.beans;
 - import java.lang.reflect.*;
 - import java.util.*;
 - /**
 - * A <code>Statement</code> object represents a primitive statement
 - * in which a single method is applied to a target and
 - * a set of arguments - as in <code>"a.setFoo(b)"</code>.
 - * Note that where this example uses names
 - * to denote the target and its argument, a statement
 - * object does not require a name space and is constructed with
 - * the values themselves.
 - * The statement object associates the named method
 - * with its environment as a simple set of values:
 - * the target and an array of argument values.
 - *
 - * @since 1.4
 - *
 - * @version 1.18 01/23/03
 - * @author Philip Milne
 - */
 - public class Statement {
 - private static Object[] emptyArray = new Object[]{};
 - private static HashMap methodCache = null;
 - static ExceptionListener defaultExceptionListener = new ExceptionListener() {
 - public void exceptionThrown(Exception e) {
 - System.err.println(e);
 - // e.printStackTrace();
 - System.err.println("Continuing ...");
 - }
 - };
 - Object target;
 - String methodName;
 - Object[] arguments;
 - /**
 - * Creates a new <code>Statement</code> object with a <code>target</code>,
 - * <code>methodName</code> and <code>arguments</code> as per the parameters.
 - *
 - * @param target The target of this statement.
 - * @param methodName The methodName of this statement.
 - * @param arguments The arguments of this statement.
 - *
 - */
 - public Statement(Object target, String methodName, Object[] arguments) {
 - this.target = target;
 - this.methodName = methodName;
 - this.arguments = (arguments == null) ? emptyArray : arguments;
 - }
 - /**
 - * Returns the target of this statement.
 - *
 - * @return The target of this statement.
 - */
 - public Object getTarget() {
 - return target;
 - }
 - /**
 - * Returns the name of the method.
 - *
 - * @return The name of the method.
 - */
 - public String getMethodName() {
 - return methodName;
 - }
 - /**
 - * Returns the arguments of this statement.
 - *
 - * @return the arguments of this statement.
 - */
 - public Object[] getArguments() {
 - return arguments;
 - }
 - /**
 - * The execute method finds a method whose name is the same
 - * as the methodName property, and invokes the method on
 - * the target.
 - *
 - * When the target's class defines many methods with the given name
 - * the implementation should choose the most specific method using
 - * the algorithm specified in the Java Language Specification
 - * (15.11). The dynamic class of the target and arguments are used
 - * in place of the compile-time type information and, like the
 - * <code>java.lang.reflect.Method</code> class itself, conversion between
 - * primitive values and their associated wrapper classes is handled
 - * internally.
 - * <p>
 - * The following method types are handled as special cases:
 - * <ul>
 - * <li>
 - * Static methods may be called by using a class object as the target.
 - * <li>
 - * The reserved method name "new" may be used to call a class's constructor
 - * as if all classes defined static "new" methods. Constructor invocations
 - * are typically considered <code>Expression</code>s rather than <code>Statement</code>s
 - * as they return a value.
 - * <li>
 - * The method names "get" and "set" defined in the <code>java.util.List</code>
 - * interface may also be applied to array instances, mapping to
 - * the static methods of the same name in the <code>Array</code> class.
 - * </ul>
 - */
 - public void execute() throws Exception {
 - invoke();
 - }
 - /*pp*/ static Class typeToClass(Class type) {
 - return type.isPrimitive() ? typeNameToClass(type.getName()) : type;
 - }
 - /*pp*/ static Class typeNameToClass(String typeName) {
 - typeName = typeName.intern();
 - if (typeName == "boolean") return Boolean.class;
 - if (typeName == "byte") return Byte.class;
 - if (typeName == "char") return Character.class;
 - if (typeName == "short") return Short.class;
 - if (typeName == "int") return Integer.class;
 - if (typeName == "long") return Long.class;
 - if (typeName == "float") return Float.class;
 - if (typeName == "double") return Double.class;
 - if (typeName == "void") return Void.class;
 - return null;
 - }
 - private static Class typeNameToPrimitiveClass(String typeName) {
 - typeName = typeName.intern();
 - if (typeName == "boolean") return boolean.class;
 - if (typeName == "byte") return byte.class;
 - if (typeName == "char") return char.class;
 - if (typeName == "short") return short.class;
 - if (typeName == "int") return int.class;
 - if (typeName == "long") return long.class;
 - if (typeName == "float") return float.class;
 - if (typeName == "double") return double.class;
 - if (typeName == "void") return void.class;
 - return null;
 - }
 - /*pp*/ static Class primitiveTypeFor(Class wrapper) {
 - if (wrapper == Boolean.class) return Boolean.TYPE;
 - if (wrapper == Byte.class) return Byte.TYPE;
 - if (wrapper == Character.class) return Character.TYPE;
 - if (wrapper == Short.class) return Short.TYPE;
 - if (wrapper == Integer.class) return Integer.TYPE;
 - if (wrapper == Long.class) return Long.TYPE;
 - if (wrapper == Float.class) return Float.TYPE;
 - if (wrapper == Double.class) return Double.TYPE;
 - if (wrapper == Void.class) return Void.TYPE;
 - return null;
 - }
 - static Class classForName(String name) throws ClassNotFoundException {
 - // l.loadClass("int") fails.
 - Class primitiveType = typeNameToPrimitiveClass(name);
 - if (primitiveType != null) {
 - return primitiveType;
 - }
 - ClassLoader l = Thread.currentThread().getContextClassLoader();
 - return l.loadClass(name);
 - }
 - /**
 - * Tests each element on the class arrays for assignability.
 - *
 - * @param argClasses arguments to be tested
 - * @param argTypes arguments from Method
 - * @return true if each class in argTypes is assignable from the
 - * corresponding class in argClasses.
 - */
 - private static boolean matchArguments(Class[] argClasses, Class[] argTypes) {
 - boolean match = (argClasses.length == argTypes.length);
 - for(int j = 0; j < argClasses.length && match; j++) {
 - Class argType = argTypes[j];
 - if (argType.isPrimitive()) {
 - argType = typeToClass(argType);
 - }
 - // Consider null an instance of all classes.
 - if (argClasses[j] != null && !(argType.isAssignableFrom(argClasses[j]))) {
 - match = false;
 - }
 - }
 - return match;
 - }
 - /**
 - * Tests each element on the class arrays for equality.
 - *
 - * @param argClasses arguments to be tested
 - * @param argTypes arguments from Method
 - * @return true if each class in argTypes is equal to the
 - * corresponding class in argClasses.
 - */
 - private static boolean matchExplicitArguments(Class[] argClasses, Class[] argTypes) {
 - boolean match = (argClasses.length == argTypes.length);
 - for(int j = 0; j < argClasses.length && match; j++) {
 - Class argType = argTypes[j];
 - if (argType.isPrimitive()) {
 - argType = typeToClass(argType);
 - }
 - if (argClasses[j] != argType) {
 - match = false;
 - }
 - }
 - return match;
 - }
 - // Pending: throw when the match is ambiguous.
 - private static Method findPublicMethod(Class declaringClass, String methodName, Class[] argClasses) {
 - // Many methods are "getters" which take no arguments.
 - // This permits the following optimisation which
 - // avoids the expensive call to getMethods().
 - if (argClasses.length == 0) {
 - try {
 - return declaringClass.getMethod(methodName, argClasses);
 - }
 - catch (NoSuchMethodException e) {
 - return null;
 - }
 - }
 - // System.out.println("getMethods " + declaringClass + " for " + methodName);
 - Method[] methods = declaringClass.getMethods();
 - ArrayList list = new ArrayList();
 - for(int i = 0; i < methods.length; i++) {
 - // Collect all the methods which match the signature.
 - Method method = methods[i];
 - if (method.getName().equals(methodName)) {
 - if (matchArguments(argClasses, method.getParameterTypes())) {
 - list.add(method);
 - }
 - }
 - }
 - if (list.size() > 0) {
 - if (list.size() == 1) {
 - return (Method)list.get(0);
 - }
 - else {
 - ListIterator iterator = list.listIterator();
 - Method method;
 - while (iterator.hasNext()) {
 - method = (Method)iterator.next();
 - if (matchExplicitArguments(argClasses, method.getParameterTypes())) {
 - return method;
 - }
 - }
 - // This list is valid. Should return something.
 - return (Method)list.get(0);
 - }
 - }
 - return null;
 - }
 - // Pending: throw when the match is ambiguous.
 - private static Method findMethod(Class targetClass, String methodName, Class[] argClasses) {
 - Method m = findPublicMethod(targetClass, methodName, argClasses);
 - if (m != null && Modifier.isPublic(m.getDeclaringClass().getModifiers())) {
 - return m;
 - }
 - /*
 - Search the interfaces for a public version of this method.
 - Example: the getKeymap() method of a JTextField
 - returns a package private implementation of the
 - of the public Keymap interface. In the Keymap
 - interface there are a number of "properties" one
 - being the "resolveParent" property implied by the
 - getResolveParent() method. This getResolveParent()
 - cannot be called reflectively because the class
 - itself is not public. Instead we search the class's
 - interfaces and find the getResolveParent()
 - method of the Keymap interface - on which invoke
 - may be applied without error.
 - So in :-
 - JTextField o = new JTextField("Hello, world");
 - Keymap km = o.getKeymap();
 - Method m1 = km.getClass().getMethod("getResolveParent", new Class[0]);
 - Method m2 = Keymap.class.getMethod("getResolveParent", new Class[0]);
 - Methods m1 and m2 are different. The invocation of method
 - m1 unconditionally throws an IllegalAccessException where
 - the invocation of m2 will invoke the implementation of the
 - method. Note that (ignoring the overloading of arguments)
 - there is only one implementation of the named method which
 - may be applied to this target.
 - */
 - for(Class type = targetClass; type != null; type = type.getSuperclass()) {
 - Class[] interfaces = type.getInterfaces();
 - for(int i = 0; i < interfaces.length; i++) {
 - m = findPublicMethod(interfaces[i], methodName, argClasses);
 - if (m != null) {
 - return m;
 - }
 - }
 - }
 - return null;
 - }
 - private static class Signature {
 - Class targetClass;
 - String methodName;
 - Class[] argClasses;
 - public Signature(Class targetClass, String methodName, Class[] argClasses) {
 - this.targetClass = targetClass;
 - this.methodName = methodName;
 - this.argClasses = argClasses;
 - }
 - public boolean equals(Object o2) {
 - Signature that = (Signature)o2;
 - if (!(targetClass == that.targetClass)) {
 - return false;
 - }
 - if (!(methodName.equals(that.methodName))) {
 - return false;
 - }
 - if (argClasses.length != that.argClasses.length) {
 - return false;
 - }
 - for (int i = 0; i < argClasses.length; i++) {
 - if (!(argClasses[i] == that.argClasses[i])) {
 - return false;
 - }
 - }
 - return true;
 - }
 - // Pending(milne) Seek advice an a suitable hash function to use here.
 - public int hashCode() {
 - return targetClass.hashCode() * 35 + methodName.hashCode();
 - }
 - }
 - /** A wrapper to findMethod(), which will cache its results if
 - * isCaching() returns true. See clear().
 - */
 - static Method getMethod(Class targetClass, String methodName, Class[] argClasses) {
 - if (!isCaching()) {
 - return findMethod(targetClass, methodName, argClasses);
 - }
 - Object signature = new Signature(targetClass, methodName, argClasses);
 - Method m = (Method)methodCache.get(signature);
 - if (m != null) {
 - // System.out.println("findMethod found " + methodName + " for " + targetClass);
 - return m;
 - }
 - // System.out.println("findMethod searching " + targetClass + " for " + methodName);
 - m = findMethod(targetClass, methodName, argClasses);
 - if (m != null) {
 - methodCache.put(signature, m);
 - }
 - return m;
 - }
 - static void setCaching(boolean b) {
 - methodCache = b ? new HashMap() : null;
 - }
 - private static boolean isCaching() {
 - return methodCache != null;
 - }
 - Object invoke() throws Exception {
 - // System.out.println("Invoking: " + toString());
 - Object target = getTarget();
 - String methodName = getMethodName();
 - Object[] arguments = getArguments();
 - // Class.forName() won't load classes outside
 - // of core from a class inside core. Special
 - // case this method.
 - if (target == Class.class && methodName == "forName") {
 - return classForName((String)arguments[0]);
 - }
 - Class[] argClasses = new Class[arguments.length];
 - for(int i = 0; i < arguments.length; i++) {
 - argClasses[i] = (arguments[i] == null) ? null : arguments[i].getClass();
 - }
 - AccessibleObject m = null;
 - if (target instanceof Class) {
 - /*
 - For class methods, simluate the effect of a meta class
 - by taking the union of the static methods of the
 - actual class, with the instance methods of "Class.class"
 - and the overloaded "newInstance" methods defined by the
 - constructors.
 - This way "System.class", for example, will perform both
 - the static method getProperties() and the instance method
 - getSuperclass() defined in "Class.class".
 - */
 - if (methodName == "new") {
 - methodName = "newInstance";
 - }
 - // Provide a short form for array instantiation by faking an nary-constructor.
 - if (methodName == "newInstance" && ((Class)target).isArray()) {
 - Object result = Array.newInstance(((Class)target).getComponentType(), arguments.length);
 - for(int i = 0; i < arguments.length; i++) {
 - Array.set(result, i, arguments[i]);
 - }
 - return result;
 - }
 - if (methodName == "newInstance" && arguments.length != 0) {
 - // The Character class, as of 1.4, does not have a constructor
 - // which takes a String. All of the other "wrapper" classes
 - // for Java's primitive types have a String constructor so we
 - // fake such a constructor here so that this special case can be
 - // ignored elsewhere.
 - if (target == Character.class && arguments.length == 1 && argClasses[0] == String.class) {
 - return new Character(((String)arguments[0]).charAt(0));
 - }
 - Constructor[] constructors = ((Class)target).getConstructors();
 - // PENDING: Implement the resolutuion of ambiguities properly.
 - for(int i = 0; i < constructors.length; i++) {
 - Constructor constructor = constructors[i];
 - if (matchArguments(argClasses, constructor.getParameterTypes())) {
 - m = constructor;
 - }
 - }
 - }
 - if (m == null) {
 - m = getMethod((Class)target, methodName, argClasses);
 - }
 - if (m == null) {
 - m = getMethod(Class.class, methodName, argClasses);
 - }
 - }
 - else {
 - /*
 - This special casing of arrays is not necessary, but makes files
 - involving arrays much shorter and simplifies the archiving infrastrcure.
 - The Array.set() method introduces an unusual idea - that of a static method
 - changing the state of an instance. Normally statements with side
 - effects on objects are instance methods of the objects themselves
 - and we reinstate this rule (perhaps temporarily) by special-casing arrays.
 - */
 - if (target.getClass().isArray() && (methodName == "set" || methodName == "get")) {
 - int index = ((Integer)arguments[0]).intValue();
 - if (methodName == "get") {
 - return Array.get(target, index);
 - }
 - else {
 - Array.set(target, index, arguments[1]);
 - return null;
 - }
 - }
 - m = getMethod(target.getClass(), methodName, argClasses);
 - }
 - if (m != null) {
 - // System.err.println("Calling \"" + methodName + "\"" + " on " + ((o == null) ? null : target.getClass()));
 - try {
 - if (m instanceof Method) {
 - return ((Method)m).invoke(target, arguments);
 - }
 - else {
 - return ((Constructor)m).newInstance(arguments);
 - }
 - }
 - catch (IllegalAccessException iae) {
 - throw new IllegalAccessException(toString());
 - }
 - catch (InvocationTargetException ite) {
 - Throwable te = ite.getTargetException();
 - if (te instanceof Exception) {
 - throw (Exception)te;
 - }
 - else {
 - throw ite;
 - }
 - }
 - }
 - throw new NoSuchMethodException(toString());
 - }
 - /*pp*/ String instanceName(Object instance) {
 - return (instance != null && instance.getClass() == String.class)
 - ? "\""+(String)instance + "\""
 - : NameGenerator.instanceName(instance);
 - }
 - /**
 - * Prints the value of this statement using a Java-style syntax.
 - */
 - public String toString() {
 - // Respect a subclass's implementation here.
 - Object target = getTarget();
 - String methodName = getMethodName();
 - Object[] arguments = getArguments();
 - StringBuffer result = new StringBuffer(instanceName(target) + "." + methodName + "(");
 - int n = arguments.length;
 - for(int i = 0; i < n; i++) {
 - result.append(instanceName(arguments[i]));
 - if (i != n -1) {
 - result.append(", ");
 - }
 - }
 - result.append(");");
 - return result.toString();
 - }
 - }