blob: c622ffc28ca887ba02be84d29859ae1c12108cdd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.brooklyn.util.javalang;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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.lang.reflect.Modifier;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import javax.annotation.Nullable;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
/**
* Reflection utilities ("borrowed" from cloudsoft monterey).
*
* @author aled
*/
public class Reflections {
private static final Logger LOG = LoggerFactory.getLogger(Reflections.class);
public static class ReflectionNotFoundException extends RuntimeException {
private static final long serialVersionUID = 9032835250796708037L;
public ReflectionNotFoundException(String message, Throwable cause) {
super(message, cause);
}
public ReflectionNotFoundException(String message) {
super(message);
}
}
public static class ReflectionAccessException extends RuntimeException {
private static final long serialVersionUID = 6569605861192432009L;
public ReflectionAccessException(String message, Throwable cause) {
super(message, cause);
}
}
private final ClassLoader classLoader;
private final Map<String, String> classRenameMap = MutableMap.of();
public Reflections(ClassLoader classLoader) {
this.classLoader = checkNotNull(classLoader);
}
/** supply a map of known renames, of the form "old-class -> new-class" */
public Reflections applyClassRenames(Map<String,String> newClassRenames) {
this.classRenameMap.putAll(newClassRenames);
return this;
}
public Object loadInstance(String classname, Object...argValues) throws ReflectionNotFoundException, ReflectionAccessException {
Class<?> clazz = loadClass(classname);
Optional<?> v = null;
try {
v = invokeConstructorWithArgs(clazz, argValues);
if (v.isPresent()) return v.get();
} catch (Exception e) {
throw new IllegalStateException("Error invoking constructor for "+clazz+Arrays.toString(argValues) + ": " + Exceptions.collapseText(e));
}
throw new IllegalStateException("No suitable constructor for "+clazz+Arrays.toString(argValues));
}
public Object loadInstance(String classname, Class<?>[] argTypes, Object[] argValues) throws ReflectionNotFoundException, ReflectionAccessException {
Class<?> clazz = loadClass(classname);
Constructor<?> constructor = loadConstructor(clazz, argTypes);
return loadInstance(constructor, argValues);
}
public Object loadInstance(String classname) throws ReflectionNotFoundException, ReflectionAccessException {
Class<?> clazz = loadClass(classname);
try {
return clazz.newInstance();
} catch (InstantiationException e) {
throw new ReflectionAccessException("Failed to create instance of class '" + classname + "' using class loader " + classLoader + ": " + Exceptions.collapseText(e), e);
} catch (IllegalAccessException e) {
throw new ReflectionAccessException("Failed to create instance of class '" + classname + "' using class loader " + classLoader + ": " + Exceptions.collapseText(e), e);
}
}
/** instantiates the given class from its binary name */
public Class<?> loadClass(String classname) throws ReflectionNotFoundException {
try {
classname = findMappedNameAndLog(classRenameMap, classname);
return classLoader.loadClass(classname);
} catch (ClassNotFoundException e) {
throw new ReflectionNotFoundException("Failed to load class '" + classname + "' using class loader " + classLoader + ": " + Exceptions.collapseText(e), e);
} catch (NoClassDefFoundError e) {
throw new ReflectionNotFoundException("Failed to load class '" + classname + "' using class loader " + classLoader + ": " + Exceptions.collapseText(e), e);
} catch (UnsupportedClassVersionError e) {
throw new ReflectionNotFoundException("Failed to load class '" + classname + "' using class loader " + classLoader + ": " + Exceptions.collapseText(e), e);
}
}
@SuppressWarnings("unchecked")
public <T> Class<? extends T> loadClass(String classname, Class<T> superType) throws ReflectionNotFoundException {
return (Class<? extends T>) loadClass(classname);
}
/** given a nested part, e.g. Inner$VeryInner, this will recurse through clazz.Inner, looking for VeryInner,
* then looking in each supertype (interface) of clazz for Inner.VeryInner;
* <p>
* so it will find Clazz.Inner.VeryInner wherever in the hierarchy it is defined
* <p>
* (as opposed to ClassLoader which requires Inner.VeryInner to be _declared_ in clazz, not in any supertype
* <p>
* returns null if not found
*/
public static Class<?> loadInnerClassPossiblyInheritted(Class<?> clazz, String nestedPart) throws ReflectionNotFoundException {
Set<String> visited = new HashSet<String>();
Class<?> result = loadInnerClassPossiblyInheritted(visited, clazz, nestedPart);
if (result!=null) return result;
throw new ReflectionNotFoundException("Inner class " + nestedPart + " could not be found in " + clazz + " or any of its super-types");
}
/** as 2-arg, but maintains set of visited elements, and returns null if not found */
private static Class<?> loadInnerClassPossiblyInheritted(Set<String> visited, Class<?> clazz, String nestedPart) throws ReflectionNotFoundException {
if (clazz==null) return null;
if (nestedPart==null || nestedPart.length()==0) return clazz;
int i1 = nestedPart.indexOf('$');
int i2 = nestedPart.indexOf('.');
int idx = (i2 > -1 && (i2 < i1 || i1==-1) ? i2 : i1);
String thisClassToFind = nestedPart;
String nextClassesToFind = "";
if (idx>=0) {
thisClassToFind = nestedPart.substring(0, idx);
nextClassesToFind = nestedPart.substring(idx+1);
}
if (!visited.add(clazz.getCanonicalName()+"!"+nestedPart)) {
//already visited
return null;
}
Class<?>[] members = clazz.getClasses();
for (int i = 0; i < members.length; i++) {
if (members[i].getSimpleName().equals(thisClassToFind)) {
Class<?> clazzI = loadInnerClassPossiblyInheritted(visited, members[i], nextClassesToFind);
if (clazzI!=null) return clazzI;
}
}
//look in supertype first (not sure if necessary)
Class<?> result = loadInnerClassPossiblyInheritted(visited, clazz.getSuperclass(), nestedPart);
if (result!=null) return result;
for (Class<?> iface : clazz.getInterfaces()) {
result = loadInnerClassPossiblyInheritted(visited, iface, nestedPart);
if (result!=null) return result;
}
return null;
}
/** does not look through ancestors of outer class */
public Class<?> loadInnerClassNotInheritted(String outerClassname, String innerClassname) throws ReflectionNotFoundException {
return loadClass(outerClassname + "$" + innerClassname);
}
/** does not look through ancestors of outer class
* <p>
* uses the classloader set in this class, not in the clazz supplied */
public Class<?> loadInnerClassNotInheritted(Class<?> outerClazz, String innerClassname) throws ReflectionNotFoundException {
return loadClass(outerClazz.getName() + "$" + innerClassname);
}
public Constructor<?> loadConstructor(Class<?> clazz, Class<?>[] argTypes) throws ReflectionAccessException {
try {
return clazz.getConstructor(argTypes);
} catch (SecurityException e) {
throw new ReflectionAccessException("Failed to load constructor of class '" + clazz + " with argument types " + Arrays.asList(argTypes) + ": " + Exceptions.collapseText(e), e);
} catch (NoSuchMethodException e) {
throw new ReflectionAccessException("Failed to load constructor of class '" + clazz + " with argument types " + Arrays.asList(argTypes) + ": " + Exceptions.collapseText(e), e);
}
}
/** Invokes a suitable constructor, supporting varargs and primitives */
public static <T> Optional<T> invokeConstructorWithArgs(ClassLoader classLoader, String className, Object...argsArray) {
Reflections reflections = new Reflections(classLoader);
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) reflections.loadClass(className);
return invokeConstructorWithArgs(reflections, clazz, argsArray, false);
}
/** Invokes a suitable constructor, supporting varargs and primitives */
public static <T> Optional<T> invokeConstructorWithArgs(ClassLoader classLoader, Class<T> clazz, Object[] argsArray, boolean setAccessible) {
Reflections reflections = new Reflections(classLoader);
return invokeConstructorWithArgs(reflections, clazz, argsArray, setAccessible);
}
/** Invokes a suitable constructor, supporting varargs and primitives */
public static <T> Optional<T> invokeConstructorWithArgs(Class<T> clazz, Object...argsArray) {
return invokeConstructorWithArgs(clazz, argsArray, false);
}
/** Invokes a suitable constructor, supporting varargs and primitives */
public static <T> Optional<T> invokeConstructorWithArgs(Class<T> clazz, Object[] argsArray, boolean setAccessible) {
Reflections reflections = new Reflections(clazz.getClassLoader());
return invokeConstructorWithArgs(reflections, clazz, argsArray, setAccessible);
}
/** Invokes a suitable constructor, supporting varargs and primitives, additionally supporting setAccessible */
@SuppressWarnings("unchecked")
public static <T> Optional<T> invokeConstructorWithArgs(Reflections reflections, Class<T> clazz, Object[] argsArray, boolean setAccessible) {
for (Constructor<?> constructor : clazz.getConstructors()) {
Class<?>[] parameterTypes = constructor.getParameterTypes();
if (constructor.isVarArgs()) {
if (typesMatchUpTo(argsArray, parameterTypes, parameterTypes.length-1)) {
Class<?> varargType = parameterTypes[parameterTypes.length-1].getComponentType();
boolean varargsMatch = true;
for (int i=parameterTypes.length-1; i<argsArray.length; i++) {
if (!Boxing.boxedType(varargType).isInstance(argsArray[i]) ||
(varargType.isPrimitive() && argsArray[i]==null)) {
varargsMatch = false;
break;
}
}
if (varargsMatch) {
Object varargs = Array.newInstance(varargType, argsArray.length+1 - parameterTypes.length);
for (int i=parameterTypes.length-1; i<argsArray.length; i++) {
Boxing.setInArray(varargs, i+1-parameterTypes.length, argsArray[i], varargType);
}
Object[] newArgsArray = new Object[parameterTypes.length];
System.arraycopy(argsArray, 0, newArgsArray, 0, parameterTypes.length-1);
newArgsArray[parameterTypes.length-1] = varargs;
if (setAccessible) constructor.setAccessible(true);
return (Optional<T>) Optional.of(reflections.loadInstance(constructor, newArgsArray));
}
}
}
if (typesMatch(argsArray, parameterTypes)) {
if (setAccessible) constructor.setAccessible(true);
return (Optional<T>) Optional.of(reflections.loadInstance(constructor, argsArray));
}
}
return Optional.absent();
}
/** returns a single constructor in a given class, or throws an exception */
public Constructor<?> loadSingleConstructor(Class<?> clazz) {
Constructor<?>[] constructors = clazz.getConstructors();
if (constructors.length == 1) {
return constructors[0];
}
throw new IllegalArgumentException("Class " + clazz + " has more than one constructor");
}
public <T> T loadInstance(Constructor<T> constructor, Object...argValues) throws IllegalArgumentException, ReflectionAccessException {
try {
try {
return constructor.newInstance(argValues);
} catch (IllegalArgumentException e) {
try {
LOG.warn("Failure passing provided arguments ("+getIllegalArgumentsErrorMessage(constructor, argValues)+"; "+e+"); attempting to reconstitute");
argValues = (Object[]) updateFromNewClassLoader(argValues);
return constructor.newInstance(argValues);
} catch (Throwable e2) {
LOG.warn("Reconstitution attempt failed (will rethrow original excaption): "+e2, e2);
throw e;
}
}
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(getIllegalArgumentsErrorMessage(constructor, argValues)+": " + Exceptions.collapseText(e), e);
} catch (InstantiationException e) {
throw new ReflectionAccessException("Failed to create instance of " + constructor.getDeclaringClass() + ": " + Exceptions.collapseText(e), e);
} catch (IllegalAccessException e) {
throw new ReflectionAccessException("Failed to create instance of " + constructor.getDeclaringClass() + ": " + Exceptions.collapseText(e), e);
} catch (InvocationTargetException e) {
throw new ReflectionAccessException("Failed to create instance of " + constructor.getDeclaringClass() + ": " + Exceptions.collapseText(e), e);
}
}
public Method loadMethod(Class<?> clazz, String methodName, Class<?>[] argTypes) throws ReflectionNotFoundException, ReflectionAccessException {
try {
return clazz.getMethod(methodName, argTypes);
} catch (NoClassDefFoundError e) {
throw new ReflectionNotFoundException("Failed to invoke method " + methodName + " on class " + clazz + " with argument types " + Arrays.asList(argTypes) + ", using class loader " + clazz.getClassLoader() + ": " + Exceptions.collapseText(e), e);
} catch (NoSuchMethodException e) {
throw new ReflectionNotFoundException("Failed to invoke method " + methodName + " on class " + clazz + " with argument types " + Arrays.asList(argTypes) + ": " + Exceptions.collapseText(e), e);
} catch (SecurityException e) {
throw new ReflectionAccessException("Failed to invoke method " + methodName + " on class " + clazz + " with argument types " + Arrays.asList(argTypes) + ": " + Exceptions.collapseText(e), e);
}
}
/** returns the first method matching the given name */
public Method loadMethod(Class<?> clazz, String methodName) throws ReflectionNotFoundException, ReflectionAccessException {
try {
Method[] allmethods = clazz.getMethods();
for (int i = 0; i < allmethods.length; i++) {
if (allmethods[i].getName().equals(methodName)) {
return allmethods[i];
}
}
throw new ReflectionNotFoundException("Cannot find method " + methodName + " on class " + clazz);
} catch (SecurityException e) {
throw new ReflectionAccessException("Failed to invoke method '" + methodName + " on class " + clazz + ": " + Exceptions.collapseText(e), e);
}
}
/**
*
* @throws ReflectionAccessException If invocation failed due to illegal access or the invoked method failed
* @throws IllegalArgumentException If the arguments were invalid
*/
public Object invokeMethod(Method method, Object obj, Object... argValues) throws ReflectionAccessException {
try {
return method.invoke(obj, argValues);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(getIllegalArgumentsErrorMessage(method, argValues), e);
} catch (IllegalAccessException e) {
throw new ReflectionAccessException("Failed to invoke method '" + method.toGenericString() + " on class " + method.getDeclaringClass() + " with argument values " + Arrays.asList(argValues) + ": " + Exceptions.collapseText(e), e);
} catch (InvocationTargetException e) {
throw new ReflectionAccessException("Failed to invoke method '" + method.toGenericString() + " on class " + method.getDeclaringClass() + " with argument values " + Arrays.asList(argValues) + ": " + Exceptions.collapseText(e), e);
}
}
public Object invokeStaticMethod(Method method, Object... argValues) throws IllegalArgumentException, ReflectionAccessException {
try {
return method.invoke(null, argValues);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(getIllegalArgumentsErrorMessage(method, argValues), e);
} catch (IllegalAccessException e) {
throw new ReflectionAccessException("Failed to invoke method '" + method.toGenericString() + " on class " + method.getDeclaringClass() + " with argument values " + Arrays.asList(argValues) + ": " + Exceptions.collapseText(e), e);
} catch (InvocationTargetException e) {
throw new ReflectionAccessException("Failed to invoke method '" + method.toGenericString() + " on class " + method.getDeclaringClass() + " with argument values " + Arrays.asList(argValues) + ": " + Exceptions.collapseText(e), e);
}
}
public Object loadStaticField(Class<?> clazz, String fieldname) throws ReflectionAccessException {
return loadStaticFields(clazz, new String[] {fieldname}, null)[0];
}
public Object[] loadStaticFields(Class<?> clazz, String[] fieldnamesArray, Object[] defaults) throws ReflectionAccessException {
Object[] result = new Object[fieldnamesArray.length];
if (defaults!=null) {
for (int i = 0; i < defaults.length; i++) {
result[i] = defaults[i];
}
}
List<String> fieldnames = Arrays.asList(fieldnamesArray);
Field[] classFields = clazz.getDeclaredFields();
for (int i = 0; i < classFields.length; i++) {
Field field = classFields[i];
int index = fieldnames.indexOf(field.getName());
if (index >= 0) {
try {
result[index] = field.get(null);
} catch (IllegalArgumentException e) {
throw new ReflectionAccessException("Failed to load field '" + field.getName() + " from class " + clazz + ": " + Exceptions.collapseText(e), e);
} catch (IllegalAccessException e) {
throw new ReflectionAccessException("Failed to load field '" + field.getName() + " from class " + clazz + ": " + Exceptions.collapseText(e), e);
}
}
}
return result;
}
private static String getIllegalArgumentsErrorMessage(Method method, Object[] argValues) {
return method.toGenericString() + " not applicable for the parameters of type " + argumentTypesToString(argValues);
}
private static String getIllegalArgumentsErrorMessage(Constructor<?> constructor, Object[] argValues) {
return constructor.toGenericString() + " not applicable for the parameters of type " + argumentTypesToString(argValues);
}
private static String argumentTypesToString(Object[] argValues) {
StringBuffer msg = new StringBuffer("(");
for (int i = 0; i < argValues.length; i++) {
if (i != 0) msg.append(", ");
msg.append(argValues[i] != null ? argValues[i].getClass().getName() : "null");
}
msg.append(")");
return msg.toString();
}
/** copies all fields from the source to target; very little compile-time safety checking, so use with care
* @throws IllegalAccessException
* @throws IllegalArgumentException */
public static <T> void copyFields(T source, T target) throws IllegalArgumentException, IllegalAccessException {
Class<? extends Object> clazz = source.getClass();
while (clazz!=null) {
Field[] fields = clazz.getDeclaredFields();
for (Field f : fields) {
f.setAccessible(true);
Object vs = f.get(source);
Object vt = f.get(target);
if ((vs==null && vt!=null) || (vs!=null && !vs.equals(vt))) {
f.set(target, vs);
}
}
clazz = clazz.getSuperclass();
}
}
/**
* Loads class given its canonical name format (e.g. com.acme.Foo.Inner),
* using iterative strategy (trying com.acme.Foo$Inner, then com.acme$Foo$Inner, etc).
* @throws ReflectionNotFoundException
*/
public Class<?> loadClassFromCanonicalName(String canonicalName) throws ClassNotFoundException, ReflectionNotFoundException {
ClassNotFoundException err = null;
String name = canonicalName;
do {
try {
return classLoader.loadClass(name);
} catch (ClassNotFoundException e) {
if (err == null) err = e;
int lastIndexOf = name.lastIndexOf(".");
if (lastIndexOf >= 0) {
name = name.substring(0, lastIndexOf) + "$" + name.substring(lastIndexOf+1);
}
}
} while (name.contains("."));
throw err;
}
/** finds the resource in the classloader, if it exists; inserts or replaces leading slash as necessary
* (i believe it should _not_ have one, but there is some inconsistency)
*
* Will return null if no resource is found.
*/
@Nullable
public URL getResource(String r) {
URL u = null;
u = classLoader.getResource(r);
if (u!=null) return u;
if (r.startsWith("/")) r = r.substring(1);
else r = "/"+r;
return classLoader.getResource(r);
}
/**
* Serialize the given object, then reload using the current class loader;
* this removes linkages to instances with classes loaded by an older class loader.
* <p>
* (like a poor man's clone)
* <p>
* aka "reconstitute(Object)"
*/
public final Object updateFromNewClassLoader(Object data) throws IOException, ClassNotFoundException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
new ObjectOutputStream(bytes).writeObject(data);
Object reconstituted = new ObjectInputStream(new ByteArrayInputStream(bytes.toByteArray())).readObject();
if (LOG.isDebugEnabled()) LOG.debug("Reconstituted data: " + reconstituted + ", class loader: " + classLoader);
return reconstituted;
}
public ClassLoader getClassLoader() {
return classLoader;
}
@SuppressWarnings("unchecked")
public static <T> Class<? super T> findSuperType(T impl, String typeName) {
Set<Class<?>> toinspect = new LinkedHashSet<Class<?>>();
Set<Class<?>> inspected = new HashSet<Class<?>>();
toinspect.add(impl.getClass());
while (toinspect.size() > 0) {
Class<?> clazz = toinspect.iterator().next(); // get and remove the first element
if (clazz.getName().equals(typeName)) {
return (Class<? super T>) clazz;
}
inspected.add(clazz);
List<Class<?>> toAdd = Arrays.asList(clazz.getInterfaces());
toinspect.addAll( toAdd );
if (clazz.getSuperclass() != null) toinspect.add(clazz.getSuperclass());
toinspect.removeAll(inspected);
}
return null;
}
/** whereas Class.getInterfaces() only returns interfaces directly implemented by a class,
* this walks the inheritance hierarchy to include interfaces implemented by superclass/ancestors;
* (note it does not include superinterfaces)
*/
public static Set<Class<?>> getInterfacesIncludingClassAncestors(Class<?> clazz) {
Set<Class<?>> result = new LinkedHashSet<Class<?>>();
while (clazz!=null) {
for (Class<?> iface: clazz.getInterfaces())
result.add(iface);
clazz = clazz.getSuperclass();
}
return result;
}
public static Method findMethod(Class<?> clazz, String name, Class<?>... parameterTypes) throws NoSuchMethodException {
if (clazz == null || name == null) {
throw new NullPointerException("Must not be null: clazz="+clazz+"; name="+name);
}
Class<?> clazzToInspect = clazz;
NoSuchMethodException toThrowIfFails = null;
while (clazzToInspect != null) {
try {
return clazzToInspect.getDeclaredMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
if (toThrowIfFails == null) toThrowIfFails = e;
clazzToInspect = clazzToInspect.getSuperclass();
}
}
throw toThrowIfFails;
}
public static Field findField(Class<?> clazz, String name) throws NoSuchFieldException {
if (clazz == null || name == null) {
throw new NullPointerException("Must not be null: clazz="+clazz+"; name="+name);
}
Class<?> clazzToInspect = clazz;
NoSuchFieldException toThrowIfFails = null;
while (clazzToInspect != null) {
try {
return clazzToInspect.getDeclaredField(name);
} catch (NoSuchFieldException e) {
if (toThrowIfFails == null) toThrowIfFails = e;
clazzToInspect = clazzToInspect.getSuperclass();
}
}
throw toThrowIfFails;
}
public static List<Field> findPublicFieldsOrderedBySuper(Class<?> clazz) {
checkNotNull(clazz, "clazz");
MutableList.Builder<Field> result = MutableList.<Field>builder();
Stack<Class<?>> tovisit = new Stack<Class<?>>();
Set<Class<?>> visited = Sets.newLinkedHashSet();
tovisit.push(clazz);
while (!tovisit.isEmpty()) {
Class<?> nextclazz = tovisit.pop();
if (!visited.add(nextclazz)) {
continue; // already visited
}
if (nextclazz.getSuperclass() != null) tovisit.add(nextclazz.getSuperclass());
tovisit.addAll(Arrays.asList(nextclazz.getInterfaces()));
result.addAll(Iterables.filter(Arrays.asList(nextclazz.getDeclaredFields()), new Predicate<Field>() {
@Override public boolean apply(Field input) {
return Modifier.isPublic(input.getModifiers());
}}));
}
List<Field> resultList = result.build();
Collections.sort(resultList, new Comparator<Field>() {
@Override public int compare(Field f1, Field f2) {
Field fsubbest = inferSubbestField(f1, f2);
return (fsubbest == null) ? 0 : (fsubbest == f1 ? 1 : -1);
}});
return resultList;
}
// TODO I've seen strange behaviour where class.getMethods() does not include methods from interfaces.
// Also the ordering guarantees here are useful...
public static List<Method> findPublicMethodsOrderedBySuper(Class<?> clazz) {
checkNotNull(clazz, "clazz");
MutableList.Builder<Method> result = MutableList.<Method>builder();
Stack<Class<?>> tovisit = new Stack<Class<?>>();
Set<Class<?>> visited = Sets.newLinkedHashSet();
tovisit.push(clazz);
while (!tovisit.isEmpty()) {
Class<?> nextclazz = tovisit.pop();
if (!visited.add(nextclazz)) {
continue; // already visited
}
if (nextclazz.getSuperclass() != null) tovisit.add(nextclazz.getSuperclass());
tovisit.addAll(Arrays.asList(nextclazz.getInterfaces()));
result.addAll(Iterables.filter(Arrays.asList(nextclazz.getDeclaredMethods()), new Predicate<Method>() {
@Override public boolean apply(Method input) {
return Modifier.isPublic(input.getModifiers());
}}));
}
List<Method> resultList = result.build();
Collections.sort(resultList, new Comparator<Method>() {
@Override public int compare(Method m1, Method m2) {
Method msubbest = inferSubbestMethod(m1, m2);
return (msubbest == null) ? 0 : (msubbest == m1 ? 1 : -1);
}});
return resultList;
}
/**
* Gets the field that is in the sub-class; or null if one field does not come from a sub-class of the other field's class
*/
public static Field inferSubbestField(Field f1, Field f2) {
Class<?> c1 = f1.getDeclaringClass();
Class<?> c2 = f2.getDeclaringClass();
boolean isSuper1 = c1.isAssignableFrom(c2);
boolean isSuper2 = c2.isAssignableFrom(c1);
return (isSuper1) ? (isSuper2 ? null : f2) : (isSuper2 ? f1 : null);
}
/**
* Gets the method that is in the sub-class; or null if one method does not come from a sub-class of the other method's class
*/
public static Method inferSubbestMethod(Method m1, Method m2) {
Class<?> c1 = m1.getDeclaringClass();
Class<?> c2 = m2.getDeclaringClass();
boolean isSuper1 = c1.isAssignableFrom(c2);
boolean isSuper2 = c2.isAssignableFrom(c1);
return (isSuper1) ? (isSuper2 ? null : m2) : (isSuper2 ? m1 : null);
}
/**
* Gets the class that is in the sub-class; or null if neither is a sub-class of the other.
*/
public static Class<?> inferSubbest(Class<?> c1, Class<?> c2) {
boolean isSuper1 = c1.isAssignableFrom(c2);
boolean isSuper2 = c2.isAssignableFrom(c1);
return (isSuper1) ? (isSuper2 ? null : c2) : (isSuper2 ? c1 : null);
}
/** convenience for casting the given candidate to the given type (without any coercion, and allowing candidate to be null) */
@SuppressWarnings("unchecked")
public static <T> T cast(Object candidate, Class<? extends T> type) {
if (candidate==null) return null;
if (!type.isAssignableFrom(candidate.getClass()))
throw new IllegalArgumentException("Requires a "+type+", but had a "+candidate.getClass()+" ("+candidate+")");
return (T)candidate;
}
/** invokes the given method on the given clazz or instance, doing reasonably good matching on args etc
* @throws InvocationTargetException
* @throws IllegalAccessException
* @throws IllegalArgumentException */
public static Optional<Object> invokeMethodWithArgs(Object clazzOrInstance, String method, List<Object> args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
return invokeMethodWithArgs(clazzOrInstance, method, args, false);
}
public static Optional<Object> invokeMethodWithArgs(Object clazzOrInstance, String method, List<Object> args, boolean setAccessible) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
Preconditions.checkNotNull(clazzOrInstance, "clazz or instance");
Preconditions.checkNotNull(method, "method");
Preconditions.checkNotNull(args, "args to "+method);
Class<?> clazz;
Object instance;
if (clazzOrInstance instanceof Class) {
clazz = (Class<?>)clazzOrInstance;
instance = null;
} else {
clazz = clazzOrInstance.getClass();
instance = clazzOrInstance;
}
Object[] argsArray = args.toArray();
for (Method m: clazz.getMethods()) {
if (method.equals(m.getName())) {
Class<?>[] parameterTypes = m.getParameterTypes();
if (m.isVarArgs()) {
if (typesMatchUpTo(argsArray, parameterTypes, parameterTypes.length-1)) {
Class<?> varargType = parameterTypes[parameterTypes.length-1].getComponentType();
boolean varargsMatch = true;
for (int i=parameterTypes.length-1; i<argsArray.length; i++) {
if (!Boxing.boxedType(varargType).isInstance(argsArray[i]) ||
(varargType.isPrimitive() && argsArray[i]==null)) {
varargsMatch = false;
break;
}
}
if (varargsMatch) {
Object varargs = Array.newInstance(varargType, argsArray.length+1 - parameterTypes.length);
for (int i=parameterTypes.length-1; i<argsArray.length; i++) {
Boxing.setInArray(varargs, i+1-parameterTypes.length, argsArray[i], varargType);
}
Object[] newArgsArray = new Object[parameterTypes.length];
System.arraycopy(argsArray, 0, newArgsArray, 0, parameterTypes.length-1);
newArgsArray[parameterTypes.length-1] = varargs;
if (setAccessible) m.setAccessible(true);
return Optional.of(m.invoke(instance, newArgsArray));
}
}
}
if (typesMatch(argsArray, parameterTypes)) {
if (setAccessible) m.setAccessible(true);
return Optional.of(m.invoke(instance, argsArray));
}
}
}
return Optional.absent();
}
/** true iff all args match the corresponding types */
public static boolean typesMatch(Object[] argsArray, Class<?>[] parameterTypes) {
if (argsArray.length != parameterTypes.length)
return false;
return typesMatchUpTo(argsArray, parameterTypes, argsArray.length);
}
/** true iff the initial N args match the corresponding types */
public static boolean typesMatchUpTo(Object[] argsArray, Class<?>[] parameterTypes, int lengthRequired) {
if (argsArray.length < lengthRequired || parameterTypes.length < lengthRequired)
return false;
for (int i=0; i<lengthRequired; i++) {
if (argsArray[i]==null) continue;
if (Boxing.boxedType(parameterTypes[i]).isInstance(argsArray[i])) continue;
return false;
}
return true;
}
/**
* Gets all the interfaces implemented by the given type, including its parent classes.
*
* @param type the class to look up
* @return an immutable list of the interface classes
*/
public static List<Class<?>> getAllInterfaces(@Nullable Class<?> type) {
Set<Class<?>> found = Sets.newLinkedHashSet();
findAllInterfaces(type, found);
return ImmutableList.copyOf(found);
}
/** Recurse through the class hierarchies of the type and its interfaces. */
private static void findAllInterfaces(@Nullable Class<?> type, Set<Class<?>> found) {
if (type == null) return;
for (Class<?> i : type.getInterfaces()) {
if (found.add(i)) { // not seen before
findAllInterfaces(i, found);
}
}
findAllInterfaces(type.getSuperclass(), found);
}
public static boolean hasNoArgConstructor(Class<?> clazz) {
try {
clazz.getConstructor(new Class[0]);
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
public static boolean hasNoNonObjectFields(Class<? extends Object> clazz) {
if (Object.class.equals(clazz)) return true;
if (clazz.getDeclaredFields().length>0) return false;
return hasNoNonObjectFields(clazz.getSuperclass());
}
/** Takes a map of old-class-names to renames classes, and returns the mapped name if matched, or absent */
public static Optional<String> tryFindMappedName(Map<String, String> renames, String name) {
if (renames==null) return Optional.absent();
String mappedName = renames.get(name);
if (mappedName != null) {
return Optional.of(mappedName);
}
// look for inner classes by mapping outer class
if (name.contains("$")) {
String outerClassName = name.substring(0, name.indexOf('$'));
mappedName = renames.get(outerClassName);
if (mappedName != null) {
return Optional.of(mappedName + name.substring(name.indexOf('$')));
}
}
return Optional.absent();
}
public static String findMappedNameAndLog(Map<String, String> renames, String name) {
Optional<String> rename = Reflections.tryFindMappedName(renames, name);
if (rename.isPresent()) {
LOG.debug("Mapping class '"+name+"' to '"+rename.get()+"'");
return rename.get();
}
return name;
}
}