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

nonapi.io.github.classgraph.utils.ReflectionUtils Maven / Gradle / Ivy

/*
 * This file is part of ClassGraph.
 *
 * Author: Luke Hutchison
 *
 * Hosted at: https://github.com/classgraph/classgraph
 *
 * --
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Luke Hutchison
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without
 * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
 * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
 * OR OTHER DEALINGS IN THE SOFTWARE.
 */
package nonapi.io.github.classgraph.utils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/** Reflection utility methods that can be used by ClassLoaderHandlers. */
public final class ReflectionUtils {

    // In JDK 9+, could use MethodHandles.privateLookupIn
    // And then use getter lookup to get fields (which works even if there is no getter function defined):
    // https://stackoverflow.com/q/19135218/3950982

    /**
     * Constructor.
     */
    private ReflectionUtils() {
        // Cannot be constructed
    }

    /**
     * Get the value of the named field in the class of the given object or any of its superclasses. If an exception
     * is thrown while trying to read the field, and throwException is true, then IllegalArgumentException is thrown
     * wrapping the cause, otherwise this will return null. If passed a null object, returns null unless
     * throwException is true, then throws IllegalArgumentException.
     *
     * @param cls
     *            The class.
     * @param obj
     *            The object, or null to get the value of a static field.
     * @param fieldName
     *            The field name.
     * @param throwException
     *            If true, throw an exception if the field value could not be read.
     * @return The field value.
     * @throws IllegalArgumentException
     *             If the field value could not be read.
     */
    private static Object getFieldVal(final Class cls, final Object obj, final String fieldName,
            final boolean throwException) throws IllegalArgumentException {
        Field field = null;
        for (Class currClass = cls; currClass != null; currClass = currClass.getSuperclass()) {
            try {
                field = currClass.getDeclaredField(fieldName);
                // Field found
                break;
            } catch (final ReflectiveOperationException | SecurityException e) {
                // Try parent
            }
        }
        if (field == null) {
            if (throwException) {
                throw new IllegalArgumentException((obj == null ? "Static field " : "Field ") + "\"" + fieldName
                        + "\" not found or not accessible");
            }
        } else {
            try {
                field.setAccessible(true);
            } catch (final RuntimeException e) { // JDK 9+: InaccessibleObjectException | SecurityException
                // Ignore
            }
            try {
                return field.get(obj);
            } catch (final IllegalAccessException e) {
                if (throwException) {
                    throw new IllegalArgumentException(
                            "Can't read " + (obj == null ? "static " : "") + " field \"" + fieldName + "\": " + e);
                }
            }
        }
        return null;
    }

    /**
     * Get the value of the named field in the class of the given object or any of its superclasses. If an exception
     * is thrown while trying to read the field, and throwException is true, then IllegalArgumentException is thrown
     * wrapping the cause, otherwise this will return null. If passed a null object, returns null unless
     * throwException is true, then throws IllegalArgumentException.
     * 
     * @param obj
     *            The object.
     * @param fieldName
     *            The field name.
     * @param throwException
     *            If true, throw an exception if the field value could not be read.
     * @return The field value.
     * @throws IllegalArgumentException
     *             If the field value could not be read.
     */
    public static Object getFieldVal(final Object obj, final String fieldName, final boolean throwException)
            throws IllegalArgumentException {
        if (obj == null || fieldName == null) {
            if (throwException) {
                throw new NullPointerException();
            } else {
                return null;
            }
        }
        return getFieldVal(obj.getClass(), obj, fieldName, throwException);
    }

    /**
     * Get the value of the named static field in the given class or any of its superclasses. If an exception is
     * thrown while trying to read the field value, and throwException is true, then IllegalArgumentException is
     * thrown wrapping the cause, otherwise this will return null. If passed a null class reference, returns null
     * unless throwException is true, then throws IllegalArgumentException.
     * 
     * @param cls
     *            The class.
     * @param fieldName
     *            The field name.
     * @param throwException
     *            If true, throw an exception if the field value could not be read.
     * @return The field value.
     * @throws IllegalArgumentException
     *             If the field value could not be read.
     */
    public static Object getStaticFieldVal(final Class cls, final String fieldName, final boolean throwException)
            throws IllegalArgumentException {
        if (cls == null || fieldName == null) {
            if (throwException) {
                throw new NullPointerException();
            } else {
                return null;
            }
        }
        return getFieldVal(cls, null, fieldName, throwException);
    }

    /**
     * Iterate through implemented interfaces, top-down, then superclass to subclasses, top-down (since higher-up
     * superclasses and superinterfaces have the highest chance of being visible).
     *
     * @param cls
     *            the class
     * @return the reverse of the order in which method calls would be attempted by the JRE.
     */
    private static List> getReverseMethodAttemptOrder(final Class cls) {
        final List> reverseAttemptOrder = new ArrayList<>();

        // Iterate from class to its superclasses
        for (Class c = cls; c != null && c != Object.class; c = c.getSuperclass()) {
            reverseAttemptOrder.add(c);
        }

        // Find interfaces and superinterfaces implemented by this class or its superclasses
        final Set> addedIfaces = new HashSet<>();
        final LinkedList> ifaceQueue = new LinkedList<>();
        for (Class c = cls; c != null; c = c.getSuperclass()) {
            if (c.isInterface() && addedIfaces.add(c)) {
                ifaceQueue.add(c);
            }
            for (final Class iface : c.getInterfaces()) {
                if (addedIfaces.add(iface)) {
                    ifaceQueue.add(iface);
                }
            }
        }
        while (!ifaceQueue.isEmpty()) {
            final Class iface = ifaceQueue.remove();
            reverseAttemptOrder.add(iface);
            final Class[] superIfaces = iface.getInterfaces();
            if (superIfaces.length > 0) {
                for (final Class superIface : superIfaces) {
                    if (addedIfaces.add(superIface)) {
                        ifaceQueue.add(superIface);
                    }
                }
            }
        }
        return reverseAttemptOrder;
    }

    /**
     * Invoke the named method in the given object or its superclasses. If an exception is thrown while trying to
     * call the method, and throwException is true, then IllegalArgumentException is thrown wrapping the cause,
     * otherwise this will return null. If passed a null object, returns null unless throwException is true, then
     * throws IllegalArgumentException.
     *
     * @param cls
     *            The class.
     * @param obj
     *            The object, or null to invoke a static method.
     * @param methodName
     *            The method name.
     * @param oneArg
     *            If true, look for a method with one argument of type argType. If false, look for method with no
     *            arguments.
     * @param argType
     *            The type of the first argument to the method.
     * @param param
     *            The value of the first parameter to invoke the method with.
     * @param throwException
     *            If true, throw an exception if the field value could not be read.
     * @return The field value.
     * @throws IllegalArgumentException
     *             If the field value could not be read.
     */
    private static Object invokeMethod(final Class cls, final Object obj, final String methodName,
            final boolean oneArg, final Class argType, final Object param, final boolean throwException)
            throws IllegalArgumentException {
        Method method = null;
        final List> reverseAttemptOrder = getReverseMethodAttemptOrder(cls);
        for (int i = reverseAttemptOrder.size() - 1; i >= 0; i--) {
            final Class classOrInterface = reverseAttemptOrder.get(i);
            try {
                method = oneArg ? classOrInterface.getDeclaredMethod(methodName, argType)
                        : classOrInterface.getDeclaredMethod(methodName);
                // Method found
                break;
            } catch (final ReflectiveOperationException | SecurityException e) {
                // Try next interface or superclass 
            }
        }
        if (method == null) {
            if (throwException) {
                throw new IllegalArgumentException((obj == null ? "Static method " : "Method ") + "\"" + methodName
                        + "\" not found or not accesible");
            }
        } else {
            try {
                method.setAccessible(true);
            } catch (final RuntimeException e) { // JDK 9+: InaccessibleObjectException | SecurityException
                // Ignore
            }
            try {
                return oneArg ? method.invoke(obj, param) : method.invoke(obj);
            } catch (final IllegalAccessException e) {
                if (throwException) {
                    throw new IllegalArgumentException(
                            "Can't call " + (obj == null ? "static " : "") + "method \"" + methodName + "\": " + e);
                }
            } catch (final InvocationTargetException e) {
                if (throwException) {
                    throw new IllegalArgumentException("Exception while invoking " + (obj == null ? "static " : "")
                            + "method \"" + methodName + "\"", e);
                }
            }
        }
        return null;
    }

    /**
     * Invoke the named method in the given object or its superclasses. If an exception is thrown while trying to
     * call the method, and throwException is true, then IllegalArgumentException is thrown wrapping the cause,
     * otherwise this will return null. If passed a null object, returns null unless throwException is true, then
     * throws IllegalArgumentException.
     * 
     * @param obj
     *            The object.
     * @param methodName
     *            The method name.
     * @param throwException
     *            If true, throw an exception if the field value could not be read.
     * @return The field value.
     * @throws IllegalArgumentException
     *             If the field value could not be read.
     */
    public static Object invokeMethod(final Object obj, final String methodName, final boolean throwException)
            throws IllegalArgumentException {
        if (obj == null || methodName == null) {
            if (throwException) {
                throw new NullPointerException();
            } else {
                return null;
            }
        }
        return invokeMethod(obj.getClass(), obj, methodName, false, null, null, throwException);
    }

    /**
     * Invoke the named method in the given object or its superclasses. If an exception is thrown while trying to
     * call the method, and throwException is true, then IllegalArgumentException is thrown wrapping the cause,
     * otherwise this will return null. If passed a null object, returns null unless throwException is true, then
     * throws IllegalArgumentException.
     * 
     * @param obj
     *            The object.
     * @param methodName
     *            The method name.
     * @param argType
     *            The type of the method argument.
     * @param param
     *            The parameter value to use when invoking the method.
     * @param throwException
     *            Whether to throw an exception on failure.
     * @return The result of the method invocation.
     * @throws IllegalArgumentException
     *             If the method could not be invoked.
     */
    public static Object invokeMethod(final Object obj, final String methodName, final Class argType,
            final Object param, final boolean throwException) throws IllegalArgumentException {
        if (obj == null || methodName == null) {
            if (throwException) {
                throw new NullPointerException();
            } else {
                return null;
            }
        }
        return invokeMethod(obj.getClass(), obj, methodName, true, argType, param, throwException);
    }

    /**
     * Invoke the named static method. If an exception is thrown while trying to call the method, and throwException
     * is true, then IllegalArgumentException is thrown wrapping the cause, otherwise this will return null. If
     * passed a null class reference, returns null unless throwException is true, then throws
     * IllegalArgumentException.
     * 
     * @param cls
     *            The class.
     * @param methodName
     *            The method name.
     * @param throwException
     *            Whether to throw an exception on failure.
     * @return The result of the method invocation.
     * @throws IllegalArgumentException
     *             If the method could not be invoked.
     */
    public static Object invokeStaticMethod(final Class cls, final String methodName,
            final boolean throwException) throws IllegalArgumentException {
        if (cls == null || methodName == null) {
            if (throwException) {
                throw new NullPointerException();
            } else {
                return null;
            }
        }
        return invokeMethod(cls, null, methodName, false, null, null, throwException);
    }

    /**
     * Invoke the named static method. If an exception is thrown while trying to call the method, and throwException
     * is true, then IllegalArgumentException is thrown wrapping the cause, otherwise this will return null. If
     * passed a null class reference, returns null unless throwException is true, then throws
     * IllegalArgumentException.
     * 
     * @param cls
     *            The class.
     * @param methodName
     *            The method name.
     * @param argType
     *            The type of the method argument.
     * @param param
     *            The parameter value to use when invoking the method.
     * @param throwException
     *            Whether to throw an exception on failure.
     * @return The result of the method invocation.
     * @throws IllegalArgumentException
     *             If the method could not be invoked.
     */
    public static Object invokeStaticMethod(final Class cls, final String methodName, final Class argType,
            final Object param, final boolean throwException) throws IllegalArgumentException {
        if (cls == null || methodName == null) {
            if (throwException) {
                throw new NullPointerException();
            } else {
                return null;
            }
        }
        return invokeMethod(cls, null, methodName, true, argType, param, throwException);
    }

    /**
     * Call Class.forName(className), but return null if any exception is thrown.
     * 
     * @param className
     *            The class name to load.
     * @return The class of the requested name, or null if an exception was thrown while trying to load the class.
     */
    public static Class classForNameOrNull(final String className) {
        try {
            return Class.forName(className);
        } catch (final ReflectiveOperationException | LinkageError e) {
            return null;
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy