001: package org.conform.mdl;
002:
003: /*
004: * @(#)Statement.java 1.18 03/01/23
005: *
006: * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
007: * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
008: */
009:
010: import java.lang.reflect.*;
011: import java.util.*;
012: import java.beans.ExceptionListener;
013:
014: /**
015: * A <code>Statement</code> object represents a primitive statement
016: * in which a single method is applied to a target and
017: * a set of arguments - as in <code>"a.setFoo(b)"</code>.
018: * Note that where this example uses names
019: * to denote the target and its argument, a statement
020: * object does not require a name space and is constructed with
021: * the values themselves.
022: * The statement object associates the named method
023: * with its environment as a simple set of values:
024: * the target and an array of argument values.
025: *
026: * @since 1.4
027: *
028: * @version 1.18 01/23/03
029: * @author Philip Milne
030: */
031:
032: class Statement {
033:
034: private static Object[] emptyArray = new Object[] {};
035: private static HashMap methodCache = null;
036:
037: static ExceptionListener defaultExceptionListener = new ExceptionListener() {
038: public void exceptionThrown(Exception e) {
039: System.err.println(e);
040: // e.printStackTrace();
041: System.err.println("Continuing ...");
042: }
043: };
044:
045: Object target;
046: String methodName;
047: Object[] arguments;
048:
049: /**
050: * Creates a new <code>Statement</code> object with a <code>target</code>,
051: * <code>methodName</code> and <code>arguments</code> as per the parameters.
052: *
053: * @param target The target of this statement.
054: * @param methodName The methodName of this statement.
055: * @param arguments The arguments of this statement.
056: *
057: */
058: public Statement(Object target, String methodName,
059: Object[] arguments) {
060: this .target = target;
061: this .methodName = methodName;
062: this .arguments = (arguments == null) ? emptyArray : arguments;
063: }
064:
065: /**
066: * Returns the target of this statement.
067: *
068: * @return The target of this statement.
069: */
070: public Object getTarget() {
071: return target;
072: }
073:
074: /**
075: * Returns the name of the method.
076: *
077: * @return The name of the method.
078: */
079: public String getMethodName() {
080: return methodName;
081: }
082:
083: /**
084: * Returns the arguments of this statement.
085: *
086: * @return the arguments of this statement.
087: */
088: public Object[] getArguments() {
089: return arguments;
090: }
091:
092: /**
093: * The execute method finds a method whose name is the same
094: * as the methodName property, and invokes the method on
095: * the target.
096: *
097: * When the target's class defines many methods with the given name
098: * the implementation should choose the most specific method using
099: * the algorithm specified in the Java Language Specification
100: * (15.11). The dynamic class of the target and arguments are used
101: * in place of the compile-time type information and, like the
102: * <code>java.lang.reflect.Method</code> class itself, conversion between
103: * primitive values and their associated wrapper classes is handled
104: * internally.
105: * <p>
106: * The following method types are handled as special cases:
107: * <ul>
108: * <li>
109: * Static methods may be called by using a class object as the target.
110: * <li>
111: * The reserved method name "new" may be used to call a class's constructor
112: * as if all classes defined static "new" methods. Constructor invocations
113: * are typically considered <code>Expression</code>s rather than <code>Statement</code>s
114: * as they return a value.
115: * <li>
116: * The method names "get" and "set" defined in the <code>java.util.List</code>
117: * interface may also be applied to array instances, mapping to
118: * the static methods of the same name in the <code>Array</code> class.
119: * </ul>
120: */
121: public void execute() throws Exception {
122: invoke();
123: }
124:
125: /*pp*/static Class typeToClass(Class type) {
126: return type.isPrimitive() ? typeNameToClass(type.getName())
127: : type;
128:
129: }
130:
131: /*pp*/static Class typeNameToClass(String typeName) {
132: typeName = typeName.intern();
133: if (typeName == "boolean")
134: return Boolean.class;
135: if (typeName == "byte")
136: return Byte.class;
137: if (typeName == "char")
138: return Character.class;
139: if (typeName == "short")
140: return Short.class;
141: if (typeName == "int")
142: return Integer.class;
143: if (typeName == "long")
144: return Long.class;
145: if (typeName == "float")
146: return Float.class;
147: if (typeName == "double")
148: return Double.class;
149: if (typeName == "void")
150: return Void.class;
151: return null;
152: }
153:
154: private static Class typeNameToPrimitiveClass(String typeName) {
155: typeName = typeName.intern();
156: if (typeName == "boolean")
157: return boolean.class;
158: if (typeName == "byte")
159: return byte.class;
160: if (typeName == "char")
161: return char.class;
162: if (typeName == "short")
163: return short.class;
164: if (typeName == "int")
165: return int.class;
166: if (typeName == "long")
167: return long.class;
168: if (typeName == "float")
169: return float.class;
170: if (typeName == "double")
171: return double.class;
172: if (typeName == "void")
173: return void.class;
174: return null;
175: }
176:
177: /*pp*/static Class primitiveTypeFor(Class wrapper) {
178: if (wrapper == Boolean.class)
179: return Boolean.TYPE;
180: if (wrapper == Byte.class)
181: return Byte.TYPE;
182: if (wrapper == Character.class)
183: return Character.TYPE;
184: if (wrapper == Short.class)
185: return Short.TYPE;
186: if (wrapper == Integer.class)
187: return Integer.TYPE;
188: if (wrapper == Long.class)
189: return Long.TYPE;
190: if (wrapper == Float.class)
191: return Float.TYPE;
192: if (wrapper == Double.class)
193: return Double.TYPE;
194: if (wrapper == Void.class)
195: return Void.TYPE;
196: return null;
197: }
198:
199: static Class classForName(String name)
200: throws ClassNotFoundException {
201: // l.loadClass("int") fails.
202: Class primitiveType = typeNameToPrimitiveClass(name);
203: if (primitiveType != null) {
204: return primitiveType;
205: }
206: ClassLoader l = Thread.currentThread().getContextClassLoader();
207: return l.loadClass(name);
208: }
209:
210: /**
211: * Tests each element on the class arrays for assignability.
212: *
213: * @param argClasses arguments to be tested
214: * @param argTypes arguments from Method
215: * @return true if each class in argTypes is assignable from the
216: * corresponding class in argClasses.
217: */
218: private static boolean matchArguments(Class[] argClasses,
219: Class[] argTypes) {
220: boolean match = (argClasses.length == argTypes.length);
221: for (int j = 0; j < argClasses.length && match; j++) {
222: Class argType = argTypes[j];
223: if (argType.isPrimitive()) {
224: argType = typeToClass(argType);
225: }
226: // Consider null an instance of all classes.
227: if (argClasses[j] != null
228: && !(argType.isAssignableFrom(argClasses[j]))) {
229: match = false;
230: }
231: }
232: return match;
233: }
234:
235: /**
236: * Tests each element on the class arrays for equality.
237: *
238: * @param argClasses arguments to be tested
239: * @param argTypes arguments from Method
240: * @return true if each class in argTypes is equal to the
241: * corresponding class in argClasses.
242: */
243: private static boolean matchExplicitArguments(Class[] argClasses,
244: Class[] argTypes) {
245: boolean match = (argClasses.length == argTypes.length);
246: for (int j = 0; j < argClasses.length && match; j++) {
247: Class argType = argTypes[j];
248: if (argType.isPrimitive()) {
249: argType = typeToClass(argType);
250: }
251: if (argClasses[j] != argType) {
252: match = false;
253: }
254: }
255: return match;
256: }
257:
258: // Pending: throw when the match is ambiguous.
259: private static Method findPublicMethod(Class declaringClass,
260: String methodName, Class[] argClasses) {
261: // Many methods are "getters" which take no arguments.
262: // This permits the following optimisation which
263: // avoids the expensive call to getMethods().
264: if (argClasses.length == 0) {
265: try {
266: return declaringClass.getMethod(methodName, argClasses);
267: } catch (NoSuchMethodException e) {
268: return null;
269: }
270: }
271: // logger.finest("getMethods " + declaringClass + " for " + methodName);
272: Method[] methods = declaringClass.getMethods();
273: ArrayList list = new ArrayList();
274: for (int i = 0; i < methods.length; i++) {
275: // Collect all the methods which match the signature.
276: Method method = methods[i];
277: if (method.getName().equals(methodName)) {
278: if (matchArguments(argClasses, method
279: .getParameterTypes())) {
280: list.add(method);
281: }
282: }
283: }
284: if (list.size() > 0) {
285: if (list.size() == 1) {
286: return (Method) list.get(0);
287: } else {
288: ListIterator iterator = list.listIterator();
289: Method method;
290: while (iterator.hasNext()) {
291: method = (Method) iterator.next();
292: if (matchExplicitArguments(argClasses, method
293: .getParameterTypes())) {
294: return method;
295: }
296: }
297: // This list is valid. Should return something.
298: return (Method) list.get(0);
299: }
300: }
301: return null;
302: }
303:
304: // Pending: throw when the match is ambiguous.
305: private static Method findMethod(Class targetClass,
306: String methodName, Class[] argClasses) {
307: Method m = findPublicMethod(targetClass, methodName, argClasses);
308: if (m != null
309: && Modifier.isPublic(m.getDeclaringClass()
310: .getModifiers())) {
311: return m;
312: }
313:
314: /*
315: Search the interfaces for a public version of this method.
316:
317: Example: the getKeymap() method of a JTextField
318: returns a package private implementation of the
319: of the public Keymap interface. In the Keymap
320: interface there are a number of "properties" one
321: being the "resolveParent" property implied by the
322: getResolveParent() method. This getResolveParent()
323: cannot be called reflectively because the class
324: itself is not public. Instead we search the class's
325: interfaces and find the getResolveParent()
326: method of the Keymap interface - on which invoke
327: may be applied without error.
328:
329: So in :-
330:
331: JTextField o = new JTextField("Hello, world");
332: Keymap km = o.getKeymap();
333: Method m1 = km.getClass().getMethod("getResolveParent", new Class[0]);
334: Method m2 = Keymap.class.getMethod("getResolveParent", new Class[0]);
335:
336: Methods m1 and m2 are different. The invocation of method
337: m1 unconditionally throws an IllegalAccessException where
338: the invocation of m2 will invoke the implementation of the
339: method. Note that (ignoring the overloading of arguments)
340: there is only one implementation of the named method which
341: may be applied to this target.
342: */
343: for (Class type = targetClass; type != null; type = type
344: .getSuperclass()) {
345: Class[] interfaces = type.getInterfaces();
346: for (int i = 0; i < interfaces.length; i++) {
347: m = findPublicMethod(interfaces[i], methodName,
348: argClasses);
349: if (m != null) {
350: return m;
351: }
352: }
353: }
354: return null;
355: }
356:
357: private static class Signature {
358: Class targetClass;
359: String methodName;
360: Class[] argClasses;
361:
362: public Signature(Class targetClass, String methodName,
363: Class[] argClasses) {
364: this .targetClass = targetClass;
365: this .methodName = methodName;
366: this .argClasses = argClasses;
367: }
368:
369: public boolean equals(Object o2) {
370: Statement.Signature that = (Statement.Signature) o2;
371: if (!(targetClass == that.targetClass)) {
372: return false;
373: }
374: if (!(methodName.equals(that.methodName))) {
375: return false;
376: }
377: if (argClasses.length != that.argClasses.length) {
378: return false;
379: }
380: for (int i = 0; i < argClasses.length; i++) {
381: if (!(argClasses[i] == that.argClasses[i])) {
382: return false;
383: }
384: }
385: return true;
386: }
387:
388: // Pending(milne) Seek advice an a suitable hash function to use here.
389: public int hashCode() {
390: return targetClass.hashCode() * 35 + methodName.hashCode();
391: }
392: }
393:
394: /** A wrapper to findMethod(), which will cache its results if
395: * isCaching() returns true. See clear().
396: */
397: static Method getMethod(Class targetClass, String methodName,
398: Class[] argClasses) {
399: if (!isCaching()) {
400: return findMethod(targetClass, methodName, argClasses);
401: }
402: Object signature = new Statement.Signature(targetClass,
403: methodName, argClasses);
404: Method m = (Method) methodCache.get(signature);
405: if (m != null) {
406: // logger.finest("findMethod found " + methodName + " for " + targetClass);
407: return m;
408: }
409: // logger.finest("findMethod searching " + targetClass + " for " + methodName);
410: m = findMethod(targetClass, methodName, argClasses);
411: if (m != null) {
412: methodCache.put(signature, m);
413: }
414: return m;
415: }
416:
417: static void setCaching(boolean b) {
418: methodCache = b ? new HashMap() : null;
419: }
420:
421: private static boolean isCaching() {
422: return methodCache != null;
423: }
424:
425: Object invoke() throws Exception {
426: // logger.finest("Invoking: " + toString());
427: Object target = getTarget();
428: String methodName = getMethodName();
429: Object[] arguments = getArguments();
430: // Class.forName() won't load classes outside
431: // of core from a class inside core. Special
432: // case this method.
433: if (target == Class.class && methodName == "forName") {
434: return classForName((String) arguments[0]);
435: }
436: Class[] argClasses = new Class[arguments.length];
437: for (int i = 0; i < arguments.length; i++) {
438: argClasses[i] = (arguments[i] == null) ? null
439: : arguments[i].getClass();
440: }
441:
442: AccessibleObject m = null;
443: if (target instanceof Class) {
444: /*
445: For class methods, simluate the effect of a meta class
446: by taking the union of the static methods of the
447: actual class, with the instance methods of "Class.class"
448: and the overloaded "newInstance" methods defined by the
449: constructors.
450: This way "System.class", for example, will perform both
451: the static method getProperties() and the instance method
452: getSuperclass() defined in "Class.class".
453: */
454: if (methodName == "new") {
455: methodName = "newInstance";
456: }
457: // Provide a short conform for array instantiation by faking an nary-constructor.
458: if (methodName == "newInstance"
459: && ((Class) target).isArray()) {
460: Object result = Array.newInstance(((Class) target)
461: .getComponentType(), arguments.length);
462: for (int i = 0; i < arguments.length; i++) {
463: Array.set(result, i, arguments[i]);
464: }
465: return result;
466: }
467: if (methodName == "newInstance" && arguments.length != 0) {
468: // The Character class, as of 1.4, does not have a constructor
469: // which takes a String. All of the other "wrapper" classes
470: // for Java's primitive types have a String constructor so we
471: // fake such a constructor here so that this special case can be
472: // ignored elsewhere.
473: if (target == Character.class && arguments.length == 1
474: && argClasses[0] == String.class) {
475: return new Character(((String) arguments[0])
476: .charAt(0));
477: }
478: Constructor[] constructors = ((Class) target)
479: .getConstructors();
480: // PENDING: Implement the resolutuion of ambiguities properly.
481: for (int i = 0; i < constructors.length; i++) {
482: Constructor constructor = constructors[i];
483: if (matchArguments(argClasses, constructor
484: .getParameterTypes())) {
485: m = constructor;
486: }
487: }
488: }
489: if (m == null) {
490: m = getMethod((Class) target, methodName, argClasses);
491: }
492: if (m == null) {
493: m = getMethod(Class.class, methodName, argClasses);
494: }
495: } else {
496: /*
497: This special casing of arrays is not necessary, but makes files
498: involving arrays much shorter and simplifies the archiving infrastrcure.
499: The Array.set() method introduces an unusual idea - that of a static method
500: changing the state of an instance. Normally statements with side
501: effects on objects are instance methods of the objects themselves
502: and we reinstate this rule (perhaps temporarily) by special-casing arrays.
503: */
504: if (target.getClass().isArray()
505: && (methodName == "set" || methodName == "get")) {
506: int index = ((Integer) arguments[0]).intValue();
507: if (methodName == "get") {
508: return Array.get(target, index);
509: } else {
510: Array.set(target, index, arguments[1]);
511: return null;
512: }
513: }
514: m = getMethod(target.getClass(), methodName, argClasses);
515: }
516: if (m != null) {
517: // System.err.println("Calling \"" + methodName + "\"" + " on " + ((o == null) ? null : target.getClass()));
518: try {
519: if (m instanceof Method) {
520: return ((Method) m).invoke(target, arguments);
521: } else {
522: return ((Constructor) m).newInstance(arguments);
523: }
524: } catch (IllegalAccessException iae) {
525: throw new IllegalAccessException(toString());
526: } catch (InvocationTargetException ite) {
527: Throwable te = ite.getTargetException();
528: if (te instanceof Exception) {
529: throw (Exception) te;
530: } else {
531: throw ite;
532: }
533: }
534: }
535: throw new NoSuchMethodException(toString());
536: }
537:
538: /*pp*/String instanceName(Object instance) {
539: return (instance != null && instance.getClass() == String.class) ? "\""
540: + (String) instance + "\""
541: : NameGenerator.instanceName(instance);
542: }
543:
544: /**
545: * Prints the value of this statement using a Java-style syntax.
546: */
547: public String toString() {
548: // Respect a subclass's implementation here.
549: Object target = getTarget();
550: String methodName = getMethodName();
551: Object[] arguments = getArguments();
552:
553: StringBuffer result = new StringBuffer(instanceName(target)
554: + "." + methodName + "(");
555: int n = arguments.length;
556: for (int i = 0; i < n; i++) {
557: result.append(instanceName(arguments[i]));
558: if (i != n - 1) {
559: result.append(", ");
560: }
561: }
562: result.append(");");
563: return result.toString();
564: }
565: }
|