/*
* 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 java.lang.CharSequence;
import java.util.Collection;
import java.util.Iterator;
import javolution.context.LogContext;
import javolution.text.TextBuilder;
import javolution.util.FastComparator;
import javolution.util.FastMap;
import javolution.util.FastSet;
import javolution.util.FastTable;
/**
* <p> This utility class greatly facilitates the use of reflection to invoke
* constructors or methods which may or may not exist at runtime or
* may be loaded/unloaded dynamically such as when running on a
* <a href="http://www.osgi.org/">OSGi Platform</a>. For example:[code]
* public class Activator implements BundleActivator {
* public void start(BundleContext context) throws Exception {
* Reflection.getInstance().add(Activator.class.getClassLoader());
* ...
* }
* public void stop(BundleContext context) throws Exception {
* Reflection.getInstance().remove(Activator.class.getClassLoader());
* ...
* }
* }[/code]
*
* <p> 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 <code>null</code>
* (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.getInstance().getMethod("java.lang.System.nanoTime()");[/code]</p>
*
* <p> Arrays and primitive types are supported. For example:[code]
* Reflection.Constructor sbc = Reflection.getInstance().getConstructor("java.lang.StringBuilder(int)");
* if (sbc != null) { // JDK 1.5+
* Object sb = sbc.newInstance(new Integer(32));
* Reflection.Method append = Reflection.getInstance().getMethod("java.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]</p>
*
* @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
* @version 5.5, April 20, 2009
*/
public abstract class Reflection {
/**
* Holds the default implementation (configurable).
*/
private static volatile Reflection INSTANCE = new Default();
/**
* Holds the default implementation (configurable).
*/
public static final Configurable <Class<? extends Reflection>> CLASS = new Configurable(Default.class) {
protected void notifyChange(Object oldValue, Object newValue) {
try {
INSTANCE = (Reflection) ((Class) newValue).newInstance();
} catch (Throwable error) {
LogContext.error(error);
}
}
};
/**
* Default constructor.
*/
protected Reflection() {
}
/**
* Returns the current reflection instance. The implementation class
* is defined by {@link #CLASS} (configurable}.
*
* @return the reflection instance.
*/
public static final Reflection getInstance() {
return INSTANCE;
}
/**
* Adds the specified class loader to the research tree.
*
* @param classLoader the class loader being added.
*/
public abstract void add(Object classLoader);
/**
* Removes the specified class loader from the research tree.
* This method clears any cache data to allow for classes
* associated to the specified class loader to be garbage collected.
*
* @param classLoader the class loader being removed.
*/
public abstract void remove(Object classLoader);
/**
* Returns the class having the specified name. This method searches the
* class loader of the reflection implementation, then any
* {@link #add additional} class loaders.
* If the class is found, it is initialized
* and returned; otherwise <code>null</code> is returned.
* The class may be cached for performance reasons.
*
* @param name the name of the class to search for.
* @return the corresponding class or <code>null</code>
*/
public abstract Class getClass(CharSequence name);
/**
* Equivalent to {@link #getClass(CharSequence)} (for J2ME compatibility).
*/
public Class getClass(String name) {
Object obj = name;
if (obj instanceof CharSequence)
return getClass((CharSequence) obj);
// String not a CharSequence on J2ME
TextBuilder tmp = TextBuilder.newInstance();
try {
tmp.append(name);
return getClass(tmp);
} finally {
TextBuilder.recycle(tmp);
}
}
/**
* Returns the parent class of the specified class or interface.
*
* @param forClass the class for which the parent class is returned.
* @return the parent class of the specified class or <code>null</code>
* if none (e.g. Object.class or top interface).
*/
public abstract Class getSuperclass(Class forClass);
/**
* Returns the interfaces implemented by the specified class or interface.
*
* @param forClass the class for which the interfaces are returned.
* @return an array holding the interfaces implemented (empty if none).
*/
public abstract Class[] getInterfaces(Class forClass);
/**
* Returns the constructor having the specified signature.
*
* @param signature the textual representation of the constructor signature.
* @return the corresponding constructor or <code>null</code> if none
* found.
*/
public abstract Constructor getConstructor(String signature);
/**
* Returns the method having the specified signature.
*
* @param signature the textual representation of the method signature.
* @return the corresponding constructor or <code>null</code> if none
* found.
*/
public abstract Method getMethod(String signature);
/**
* Returns the field of specified type which has been attached to a class.
* If <code>inherited</code> is <code>true</code> the class hierarchy
* of the given class (parent classes and implementing interfaces) is
* searched. The method forces the initialization of the specified
* <code>forClass</code>.
*
* @param forClass the base class for which the attached field is searched.
* @param type the type of field being searched for.
* @param inherited indicates if the class hierarchy is searched too.
* @return an attached field of specified type possibly inherited or <code>null</code>
* if none found.
* @see #setField(java.lang.Object, java.lang.Class, java.lang.Class)
*/
public abstract <T> T getField(Class forClass, Class <T> type, boolean inherited);
/**
* Attaches a field of specified type to a class (the attached field is
* dereferenced when the class is unloaded).
*
* @param obj the field object being attached.
* @param forClass the class to which the field is attached.
* @param type the category type of the field being attached.
* @throws IllegalArgumentException if a field of specified type is already
* attached to the specified class.
*/
public abstract <T> void setField( T obj, Class forClass, Class <T> type);
/**
* This interface represents a run-time constructor obtained through reflection.
*
* Here are few examples of utilization:[code]
* // Default constructor (fastList = new FastList())
* Reflection.Constructor fastListConstructor
* = Reflection.getInstance().getConstructor("javolution.util.FastList()");
* Object fastList = fastListConstructor.newInstance();
*
* // Constructor with arguments (fastMap = new FastMap(64))
* Reflection.Constructor fastMapConstructor
* = Reflection.getInstance().getConstructor("javolution.util.FastMap(int)");
* Object fastMap = fastMapConstructor.newInstance(new Integer(64));
* [/code]
*/
public interface Constructor {
/**
* Returns an array of <code>Class</code> objects that represents
* the formal parameter types, in declaration order of this constructor.
*
* @return the parameter types for this constructor.
*/
Class[] getParameterTypes();
/**
* Invokes this constructor with no argument.
*
* @return the object being instantiated.
* @throws IllegalArgumentException if
* <code>this.getParametersTypes().length != 0</code>
*/
Object newInstance();
/**
* Invokes this constructor with the specified single argument.
*
* @param arg0 the first argument.
* @return the object being instantiated.
* @throws IllegalArgumentException if
* <code>this.getParametersTypes().length != 1</code>
*/
Object newInstance(Object arg0);
/**
* Invokes this constructor with the specified two arguments.
*
* @param arg0 the first argument.
* @param arg1 the second argument.
* @return the object being instantiated.
* @throws IllegalArgumentException if
* <code>this.getParametersTypes().length != 2</code>
*/
Object newInstance(Object arg0, Object arg1);
/**
* 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.
* @throws IllegalArgumentException if
* <code>this.getParametersTypes().length != 3</code>
*/
Object newInstance(Object arg0, Object arg1, Object arg2);
/**
* Invokes this constructor with the specified arguments.
*
* @param args the arguments.
* @return the object being instantiated.
* @throws IllegalArgumentException if
* <code>this.getParametersTypes().length != args.length</code>
*/
Object newInstance(Object... args);
/**/
}
/**
* This interface 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.getInstance().getMethod(
* "javolution.util.FastMap.put(java.lang.Object, java.lang.Object)");
* Object previous = putKeyValue.invoke(fastMap, myKey, myValue);
*
* // Static method: System.nanoTime() (JRE1.5+)
* Reflection.Method nanoTime
* = Reflection.getInstance().getMethod("java.lang.System.nanoTime()");
* long time = ((Long)nanoTime.invoke(null)).longValue();[/code]
*/
public interface Method {
/**
* Returns an array of <code>Class</code> objects that represents
* the formal parameter types, in declaration order of this constructor.
*
* @return the parameter types for this constructor.
*/
Class[] getParameterTypes();
/**
* Invokes this method on the specified object which might be
* <code>null</code> if the method is static (convenience method).
*
* @param thisObject the object upon which this method is invoked
* or <code>null</code> for static methods.
* @return the result of the invocation.
* @throws IllegalArgumentException if
* <code>this.getParametersTypes().length != 0</code>
*/
Object invoke(Object thisObject);
/**
* Invokes this method with the specified single argument
* on the specified object which might be <code>null</code>
* if the method is static (convenience method).
*
* @param thisObject the object upon which this method is invoked
* or <code>null</code> for static methods.
* @param arg0 the single argument.
* @return the result of the invocation.
* @throws IllegalArgumentException if
* <code>this.getParametersTypes().length != 1</code>
*/
Object invoke(Object thisObject, Object arg0);
/**
* Invokes this method with the specified two arguments
* on the specified object which might be <code>null</code>
* if the method is static (convenience method).
*
* @param thisObject the object upon which this method is invoked
* or <code>null</code> for static methods.
* @param arg0 the first argument.
* @param arg1 the second argument.
* @return the result of the invocation.
* @throws IllegalArgumentException if
* <code>this.getParametersTypes().length != 2</code>
*/
Object invoke(Object thisObject, Object arg0, Object arg1);
/**
* Invokes this method with the specified three arguments
* on the specified object which might be <code>null</code>
* if the method is static.
*
* @param thisObject the object upon which this method is invoked
* or <code>null</code> 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.
* @throws IllegalArgumentException if
* <code>this.getParametersTypes().length != 3</code>
*/
Object invoke(Object thisObject, Object arg0, Object arg1,
Object arg2);
/**
* Invokes this method with the specified arguments
* on the specified object which might be <code>null</code>
* if the method is static.
*
* @param thisObject the object upon which this method is invoked
* or <code>null</code> for static methods.
* @param args the arguments.
* @return the result of the invocation.
* @throws IllegalArgumentException if
* <code>this.getParametersTypes().length != args.length</code>
*/
Object invoke(Object thisObject, Object... args);
/**/
}
//////////////////////////////////
// Holds Default Implementation //
//////////////////////////////////
private static final class Default extends Reflection {
private final FastMap _fields = new FastMap().shared();
private final Collection _classLoaders = new FastSet().shared();
private final FastMap _nameToClass = new FastMap().shared().setKeyComparator(FastComparator.LEXICAL);
public void add(Object classLoader) {
_classLoaders.add(classLoader);
}
public void remove(Object classLoader) {
_classLoaders.remove(classLoader);
_nameToClass.clear(); // Clear cache.
for (Iterator i = _fields.entrySet().iterator(); i.hasNext();) {
FastMap.Entry entry = (FastMap.Entry) i.next();
Class cls = (Class) entry.getKey();
if (cls.getClassLoader().equals(classLoader)) {
_fields.remove(cls); // Remove class and its fields.
}
}
}
public Class getClass(CharSequence name) {
Class cls = (Class) _nameToClass.get(name); // First search cache.
return (cls != null) ? cls : searchClass(name.toString());
}
private Class searchClass(String name) {
Class cls = null;
try {
cls = Class.forName(name);
} catch (ClassNotFoundException e1) {
/* */
for (Iterator i = _classLoaders.iterator(); i.hasNext();) {
ClassLoader classLoader = (ClassLoader) i.next();
try {
cls = Class.forName(name, true, classLoader);
} catch (ClassNotFoundException e2) {
// Not found, continue.
}
}
/**/
}
if (cls != null) { // Cache the result.
_nameToClass.put(name, cls);
}
return cls;
}
public 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 = getClass(className);
if (theClass == null) {
return null;
}
String args = signature.substring(argStart, argEnd);
if (args.length() == 0) {
return new DefaultConstructor(theClass);
}
/**/
Class[] argsTypes = classesFor(args);
if (argsTypes == null) return null;
try {
return new ReflectConstructor(theClass.getConstructor(argsTypes),
signature);
} catch (NoSuchMethodException e) {
}
/**/
LogContext.warning("Reflection not supported (Reflection.getConstructor(String)");
return null;
}
public Class[] getInterfaces(Class cls) {
/**/
if (true) return cls.getInterfaces();
/**/
LogContext.warning("Reflection not supported (Reflection.getInterfaces(Class)");
return new Class[0];
}
public Class getSuperclass(Class cls) {
/**/
if (true) return cls.getSuperclass();
/**/
LogContext.warning("Reflection not supported (Reflection.getSuperclass(Class)");
return null;
}
// Implements abstract method.
public Method getMethod(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");
}
int nameStart = signature.substring(0, argStart).lastIndexOf('.') + 1;
try {
String className = signature.substring(0, nameStart - 1);
Class theClass = getClass(className);
if (theClass == null) return null;
String methodName = signature.substring(nameStart, argStart - 1);
String args = signature.substring(argStart, argEnd);
Class[] argsTypes = classesFor(args);
if (argsTypes == null) return null;
return new ReflectMethod(theClass.getMethod(methodName, argsTypes),
signature);
} catch (Throwable t) {
}
/**/
LogContext.warning("Reflection not supported (Reflection.getMethod(String)");
return null;
}
@Override
public Object getField(Class forClass, Class type, boolean inherited) {
ClassInitializer.initialize(forClass);
return getField2(forClass, type, inherited);
}
private Object getField2(Class forClass, Class type, boolean inherited) {
FastMap typeToField = (FastMap) _fields.get(forClass);
if (typeToField != null) {
Object field = typeToField.get(type);
if (field != null)
return field;
}
if (!inherited)
return null;
// Search direct interfaces.
Class[] interfaces = getInterfaces(forClass);
for (int i = 0; i < interfaces.length; i++) {
Object field = getField2(interfaces[i], type, false);
if (field != null)
return field;
}
// Recursion with the parent class.
Class parentClass = getSuperclass(forClass);
return (parentClass != null) ? getField2(parentClass, type, inherited) : null;
}
@Override
public void setField(Object obj, Class forClass, Class type) {
synchronized (forClass) { // We don't want to attach simultaneously to the same class.
FastMap typeToField = (FastMap) _fields.get(forClass);
if ((typeToField != null) && typeToField.containsKey(type))
throw new IllegalArgumentException("Field of type " + type + " already attached to class " + forClass);
if (typeToField == null) {
typeToField = new FastMap();
_fields.put(forClass, typeToField);
}
typeToField.put(type, obj);
}
}
////////////////////////////////
// Constructor Implementation //
////////////////////////////////
private static abstract class BaseConstructor implements Constructor {
private final Class[] _parameterTypes;
protected BaseConstructor(Class[] parameterTypes) {
_parameterTypes = parameterTypes;
}
public Class[] getParameterTypes() {
return _parameterTypes;
}
protected abstract Object allocate(Object[] args);
public final Object newInstance() {
if (_parameterTypes.length != 0)
throw new IllegalArgumentException(
"Expected number of parameters is " + _parameterTypes.length);
return allocate(EMPTY_ARRAY);
}
public final Object newInstance(Object arg0) {
if (_parameterTypes.length != 1)
throw new IllegalArgumentException(
"Expected number of parameters is " + _parameterTypes.length);
Object[] args = {arg0};
return allocate(args);
}
public final Object newInstance(Object arg0, Object arg1) {
if (_parameterTypes.length != 2)
throw new IllegalArgumentException(
"Expected number of parameters is " + _parameterTypes.length);
Object[] args = {arg0, arg1};
return allocate(args);
}
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 = {arg0, arg1, arg2};
return allocate(args);
}
/**
*/
public final Object newInstance(Object... args) {
if (_parameterTypes.length != args.length)
throw new IllegalArgumentException(
"Expected number of parameters is " + _parameterTypes.length);
return allocate(args);
}
/**/
}
private static class DefaultConstructor extends BaseConstructor {
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 RuntimeException("Default constructor instantiation error for " + _class.getName() + " (" + e.getMessage() + ")");
} catch (IllegalAccessException e) {
throw new RuntimeException("Default constructor illegal access error for " + _class.getName() + " (" + e.getMessage() + ")");
}
}
public String toString() {
return _class + " default constructor";
}
}
/**/
private final class ReflectConstructor extends BaseConstructor {
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 (InstantiationException e) {
throw new RuntimeException("Instantiation error for "
+ _signature + " constructor", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Illegal access error for "
+ _signature + " constructor", e);
} catch (java.lang.reflect.InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException)
throw (RuntimeException)e.getCause();
throw new RuntimeException("Invocation exception for "
+ _signature + " constructor",
(java.lang.reflect.InvocationTargetException) e.getCause());
}
}
public String toString() {
return _signature + " constructor";
}
}
/**/
///////////////////////////
// Method Implementation //
///////////////////////////
private static abstract class BaseMethod implements Method {
private final Class[] _parameterTypes;
protected BaseMethod(Class[] parameterTypes) {
_parameterTypes = parameterTypes;
}
public Class[] getParameterTypes() {
return _parameterTypes;
}
protected abstract Object execute(Object thisObject, Object[] args);
public final Object invoke(Object thisObject) {
if (_parameterTypes.length != 0)
throw new IllegalArgumentException(
"Expected number of parameters is " + _parameterTypes.length);
return execute(thisObject, EMPTY_ARRAY);
}
public final Object invoke(Object thisObject, Object arg0) {
if (_parameterTypes.length != 1)
throw new IllegalArgumentException(
"Expected number of parameters is " + _parameterTypes.length);
Object[] args = {arg0};
return execute(thisObject, args);
}
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 = {arg0, arg1};
return execute(thisObject, args);
}
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 = {arg0, arg1, arg2};
return execute(thisObject, args);
}
/**
*/
public final Object invoke(Object thisObject, Object... args) {
if (_parameterTypes.length != args.length)
throw new IllegalArgumentException(
"Expected number of parameters is " + _parameterTypes.length);
return execute(thisObject, args);
}
/**/
}
/**/
private final class ReflectMethod extends BaseMethod {
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 (IllegalAccessException e) {
throw new IllegalAccessError("Illegal access error for " + _signature + " method");
} catch (java.lang.reflect.InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException)
throw (RuntimeException) e.getCause();
throw new RuntimeException(
"Invocation exception for " + _signature + " method",
(java.lang.reflect.InvocationTargetException) e.getCause());
}
}
public String toString() {
return _signature + " method";
}
}
/**/
/**
* Returns the classes for the specified argument.
*
* @param args the comma separated arguments.
* @return the classes or <code>null</code> if one of the class is not found.
*/
private Class[] classesFor(String args) {
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 Class classFor(String className) {
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 getClass("[[["
+ descriptorFor(className.substring(0,
arrayIndex)));
}
} else { // Dimension two.
return getClass("[["
+ descriptorFor(className.substring(0, arrayIndex)));
}
} else { // Dimension one.
return 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 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 + ";";
}
}
/**/
}
private static final Object[] EMPTY_ARRAY = new Object[0]; // Immutable.
}