001: package jsint;
002:
003: /**
004: * @author Ken R. Anderson, Copyright 2000, kanderso@bbn.com, <a href="license.txt">license</a>
005: * subsequently modified by Jscheme project members
006: * licensed under zlib licence (see license.txt)
007: */
008:
009: //import java.lang.reflect.AccessibleObject; // only in JDK1.2 revision:
010: import java.lang.reflect.Constructor;
011: import java.lang.reflect.Field;
012: import java.lang.reflect.InvocationTargetException;
013: import java.lang.reflect.Method;
014: import java.lang.reflect.Member;
015: import java.lang.reflect.Modifier;
016: import java.util.Enumeration;
017: import java.util.Hashtable;
018: import java.util.Vector;
019:
020: /**
021: Provides dynamic Java method invocation through Java's Reflection
022: interface. For a good discussion of a Scheme implementation, and
023: the issues involved with dynamic method invocation in Java see:
024:
025: <p> Michael Travers, Java Q & A, Dr. Dobb's Journal, Jan., 2000,
026: p. 103-112.
027:
028: <p>Primitive types are not widened because it would make method
029: selection more ambiguous. By memoizing constructorTable() and
030: methodTable() dynamic method lookup can be done without consing.
031:
032: <p>You'll notice that Java doesn't make this very easy. For
033: example it would be nice if Method and Constructor shared an
034: Invokable interface.
035:
036: <p>Privileged methods can be invoked if the JVM allows it.
037:
038: <p>The name of a method to be invoked can be any nonnull Object
039: with a .toString() that names a method. It should probably be
040: changed to String.
041: **/
042:
043: public class Invoke {
044:
045: /** Each bucket in an method table contains a Class[] of
046: parameterTypes and the corresponding method or constructor. **/
047: public static final int BUCKET_SIZE = 2;
048:
049: public static Object peek(Object target, String name) {
050: return peek0(target.getClass(), name, target);
051: }
052:
053: public static Object peekStatic(Class c, String name) {
054: return peek0(c, name, c);
055: }
056:
057: private static Object peek0(Class c, String name, Object target) {
058: try {
059: return c.getField(name).get(target);
060: } catch (NoSuchFieldException e) {
061: return E.error(target + " has no field named " + name);
062: } catch (IllegalAccessException e) {
063: return E.error("Can't access the " + name + " field of "
064: + target);
065: }
066: }
067:
068: public static Object poke(Object target, String name, Object value) {
069: return poke0(target.getClass(), name, target, value);
070: }
071:
072: public static Object pokeStatic(Class c, String name, Object value) {
073: return poke0(c, name, c, value);
074: }
075:
076: private static Object poke0(Class c, String name, Object target,
077: Object value) {
078: try {
079: c.getField(name).set(target, value);
080: return value;
081: } catch (NoSuchFieldException e) {
082: return E.error(target + " has no field named " + name);
083: } catch (IllegalAccessException e) {
084: return E.error("Can't access the " + name + " field of "
085: + target);
086: }
087: }
088:
089: public static Object invokeConstructor(String c, Object[] args) {
090: Object[] ms = constructorTable(c, false);
091: return invokeRawConstructor(
092: ((Constructor) findMethod(ms, args)), args);
093: }
094:
095: public static Object invokeRawConstructor(Constructor m,
096: Object[] args) {
097: try {
098: return m.newInstance(args);
099: } catch (InvocationTargetException e) {
100: throw new BacktraceException(e.getTargetException(),
101: new Object[] { m, args });
102: } catch (InstantiationException e) {
103: return E.error("Error during instantiation: ", U.list(e, m,
104: args));
105: } catch (IllegalAccessException e) {
106: return E.error("Bad constructor application:", U.list(e, m,
107: args));
108: }
109: }
110:
111: public static Object invokeStatic(Class c, String name,
112: Object[] args) {
113: return invokeMethod(c, c, name, args, true, false);
114: }
115:
116: public static Object invokeInstance(Object target, String name,
117: Object[] args, boolean isPrivileged) {
118: return invokeMethod(target.getClass(), target, name, args,
119: false, isPrivileged);
120: }
121:
122: public static Object invokeMethod(Class c, Object target,
123: String name, Object[] args, boolean isStatic,
124: boolean isPrivileged) {
125: Object[] ms = methodTable(c, name, isStatic, isPrivileged);
126: return invokeRawMethod((Method) findMethod(ms, args), target,
127: args);
128: }
129:
130: public static Object invokeRawMethod(Method m, Object target,
131: Object[] args) {
132: try {
133: return m.invoke(target, args);
134: } catch (InvocationTargetException e) {
135: throw new BacktraceException(e.getTargetException(),
136: new Object[] { m, target, args });
137: } catch (IllegalAccessException e) {
138: return E.error(
139: "Bad method application from a private class: ", U
140: .list(e, m, args));
141: } catch (java.lang.IllegalArgumentException e) {
142: if (args == null)
143: return E.error(e + "\n " + m.toString()
144: + "\n called with target: "
145: + U.stringify(target)
146: + " and a null argument vector.");
147: else
148: return E.error(e
149: + "\nARGUMENT MISMATCH for method \n\n "
150: + m.toString() + "\n called with "
151: + U.vectorToList(args));
152: }
153: }
154:
155: public static final Hashtable constructorCache = new Hashtable(50);
156: public static final Hashtable constructorCachePriv = new Hashtable(
157: 50);
158:
159: /** Return the constructor table for the named class. **/
160: public static Object[] constructorTable(String c,
161: boolean isPrivileged) {
162: if (isPrivileged)
163: return constructorTable0Priv(c);
164: else
165: return constructorTable0(c);
166: }
167:
168: public static Object[] constructorTable0Priv(String c) {
169: Object[] result = ((Object[]) constructorCachePriv.get(c));
170: if (result == null) {
171: try {
172: result = methodArray(makeAccessible(Import
173: .classNamed(c).getDeclaredConstructors()));
174: } catch (Exception e) {
175: result = methodArray(Import.classNamed(c)
176: .getConstructors());
177: }
178: constructorCachePriv.put(c, result);
179: }
180: if (result.length == 0)
181: return ((Object[]) E.error("Constructor " + c
182: + " has no methods."));
183: else
184: return result;
185: }
186:
187: public static Object[] constructorTable0(String c) {
188: Object[] result = ((Object[]) constructorCache.get(c));
189: if (result == null) {
190: result = methodArray(Import.classNamed(c).getConstructors());
191: constructorCache.put(c, result);
192: }
193: if (result.length == 0)
194: return ((Object[]) E.error("Constructor " + c
195: + " has no methods."));
196: else
197: return result;
198: }
199:
200: /** Static method name -> Class -> parameter[]/method array. **/
201: public static final Hashtable staticCache = new Hashtable(50);
202: /** Instance method name -> Class -> parameter[]/method array. **/
203: public static final Hashtable instanceCache = new Hashtable(100);
204:
205: private static Hashtable getMethodCache(boolean isStatic) {
206: return (isStatic) ? staticCache : instanceCache;
207: }
208:
209: private static Hashtable getNameTable(Hashtable table, String name) {
210: Hashtable nameTable = ((Hashtable) table.get(name));
211: if (nameTable != null)
212: return ((Hashtable) nameTable);
213: else {
214: nameTable = new Hashtable(10);
215: table.put(name, nameTable);
216: return ((Hashtable) nameTable);
217: }
218: }
219:
220: /** Returns a Class -> prameter[]/method array for the method named
221: * name. **/
222: public static Hashtable getClassTable(String name, boolean isStatic) {
223: return getNameTable(getMethodCache(isStatic), name);
224: }
225:
226: public static Object[] getCachedMethodTable(Class c, String name,
227: boolean isStatic) {
228: return ((Object[]) getNameTable(getMethodCache(isStatic), name)
229: .get(c));
230: }
231:
232: public static void putCachedMethodTable(Class c, String name,
233: boolean isStatic, Object value) {
234: getNameTable(getMethodCache(isStatic), name).put(c, value);
235: }
236:
237: public static Object[] methodTable0(Class c, String name,
238: boolean isStatic, boolean isPrivileged) {
239: String internalName = isPrivileged ? name.concat("#") : name;
240: Object[] result1 = getCachedMethodTable(c, internalName,
241: isStatic);
242: if (result1 == null) {
243: result1 = methodTableLookup(c, name, isStatic, isPrivileged);
244: putCachedMethodTable(c, internalName, isStatic, result1);
245: }
246: return result1;
247: }
248:
249: public static Object[] methodTable(Class c, String name,
250: boolean isStatic, boolean isPrivileged) {
251: Object[] result1 = methodTable0(c, name, isStatic, isPrivileged);
252: if (result1 == null || result1.length == 0)
253: if (isStatic)
254: return ((Object[]) E
255: .error("ERROR: \nNO STATIC METHOD OF TYPE \n\n ("
256: + c.getName() + "." + name + " ...)"));
257: else
258: return ((Object[]) E
259: .error("ERROR: \nNO INSTANCE METHOD OF TYPE \n\n (."
260: + name + " " + c.getName() + " ...)"));
261: else
262: return result1;
263: }
264:
265: public static Object[] methodTableLookup(Class c, String name,
266: boolean isStatic, boolean isPrivileged) {
267: if (isStatic)
268: return methodTableLookupStatic(c, name, isPrivileged);
269: else
270: return methodTableLookupInstance(c, name, isPrivileged);
271: }
272:
273: public static Object[] methodTableLookupStatic(Class c,
274: String name, boolean isPrivileged) {
275: Method[] ms = getMethods(c, isPrivileged);
276: Vector result = new Vector(ms.length);
277: for (int i = 0; i < ms.length; i++) {
278: Method m = ms[i];
279: if (Modifier.isStatic(m.getModifiers())
280: && m.getName().equals(name))
281: result.addElement(m);
282: }
283: Object[] result1 = new Object[result.size()];
284: result.copyInto(result1);
285: return methodArray(result1);
286: }
287:
288: public static Object[] methodTableLookupInstance(Class c,
289: String name) {
290: return methodTableLookupInstance(c, name, false);
291: }
292:
293: public static Object[] methodTableLookupInstance(Class c,
294: String name, boolean isPrivileged) {
295: Vector result = methodVector(c, name, isPrivileged);
296: Object[] result1 = new Object[result.size()];
297: result.copyInto(result1);
298: return methodArray(result1);
299: }
300:
301: public static Vector methodVector(Class c, String name) {
302: return methodVector(c, name, false);
303: }
304:
305: public static Vector methodVector(Class c, String name,
306: boolean isPrivileged) {
307: return methodVectorMerge(c, name, new Vector(10), isPrivileged);
308: }
309:
310: /** Add new methods to your superclasses table. **/
311: public static Vector methodVectorMerge(Class c, String name,
312: Vector result) {
313: return methodVectorMerge(c, name, result, false);
314: }
315:
316: public static Vector methodVectorMerge(Class c, String name,
317: Vector result, boolean isPrivileged) {
318: Class s = c.getSuperclass();
319:
320: if (s != null)
321: result = methodVectorMerge(s, name, result, isPrivileged);
322: Class[] is = c.getInterfaces();
323: for (int i = 0; i < is.length; i = i + 1)
324: result = methodVectorMerge(is[i], name, result,
325: isPrivileged);
326:
327: Method[] ms = getMethods(c, isPrivileged);
328: for (int i = 0; i < ms.length; i++) {
329: Method m = ms[i];
330: if ((!Modifier.isStatic(m.getModifiers())) &&
331: // KRA 25OCT04: Fixes problem with .append in JDK 1.5.0
332: ((isPrivileged || (Modifier.isPublic(m
333: .getModifiers()) && Modifier.isPublic(m
334: .getDeclaringClass().getModifiers()))) && m
335: .getName().equals(name)))
336: maybeAdd(result, m);
337:
338: }
339: return result;
340: }
341:
342: /** Only add an instance method if no superclass provides one. **/
343: private static void maybeAdd(Vector result, Method m1) {
344: for (int i = 0; i < result.size(); i++) {
345: Method m2 = ((Method) result.elementAt(i));
346: if (parameterTypesMatch(getParameterTypes(m1),
347: getParameterTypes(m2)))
348: return;
349: }
350: result.addElement(m1);
351: }
352:
353: private static Class[] getParameterTypes(Object m) {
354: return (m instanceof Method) ? ((Method) m).getParameterTypes()
355: : ((Constructor) m).getParameterTypes();
356: }
357:
358: /** Returns Object[] of parameterType, method pairs. **/
359: private static Object[] methodArray(Object[] v) {
360: Object[] result = new Object[v.length * BUCKET_SIZE];
361: for (int i = 0; i < v.length; i++) {
362: result[i * BUCKET_SIZE] = getParameterTypes(v[i]);
363: result[i * BUCKET_SIZE + 1] = v[i];
364: }
365: return result;
366: }
367:
368: /** Do the paramter types of an instance method match? **/
369: public static boolean parameterTypesMatch(Class[] p1, Class[] p2) {
370: if (p1.length == p2.length) {
371: for (int i = 0; i < p1.length; i++)
372: if (p1[i] != p2[i])
373: return false;
374: return true;
375: } else
376: return false;
377: }
378:
379: /** Find the most applicable method. For instance methods
380: getMethods() has already handled the "this" argument, so
381: instance and static methods are matched the same way. **/
382:
383: public static Object findMethod(Object[] methods, Object[] args) {
384: if (methods.length == BUCKET_SIZE)
385: return methods[1]; // Hope it works!
386: return findMethodNoOpt(methods, args);
387: }
388:
389: static Object findMethodNoOpt(Object[] methods, Object[] args) {
390: int best = -1;
391: for (int m1 = 0; m1 < methods.length; m1 = m1 + BUCKET_SIZE) {
392: Class[] p1 = ((Class[]) methods[m1]);
393: if (isApplicable(p1, args)
394: && (best == -1 || !moreApplicable(
395: ((Class[]) methods[best]), p1)))
396: best = m1;
397: }
398: if (best != -1)
399: return methods[best + 1];
400:
401: // print debugging info
402: StringBuffer alts = new StringBuffer();
403: for (int m1 = 0; m1 < methods.length; m1 = m1 + BUCKET_SIZE)
404: if (methods[m1 + 1] instanceof Member)
405: alts.append(" * " + methods[m1 + 1] + "\n");
406: else {
407: Class[] ts = (Class[]) methods[m1];
408: alts.append(" * " + methods[m1 + 1] + " ( ");
409: for (int i = 0; i < ts.length; i++)
410: alts.append(ts[i] + " ");
411: alts.append(")\n");
412: }
413:
414: StringBuffer argtypes = new StringBuffer();
415: for (int i = 0; i < args.length; i++)
416: if (args[i] == null)
417: argtypes.append(" ? ");
418: else
419: argtypes.append(" " + args[i].getClass() + " ");
420: return E
421: .error("\n\nERROR: NO "
422: + ((methods[1] instanceof Member) ? ((methods[1] instanceof Method) ? "METHOD"
423: : "CONSTRUCTOR")
424: : "PROCEDURE")
425: + " WITH NAME\n "
426: + ((methods[1] instanceof Member) ? ((Member) methods[1])
427: .getName()
428: : "?") + "\n and args\n "
429: + U.vectorToList(args) + "\n of types \n "
430: + argtypes.toString()
431: + "\n\n possible alternatives are :\n"
432: + alts.toString() + "\n\n");
433: }
434:
435: public static boolean isApplicable(Class[] types, Object[] args) {
436: if (types.length == args.length) {
437: for (int i = 0; i < args.length; i++)
438: if (!isArgApplicable(types[i], args[i]))
439: return false;
440: return true;
441: } else
442: return false;
443: }
444:
445: // Applets don't allow .getClass for non-public objects x
446: // but (x instanceof C) is OK
447: private static boolean isArgApplicable(Class p, Object a) {
448: return (a == null && Object.class.isAssignableFrom(p))
449: || p.isInstance(a) || p.isPrimitive()
450: && (primitiveWrapperType(p)).isInstance(a);
451: }
452:
453: /** Given a primitive type return its wrapper class. **/
454: private static Class primitiveWrapperType(Class p) {
455: return p == Byte.TYPE ? Byte.class
456: : p == Long.TYPE ? Long.class
457: : p == Float.TYPE ? Float.class
458: : p == Short.TYPE ? Short.class
459: : p == Double.TYPE ? Double.class
460: : p == Boolean.TYPE ? Boolean.class
461: : p == Integer.TYPE ? Integer.class
462: : p == Character.TYPE ? Character.class
463: : (Class) E
464: .error(
465: "unknow primitive type: ",
466: p);
467: }
468:
469: /** A method m1 is more specific than method m2 if all parameters of
470: m1 are subclasses of the corresponding parameters of m2. **/
471: private static boolean moreApplicable(Class[] p1, Class[] p2) {
472: for (int i = 0; i < p1.length; i++)
473: if (!p2[i].isAssignableFrom(p1[i]))
474: return false;
475: return true;
476: }
477:
478: /** Look up a particular method given its name, and the name of its
479: declaring class, and a list of argument type names.
480: <P>This is only used by (method).
481: **/
482:
483: public static Method findMethod(String name, Object target,
484: Pair types) {
485: try {
486: return U.toClass(target).getMethod(name,
487: toClassArray(types, 0));
488: } catch (NoSuchMethodException e) {
489: return ((Method) E.error("No method: ", U.list(name,
490: target, types)));
491: }
492: }
493:
494: /** Look up a particular constructor given its name, and the name of its
495: declaring class, and a list of argument type names.
496: <p>This is only used by (constructor).
497: **/
498: public static Constructor findConstructor(Object target, Pair types) {
499: try {
500: return U.toClass(target).getConstructor(
501: toClassArray(types, 0));
502: } catch (NoSuchMethodException e) {
503: return ((Constructor) E.error("No constructor: ", U.list(
504: target, types)));
505: }
506: }
507:
508: public static Class[] toClassArray(Pair types, int n) {
509: if (types == Pair.EMPTY)
510: return new Class[n];
511: else {
512: Class[] cs = toClassArray(((Pair) types.rest), n + 1);
513: cs[n] = U.toClass(types.first);
514: return cs;
515: }
516: }
517:
518: /** Return all the methods for this class. If you can't get all, for
519: * some reason,, just return the public ones.
520: <p>Memoizable.
521: **/
522: public static Method[] getMethods(Class c, boolean isPrivileged) {
523: Method[] methods = getAllMethods(c, isPrivileged);
524: return (methods == null) ? c.getMethods() : methods;
525: }
526:
527: /** Return all the methods on this class, and make them accessable.
528: If you can't for some reason, return null;
529: **/
530: private static Method[] getAllMethods(Class c) {
531: return getAllMethods(c, false);
532: }
533:
534: private static Method[] getAllMethods(Class c, boolean isPrivileged) {
535: if (isPrivileged)
536: try {
537: return ((Method[]) makeAccessible(getAllMethods0(c)));
538: } catch (Exception e) {
539: return null;
540: }
541: else
542: return null;
543: }
544:
545: /**
546: In some situations you may not be able to get declared methods.
547: We only try once.
548: **/
549: static final boolean ALLOW_PRIVATE_ACCESS = true;
550: private static boolean CAN_GET_DECLARED_METHODS = ALLOW_PRIVATE_ACCESS ? canGetDeclaredMethods()
551: : false;
552:
553: private static boolean canGetDeclaredMethods() {
554: try {
555: Invoke.class.getDeclaredMethods();
556: return true;
557: } catch (Exception e) {
558: return false;
559: }
560: }
561:
562: private static Method[] getAllMethods0(Class c) {
563: if (CAN_GET_DECLARED_METHODS) {
564: Hashtable table = new Hashtable(35);
565: collectDeclaredMethods(c, table);
566: Enumeration e = ((Enumeration) table.elements());
567: Method[] ms = new Method[table.size()];
568: for (int i = 0; e.hasMoreElements(); i++)
569: ms[i] = ((Method) e.nextElement());
570: return ms;
571: } else
572: return null;
573: }
574:
575: private static void collectDeclaredMethods(Class c, Hashtable h) {
576: Method[] ms = c.getDeclaredMethods();
577: for (int i = 0; i < ms.length; i++)
578: h.put(ms[i], ms[i]);
579: Class[] is = c.getInterfaces();
580: for (int j = 0; j < is.length; j++)
581: collectDeclaredMethods(is[j], h);
582: Class sup = c.getSuperclass();
583: if (sup != null)
584: collectDeclaredMethods(sup, h);
585: }
586:
587: /**
588: Check that this JVM has AccessibleObject.
589: We only try once.
590: **/
591: static Method SETACCESSIBLE = getSetAccessibleMethod();
592:
593: private static Method getSetAccessibleMethod() {
594: try {
595: Class c = Class
596: .forName("java.lang.reflect.AccessibleObject");
597: Class ca = Class
598: .forName("[Ljava.lang.reflect.AccessibleObject;");
599: return c.getMethod("setAccessible", new Class[] { ca,
600: Boolean.TYPE });
601: } catch (Exception e) {
602: return null;
603: }
604: }
605:
606: /** Items should be of type AccessibleObject[] but we can't say that
607: on JVM's older than JDK 1.2
608: <p>Also used by JavaField.
609: **/
610: static Object[] makeAccessible(Object[] items) {
611: if (items != null && SETACCESSIBLE != null) {
612: // AccessibleObject.setAccessible(items, true);
613: try {
614: SETACCESSIBLE.invoke(null, new Object[] { items,
615: Boolean.TRUE });
616: } catch (Exception e) {
617: }
618: }
619: return items;
620: }
621: }
|