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

org.xmlbeam.util.intern.ReflectionHelper Maven / Gradle / Ivy

Go to download

The coolest XML library for Java around. Define typesafe views (projections) to xml. Use XPath to read and write XML. Bind XML to Java collections. Requires at least Java6, supports Java8 features and has no further runtime dependencies.

There is a newer version: 1.4.24
Show newest version
/**
 *  Copyright 2013 Sven Ewald
 *
 *  Licensed 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.xmlbeam.util.intern;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.regex.Pattern;

/**
 * A set of tiny helper methods internally used in the projection framework. This methods are
 * not part of the public framework API and might change in minor version updates.
 *
 * @author Sven Ewald
 */
public final class ReflectionHelper {

    private final static Method ISDEFAULT = findMethodByName(Method.class, "isDefault");
    private final static Class OPTIONAL_CLASS = findClass("java.util.Optional");
    private final static Method OFNULLABLE = findMethodByName(OPTIONAL_CLASS, "ofNullable");
    private final static Method GETPARAMETERS = findMethodByName(Method.class, "getParameters");
    private final static int PUBLIC_STATIC_MODIFIER = Modifier.STATIC | Modifier.PUBLIC;
    private final static Pattern VALID_FACTORY_METHOD_NAMES = Pattern.compile("valueOf|of|parse|getInstance");
    private final static Method STREAM = findMethodByName(List.class, "stream");

    private static Class findClass(String name) {
        try {
            return Class.forName(name, false, ReflectionHelper.class.getClassLoader());
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    /**
     * @param a
     * @param b
     * @return a set of all interfaces that a extends and b extends
     */
    public static Set> findAllCommonSuperInterfaces(final Class a, final Class b) {
        final Set> seta = new HashSet>(findAllSuperInterfaces(a));
        final Set> setb = new HashSet>(findAllSuperInterfaces(b));
        seta.retainAll(setb);
        return seta;
    }

    /**
     * Non exception throwing shortcut to find the first method with a given name.
     *
     * @param clazz
     * @param name
     * @return method with name "name" or null if it does not exist.
     */
    public static Method findMethodByName(final Class clazz, final String name) {
        if (clazz == null) {
            return null;
        }
        for (final Method m : clazz.getMethods()) {
            if (name.equals(m.getName())) {
                return m;
            }
        }
        return null;
    }

    /**
     * Non exception throwing shortcut to find the first method with a given name and parameter
     * types.
     *
     * @param clazz
     * @param name
     * @param paramTypes
     * @return method with name "name" and parameters or null if it does not exist.
     */
    public static Method findMethodByNameAndParams(final Class clazz, final String name, final Class[] paramTypes) {
        for (final Method m : clazz.getMethods()) {
            if (!name.equals(m.getName())) {
                continue;
            }
            if (!Arrays.equals(m.getParameterTypes(), paramTypes)) {
                continue;
            }
            return m;
        }
        return null;
    }

    /**
     * Returns list of super interfaces,sorted from the top (super) to the bottom (extended).
     *
     * @param a
     * @return a set of all super interfaces of a
     */
    public static List> findAllSuperInterfaces(final Class a) {
        LinkedList> list = new LinkedList>();
        Queue> queue = new LinkedList>();
        queue.add(a);
        while (!queue.isEmpty()) {
            Class c = queue.poll();
            if (c.isInterface()) {
                list.addFirst(c);
            }
            queue.addAll(Arrays.asList(c.getInterfaces()));
        }
        return list;
    };

    /**
     * @param c
     * @return list of all extended classes and implemented interfaces
     */
    public static List> findAllSuperClasses(final Class c) {
        if (c == null) {
            return Collections.emptyList();
        }
        if (c.isInterface()) {
            return new LinkedList>(findAllSuperInterfaces(c));
        }
        LinkedList> superclasses = new LinkedList>(findAllSuperClasses(c.getSuperclass()));
        superclasses.addFirst(c);
        return superclasses;
    }

    /**
     * Defensive implemented method to determine if method has a return type.
     *
     * @param method
     * @return true if and only if it is not a void method.
     */
    public static boolean hasReturnType(final Method method) {
        if (method == null) {
            return false;
        }
        if (method.getReturnType() == null) {
            return false;
        }
        if (Void.class.equals(method.getReturnType())) {
            return false;
        }
        return !Void.TYPE.equals(method.getReturnType());
    }

    /**
     * @param method
     * @return true if and only if the method has at least one parameter.
     */
    public static boolean hasParameters(final Method method) {
        return (method != null) && (method.getParameterTypes().length > 0);
    }

    /**
     * @param method
     * @param projectionInterface
     * @return lowest type in hierarchy that defines the given method
     */
    public static Class findDeclaringInterface(final Method method, final Class projectionInterface) {
        for (final Class interf : findAllSuperInterfaces(projectionInterface)) {
            if (declaresMethod(interf, method)) {
                return interf;
            }
        }
        return method.getDeclaringClass();
    }

    /**
     * @param interf
     * @param method
     * @return
     */
    private static boolean declaresMethod(final Class interf, final Method method) {
        for (final Method m : interf.getDeclaredMethods()) {
            if (!m.getName().equals(method.getName())) {
                continue;
            }
            if (!Arrays.equals(m.getParameterTypes(), method.getParameterTypes())) {
                continue;
            }
            return true;
        }
        return false;
    }

    /**
     * Same as Arrays.asList(...), but does automatically conversion of primitive arrays.
     *
     * @param array
     * @return List of objects representing the given array contents
     */
    public static List array2ObjectList(final Object array) {
        final int length = Array.getLength(array);
        final List list = new ArrayList(length);
        for (int i = 0; i < length; ++i) {
            list.add(Array.get(array, i));
        }
        return list;
    }

    /**
     * @param projectionInterface
     * @return a LinkedList containing all non default methods for the given projection interface.
     */
//    public static List getNonDefaultMethods(final Class projectionInterface) {
//        final List list = new LinkedList();
//        for (final Method m : projectionInterface.getMethods()) {
//            if (isDefaultMethod(m)) {
//                continue;
//            }
//            list.add(m);
//        }
//        return list;
//    }

    /**
     * @param m
     * @return true if the given method is a Java 8 default method
     */
    public static boolean isDefaultMethod(final Method m) {
        try {
            return (ISDEFAULT != null) && ((Boolean) ISDEFAULT.invoke(m, (Object[]) null));
        } catch (final IllegalArgumentException e) {
            throw new RuntimeException(e);
        } catch (final IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (final InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Try to determine the names of the method parameters. Does not work on pre Java 8 jdk and
     * given method owner has to be compiled with "-parameters" option. NOTICE: The correct function
     * depends on the JVM implementation.
     *
     * @param m
     * @return Empty list if no parameters present or names could not be determined. List of
     *         parameter names else.
     */
    public static Map getMethodParameterIndexes(final Method m) {
        if ((GETPARAMETERS == null) || (m == null)) {
            return Collections.emptyMap();
        }
        Map paramNames = new HashMap();
        try {
            Object[] params = (Object[]) GETPARAMETERS.invoke(m);
            if (params.length == 0) {
                return Collections.emptyMap();
            }
            Method getName = findMethodByName(params[0].getClass(), "getName");
            if (getName == null) {
                return Collections.emptyMap();
            }
            int i = -1;
            for (Object o : params) {
                ++i;
                String name = (String) getName.invoke(o);
                if (name == null) {
                    continue;
                }
                paramNames.put(name.toUpperCase(Locale.ENGLISH), i);
            }
            return Collections.unmodifiableMap(paramNames);
        } catch (IllegalArgumentException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Search for a suitable constructor which is invokable by the given parameter types. Similar to
     * {@code Class.getConstructor(...)}, but does not require parameter equality and does not throw
     * exceptions.
     *
     * @param type
     * @param params
     * @return constructor or null if no matching constructor was found.
     */
    @SuppressWarnings("unchecked")
    public static  Constructor getCallableConstructorForParams(final Class type, final Class... params) {
        for (Constructor c : (Constructor[]) type.getConstructors()) {
            final Class[] parameterTypes = c.getParameterTypes();
            if (!Arrays.equals(parameterTypes, params)) {
                continue;
            }
            return c;
        }
        return null;
    }

    /**
     * Search for a static factory method returning the target type.
     *
     * @param type
     * @param params
     * @return factory method or null if it is not found.
     */
    public static Method getCallableFactoryForParams(final Class type, final Class... params) {
        for (Method m : type.getMethods()) {
            if ((m.getModifiers() & PUBLIC_STATIC_MODIFIER) != PUBLIC_STATIC_MODIFIER) {
                continue;
            }
            if (!type.isAssignableFrom(m.getReturnType())) {
                continue;
            }
            if (!Arrays.equals(m.getParameterTypes(), params)) {
                continue;
            }
            if (!VALID_FACTORY_METHOD_NAMES.matcher(m.getName()).matches()) {
                continue;
            }
            return m;
        }
        return null;
    }

    /**
     * @return true if and only if the current runtime may provide parameter names
     */
    public static boolean mayProvideParameterNames() {
        return GETPARAMETERS != null;
    }

    /**
     * Unwrap a given object until we assume it is a value. Supports Callable and Supplier so far.
     *
     * @param type
     * @param object
     * @return object if it's a value. Unwrapped object if its a Callable or a Supplier
     * @throws Exception
     *             may be thrown by given objects method
     */
    public static Object unwrap(final Class type, final Object object) throws Exception {
        if (object == null) {
            return null;
        }
        if (type == null) {
            return object;
        }
        if (Callable.class.equals(type)) {
            assert (object instanceof Callable);
            return ((Callable) object).call();
        }

        if ("java.util.function.Supplier".equals(type.getName())) {
            return findMethodByName(type, "get").invoke(object, (Object[]) null);
        }

        return object;
    }

    /**
     * @param type
     * @return generic parameter type if is optional, else type
     */
    public static Class getParameterType(final Type type) {
//        if (!isOptional(type)) {
//            return (Class) type;
//        }
        assert type instanceof ParameterizedType;
        assert ((ParameterizedType) type).getActualTypeArguments().length == 1;
        return (Class) ((ParameterizedType) type).getActualTypeArguments()[0];
    }

    /**
     * Checks if given type is java.util.Optional with a generic type parameter.
     *
     * @param type
     * @return true if and only if type is a parameterized Optional
     */
    public static boolean isOptional(final Type type) {
        if (OPTIONAL_CLASS == null) {
            return false;
        }

        if (!(type instanceof ParameterizedType)) {
            // Either this is no Optional, or it's a raw optional which is useless for us anyway...
            return false;
        }

        return OPTIONAL_CLASS.equals(((ParameterizedType) type).getRawType());
    }

    /**
     * Checks if a given type is a raw type.
     *
     * @param type
     * @return true if and only if the type may be parameterized but has no parameter type.
     */
    public static boolean isRawType(final Type type) {
        if (type instanceof Class) {
            final Class clazz = (Class) type;
            return (clazz.getTypeParameters().length > 0);
        }
        if (type instanceof ParameterizedType) {
            final ParameterizedType ptype = (ParameterizedType) type;
            return (ptype.getActualTypeArguments().length == 0);
        }
        return false;
    }

    /**
     * Create an instance of java.util.Optional for given value.
     *
     * @param value
     * @return a new instance of Optional
     */
    public static Object createOptional(final Object value) {
        if ((OPTIONAL_CLASS == null) || (OFNULLABLE == null)) {
            throw new IllegalStateException("Unreachable Code executed. You just found a bug. Please report!");
        }
        try {
            return OFNULLABLE.invoke(null, value);
        } catch (IllegalArgumentException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @param method
     * @param args
     * @param proxy
     * @return return value of invoked method
     * @throws Throwable
     *             (whatever the invoked method throws)
     */
    public static Object invokeDefaultMethod(final Method method, final Object[] args, final Object proxy) throws Throwable {
        try {
            Class MHclass = Class.forName("java.lang.invoke.MethodHandles");
            Object lookup = MHclass.getMethod("lookup", (Class[]) null).invoke(null, (Object[]) null);

            Constructor constructor = lookup.getClass().getDeclaredConstructor(Class.class);
            constructor.setAccessible(true);
            Object newLookupInstance = constructor.newInstance(method.getDeclaringClass());

            Object in = newLookupInstance.getClass().getMethod("in", new Class[] { Class.class }).invoke(newLookupInstance, method.getDeclaringClass());

            Object unreflectSpecial = in.getClass().getMethod("unreflectSpecial", new Class[] { Method.class, Class.class }).invoke(in, method, method.getDeclaringClass());
            Object bindTo = unreflectSpecial.getClass().getMethod("bindTo", Object.class).invoke(unreflectSpecial, proxy);
            try {
                Object result = bindTo.getClass().getMethod("invokeWithArguments", Object[].class).invoke(bindTo, new Object[] { args });
                return result;
            } catch (InvocationTargetException e) {
                if (e.getCause() != null) {
                    throw e.getCause();
                }
                throw e;
            }
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (IllegalArgumentException e) {
            throw new RuntimeException(e);
        } catch (SecurityException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Throws a throwable of type throwableType. The throwable will be created using an args
     * matching constructor or the default constructor if no matching constructor can be found.
     *
     * @param throwableType
     *            type of throwable to be thrown
     * @param args
     *            for the throwable construction
     * @param optionalCause
     *            cause to be set, may be null
     * @throws Throwable
     */
    public static void throwThrowable(final Class throwableType, final Object[] args, final Throwable optionalCause) throws Throwable {
        Class[] argsClasses = getClassesOfObjects(args);
        Constructor constructor = ReflectionHelper.getCallableConstructorForParams(throwableType, argsClasses);
        Throwable throwable = null;
        if (constructor != null) {
            throwable = (Throwable) constructor.newInstance(args);
        } else {
            throwable = (Throwable) throwableType.newInstance();
        }
        if (optionalCause != null) {
            throwable.initCause(optionalCause);
        }
        throw throwable;

    }

    private static Class[] getClassesOfObjects(final Object... objects) {
        Class[] classes = new Class[objects.length];
        for (int i = 0; i < objects.length; ++i) {
            classes[i] = objects[i].getClass();
        }
        return classes;
    }

    private static class ClassContextAccess extends SecurityManager {
        private static final ThreadLocal classContextAccess = new ThreadLocal() {
            @Override
            protected ClassContextAccess initialValue() {
                return new ClassContextAccess();
            }
        };

        /**
         * @param level
         * @return class of caller method.
         */
        public Class getCallerClass(final int level) {
            return getClassContext()[level];
        }

    }

    /**
     * @param level
     * @return Class of calling method
     */
    public static Class getCallerClass(final int level) {
        return ClassContextAccess.classContextAccess.get().getCallerClass(level + 1);
    }

    /**
     * @return Class of calling method
     */
    public static Class getDirectCallerClass() {
        return ClassContextAccess.classContextAccess.get().getCallerClass(3);
    }

    /**
     * @param returnType
     * @return true, if (and only if) class is "java.util.stream.Stream"
     */
    public static boolean isStreamClass(Class returnType) {
        return "java.util.stream.Stream".equals(returnType.getName());
    }

    /**
     * @param result
     * @return List.stream()
     */
    public static Object toStream(List result) {
        if (STREAM == null) {
            throw new IllegalArgumentException("Can not invoke List.stream, you need at least a JDK8 to run this");
        }
        try {
            return STREAM.invoke(result);
        } catch (IllegalArgumentException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

//    /**
//     * @param m
//     * @return list of methods overridden by given method
//     */
//    public static List findAllOverridenMethods(final Method m) {
//        List methods = new LinkedList();
//        for (Class c : findAllSuperClasses(m.getDeclaringClass())) {
//            Method method = classImplementsMethdod(c, m);
//            if (method != null) {
//                methods.add(method);
//            }
//        }
//        return methods;
//    }
//
//    /**
//     * @param c
//     * @param m
//     * @return
//     */
//    private static Method classImplementsMethdod(final Class c, final Method m) {
//        return findMethodByNameAndParams(c, m.getName(), m.getParameterTypes());
//    }
}