package org.rr.commons.utils;
import static org.rr.commons.utils.StringUtil.EMPTY;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import org.rr.commons.collection.LRUCacheMap;
import org.rr.commons.log.LoggerFactory;
public class ReflectionUtils implements Serializable {
private static final long serialVersionUID = 5079109720850871791L;
public static int VISIBILITY_VISIBLE_ACCESSIBLE_ONLY = 0;
public static int VISIBILITY_VISIBLE_ALL = 1;
public static int OS_WINDOWS = 0;
public static int OS_LINUX = 1;
public static int OS_MAC = 2;
public static int OS_UNKNOWN = 99;
/**
* stores the java version
*/
private static int javaVersion = -1;
/**
* stores the os type
*/
private static int os = -1;
private static final int DEFAULT_MAX_CACHE_CAPACITY = 30;
private static final LRUCacheMap<String, Class<?>> classNameToClassCache = new LRUCacheMap<String, Class<?>>(DEFAULT_MAX_CACHE_CAPACITY);
private static final LRUCacheMap<String, Method> methodNameToMethodCache = new LRUCacheMap<String, Method>(DEFAULT_MAX_CACHE_CAPACITY);
private static final LRUCacheMap<String, Field> fieldNameToFieldCache = new LRUCacheMap<String, Field>(DEFAULT_MAX_CACHE_CAPACITY);
private static final LRUCacheMap<Class<?>, List<Field>> fieldClassCache = new LRUCacheMap<Class<?>, List<Field>>(DEFAULT_MAX_CACHE_CAPACITY);
/**
* Gets the <code>Field</code> value from the field with the name specified with the
* <code>fieldName</code> parameter from the Object instance specified with the <code>object</code> parameter.
* <br><br>
* The Field which value should be fetched can also be declared as private, packaged or protected
* and also from superclasses.
*
* @param object The object containing the desired Field.
* @param clazz The <code>Class</code> to be fetched using <code>object.getClass()</code> from the <code>object</code> parameter.
* @param fieldName The name of the field which value should be fetched from the Object specified with the <code>object</code> parameter.
* @return The value of the <code>Field</code>.
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws NoSuchFieldException
*/
private static Field getField(final Object object, final Class<?> clazz, final String fieldName) throws ReflectionFailureException {
final Class<?> superclass = clazz.getSuperclass();
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
} catch (Exception e) {/*not found, go on*/}
if (field != null) {
//field found, return the value
field.setAccessible(true);
return field;
} else if (superclass!=null && !Object.class.equals( superclass )) {
//there is no filed found but a super class to be searched. RECURSION
return getField(object, superclass, fieldName);
} else {
//throw an Exception if the field could not be allocated.
throw new ReflectionFailureException("No such field " + fieldName);
}
}
/**
* Get all fields which are marked with the given annotation class.
* @return The desired fields.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static List<Field> getFieldsByAnnotation(final Class annotationClass, final Class<?> itemClass) {
//get fields to be displayed in the combobox
final List<Field> fields = ReflectionUtils.getFields(itemClass, ReflectionUtils.VISIBILITY_VISIBLE_ALL);
final ArrayList<Field> listEntries = new ArrayList<>(fields.size());
for (Field field : fields) {
Object dbViewFieldAnnotation = field.getAnnotation(annotationClass);
if(dbViewFieldAnnotation!=null) {
listEntries.add(field);
}
}
return listEntries;
}
/**
* Gets the <code>Field</code> value from the field with the name specified with the
* <code>fieldName</code> parameter from the Object instance specified with the <code>object</code> parameter.
* <br><br>
* The Field which value should be fetched can also be declared as private, packaged or protected
* and also from superclasses.
*
* @param object The object containing the desired Field.
* @param fieldName The name of the field which value should be fetched from the Object specified with the <code>object</code> parameter.
* @return The value of the <code>Field</code>.
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws NoSuchFieldException
*/
public static Object getFieldValue(final Object object, final String fieldName, boolean useGetter) throws ReflectionFailureException {
try {
final Class<?> c;
if(object instanceof Class<?>) {
c = (Class<?>) object;
} else {
c = object.getClass();
}
if(useGetter) {
String getter = "get" + StringUtil.capitalize(fieldName);
Method method = getMethod(c, getter, null, VISIBILITY_VISIBLE_ACCESSIBLE_ONLY);
if(method != null) {
method.setAccessible(true);
Object methodResult = method.invoke(object, null);
return methodResult;
}
}
//first try to get the desired field from the cache and return it's value.
Field field = fieldNameToFieldCache.get(createFieldCacheKey(c, fieldName));
if(field==null) {
field = getField(object, c, fieldName);
fieldNameToFieldCache.put(createFieldCacheKey(c, fieldName), field);
}
//second try to get the field from the given class, put the found Field to the cache.
Object o = field.get(object);
return o;
} catch(Exception e) {
throw new ReflectionFailureException(e);
}
}
/**
* Gets the <code>Field</code> value from the field with the name specified with the
* <code>qualifiedFieldName</code> parameter. For example: <code>"System.out"</code> returns
* the <code>{@link OutputStream}</code> from the <code>{@link System}</code> class. If only
* a Class file is requested with the <code>qualifiedFieldName</code> string, it will also be returned.
* <br><br>
* The Field which value should be fetched can also be declared as private, packaged or protected
* and also from superclasses.
*
* @param qualifiedFieldName the field name to be fetched. The qualified field name must be described with the full package
* path the class name and the name of the field as the last segment, all separated by a '.' character.
* @return The desired object instance or <code>{@link java.lang.Class}</code>.
* @throws ReflectionFailureException
*/
public static Object getStaticFieldValue(final String qualifiedFieldName) throws ReflectionFailureException {
String fieldName = StringUtil.substringAfter(qualifiedFieldName, ".", false, UtilConstants.COMPARE_BINARY);
//test if there is a class already identified and cached.
//the cache only contains the class if the class is also the result. If a
//field value is requested, it is not putten into the cache.
if(classNameToClassCache.containsKey(qualifiedFieldName)) {
Object cachedClass = classNameToClassCache.get(qualifiedFieldName);
if(cachedClass==null) {
//a cached exception ;-)
throw new ReflectionFailureException(qualifiedFieldName);
}
return cachedClass;
}
String className = StringUtil.substringBefore(qualifiedFieldName, ".", false, UtilConstants.COMPARE_BINARY);
if(className.length()==0) {
className = qualifiedFieldName;
}
Class<?> clazz = null;
try {
clazz = Class.forName(className);
} catch (Exception e) {
try {
clazz = Class.forName("java.lang." + className);
} catch (Exception e1) {
try {
clazz = Class.forName(qualifiedFieldName);
} catch (ClassNotFoundException e2) {
//the non existing class should also not searched again!
classNameToClassCache.put(qualifiedFieldName, null);
throw new ReflectionFailureException(e2);
}
classNameToClassCache.put(qualifiedFieldName, clazz);
return clazz;
}
}
if(fieldName.length()==0) {
classNameToClassCache.put(qualifiedFieldName, clazz);
return clazz;
}
Object fieldValue = getFieldValue(clazz, fieldName, false);
return fieldValue;
}
/**
* Puts the <code>value</code> to the field <code>fieldName</code> at the given <code>object</code> instance.
* <br><br>
* This method is not applicable for setting static fields on a class. Only object instanced can be feed into the
* <code>object</code> parameter.
*
* @param object The object instance which contains the field to be set.
* @param fieldName The name of the field within the object instance to be set.
* @param value The value to be set to the field specified with the <code>fieldName</code> parameter.
* @throws IllegalArgumentException
* @throws IllegalAccessException
* @throws NoSuchFieldException
*/
public static void setFieldValue(final Object object, final String fieldName, final Object value) throws ReflectionFailureException {
setFieldValue(object, object.getClass(), fieldName, value);
}
public static void setFieldValue(final Object object, Class<?> clazz, final String fieldName, final Object value) throws ReflectionFailureException {
Field field = null;
Class<?> superclass = clazz.getSuperclass();
if(object instanceof Class<?> && clazz.equals(java.lang.Class.class) && !object.equals(java.lang.Class.class)) {
clazz = (Class<?>) object;
}
try {
field = clazz.getDeclaredField(fieldName);
} catch (Exception e) {/*not found, go on*/}
if (field != null) {
//field found, set the value
field.setAccessible(true);
try {
field.set(object, value);
} catch (IllegalAccessException e) {
throw new ReflectionFailureException(e);
}
return;
} else if (superclass!=null && !Object.class.equals( superclass )) {
//there is no filed found but a super class to be searched.
setFieldValue(object, superclass, fieldName, value);
} else {
//throw an Exception if the field could not be allocated.
throw new ReflectionFailureException();
}
}
/**
* Executed the specified method in the specified class. Arguments will be converted to numeric, boolean or string
* types.
* @param object The <code>Object</code> where the method specified with the <code>methodName</code> parameter should be invoked.
* @param methodName The method to be executed.
* @param args The arguments for the method to be executed.
* @return A <code>ExpressionNode</code> matching to the result of the called method.
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws NoSuchMethodException
*/
public static Object invokeMethod(final Object object, final String methodName, final Object ... args) throws ReflectionFailureException {
if (object instanceof Class<?>) {
return invokeMethod(null, (Class<?>)object, methodName, args);
} else {
return invokeMethod(object, object.getClass(), methodName, args);
}
}
/**
* Tries to create an instance for the given class.
*
* @param className The class name where a new Object instance should be created for.
* @param initargs The constructor arguments
*/
public static Object getObjectInstance(final String className, Object[] initargs) {
Class<?> classForName = getClassForName(className);
return getObjectInstance(classForName, initargs);
}
/**
* Tries to create an instance for the given class.
*
* @param clazz The class where a new Object instance should be created for.
* @param initargs The constructor arguments
* @return
*/
public static Object getObjectInstance(final Class<?> clazz, Object[] initargs) {
if (clazz == null) {
return null;
}
Object result = null;
if (initargs==null || initargs.length==0) {
try {
result = clazz.newInstance();
return result;
} catch (Exception e) {}
initargs = new Object[0];
}
List<Constructor<?>> constructors = ListUtils.union(Arrays.asList(clazz.getConstructors()), Arrays.asList(clazz.getDeclaredConstructors()));
constructors = ListUtils.distinct(constructors, UtilConstants.COMPARE_BINARY);
for (Constructor<?> constructor : constructors) {
Class<?>[] parameterTypes = constructor.getParameterTypes();
if((parameterTypes.length==0 && initargs==null) || parameterTypes.length == initargs.length) {
Object[] args = createArguments(parameterTypes, initargs, false);
if(args!=null) {
try {
constructor.setAccessible(true);
return constructor.newInstance(args);
} catch (Exception e) {
continue;
}
} else if(initargs==null && parameterTypes.length==0) {
try {
constructor.setAccessible(true);
return constructor.newInstance(initargs);
} catch (Exception e) {
continue;
}
}
}
}
//last try .. create a new instance with the possible hidden default constructor
try {
Constructor<?> constuctor = clazz.getDeclaredConstructor(new Class[0]);
constuctor.setAccessible(true);
return constuctor.newInstance(new Object[]{});
} catch (Exception e) {}
return null;
}
public static boolean containsMethod(final String className, final String name, final Class<?>[] initargs, final int visibility) {
Class<?> classForName = getClassForName(className);
return containsMethod(classForName, name, initargs, visibility);
}
public static boolean containsMethod(final Class<?> clazz, final String name, final Class<?>[] initargs, final int visibility) {
return getMethod(clazz, name, initargs, visibility) != null;
}
/**
* Gets a Method from the given class and
*/
public static Method getMethod(final Class<?> clazz, final String name, final Class<?>[] initargs, final int visibility) {
Method result = null;
try {
result = clazz.getMethod(name, initargs);
result.setAccessible(true);
} catch (Exception e) {
if (visibility==VISIBILITY_VISIBLE_ACCESSIBLE_ONLY) {
return null;
}
try {
result = clazz.getDeclaredMethod(name, initargs);
result.setAccessible(true);
} catch (Exception e1) {
}
}
return result;
}
/**
* Get all these fields which have public getter.
* @param clazz The class where the fields should be fetched for.
* @param allowedReturnTypes All allowed field types. <code>null</code> for all types.
* @return The fields which have public getter.
*/
public static Field[] getFieldsWithGetter(final Class<?> clazz, final Class<?>[] allowedTypes) {
final List<Field> fields = getFields(clazz, VISIBILITY_VISIBLE_ALL);
final ArrayList<Field> result = new ArrayList<>(fields.size());
for (Field field : result) {
boolean isAllowedType = true;
if(allowedTypes!=null) {
isAllowedType = false;
for (int j = 0; j < allowedTypes.length; j++) {
if(field.getType().getName().equals(allowedTypes[j].getName())) {
isAllowedType = true;
break;
}
}
}
if(isAllowedType) {
Method method = getMethod(clazz, "get" + StringUtil.capitalize(field.getName()), null, VISIBILITY_VISIBLE_ACCESSIBLE_ONLY);
if(method!=null) {
result.add(field);
} else {
method = getMethod(clazz, "is" + StringUtil.capitalize(field.getName()), null, VISIBILITY_VISIBLE_ACCESSIBLE_ONLY);
if(method!=null) {
result.add(field);
}
}
}
}
return result.toArray(new Field[result.size()]);
}
/**
* Get the field from the given class with the given name.
* @param clazz The class wehere the Field instance shoudl be fetched from.
* @param name The name of the desired field.
* @return The desired Field instance or <code>null</code> if no such field could be found.
*/
public static Field getField(final Class<?> clazz, String name) {
List<Field> fields = getFields(clazz, VISIBILITY_VISIBLE_ALL);
for(Field field : fields) {
if(field.getName().equals(name)) {
return field;
}
}
return null;
}
/**
* Fetches all Fields from the specified class.
*
* @param clazz The class where the fields should be fetched from.
* @param visibility Specifies the visibility of the fields to be fetched.
* For specifying the visibility, use the constants <B>VISIBILITY_VISIBLE_ACCESSIBLE_ONLY</B>, <B>VISIBILITY_VISIBLE_ALL</B>.
* @return The fields to be fetched from the given class.
*/
public static List<Field> getFields(final Class<?> clazz, int visibility) {
if (clazz==null) {
return new ArrayList<Field>(0);
}
List<Field> cachedFields = fieldClassCache.get(clazz);
if(cachedFields != null) {
return cachedFields;
}
List<Field> fields = new ArrayList<>(0);
Class<?> superclass = clazz.getSuperclass();
try {
if (visibility==VISIBILITY_VISIBLE_ALL) {
fields = ListUtils.union(fields, Arrays.asList(clazz.getDeclaredFields()));
} else {
fields = ListUtils.union(fields, Arrays.asList(clazz.getFields()));
}
} catch (Exception e) {/*not found, go on*/}
if (superclass!=null && !Object.class.equals(superclass)) {
fields = ListUtils.union(fields, getFields(superclass, visibility));
}
//set all fields accessible
if (visibility==VISIBILITY_VISIBLE_ALL) {
for (Field field : fields) {
field.setAccessible(true);
}
}
List<Field> result = distinct(fields, UtilConstants.COMPARE_BINARY);
fieldClassCache.put(clazz, result);
return result;
}
/**
* Fetches all Methods from the specified class.
*
* @param clazz The class where the methods should be fetched from.
* @param visibility Specifies the visibility of the methods to be fetched.
* For specifying the visibility, use the constants <B>VISIBILITY_VISIBLE_ACCESSIBLE_ONLY</B>, <B>VISIBILITY_VISIBLE_ALL</B>.
* @return The methods to be fetched from the given class.
*/
public static List<Method> getMethods(final Class<?> clazz, int visibility) {
if (clazz==null) {
return new ArrayList<Method>(0);
}
List<Method> methods = new ArrayList<>();
Class<?> superclass = clazz.getSuperclass();
try {
if (visibility==VISIBILITY_VISIBLE_ALL) {
methods = ListUtils.union(methods, Arrays.asList(clazz.getDeclaredMethods()));
} else {
methods = ListUtils.union(methods, Arrays.asList(clazz.getMethods()));
}
} catch (Exception e) {/*not found, go on*/}
if (superclass!=null && !Object.class.equals(superclass)) {
methods = ListUtils.union(methods, getMethods(superclass, visibility));
}
//set all methods accessible
if (visibility==VISIBILITY_VISIBLE_ALL) {
for (Method method : methods) {
method.setAccessible(true);
}
}
return distinct(methods, UtilConstants.COMPARE_BINARY);
}
/**
* filters duplicates from a <code>Method</code> or <code>Field</code> array.
*
* @param values An Object that is a Method, a Field or provides a <code>getName()</code> method.
* @param compare
* @return A new array instance without any duplicates.
*
* @throws RuntimeInvocationTargetException should not be happens but will be possible if an array of Objects is specified
* which did not provides a <code>getName()</code> method.
*/
private static <T>List<T> distinct(final List<T> values, final int compare) {
List<T> result = new ArrayList<>(values.size());
try {
ArrayList<String> names = new ArrayList<>(values.size());
for (Object value : values) {
//getting the name using a cast is much faster than using the reflection api
if (value instanceof Method) {
names.add(((Method)value).getName());
} else if (value instanceof Field) {
names.add(((Field)value).getName());
} else {
names.add((String) invokeMethod(value, "getName", null));
}
}
Iterator<?> valuesIterator = values.iterator();
for (int i=0; valuesIterator.hasNext(); i++) {
Object value = valuesIterator.next();
if (value instanceof Method) {
if ( ListUtils.indexOf(names, ((Method)value).getName(), compare, UtilConstants.SEARCH_DEFAULT)==i ) {
result.add((T) value);
}
} else if (value instanceof Field) {
if ( ListUtils.indexOf(names, ((Field)value).getName(), compare, UtilConstants.SEARCH_DEFAULT)==i ) {
result.add((T) value);
}
}else {
if ( ListUtils.indexOf(names, (String) invokeMethod(value, "getName", null), compare, UtilConstants.SEARCH_DEFAULT)==i ) {
result.add((T) value);
}
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return result;
}
/**
* Takes the <code>targetParameterTypes</code> and tries to convert the <code>sourceValues</code>
* to a matching type. If a method requires an <code>int</code> and a <code>{@link BigDecimal}</code> instance
* is given, the <code>{@link BigDecimal}</code> instance will be converted to match to the int.
*
* @param targetParameterTypes The parameter class types required.
* @param sourceValues The values to be converted into the required <code>targetParameterTypes</code>
* @param force <code>true</code> means that the values array will also be returned if it has not been
* completely converted. <code>false</code> will return <code>null</code> if one value could not be identified and converted.
* @return All required object instances assignable to this ones given with the <code>targetParameterTypes<7code> parameter.
*/
private static Object[] createArguments(Class<?>[] targetParameterTypes, Object[] sourceValues, boolean force) {
Object[] result = new Object[targetParameterTypes.length];
int found=0;
for (int j=0; j < targetParameterTypes.length && j < sourceValues.length; j++) {
//test for numeric values
if ((targetParameterTypes[j].equals(int.class) || targetParameterTypes[j].equals(Integer.class)) && CommonUtils.toNumber(sourceValues[j])!=null) {
result[j] = Integer.valueOf( CommonUtils.toNumber(sourceValues[j]).intValue() );
found++;
continue;
} else if ((targetParameterTypes[j].equals(double.class) || targetParameterTypes[j].equals(Double.class)) && CommonUtils.toNumber(sourceValues[j])!=null) {
result[j] = Double.valueOf(CommonUtils.toNumber(sourceValues[j]).doubleValue());
found++;
continue;
} else if ((targetParameterTypes[j].equals(float.class) || targetParameterTypes[j].equals(Float.class)) && CommonUtils.toNumber(sourceValues[j])!=null) {
result[j] = Float.valueOf(CommonUtils.toNumber(sourceValues[j]).floatValue());
found++;
continue;
} else if ((targetParameterTypes[j].equals(long.class) || targetParameterTypes[j].equals(Long.class)) && CommonUtils.toNumber(sourceValues[j])!=null) {
result[j] = new Long(CommonUtils.toNumber(sourceValues[j]).longValue());
found++;
continue;
} else if ((targetParameterTypes[j].equals(short.class) || targetParameterTypes[j].equals(Short.class)) && CommonUtils.toNumber(sourceValues[j])!=null) {
result[j] = new Short(CommonUtils.toNumber(sourceValues[j]).shortValue());
found++;
continue;
}
//test for boolean
else if ((targetParameterTypes[j].equals(boolean.class) || targetParameterTypes[j].equals(Boolean.class)) && sourceValues[j] instanceof Boolean) {
result[j] = sourceValues[j];
found++;
continue;
}
//test for Character
else if (targetParameterTypes[j].equals(char.class) && sourceValues[j]!=null) {
result[j] = new Character(String.valueOf(sourceValues[j]).charAt(0));
found++;
continue;
}
//test for String
else if (targetParameterTypes[j].equals(String.class) && sourceValues[j]!=null) {
result[j] = String.valueOf(sourceValues[j]);
found++;
continue;
}
//test for Object[]
else if (targetParameterTypes[j].equals(Object[].class) && sourceValues[j]!=null) {
result[j] = sourceValues[j];
found++;
continue;
}
//test for String[]
else if (targetParameterTypes[j].equals(String[].class) && sourceValues[j]!=null) {
String[] converted = new String[Array.getLength(sourceValues[j])];
System.arraycopy(sourceValues[j], 0, converted, 0, converted.length);
result[j] = converted;
found++;
continue;
}
//test for Object[][]
else if (targetParameterTypes[j].equals(Object[][].class) && sourceValues[j]!=null) {
Object[][] cast = new Object[((Object[])sourceValues[j]).length][0];
for (int k = 0; k < cast.length; k++) {
cast[k] = (Object[]) ((Object[])sourceValues[j])[k];
}
result[j] = cast;
found++;
continue;
}
//null matches to aything else (excepting the primitives)
else if (sourceValues[j]==null &&
(targetParameterTypes[j].equals(char.class) || targetParameterTypes[j].equals(int.class) || targetParameterTypes[j].equals(double.class) || targetParameterTypes[j].equals(float.class) || targetParameterTypes[j].equals(long.class) || targetParameterTypes[j].equals(short.class) || targetParameterTypes[j].equals(boolean.class)) == false
) {
result[j] = sourceValues[j];
found++;
continue;
}
//Object (must be the last!)
else if (targetParameterTypes[j].equals(Object.class)) {
result[j] = sourceValues[j];
found++;
continue;
} else if (targetParameterTypes[j].isInstance(sourceValues[j])) {
result[j] = sourceValues[j];
found++;
continue;
}
}
//test if all arguments could be identified
if(found==targetParameterTypes.length) {
return result;
} else {
if(force) {
return result;
}
return null;
}
}
/**
* Executed the specified method in the specified class. Arguments will be converted to numeric, boolean or string
* types.
* @param object The <code>Object</code> where the method specified with the <code>methodName</code> parameter should be invoked.
* @param methodName The method to be executed.
* @param args The arguments for the method to be executed.
* @return A <code>ExpressionNode</code> matching to the result of the called method.
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws NoSuchMethodException
*/
public static Object invokeMethod(final Object object, Class<?> clazz, final String methodName, Object ... args) throws ReflectionFailureException {
final String methodCacheKey = createMethodCacheKey(object, clazz, methodName, args);
Method method = methodNameToMethodCache.get(methodCacheKey);
Object[] argClasses = null;
boolean methodFromCache = false;
if(method!=null) {
methodFromCache = true;
//use the method from the cache
Class<?>[] parameters = method.getParameterTypes();
argClasses = createArguments(parameters, args, false);
} else {
//search for the right method
if (args!=null) {
argClasses = new Object[args.length];
} else {
argClasses = new Object[0];
args = new Object[0];
}
//method name like the class name means thats not a method, it's a constructor
if(clazz!=null && (clazz.getName().equals(methodName) || clazz.getSimpleName().equals(methodName))) {
return ReflectionUtils.getObjectInstance(clazz, args);
}
//Get all matching methods
List<Method> methods = clazz != null ? ListUtils.union(Arrays.asList(clazz.getDeclaredMethods()), Arrays.asList(clazz.getMethods())) : Collections.<Method>emptyList();
for (Method m : methods) {
String targetMethodName = m.getName();
if (targetMethodName.equalsIgnoreCase(methodName) && m.getParameterTypes().length == args.length ) { //Method name match
//get the parameter class types
Class<?>[] parameters = m.getParameterTypes();
argClasses = createArguments(parameters, args, false);
//all values matching to the method
if (argClasses!=null) {
method = m;
break;
}
//but store the method with a matching amount of parameters
method = m;
}
}
}
methodNameToMethodCache.put(methodCacheKey, method);
//if a method could be identified
if (method!=null) {
method.setAccessible(true);
if(argClasses==null) {
argClasses = createArguments(method.getParameterTypes(), args, true);
}
try {
Object retval = null;
try {
try {
retval = method.invoke(object, argClasses);
} catch (NullPointerException e) {
if(object==null) {
retval = method.invoke(getObjectInstance(clazz, null), argClasses);
}
}
} catch(IllegalArgumentException nse) {
if(methodFromCache) {
//(10.11.09) Try it again but without the method from the cache.
methodNameToMethodCache.remove(methodCacheKey);
invokeMethod(object, methodName, args);
}
}
return retval;
} catch (Exception e) {
throw new ReflectionFailureException(e);
}
}
Class<?> superclass = clazz.getSuperclass();
if (superclass!=null && !Object.class.equals(superclass)) {
//search with the super class
return invokeMethod(object, superclass, methodName, args);
}
throw new ReflectionFailureException("No such method " + methodName);
}
/**
* Creates the key for the HashMap where the Methods gets cached.
* @return A key for the Method which can be used for the cache HashMap.
*/
private static String createMethodCacheKey(final Object object, Class<?> clazz, final String methodName, Object[] args) {
String methodCacheKey = String.valueOf(methodName) + (clazz!=null ? clazz.getName() : object.getClass().getName());
if(args!=null) {
methodCacheKey += args.length;
}
return methodCacheKey;
}
/**
* Creates the key for the HashMap where the Fields gets cached.
* @return A key for the Field which can be used for the cache HashMap.
*/
private static String createFieldCacheKey(final Class<?> clazz, final String fieldName) {
String key = String.valueOf(fieldName) + (clazz!=null ? clazz.getName() : EMPTY);
return key;
}
/**
* gets the {@link StackTraceElement}s for the current Thread. This method
* is a replacement for <code>Thread.currentThread().getStackTrace()</code> existing
* with java 1.5
*
* @return The {@link StackTraceElement} for the current Thread.
*/
public static StackTraceElement[] getStackTrace() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
StackTraceElement[] resultStackTrace = new StackTraceElement[stackTrace.length-1];
System.arraycopy(stackTrace, 1, resultStackTrace, 0, resultStackTrace.length);
return resultStackTrace;
}
/**
* Determines if the <code>invokingClass</code> and/or <code>invokingMethod</code>
* is in the stack of the current thread. Anonym classes are also assumed to the
* given <code>invokingClass</code>.
*
* @param invokingClass Class to be searched in the stack (required).
* @param invokingMethod Method to be searched (can be null).
* @return The location in the stack hierarchy or -1 if the class/method could not be found in the thread stack.
*/
public static int isInvokedFrom(final Class<?> invokingClass, final String invokingMethod) {
StackTraceElement[] elements = getStackTrace();
//if only the method name is specified
if (invokingClass==null) {
for (int i = 0; i < elements.length; i++) {
if (elements[i].getMethodName().equals(invokingMethod)) {
return i;
}
}
return -1;
}
String invokingPath = invokingClass.getName();
if (invokingMethod!=null && invokingMethod.length()>0) {
invokingPath += "." + invokingMethod;
}
for (int i = 2; i < elements.length; i++) {
try {
boolean isAssignable = false;
try {
isAssignable = isAssignable(invokingClass, Class.forName(elements[i].getClassName(), false, Thread.currentThread().getContextClassLoader()));
} catch (ClassNotFoundException e) {
//always happens if the class is not accessible from the current ClassLoader.
isAssignable = false;
}
String className = elements[i].getClassName();
if(className.indexOf('$')!=-1) {
String numeric = className.substring(className.lastIndexOf('$')+1);
Number num = CommonUtils.toNumber(numeric);
if(num!=null) {
//for example com.odc.eva3.rt.se.form.FormPrefetchingManager$PrefetchingThread$1
//the 1 can be cutted.. it's the anonymous class which can not requested.
className = className.substring(0, className.lastIndexOf('$'));
}
}
if (className.equals(invokingClass.getName()) || isAssignable) {
if (invokingMethod==null || (invokingMethod!=null && elements[i].getMethodName().equals(invokingMethod)) ) {
int result = i-2;
if(result<0) {
return 0;
}
return result;
}
}
} catch (Exception e) {
//LoggerUtils.getLogger(ReflectionUtils.class).log(Level.INFO, "isInvokedFrom has failed.", e);
return -1;
}
}
return -1;
}
/**
* Tests if the second class is assignable to the first class.
* @param first The first Class to be tested if the second class is assignable to it.
* @param second The second Class to be tested if it's assignable to the first Class.
* @return <code>true</code> if the second class is assignable to the first class
*/
public static boolean isAssignable(Class<?> first, Class<?> second) {
//collect all super classes from the given second class to the arraylist
ArrayList<Class<?>> superclasses = new ArrayList<Class<?>>();
Class<?> superclass = second;
superclasses.add(superclass);
while((superclass=superclass.getSuperclass())!=null) {
superclasses.add(superclass);
}
//test all classes from the superclasses if it's assignable
for (int i = 0; i < superclasses.size(); i++) {
Class<?> c = (Class<?>) superclasses.get(i);
if(first.getName().equals(c.getName())) {
return true;
}
}
return false;
}
/**
* Copies all public fields and public getter/setter methods from the <code>source</code> Object to the
* <code>target</code> Object.
*
* @param <T> The target object type.
* @param source The source object where the properties are copied from
* @param target The target object where the properties are copied to
* @return The <code>target</code> object instance.
* @throws IllegalArgumentException
* @throws NoSuchFieldException
* @throws IllegalAccessException
* @throws InvocationTargetException
*/
public static <T> T copyProperties(Object source, T target) throws ReflectionFailureException {
//start cloning public fields
Field[] fields = source.getClass().getFields();
for (int i = 0; i < fields.length; i++) {
if (fields[i].isAccessible()) {
Object fieldValue = getFieldValue(source, fields[i].getName(), false);
try {
fields[i].set(target, fieldValue);
} catch (Exception e) {}
}
}
//all available methods from the target class
Method[] methods = target.getClass().getMethods();
//the map stores the getter methods for the target class. The names are stored without any "is" or "get"
HashMap<String, Method> getterMethodsMap = new HashMap<String, Method>();
//all the setter methods are stored here available in the target class
ArrayList<Method> setterMethods = new ArrayList<>(methods.length);
//assign getter and setter to the array lists
for (int i = 0; i < methods.length; i++) {
if (methods[i].getName().startsWith("get") && methods[i].getParameterTypes().length==0) {
getterMethodsMap.put(methods[i].getName().substring(3), methods[i]);
} else if (methods[i].getName().startsWith("is") && methods[i].getParameterTypes().length==0) {
getterMethodsMap.put(methods[i].getName().substring(2), methods[i]);
} else if (methods[i].getName().startsWith("set") && methods[i].getParameterTypes().length==1) {
setterMethods.add(methods[i]);
}
}
//loop the setter and search for a matching getter for taking over the property.
Object[] parameter = new Object[1];
for (int i = 0; i < setterMethods.size(); i++) {
Method setterMethod = (Method) setterMethods.get(i);
String getterName = setterMethod.getName().substring(3);
Method getterMethod = getterMethodsMap.get(getterName);
//the getter method is possibly not available at the source object
//all methods are fetched from the target class!
try {
Method m = source.getClass().getMethod(getterMethod.getName(), getterMethod.getParameterTypes());
parameter[0] = m.invoke(source, (Object[]) null);
} catch (Exception e) {
continue;
}
try {
setterMethod.invoke(target, parameter);
} catch (Exception e) {
continue;
}
}
return null;
}
/**
* Attempts to list all the classes in the specified package as determined
* by the context class loader
*
* @param packagename
* the package name to search
* @return a list of classes that exist within that package
* @throws ClassNotFoundException
* @throws ClassNotFoundException
* if something went wrong
*/
public static List<Class<?>> getClassesForPackage(final String packagename) throws ClassNotFoundException {
// This will hold a list of directories matching the packagename. There may be more than one if a package is split over multiple jars/paths
ArrayList<File> directories = new ArrayList<>();
try {
ClassLoader cld = Thread.currentThread().getContextClassLoader();
if (cld == null) {
throw new ClassNotFoundException("Can't get class loader.");
}
String path = packagename.replace('.', '/');
// Ask for all resources for the path
Enumeration<URL> resources = cld.getResources(path);
while (resources.hasMoreElements()) {
directories.add(new File(URLDecoder.decode( resources.nextElement().getPath() , "UTF-8")));
}
} catch (NullPointerException x) {
throw new ClassNotFoundException(packagename + " does not appear to be a valid package (Null pointer exception)");
} catch (UnsupportedEncodingException encex) {
throw new ClassNotFoundException(packagename + " does not appear to be a valid package (Unsupported encoding)");
} catch (IOException ioex) {
throw new ClassNotFoundException("IOException was thrown when trying to get all resources for " + packagename);
}
ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
// For every directory identified capture all the .class files
for (int i = 0; i < directories.size(); i++) {
if (((File)directories.get(i)).exists()) {
// Get the list of the files contained in the package
String[] files = ((File)directories.get(i)).list();
for (int j = 0; j < files.length; j++) {
// we are only interested in .class files
if (files[j].endsWith(".class")) {
// removes the .class extension
classes.add(Class.forName(packagename + '.' + files[j].substring(0, files[j].length() - 6)));
}
}
} else {
throw new ClassNotFoundException(packagename + " (" + ((File)directories.get(i)).getPath() + ") does not appear to be a valid package");
}
}
return classes;
}
/**
* gets the package without the classname, empty string if there is no package.
*
* @param c
* The class where the package name should be extracted from.
* @return The The package part of the given class.
*/
public static String getPackageName(Class<?> c) {
if(c==null) {
//return an empty string if no class is given.
return EMPTY;
}
String fullyQualifiedName = c.getName();
//cut the [L from the class/package name. This happens always with array classes.
if(fullyQualifiedName.startsWith("[L")) {
fullyQualifiedName = fullyQualifiedName.substring(2);
}
int lastDot = fullyQualifiedName.lastIndexOf('.');
if (lastDot == -1) {
return EMPTY;
}
return fullyQualifiedName.substring(0, lastDot);
}
/**
* Tries to load the <code>{@link Class}</code> specified with the <code>className</code>
* parameter. The parameter should contain a qualified class name.
*
* @param className The name of the <code>{@link Class}</code> to be loaded.
* @return The desired <code>{@link Class}</code> or <code>null</code> if the class could not be loaded.
* Never throws any kind of <code>{@link Exception}</code>
*/
public static Class<?> getClassForName(String className) {
//hint: there is no reason to add some caching because the
//java framework already uses a Class cache.
Class<?> result = null;
//try to load the class using Class#forName
try {
result = Class.forName(className);
} catch (Exception e) {}
//just try out if the desired class is located in the default package
if(result==null && className.indexOf('.')==-1) {
try {
result = Class.forName("java.lang." + className);
} catch (Exception e) {}
}
return result;
}
/**
* Determines if the given class is a member class (sub class).
*
* @param cls The {@link Class} to be tested.
* @return <code>true</code> if we have a member class or <code>false</code> otherwise.
* If the given {@link Class} is <code>null</code>, <code>false</code> is returned.
*/
public static boolean isMemberClass(Class<?> cls) {
if(cls == null) {
return false;
}
String clsStr = cls.toString();
if(clsStr.indexOf('$')!=-1) {
return true;
}
return false;
}
/**
* Tests if the both given classes are the same. It will not
* be detected if both classes extends a common class or have
* the same interface.
*
* @param class1 The first classed to be compared to the second class.
* @param class2 The second class to be compared to the first one.
* @return <code>true</code> if the classes equals and <code>false</code> otherwise.
*/
public static boolean equals(Class<?> class1, Class<?> class2) {
if(class1==null || class2==null) {
return false;
}
return class1.getName().equals(class2.getName());
}
/**
* Determines the type of operating system.
* @return One of the constants:
* <ul>
* <li>OS_WINDOWS</li>
* <li>OS_LINUX</li>
* <li>OS_MAC</li>
* </ul>
*/
public static int getOS() {
if(os!=-1) {
return os;
}
String osName = System.getProperty("os.name").toLowerCase();
if(osName.indexOf("window")!=-1) {
return os = OS_WINDOWS;
} else if(osName.indexOf("linux")!=-1) {
return os = OS_LINUX;
} else if(osName.indexOf("mac")!=-1) {
return os = OS_LINUX;
}
try {
Class.forName("sun.print.Win32PrintJob");
return os = OS_WINDOWS;
} catch (Exception e) {
}
return os = OS_UNKNOWN;
}
/**
* Tells if the current operating system is linux.
*/
public static boolean isLinux() {
return getOS() == OS_LINUX;
}
/**
* is64Bit()
*
* Determine if this is a 64 bit environment
*/
public static boolean is64bit() {
String val = System.getProperty("sun.arch.data.model");
boolean is64bit = false;
if (val.equals("64")) {
is64bit = true;
}
return is64bit;
}
/**
* Adds the specified path to the java library path
*
* @param pathToAdd the path to add
*/
public static void addLibraryPath(String pathToAdd) throws Exception {
final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths");
usrPathsField.setAccessible(true);
// get array of paths
final String[] paths = (String[]) usrPathsField.get(null);
// check if the path to add is already present
for (String path : paths) {
if (path.equals(pathToAdd)) {
return;
}
}
// add the new path
final String[] newPaths = Arrays.copyOf(paths, paths.length + 1);
newPaths[newPaths.length - 1] = pathToAdd;
usrPathsField.set(null, newPaths);
}
/**
* Writes the stack trace of the current Thread to a String.
* @return The desired stack trace.
*/
public static String dumpStackToString() {
StringOutputStream str = new StringOutputStream();
new Exception("Stack trace").printStackTrace(str);
return str.toString();
}
private static class StringOutputStream extends PrintStream {
public StringOutputStream() {
super(new OutputStream() {
public void write(int b) throws IOException {
//it's a null stream!
}
});
}
StringBuilder buffer = new StringBuilder();
public void write(int b) {
buffer.append(b);
}
public void println(String x) {
buffer.append(x + "\n");
}
public String toString() {
return buffer.toString();
}
}
/**
* Determines a two digit version number. The java version 1.5.0_11 simply returns 15, the version 1.4.2 returns 14
* and so on.
*
* @return The two digit version number or -1 if no version could be determined. Never throws any kind of <code>{@link Exception}</code>.
*/
public static int javaVersion() {
if (javaVersion != -1) {
return javaVersion;
}
try {
String versionProperty = System.getProperty( "java.version" );
String numString = StringUtil.replace(versionProperty, ".", EMPTY).substring(0,2);
javaVersion = CommonUtils.toNumber(numString).intValue();
return javaVersion;
} catch (Exception e) {
return -1;
}
}
/**
* Does a <code>Thread.sleep</code> but without having these nasty
* exception handling with the {@link InterruptedException}. The {@link InterruptedException}
* gets simple be logged.
*/
public static void sleepSilent(int time) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
LoggerFactory.getLogger().log(Level.WARNING, "Sleep has been interrupted", e);
}
}
}