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

io.microsphere.reflect.MethodUtils Maven / Gradle / Ivy

The 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 io.microsphere.reflect;

import io.microsphere.logging.Logger;
import io.microsphere.util.ArrayUtils;
import io.microsphere.util.BaseUtils;

import javax.annotation.Nullable;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Predicate;

import static io.microsphere.collection.SetUtils.of;
import static io.microsphere.constants.SymbolConstants.COMMA_CHAR;
import static io.microsphere.constants.SymbolConstants.LEFT_PARENTHESIS_CHAR;
import static io.microsphere.constants.SymbolConstants.RIGHT_PARENTHESIS_CHAR;
import static io.microsphere.constants.SymbolConstants.SHARP_CHAR;
import static io.microsphere.lang.function.Streams.filterAll;
import static io.microsphere.logging.LoggerFactory.getLogger;
import static io.microsphere.reflect.AccessibleObjectUtils.trySetAccessible;
import static io.microsphere.reflect.MemberUtils.isPrivate;
import static io.microsphere.reflect.MemberUtils.isStatic;
import static io.microsphere.reflect.MethodUtils.MethodKey.buildKey;
import static io.microsphere.text.FormatUtils.format;
import static io.microsphere.util.AnnotationUtils.CALLER_SENSITIVE_ANNOTATION_CLASS;
import static io.microsphere.util.AnnotationUtils.isAnnotationPresent;
import static io.microsphere.util.ArrayUtils.EMPTY_CLASS_ARRAY;
import static io.microsphere.util.ClassUtils.getAllInheritedTypes;
import static io.microsphere.util.ClassUtils.getTypeName;
import static io.microsphere.util.ClassUtils.getTypes;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;

/**
 * The Java Reflection {@link Method} Utility class
 *
 * @author Mercy
 * @since 1.0.0
 */
public abstract class MethodUtils extends BaseUtils {

    private static final Logger logger = getLogger(MethodUtils.class);

    /**
     * The {@link Predicate} reference to {@link MethodUtils#isObjectMethod(Method)}
     */
    public final static Predicate OBJECT_METHOD_PREDICATE = MethodUtils::isObjectMethod;

    public final static Set OBJECT_METHODS = of(Object.class.getMethods());

    private final static ConcurrentMap methodsCache = new ConcurrentHashMap<>();

    static class MethodKey {

        private final Class declaredClass;

        private final String methodName;

        private final Class[] parameterTypes;

        MethodKey(Class declaredClass, String methodName, Class[] parameterTypes) {
            this.declaredClass = declaredClass;
            this.methodName = methodName;
            this.parameterTypes = parameterTypes == null ? EMPTY_CLASS_ARRAY : parameterTypes;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            MethodKey methodKey = (MethodKey) o;

            if (!Objects.equals(declaredClass, methodKey.declaredClass)) return false;
            if (!Objects.equals(methodName, methodKey.methodName)) return false;
            // Probably incorrect - comparing Object[] arrays with Arrays.equals
            return Arrays.equals(parameterTypes, methodKey.parameterTypes);
        }

        @Override
        public int hashCode() {
            int result = declaredClass != null ? declaredClass.hashCode() : 0;
            result = 31 * result + (methodName != null ? methodName.hashCode() : 0);
            result = 31 * result + Arrays.hashCode(parameterTypes);
            return result;
        }

        @Override
        public String toString() {
            StringJoiner stringJoiner = new StringJoiner(",", "(", ") ");
            ArrayUtils.forEach(parameterTypes, parameterType -> stringJoiner.add(getTypeName(parameterType)));
            return getTypeName(declaredClass) + "#" + methodName + stringJoiner;
        }

        static MethodKey buildKey(Class declaredClass, String methodName, Class[] parameterTypes) {
            return new MethodKey(declaredClass, methodName, parameterTypes);
        }
    }

    /**
     * Create an instance of {@link Predicate} for {@link Method} to exclude the specified declared class
     *
     * @param declaredClass the declared class to exclude
     * @return non-null
     */
    public static Predicate excludedDeclaredClass(Class declaredClass) {
        return method -> !Objects.equals(declaredClass, method.getDeclaringClass());
    }

    /**
     * Get all {@link Method methods} of the declared class
     *
     * @param declaringClass        the declared class
     * @param includeInheritedTypes include the inherited types, e,g. super classes or interfaces
     * @param publicOnly            only public method
     * @param methodsToFilter       (optional) the methods to be filtered
     * @return non-null read-only {@link List}
     */
    public static List getMethods(Class declaringClass, boolean includeInheritedTypes, boolean publicOnly, Predicate... methodsToFilter) {

        if (declaringClass == null || declaringClass.isPrimitive()) {
            return emptyList();
        }

        // All declared classes
        List> declaredClasses = new LinkedList<>();
        // Add the top declaring class
        declaredClasses.add(declaringClass);
        // If the super classes are resolved, all them into declaredClasses
        if (includeInheritedTypes) {
            declaredClasses.addAll(getAllInheritedTypes(declaringClass));
        }

        // All methods
        List allMethods = new LinkedList<>();

        for (Class classToSearch : declaredClasses) {
            // Method[] methods = publicOnly ? classToSearch.getMethods() : classToSearch.getDeclaredMethods();
            Method[] methods = classToSearch.getDeclaredMethods();
            // Add the declared methods or public methods
            for (Method method : methods) {
                if (publicOnly) {
                    if (Modifier.isPublic(method.getModifiers())) {
                        allMethods.add(method);
                    }
                } else {
                    allMethods.add(method);
                }
            }
        }

        return unmodifiableList(filterAll(allMethods, methodsToFilter));
    }

    /**
     * Get all declared {@link Method methods} of the declared class, excluding the inherited methods
     *
     * @param declaringClass  the declared class
     * @param methodsToFilter (optional) the methods to be filtered
     * @return non-null read-only {@link List}
     * @see #getMethods(Class, boolean, boolean, Predicate[])
     */
    public static List getDeclaredMethods(Class declaringClass, Predicate... methodsToFilter) {
        return getMethods(declaringClass, false, false, methodsToFilter);
    }

    /**
     * Get all public {@link Method methods} of the declared class, including the inherited methods.
     *
     * @param declaringClass  the declared class
     * @param methodsToFilter (optional) the methods to be filtered
     * @return non-null read-only {@link List}
     * @see #getMethods(Class, boolean, boolean, Predicate[])
     */
    public static List getMethods(Class declaringClass, Predicate... methodsToFilter) {
        return getMethods(declaringClass, false, true, methodsToFilter);
    }

    /**
     * Get all declared {@link Method methods} of the declared class, including the inherited methods.
     *
     * @param declaringClass  the declared class
     * @param methodsToFilter (optional) the methods to be filtered
     * @return non-null read-only {@link List}
     * @see #getMethods(Class, boolean, boolean, Predicate[])
     */
    public static List getAllDeclaredMethods(Class declaringClass, Predicate... methodsToFilter) {
        return getMethods(declaringClass, true, false, methodsToFilter);
    }

    /**
     * Get all public {@link Method methods} of the declared class, including the inherited methods.
     *
     * @param declaringClass  the declared class
     * @param methodsToFilter (optional) the methods to be filtered
     * @return non-null read-only {@link List}
     * @see #getMethods(Class, boolean, boolean, Predicate[])
     */
    public static List getAllMethods(Class declaringClass, Predicate... methodsToFilter) {
        return getMethods(declaringClass, true, true, methodsToFilter);
    }

    /**
     * Find the {@link Method} by the the specified type(including inherited types) and method name without the
     * parameter type.
     *
     * @param type       the target type
     * @param methodName the specified method name
     * @return if not found, return null
     */
    public static Method findMethod(Class type, String methodName) {
        return findMethod(type, methodName, EMPTY_CLASS_ARRAY);
    }

    /**
     * Find the {@link Method} by the the specified type(including inherited types) and method name and parameter types
     *
     * @param type           the target type
     * @param methodName     the method name
     * @param parameterTypes the parameter types
     * @return if not found, return null
     */
    public static Method findMethod(Class type, String methodName, Class... parameterTypes) {
        MethodKey key = buildKey(type, methodName, parameterTypes);
        return methodsCache.computeIfAbsent(key, MethodUtils::findMethod);
    }

    static Method findMethod(MethodKey key) {
        Class declaredClass = key.declaredClass;
        String methodName = key.methodName;
        Class[] parameterTypes = key.parameterTypes;
        return findDeclaredMethod(declaredClass, methodName, parameterTypes);
    }

    public static Method findDeclaredMethod(Class declaredClass, String methodName, Class... parameterTypes) {
        Method method = getDeclaredMethod(declaredClass, methodName, parameterTypes);
        if (method == null) {
            Set> inheritedTypes = getAllInheritedTypes(declaredClass);
            for (Class inheritedType : inheritedTypes) {
                method = getDeclaredMethod(inheritedType, methodName, parameterTypes);
                if (method != null) {
                    break;
                }
            }
        }
        if (method == null) {
            if (logger.isTraceEnabled()) {
                logger.trace("The declared method was not found in the declared class[name : '{}'] by name['{}'] and parameter types['']",
                        declaredClass, methodName, asList(parameterTypes));
            }
        }

        return method;
    }

    public static Method getDeclaredMethod(Class declaredClass, String methodName, Class... parameterTypes) {
        Method method = null;
        try {
            method = declaredClass.getDeclaredMethod(methodName, parameterTypes);
        } catch (NoSuchMethodException e) {
        }
        return method;
    }

    /**
     * Invoke the target objects' method
     *
     * @param object     the target object
     * @param methodName the method name
     * @param arguments  the method arguments
     * @param         the return type
     * @return the target method's execution result
     */
    public static  R invokeMethod(Object object, String methodName, Object... arguments) {
        Class type = object.getClass();
        return invokeMethod(object, type, methodName, arguments);
    }

    /**
     * Invoke the target classes' static method
     *
     * @param type       the target class
     * @param methodName the method name
     * @param arguments  the method arguments
     * @param         the return type
     * @return the target method's execution result
     */
    public static  R invokeStaticMethod(Class type, String methodName, Object... arguments) {
        return invokeMethod(null, type, methodName, arguments);
    }

    /**
     * Invoke the target classes' static method
     *
     * @param method    the method
     * @param arguments the method arguments
     * @param        the return type
     * @return the target method's execution result
     */
    public static  R invokeStaticMethod(Method method, Object... arguments) {
        return invokeMethod(null, method, arguments);
    }

    public static  R invokeMethod(Object instance, Class type, String methodName, Object... arguments) {
        Class[] parameterTypes = getTypes(arguments);
        Method method = findMethod(type, methodName, parameterTypes);

        if (method == null) {
            throw new IllegalStateException(format("cannot find method[name : '{}'], class: '{}'", methodName, type.getName()));
        }

        return invokeMethod(instance, method, arguments);
    }

    /**
     * Invokes the underlying method represented by this {@code Method}
     * object, on the specified object with the specified parameters.
     * Individual parameters are automatically unwrapped to match
     * primitive formal parameters, and both primitive and reference
     * parameters are subject to method invocation conversions as
     * necessary.
     *
     * 

If the underlying method is static, then the specified {@code instance} * argument is ignored. It may be null. * *

If the number of formal parameters required by the underlying method is * 0, the supplied {@code args} array may be of length 0 or null. * *

If the underlying method is an instance method, it is invoked * using dynamic method lookup as documented in The Java Language * Specification, section {@jls 15.12.4.4}; in particular, * overriding based on the runtime type of the target object may occur. * *

If the underlying method is static, the class that declared * the method is initialized if it has not already been initialized. * *

If the method completes normally, the value it returns is * returned to the caller of invoke; if the value has a primitive * type, it is first appropriately wrapped in an object. However, * if the value has the type of an array of a primitive type, the * elements of the array are not wrapped in objects; in * other words, an array of primitive type is returned. If the * underlying method return type is void, the invocation returns * null. * * @param instance the object the underlying method is invoked from * @param method the underlying method * @param arguments the arguments used for the method call * @param * @return the result of dispatching the method represented by * this object on {@code instance} with parameters * {@code arguments} * @throws IllegalStateException if this {@code Method} object * is enforcing Java language access control and the underlying * method is inaccessible. * @throws IllegalArgumentException if the method is an * instance method and the specified object argument * is not an instance of the class or interface * declaring the underlying method (or of a subclass * or implementor thereof); if the number of actual * and formal parameters differ; if an unwrapping * conversion for primitive arguments fails; or if, * after possible unwrapping, a parameter value * cannot be converted to the corresponding formal * parameter type by a method invocation conversion. * @throws RuntimeException if the underlying method * throws an exception. */ public static R invokeMethod(@Nullable Object instance, Method method, Object... arguments) { R result = null; boolean accessible = false; RuntimeException failure = null; try { trySetAccessible(method); result = (R) method.invoke(instance, arguments); } catch (IllegalAccessException e) { String errorMessage = format("The method[signature : '{}' , instance : {}] can't be accessed[accessible : {}]", getSignature(method), instance, accessible); failure = new IllegalStateException(errorMessage, e); } catch (IllegalArgumentException e) { String errorMessage = format("The arguments can't match the method[signature : '{}' , instance : {}] : {}", getSignature(method), instance, asList(arguments)); failure = new IllegalArgumentException(errorMessage, e); } catch (InvocationTargetException e) { String errorMessage = format("It's failed to invoke the method[signature : '{}' , instance : {} , arguments : {}]", getSignature(method), instance, asList(arguments)); failure = new RuntimeException(errorMessage, e.getTargetException()); } if (failure != null) { logger.error(failure.getMessage(), failure.getCause()); throw failure; } return result; } /** * Tests whether one method, as a member of a given type, * overrides another method. * * @param overrider the first method, possible overrider * @param overridden the second method, possibly being overridden * @return {@code true} if and only if the first method overrides * the second * @jls 8.4.8 Inheritance, Overriding, and Hiding * @jls 9.4.1 Inheritance and Overriding * @see Elements#overrides(ExecutableElement, ExecutableElement, TypeElement) */ public static boolean overrides(Method overrider, Method overridden) { if (overrider == null || overridden == null) { return false; } // equality comparison: If two methods are same if (Objects.equals(overrider, overridden)) { return false; } // Modifiers comparison: Any method must be non-static method if (isStatic(overrider) || isStatic(overridden)) { // return false; } // Modifiers comparison: the accessibility of any method must not be private if (isPrivate(overrider) || isPrivate(overridden)) { return false; } // Inheritance comparison: The declaring class of overrider must be inherit from the overridden's if (!overridden.getDeclaringClass().isAssignableFrom(overrider.getDeclaringClass())) { return false; } // Method comparison: must not be "default" method if (overrider.isDefault()) { return false; } // Method comparison: The method name must be equal if (!Objects.equals(overrider.getName(), overridden.getName())) { return false; } // Method comparison: The count of method parameters must be equal if (!Objects.equals(overrider.getParameterCount(), overridden.getParameterCount())) { return false; } // Method comparison: Any parameter type of overrider must equal the overridden's for (int i = 0; i < overrider.getParameterCount(); i++) { if (!Objects.equals(overridden.getParameterTypes()[i], overrider.getParameterTypes()[i])) { return false; } } // Method comparison: The return type of overrider must be inherit from the overridden's return overridden.getReturnType().isAssignableFrom(overrider.getReturnType()); // Throwable comparison: "throws" Throwable list will be ignored, trust the compiler verify } /** * Find the nearest overridden {@link Method method} from the inherited class * * @param overrider the overrider {@link Method method} * @return if found, the overrider method, or null */ public static Method findNearestOverriddenMethod(Method overrider) { Class declaringClass = overrider.getDeclaringClass(); Method overriddenMethod = null; for (Class inheritedType : getAllInheritedTypes(declaringClass)) { overriddenMethod = findOverriddenMethod(overrider, inheritedType); if (overriddenMethod != null) { break; } } return overriddenMethod; } /** * Find the overridden {@link Method method} from the declaring class * * @param overrider the overrider {@link Method method} * @param declaringClass the class that is declaring the overridden {@link Method method} * @return if found, the overrider method, or null */ public static Method findOverriddenMethod(Method overrider, Class declaringClass) { List matchedMethods = getAllMethods(declaringClass, method -> overrides(overrider, method)); return matchedMethods.isEmpty() ? null : matchedMethods.get(0); } /** * Get the signature of {@link Method the specified method} * * @param method {@link Method the specified method} * @return non-null */ public static String getSignature(Method method) { Class declaringClass = method.getDeclaringClass(); Class[] parameterTypes = method.getParameterTypes(); int parameterCount = parameterTypes.length; String[] parameterTypeNames = new String[parameterCount]; String methodName = method.getName(); String declaringClassName = getTypeName(declaringClass); int size = declaringClassName.length() + 1 // '#' + methodName.length() + 1 // '(' + (parameterCount == 0 ? 0 : parameterCount - 1) // (parameterCount - 1) * ',' + 1 // ')' ; for (int i = 0; i < parameterCount; i++) { Class parameterType = parameterTypes[i]; String parameterTypeName = getTypeName(parameterType); parameterTypeNames[i] = parameterTypeName; size += parameterTypeName.length(); } StringBuilder signatureBuilder = new StringBuilder(size); signatureBuilder.append(declaringClassName).append(SHARP_CHAR).append(methodName).append(LEFT_PARENTHESIS_CHAR); for (int i = 0; i < parameterCount; i++) { String parameterTypeName = parameterTypeNames[i]; signatureBuilder.append(parameterTypeName); if (i < parameterCount - 1) { signatureBuilder.append(COMMA_CHAR); } parameterTypeNames[i] = null; } signatureBuilder.append(RIGHT_PARENTHESIS_CHAR); return signatureBuilder.toString(); } public static boolean isObjectMethod(Method method) { if (method != null) { return Objects.equals(Object.class, method.getDeclaringClass()); } return false; } /** * Test whether the specified {@link Method method} annotates {@linkplain jdk.internal.reflect.CallerSensitive} or not * * @param method {@link Method} * @return true if the specified {@link Method method} annotates {@linkplain jdk.internal.reflect.CallerSensitive} * @see jdk.internal.reflect.CallerSensitive */ public static boolean isCallerSensitiveMethod(Method method) { return isAnnotationPresent(method, CALLER_SENSITIVE_ANNOTATION_CLASS); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy