001: /*
002: * Javolution - Java(TM) Solution for Real-Time and Embedded Systems
003: * Copyright (C) 2005 - Javolution (http://javolution.org/)
004: * All rights reserved.
005: *
006: * Permission to use, copy, modify, and distribute this software is
007: * freely granted, provided that this notice is preserved.
008: */
009: package javolution.lang;
010:
011: import javolution.JavolutionError;
012: import javolution.util.FastMap;
013: import j2me.lang.CharSequence;
014: import j2me.lang.ThreadLocal;
015:
016: /**
017: * <p> This utility class greatly facilitates the use of reflection to invoke
018: * constructors or methods which may or may not exist at runtime.</p>
019: *
020: * <p> The constructors/methods are identified through their signatures
021: * represented as a {@link String}. When the constructor/method does
022: * not exist (e.g. class not found) or when the platform does not support
023: * reflection, the constructor/method is <code>null</code>
024: * (no exception raised). Here is an example of timer taking advantage
025: * of the new (JRE1.5+) high resolution time when available:[code]
026: * public static long microTime() {
027: * if (NANO_TIME_METHOD != null) { // JRE 1.5+
028: * Long time = (Long) NANO_TIME_METHOD.invoke(null); // Static method.
029: * return time.longValue() / 1000;
030: * } else { // Use the less accurate time in milliseconds.
031: * return System.currentTimeMillis() * 1000;
032: * }
033: * }
034: * private static final Reflection.Method NANO_TIME_METHOD
035: * = Reflection.getMethod("j2me.lang.System.nanoTime()");[/code]</p>
036: *
037: * <p> Arrays and primitive types are supported. For example:[code]
038: * Reflection.Constructor sbc = Reflection.getConstructor("j2me.lang.StringBuilder(int)");
039: * if (sbc != null) { // JDK 1.5+
040: * Object sb = sbc.newInstance(new Integer(32));
041: * Reflection.Method append = Reflection.getMethod("j2me.lang.StringBuilder.append(char[], int, int)");
042: * append.invoke(sb, new char[] { 'h', 'i' }, new Integer(0), new Integer(2));
043: * System.out.println(sb);
044: * }
045: *
046: * > hi[/code]</p>
047: *
048: * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
049: * @version 4.0, September 1, 2006
050: */
051: public final class Reflection {
052:
053: /**
054: * Default constructor (private to forbid instantiation).
055: */
056: private Reflection() {
057: }
058:
059: /**
060: * Returns the class having the specified name.
061: * This method searches a lookup table first, then diverse class loaders
062: * (caller, context, system); the newly found class is then initialized
063: * and added to the lookup table for future reference.
064: *
065: * @param name the name of the class to search for.
066: * @return the corresponding class
067: * @throws ClassNotFoundException if the class is not found.
068: */
069: public static Class getClass(CharSequence name)
070: throws ClassNotFoundException {
071: Class cls = (Class) _NameToClass.get(name);
072: return (cls != null) ? cls : searchClass(name.toString());
073: }
074:
075: private static Class searchClass(String name)
076: throws ClassNotFoundException {
077: Class cls = null;
078: try {
079: cls = Class.forName(name); // Caller class loader.
080: } catch (ClassNotFoundException e0) { // Try context class loader.
081: /*@JVM-1.4+@
082: try {
083: ClassLoader cl = Thread.currentThread().getContextClassLoader();
084: cls = Class.forName(name, true, cl);
085: } catch (ClassNotFoundException e1) { // Try system class loader.
086: ClassLoader cl = ClassLoader.getSystemClassLoader();
087: cls = Class.forName(name, true, cl);
088: }
089: /**/
090: if (cls == null)
091: throw new ClassNotFoundException("Cannot found class "
092: + name);
093: }
094: synchronized (_NameToClass) {
095: _NameToClass.put(name, cls);
096: }
097: return cls;
098: }
099:
100: private static final FastMap _NameToClass = new FastMap();
101:
102: /**
103: * Equivalent to {@link #getClass(CharSequence)} (for J2ME compatibility).
104: */
105: public static Class getClass(String name)
106: throws ClassNotFoundException {
107: Class cls = (Class) _NameToClass.get(name);
108: return (cls != null) ? cls : searchClass(name);
109: }
110:
111: /**
112: * Returns the constructor having the specified signature.
113: *
114: * @param signature the textual representation of the constructor signature.
115: * @return the corresponding constructor or <code>null</code> if none
116: * found.
117: */
118: public static Constructor getConstructor(String signature) {
119: int argStart = signature.indexOf('(') + 1;
120: if (argStart < 0) {
121: throw new IllegalArgumentException(
122: "Parenthesis '(' not found");
123: }
124: int argEnd = signature.indexOf(')');
125: if (argEnd < 0) {
126: throw new IllegalArgumentException(
127: "Parenthesis ')' not found");
128: }
129: String className = signature.substring(0, argStart - 1);
130: Class theClass;
131: try {
132: theClass = Reflection.getClass(className);
133: } catch (ClassNotFoundException e) {
134: return null;
135: }
136: String args = signature.substring(argStart, argEnd);
137: if (args.length() == 0)
138: return new DefaultConstructor(theClass);
139: /*@JVM-1.4+@
140: Class[] argsTypes;
141: try {
142: argsTypes = classesFor(args);
143: } catch (ClassNotFoundException e) {
144: return null;
145: }
146: try {
147: return new ReflectConstructor(theClass.getConstructor(argsTypes),
148: signature);
149: } catch (NoSuchMethodException e) {
150: }
151: /**/
152: return null;
153: }
154:
155: private static class DefaultConstructor extends Constructor {
156: final Class _class;
157:
158: DefaultConstructor(Class cl) {
159: super (new Class[0]); // No arguments.
160: _class = cl;
161: }
162:
163: public Object allocate(Object[] args) {
164: try {
165: return _class.newInstance();
166: } catch (InstantiationException e) {
167: throw new JavolutionError("Instantiation error for "
168: + _class.getName() + " default constructor", e);
169: } catch (IllegalAccessException e) {
170: throw new JavolutionError("Illegal access error for "
171: + _class.getName() + " constructor", e);
172: }
173: }
174:
175: public String toString() {
176: return _class + " default constructor";
177: }
178: }
179:
180: /*@JVM-1.4+@
181: private static final class ReflectConstructor extends Constructor {
182: private final java.lang.reflect.Constructor _value;
183:
184: private final String _signature;
185:
186: public ReflectConstructor(java.lang.reflect.Constructor value,
187: String signature) {
188: super(value.getParameterTypes());
189: _value = value;
190: _signature = signature;
191: }
192:
193: public Object allocate(Object[] args) {
194: try {
195: return _value.newInstance(args);
196: } catch (IllegalArgumentException e) {
197: throw new JavolutionError("Illegal argument for " + _signature
198: + " constructor", e);
199: } catch (InstantiationException e) {
200: throw new JavolutionError("Instantiation error for "
201: + _signature + " constructor", e);
202: } catch (IllegalAccessException e) {
203: throw new JavolutionError("Illegal access error for "
204: + _signature + " constructor", e);
205: } catch (java.lang.reflect.InvocationTargetException e) {
206: throw new JavolutionError("Invocation exception for "
207: + _signature + " constructor",
208: (java.lang.reflect.InvocationTargetException) e.getTargetException());
209: }
210: }
211:
212: public String toString() {
213: return _signature + " constructor";
214: }
215: }
216: /**/
217:
218: /**
219: * Returns the method having the specified signature.
220: *
221: * @param signature the textual representation of the method signature.
222: * @return the corresponding constructor or <code>null</code> if none
223: * found.
224: */
225: public static Method getMethod(String signature) {
226: /*@JVM-1.4+@
227: int argStart = signature.indexOf('(') + 1;
228: if (argStart < 0) {
229: throw new IllegalArgumentException("Parenthesis '(' not found");
230: }
231: int argEnd = signature.indexOf(')');
232: if (argEnd < 0) {
233: throw new IllegalArgumentException("Parenthesis ')' not found");
234: }
235: int nameStart = signature.substring(0, argStart).lastIndexOf('.') + 1;
236: try {
237:
238: String className = signature.substring(0, nameStart - 1);
239: Class theClass;
240: try {
241: theClass = Reflection.getClass(className);
242: } catch (ClassNotFoundException e) {
243: return null;
244: }
245: String methodName = signature.substring(nameStart, argStart - 1);
246: String args = signature.substring(argStart, argEnd);
247: Class[] argsTypes;
248: try {
249: argsTypes = classesFor(args);
250: } catch (ClassNotFoundException e) {
251: return null;
252: }
253: return new ReflectMethod(theClass.getMethod(methodName, argsTypes),
254: signature);
255: } catch (Throwable t) {
256: }
257: /**/
258: return null;
259: }
260:
261: /*@JVM-1.4+@
262: private static final class ReflectMethod extends Method {
263: private final java.lang.reflect.Method _value;
264:
265: private final String _signature;
266:
267: public ReflectMethod(java.lang.reflect.Method value, String signature) {
268: super(value.getParameterTypes());
269: _value = value;
270: _signature = signature;
271: }
272:
273: public Object execute(Object that, Object[] args) {
274: try {
275: return _value.invoke(that, args);
276: } catch (IllegalArgumentException e) {
277: throw new JavolutionError("Illegal argument for " + _signature
278: + " method", e);
279: } catch (IllegalAccessException e) {
280: throw new JavolutionError("Illegal access error for "
281: + _signature + " method", e);
282: } catch (java.lang.reflect.InvocationTargetException e) {
283: throw new JavolutionError("Invocation exception for "
284: + _signature + " method", (java.lang.reflect.InvocationTargetException) e
285: .getTargetException());
286: }
287: }
288:
289: public String toString() {
290: return _signature + " method";
291: }
292: }
293: /**/
294:
295: /**
296: * Returns the classes for the specified argument.
297: *
298: * @param args the comma separated arguments.
299: * @return the classes or <code>null</code> if one of the class is not found.
300: @JVM-1.4+@
301: private static Class[] classesFor(String args) throws ClassNotFoundException {
302: args = args.trim();
303: if (args.length() == 0) {
304: return new Class[0];
305: }
306: // Counts commas.
307: int commas = 0;
308: for (int i=0;;) {
309: i = args.indexOf(',', i);
310: if (i++ < 0) break;
311: commas++;
312: }
313: Class[] classes = new Class[commas + 1];
314:
315: int index = 0;
316: for (int i = 0; i < commas; i++) {
317: int sep = args.indexOf(',', index);
318: classes[i] = classFor(args.substring(index, sep).trim());
319: if (classes[i] == null) return null;
320: index = sep + 1;
321: }
322: classes[commas] = classFor(args.substring(index).trim());
323: if (classes[commas] == null) return null;
324: return classes;
325: }
326:
327: private static Class classFor(String className) throws ClassNotFoundException {
328: int arrayIndex = className.indexOf("[]");
329: if (arrayIndex >= 0) {
330: if (className.indexOf("[][]") >= 0) {
331: if (className.indexOf("[][][]") >= 0) {
332: if (className.indexOf("[][][][]") >= 0) {
333: throw new UnsupportedOperationException(
334: "The maximum array dimension is 3");
335: } else { // Dimension three.
336: return Reflection.getClass("[[["
337: + descriptorFor(className.substring(0,
338: arrayIndex)));
339: }
340: } else { // Dimension two.
341: return Reflection.getClass("[["
342: + descriptorFor(className.substring(0, arrayIndex)));
343: }
344: } else { // Dimension one.
345: return Reflection.getClass("["
346: + descriptorFor(className.substring(0, arrayIndex)));
347: }
348: }
349: if (className.equals("boolean")) {
350: return boolean.class;
351: } else if (className.equals("byte")) {
352: return byte.class;
353: } else if (className.equals("char")) {
354: return char.class;
355: } else if (className.equals("short")) {
356: return short.class;
357: } else if (className.equals("int")) {
358: return int.class;
359: } else if (className.equals("long")) {
360: return long.class;
361: } else if (className.equals("float")) {
362: return float.class;
363: } else if (className.equals("double")) {
364: return double.class;
365: } else {
366: return Reflection.getClass(className);
367: }
368: }
369:
370: private static String descriptorFor(String className) {
371: if (className.equals("boolean")) {
372: return "Z";
373: } else if (className.equals("byte")) {
374: return "B";
375: } else if (className.equals("char")) {
376: return "C";
377: } else if (className.equals("short")) {
378: return "S";
379: } else if (className.equals("int")) {
380: return "I";
381: } else if (className.equals("long")) {
382: return "J";
383: } else if (className.equals("float")) {
384: return "F";
385: } else if (className.equals("double")) {
386: return "D";
387: } else {
388: return "L" + className + ";";
389: }
390: }
391: /**/
392:
393: /**
394: * This class represents a run-time constructor obtained through reflection.
395: *
396: * Here are few examples of utilization:[code]
397: * // Default constructor (fastList = new FastList())
398: * Reflection.Constructor fastListConstructor
399: * = Reflection.getConstructor("javolution.util.FastList()");
400: * Object fastList = fastListConstructor.newInstance();
401: *
402: * // Constructor with arguments (fastMap = new FastMap(64))
403: * Reflection.Constructor fastMapConstructor
404: * = Reflection.getConstructor("javolution.util.FastMap(int)");
405: * Object fastMap = fastMapConstructor.newInstance(new Integer(64));
406: * [/code]
407: */
408: public static abstract class Constructor {
409:
410: /**
411: * Holds the parameter types.
412: */
413: private final Class[] _parameterTypes;
414:
415: /**
416: * Creates a new constructor having the specified parameter types.
417: *
418: * @param parameterTypes the parameters types.
419: */
420: protected Constructor(Class[] parameterTypes) {
421: _parameterTypes = parameterTypes;
422: }
423:
424: /**
425: * Returns an array of <code>Class</code> objects that represents
426: * the formal parameter types, in declaration order of this constructor.
427: *
428: * @return the parameter types for this constructor.
429: */
430: public Class[] getParameterTypes() {
431: return _parameterTypes;
432: }
433:
434: /**
435: * Allocates a new object using this constructor with the specified
436: * arguments.
437: *
438: * @param args the constructor arguments.
439: * @return the object being instantiated.
440: */
441: protected abstract Object allocate(Object[] args);
442:
443: /**
444: * Invokes this constructor with no argument (convenience method).
445: *
446: * @return the object being instantiated.
447: */
448: public final Object newInstance() {
449: if (_parameterTypes.length != 0)
450: throw new IllegalArgumentException(
451: "Expected number of parameters is "
452: + _parameterTypes.length);
453: return allocate(ARRAY_0);
454: }
455:
456: /**
457: * Invokes this constructor with the specified single argument.
458: *
459: * @param arg0 the first argument.
460: * @return the object being instantiated.
461: */
462: public final Object newInstance(Object arg0) {
463: if (_parameterTypes.length != 1)
464: throw new IllegalArgumentException(
465: "Expected number of parameters is "
466: + _parameterTypes.length);
467: Object[] args = (Object[]) ARRAY_1.get();
468: args[0] = arg0;
469: Object result = allocate(args);
470: args[0] = null;
471: return result;
472: }
473:
474: /**
475: * Invokes this constructor with the specified two arguments.
476: *
477: * @param arg0 the first argument.
478: * @param arg1 the second argument.
479: * @return the object being instantiated.
480: */
481: public final Object newInstance(Object arg0, Object arg1) {
482: if (_parameterTypes.length != 2)
483: throw new IllegalArgumentException(
484: "Expected number of parameters is "
485: + _parameterTypes.length);
486: Object[] args = (Object[]) ARRAY_2.get();
487: args[0] = arg0;
488: args[1] = arg1;
489: Object result = allocate(args);
490: args[0] = null;
491: args[1] = null;
492: return result;
493: }
494:
495: /**
496: * Invokes this constructor with the specified three arguments.
497: *
498: * @param arg0 the first argument.
499: * @param arg1 the second argument.
500: * @param arg2 the third argument.
501: * @return the object being instantiated.
502: */
503: public final Object newInstance(Object arg0, Object arg1,
504: Object arg2) {
505: if (_parameterTypes.length != 3)
506: throw new IllegalArgumentException(
507: "Expected number of parameters is "
508: + _parameterTypes.length);
509: Object[] args = (Object[]) ARRAY_3.get();
510: args[0] = arg0;
511: args[1] = arg1;
512: args[2] = arg2;
513: Object result = allocate(args);
514: args[0] = null;
515: args[1] = null;
516: args[2] = null;
517: return result;
518: }
519:
520: /**
521: * Invokes this constructor with the specified four arguments.
522: *
523: * @param arg0 the first argument.
524: * @param arg1 the second argument.
525: * @param arg2 the third argument.
526: * @param arg3 the fourth argument.
527: * @return the object being instantiated.
528: */
529: public final Object newInstance(Object arg0, Object arg1,
530: Object arg2, Object arg3) {
531: if (_parameterTypes.length != 4)
532: throw new IllegalArgumentException(
533: "Expected number of parameters is "
534: + _parameterTypes.length);
535: Object[] args = (Object[]) ARRAY_4.get();
536: args[0] = arg0;
537: args[1] = arg1;
538: args[2] = arg2;
539: args[3] = arg3;
540: Object result = allocate(args);
541: args[0] = null;
542: args[1] = null;
543: args[2] = null;
544: args[3] = null;
545: return result;
546: }
547:
548: }
549:
550: /**
551: * This class represents a run-time method obtained through reflection.
552: *
553: * Here are few examples of utilization:[code]
554: * // Non-static method: fastMap.put(myKey, myValue)
555: * Reflection.Method putKeyValue
556: * = Reflection.getMethod(
557: * "javolution.util.FastMap.put(j2me.lang.Object, j2me.lang.Object)");
558: * Object previous = putKeyValue.invoke(fastMap, myKey, myValue);
559: *
560: * // Static method: System.nanoTime() (JRE1.5+)
561: * Reflection.Method nanoTime
562: * = Reflection.getMethod("j2me.lang.System.nanoTime()");
563: * long time = ((Long)nanoTime.invoke(null)).longValue();[/code]
564: */
565: public static abstract class Method {
566:
567: /**
568: * Holds the parameter types.
569: */
570: private final Class[] _parameterTypes;
571:
572: /**
573: * Creates a new constructor having the specified parameter types.
574: *
575: * @param parameterTypes the parameters types.
576: */
577: protected Method(Class[] parameterTypes) {
578: _parameterTypes = parameterTypes;
579: }
580:
581: /**
582: * Returns an array of <code>Class</code> objects that represents
583: * the formal parameter types, in declaration order of this constructor.
584: *
585: * @return the parameter types for this constructor.
586: */
587: public Class[] getParameterTypes() {
588: return _parameterTypes;
589: }
590:
591: /**
592: * Executes this method with the specified arguments.
593: *
594: * @param thisObject the object upon which this method is invoked
595: * or <code>null</code> for static methods.
596: * @param args the method arguments.
597: * @return the result of the execution.
598: */
599: protected abstract Object execute(Object this Object,
600: Object[] args);
601:
602: /**
603: * Invokes this method on the specified object which might be
604: * <code>null</code> if the method is static (convenience method).
605: *
606: * @param thisObject the object upon which this method is invoked
607: * or <code>null</code> for static methods.
608: * @return the result of the invocation.
609: */
610: public final Object invoke(Object this Object) {
611: return execute(this Object, ARRAY_0);
612: }
613:
614: /**
615: * Invokes this method with the specified single argument
616: * on the specified object which might be <code>null</code>
617: * if the method is static (convenience method).
618: *
619: * @param thisObject the object upon which this method is invoked
620: * or <code>null</code> for static methods.
621: * @param arg0 the single argument.
622: * @return the result of the invocation.
623: */
624: public final Object invoke(Object this Object, Object arg0) {
625: if (_parameterTypes.length != 1)
626: throw new IllegalArgumentException(
627: "Expected number of parameters is "
628: + _parameterTypes.length);
629: Object[] args = (Object[]) ARRAY_1.get();
630: args[0] = arg0;
631: Object result = execute(this Object, args);
632: args[0] = null;
633: return result;
634: }
635:
636: /**
637: * Invokes this method with the specified two arguments
638: * on the specified object which might be <code>null</code>
639: * if the method is static (convenience method).
640: *
641: * @param thisObject the object upon which this method is invoked
642: * or <code>null</code> for static methods.
643: * @param arg0 the first argument.
644: * @param arg1 the second argument.
645: * @return the result of the invocation.
646: * @throws RuntimeException wrapping any exception raised during
647: * invocation (see <code>Throwable.getCause()</code>).
648: */
649: public final Object invoke(Object this Object, Object arg0,
650: Object arg1) {
651: if (_parameterTypes.length != 2)
652: throw new IllegalArgumentException(
653: "Expected number of parameters is "
654: + _parameterTypes.length);
655: Object[] args = (Object[]) ARRAY_2.get();
656: args[0] = arg0;
657: args[1] = arg1;
658: Object result = execute(this Object, args);
659: args[0] = null;
660: args[1] = null;
661: return result;
662: }
663:
664: /**
665: * Invokes this method with the specified three arguments
666: * on the specified object which might be <code>null</code>
667: * if the method is static.
668: *
669: * @param thisObject the object upon which this method is invoked
670: * or <code>null</code> for static methods.
671: * @param arg0 the first argument (convenience method).
672: * @param arg1 the second argument.
673: * @param arg2 the third argument.
674: * @return the result of the invocation.
675: */
676: public final Object invoke(Object this Object, Object arg0,
677: Object arg1, Object arg2) {
678: if (_parameterTypes.length != 3)
679: throw new IllegalArgumentException(
680: "Expected number of parameters is "
681: + _parameterTypes.length);
682: Object[] args = (Object[]) ARRAY_3.get();
683: args[0] = arg0;
684: args[1] = arg1;
685: args[2] = arg2;
686: Object result = execute(this Object, args);
687: args[0] = null;
688: args[1] = null;
689: args[2] = null;
690: return result;
691: }
692:
693: /**
694: * Invokes this method with the specified four arguments
695: * on the specified object which might be <code>null</code>
696: * if the method is static (convenience method).
697: *
698: * @param thisObject the object upon which this method is invoked
699: * or <code>null</code> for static methods.
700: * @param arg0 the first argument.
701: * @param arg1 the second argument.
702: * @param arg2 the third argument.
703: * @param arg3 the fourth argument.
704: * @return the result of the invocation.
705: */
706: public final Object invoke(Object this Object, Object arg0,
707: Object arg1, Object arg2, Object arg3) {
708: if (_parameterTypes.length != 3)
709: throw new IllegalArgumentException(
710: "Expected number of parameters is "
711: + _parameterTypes.length);
712: Object[] args = (Object[]) ARRAY_3.get();
713: args[0] = arg0;
714: args[1] = arg1;
715: args[2] = arg2;
716: args[3] = arg3;
717: Object result = execute(this Object, args);
718: args[0] = null;
719: args[1] = null;
720: args[2] = null;
721: args[3] = null;
722: return result;
723: }
724:
725: }
726:
727: // Holds array containers to avoid dynamic allocations.
728:
729: private static final Object[] ARRAY_0 = new Object[0]; // Immutable.
730:
731: private static final ThreadLocal ARRAY_1 = new ThreadLocal() {
732: protected Object initialValue() {
733: return new Object[1];
734: }
735: };
736:
737: private static final ThreadLocal ARRAY_2 = new ThreadLocal() {
738: protected Object initialValue() {
739: return new Object[2];
740: }
741: };
742:
743: private static final ThreadLocal ARRAY_3 = new ThreadLocal() {
744: protected Object initialValue() {
745: return new Object[3];
746: }
747: };
748:
749: private static final ThreadLocal ARRAY_4 = new ThreadLocal() {
750: protected Object initialValue() {
751: return new Object[4];
752: }
753: };
754: }
|