java - 通过扫描文件系统查找直接和间接子类

标签 java algorithm recursion java-8 nio

我在编写算法来帮助我扫描文件系统并找到某个类的所有子类时遇到了问题。

详细信息:

我有一个应用程序在检索时使用 nio Files.walk() 扫描外部应用程序 我在读取文件时检查“extends SuperClass”,如果单词退出后,我将类名添加到我的列表中,如下所示:

List<String> subclasses = new ArrayList<>();
Files.walk(appPath)
     .filter(p->Files.isRegularFile(p) && p.toString()
     .endsWith(".java")).forEach(path -> {
        try {
         List<String> lines = Files.readAllLines(path);
         Pattern pattern = Pattern.compile("\\bextends SuperClass\\b");
         Matcher matcher = pattern
                           .matcher(lines.stream()
                                 .collect(Collectors.joining(" ")));
         boolean isChild = matcher.find();
         if(isChild) subclasses.add(path.getFileName().toString());
        }catch (IOException e){
                //handle IOE
        }

上面的问题是它只获取 SuperClass 的直接子类,但我需要检索所有直接和间接子类。 我考虑过递归,因为我不知道 SuperClass 有多少个子类,但我无法实现任何合理的实现。

注意事项:

  • 扫描超过 60 万个文件
  • 我不知道 SuperClass 有多少直接/间接子类
  • 我正在扫描的应用程序是外部的,我不能修改它的代码,所以我只能通过读取文件来访问它并查看 extends 存在的位置
  • 如果问题有非递归解决方案那会很好,但如果没有其他方法,我会非常乐意接受递归解决方案,因为我更关心解决方案而不是性能。

编辑:

我使用以下正则表达式来比较名称和导入,以确保即使在相同名称的不同包的情况下输出也是正确的:

Pattern pattern = Pattern.compile("("+superClasss.getPackage()+")[\\s\\S]*(\\bextends "+superClass.getName()+"\\b)[\\s\\S]");

我也试过:

Pattern pattern = Pattern.compile("\\bextends "+superClass.getName()+"\\b");

但是也有一些缺失的子类,我相信下面的代码跳过了一些检查,并不能完全工作:

public static List<SuperClass> getAllSubClasses(Path path, SuperClass parentClass) throws IOException{
classesToDo.add(baseClass);
while(classesToDo.size() > 0) {
    SuperClass superClass = classesToDo.remove(0);
    List<SuperClass> subclasses = getDirectSubClasses(parentPath,parentClass);
    if(subclasses.size() > 0)
        classes.addAll(subclasses);
    classesToDo.addAll(subclasses);
}
return classes;

非常感谢任何帮助!

编辑 2 我还注意到另一个问题,当我检测到 subclass 时,我得到文件名 currentPath.getFileName() 这可能是也可能不是子类名称,因为子类可能是同一文件中的嵌套 或非公共(public)

最佳答案

我强烈建议解析编译后的类文件而不是源代码。由于这些类文件已经针对机器处理进行了优化,因此消除了源代码文件处理的许多复杂性和极端情况。

因此一个使用ASM构建完整类层次结构树的解决方案图书馆看起来像这样:

public static Map<String, Set<String>> getClassHierarchy(Path root) throws IOException {
    return Files.walk(root)
         .filter(p->Files.isRegularFile(p) && isClass(p.getFileName().toString()))
         .map(p -> getClassAndSuper(p))
         .collect(Collectors.groupingBy(Map.Entry::getValue,
                Collectors.mapping(Map.Entry::getKey, Collectors.toSet())));
}
private static boolean isClass(String fName) {
    // skip package-info and module-info
    return fName.endsWith(".class") && !fName.endsWith("-info.class");
}
private static Map.Entry<String,String> getClassAndSuper(Path p) {
    final class CV extends ClassVisitor {
        Map.Entry<String,String> result;
        public CV() {
            super(Opcodes.ASM5);
        }
        @Override
        public void visit(int version, int access,
                String name, String signature, String superName, String[] interfaces) {
            result = new AbstractMap.SimpleImmutableEntry<>(
                Type.getObjectType(name).getClassName(),
                superName!=null? Type.getObjectType(superName).getClassName(): "");
        }
    }
    try {
        final CV visitor = new CV();
        new ClassReader(Files.readAllBytes(p)).accept(visitor, ClassReader.SKIP_CODE);
        return visitor.result;
    } catch (IOException ex) {
        throw new UncheckedIOException(ex);
    }
}

作为奖励,resp。为了创建一些测试用例,以下方法添加了为运行时类的源构建层次结构的能力:

public static Map<String, Set<String>> getClassHierarchy(Class<?> context)
                                        throws IOException, URISyntaxException {
    Path p;
    URI clURI = context.getResource(context.getSimpleName()+".class").toURI();
    if(clURI.getScheme().equals("jrt")) p = Paths.get(URI.create("jrt:/modules"));
    else {
        if(!clURI.getScheme().equals("file")) try {
            FileSystems.getFileSystem(clURI);
        } catch(FileSystemNotFoundException ex) {
            FileSystems.newFileSystem(clURI, Collections.emptyMap());
        }
        String qn = context.getName();
        p = Paths.get(clURI).getParent();
        for(int ix = qn.indexOf('.'); ix>0; ix = qn.indexOf('.', ix+1)) p = p.getParent();
    }
    return getClassHierarchy(p);
}

然后,你可以做

Map<String, Set<String>> hierarchy = getClassHierarchy(Number.class);
System.out.println("Direct subclasses of "+Number.class);
hierarchy.getOrDefault("java.lang.Number", Collections.emptySet())
         .forEach(System.out::println);

得到

Direct subclasses of class java.lang.Number
java.lang.Float
java.math.BigDecimal
java.util.concurrent.atomic.AtomicLong
java.lang.Double
java.lang.Long
java.util.concurrent.atomic.AtomicInteger
java.lang.Short
java.math.BigInteger
java.lang.Byte
java.util.concurrent.atomic.Striped64
java.lang.Integer

Map<String, Set<String>> hierarchy = getClassHierarchy(Number.class);
System.out.println("All subclasses of "+Number.class);
printAllClasses(hierarchy, "java.lang.Number", "  ");
private static void printAllClasses(
        Map<String, Set<String>> hierarchy, String parent, String i) {
    hierarchy.getOrDefault(parent, Collections.emptySet())
        .forEach(x -> {
            System.out.println(i+x);
            printAllClasses(hierarchy, x, i+"  ");
    });
}

得到

All subclasses of class java.lang.Number
  java.lang.Float
  java.math.BigDecimal
  java.util.concurrent.atomic.AtomicLong
  java.lang.Double
  java.lang.Long
  java.util.concurrent.atomic.AtomicInteger
  java.lang.Short
  java.math.BigInteger
  java.lang.Byte
  java.util.concurrent.atomic.Striped64
    java.util.concurrent.atomic.LongAdder
    java.util.concurrent.atomic.LongAccumulator
    java.util.concurrent.atomic.DoubleAdder
    java.util.concurrent.atomic.DoubleAccumulator
  java.lang.Integer

关于java - 通过扫描文件系统查找直接和间接子类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49896798/

相关文章:

recursion - 在DOS中递归复制匹配通配符组合但不创建目录树的文件

java - 如何为camel-https4配置trustStore

java - 在java中初始化对象A等于另一个对象B

java - android-无法获取 fragment 中编辑文本的值

sql - 如何生成字符数组的二进制组合

python - 用于重命名以空格或句点结尾的文件夹的递归脚本

Python:查找列表中最大数字的递归函数

java - 为什么 Java 中的 Map<> 设计为需要两种类型; Map<> 和 HashMap<>?

algorithm - 程序可以用来简化代数表达式吗?

python - 将数组拆分为均匀分布的 block