package winterwell.utils;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import winterwell.utils.io.FileUtils;
/**
* Reflection-related utility functions
*
* @testedby {@link ReflectionUtilsTest}
*/
public final class ReflectionUtils {
/**
*
* MAY BE VERY SLOW - may attempt to load every damn class!
*
* @param implementMe
* @param packageIndicator
* The search will start from here. It recursively explores all
* sub-packages.
* @return classes implementing implementMe
* @testedby {@link ReflectionUtilsTest#testFindClasses()}
*/
public static List<Class> findClasses(Class implementMe,
Class packageIndicator) {
Package p = packageIndicator.getPackage();
URL r = packageIndicator.getResource("");
String dir = r.getFile();
return findClasses(implementMe, new File(dir), p.getName());
}
public static List<Class> findClasses(Class implementMe, File dir,
String packageName) {
List<Class> found = new ArrayList<Class>();
List<File> classFiles = FileUtils.find(dir, ".*\\.class");
// // sort? -- age (the useful sort order) of class file is irrelevant
// if (sortFiles!=null) {
// Collections.sort(classFiles, sortFiles);
// }
for (File file : classFiles) {
// try to load class
String cName = FileUtils.getBasename(file);
if (cName.contains("$")) {
continue;
}
cName = packageName + "." + cName;
try {
Class c = Class.forName(cName);
// ignore interfaces
if (c.isInterface()) {
continue;
}
if (isa(c, implementMe)) {
found.add(c);
}
} catch (Exception e) {
// oh well
}
}
return found;
}
/**
* @param clazz
* @return Instance fields - public and private - which can be accessed from
* this class. Excludes: static fields and fields which cannot be
* accessed due to JVM security.<br>
* Includes: non-static final fields
*/
public static List<Field> getAllFields(Class clazz) {
ArrayList<Field> list = new ArrayList<Field>();
ReflectionUtils.getAllFields2(clazz, list);
return list;
}
private static void getAllFields2(Class clazz, ArrayList<Field> list) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// exclude static
int m = field.getModifiers();
if (Modifier.isStatic(m)) {
continue;
}
if (!field.isAccessible()) {
try {
field.setAccessible(true);
} catch (SecurityException e) {
// skip over this field
continue;
}
}
list.add(field);
}
// recurse
Class superClass = clazz.getSuperclass();
if (superClass == null)
return;
getAllFields2(superClass, list);
}
/**
* @param object
* @param annotation
* @param incPrivate
* If true, will return private and protected fields (provided
* they can be set accessible).
* @return (All fields / accessible public fields) in object which are
* annotated with annotation
*/
public static List<Field> getAnnotatedFields(Object object,
Class<? extends Annotation> annotation, boolean incPrivate) {
List<Field> allFields = incPrivate ? getAllFields(object.getClass())
: Arrays.asList(object.getClass().getFields());
List<Field> fields = new ArrayList<Field>();
for (Field f : allFields) {
if (f.isAnnotationPresent(annotation)) {
fields.add(f);
}
}
return fields;
}
/**
* Recurse to get a private field which may be declared in a super-class.
* Note: {@link Class#getField(String)} will only retrieve public fields.
*
* @param klass
* @param fieldName
* @return Field or null
*/
public static Field getField(Class klass, String fieldName) {
try {
Field f = klass.getDeclaredField(fieldName);
return f;
} catch (NoSuchFieldException e) {
klass = klass.getSuperclass();
if (klass == null)
return null;
}
return getField(klass, fieldName);
}
/**
* @param obj
* @param clazz
* @param includeStatic
* if true, include static fields too
* @return all <i>public</i> field values of type clazz in obj
*/
public static <X> List<X> getFieldValues(Object obj, Class<X> clazz,
boolean includeStatic) {
try {
Field[] fields = obj.getClass().getFields();
List<X> values = new ArrayList<X>();
for (Field field : fields) {
if (!includeStatic && Modifier.isStatic(field.getModifiers())) {
continue;
}
Object val = field.get(obj);
if (val == null) {
continue;
}
if (clazz.isAssignableFrom(val.getClass())) {
values.add((X) val);
}
}
return values;
} catch (Exception e) {
throw Utils.runtime(e);
}
}
/**
* Where is this class from?
*
* @param klass
* @return
*/
public static File getFile(Class klass) {
try {
URL rs = klass.getResource(klass.getSimpleName() + ".class");
return new File(rs.toURI());
} catch (Exception e) {
throw Utils.runtime(e);
}
}
/**
*
* @param clazz
* @param methodName
* @return method or null if it isn't there
*/
static Method getMethod(Class<?> clazz, String methodName) {
for (Method m : clazz.getMethods()) {
if (m.getName().equals(methodName))
return m;
}
return null;
}
public static <X> X getPrivateField(Object obj, String fieldName) {
Field f = ReflectionUtils.getField(obj.getClass(), fieldName);
f.setAccessible(true);
try {
return (X) f.get(obj);
} catch (Exception e) {
throw Utils.runtime(e);
}
}
/**
*
* @param obj
* @param property
* i.e. "name" maps to the method getName()
* @return null if the property is not present (so this is ambiguous)
*/
public static Object getProperty(Object obj, String property) {
try {
String mName = "get" + StrUtils.toTitleCase(property);
Method m = obj.getClass().getMethod(mName);
if (!m.isAccessible()) {
m.setAccessible(true);
}
try {
return m.invoke(obj);
} catch (Exception e) {
throw Utils.runtime(e);
}
} catch (NoSuchMethodException e) {
return null;
}
}
public static boolean hasField(Class klass, String field) {
return getField(klass, field) != null;
}
public static boolean hasMethod(Class klass, String methodName) {
for (Method m : klass.getMethods()) {
if (m.getName().equals(methodName))
return true;
}
return false;
}
public static Object invoke(Object obj, String methodName, Object... args) {
Method m = getMethod(obj.getClass(), methodName);
if (m == null)
throw Utils.runtime(new NoSuchMethodException(obj.getClass() + "."
+ methodName));
try {
return m.invoke(obj, args);
} catch (Exception e) {
throw Utils.runtime(e);
}
}
/**
* The equivalent of instanceof, but for Class objects. 'cos I always forget
* how to do this.
*
* @param possSubType
* @param superType
* @return true if possSubType <i>is</i> a subType of superType
*/
public static boolean isa(Class possSubType, Class superType) {
return superType.isAssignableFrom(possSubType);
}
/**
* Output manifest info on jar files
*
* @param args
* jar filenames
*/
public static void main(String[] args) {
assert args.length != 0;
for (String string : args) {
System.out.println("Info on jar " + string);
try {
JarFile jar = new JarFile(new File(string));
Manifest manifest = jar.getManifest();
Map<String, Attributes> entries = manifest.getEntries();
Attributes v = manifest.getMainAttributes();
for (Object k : v.keySet()) {
System.out.println(k + ": " + v.get(k));
}
for (String a : entries.keySet()) {
v = entries.get(a);
for (Object k : v.keySet()) {
System.out.println(a + k + ": " + v.get(k));
}
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
/**
* Set a field, which can be private. Throws exceptions if the field does
* not exist or if you cannot do this.
*
* @param obj
* @param fieldName
* @param value
*/
public static void setPrivateField(Object obj, String fieldName,
Object value) {
Field f = ReflectionUtils.getField(obj.getClass(), fieldName);
f.setAccessible(true);
try {
f.set(obj, value);
} catch (Exception e) {
throw Utils.runtime(e);
}
}
/**
* FIXME: Is this actually used anywhere?
*
* @deprecated
*/
@Deprecated
public static void setProperty(Object target, String propName, Object value) {
Class tClass = target.getClass();
// Is there a setXXX method?
try {
Method method = tClass.getMethod(
"set" + StrUtils.toTitleCase(propName), value.getClass());
method.invoke(target, value);
} catch (Exception e) {
// oh well
}
// Set field
setPrivateField(target, propName, value);
}
/**
* @return total memory used, in bytes.
* Runs garbage collection to try and give a better measure
* of what's currently in play.
* Warning: this is a bit of a slow call.
*/
public static long getUsedMemory() {
Runtime rt = Runtime.getRuntime();
rt.runFinalization();
rt.gc();
// The methods above shouldn't need it, but add in a pause anyway
Utils.sleep(100);
return rt.totalMemory() - rt.freeMemory();
}
}