From 90219696ecf04b5f49eae49dcc4e834970cfbf3e Mon Sep 17 00:00:00 2001 From: huangchengxing <841396397@qq.com> Date: Mon, 8 May 2023 23:42:49 +0800 Subject: [PATCH] =?UTF-8?q?feat(ClassUtil):=20=E6=B7=BB=E5=8A=A0=E7=94=A8?= =?UTF-8?q?=E4=BA=8E=E9=81=8D=E5=8E=86=E7=B1=BB=E5=B1=82=E7=BA=A7=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E7=9A=84=E6=96=B9=E6=B3=95=20(Gitee=20#I6XUCF)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hutool/core/reflect/ClassUtil.java | 166 +++++++++++------- .../hutool/core/reflect/ClassUtilTest.java | 117 ++++++++++++ 2 files changed, 221 insertions(+), 62 deletions(-) create mode 100644 hutool-core/src/test/java/org/dromara/hutool/core/reflect/ClassUtilTest.java 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/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 {} +}