1

我正在尝试以编程方式加载使用 Java 平台模块系统的项目,因此我可以分析存在多少模块以及它们之间的关系。我写了以下代码:

public void retrieveModules() throws IOException {
    moduleRefs = new ArrayList<ModuleReference>();

    // search for all module-info.class
    Stream<Path> classPaths = Files.walk(Paths.get(this.basePath));
    List<String> moduleInfoClassFiles = classPaths
            .filter(p -> p.getFileName()
                    .toString()
                    .toLowerCase()
                    .contains("module-info.class"))
            .map(p -> p.toString())
            .collect(Collectors.toList());

    // load modules
    for (String classFilePath : moduleInfoClassFiles) {
        Path dir = Paths.get(classFilePath.replace("/module-info.class", ""));
        ModuleFinder finder = ModuleFinder.of(dir);
        moduleRefs.addAll(finder.findAll());
    }
}

此代码在找到模块时一直运行良好finder.findAll(),我可以检索正确的模块描述符。但是,当我尝试在 JUnit 5 上运行它时(https://github.com/junit-team/junit5

我收到以下错误消息:

Exception in thread "main" java.lang.module.FindException: Error reading module: /.../junit-team_junit5/junit-platform-engine/build/classes/java/module/org.junit.platform.commons
    at java.base/jdk.internal.module.ModulePath.readModule(ModulePath.java:350)
    at java.base/jdk.internal.module.ModulePath.scan(ModulePath.java:237)
    at java.base/jdk.internal.module.ModulePath.scanNextEntry(ModulePath.java:190)
    at java.base/jdk.internal.module.ModulePath.findAll(ModulePath.java:166)
    at RepoAnalyzer.retrieveModules(RepoAnalyzer.java:41)
    at RepoAnalyzer.main(RepoAnalyzer.java:23)
Caused by: java.lang.module.InvalidModuleDescriptorException: Package org.junit.platform.commons.util not found in module

我试图找出问题所在。我验证了两个模块都正确导出和需要包。有谁知道我做错了什么?

4

1 回答 1

1

无需对遇到的路径执行字符串操作。您的代码可以简化为

public void retrieveModules() throws IOException {
    // search for all module-info.class
    try(Stream<Path> classPaths = Files.walk(Paths.get(this.basePath))) {
        moduleRefs = ModuleFinder.of(classPaths
            .filter(p -> p.getFileName().toString().equals("module-info.class"))
            .map(Path::getParent)
            .toArray(Path[]::new)).findAll();
    }
}

假设您可以更改moduleRefsSetorCollection而不是List. 否则,将表达式包装在new ArrayList<>(…).

您似乎正在扫描默认文件系统上的“爆炸模块”,而遇到的module-info.class并没有明确声明所有包。内部发生的事情ModuleFinder相当于调用 ,ModuleDescriptor.read(InputStream in, Supplier<Set<String>> packageFinder)而提供的包查找器将扫描包含模块信息的目录的子树,以查找包含至少一个常规文件、类或资源并具有形成有效包的名称的目录姓名。所有这些都被认为是模块的包。

该方法的文档指出:

投掷

InvalidModuleDescriptorException - 如果检测到无效的模块描述符或 packageFinder 返回的包集不包括从模块描述符获得的所有包

从模块描述符获得的包在packages()方法中命名:

包集包括所有导出和打开的包,以及任何服务提供者的包,以及主类的包。

所以这个包org.junit.platform.commons.util已经被识别为强制包,module-info但是通过包查找器没有找到,即对应的目录可能不存在。

您可以尝试通过手动追溯这些步骤来缩小问题范围:

public void checkModules() throws IOException {
    try(Stream<Path> classPaths = Files.walk(Paths.get(this.basePath))) {
        classPaths
            .filter(p -> p.getFileName().toString().equals("module-info.class"))
            .forEach(this::check);
    }
}
private void check(Path path) {
    try {
        ModuleDescriptor md = md(path);
        System.out.println("module " + md.name());

        for(String pkg: md.packages()) {
            Path pLoc = path.getParent();
            for(String sub: pkg.split("\\.")) pLoc = pLoc.resolve(sub);
            System.out.print("package " + pkg + ": " + pLoc);
            if(!Files.exists(pLoc)) System.out.println(" does not exist");
            else if(!Files.isDirectory(pLoc)) System.out.println(" is not a directory");
            else if(!checkClassOrResource(pLoc))
                System.out.println(" contains no class or resource");
            else System.out.println(" seems ok");
        }
    }
    catch(IOException ex) {
        throw new UncheckedIOException(ex);
    }
}
private boolean checkClassOrResource(Path pLoc) throws IOException {
    try(Stream<Path> members = Files.list(pLoc)) {
        return members.anyMatch(Files::isRegularFile);
    }
}
private ModuleDescriptor md(Path p) throws IOException {
    try(InputStream is = Files.newInputStream(p)) {
        return ModuleDescriptor.read(is);
    }
}

这将检查与包对应的目录并打印诊断消息。请注意,ModuleDescriptor.read(InputStream is)上面使用的没有包查找器的方法变体不会验证包位置,而是仅使用在其中找到的那些module-info已经包含与跨模块关系相关的所有包。如果您对分析中的其他包不感兴趣,您可以简单地使用该方法ModuleDescriptor为每个包构造一个module-info并继续处理它们。

try(Stream<Path> classPaths = Files.walk(Paths.get(this.basePath))) {
    List<ModuleDescriptor> modules = classPaths
        .filter(p -> p.getFileName().toString().equals("module-info.class"))
        .map(path -> {
            try(InputStream is = Files.newInputStream(path)) {
                return ModuleDescriptor.read(is);
            } catch(IOException ex) {
                throw new UncheckedIOException(ex);
            }
        })
        .collect(Collectors.toList());
}
于 2021-06-21T17:07:29.267 回答