All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.brooklyn.util.javalang.Reflections Maven / Gradle / Ivy

Go to download

Utility classes and methods developed for Brooklyn but not dependendent on Brooklyn or much else

There is a newer version: 1.1.0
Show newest version
/*
 * 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.lang.reflect.TypeVariable;
import java.net.URL;
import java.util.ArrayList;
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.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.javalang.coerce.TypeCoercer;
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.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.reflect.TypeToken;

/**
 * Reflection utilities
 * 
 * @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 classRenameMap = MutableMap.of();
    
    public Reflections(ClassLoader classLoader) {
        this.classLoader = classLoader!=null ? classLoader : getClass().getClassLoader();
    }
    
    /** supply a map of known renames, of the form "old-class -> new-class" */ 
    public Reflections applyClassRenames(Map newClassRenames) {
        this.classRenameMap.putAll(newClassRenames);
        return this;
    }

    /** @deprecated since 0.11.0, use {@link org.apache.brooklyn.util.core.ClassLoaderUtils} in a combination with {@link #invokeConstructorFromArgs(Class, Object...)} instead */
    public Object loadInstance(String classname, Object...argValues) throws ReflectionNotFoundException, ReflectionAccessException {
        Class clazz = loadClass(classname);
        Maybe v = null;
        try {
            v = invokeConstructorFromArgs(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));
    }
    /** @deprecated since 0.11.0, use {@link org.apache.brooklyn.util.core.ClassLoaderUtils} in a combination with {@link #invokeConstructorFromArgs(Class, Object...)} instead */
    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);
    }

    /** @deprecated since 0.11.0, use {@link org.apache.brooklyn.util.core.ClassLoaderUtils} in a combination with {@link #invokeConstructorFromArgs(Class, Object...)} instead */
    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 */
    /** @deprecated since 0.11.0, use {@link org.apache.brooklyn.util.core.ClassLoaderUtils} in a combination with {@link #invokeConstructorFromArgs(Class, Object...)} instead */
    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);
        }
    }

    /** @deprecated since 0.11.0, use {@link org.apache.brooklyn.util.core.ClassLoaderUtils} in a combination with {@link #invokeConstructorFromArgs(Class, Object...)} instead */
    @SuppressWarnings("unchecked")
    public  Class loadClass(String classname, Class superType) throws ReflectionNotFoundException {
        return (Class) 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;
     * 

* so it will find Clazz.Inner.VeryInner wherever in the hierarchy it is defined *

* (as opposed to ClassLoader which requires Inner.VeryInner to be _declared_ in clazz, not in any supertype *

* returns null if not found */ public static Class loadInnerClassPossiblyInheritted(Class clazz, String nestedPart) throws ReflectionNotFoundException { Set visited = new HashSet(); 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 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 */ /** @deprecated since 0.11.0, use {@link org.apache.brooklyn.util.core.ClassLoaderUtils} in a combination with {@link #invokeConstructorFromArgs(Class, Object...)} instead */ public Class loadInnerClassNotInheritted(String outerClassname, String innerClassname) throws ReflectionNotFoundException { return loadClass(outerClassname + "$" + innerClassname); } /** does not look through ancestors of outer class *

* 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); } } /** @deprecated since 0.10.0 use {@link #invokeConstructorFromArgs(Class, Object...)} or one of the variants; * this allows null field values */ @Deprecated public static Optional invokeConstructorWithArgs(ClassLoader classLoader, String className, Object...argsArray) { return Reflections.invokeConstructorFromArgsUntyped(classLoader, className, argsArray).toOptional(); } /** @deprecated since 0.10.0 use {@link #invokeConstructorFromArgs(Class, Object...)} or one of the variants */ @Deprecated public static Optional invokeConstructorWithArgs(ClassLoader classLoader, Class clazz, Object[] argsArray, boolean setAccessible) { return invokeConstructorFromArgs(classLoader, clazz, argsArray, setAccessible).toOptional(); } /** @deprecated since 0.10.0 use {@link #invokeConstructorFromArgs(Class, Object...)} or one of the variants */ @Deprecated public static Optional invokeConstructorWithArgs(Class clazz, Object...argsArray) { return Reflections.invokeConstructorFromArgs(clazz, argsArray).toOptional(); } /** @deprecated since 0.10.0 use {@link #invokeConstructorFromArgs(Class, Object...)} or one of the variants */ @Deprecated public static Optional invokeConstructorWithArgs(Class clazz, Object[] argsArray, boolean setAccessible) { return Reflections.invokeConstructorFromArgs(clazz, argsArray, setAccessible).toOptional(); } /** @deprecated since 0.10.0 use {@link #invokeConstructorFromArgs(Class, Object...)} or one of the variants */ @Deprecated public static Optional invokeConstructorWithArgs(Reflections reflections, Class clazz, Object[] argsArray, boolean setAccessible) { return Reflections.invokeConstructorFromArgs(reflections, clazz, argsArray, setAccessible).toOptional(); } /** Finds and invokes a suitable constructor, supporting varargs and primitives, boxing and looking at compatible supertypes in the constructor's signature */ public static Maybe invokeConstructorFromArgs(Class clazz, Object...argsArray) { return invokeConstructorFromArgs(clazz, argsArray, false); } /** As {@link #invokeConstructorFromArgs(Class, Object...)} but allowing more configurable input */ /** @deprecated since 0.11.0, use {@link org.apache.brooklyn.util.core.ClassLoaderUtils} in a combination with {@link #invokeConstructorFromArgs(Class, Object...)} instead */ public static Maybe invokeConstructorFromArgs(ClassLoader classLoader, String className, Object...argsArray) { return invokeConstructorFromArgs(classLoader, null, className, argsArray); } /** As {@link #invokeConstructorFromArgs(Class, Object...)} but allowing more configurable input */ /** @deprecated since 0.11.0, use {@link org.apache.brooklyn.util.core.ClassLoaderUtils} in a combination with {@link #invokeConstructorFromArgs(Class, Object...)} instead */ @SuppressWarnings("unchecked") public static Maybe invokeConstructorFromArgs(ClassLoader classLoader, Class optionalSupertype, String className, Object...argsArray) { Reflections reflections = new Reflections(classLoader); Class clazz = reflections.loadClass(className); if (optionalSupertype!=null && !optionalSupertype.isAssignableFrom(clazz)) { return Maybe.absent("The type requested '"+className+"' is not assignable to "+optionalSupertype); } return invokeConstructorFromArgs(reflections, (Class)clazz, argsArray, false); } /** As {@link #invokeConstructorFromArgs(Class, Object...)} but allowing more configurable input */ /** @deprecated since 0.11.0, use {@link org.apache.brooklyn.util.core.ClassLoaderUtils} in a combination with {@link #invokeConstructorFromArgs(Class, Object...)} instead */ public static Maybe invokeConstructorFromArgsUntyped(ClassLoader classLoader, String className, Object...argsArray) { Reflections reflections = new Reflections(classLoader); @SuppressWarnings("unchecked") Class clazz = (Class)reflections.loadClass(className); return invokeConstructorFromArgs(reflections, clazz, argsArray, false); } /** As {@link #invokeConstructorFromArgs(Class, Object...)} but allowing more configurable input; * in particular setAccessible allows private constructors to be used (not the default) */ public static Maybe invokeConstructorFromArgs(ClassLoader classLoader, Class clazz, Object[] argsArray, boolean setAccessible) { Reflections reflections = new Reflections(classLoader); return invokeConstructorFromArgs(reflections, clazz, argsArray, setAccessible); } /** As {@link #invokeConstructorFromArgs(Class, Object...)} but allowing more configurable input; * in particular setAccessible allows private constructors to be used (not the default) */ public static Maybe invokeConstructorFromArgs(Class clazz, Object[] argsArray, boolean setAccessible) { Reflections reflections = new Reflections(clazz.getClassLoader()); return invokeConstructorFromArgs(reflections, clazz, argsArray, setAccessible); } /** As {@link #invokeConstructorFromArgs(Class, Object...)} but will use private constructors (with setAccessible = true) */ public static Maybe invokeConstructorFromArgsIncludingPrivate(Class clazz, Object ...argsArray) { return Reflections.invokeConstructorFromArgs(new Reflections(clazz.getClassLoader()), clazz, argsArray, true); } /** As {@link #invokeConstructorFromArgs(Class, Object...)} but allowing more configurable input; * in particular setAccessible allows private constructors to be used (not the default) */ @SuppressWarnings("unchecked") public static Maybe invokeConstructorFromArgs(Reflections reflections, Class clazz, Object[] argsArray, boolean setAccessible) { for (Constructor constructor : MutableList.>of().appendAll(Arrays.asList(clazz.getConstructors())).appendAll(Arrays.asList(clazz.getDeclaredConstructors()))) { 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 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 loadInstance(Constructor 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 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 void copyFields(T source, T target) throws IllegalArgumentException, IllegalAccessException { Class 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. *

* (like a poor man's clone) *

* 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 Class findSuperType(T impl, String typeName) { Set> toinspect = new LinkedHashSet>(); Set> inspected = new HashSet>(); 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) clazz; } inspected.add(clazz); List> 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> getInterfacesIncludingClassAncestors(Class clazz) { Set> result = new LinkedHashSet>(); while (clazz!=null) { for (Class iface: clazz.getInterfaces()) result.add(iface); clazz = clazz.getSuperclass(); } return result; } /** Returns any method exactly matching the given signature, including privates and on parent classes. */ public static Maybe findMethodMaybe(Class clazz, String name, Class... parameterTypes) { if (clazz == null || name == null) return Maybe.absentNoTrace("class or name is null"); Iterable result = findMethods(false, clazz, name, parameterTypes); if (!result.iterator().hasNext()) return Maybe.absentNoTrace("no methods matching "+clazz.getName()+"."+name+"("+Arrays.asList(parameterTypes)+")"); return Maybe.of(result.iterator().next()); } /** Returns all methods compatible with the given argument types, including privates and on parent classes and where the method takes a supertype. */ public static Iterable findMethodsCompatible(Class clazz, String name, Class... parameterTypes) { return findMethods(true, clazz, name, parameterTypes); } private static Iterable findMethods(boolean allowCovariantParameterClasses, Class clazz, String name, Class... parameterTypes) { if (clazz == null || name == null) { return Collections.emptySet(); } List result = MutableList.of(); Class clazzToInspect = clazz; while (clazzToInspect != null) { methods: for (Method m: clazzToInspect.getDeclaredMethods()) { if (!name.equals(m.getName())) continue methods; if (m.getParameterTypes().length!=parameterTypes.length) continue methods; parameters: for (int i=0; i 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; } /** Finds the field with the given name declared on the given class or any superclass, * using {@link Class#getDeclaredField(String)}. *

* If the field name contains a '.' the field is interpreted as having * DeclaringClassCanonicalName.FieldName format, * allowing a way to set a field unambiguously if some are masked. *

* @throws NoSuchFieldException if not found */ public static Field findField(Class clazz, String name) throws NoSuchFieldException { return findFieldMaybe(clazz, name).orThrowUnwrapped(); } public static Maybe findFieldMaybe(Class clazz, String originalName) { String name = originalName; if (clazz == null || name == null) { throw new NullPointerException("Must not be null: clazz="+clazz+"; name="+name); } Class clazzToInspect = clazz; NoSuchFieldException toThrowIfFails = null; String clazzRequired = null; if (name.indexOf('.')>=0) { int lastDotIndex = name.lastIndexOf('.'); clazzRequired = name.substring(0, lastDotIndex); name = name.substring(lastDotIndex+1); } while (clazzToInspect != null) { try { if (clazzRequired==null || clazzRequired.equals(clazzToInspect.getCanonicalName())) { return Maybe.of(clazzToInspect.getDeclaredField(name)); } } catch (NoSuchFieldException e) { if (toThrowIfFails == null) toThrowIfFails = e; } clazzToInspect = clazzToInspect.getSuperclass(); } if (toThrowIfFails==null) return Maybe.absent("Field '"+originalName+"' not found"); return Maybe.absent(toThrowIfFails); } public static Maybe getFieldValueMaybe(Object instance, String fieldName) { try { if (instance==null) return null; Field f = findField(instance.getClass(), fieldName); return getFieldValueMaybe(instance, f); } catch (Exception e) { Exceptions.propagateIfFatal(e); return Maybe.absent(e); } } public static Maybe getFieldValueMaybe(Object instance, Field field) { try { if (instance==null) return null; if (field==null) return null; field.setAccessible(true); return Maybe.of(field.get(instance)); } catch (Exception e) { Exceptions.propagateIfFatal(e); return Maybe.absent(e); } } /** Lists all public fields declared on the class or any ancestor, with those HIGHEST in the hierarchy first */ public static List findPublicFieldsOrderedBySuper(Class clazz) { return findFields(clazz, new Predicate() { @Override public boolean apply(Field input) { return Modifier.isPublic(input.getModifiers()); }}, FieldOrderings.SUB_BEST_FIELD_LAST_THEN_DEFAULT); } /** Lists all fields declared on the class, with those lowest in the hierarchy first, * filtered and ordered as requested. *

* See {@link ReflectionPredicates} and {@link FieldOrderings} for conveniences. *

* Default is no filter and {@link FieldOrderings#SUB_BEST_FIELD_LAST_THEN_ALPHABETICAL} * */ public static List findFields(final Class clazz, Predicate filter, Comparator fieldOrdering) { checkNotNull(clazz, "clazz"); MutableList.Builder result = MutableList.builder(); Stack> tovisit = new Stack>(); Set> 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()), filter!=null ? filter : Predicates.alwaysTrue())); } List resultList = result.build(); Collections.sort(resultList, fieldOrdering != null ? fieldOrdering : FieldOrderings.SUB_BEST_FIELD_LAST_THEN_ALPHABETICAL); 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 findPublicMethodsOrderedBySuper(Class clazz) { checkNotNull(clazz, "clazz"); MutableList.Builder result = MutableList.builder(); Stack> tovisit = new Stack>(); Set> 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() { @Override public boolean apply(Method input) { return Modifier.isPublic(input.getModifiers()); }})); } List resultList = result.build(); Collections.sort(resultList, new Comparator() { @Override public int compare(Method m1, Method m2) { Method msubbest = inferSubbestMethod(m1, m2); return (msubbest == null) ? 0 : (msubbest == m1 ? 1 : -1); }}); return resultList; } /** * If the classes of the fields satisfy {@link #inferSubbest(Class, Class)} * return the field in the lower (sub-best) class, otherwise null. */ 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 ? /* same field */ null : /* f1 from super */ f2) : (isSuper2 ? /* f2 from super of f1 */ f1 : /* fields are from different hierarchies */ null); } /** * If the classes of the methods satisfy {@link #inferSubbest(Class, Class)} * return the field in the lower (sub-best) class, otherwise null. */ 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); } /** * If one class is a subclass of the other, return that (the lower in the type hierarchy); * otherwise return null (if they are the same or neither is a subclass 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 cast(Object candidate, Class 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; } /** @deprecated since 0.10.0 use {@link #invokeMethodFromArgs(Object, String, List)}; * this allows null return values */ @Deprecated public static Optional invokeMethodWithArgs(Object clazzOrInstance, String method, List args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { return invokeMethodWithArgs(clazzOrInstance, method, args, false); } /** @deprecated since 0.10.0 use {@link #invokeMethodFromArgs(Object, String, List)} */ @Deprecated public static Optional invokeMethodWithArgs(Object clazzOrInstance, String method, List args, boolean setAccessible) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { return invokeMethodFromArgs(clazzOrInstance, method, args, setAccessible).toOptional(); } /** 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 Maybe invokeMethodFromArgs(Object clazzOrInstance, String method, List args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { return invokeMethodFromArgs(clazzOrInstance, method, args, false); } /** as {@link #invokeMethodFromArgs(Object, String, List)} but giving control over whether to set it accessible */ public static Maybe invokeMethodFromArgs(Object clazzOrInstance, String method, List args, boolean setAccessible) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { return invokeMethodFromArgs(clazzOrInstance, method, args, setAccessible, Optional.absent()); } /** as {@link #invokeMethodFromArgs(Object, String, List)} but giving control over whether to set it accessible */ public static Maybe invokeMethodFromArgs(Object clazzOrInstance, String method, List args, boolean setAccessible, Optional coercer) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { Maybe maybeMethod = getMethodFromArgs(clazzOrInstance, method, args); if (coercer.isPresent() && maybeMethod.isAbsent()) { maybeMethod = getMethodFromArgs(clazzOrInstance, method, args, coercer); } if (maybeMethod.isAbsent()) { return Maybe.absent(Maybe.getException(maybeMethod)); } Method m = maybeMethod.get(); return Maybe.of(invokeMethodFromArgs(clazzOrInstance, m, args, setAccessible, coercer)); } /** searches for the given method on the given clazz or instance, doing reasonably good matching on args etc */ public static Maybe getMethodFromArgs(Object clazzOrInstance, String method, List args) { return getMethodFromArgs(clazzOrInstance, method, args, Optional.absent()); } /** searches for the given method on the given clazz or instance, doing reasonably good matching on args etc */ public static Maybe getMethodFromArgs(Object clazzOrInstance, String method, List args, Optional coercer) { Preconditions.checkNotNull(clazzOrInstance, "clazz or instance"); Preconditions.checkNotNull(method, "method"); Preconditions.checkNotNull(args, "args to "+method); Class clazz; if (clazzOrInstance instanceof Class) { clazz = (Class)clazzOrInstance; } else { clazz = clazzOrInstance.getClass(); } 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, coercer)) { Class varargType = parameterTypes[parameterTypes.length-1].getComponentType(); boolean varargsMatch = true; for (int i=parameterTypes.length-1; i argTypes = Lists.newArrayList(); for (Object arg : args) { argTypes.add(arg == null ? "" : arg.getClass().getSimpleName()); } return Maybe.absent("Method '"+method+"' not found matching given args of type "+argTypes); } /** invokes the given method on the given clazz or instance, assuming that the method matches passed arguments * @throws InvocationTargetException * @throws IllegalAccessException * @throws IllegalArgumentException */ public static Object invokeMethodFromArgs(Object clazzOrInstance, Method m, List args) throws IllegalAccessException, InvocationTargetException { return invokeMethodFromArgs(clazzOrInstance, m, args, false); } /** as {@link #invokeMethodFromArgs(Object, Method, List)} but giving control over whether to set it accessible */ public static Object invokeMethodFromArgs(Object clazzOrInstance, Method m, List args, boolean setAccessible) throws IllegalAccessException, InvocationTargetException { return invokeMethodFromArgs(clazzOrInstance, m, args, setAccessible, Optional.absent()); } /** as {@link #invokeMethodFromArgs(Object, Method, List)} but giving control over whether to set it accessible */ public static Object invokeMethodFromArgs(Object clazzOrInstance, Method m, List args, boolean setAccessible, Optional coercer) throws IllegalAccessException, InvocationTargetException { Preconditions.checkNotNull(clazzOrInstance, "clazz or instance"); Preconditions.checkNotNull(m, "method"); Preconditions.checkNotNull(args, "args to "+m); Object instance; if (clazzOrInstance instanceof Class) { instance = null; } else { instance = clazzOrInstance; } Object[] argsArray = args.toArray(); Class[] parameterTypes = m.getParameterTypes(); if (m.isVarArgs()) { Class varargType = parameterTypes[parameterTypes.length-1].getComponentType(); Object varargs = Array.newInstance(varargType, argsArray.length+1 - parameterTypes.length); for (int i=parameterTypes.length-1; i[] parameterTypes) { return typesMatch(argsArray, parameterTypes, Optional.absent()); } /** true iff all args match the corresponding types */ public static boolean typesMatch(Object[] argsArray, Class[] parameterTypes, Optional coercer) { if (argsArray.length != parameterTypes.length) return false; return typesMatchUpTo(argsArray, parameterTypes, argsArray.length, coercer); } /** true iff the initial N args match the corresponding types */ public static boolean typesMatchUpTo(Object[] argsArray, Class[] parameterTypes, int lengthRequired) { return typesMatchUpTo(argsArray, parameterTypes, lengthRequired, Optional.absent()); } /** true iff the initial N args match the corresponding types */ public static boolean typesMatchUpTo(Object[] argsArray, Class[] parameterTypes, int lengthRequired, Optional coercer) { if (argsArray.length < lengthRequired || parameterTypes.length < lengthRequired) return false; for (int i=0; i parameterType, Optional coercer) { if (parameterType.isPrimitive() && arg == null) return false; if (arg == null) return true; if (Boxing.boxedType(parameterType).isInstance(arg)) return true; if (coercer.isPresent() && coercer.get().tryCoerce(arg, parameterType).isPresent()) return true; return false; } /** * 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> getAllInterfaces(@Nullable Class type) { Set> 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> 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 clazz) { if (Object.class.equals(clazz)) return true; if (clazz.getDeclaredFields().length>0) return false; return hasNoNonObjectFields(clazz.getSuperclass()); } /** @deprecated since 0.10.0 use {@link #findMappedNameMaybe(Map, String)} */ @Deprecated public static Optional tryFindMappedName(Map renames, String name) { return findMappedNameMaybe(renames, name).toOptional(); } /** Takes a map of old-class-names to renames classes, and returns the mapped name if matched, or absent */ public static Maybe findMappedNameMaybe(Map renames, String name) { if (renames==null) return Maybe.absent("no renames supplied"); String mappedName = renames.get(name); if (mappedName != null) { return Maybe.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 Maybe.of(mappedName + name.substring(name.indexOf('$'))); } } return Maybe.absent("mapped name not present"); } public static String findMappedNameAndLog(Map renames, String name) { Maybe rename = Reflections.findMappedNameMaybe(renames, name); if (rename.isPresent()) { LOG.debug("Mapping class '"+name+"' to '"+rename.get()+"'"); return rename.get(); } return name; } public static boolean hasSpecialSerializationMethods(Class type) { if (type==null) return false; if (findMethodMaybe(type, "writeObject", java.io.ObjectOutputStream.class).isPresent()) return true; return hasSpecialSerializationMethods(type.getSuperclass()); } public static List arrayToList(Object input) { // We can't just use Arrays.asList(), because that would return a list containing the single // value "input". List result = new ArrayList<>(); int length = Array.getLength(input); for (int i = 0; i < length; i++) { result.add(Array.get(input, i)); } return result; } /** * Attempts to find an equivalent accessible method to be invoked (or if the given method is * already accessible, then return it). Otherwise return absent. * * "Accessible" means that it is a public method declared on a public type. * * For example, if {@code method} is declared on a private sub-class, but that overides a * method declared on a public super-class, then this method will return the {@link Method} * instance for the public super-class (assuming the method is not static). * * If no better method could be found, it does a log.warn (once per method signature, per * jvm-invocation), and then returns absent. */ public static Maybe findAccessibleMethod(Method method) { return MethodAccessibleReflections.findAccessibleMethod(method); } /** * Calls {@link Method#setAccessible(boolean)} "safely", wrapping in a try-catch block so that * the exception is never propagated. *

* It will log.warn once per method signature for which we fail to set it accessible. It will * also log.warn if it succeeds (once per method signature) as this is discouraged! * * @return True if setAccessible succeeded; false otherwise */ public static boolean trySetAccessible(Method method) { return MethodAccessibleReflections.trySetAccessible(method); } public static TypeToken[] getGenericParameterTypeTokens(TypeToken t) { Class rawType = t.getRawType(); TypeVariable[] pT = rawType.getTypeParameters(); TypeToken pTT[] = new TypeToken[pT.length]; for (int i=0; i