/* * Javolution - Java(TM) Solution for Real-Time and Embedded Systems * Copyright (C) 2005 - Javolution (http://javolution.org/) * All rights reserved. * * Permission to use, copy, modify, and distribute this software is * freely granted, provided that this notice is preserved. */ package javolution.lang; import javolution.JavolutionError; import javolution.util.FastMap; import j2me.lang.CharSequence; import j2me.lang.ThreadLocal; /** *

This utility class greatly facilitates the use of reflection to invoke * constructors or methods which may or may not exist at runtime.

* *

The constructors/methods are identified through their signatures * represented as a {@link String}. When the constructor/method does * not exist (e.g. class not found) or when the platform does not support * reflection, the constructor/method is null * (no exception raised). Here is an example of timer taking advantage * of the new (JRE1.5+) high resolution time when available:[code] * public static long microTime() { * if (NANO_TIME_METHOD != null) { // JRE 1.5+ * Long time = (Long) NANO_TIME_METHOD.invoke(null); // Static method. * return time.longValue() / 1000; * } else { // Use the less accurate time in milliseconds. * return System.currentTimeMillis() * 1000; * } * } * private static final Reflection.Method NANO_TIME_METHOD * = Reflection.getMethod("j2me.lang.System.nanoTime()");[/code]

* *

Arrays and primitive types are supported. For example:[code] * Reflection.Constructor sbc = Reflection.getConstructor("j2me.lang.StringBuilder(int)"); * if (sbc != null) { // JDK 1.5+ * Object sb = sbc.newInstance(new Integer(32)); * Reflection.Method append = Reflection.getMethod("j2me.lang.StringBuilder.append(char[], int, int)"); * append.invoke(sb, new char[] { 'h', 'i' }, new Integer(0), new Integer(2)); * System.out.println(sb); * } * * > hi[/code]

* * @author Jean-Marie Dautelle * @version 4.0, September 1, 2006 */ public final class Reflection { /** * Default constructor (private to forbid instantiation). */ private Reflection() { } /** * Returns the class having the specified name. * This method searches a lookup table first, then diverse class loaders * (caller, context, system); the newly found class is then initialized * and added to the lookup table for future reference. * * @param name the name of the class to search for. * @return the corresponding class * @throws ClassNotFoundException if the class is not found. */ public static Class getClass(CharSequence name) throws ClassNotFoundException { Class cls = (Class) _NameToClass.get(name); return (cls != null) ? cls : searchClass(name.toString()); } private static Class searchClass(String name) throws ClassNotFoundException { Class cls = null; try { cls = Class.forName(name); // Caller class loader. } catch (ClassNotFoundException e0) { // Try context class loader. /*@JVM-1.4+@ try { ClassLoader cl = Thread.currentThread().getContextClassLoader(); cls = Class.forName(name, true, cl); } catch (ClassNotFoundException e1) { // Try system class loader. ClassLoader cl = ClassLoader.getSystemClassLoader(); cls = Class.forName(name, true, cl); } /**/ if (cls == null) throw new ClassNotFoundException("Cannot found class " + name); } synchronized (_NameToClass) { _NameToClass.put(name, cls); } return cls; } private static final FastMap _NameToClass = new FastMap(); /** * Equivalent to {@link #getClass(CharSequence)} (for J2ME compatibility). */ public static Class getClass(String name) throws ClassNotFoundException { Class cls = (Class) _NameToClass.get(name); return (cls != null) ? cls : searchClass(name); } /** * Returns the constructor having the specified signature. * * @param signature the textual representation of the constructor signature. * @return the corresponding constructor or null if none * found. */ public static Constructor getConstructor(String signature) { int argStart = signature.indexOf('(') + 1; if (argStart < 0) { throw new IllegalArgumentException("Parenthesis '(' not found"); } int argEnd = signature.indexOf(')'); if (argEnd < 0) { throw new IllegalArgumentException("Parenthesis ')' not found"); } String className = signature.substring(0, argStart - 1); Class theClass; try { theClass = Reflection.getClass(className); } catch (ClassNotFoundException e) { return null; } String args = signature.substring(argStart, argEnd); if (args.length() == 0) return new DefaultConstructor(theClass); /*@JVM-1.4+@ Class[] argsTypes; try { argsTypes = classesFor(args); } catch (ClassNotFoundException e) { return null; } try { return new ReflectConstructor(theClass.getConstructor(argsTypes), signature); } catch (NoSuchMethodException e) { } /**/ return null; } private static class DefaultConstructor extends Constructor { final Class _class; DefaultConstructor(Class cl) { super(new Class[0]); // No arguments. _class = cl; } public Object allocate(Object[] args) { try { return _class.newInstance(); } catch (InstantiationException e) { throw new JavolutionError("Instantiation error for " + _class.getName() + " default constructor", e); } catch (IllegalAccessException e) { throw new JavolutionError("Illegal access error for " + _class.getName() + " constructor", e); } } public String toString() { return _class + " default constructor"; } } /*@JVM-1.4+@ private static final class ReflectConstructor extends Constructor { private final java.lang.reflect.Constructor _value; private final String _signature; public ReflectConstructor(java.lang.reflect.Constructor value, String signature) { super(value.getParameterTypes()); _value = value; _signature = signature; } public Object allocate(Object[] args) { try { return _value.newInstance(args); } catch (IllegalArgumentException e) { throw new JavolutionError("Illegal argument for " + _signature + " constructor", e); } catch (InstantiationException e) { throw new JavolutionError("Instantiation error for " + _signature + " constructor", e); } catch (IllegalAccessException e) { throw new JavolutionError("Illegal access error for " + _signature + " constructor", e); } catch (java.lang.reflect.InvocationTargetException e) { throw new JavolutionError("Invocation exception for " + _signature + " constructor", (java.lang.reflect.InvocationTargetException) e.getTargetException()); } } public String toString() { return _signature + " constructor"; } } /**/ /** * Returns the method having the specified signature. * * @param signature the textual representation of the method signature. * @return the corresponding constructor or null if none * found. */ public static Method getMethod(String signature) { /*@JVM-1.4+@ int argStart = signature.indexOf('(') + 1; if (argStart < 0) { throw new IllegalArgumentException("Parenthesis '(' not found"); } int argEnd = signature.indexOf(')'); if (argEnd < 0) { throw new IllegalArgumentException("Parenthesis ')' not found"); } int nameStart = signature.substring(0, argStart).lastIndexOf('.') + 1; try { String className = signature.substring(0, nameStart - 1); Class theClass; try { theClass = Reflection.getClass(className); } catch (ClassNotFoundException e) { return null; } String methodName = signature.substring(nameStart, argStart - 1); String args = signature.substring(argStart, argEnd); Class[] argsTypes; try { argsTypes = classesFor(args); } catch (ClassNotFoundException e) { return null; } return new ReflectMethod(theClass.getMethod(methodName, argsTypes), signature); } catch (Throwable t) { } /**/ return null; } /*@JVM-1.4+@ private static final class ReflectMethod extends Method { private final java.lang.reflect.Method _value; private final String _signature; public ReflectMethod(java.lang.reflect.Method value, String signature) { super(value.getParameterTypes()); _value = value; _signature = signature; } public Object execute(Object that, Object[] args) { try { return _value.invoke(that, args); } catch (IllegalArgumentException e) { throw new JavolutionError("Illegal argument for " + _signature + " method", e); } catch (IllegalAccessException e) { throw new JavolutionError("Illegal access error for " + _signature + " method", e); } catch (java.lang.reflect.InvocationTargetException e) { throw new JavolutionError("Invocation exception for " + _signature + " method", (java.lang.reflect.InvocationTargetException) e .getTargetException()); } } public String toString() { return _signature + " method"; } } /**/ /** * Returns the classes for the specified argument. * * @param args the comma separated arguments. * @return the classes or null if one of the class is not found. @JVM-1.4+@ private static Class[] classesFor(String args) throws ClassNotFoundException { args = args.trim(); if (args.length() == 0) { return new Class[0]; } // Counts commas. int commas = 0; for (int i=0;;) { i = args.indexOf(',', i); if (i++ < 0) break; commas++; } Class[] classes = new Class[commas + 1]; int index = 0; for (int i = 0; i < commas; i++) { int sep = args.indexOf(',', index); classes[i] = classFor(args.substring(index, sep).trim()); if (classes[i] == null) return null; index = sep + 1; } classes[commas] = classFor(args.substring(index).trim()); if (classes[commas] == null) return null; return classes; } private static Class classFor(String className) throws ClassNotFoundException { int arrayIndex = className.indexOf("[]"); if (arrayIndex >= 0) { if (className.indexOf("[][]") >= 0) { if (className.indexOf("[][][]") >= 0) { if (className.indexOf("[][][][]") >= 0) { throw new UnsupportedOperationException( "The maximum array dimension is 3"); } else { // Dimension three. return Reflection.getClass("[[[" + descriptorFor(className.substring(0, arrayIndex))); } } else { // Dimension two. return Reflection.getClass("[[" + descriptorFor(className.substring(0, arrayIndex))); } } else { // Dimension one. return Reflection.getClass("[" + descriptorFor(className.substring(0, arrayIndex))); } } if (className.equals("boolean")) { return boolean.class; } else if (className.equals("byte")) { return byte.class; } else if (className.equals("char")) { return char.class; } else if (className.equals("short")) { return short.class; } else if (className.equals("int")) { return int.class; } else if (className.equals("long")) { return long.class; } else if (className.equals("float")) { return float.class; } else if (className.equals("double")) { return double.class; } else { return Reflection.getClass(className); } } private static String descriptorFor(String className) { if (className.equals("boolean")) { return "Z"; } else if (className.equals("byte")) { return "B"; } else if (className.equals("char")) { return "C"; } else if (className.equals("short")) { return "S"; } else if (className.equals("int")) { return "I"; } else if (className.equals("long")) { return "J"; } else if (className.equals("float")) { return "F"; } else if (className.equals("double")) { return "D"; } else { return "L" + className + ";"; } } /**/ /** * This class represents a run-time constructor obtained through reflection. * * Here are few examples of utilization:[code] * // Default constructor (fastList = new FastList()) * Reflection.Constructor fastListConstructor * = Reflection.getConstructor("javolution.util.FastList()"); * Object fastList = fastListConstructor.newInstance(); * * // Constructor with arguments (fastMap = new FastMap(64)) * Reflection.Constructor fastMapConstructor * = Reflection.getConstructor("javolution.util.FastMap(int)"); * Object fastMap = fastMapConstructor.newInstance(new Integer(64)); * [/code] */ public static abstract class Constructor { /** * Holds the parameter types. */ private final Class[] _parameterTypes; /** * Creates a new constructor having the specified parameter types. * * @param parameterTypes the parameters types. */ protected Constructor(Class[] parameterTypes) { _parameterTypes = parameterTypes; } /** * Returns an array of Class objects that represents * the formal parameter types, in declaration order of this constructor. * * @return the parameter types for this constructor. */ public Class[] getParameterTypes() { return _parameterTypes; } /** * Allocates a new object using this constructor with the specified * arguments. * * @param args the constructor arguments. * @return the object being instantiated. */ protected abstract Object allocate(Object[] args); /** * Invokes this constructor with no argument (convenience method). * * @return the object being instantiated. */ public final Object newInstance() { if (_parameterTypes.length != 0) throw new IllegalArgumentException( "Expected number of parameters is " + _parameterTypes.length); return allocate(ARRAY_0); } /** * Invokes this constructor with the specified single argument. * * @param arg0 the first argument. * @return the object being instantiated. */ public final Object newInstance(Object arg0) { if (_parameterTypes.length != 1) throw new IllegalArgumentException( "Expected number of parameters is " + _parameterTypes.length); Object[] args = (Object[])ARRAY_1.get(); args[0] = arg0; Object result = allocate(args); args[0] = null; return result; } /** * Invokes this constructor with the specified two arguments. * * @param arg0 the first argument. * @param arg1 the second argument. * @return the object being instantiated. */ public final Object newInstance(Object arg0, Object arg1) { if (_parameterTypes.length != 2) throw new IllegalArgumentException( "Expected number of parameters is " + _parameterTypes.length); Object[] args = (Object[])ARRAY_2.get(); args[0] = arg0; args[1] = arg1; Object result = allocate(args); args[0] = null; args[1] = null; return result; } /** * Invokes this constructor with the specified three arguments. * * @param arg0 the first argument. * @param arg1 the second argument. * @param arg2 the third argument. * @return the object being instantiated. */ public final Object newInstance(Object arg0, Object arg1, Object arg2) { if (_parameterTypes.length != 3) throw new IllegalArgumentException( "Expected number of parameters is " + _parameterTypes.length); Object[] args = (Object[])ARRAY_3.get(); args[0] = arg0; args[1] = arg1; args[2] = arg2; Object result = allocate(args); args[0] = null; args[1] = null; args[2] = null; return result; } /** * Invokes this constructor with the specified four arguments. * * @param arg0 the first argument. * @param arg1 the second argument. * @param arg2 the third argument. * @param arg3 the fourth argument. * @return the object being instantiated. */ public final Object newInstance(Object arg0, Object arg1, Object arg2, Object arg3) { if (_parameterTypes.length != 4) throw new IllegalArgumentException( "Expected number of parameters is " + _parameterTypes.length); Object[] args = (Object[])ARRAY_4.get(); args[0] = arg0; args[1] = arg1; args[2] = arg2; args[3] = arg3; Object result = allocate(args); args[0] = null; args[1] = null; args[2] = null; args[3] = null; return result; } } /** * This class represents a run-time method obtained through reflection. * * Here are few examples of utilization:[code] * // Non-static method: fastMap.put(myKey, myValue) * Reflection.Method putKeyValue * = Reflection.getMethod( * "javolution.util.FastMap.put(j2me.lang.Object, j2me.lang.Object)"); * Object previous = putKeyValue.invoke(fastMap, myKey, myValue); * * // Static method: System.nanoTime() (JRE1.5+) * Reflection.Method nanoTime * = Reflection.getMethod("j2me.lang.System.nanoTime()"); * long time = ((Long)nanoTime.invoke(null)).longValue();[/code] */ public static abstract class Method { /** * Holds the parameter types. */ private final Class[] _parameterTypes; /** * Creates a new constructor having the specified parameter types. * * @param parameterTypes the parameters types. */ protected Method(Class[] parameterTypes) { _parameterTypes = parameterTypes; } /** * Returns an array of Class objects that represents * the formal parameter types, in declaration order of this constructor. * * @return the parameter types for this constructor. */ public Class[] getParameterTypes() { return _parameterTypes; } /** * Executes this method with the specified arguments. * * @param thisObject the object upon which this method is invoked * or null for static methods. * @param args the method arguments. * @return the result of the execution. */ protected abstract Object execute(Object thisObject, Object[] args); /** * Invokes this method on the specified object which might be * null if the method is static (convenience method). * * @param thisObject the object upon which this method is invoked * or null for static methods. * @return the result of the invocation. */ public final Object invoke(Object thisObject) { return execute(thisObject, ARRAY_0); } /** * Invokes this method with the specified single argument * on the specified object which might be null * if the method is static (convenience method). * * @param thisObject the object upon which this method is invoked * or null for static methods. * @param arg0 the single argument. * @return the result of the invocation. */ public final Object invoke(Object thisObject, Object arg0) { if (_parameterTypes.length != 1) throw new IllegalArgumentException( "Expected number of parameters is " + _parameterTypes.length); Object[] args = (Object[])ARRAY_1.get(); args[0] = arg0; Object result = execute(thisObject, args); args[0] = null; return result; } /** * Invokes this method with the specified two arguments * on the specified object which might be null * if the method is static (convenience method). * * @param thisObject the object upon which this method is invoked * or null for static methods. * @param arg0 the first argument. * @param arg1 the second argument. * @return the result of the invocation. * @throws RuntimeException wrapping any exception raised during * invocation (see Throwable.getCause()). */ public final Object invoke(Object thisObject, Object arg0, Object arg1) { if (_parameterTypes.length != 2) throw new IllegalArgumentException( "Expected number of parameters is " + _parameterTypes.length); Object[] args = (Object[])ARRAY_2.get(); args[0] = arg0; args[1] = arg1; Object result = execute(thisObject, args); args[0] = null; args[1] = null; return result; } /** * Invokes this method with the specified three arguments * on the specified object which might be null * if the method is static. * * @param thisObject the object upon which this method is invoked * or null for static methods. * @param arg0 the first argument (convenience method). * @param arg1 the second argument. * @param arg2 the third argument. * @return the result of the invocation. */ public final Object invoke(Object thisObject, Object arg0, Object arg1, Object arg2) { if (_parameterTypes.length != 3) throw new IllegalArgumentException( "Expected number of parameters is " + _parameterTypes.length); Object[] args = (Object[])ARRAY_3.get(); args[0] = arg0; args[1] = arg1; args[2] = arg2; Object result = execute(thisObject, args); args[0] = null; args[1] = null; args[2] = null; return result; } /** * Invokes this method with the specified four arguments * on the specified object which might be null * if the method is static (convenience method). * * @param thisObject the object upon which this method is invoked * or null for static methods. * @param arg0 the first argument. * @param arg1 the second argument. * @param arg2 the third argument. * @param arg3 the fourth argument. * @return the result of the invocation. */ public final Object invoke(Object thisObject, Object arg0, Object arg1, Object arg2, Object arg3) { if (_parameterTypes.length != 3) throw new IllegalArgumentException( "Expected number of parameters is " + _parameterTypes.length); Object[] args = (Object[])ARRAY_3.get(); args[0] = arg0; args[1] = arg1; args[2] = arg2; args[3] = arg3; Object result = execute(thisObject, args); args[0] = null; args[1] = null; args[2] = null; args[3] = null; return result; } } // Holds array containers to avoid dynamic allocations. private static final Object[] ARRAY_0 = new Object[0]; // Immutable. private static final ThreadLocal ARRAY_1 = new ThreadLocal() { protected Object initialValue() { return new Object[1]; } }; private static final ThreadLocal ARRAY_2 = new ThreadLocal() { protected Object initialValue() { return new Object[2]; } }; private static final ThreadLocal ARRAY_3 = new ThreadLocal() { protected Object initialValue() { return new Object[3]; } }; private static final ThreadLocal ARRAY_4 = new ThreadLocal() { protected Object initialValue() { return new Object[4]; } }; }