Reflector.java
package org.codehaus.plexus.util.reflection;
/*
* Copyright The Codehaus Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* Utility class used to instantiate an object using reflection. This utility hides many of the gory details needed to
* do this.
*
* @author John Casey
*/
public final class Reflector {
private static final String CONSTRUCTOR_METHOD_NAME = "$$CONSTRUCTOR$$";
private static final String GET_INSTANCE_METHOD_NAME = "getInstance";
private Map<String, Map<String, Map<String, Method>>> classMaps =
new HashMap<String, Map<String, Map<String, Method>>>();
/** Ensure no instances of Reflector are created...this is a utility. */
public Reflector() {}
/**
* Create a new instance of a class, given the array of parameters... Uses constructor caching to find a constructor
* that matches the parameter types, either specifically (first choice) or abstractly...
*
* @param theClass The class to instantiate
* @param params The parameters to pass to the constructor
* @param <T> the type
* @return The instantiated object
* @throws ReflectorException In case anything goes wrong here...
*/
@SuppressWarnings({"UnusedDeclaration"})
public <T> T newInstance(Class<T> theClass, Object[] params) throws ReflectorException {
if (params == null) {
params = new Object[0];
}
Class[] paramTypes = new Class[params.length];
for (int i = 0, len = params.length; i < len; i++) {
paramTypes[i] = params[i].getClass();
}
try {
Constructor<T> con = getConstructor(theClass, paramTypes);
if (con == null) {
StringBuilder buffer = new StringBuilder();
buffer.append("Constructor not found for class: ");
buffer.append(theClass.getName());
buffer.append(" with specified or ancestor parameter classes: ");
for (Class paramType : paramTypes) {
buffer.append(paramType.getName());
buffer.append(',');
}
buffer.setLength(buffer.length() - 1);
throw new ReflectorException(buffer.toString());
}
return con.newInstance(params);
} catch (InstantiationException | InvocationTargetException | IllegalAccessException ex) {
throw new ReflectorException(ex);
}
}
/**
* Retrieve the singleton instance of a class, given the array of parameters... Uses constructor caching to find a
* constructor that matches the parameter types, either specifically (first choice) or abstractly...
*
* @param theClass The class to retrieve the singleton of
* @param initParams The parameters to pass to the constructor
* @param <T> the type
* @return The singleton object
* @throws ReflectorException In case anything goes wrong here...
*/
@SuppressWarnings({"UnusedDeclaration"})
public <T> T getSingleton(Class<T> theClass, Object[] initParams) throws ReflectorException {
Class[] paramTypes = new Class[initParams.length];
for (int i = 0, len = initParams.length; i < len; i++) {
paramTypes[i] = initParams[i].getClass();
}
try {
Method method = getMethod(theClass, GET_INSTANCE_METHOD_NAME, paramTypes);
// noinspection unchecked
return (T) method.invoke(null, initParams);
} catch (InvocationTargetException | IllegalAccessException ex) {
throw new ReflectorException(ex);
}
}
/**
* Invoke the specified method on the specified target with the specified params...
*
* @param target The target of the invocation
* @param methodName The method name to invoke
* @param params The parameters to pass to the method invocation
* @return The result of the method call
* @throws ReflectorException In case of an error looking up or invoking the method.
*/
@SuppressWarnings({"UnusedDeclaration"})
public Object invoke(Object target, String methodName, Object[] params) throws ReflectorException {
if (params == null) {
params = new Object[0];
}
Class[] paramTypes = new Class[params.length];
for (int i = 0, len = params.length; i < len; i++) {
paramTypes[i] = params[i].getClass();
}
try {
Method method = getMethod(target.getClass(), methodName, paramTypes);
if (method == null) {
StringBuilder buffer = new StringBuilder();
buffer.append("Singleton-producing method named '")
.append(methodName)
.append("' not found with specified parameter classes: ");
for (Class paramType : paramTypes) {
buffer.append(paramType.getName());
buffer.append(',');
}
buffer.setLength(buffer.length() - 1);
throw new ReflectorException(buffer.toString());
}
return method.invoke(target, params);
} catch (InvocationTargetException | IllegalAccessException ex) {
throw new ReflectorException(ex);
}
}
@SuppressWarnings({"UnusedDeclaration"})
public Object getStaticField(Class targetClass, String fieldName) throws ReflectorException {
try {
Field field = targetClass.getField(fieldName);
return field.get(null);
} catch (SecurityException | NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
throw new ReflectorException(e);
}
}
@SuppressWarnings({"UnusedDeclaration"})
public Object getField(Object target, String fieldName) throws ReflectorException {
return getField(target, fieldName, false);
}
public Object getField(Object target, String fieldName, boolean breakAccessibility) throws ReflectorException {
Class targetClass = target.getClass();
while (targetClass != null) {
try {
Field field = targetClass.getDeclaredField(fieldName);
boolean accessibilityBroken = false;
if (!field.isAccessible() && breakAccessibility) {
field.setAccessible(true);
accessibilityBroken = true;
}
Object result = field.get(target);
if (accessibilityBroken) {
field.setAccessible(false);
}
return result;
} catch (SecurityException e) {
throw new ReflectorException(e);
} catch (NoSuchFieldException e) {
if (targetClass == Object.class) throw new ReflectorException(e);
targetClass = targetClass.getSuperclass();
} catch (IllegalAccessException e) {
throw new ReflectorException(e);
}
}
// Never reached, but needed to satisfy compiler
return null;
}
/**
* Invoke the specified static method with the specified params...
*
* @param targetClass The target class of the invocation
* @param methodName The method name to invoke
* @param params The parameters to pass to the method invocation
* @return The result of the method call
* @throws ReflectorException In case of an error looking up or invoking the method.
*/
@SuppressWarnings({"UnusedDeclaration"})
public Object invokeStatic(Class targetClass, String methodName, Object[] params) throws ReflectorException {
if (params == null) {
params = new Object[0];
}
Class[] paramTypes = new Class[params.length];
for (int i = 0, len = params.length; i < len; i++) {
paramTypes[i] = params[i].getClass();
}
try {
Method method = getMethod(targetClass, methodName, paramTypes);
if (method == null) {
StringBuilder buffer = new StringBuilder();
buffer.append("Singleton-producing method named \'")
.append(methodName)
.append("\' not found with specified parameter classes: ");
for (Class paramType : paramTypes) {
buffer.append(paramType.getName());
buffer.append(',');
}
buffer.setLength(buffer.length() - 1);
throw new ReflectorException(buffer.toString());
}
return method.invoke(null, params);
} catch (InvocationTargetException | IllegalAccessException ex) {
throw new ReflectorException(ex);
}
}
/**
* Return the constructor, checking the cache first and storing in cache if not already there..
*
* @param targetClass The class to get the constructor from
* @param params The classes of the parameters which the constructor should match.
* @param <T> the type
* @return the Constructor object that matches.
* @throws ReflectorException In case we can't retrieve the proper constructor.
*/
public <T> Constructor<T> getConstructor(Class<T> targetClass, Class[] params) throws ReflectorException {
Map<String, Constructor<T>> constructorMap = getConstructorMap(targetClass);
StringBuilder key = new StringBuilder(200);
key.append("(");
for (Class param : params) {
key.append(param.getName());
key.append(",");
}
if (params.length > 0) {
key.setLength(key.length() - 1);
}
key.append(")");
Constructor<T> constructor;
String paramKey = key.toString();
synchronized (paramKey.intern()) {
constructor = constructorMap.get(paramKey);
if (constructor == null) {
@SuppressWarnings({"unchecked"})
Constructor<T>[] cands = (Constructor<T>[]) targetClass.getConstructors();
for (Constructor<T> cand : cands) {
Class[] types = cand.getParameterTypes();
if (params.length != types.length) {
continue;
}
for (int j = 0, len2 = params.length; j < len2; j++) {
if (!types[j].isAssignableFrom(params[j])) {
continue;
}
}
// we got it, so store it!
constructor = cand;
constructorMap.put(paramKey, constructor);
}
}
}
if (constructor == null) {
throw new ReflectorException(
"Error retrieving constructor object for: " + targetClass.getName() + paramKey);
}
return constructor;
}
public Object getObjectProperty(Object target, String propertyName) throws ReflectorException {
Object returnValue;
if (propertyName == null || propertyName.trim().length() < 1) {
throw new ReflectorException("Cannot retrieve value for empty property.");
}
String beanAccessor = "get" + Character.toUpperCase(propertyName.charAt(0));
if (propertyName.trim().length() > 1) {
beanAccessor += propertyName.substring(1).trim();
}
Class targetClass = target.getClass();
Class[] emptyParams = {};
Method method = _getMethod(targetClass, beanAccessor, emptyParams);
if (method == null) {
method = _getMethod(targetClass, propertyName, emptyParams);
}
if (method != null) {
try {
returnValue = method.invoke(target, new Object[] {});
} catch (IllegalAccessException e) {
throw new ReflectorException(
"Error retrieving property \'" + propertyName + "\' from \'" + targetClass + "\'", e);
} catch (InvocationTargetException e) {
throw new ReflectorException(
"Error retrieving property \'" + propertyName + "\' from \'" + targetClass + "\'", e);
}
}
if (method != null) {
try {
returnValue = method.invoke(target, new Object[] {});
} catch (IllegalAccessException e) {
throw new ReflectorException(
"Error retrieving property \'" + propertyName + "\' from \'" + targetClass + "\'", e);
} catch (InvocationTargetException e) {
throw new ReflectorException(
"Error retrieving property \'" + propertyName + "\' from \'" + targetClass + "\'", e);
}
} else {
returnValue = getField(target, propertyName, true);
if (returnValue == null) {
// TODO: Check if exception is the right action! Field exists, but contains null
throw new ReflectorException("Neither method: \'" + propertyName + "\' nor bean accessor: \'"
+ beanAccessor + "\' can be found for class: \'" + targetClass
+ "\', and retrieval of field: \'"
+ propertyName + "\' returned null as value.");
}
}
return returnValue;
}
/**
* Return the method, checking the cache first and storing in cache if not already there..
*
* @param targetClass The class to get the method from
* @param params The classes of the parameters which the method should match.
* @param methodName the method name
* @return the Method object that matches.
* @throws ReflectorException In case we can't retrieve the proper method.
*/
public Method getMethod(Class targetClass, String methodName, Class[] params) throws ReflectorException {
Method method = _getMethod(targetClass, methodName, params);
if (method == null) {
throw new ReflectorException("Method: \'" + methodName + "\' not found in class: \'" + targetClass + "\'");
}
return method;
}
private Method _getMethod(Class targetClass, String methodName, Class[] params) throws ReflectorException {
Map<String, Method> methodMap = (Map<String, Method>) getMethodMap(targetClass, methodName);
StringBuilder key = new StringBuilder(200);
key.append("(");
for (Class param : params) {
key.append(param.getName());
key.append(",");
}
key.append(")");
Method method;
String paramKey = key.toString();
synchronized (paramKey.intern()) {
method = methodMap.get(paramKey);
if (method == null) {
Method[] cands = targetClass.getMethods();
for (Method cand : cands) {
String name = cand.getName();
if (!methodName.equals(name)) {
continue;
}
Class[] types = cand.getParameterTypes();
if (params.length != types.length) {
continue;
}
for (int j = 0, len2 = params.length; j < len2; j++) {
if (!types[j].isAssignableFrom(params[j])) {
continue;
}
}
// we got it, so store it!
method = cand;
methodMap.put(paramKey, method);
}
}
}
return method;
}
/**
* Retrieve the cache of constructors for the specified class.
*
* @param theClass the class to lookup.
* @return The cache of constructors.
* @throws ReflectorException in case of a lookup error.
*/
private <T> Map<String, Constructor<T>> getConstructorMap(Class<T> theClass) throws ReflectorException {
return (Map<String, Constructor<T>>) getMethodMap(theClass, CONSTRUCTOR_METHOD_NAME);
}
/**
* Retrieve the cache of methods for the specified class and method name.
*
* @param theClass the class to lookup.
* @param methodName The name of the method to lookup.
* @return The cache of constructors.
* @throws ReflectorException in case of a lookup error.
*/
private Map<String, ?> getMethodMap(Class theClass, String methodName) throws ReflectorException {
Map<String, Method> methodMap;
if (theClass == null) {
return null;
}
String className = theClass.getName();
synchronized (className.intern()) {
Map<String, Map<String, Method>> classMethods = classMaps.get(className);
if (classMethods == null) {
classMethods = new HashMap<>();
methodMap = new HashMap<>();
classMethods.put(methodName, methodMap);
classMaps.put(className, classMethods);
} else {
String key = className + "::" + methodName;
synchronized (key.intern()) {
methodMap = classMethods.get(methodName);
if (methodMap == null) {
methodMap = new HashMap<>();
classMethods.put(methodName, methodMap);
}
}
}
}
return methodMap;
}
}