diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/ClassUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/ClassUtil.java index 695ea7edf..b14309d13 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/ClassUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/ClassUtil.java @@ -12,6 +12,7 @@ package org.dromara.hutool.core.reflect; +import org.dromara.hutool.core.array.ArrayUtil; import org.dromara.hutool.core.bean.NullWrapperBean; import org.dromara.hutool.core.classloader.ClassLoaderUtil; import org.dromara.hutool.core.convert.BasicType; @@ -20,10 +21,11 @@ import org.dromara.hutool.core.io.file.FileUtil; import org.dromara.hutool.core.io.resource.ResourceUtil; import org.dromara.hutool.core.net.url.URLDecoder; import org.dromara.hutool.core.net.url.URLUtil; +import org.dromara.hutool.core.text.CharUtil; import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.text.split.SplitUtil; -import org.dromara.hutool.core.array.ArrayUtil; -import org.dromara.hutool.core.text.CharUtil; +import org.dromara.hutool.core.tree.hierarchy.HierarchyIteratorUtil; +import org.dromara.hutool.core.tree.hierarchy.HierarchyUtil; import org.dromara.hutool.core.util.CharsetUtil; import java.io.IOException; @@ -33,14 +35,9 @@ import java.lang.reflect.Type; import java.net.URI; import java.net.URL; import java.time.temporal.TemporalAccessor; -import java.util.ArrayList; -import java.util.Date; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Predicate; /** @@ -709,44 +706,6 @@ public class ClassUtil { return location.getPath(); } - /** - * 获取指定类的所有父类,结果不包括指定类本身
- * 如果无父类,返回一个空的列表 - * - * @param clazz 类, 可以为{@code null} - * @return 所有父类列表,参数为{@code null} 则返回{@code null} - */ - public static List> getSuperClasses(final Class clazz) { - if (clazz == null) { - return null; - } - final List> classes = new ArrayList<>(); - Class superclass = clazz.getSuperclass(); - while (superclass != null) { - classes.add(superclass); - superclass = superclass.getSuperclass(); - } - return classes; - } - - /** - * 获取指定类及其父类所有的实现接口。
- * 结果顺序取决于查找顺序,当前类在前,父类的接口在后。 - * - * @param cls 被查找的类 - * @return 接口列表,若提供的查找类为{@code null},返回{@code null} - */ - public static List> getInterfaces(final Class cls) { - if (cls == null) { - return null; - } - - final LinkedHashSet> interfacesFound = new LinkedHashSet<>(); - getInterfaces(cls, interfacesFound); - - return new ArrayList<>(interfacesFound); - } - /** * 加载指定名称的类,支持: *
    @@ -810,21 +769,104 @@ public class ClassUtil { return clazz; } - /** - * 获取指定类的的接口列表 - * - * @param clazz 指定类 - * @param interfacesFound 接口Set - */ - private static void getInterfaces(Class clazz, final HashSet> interfacesFound) { - while (clazz != null) { - for (final Class i : clazz.getInterfaces()) { - if (interfacesFound.add(i)) { - getInterfaces(i, interfacesFound); - } - } - clazz = clazz.getSuperclass(); + /** + * 获取指定类的所有父类,结果不包括指定类本身
    + * 如果无父类,返回一个空的列表 + * + * @param clazz 类, 可以为{@code null} + * @return 指定类的所有父类列表,如果无父类,返回一个空的列表 + */ + public static List> getSuperClasses(final Class clazz) { + if (clazz == null) { + return Collections.emptyList(); } + List> superclasses = new ArrayList<>(); + traverseTypeHierarchy(clazz, t -> !t.isInterface(), superclasses::add, false); + return superclasses; + } + + /** + * 获取指定类及其父类所有的实现接口。
    + * 结果顺序取决于查找顺序,当前类在前,父类的接口在后。 + * + * @param cls 被查找的类 + * @return 接口列表,若提供的查找类为{@code null},则返回空列表 + */ + public static List> getInterfaces(final Class cls) { + if (cls == null) { + return Collections.emptyList(); + } + List> interfaces = new ArrayList<>(); + traverseTypeHierarchy(cls, t -> true, t -> { + if (t.isInterface()) { + interfaces.add(t); + } + }, false); + return interfaces; + } + + /** + * 按广度优先遍历包括{@code root}在内,其层级结构中的所有类和接口,直到{@code terminator}返回{@code false} + * + * @param root 根类 + * @param terminator 对遍历到的每个类与接口执行的校验,若为{@code false}则立刻中断遍历 + */ + public static void traverseTypeHierarchyWhile( + final Class root, Predicate> terminator) { + traverseTypeHierarchyWhile(root, t -> true, terminator); + } + + /** + * 按广度优先遍历包括{@code root}在内,其层级结构中的所有类和接口,直到{@code terminator}返回{@code false} + * + * @param root 根类 + * @param filter 过滤器,被过滤的类及其层级结构中的类与接口将被忽略 + * @param terminator 对遍历到的每个类与接口执行的校验,若为{@code false}则立刻中断遍历 + */ + public static void traverseTypeHierarchyWhile( + final Class root, final Predicate> filter, final Predicate> terminator) { + HierarchyUtil.traverseByBreadthFirst( + root, filter, + HierarchyIteratorUtil.scan(ClassUtil::getNextTypeHierarchies, terminator.negate()) + ); + } + + /** + *

    按广度优先遍历包括{@code root}在内,其层级结构中的所有类和接口。
    + * 类遍历顺序如下: + *

      + *
    • 离{@code type}距离越近,则顺序越靠前;
    • + *
    • 与{@code type}距离相同,则父类优先于接口;
    • + *
    • 与{@code type}距离相同的接口,则顺序遵循接口在{@link Class#getInterfaces()}的顺序;
    • + *
    + * + * @param root 根类 + * @param filter 过滤器,被过滤的类及其层级结构中的类与接口将被忽略 + * @param consumer 对遍历到的每个类与接口执行的操作,每个类和接口都只会被访问一次 + * @param includeRoot 是否包括根类 + */ + public static void traverseTypeHierarchy( + final Class root, final Predicate> filter, final Consumer> consumer, boolean includeRoot) { + Objects.requireNonNull(root); + Objects.requireNonNull(filter); + Objects.requireNonNull(consumer); + Function, Collection>> function = t -> { + if (includeRoot || !root.equals(t)) { + consumer.accept(t); + } + return getNextTypeHierarchies(t); + }; + HierarchyUtil.traverseByBreadthFirst(root, filter, HierarchyIteratorUtil.scan(function)); + } + + private static Set> getNextTypeHierarchies(Class t) { + Set> next = new LinkedHashSet<>(); + Class superclass = t.getSuperclass(); + if (Objects.nonNull(superclass)) { + next.add(superclass); + } + next.addAll(Arrays.asList(t.getInterfaces())); + return next; } } diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodMatcher.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodMatcher.java new file mode 100644 index 000000000..5af32994c --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodMatcher.java @@ -0,0 +1,75 @@ +package org.dromara.hutool.core.reflect.method; + +import java.lang.reflect.Method; +import java.util.Objects; +import java.util.function.Predicate; + +/** + *

    方法匹配器,本身可作为{@link Predicate}校验方法是否匹配指定条件。 + * 当作为{@link MethodMetadataLookup}使用时, + * 若方法符合条件,则返回{@link Boolean#TRUE},否则返回{@code null}。 + * + * @author huangchengxing + * @see MethodMatcherUtils + * @see MethodMetadataLookup + * @since 6.0.0 + */ +@FunctionalInterface +public interface MethodMatcher extends MethodMetadataLookup, Predicate { + + /** + * 检查方法是否匹配 + * + * @param method 方法 + * @return 结果 + */ + boolean test(Method method); + + /** + * 返回一个组合的条件,当且仅当所有条件都符合时,才返回{@code true}, + * 等同于{@link Predicate#and(Predicate)}. + * + * @param other 其他条件 + * @return 条件 + * @throws NullPointerException 当other为null时抛出 + */ + @Override + default MethodMatcher and(Predicate other) { + Objects.requireNonNull(other); + return t -> test(t) && other.test(t); + } + + /** + * 返回一个与此条件相反的条件 + * + * @return 条件 + */ + @Override + default MethodMatcher negate() { + return t -> !test(t); + } + + /** + * 返回一个组合的条件,当且仅当任一条件符合时,才返回{@code true}, + * + * @param other 其他条件 + * @return 条件 + * @throws NullPointerException 当other为null时抛出 + */ + @Override + default MethodMatcher or(Predicate other) { + Objects.requireNonNull(other); + return t -> test(t) || other.test(t); + } + + /** + * 检查方法,若结果不为{@code null}则认为方法与其匹配 + * + * @param method 要检查的方法 + * @return 结果 + */ + @Override + default Boolean inspect(Method method) { + return test(method) ? Boolean.TRUE : null; + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodMatcherUtils.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodMatcherUtils.java new file mode 100644 index 000000000..840ef62f9 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodMatcherUtils.java @@ -0,0 +1,598 @@ +package org.dromara.hutool.core.reflect.method; + +import org.dromara.hutool.core.annotation.AnnotatedElementUtil; +import org.dromara.hutool.core.array.ArrayUtil; +import org.dromara.hutool.core.reflect.ClassUtil; +import org.dromara.hutool.core.text.CharSequenceUtil; +import org.jetbrains.annotations.NotNull; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Objects; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import java.util.stream.Stream; + +/** + * 方法匹配器工具类,用于基于各种预设条件创建方法匹配器,用于配合{@link MethodScanner}从各种范围中寻找匹配的方法。 + * + * @author huangchengxing + * @see MethodMatcher + * @since 6.0.0 + */ +public class MethodMatcherUtils { + + + /** + *

    创建一个匹配任何方法的方法匹配器 + * + * @return 方法匹配器 + */ + public static MethodMatcher alwaysMatch() { + return method -> true; + } + + /** + *

    创建一个方法匹配器 + * + * @param predicate 条件 + * @return 方法匹配器 + */ + public static MethodMatcher of(final Predicate predicate) { + return predicate::test; + } + + /** + *

    用于组合多个方法匹配器的方法匹配器,仅当所有方法匹配器均匹配失败时才认为方法匹配。 + * + * @param matchers 方法匹配器 + * @return 方法匹配器 + * @see Stream#noneMatch + */ + public static MethodMatcher noneMatch(final MethodMatcher... matchers) { + return method -> Stream.of(matchers).noneMatch(matcher -> matcher.test(method)); + } + + /** + *

    用于组合多个方法匹配器的方法匹配器,当任意方法匹配器匹配成功时即认为方法匹配。 + * + * @param matchers 方法匹配器 + * @return 方法匹配器 + * @see Stream#anyMatch + */ + public static MethodMatcher anyMatch(final MethodMatcher... matchers) { + return method -> Stream.of(matchers).anyMatch(matcher -> matcher.test(method)); + } + + /** + *

    用于组合多个方法匹配器的方法匹配器,当所有方法匹配器均匹配成功时才认为方法匹配。 + * + * @param matchers 方法匹配器 + * @return 方法匹配器 + * @see Stream#allMatch + */ + public static MethodMatcher allMatch(final MethodMatcher... matchers) { + return method -> Stream.of(matchers).allMatch(matcher -> matcher.test(method)); + } + + // region ============= 访问修饰符 ============= + + /** + *

    用于匹配共有方法的方法匹配器。 + * + * @return 方法匹配器 + */ + public static MethodMatcher isPublic() { + return forModifiers(Modifier.PUBLIC); + } + + /** + *

    用于匹配静态方法的方法匹配器。 + * + * @return 方法匹配器 + */ + public static MethodMatcher isStatic() { + return forModifiers(Modifier.STATIC); + } + + /** + *

    用于匹配公共静态方法的方法匹配器。 + * + * @return 方法匹配器 + */ + public static MethodMatcher isPublicStatic() { + return forModifiers(Modifier.PUBLIC, Modifier.STATIC); + } + + /** + *

    用于具有指定修饰符的方法的方法匹配器。 + * + * @param modifiers 修饰符 + * @return 方法匹配器 + */ + public static MethodMatcher forModifiers(final int... modifiers) { + return method -> { + final int methodModifiers = method.getModifiers(); + return Arrays.stream(modifiers).allMatch(modifier -> (methodModifiers & modifier) != 0); + }; + } + + // endregion + + // region ============= 注解 ============= + + /** + *

    用于匹配被指定注解标注、或注解层级结构中存在指定注解的方法的方法匹配器。
    + * 比如:指定注解为 {@code @Annotation},则匹配直接被{@code @Annotation}标注的方法。 + * + * @param annotationType 注解类型 + * @return 方法匹配器 + * @see AnnotatedElementUtil#isAnnotationPresent + */ + public static MethodMatcher hasDeclaredAnnotation(final Class annotationType) { + return method -> method.isAnnotationPresent(annotationType); + } + + /** + *

    用于匹配被指定注解标注、或注解层级结构中存在指定注解的方法的方法匹配器。
    + * 比如:指定注解为 {@code @Annotation},则匹配: + *

      + *
    • 被{@code @Annotation}标注的方法;
    • + *
    • 被带有{@code @Annotation}注解的派生注解标注的方法;
    • + *
    + * + * @param annotationType 注解类型 + * @return 方法匹配器 + * @see AnnotatedElementUtil#isAnnotationPresent + */ + public static MethodMatcher hasAnnotation(final Class annotationType) { + return method -> AnnotatedElementUtil.isAnnotationPresent(method, annotationType); + } + + /** + *

    用于匹配声明方法的类的层级接口中,存在任意类被指定注解标注、或注解层级结构中存在指定注解的方法的方法匹配器。
    + * 比如:指定注解为 {@code @Annotation},则匹配: + *

      + *
    • 声明方法的类被{@code @Annotation}标注的方法;
    • + *
    • 声明方法的类被带有{@code @Annotation}注解的派生注解标注的方法;
    • + *
    + * + * @param annotationType 注解类型 + * @return 方法匹配器 + * @see AnnotatedElementUtil#isAnnotationPresent + */ + public static MethodMatcher hasAnnotationOnDeclaringClass(final Class annotationType) { + return method -> AnnotatedElementUtil.isAnnotationPresent(method.getDeclaringClass(), annotationType); + } + + /** + *

    用于匹配方法本身或声明方法的类上,直接被指定注解标注、或注解层级结构中存在指定注解的方法的方法匹配器。
    + * 比如:指定注解为 {@code @Annotation},则匹配: + *

      + *
    • 被{@code @Annotation}标注的方法;
    • + *
    • 被带有{@code @Annotation}注解的派生注解标注的方法;
    • + *
    • 声明方法的类被{@code @Annotation}标注的方法;
    • + *
    • 声明方法的类被带有{@code @Annotation}注解的派生注解标注的方法;
    • + *
    + * + * @param annotationType 注解类型 + * @return 方法匹配器 + */ + public static MethodMatcher hasAnnotationOnMethodOrDeclaringClass(final Class annotationType) { + return method -> AnnotatedElementUtil.isAnnotationPresent(method, annotationType) + || AnnotatedElementUtil.isAnnotationPresent(method.getDeclaringClass(), annotationType); + } + + // endregion + + // region ============= getter & setter ============= + + /** + *

    用于获得指定属性的getter方法的匹配器 + *

      + *
    • 查找方法名为{@code get + 首字母大写的属性名}的无参数方法;
    • + *
    • 查找方法名为属性名的无参数方法;
    • + *
    • 若{@code fieldType}为{@code boolean}或{@code Boolean},则同时查找方法名为{@code is + 首字母大写的属性}的无参数方法;
    • + *
    + * + * @param fieldName 属性名 + * @param fieldType 属性类型 + * @return 方法匹配器 + */ + public static MethodMatcher forGetterMethod(final String fieldName, final Class fieldType) { + Objects.requireNonNull(fieldName); + Objects.requireNonNull(fieldType); + // 匹配方法名为 get + 首字母大写的属性名的无参数方法 + MethodMatcher nameMatcher = forName(CharSequenceUtil.upperFirstAndAddPre(fieldName, "get")); + // 查找方法名为属性名的无参数方法 + nameMatcher = nameMatcher.or(forName(fieldName)); + if (Objects.equals(boolean.class, fieldType) || Objects.equals(Boolean.class, fieldType)) { + // 匹配方法名为 get + 首字母大写的属性名的无参数方法 + nameMatcher = nameMatcher.or(forName(CharSequenceUtil.upperFirstAndAddPre(fieldName, "is"))); + } + return allMatch(nameMatcher, forReturnType(fieldType), forNoneParameter()); + } + + /** + *

    用于获得指定属性的getter方法的匹配器 + *

      + *
    • 查找方法名为{@code get + 首字母大写的属性名}的无参数方法;
    • + *
    • 查找方法名为属性名的无参数方法;
    • + *
    • 若{@code fieldType}为{@code boolean}或{@code Boolean},则同时查找方法名为{@code is + 首字母大写的属性}的无参数方法;
    • + *
    + * + * @param field 属性 + * @return 方法匹配器 + */ + public static MethodMatcher forGetterMethod(final Field field) { + Objects.requireNonNull(field); + return forGetterMethod(field.getName(), field.getType()); + } + + /** + *

    用于获得指定属性的setter方法的匹配器,默认查找方法名为{@code set + 首字母大写的属性}的单参数方法。 + *

      + *
    • 查找方法名为{@code set + 首字母大写的属性名}的单参数方法;
    • + *
    • 查找方法名为属性名的单参数方法;
    • + *
    + * + * @param fieldName 属性名 + * @param fieldType 属性类型 + * @return 方法匹配器 + */ + public static MethodMatcher forSetterMethod(final String fieldName, final Class fieldType) { + Objects.requireNonNull(fieldName); + Objects.requireNonNull(fieldType); + final MethodMatcher nameMatcher = forName(CharSequenceUtil.upperFirstAndAddPre(fieldName, "set")) + .or(forName(fieldName)); + return allMatch(nameMatcher, forParameterTypes(fieldType)); + } + + /** + *

    用于获得指定属性的setter方法的匹配器,默认查找方法名为{@code set + 首字母大写的属性}的单参数方法。 + *

      + *
    • 查找方法名为{@code set + 首字母大写的属性名}的单参数方法;
    • + *
    • 查找方法名为属性名的单参数方法;
    • + *
    + * + * @param field 属性 + * @return 方法匹配器 + */ + public static MethodMatcher forSetterMethod(final Field field) { + Objects.requireNonNull(field); + return forSetterMethod(field.getName(), field.getType()); + } + + // endregion + + // region ============= 名称 & 参数类型 ============= + + /** + *

    用于同时匹配方法名和参数类型的方法匹配器,其中,参数类型匹配时允许参数类型为方法参数类型的子类。 + * + * @param methodName 方法名 + * @param parameterTypes 参数类型 + * @return 方法匹配器 + */ + public static MethodMatcher forNameAndParameterTypes(final String methodName, final Class... parameterTypes) { + Objects.requireNonNull(methodName); + Objects.requireNonNull(parameterTypes); + return allMatch(forName(methodName), forParameterTypes(parameterTypes)); + } + + /** + *

    用于同时匹配方法名和参数类型的方法匹配器,其中,参数类型匹配时要求参数类型与方法参数类型完全一致。 + * + * @param methodName 方法名 + * @param parameterTypes 参数类型 + * @return 方法匹配器 + */ + public static MethodMatcher forNameAndStrictParameterTypes(final String methodName, final Class... parameterTypes) { + Objects.requireNonNull(methodName); + Objects.requireNonNull(parameterTypes); + return allMatch(forName(methodName), forStrictParameterTypes(parameterTypes)); + } + + /** + *

    用于同时匹配方法名和参数类型的方法匹配器,其中,参数类型匹配时允许参数类型为方法参数类型的子类,且方法名忽略大小写。 + * + * @param methodName 方法名 + * @param parameterTypes 参数类型 + * @return 方法匹配器 + */ + public static MethodMatcher forNameIgnoreCaseAndParameterTypes( + final String methodName, final Class... parameterTypes) { + Objects.requireNonNull(methodName); + Objects.requireNonNull(parameterTypes); + return allMatch(forNameIgnoreCase(methodName), forParameterTypes(parameterTypes)); + } + + /** + *

    用于同时匹配方法名和参数类型的方法匹配器,其中,参数类型匹配时要求参数类型与方法参数类型完全一致,且方法名忽略大小写。 + * + * @param methodName 方法名 + * @param parameterTypes 参数类型 + * @return 方法匹配器 + */ + public static MethodMatcher forNameIgnoreCaseAndStrictParameterTypes( + final String methodName, Class... parameterTypes) { + Objects.requireNonNull(methodName); + Objects.requireNonNull(parameterTypes); + return allMatch(forNameIgnoreCase(methodName), forStrictParameterTypes(parameterTypes)); + } + + // endregion + + // region ============= 名称 & 返回值类型 & 参数类型 ============= + + /** + *

    用于匹配方法签名的方法匹配器,检查的内容包括: + *

      + *
    • 方法名是否完全一致;
    • + *
    • 返回值类型是否匹配,允许返回值类型为方法返回值类型的子类;
    • + *
    • 参数类型是否匹配,允许参数类型为方法参数类型的子类;
    • + *
    + * + * @param method 方法 + * @return 方法匹配器 + */ + public static MethodMatcher forMethodSignature(final Method method) { + Objects.requireNonNull(method); + return forMethodSignature(method.getName(), method.getReturnType(), method.getParameterTypes()); + } + + /** + *

    用于匹配方法签名的方法匹配器,检查的内容包括: + *

      + *
    • 方法名是否完全一致;
    • + *
    • 返回值类型是否匹配,允许返回值类型为方法返回值类型的子类,若返回值类型为{@code null}则表示匹配无返回值的方法;
    • + *
    • 参数类型是否匹配,允许参数类型为方法参数类型的子类,若参数类型为{@code null}则表示匹配无参数的方法;
    • + *
    + * + * @param methodName 方法名 + * @param returnType 返回值类型,若为{@code null}则表示匹配无返回值的方法 + * @param parameterTypes 参数类型,若为{@code null}则表示匹配无参数的方法 + * @return 方法匹配器 + */ + public static MethodMatcher forMethodSignature( + final String methodName, final Class returnType, final Class... parameterTypes) { + Objects.requireNonNull(methodName); + final MethodMatcher resultMatcher = Objects.isNull(returnType) ? + forNoneReturnType() : forReturnType(returnType); + final MethodMatcher parameterMatcher = Objects.isNull(parameterTypes) ? + forNoneParameter() : forParameterTypes(parameterTypes); + return allMatch(forName(methodName), resultMatcher, parameterMatcher); + } + + /** + *

    用于匹配方法签名的方法匹配器,检查的内容包括: + *

      + *
    • 方法名是否完全一致;
    • + *
    • 返回值类型是否匹配,要求返回值类型与方法返回值类型完全一致,若返回值类型为{@code null}则表示匹配无返回值的方法;
    • + *
    • 参数类型是否匹配,要求参数类型与方法参数类型完全一致,若参数类型为{@code null}则表示匹配无参数的方法;
    • + *
    + * + * @param methodName 方法名 + * @param returnType 返回值类型,若为{@code null}则表示匹配无返回值的方法 + * @param parameterTypes 参数类型,若为{@code null}则表示匹配无参数的方法 + * @return 方法匹配器 + */ + public static MethodMatcher forStrictMethodSignature( + final String methodName, final Class returnType, final Class... parameterTypes) { + Objects.requireNonNull(methodName); + final MethodMatcher resultMatcher = Objects.isNull(returnType) ? + forNoneReturnType() : forReturnType(returnType); + final MethodMatcher parameterMatcher = Objects.isNull(parameterTypes) ? + forNoneParameter() : forStrictParameterTypes(parameterTypes); + return allMatch(forName(methodName), resultMatcher, parameterMatcher); + } + + /** + *

    用于匹配方法签名的方法匹配器,检查的内容包括: + *

      + *
    • 方法名是否完全一致;
    • + *
    • 返回值类型是否匹配,要求返回值类型与方法返回值类型完全一致;
    • + *
    • 参数类型是否匹配,要求参数类型与方法参数类型完全一致;
    • + *
    + * + * @param method 方法 + * @return 方法匹配器 + */ + public static MethodMatcher forStrictMethodSignature(final Method method) { + Objects.requireNonNull(method); + return forMethodSignature(method.getName(), method.getReturnType(), method.getParameterTypes()); + } + + // endregion + + // region ============= 单条件匹配 ============= + + /** + *

    用于根据方法名匹配方法的方法匹配器。 + * + * @param methodName 方法名 + * @return 方法匹配器 + */ + public static MethodMatcher forName(final String methodName) { + return method -> Objects.equals(method.getName(), methodName); + } + + /** + *

    用于根据方法名匹配方法的方法匹配器,忽略方法名大小写。 + * + * @param methodName 方法名 + * @return 方法匹配器 + */ + public static MethodMatcher forNameIgnoreCase(final String methodName) { + return method -> CharSequenceUtil.endWithIgnoreCase(method.getName(), methodName); + } + + /** + *

    用于匹配无返回值的方法的方法匹配器。 + * + * @return 方法匹配器 + */ + public static MethodMatcher forNoneReturnType() { + return method -> Objects.equals(method.getReturnType(), Void.TYPE); + } + + /** + *

    用于匹配指定参数类型的方法的方法匹配器,只要参数类型可以赋值给方法参数类型。 + * + * @param returnType 返回值类型 + * @return 方法匹配器 + */ + public static MethodMatcher forReturnType(final Class returnType) { + return method -> ClassUtil.isAssignable(returnType, method.getReturnType()); + } + + /** + *

    用于匹配指定返回值类型的方法的方法匹配器,要求返回值类型与指定类型完全一致。 + * + * @param returnType 返回值类型 + * @return 方法匹配器 + */ + public static MethodMatcher forStrictReturnType(final Class returnType) { + return method -> Objects.equals(method.getReturnType(), returnType); + } + + /** + *

    用于匹配无参数方法的方法匹配器。 + * + * @return 方法匹配器 + */ + public static MethodMatcher forNoneParameter() { + return method -> method.getParameterCount() == 0; + } + + /** + *

    用于匹配指定参数个数的方法的方法匹配器。 + * + * @param count 参数个数 + * @return 方法匹配器 + */ + public static MethodMatcher forParameterCount(int count) { + return method -> method.getParameterCount() == count; + } + + /** + *

    用于匹配指定参数类型的方法的方法匹配器,只要参数类型可以赋值给方法参数类型即认为匹配成功。 + * 比如:参数类型为{@link java.util.ArrayList},则方法参数类型可以为{@link java.util.List}、{@link java.util.Collection}等。 + * + * @param parameterTypes 参数类型 + * @return 方法匹配器 + */ + public static MethodMatcher forParameterTypes(final Class... parameterTypes) { + Objects.requireNonNull(parameterTypes); + return method -> ClassUtil.isAllAssignableFrom(parameterTypes, method.getParameterTypes()); + } + + /** + *

    用于匹配指定参数类型的方法的方法匹配器,与{@link #forParameterTypes}不同的是,该方法仅用于尽量可能最匹配的方法 + *

      + *
    • 若参数为空,则表示匹配无参数方法;
    • + *
    • + * 若参数不为空: + *
        + *
      • 仅匹配{@code parameterTypes}中不为{@code null}的参数类型,若参数类型为{@code null}则表示匹配任意类型的参数;
      • + *
      • 若N为{@code parameterTypes}长度,则仅要求{@code parameterTypes}不为{@code null}的类型与方法前N个参数类型匹配即可;
      • + *
      • 若{@code parameterTypes}长度大于参数列表长度,则直接返回{@code false};
      • + *
      + *
    • + *
    + * 比如:
    + * 若存在三参数方法{@code method(String, Integer, Object)},支持以下匹配: + *
      + *
    • {@code forMostSpecificParameterTypes(CharSequence.class, Number.class, Object.class)}
    • + *
    • {@code forMostSpecificParameterTypes(String.class, Integer.class, Object.class)}
    • + *
    • {@code forMostSpecificParameterTypes(String.class, Integer.class, null)}
    • + *
    • {@code forMostSpecificParameterTypes(String.class, null, null)}
    • + *
    • {@code forMostSpecificParameterTypes(null, null, null)}
    • + *
    • {@code forMostSpecificParameterTypes(String.class, Integer.class)}
    • + *
    • {@code forMostSpecificParameterTypes(String.class)}
    • + *
    + * + * @param parameterTypes 参数类型 + * @return 方法匹配器 + */ + public static MethodMatcher forMostSpecificParameterTypes(final Class... parameterTypes) { + return mostSpecificStrictParameterTypesMatcher(parameterTypes, ClassUtil::isAssignable); + } + + /** + *

    用于匹配指定参数类型的方法的方法匹配器,与{@link #forParameterTypes}不同的是,该方法仅用于尽量可能最匹配的方法 + *

      + *
    • 若参数为空,则表示匹配无参数方法;
    • + *
    • + * 若参数不为空: + *
        + *
      • 仅匹配{@code parameterTypes}中不为{@code null}的参数类型,若参数类型为{@code null}则表示匹配任意类型的参数;
      • + *
      • 若N为{@code parameterTypes}长度,则仅要求{@code parameterTypes}不为{@code null}的类型与方法前N个参数类型匹配即可;
      • + *
      • 若{@code parameterTypes}长度大于参数列表长度,则直接返回{@code false};
      • + *
      + *
    • + *
    + * 比如:
    + * 若存在三参数方法{@code method(String, Integer, Object)},支持以下匹配: + *
      + *
    • {@code forMostSpecificParameterTypes(String.class, Integer.class, Object.class)}
    • + *
    • {@code forMostSpecificParameterTypes(String.class, Integer.class, null)}
    • + *
    • {@code forMostSpecificParameterTypes(String.class, null, null)}
    • + *
    • {@code forMostSpecificParameterTypes(null, null, null)}
    • + *
    • {@code forMostSpecificParameterTypes(String.class, Integer.class)}
    • + *
    • {@code forMostSpecificParameterTypes(String.class)}
    • + *
    + * + * @param parameterTypes 参数类型 + * @return 方法匹配器 + */ + public static MethodMatcher forMostSpecificStrictParameterTypes(final Class... parameterTypes) { + return mostSpecificStrictParameterTypesMatcher(parameterTypes, Objects::equals); + } + + /** + *

    用于匹配指定参数类型的方法的方法匹配器,只有参数类型完全匹配才认为匹配成功。 + * + * @param parameterTypes 参数类型 + * @return 方法匹配器 + */ + public static MethodMatcher forStrictParameterTypes(final Class... parameterTypes) { + Objects.requireNonNull(parameterTypes); + return method -> ArrayUtil.equals(method.getParameterTypes(), parameterTypes); + } + + // endregion + + @NotNull + private static MethodMatcher mostSpecificStrictParameterTypesMatcher( + Class[] parameterTypes, BiPredicate, Class> typeMatcher) { + Objects.requireNonNull(parameterTypes); + // 若参数为空,则表示匹配无参数方法 + if (parameterTypes.length == 0) { + return forNoneParameter(); + } + // 若参数不为空,则表示匹配指定参数类型的方法 + return method -> { + final Class[] methodParameterTypes = method.getParameterTypes(); + if (parameterTypes.length > methodParameterTypes.length) { + return false; + } + for (int i = 0; i < parameterTypes.length; i++) { + final Class parameterType = parameterTypes[i]; + // 若参数类型为null,则表示匹配任意类型的参数 + if (Objects.isNull(parameterType)) { + continue; + } + // 若参数类型不为null,则要求参数类型可以赋值给方法参数类型 + if (typeMatcher.negate().test(parameterType, methodParameterTypes[i])) { + return false; + } + } + return true; + }; + } + +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodMetadataLookup.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodMetadataLookup.java new file mode 100644 index 000000000..6e497e050 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodMetadataLookup.java @@ -0,0 +1,23 @@ +package org.dromara.hutool.core.reflect.method; + +import java.lang.reflect.Method; + +/** + * 方法的元数据查找器,参照 spring 的 {@code MethodIntrospector.MetadataLookup},用于从方法上获得特定的元数据。 + * + * @author huangchengxing + * @see MethodMatcher + * @see MethodMatcherUtils + * @since 6.0.0 + */ +@FunctionalInterface +public interface MethodMetadataLookup { + + /** + * 检查并返回方法上的特定元数据,若结果不为{@code null}则认为方法与其匹配 + * + * @param method 要检查的方法 + * @return 结果 + */ + T inspect(Method method); +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodScanner.java b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodScanner.java new file mode 100644 index 000000000..2d4be4eb9 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/reflect/method/MethodScanner.java @@ -0,0 +1,373 @@ +package org.dromara.hutool.core.reflect.method; + +import org.dromara.hutool.core.array.ArrayUtil; +import org.dromara.hutool.core.lang.mutable.Mutable; +import org.dromara.hutool.core.lang.mutable.MutableObj; +import org.dromara.hutool.core.map.MapUtil; +import org.dromara.hutool.core.map.WeakConcurrentMap; +import org.dromara.hutool.core.reflect.ClassUtil; +import org.dromara.hutool.core.reflect.MethodUtil; + +import java.lang.reflect.Method; +import java.util.*; +import java.util.function.Predicate; + +/** + *

    方法查找工具类,用于从指定的类或类层级结构中,根据特定规则搜索方法。 + * + *

    查找方式与查找范围 + *

    支持四种语义的查找: + *

      + *
    • get: 获取首个符合条件的方法;
    • + *
    • getWithMetadata: 获取首个带有指定元数据的方法与该元数据;
    • + *
    • find: 获得所有符合条件的方法;
    • + *
    • findWithMetadata: 获取所有带有指定元数据的方法与这些元数据;
    • + *
    + * 基于上述四种语义的查找,提供四种查找范围: + *
      + *
    • xxxFromSpecificMethods:在用户给定的方法列表中查找匹配的方法;
    • + *
    • xxxFromMethods:在{@link Class#getMethods}的范围中查找匹配的方法;
    • + *
    • xxxFromDeclaredMethods:在{@link Class#getDeclaredMethods}的范围中查找匹配的方法;
    • + *
    • xxxFromAllMethods:在类的层级结构中的所有类的所有方法的范围中查找匹配的方法;
    • + *
    + * 比如,我们希望获取{@link Class#getMethods()}中所有匹配条件的方法,则应当调用{@link #findFromMethods}, + * 若我们希望获得类所有方法范围中首个匹配的方法,则应当调用{@link #getFromAllMethods}。 + * + *

    匹配规则 + *

    方法查找的规则由{@link MethodMetadataLookup}实现。
    + * 规定,当{@link MethodMetadataLookup#inspect(Method)}方法返回元数据不为{@code null}时,则认为方法与其匹配,返回结果时将同时返回匹配的方法与元数据。
    + * 因此,我们可以通过实现{@link MethodMetadataLookup}接口来同时实现方法的查找与元数据的获取:
    + * 比如,我们希望查找所有方法上带有{@code Annotation}注解的方法,则可以实现如下: + *

    {@code
    + * 		Map methods = MethodScanner.findFromAllMethods(Foo.class, method -> method.getAnnotation(Annotation.class));
    + * }
    + * 此外,对于一些无需获取元数据的查找,我们可以使用{@link MethodMatcherUtils}提供的一些内置实现: + *
    {@code
    + * 		// 查找所有静态公开方法
    + * 		Set methods = MethodScanner.findFromAllMethods(Foo.class, MethodMatcherUtils.isPublicStatic());
    + * 		// 按照方法名与参数类型查找方法
    + * 		Method method = MethodScanner.getFromAllMethods(Foo.class, MethodMatcherUtils.forNameAndParameterTypes("foo", String.class));
    + * }
    + * + *

    缓存 + *

    对于{@link #getDeclaredMethods}与{@link #getMethods}方法与基于这两个方法实现的, + * 所有{@code xxxFromMethods}与{@code xxxFromDeclaredMethods}方法, + * 都提供了缓存基于{@link WeakConcurrentMap}的缓存支持。
    + * {@link #getAllMethods}与所有{@code xxxFromAllMethods}方法都基于{@link #getDeclaredMethods}实现, + * 但是每次全量查找,都需要重新遍历类层级结构,因此会带来一定的额外的性能损耗。
    + * 缓存在GC时会被回收,但是也可以通过{@link #clearCaches}手动清除缓存。 + * + * @author huangchengxing + * @see MethodMetadataLookup + * @see MethodMatcher + * @see MethodMatcherUtils + * @since 6.0.0 + */ +public class MethodScanner { + /** + * TODO 替换{@link MethodUtil}中的部分方法实现 + */ + + /** + * 空方法列表 + */ + private static final Method[] EMPTY_METHODS = new Method[0]; + + /** + * 方法缓存 + */ + private static final WeakConcurrentMap, Method[]> METHODS_CACHE = new WeakConcurrentMap<>(); + + /** + * 直接声明的方法缓存 + */ + private static final WeakConcurrentMap, Method[]> DECLARED_METHODS_CACHE = new WeakConcurrentMap<>(); + + // region ============= basic ============= + + /** + * 获取当前类及父类的所有公共方法,等同于{@link Class#getMethods()} + * + * @param type 类 + * @return 当前类及父类的所有公共方法 + */ + public static Method[] getMethods(Class type) { + if (Objects.isNull(type)) { + return EMPTY_METHODS; + } + return MapUtil.computeIfAbsentForJdk8(METHODS_CACHE, type, Class::getMethods); + } + + /** + * 获取当前类直接声明的所有方法,等同于{@link Class#getDeclaredMethods()} + * + * @param type 类 + * @return 当前类及父类的所有公共方法 + */ + public static Method[] getDeclaredMethods(Class type) { + if (Objects.isNull(type)) { + return EMPTY_METHODS; + } + return MapUtil.computeIfAbsentForJdk8(DECLARED_METHODS_CACHE, type, Class::getDeclaredMethods); + } + + /** + *

    获取当前类层级结构中的所有方法。
    + * 等同于按广度优先遍历类及其所有父类与接口,并依次调用{@link Class#getDeclaredMethods()}。
    + * 返回的方法排序规则如下: + *

      + *
    • 离{@code type}距离越近,则顺序越靠前;
    • + *
    • 与{@code type}距离相同,则父类优先于接口;
    • + *
    • 与{@code type}距离相同的接口,则顺序遵循接口在{@link Class#getInterfaces()}的顺序;
    • + *
    + * + * @param type 类 + * @return 当前类及父类的所有公共方法 + * @see ClassUtil#traverseTypeHierarchyWhile(Class, Predicate) + */ + public static Method[] getAllMethods(Class type) { + if (Objects.isNull(type)) { + return EMPTY_METHODS; + } + List methods = new ArrayList<>(); + ClassUtil.traverseTypeHierarchyWhile(type, t -> { + methods.addAll(Arrays.asList(getDeclaredMethods(t))); + return true; + }); + return methods.isEmpty() ? EMPTY_METHODS : methods.toArray(new Method[0]); + } + + /** + * 清空缓存 + */ + public static void clearCaches() { + METHODS_CACHE.clear(); + DECLARED_METHODS_CACHE.clear(); + } + + // endregion + + // region ============= specific methods ============= + + /** + * 从指定方法列表中筛选所有方法上带有指定元数据方法的方法与对应元数据 + * + * @param methods 方法列表 + * @param lookup 查找器 + * @return 方法与对应的元数据集合 + */ + public static Map findWithMetadataFromSpecificMethods(Method[] methods, MethodMetadataLookup lookup) { + if (ArrayUtil.isEmpty(methods)) { + return Collections.emptyMap(); + } + Map results = new LinkedHashMap<>(); + for (Method method : methods) { + T result = lookup.inspect(method); + if (Objects.nonNull(result)) { + results.put(method, result); + } + } + return results; + } + + /** + * 从指定方法列表中筛选所有方法上带有指定元数据方法的方法 + * + * @param methods 方法列表 + * @param lookup 查找器 + * @return 方法集合 + */ + public static Set findFromSpecificMethods(Method[] methods, MethodMetadataLookup lookup) { + return findWithMetadataFromSpecificMethods(methods, lookup).keySet(); + } + + /** + * 从指定方法列表中筛选所有方法上带有指定元数据方法的方法与对应元数据 + * + * @param methods 方法列表 + * @param lookup 查找器 + * @return 方法与对应的元数据 + */ + public static Map.Entry getWithMetadataFromSpecificMethods(Method[] methods, MethodMetadataLookup lookup) { + for (Method method : methods) { + T result = lookup.inspect(method); + if (Objects.nonNull(result)) { + return MapUtil.entry(method, result); + } + } + return null; + } + + /** + * 从指定方法列表中筛选所有方法上带有指定元数据方法的方法 + * + * @param methods 方法列表 + * @param lookup 查找器 + * @return 方法 + */ + public static Method getFromSpecificMethods(Method[] methods, MethodMetadataLookup lookup) { + Map.Entry result = getWithMetadataFromSpecificMethods(methods, lookup); + return Objects.isNull(result) ? null : result.getKey(); + } + + // endregion + + // region ============= methods ============= + + /** + * 获取方法上带有指定元数据的方法与对应元数据 + * + * @param type 类型 + * @param lookup 查找器 + * @return 方法与对应的元数据集合 + */ + public static Map findWithMetadataFromMethods(Class type, MethodMetadataLookup lookup) { + return findWithMetadataFromSpecificMethods(getMethods(type), lookup); + } + + /** + * 获取方法上带有指定元数据的方法 + * + * @param type 类型 + * @param lookup 查找器 + * @return 方法集合 + */ + public static Set findFromMethods(Class type, MethodMetadataLookup lookup) { + return findFromSpecificMethods(getMethods(type), lookup); + } + + /** + * 获取首个方法上带有指定元数据的方法及元数据 + * + * @param type 类型 + * @param lookup 查找器 + * @return 方法及元数据,若无任何匹配的结果则返回{@code null} + */ + public static Map.Entry getWithMetadataFromMethods(Class type, MethodMetadataLookup lookup) { + return getWithMetadataFromSpecificMethods(getMethods(type), lookup); + } + + /** + * 获取首个方法上带有指定元数据的方法及元数据 + * + * @param type 类型 + * @param lookup 查找器 + * @return 方法,若无任何匹配的结果则返回{@code null} + */ + public static Method getFromMethods(Class type, MethodMetadataLookup lookup) { + return getFromSpecificMethods(getMethods(type), lookup); + } + + // endregion + + // region ============= declared methods ============= + + /** + * 获取方法上带有指定元数据的方法与对应元数据 + * + * @param type 类型 + * @param lookup 查找器 + * @return 方法与对应的元数据集合 + */ + public static Map findWithMetadataFromDeclaredMethods(Class type, MethodMetadataLookup lookup) { + return findWithMetadataFromSpecificMethods(getDeclaredMethods(type), lookup); + } + + /** + * 获取方法上带有指定元数据的方法 + * + * @param type 类型 + * @param lookup 查找器 + * @return 方法集合 + */ + public static Set findFromDeclaredMethods(Class type, MethodMetadataLookup lookup) { + return findFromSpecificMethods(getDeclaredMethods(type), lookup); + } + + /** + * 获取首个方法上带有指定元数据的方法及元数据 + * + * @param type 类型 + * @param lookup 查找器 + * @return 方法及元数据,若无任何匹配的结果则返回{@code null} + */ + public static Map.Entry getWithMetadataFromDeclaredMethods(Class type, MethodMetadataLookup lookup) { + return getWithMetadataFromSpecificMethods(getDeclaredMethods(type), lookup); + } + + /** + * 获取首个方法上带有指定元数据的方法及元数据 + * + * @param type 类型 + * @param lookup 查找器 + * @return 方法,若无任何匹配的结果则返回{@code null} + */ + public static Method getFromDeclaredMethods(Class type, MethodMetadataLookup lookup) { + return getFromSpecificMethods(getDeclaredMethods(type), lookup); + } + + // endregion + + + // region ============= all methods ============= + + /** + * 获取方法上带有指定元数据的方法与对应元数据 + * + * @param type 类型 + * @param lookup 查找器 + * @return 方法与对应的元数据集合 + */ + public static Map findWithMetadataFromAllMethods(Class type, MethodMetadataLookup lookup) { + return findWithMetadataFromSpecificMethods(getAllMethods(type), lookup); + } + + /** + * 获取方法上带有指定元数据的方法 + * + * @param type 类型 + * @param lookup 查找器 + * @return 方法集合 + */ + public static Set findFromAllMethods(Class type, MethodMetadataLookup lookup) { + return findFromSpecificMethods(getAllMethods(type), lookup); + } + + /** + * 获取首个方法上带有指定元数据的方法及元数据 + * + * @param type 类型 + * @param lookup 查找器 + * @return 方法及元数据,若无任何匹配的结果则返回{@code null} + */ + public static Map.Entry getWithMetadataFromAllMethods(Class type, MethodMetadataLookup lookup) { + if (Objects.isNull(type)) { + return null; + } + Mutable> result = new MutableObj<>(); + ClassUtil.traverseTypeHierarchyWhile(type, t -> { + Map.Entry target = getWithMetadataFromDeclaredMethods(t, lookup); + if (Objects.nonNull(target)) { + result.set(target); + return false; + } + return true; + }); + return result.get(); + } + + /** + * 获取首个方法上带有指定元数据的方法及元数据 + * + * @param type 类型 + * @param lookup 查找器 + * @return 方法,若无任何匹配的结果则返回{@code null} + */ + public static Method getFromAllMethods(Class type, MethodMetadataLookup lookup) { + Map.Entry target = getWithMetadataFromAllMethods(type, lookup); + return Objects.isNull(target) ? null : target.getKey(); + } + + // endregion + +} diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/reflect/ClassUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/reflect/ClassUtilTest.java new file mode 100644 index 000000000..f27117e10 --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/reflect/ClassUtilTest.java @@ -0,0 +1,117 @@ +package org.dromara.hutool.core.reflect; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * test for {@link ClassUtil} + * + * @author huangchengxing + */ +class ClassUtilTest { + + @Test + void testGetSuperClasses() { + // if root is null + List> superclasses = ClassUtil.getSuperClasses(null); + Assertions.assertEquals(0, superclasses.size()); + // if root not null + superclasses = ClassUtil.getSuperClasses(Child.class); + Assertions.assertEquals(3, superclasses.size()); + Assertions.assertEquals(Parent.class, superclasses.get(0)); + Assertions.assertEquals(GrandParent.class, superclasses.get(1)); + Assertions.assertEquals(Object.class, superclasses.get(2)); + } + + @Test + void testGetInterface() { + // if root is null + List> interfaces = ClassUtil.getInterfaces(null); + Assertions.assertEquals(0, interfaces.size()); + // if root not null + interfaces = ClassUtil.getInterfaces(Child.class); + Assertions.assertEquals(4, interfaces.size()); + Assertions.assertEquals(Mother.class, interfaces.get(0)); + Assertions.assertEquals(Father.class, interfaces.get(1)); + Assertions.assertEquals(GrandMother.class, interfaces.get(2)); + Assertions.assertEquals(GrandFather.class, interfaces.get(3)); + } + + @Test + void testTraverseTypeHierarchy() { + // collect all superclass of child by bfs (include child) + List> superclasses = new ArrayList<>(); + ClassUtil.traverseTypeHierarchy( + Child.class, t -> !t.isInterface(), superclasses::add, true + ); + Assertions.assertEquals(4, superclasses.size()); + Assertions.assertEquals(Child.class, superclasses.get(0)); + Assertions.assertEquals(Parent.class, superclasses.get(1)); + Assertions.assertEquals(GrandParent.class, superclasses.get(2)); + Assertions.assertEquals(Object.class, superclasses.get(3)); + + // collect all superclass of child by bfs (exclude child) + superclasses.clear(); + ClassUtil.traverseTypeHierarchy( + Child.class, t -> !t.isInterface(), superclasses::add, false + ); + Assertions.assertEquals(3, superclasses.size()); + Assertions.assertEquals(Parent.class, superclasses.get(0)); + Assertions.assertEquals(GrandParent.class, superclasses.get(1)); + Assertions.assertEquals(Object.class, superclasses.get(2)); + } + + @Test + void testTraverseTypeHierarchyWithTerminator() { + // collect all superclass of child until Parent by bfs (include child) + List> superclasses = new ArrayList<>(); + ClassUtil.traverseTypeHierarchyWhile( + Child.class, t -> !t.isInterface(), t -> { + if (!Objects.equals(t, GrandParent.class)) { + superclasses.add(t); + return true; + } + return false; + } + ); + Assertions.assertEquals(2, superclasses.size()); + Assertions.assertEquals(Child.class, superclasses.get(0)); + Assertions.assertEquals(Parent.class, superclasses.get(1)); + + // collect all class of child until GrandMother or GrandFather by bfs (include child) + superclasses.clear(); + ClassUtil.traverseTypeHierarchyWhile( + Child.class, t -> { + if (!Objects.equals(t, GrandMother.class) && !Objects.equals(t, GrandFather.class)) { + superclasses.add(t); + return true; + } + return false; + } + ); + Assertions.assertEquals(6, superclasses.size()); + Assertions.assertEquals(Child.class, superclasses.get(0)); + Assertions.assertEquals(Parent.class, superclasses.get(1)); + Assertions.assertEquals(GrandParent.class, superclasses.get(2)); + Assertions.assertEquals(Mother.class, superclasses.get(3)); + Assertions.assertEquals(Father.class, superclasses.get(4)); + Assertions.assertEquals(Object.class, superclasses.get(5)); + } + + private interface Mother {} + + private interface Father {} + + private interface GrandMother extends Mother {} + + private interface GrandFather extends Father {} + + private static class GrandParent implements GrandMother, GrandFather {} + private static class Parent extends GrandParent implements Mother, Father {} + + private static class Child extends Parent {} +} diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/reflect/method/MethodMatcherTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/reflect/method/MethodMatcherTest.java new file mode 100644 index 000000000..ca8d285cb --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/reflect/method/MethodMatcherTest.java @@ -0,0 +1,63 @@ +package org.dromara.hutool.core.reflect.method; + +import lombok.SneakyThrows; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Method; + +/** + * test for {@link MethodMatcher} + * + * @author huangchengxing + */ +class MethodMatcherTest { + + private MethodMatcher matchToString = (MethodMatcher)t -> "toString".equals(t.getName()); + + @SneakyThrows + @Test + void test() { + Method toString = Object.class.getDeclaredMethod("toString"); + Assertions.assertTrue(matchToString.test(toString)); + Method hashCode = Object.class.getDeclaredMethod("hashCode"); + Assertions.assertFalse(matchToString.test(hashCode)); + } + + @SneakyThrows + @Test + void and() { + Method toString = Object.class.getDeclaredMethod("toString"); + Assertions.assertTrue(matchToString.test(toString)); + MethodMatcher newMatcher = matchToString.and(t -> t.getReturnType() == String.class); + Assertions.assertTrue(newMatcher.test(toString)); + } + + @SneakyThrows + @Test + void negate() { + Method toString = Object.class.getDeclaredMethod("toString"); + Assertions.assertTrue(matchToString.test(toString)); + MethodMatcher newMatcher = matchToString.negate(); + Assertions.assertFalse(newMatcher.test(toString)); + } + + @SneakyThrows + @Test + void or() { + MethodMatcher newMatcher = matchToString.or(t -> "hashCode".equals(t.getName())); + Method toString = Object.class.getDeclaredMethod("toString"); + Method hashCode = Object.class.getDeclaredMethod("hashCode"); + Assertions.assertTrue(newMatcher.test(toString)); + Assertions.assertTrue(newMatcher.test(hashCode)); + } + + @SneakyThrows + @Test + void inspect() { + Method toString = Object.class.getDeclaredMethod("toString"); + Assertions.assertTrue(matchToString.inspect(toString)); + Method hashCode = Object.class.getDeclaredMethod("hashCode"); + Assertions.assertNull(matchToString.inspect(hashCode)); + } +} diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/reflect/method/MethodMatcherUtilsTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/reflect/method/MethodMatcherUtilsTest.java new file mode 100644 index 000000000..5b94dda2d --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/reflect/method/MethodMatcherUtilsTest.java @@ -0,0 +1,662 @@ +package org.dromara.hutool.core.reflect.method; + +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * test for {@link MethodMatcherUtils} + * + * @author huangchengxing + */ +class MethodMatcherUtilsTest { + + private Method noneReturnNoArgs; + private Method noneReturnOneArgs; + private Method noneReturnTwoArgs; + private Method noneReturnTwoArgs2; + private Method returnNoArgs; + private Method returnOneArgs; + private Method returnTwoArgs; + + @SneakyThrows + @BeforeEach + void init() { + this.noneReturnNoArgs = MethodMatcherUtilsTest.class.getDeclaredMethod("noneReturnNoArgs"); + this.noneReturnOneArgs = MethodMatcherUtilsTest.class.getDeclaredMethod("noneReturnOneArgs", String.class); + this.noneReturnTwoArgs = MethodMatcherUtilsTest.class.getDeclaredMethod("noneReturnTwoArgs", String.class, List.class); + this.noneReturnTwoArgs2 = MethodMatcherUtilsTest.class.getDeclaredMethod("noneReturnTwoArgs", CharSequence.class, Collection.class); + this.returnNoArgs = MethodMatcherUtilsTest.class.getDeclaredMethod("returnNoArgs"); + this.returnOneArgs = MethodMatcherUtilsTest.class.getDeclaredMethod("returnOneArgs", String.class); + this.returnTwoArgs = MethodMatcherUtilsTest.class.getDeclaredMethod("returnTwoArgs", String.class, List.class); + } + + @Test + void testForName() { + MethodMatcher methodMatcher = MethodMatcherUtils.forName("noneReturnNoArgs"); + Assertions.assertTrue(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + } + + @Test + void forNameIgnoreCase() { + MethodMatcher methodMatcher = MethodMatcherUtils.forNameIgnoreCase("noneReturnNoArgs"); + Assertions.assertTrue(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + // if name is upper case, it will be ignored + methodMatcher = MethodMatcherUtils.forNameIgnoreCase("NONERETURNNOARGS"); + Assertions.assertTrue(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + } + + @Test + void forNoneReturnType() { + MethodMatcher methodMatcher = MethodMatcherUtils.forNoneReturnType(); + Assertions.assertTrue(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + } + + @Test + void forReturnType() { + MethodMatcher methodMatcher = MethodMatcherUtils.forReturnType(Collection.class); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(returnNoArgs)); + Assertions.assertTrue(methodMatcher.test(returnOneArgs)); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + } + + @Test + void forStrictReturnType() { + MethodMatcher methodMatcher = MethodMatcherUtils.forStrictReturnType(Collection.class); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + // only match return type is strict equal to parameter type + methodMatcher = MethodMatcherUtils.forStrictReturnType(List.class); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + } + + @Test + void forParameterCount() { + MethodMatcher methodMatcher = MethodMatcherUtils.forParameterCount(2); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + } + + @Test + void forMostSpecificParameterTypes() { + // match none args method + MethodMatcher methodMatcher = MethodMatcherUtils.forMostSpecificParameterTypes(); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnNoArgs)); + + // match all args types + methodMatcher = MethodMatcherUtils.forMostSpecificParameterTypes(null, null); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + + // match first arg type + methodMatcher = MethodMatcherUtils.forMostSpecificParameterTypes(CharSequence.class, null); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + + // match second arg type + methodMatcher = MethodMatcherUtils.forMostSpecificParameterTypes(null, Collection.class); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + + // match two arg type + methodMatcher = MethodMatcherUtils.forMostSpecificParameterTypes(CharSequence.class, Collection.class); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + } + + @Test + void forMostSpecificStrictParameterTypes() { + // match none args method + MethodMatcher methodMatcher = MethodMatcherUtils.forMostSpecificStrictParameterTypes(); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnNoArgs)); + + // match all args types + methodMatcher = MethodMatcherUtils.forMostSpecificStrictParameterTypes(null, null); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + + // match first arg type + methodMatcher = MethodMatcherUtils.forMostSpecificStrictParameterTypes(CharSequence.class, null); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + methodMatcher = MethodMatcherUtils.forMostSpecificStrictParameterTypes(String.class, null); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + + // match second arg type + methodMatcher = MethodMatcherUtils.forMostSpecificStrictParameterTypes(null, Collection.class); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + + // match two arg type + methodMatcher = MethodMatcherUtils.forMostSpecificStrictParameterTypes(CharSequence.class, Collection.class); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + } + + @Test + void forParameterTypes() { + MethodMatcher methodMatcher = MethodMatcherUtils.forParameterTypes(); + Assertions.assertTrue(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + // match parameter types is empty + methodMatcher = MethodMatcherUtils.forParameterTypes(CharSequence.class, Collection.class); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + } + + @Test + void forStrictParameterTypes() { + MethodMatcher methodMatcher = MethodMatcherUtils.forStrictParameterTypes(); + Assertions.assertTrue(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + + // cannot match assignable parameter types + methodMatcher = MethodMatcherUtils.forStrictParameterTypes(CharSequence.class, Collection.class); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + + // only match parameter types is strict equal to parameter type + methodMatcher = MethodMatcherUtils.forStrictParameterTypes(String.class, List.class); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + } + + @Test + void noneMatch() { + MethodMatcher methodMatcher = MethodMatcherUtils.noneMatch(); + Assertions.assertTrue(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(returnNoArgs)); + Assertions.assertTrue(methodMatcher.test(returnOneArgs)); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + + // combine with other matchers + methodMatcher = MethodMatcherUtils.noneMatch( + MethodMatcherUtils.forName("noneReturnNoArgs"), + MethodMatcherUtils.forReturnType(Collection.class) + ); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnOneArgs)); + } + + @Test + void anyMatch() { + MethodMatcher methodMatcher = MethodMatcherUtils.anyMatch(); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + + // combine with other matchers + methodMatcher = MethodMatcherUtils.anyMatch( + MethodMatcherUtils.forName("noneReturnNoArgs"), + MethodMatcherUtils.forReturnType(Collection.class) + ); + Assertions.assertTrue(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + } + + @Test + void allMatch() { + MethodMatcher methodMatcher = MethodMatcherUtils.allMatch(); + Assertions.assertTrue(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(returnNoArgs)); + Assertions.assertTrue(methodMatcher.test(returnOneArgs)); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + + // combine with other matchers + methodMatcher = MethodMatcherUtils.allMatch( + MethodMatcherUtils.forName("noneReturnNoArgs"), + MethodMatcherUtils.forReturnType(Collection.class) + ); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + } + + @Test + void isPublic() { + MethodMatcher methodMatcher = MethodMatcherUtils.isPublic(); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(returnNoArgs)); + Assertions.assertTrue(methodMatcher.test(returnOneArgs)); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + } + + @Test + void isStatic() { + MethodMatcher methodMatcher = MethodMatcherUtils.isStatic(); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + } + + @Test + void isPublicStatic() { + MethodMatcher methodMatcher = MethodMatcherUtils.isPublicStatic(); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + } + + @Test + void forModifiers() { + MethodMatcher methodMatcher = MethodMatcherUtils.forModifiers(Modifier.PUBLIC, Modifier.STATIC); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + } + + @Test + void forNameAndParameterTypes() { + MethodMatcher methodMatcher = MethodMatcherUtils.forNameAndParameterTypes("noneReturnTwoArgs", CharSequence.class, List.class); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + } + + @Test + void forNameAndStrictParameterTypes() { + MethodMatcher methodMatcher = MethodMatcherUtils.forNameAndStrictParameterTypes("noneReturnTwoArgs", CharSequence.class, List.class); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + + methodMatcher = MethodMatcherUtils.forNameAndStrictParameterTypes("returnTwoArgs", String.class, List.class); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + } + + @Test + void forNameIgnoreCaseAndParameterTypes() { + MethodMatcher methodMatcher = MethodMatcherUtils.forNameIgnoreCaseAndParameterTypes("NONEReturnTWOArgs", CharSequence.class, List.class); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + + methodMatcher = MethodMatcherUtils.forNameIgnoreCaseAndParameterTypes("ReturnTWOArgs", String.class, List.class); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + } + + @Test + void forNameIgnoreCaseAndStrictParameterTypes() { + MethodMatcher methodMatcher = MethodMatcherUtils.forNameIgnoreCaseAndStrictParameterTypes("NONEReturnTWOArgs", CharSequence.class, List.class); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + + methodMatcher = MethodMatcherUtils.forNameIgnoreCaseAndStrictParameterTypes("ReturnTWOArgs", String.class, List.class); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + } + + @Test + void forStrictMethodSignature() { + MethodMatcher methodMatcher = MethodMatcherUtils.forStrictMethodSignature("noneReturnTwoArgs", null, CharSequence.class, Collection.class); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs2)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + + methodMatcher = MethodMatcherUtils.forStrictMethodSignature("noneReturnTwoArgs", null, String.class, List.class); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnTwoArgs2)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + } + + @Test + void forStrictMethodSignatureWithMethod() { + MethodMatcher methodMatcher = MethodMatcherUtils.forStrictMethodSignature(noneReturnTwoArgs); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + + methodMatcher = MethodMatcherUtils.forStrictMethodSignature(returnTwoArgs); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertTrue(methodMatcher.test(returnTwoArgs)); + } + + @Test + void forMethodSignatureWithMethod() { + MethodMatcher methodMatcher = MethodMatcherUtils.forMethodSignature(noneReturnTwoArgs2); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs2)); + } + + @Test + void forMethodSignature() { + MethodMatcher methodMatcher = MethodMatcherUtils.forMethodSignature( + "noneReturnTwoArgs", null, CharSequence.class, Collection.class + ); + Assertions.assertFalse(methodMatcher.test(noneReturnNoArgs)); + Assertions.assertFalse(methodMatcher.test(noneReturnOneArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs)); + Assertions.assertFalse(methodMatcher.test(returnNoArgs)); + Assertions.assertFalse(methodMatcher.test(returnOneArgs)); + Assertions.assertFalse(methodMatcher.test(returnTwoArgs)); + Assertions.assertTrue(methodMatcher.test(noneReturnTwoArgs2)); + } + + @Test + @SneakyThrows + void forGetterMethodWithField() { + Field nameField = Foo.class.getDeclaredField("name"); + MethodMatcher methodMatcher = MethodMatcherUtils.forGetterMethod(nameField); + Method getName = Foo.class.getMethod("getName"); + Assertions.assertTrue(methodMatcher.test(getName)); + + Field flagField = Foo.class.getDeclaredField("flag"); + methodMatcher = MethodMatcherUtils.forGetterMethod(flagField); + Method isFlag = Foo.class.getMethod("isFlag"); + Assertions.assertTrue(methodMatcher.test(isFlag)); + + Field objectField = Foo.class.getDeclaredField("object"); + methodMatcher = MethodMatcherUtils.forGetterMethod(objectField); + Method object = Foo.class.getMethod("object"); + Assertions.assertTrue(methodMatcher.test(object)); + } + + @Test + @SneakyThrows + void forGetterMethod() { + MethodMatcher methodMatcher = MethodMatcherUtils.forGetterMethod("name", String.class); + Method getName = Foo.class.getMethod("getName"); + Assertions.assertTrue(methodMatcher.test(getName)); + + methodMatcher = MethodMatcherUtils.forGetterMethod("flag", boolean.class); + Method isFlag = Foo.class.getMethod("isFlag"); + Assertions.assertTrue(methodMatcher.test(isFlag)); + + methodMatcher = MethodMatcherUtils.forGetterMethod("object", Object.class); + Method object = Foo.class.getMethod("object"); + Assertions.assertTrue(methodMatcher.test(object)); + } + + @Test + @SneakyThrows + void forSetterMethodWithField() { + Field nameField = Foo.class.getDeclaredField("name"); + MethodMatcher methodMatcher = MethodMatcherUtils.forSetterMethod(nameField); + Method setName = Foo.class.getMethod("setName", String.class); + Assertions.assertTrue(methodMatcher.test(setName)); + + Field flagField = Foo.class.getDeclaredField("flag"); + methodMatcher = MethodMatcherUtils.forSetterMethod(flagField); + Method setFlag = Foo.class.getMethod("setFlag", boolean.class); + Assertions.assertTrue(methodMatcher.test(setFlag)); + + Field objectField = Foo.class.getDeclaredField("object"); + methodMatcher = MethodMatcherUtils.forSetterMethod(objectField); + Method object = Foo.class.getMethod("object", Object.class); + Assertions.assertTrue(methodMatcher.test(object)); + } + + @Test + @SneakyThrows + void forSetterMethod() { + MethodMatcher methodMatcher = MethodMatcherUtils.forSetterMethod("name", String.class); + Method setName = Foo.class.getMethod("setName", String.class); + Assertions.assertTrue(methodMatcher.test(setName)); + + methodMatcher = MethodMatcherUtils.forSetterMethod("flag", boolean.class); + Method setFlag = Foo.class.getMethod("setFlag", boolean.class); + Assertions.assertTrue(methodMatcher.test(setFlag)); + + methodMatcher = MethodMatcherUtils.forSetterMethod("object", Object.class); + Method object = Foo.class.getMethod("object", Object.class); + Assertions.assertTrue(methodMatcher.test(object)); + } + + @Test + @SneakyThrows + void hasDeclaredAnnotation() { + MethodMatcher methodMatcher = MethodMatcherUtils.hasDeclaredAnnotation(GrandParentAnnotation.class); + Assertions.assertFalse(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByChildAnnotation"))); + Assertions.assertFalse(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByParentAnnotation"))); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByGrandParentAnnotation"))); + Assertions.assertFalse(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("noneAnnotated"))); + } + + @Test + @SneakyThrows + void hasAnnotation() { + MethodMatcher methodMatcher = MethodMatcherUtils.hasAnnotation(GrandParentAnnotation.class); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByChildAnnotation"))); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByParentAnnotation"))); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByGrandParentAnnotation"))); + Assertions.assertFalse(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("noneAnnotated"))); + + methodMatcher = MethodMatcherUtils.hasAnnotation(ParentAnnotation.class); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByChildAnnotation"))); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByParentAnnotation"))); + Assertions.assertFalse(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByGrandParentAnnotation"))); + Assertions.assertFalse(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("noneAnnotated"))); + } + + @Test + @SneakyThrows + void hasAnnotationOnDeclaringClass() { + MethodMatcher methodMatcher = MethodMatcherUtils.hasAnnotationOnDeclaringClass(GrandParentAnnotation.class); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByChildAnnotation"))); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByParentAnnotation"))); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByGrandParentAnnotation"))); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("noneAnnotated"))); + + methodMatcher = MethodMatcherUtils.hasAnnotationOnDeclaringClass(ParentAnnotation.class); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByChildAnnotation"))); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByParentAnnotation"))); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByGrandParentAnnotation"))); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("noneAnnotated"))); + + methodMatcher = MethodMatcherUtils.hasAnnotationOnDeclaringClass(ChildAnnotation.class); + Assertions.assertFalse(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByChildAnnotation"))); + Assertions.assertFalse(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByParentAnnotation"))); + Assertions.assertFalse(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByGrandParentAnnotation"))); + Assertions.assertFalse(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("noneAnnotated"))); + } + + @Test + @SneakyThrows + void hasAnnotationOnMethodOrDeclaringClass() { + MethodMatcher methodMatcher = MethodMatcherUtils.hasAnnotationOnMethodOrDeclaringClass(GrandParentAnnotation.class); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByChildAnnotation"))); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByParentAnnotation"))); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByGrandParentAnnotation"))); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("noneAnnotated"))); + + methodMatcher = MethodMatcherUtils.hasAnnotationOnMethodOrDeclaringClass(ParentAnnotation.class); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByChildAnnotation"))); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByParentAnnotation"))); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByGrandParentAnnotation"))); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("noneAnnotated"))); + + methodMatcher = MethodMatcherUtils.hasAnnotationOnMethodOrDeclaringClass(ChildAnnotation.class); + Assertions.assertTrue(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByChildAnnotation"))); + Assertions.assertFalse(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByParentAnnotation"))); + Assertions.assertFalse(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("annotatedByGrandParentAnnotation"))); + Assertions.assertFalse(methodMatcher.test(AnnotatedClass.class.getDeclaredMethod("noneAnnotated"))); + } + + @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + private @interface GrandParentAnnotation {} + + @GrandParentAnnotation + @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + private @interface ParentAnnotation {} + + @ParentAnnotation + @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE}) + @Retention(RetentionPolicy.RUNTIME) + private @interface ChildAnnotation {} + + @ParentAnnotation + private static class AnnotatedClass { + + @ChildAnnotation + private void annotatedByChildAnnotation() { } + @ParentAnnotation + private static void annotatedByParentAnnotation() { } + @GrandParentAnnotation + public static void annotatedByGrandParentAnnotation() { } + public static void noneAnnotated() { } + } + + private static class Foo { + @Setter + @Getter + private String name; + @Setter + @Getter + private boolean flag; + private Object object; + public void setName(String name, Void none) { } + + public Object object() { + return object; + } + + public Foo object(Object object) { + this.object = object; + return this; + } + } + + private void noneReturnNoArgs() { } + private static void noneReturnOneArgs(String arg1) { } + public static void noneReturnTwoArgs(String arg1, List stringList) { } + public static void noneReturnTwoArgs(CharSequence arg1, Collection stringList) { } + public List returnNoArgs() { return null; } + public Set returnOneArgs(String arg1) { return null; } + public List returnTwoArgs(String arg1, List stringList) { return null; } +} diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/reflect/method/MethodScannerTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/reflect/method/MethodScannerTest.java new file mode 100644 index 000000000..4b85ded54 --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/reflect/method/MethodScannerTest.java @@ -0,0 +1,279 @@ +package org.dromara.hutool.core.reflect.method; + +import lombok.SneakyThrows; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +/** + * test for {@link MethodScanner} + * + * @author huangchengxing + */ +class MethodScannerTest { + + @Test + void testGetMethods() { + Assertions.assertEquals(0, MethodScanner.getMethods(null).length); + Method[] actual = MethodScanner.getMethods(Child.class); + Assertions.assertSame(actual, MethodScanner.getMethods(Child.class)); + Method[] expected = Child.class.getMethods(); + Assertions.assertArrayEquals(expected, actual); + } + + @Test + void testGetDeclaredMethods() { + Assertions.assertEquals(0, MethodScanner.getDeclaredMethods(null).length); + Method[] actual = MethodScanner.getDeclaredMethods(Child.class); + Assertions.assertSame(actual, MethodScanner.getDeclaredMethods(Child.class)); + Method[] expected = Child.class.getDeclaredMethods(); + Assertions.assertArrayEquals(expected, actual); + } + + @Test + void testGetAllMethods() { + Assertions.assertEquals(0, MethodScanner.getAllMethods(null).length); + Method[] actual = MethodScanner.getAllMethods(Child.class); + // get declared method from child、parent、grandparent + Method[] expected = Stream.of(Child.class, Parent.class, Interface.class, Object.class) + .flatMap(c -> Stream.of(MethodScanner.getDeclaredMethods(c))) + .toArray(Method[]::new); + Assertions.assertArrayEquals(expected, actual); + } + + @Test + void testClearCaches() { + Method[] declaredMethods = MethodScanner.getDeclaredMethods(Child.class); + Assertions.assertSame(declaredMethods, MethodScanner.getDeclaredMethods(Child.class)); + Method[] methods = MethodScanner.getMethods(Child.class); + Assertions.assertSame(methods, MethodScanner.getMethods(Child.class)); + + // clear method cache + MethodScanner.clearCaches(); + Assertions.assertNotSame(declaredMethods, MethodScanner.getDeclaredMethods(Child.class)); + Assertions.assertNotSame(methods, MethodScanner.getMethods(Child.class)); + } + + @SneakyThrows + @Test + void testFindWithMetadataFromSpecificMethods() { + Assertions.assertTrue(MethodScanner.findWithMetadataFromSpecificMethods(null, m -> m.getAnnotation(Annotation.class)).isEmpty()); + Method[] methods = MethodScanner.getMethods(Child.class); + Map actual = MethodScanner.findWithMetadataFromSpecificMethods(methods, m -> m.getAnnotation(Annotation.class)); + Assertions.assertEquals(1, actual.size()); + + // check method + Method expectedMethod = Parent.class.getDeclaredMethod("oneArgMethod", String.class); + Assertions.assertTrue(actual.containsKey(expectedMethod)); + + // check annotation + Annotation expectedAnnotation = expectedMethod.getAnnotation(Annotation.class); + Assertions.assertEquals(expectedAnnotation, actual.get(expectedMethod)); + } + + @SneakyThrows + @Test + void testFindFromSpecificMethods() { + Method[] methods = MethodScanner.getMethods(Child.class); + Set actual = MethodScanner.findFromSpecificMethods(methods, m -> m.getAnnotation(Annotation.class)); + Assertions.assertEquals(1, actual.size()); + + // check method + Method expectedMethod = Parent.class.getDeclaredMethod("oneArgMethod", String.class); + Assertions.assertTrue(actual.contains(expectedMethod)); + } + + @SneakyThrows + @Test + void testGetWithMetadataFromSpecificMethods() { + // find first oneArgMethod method + Method[] methods = MethodScanner.getMethods(Child.class); + Map.Entry actual = MethodScanner.getWithMetadataFromSpecificMethods(methods, MethodMatcherUtils.forName("oneArgMethod")); + Assertions.assertNotNull(actual); + Method expectedMethod = Parent.class.getDeclaredMethod("oneArgMethod", String.class); + Assertions.assertEquals(expectedMethod, actual.getKey()); + Assertions.assertTrue(actual.getValue()); + } + + @SneakyThrows + @Test + void testGetFromSpecificMethods() { + // find first oneArgMethod method + Method[] methods = MethodScanner.getMethods(Child.class); + Method actual = MethodScanner.getFromSpecificMethods(methods, MethodMatcherUtils.forName("oneArgMethod")); + Method expectedMethod = Parent.class.getDeclaredMethod("oneArgMethod", String.class); + Assertions.assertEquals(expectedMethod, actual); + } + + @SneakyThrows + @Test + void testFindWithMetadataFromMethods() { + Map actual = MethodScanner.findWithMetadataFromMethods(Child.class, m -> m.getAnnotation(Annotation.class)); + Assertions.assertEquals(1, actual.size()); + + // check method + Method expectedMethod = Parent.class.getDeclaredMethod("oneArgMethod", String.class); + Assertions.assertTrue(actual.containsKey(expectedMethod)); + + // check annotation + Annotation expectedAnnotation = expectedMethod.getAnnotation(Annotation.class); + Assertions.assertEquals(expectedAnnotation, actual.get(expectedMethod)); + } + + @SneakyThrows + @Test + void testFindFromMethods() { + Set actual = MethodScanner.findFromMethods(Child.class, m -> m.getAnnotation(Annotation.class)); + Assertions.assertEquals(1, actual.size()); + + // check method + Method expectedMethod = Parent.class.getDeclaredMethod("oneArgMethod", String.class); + Assertions.assertTrue(actual.contains(expectedMethod)); + } + + @SneakyThrows + @Test + void testGetWithMetadataFromMethods() { + Map.Entry actual = MethodScanner.getWithMetadataFromMethods(Child.class, m -> m.getAnnotation(Annotation.class)); + Assertions.assertNotNull(actual); + Method expectedMethod = Parent.class.getDeclaredMethod("oneArgMethod", String.class); + Assertions.assertEquals(expectedMethod, actual.getKey()); + Annotation expectedAnnotation = expectedMethod.getAnnotation(Annotation.class); + Assertions.assertEquals(expectedAnnotation, actual.getValue()); + } + + @SneakyThrows + @Test + void testGetFromMethods() { + Method actual = MethodScanner.getFromMethods(Child.class, m -> m.getAnnotation(Annotation.class)); + Method expectedMethod = Parent.class.getDeclaredMethod("oneArgMethod", String.class); + Assertions.assertEquals(expectedMethod, actual); + } + + @SneakyThrows + @Test + void testFindWithMetadataFromDeclaredMethods() { + Map actual = MethodScanner.findWithMetadataFromDeclaredMethods(Parent.class, m -> m.getAnnotation(Annotation.class)); + Assertions.assertEquals(1, actual.size()); + + // check method + Method expectedMethod = Parent.class.getDeclaredMethod("oneArgMethod", String.class); + Assertions.assertTrue(actual.containsKey(expectedMethod)); + + // check annotation + Annotation expectedAnnotation = expectedMethod.getAnnotation(Annotation.class); + Assertions.assertEquals(expectedAnnotation, actual.get(expectedMethod)); + } + + @SneakyThrows + @Test + void testFindFromDeclaredMethods() { + Set actual = MethodScanner.findFromDeclaredMethods(Parent.class, m -> m.getAnnotation(Annotation.class)); + Assertions.assertEquals(1, actual.size()); + + // check method + Method expectedMethod = Parent.class.getDeclaredMethod("oneArgMethod", String.class); + Assertions.assertTrue(actual.contains(expectedMethod)); + } + + @SneakyThrows + @Test + void testGetWithMetadataFromDeclaredMethods() { + Map.Entry actual = MethodScanner.getWithMetadataFromDeclaredMethods(Parent.class, m -> m.getAnnotation(Annotation.class)); + Assertions.assertNotNull(actual); + Method expectedMethod = Parent.class.getDeclaredMethod("oneArgMethod", String.class); + Assertions.assertEquals(expectedMethod, actual.getKey()); + Annotation expectedAnnotation = expectedMethod.getAnnotation(Annotation.class); + Assertions.assertEquals(expectedAnnotation, actual.getValue()); + } + + @SneakyThrows + @Test + void testGetFromDeclaredMethods() { + Method actual = MethodScanner.getFromDeclaredMethods(Parent.class, m -> m.getAnnotation(Annotation.class)); + Method expectedMethod = Parent.class.getDeclaredMethod("oneArgMethod", String.class); + Assertions.assertEquals(expectedMethod, actual); + } + + @SneakyThrows + @Test + void testFindWithMetadataFromAllMethods() { + Map actual = MethodScanner.findWithMetadataFromAllMethods(Child.class, m -> m.getAnnotation(Annotation.class)); + Assertions.assertEquals(1, actual.size()); + + // check method + Method expectedMethod = Parent.class.getDeclaredMethod("oneArgMethod", String.class); + Assertions.assertTrue(actual.containsKey(expectedMethod)); + + // check annotation + Annotation expectedAnnotation = expectedMethod.getAnnotation(Annotation.class); + Assertions.assertEquals(expectedAnnotation, actual.get(expectedMethod)); + } + + @SneakyThrows + @Test + void testFindFromAllMethods() { + Set actual = MethodScanner.findFromAllMethods(Child.class, m -> m.getAnnotation(Annotation.class)); + Assertions.assertEquals(1, actual.size()); + + // check method + Method expectedMethod = Parent.class.getDeclaredMethod("oneArgMethod", String.class); + Assertions.assertTrue(actual.contains(expectedMethod)); + } + + @SneakyThrows + @Test + void testGetWithMetadataFromAllMethods() { + Assertions.assertNull(MethodScanner.getWithMetadataFromAllMethods(Child.class, m -> m.getAnnotation(Deprecated.class))); + Map.Entry actual = MethodScanner.getWithMetadataFromAllMethods(Child.class, m -> m.getAnnotation(Annotation.class)); + Assertions.assertNotNull(actual); + Method expectedMethod = Parent.class.getDeclaredMethod("oneArgMethod", String.class); + Assertions.assertEquals(expectedMethod, actual.getKey()); + Annotation expectedAnnotation = expectedMethod.getAnnotation(Annotation.class); + Assertions.assertEquals(expectedAnnotation, actual.getValue()); + } + + @SneakyThrows + @Test + void testGetFromAllMethods() { + Method actual = MethodScanner.getFromAllMethods(Child.class, m -> m.getAnnotation(Annotation.class)); + Method expectedMethod = Parent.class.getDeclaredMethod("oneArgMethod", String.class); + Assertions.assertEquals(expectedMethod, actual); + } + + @Target({ElementType.METHOD, ElementType.TYPE, ElementType.ANNOTATION_TYPE}) + @Retention(RetentionPolicy.RUNTIME) + public @interface Annotation { + String value() default "default"; + } + + @Annotation("Interface") + public interface Interface { + static void staticMethod() { + + } + void noneArgMethod(); + void oneArgMethod(String arg); + } + + public static class Parent implements Interface { + @Override + public void noneArgMethod() { } + + @Annotation("oneArgMethod") + @Override + public void oneArgMethod(String arg) { } + } + + public static class Child extends Parent implements Interface { + + } +}