/*
* Copyright 2013 The Solmix Project
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.gnu.org/licenses/
* or see the FSF site: http://www.fsf.org.
*/
package org.solmix.commons.util;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Java Reflection Utils
*
* @author solmix.f@gmail.com
*/
public class Reflection
{
private static Logger LOG = LoggerFactory.getLogger(Reflection.class);
private static Map<String, ClassEntry> reflectionCache = new ConcurrentHashMap<String, ClassEntry>();
private static Throwable getRealTargetException(Throwable ite) {
if (ite instanceof InvocationTargetException)
return getRealTargetException(((InvocationTargetException) ite).getTargetException());
else
return ite;
}
public static Object newInstance(String className) throws Exception {
return newInstance(classForName(className));
}
public static Class<?> classForName(String name) throws Exception {
return ClassLoaderUtils.loadClass(name, Reflection.class);
}
/**
* Instance of given clazz.
*
* @param clazz
* @return
* @throws Exception
*/
public static <T> T newInstance(Class<T> clazz) throws Exception {
try {
return clazz.newInstance();
} catch (IllegalAccessException iae) {
iae = new IllegalAccessException((new StringBuilder()).append("Instantiation of class: ").append(clazz.getName()).append(
" threw an IllegalAccessException - either the class or its").append(" zero-argument constructor is not accessible").toString());
iae.fillInStackTrace();
throw iae;
} catch (InstantiationException ie) {
ie = new InstantiationException((new StringBuilder()).append("Instantiation of class: ").append(clazz.getName()).append(
" threw an InstantiationException - most likely cause is the").append(" class represents an abstract class, an interface,").append(
" an array class, a primitive type, or void; or the class has no").append(" zero-argument constructor.").toString());
ie.fillInStackTrace();
throw ie;
} catch (ExceptionInInitializerError eiie) {
eiie = new ExceptionInInitializerError(
(new StringBuilder()).append("Instantiation of class: ").append(clazz.getName()).append(
" threw an ExceptionInInitializerError - most likely cause is").append(
" a failure to initialize the class (check your static initializers).").append(" Root cause: ").append(eiie.getCause().toString()).toString());
eiie.fillInStackTrace();
throw eiie;
} catch (SecurityException se) {
se = new SecurityException((new StringBuilder()).append("Instantiation of class: ").append(clazz.getName()).append(
" threw a SecurityException - most likely cause is").append(" that there is no permissio nto create a new instance").append(
" of this class - error: ").append(se.toString()).toString());
se.fillInStackTrace();
throw se;
}
}
/**
* Invoke static method.
*/
public static Object invokeStaticMethod(String className, String methodName, Object... params) throws Exception {
try {
Method method = findMethod(className, methodName, lookupTypes(params));
return method.invoke(null, params);
} catch (InvocationTargetException ite) {
Throwable throwable = getRealTargetException(ite);
if (throwable instanceof Exception)
throw (Exception) throwable;
if (throwable instanceof Error) {
throw (Error) throwable;
} else {
LOG.error((new StringBuilder()).append("invokeStaticMethod() for method '").append(methodName).append("' caught a throwable that is").append(
" neither an Exception or an Error. Repackaging and re-throwing as an Error").toString());
Error error = new Error(throwable.getMessage());
error.fillInStackTrace();
throw error;
}
}
}
public static Method findMethod(String methodName) throws Exception {
int index = methodName.lastIndexOf('.');
return findMethod(methodName.substring(0, index), methodName.substring(index + 1));
}
public static Method findMethod(String className, String methodName) throws Exception {
return findMethod(className, methodName, new Class[0]);
}
public static Method findMethod(Object instance, String methodName) throws Exception {
return findMethod(instance.getClass().getName(), methodName, new Class[0]);
}
public static Method findMethod(String className, String methodName, Class<?>... methodArgs) throws Exception {
return findMethod(className,methodName,false,methodArgs);
}
public static Method findMethod(String className, String methodName, boolean populateCache,Class<?>... methodArgs) throws Exception {
Assert.isNotNull(methodArgs, "methodArgs must not null, An array of length 0 if the underlying method takes no parameters. ");
if(populateCache){
ClassEntry classCache = getClassCache(className);
MethodEntry[] methods=classCache.methods;
for(MethodEntry entry:methods){
if(entry.equals(methodName, methodArgs)){
return entry.method;
}
}
}else{
Class<?> classObject = classForName(className);
Method lookup=findMethod(classObject,methodName, methodArgs);
if(lookup!=null)
return lookup;
}
String message = (new StringBuilder()).append("Method ").append(methodName).append(" not found on class ").append(className).toString();
throw new NoSuchMethodException(message);
}
public static Method findMethod(Class<?> cls, String name,
Class<?>... params) {
if (cls == null) {
return null;
}
for (Class<?> cs : cls.getInterfaces()) {
if (Modifier.isPublic(cs.getModifiers())) {
Method m = findMethod(cs, name, params);
if (m != null && Modifier.isPublic(m.getModifiers())) {
return m;
}
}
}
try {
Method m = cls.getDeclaredMethod(name, params);
if (m != null && Modifier.isPublic(m.getModifiers())) {
return m;
}
} catch (Exception e) {
// ignore
}
Method m = findMethod(cls.getSuperclass(), name, params);
if (m == null) {
try {
m = cls.getMethod(name, params);
} catch (Exception e) {
// ignore
}
}
return m;
}
public static Object invokeMethod(Object classInstance, String methodName) throws Exception {
return invokeMethod(classInstance, methodName, new Object[0]);
}
public static Object invokeMethod(Object classInstance, String methodName, Object... params) throws Exception {
try {
Method method = findMethod(classInstance.getClass().getName(), methodName, lookupTypes(params));
return method.invoke(classInstance, params);
} catch (InvocationTargetException ite) {
Throwable throwable = getRealTargetException(ite);
if (throwable instanceof Exception)
throw (Exception) throwable;
if (throwable instanceof Error) {
throw (Error) throwable;
} else {
LOG.error((new StringBuilder()).append("invokeMethod() for method '").append(methodName).append(
"' caught a throwable that is neither").append(" an Exception nor an Error. Repackaging and re-throwing as an Error").toString());
Error error = new Error(throwable.getMessage());
error.fillInStackTrace();
throw error;
}
}
}
private static ClassEntry getClassCache(String className) throws Exception {
ClassEntry classCache = reflectionCache.get(className);
if (classCache == null)
classCache = populateClassCache(className);
return classCache;
}
private static ClassEntry populateClassCache(String className) throws Exception {
Class<?> classObject = classForName(className);
ClassEntry classEntry= new ClassEntry(classObject);
populateMethodCache(classObject,classEntry);
reflectionCache.put(className, classEntry);
return classEntry;
}
private static void populateMethodCache(Class<?> classObject,
ClassEntry classEntry) {
Method classMethods[] = classObject.getMethods();
MethodEntry[] entrys= new MethodEntry[classMethods.length];
for (int ii = 0; ii < classMethods.length; ii++) {
entrys[ii].name=classMethods[ii].getName();
entrys[ii].params= classMethods[ii].getParameterTypes();
entrys[ii].method= classMethods[ii];
}
classEntry.methods=entrys;
}
private static Class<?>[] lookupTypes(Object args[]) {
if (args == null)
return null;
Class<?> result[] = new Class[args.length];
for (int ii = 0; ii < args.length; ii++)
if (args[ii] == null)
result[ii] = null;
else
result[ii] = args[ii].getClass();
return result;
}
/**
* @param targetClass
* @return
*/
public static Field[] getDeclaredFields(final Class<? extends Object> cls) {
return AccessController.doPrivileged(new PrivilegedAction<Field[]>() {
@Override
public Field[] run() {
return cls.getDeclaredFields();
}
});
}
/**
* @param targetClass
* @return
*/
public static Method[] getDeclaredMethods(final Class<? extends Object> cls) {
return AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
@Override
public Method[] run() {
return cls.getDeclaredMethods();
}
});
}
/**
* @param <T>
* @param method
*/
public static <T extends AccessibleObject> T setAccessible(final T o) {
return AccessController.doPrivileged(new PrivilegedAction<T>() {
@Override
public T run() {
o.setAccessible(true);
return o;
}
});
}
public static <T extends AccessibleObject> T setAccessible(final T o, final boolean b) {
return AccessController.doPrivileged(new PrivilegedAction<T>() {
@Override
public T run() {
o.setAccessible(b);
return o;
}
});
}
}
class MethodEntry
{
public Method method;
public String name;
public Class<?>[] params;
public boolean equals(String name, Class<?>[] argsType) {
if (!this.name.equals(name))
return false;
if (params.length != argsType.length)
return false;
for (int i = 0; i < params.length; i++) {
if (!params[i].getName().equals(argsType[i].getName()))
return false;
}
return true;
}
}
class ClassEntry
{
public ClassEntry(Class<?> classObject)
{
this.classObject = classObject;
}
public Class<?> classObject;
public MethodEntry[] methods;
}