gnodet commented on code in PR #508:
URL: https://github.com/apache/maven-jar-plugin/pull/508#discussion_r3857395405


##########
src/main/java/org/apache/maven/plugins/jar/ExcludedFiles.java:
##########
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.jar;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+
+/**
+ * A list of files to temporarily move outside the directory to package in a 
<abbr>JAR</abbr> archive.
+ * This is used for excluding files from the <abbr>JAR</abbr> archive 
according include/exclude filters.
+ * We move these files for making possible to specify the whole directory to 
the {@code jar} tool.
+ * This approach is used instead of enumerating files in arguments given to 
the {@code jar} tool because
+ * such enumeration can not contain directory entries (otherwise the whole 
directory would be included).
+ * Some software such as Spring applications component scan relies on the 
presence of directory entries.
+ */
+final class ExcludedFiles implements Closeable {
+    /**
+     * The paths of files or directories to temporarily move in another 
directory.
+     */
+    private final Path[] original;
+
+    /**
+     * The paths where files or directories were moved.
+     * For each index <var>i</var>, the original path of {@code moved[i]} was 
{@code original[i]}.
+     */
+    private final Path[] moved;
+
+    /**
+     * Temporary directory which will contain the {@code moved} files.
+     * It should be a parent directory of {@link #original} files for
+     * increasing the chances that it is on the same file system.
+     */
+    private final Path temporaryDirectory;
+
+    /**
+     * Creates a new list of files to move in a temporary directory.
+     *
+     * @param directory the directory which was scanned for files to include 
in the <abbr>JAR</abbr>
+     * @param files paths of files or directories to temporarily move in 
another directory
+     * @throws IOException if an error occurred while creating the temporary 
directory.
+     */
+    ExcludedFiles(final Path directory, final List<Path> files) throws 
IOException {
+        original = files.toArray(Path[]::new);
+        moved = new Path[original.length];
+        temporaryDirectory = Files.createTempDirectory(directory, "excluded-");
+    }
+
+    /**
+     * Moves the files now. This method should be invoked inside the "try with 
resource" block.
+     *
+     * @throws IOException if an error occurred while moving a file.
+     */
+    public void move() throws IOException {
+        for (int i = 0; i < original.length; i++) {
+            final Path source = original[i];
+            String prefix = source.getFileName().toString();
+            String suffix = null;
+            int s = prefix.lastIndexOf('.');
+            if (s > 0) {
+                suffix = prefix.substring(s);
+                prefix = prefix.substring(0, s);
+            }
+            Path target = Files.createTempFile(temporaryDirectory, prefix, 
suffix);
+            try {
+                moved[i] = Files.move(source, target, 
StandardCopyOption.REPLACE_EXISTING);

Review Comment:
   **[Important]** `Files.move(source, target, REPLACE_EXISTING)` may fail when 
`source` is a **directory** and `target` is a regular file (created by 
`createTempFile` on line 83).
   
   On POSIX systems, `rename(2)` returns `ENOTDIR` when the old path is a 
directory and the new path is a non-directory. This code path is reachable: 
`FileCollector.preVisitDirectory()` adds excluded directories to the 
`exclusion` list (line 252), which are then passed to `ExcludedFiles`.
   
   Concrete scenario — an exclude pattern like `**/internal/**` matching a 
directory:
   ```
   FileCollector.preVisitDirectory() → exclusion.add(directory)
   → ExcludedFiles.move() → createTempFile (regular file) → Files.move(dir, 
file) → ENOTDIR
   ```
   
   The current ITs only exclude individual files (`ExcludedByFilter.class`), so 
this path is untested.
   
   **Suggested fix** — delete the temp file before moving when the source is a 
directory:
   ```java
   Path target = Files.createTempFile(temporaryDirectory, prefix, suffix);
   if (Files.isDirectory(source)) {
       Files.delete(target); // rename(dir → file) fails on POSIX
   }
   try {
       moved[i] = Files.move(source, target, 
StandardCopyOption.REPLACE_EXISTING);
   ```
   
   Or alternatively, use `createTempDirectory()` for directory sources.



##########
src/main/java/org/apache/maven/plugins/jar/PomDerivation.java:
##########
@@ -0,0 +1,528 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.jar;
+
+import javax.xml.stream.XMLStreamException;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleFinder;
+import java.lang.module.ModuleReference;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import org.apache.maven.api.DependencyCoordinates;
+import org.apache.maven.api.JavaPathType;
+import org.apache.maven.api.Session;
+import org.apache.maven.api.Type;
+import org.apache.maven.api.model.Dependency;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.model.Parent;
+import org.apache.maven.api.plugin.MojoException;
+import org.apache.maven.api.services.DependencyCoordinatesFactory;
+import org.apache.maven.api.services.DependencyCoordinatesFactoryRequest;
+import org.apache.maven.api.services.DependencyResolver;
+import org.apache.maven.api.services.DependencyResolverRequest;
+import org.apache.maven.api.services.DependencyResolverResult;
+import org.apache.maven.api.services.ModelBuilderException;
+import org.apache.maven.model.v4.MavenStaxWriter;
+
+/**
+ * A mapper from Maven model dependencies to Java module names.
+ * A single instance of this class is created for a Maven project,
+ * then shared by all {@link ForModule} instances (one per module to archive).
+ */
+final class PomDerivation {
+    /**
+     * Whether to expand the list of transitive dependencies in the generated 
<abbr>POM</abbr>.
+     */
+    private static final boolean EXPAND_TRANSITIVE = false;
+
+    /**
+     * Copy of {@link AbstractJarMojo#session}.
+     */
+    private final Session session;
+
+    /**
+     * The project model, which includes dependencies of all modules.
+     */
+    private final Model projectModel;
+
+    /**
+     * The factory to use for creating temporary {@link DependencyCoordinates} 
instances.
+     */
+    private final DependencyCoordinatesFactory coordinateFactory;
+
+    /**
+     * Provide module descriptors from module names.
+     * May be {@code null} if no {@code module-info} was found.
+     */
+    private final ModuleFinder moduleFinder;
+
+    /**
+     * Module references from paths to the <abbr>JAR</abbr> file or root 
directory.
+     */
+    private final Map<URI, ModuleReference> fromURI;
+
+    /**
+     * Module names associated to Maven dependencies.
+     * This map contains {@link DependencyCoordinates#getId()} as keys and 
module references as values.
+     * This is used for detecting which dependencies are really used according 
{@code module-info.class}.
+     *
+     * @todo The keys should be instances of {@link DependencyCoordinates}. 
Unfortunately, as of Maven 4.0.0-rc-5
+     *       that interface does not define the {@code equals} and {@code 
hashCode} contracts.
+     */
+    private final Map<String, ModuleReference> fromDependency;
+
+    /**
+     * Modules that are built by the project. Keys are module names.
+     */
+    private final Map<String, Dependency> builtModules;
+
+    /**
+     * Creates a new mapper from Maven dependency to module name.
+     *
+     * @param mojo the enclosing <abbr>MOJO</abbr>
+     * @param moduleRoots paths to root directories of each module to archive 
in a module hierarchy
+     * @throws IOException if an I/O error occurred while fetching dependencies
+     * @throws MavenException if an error occurred while fetching dependencies 
for a reason other than I/O.
+     */
+    PomDerivation(final AbstractJarMojo mojo, final List<Path> moduleRoots) 
throws IOException {
+        this.session = mojo.session;
+        projectModel = mojo.project.getModel();
+        coordinateFactory = 
session.getService(DependencyCoordinatesFactory.class);
+        DependencyResolver resolver = 
session.getService(DependencyResolver.class);
+        DependencyResolverResult result = 
resolver.resolve(DependencyResolverRequest.builder()
+                .session(session)
+                .project(mojo.project)
+                .requestType(DependencyResolverRequest.RequestType.RESOLVE)
+                .pathScope(mojo.getDependencyScope())
+                .pathTypeFilter(Set.of(JavaPathType.MODULES, 
JavaPathType.CLASSES))
+                .build());
+
+        rethrow(result);
+        final Map<org.apache.maven.api.Dependency, Path> dependencies = 
result.getDependencies();
+        final Path[] allModulePaths = toRealPaths(moduleRoots, 
dependencies.values());
+        fromURI = new HashMap<>(allModulePaths.length); // TODO: use 
newHashMap with JDK19.
+        moduleFinder = ModuleFinder.of(allModulePaths);
+        if (moduleFinder != null) {
+            for (ModuleReference reference : moduleFinder.findAll()) {
+                reference.location().ifPresent((location) -> 
fromURI.put(location, reference));
+            }
+        }
+        fromDependency = new HashMap<>(dependencies.size()); // TODO: use 
newHashMap with JDK19.
+        for (Map.Entry<org.apache.maven.api.Dependency, Path> entry : 
dependencies.entrySet()) {
+            Path modulePath = entry.getValue().toRealPath();
+            ModuleReference reference = fromURI.get(modulePath.toUri());
+            if (reference != null) {
+                DependencyCoordinates coordinates = 
entry.getKey().toCoordinates();
+                String id = coordinates.getId();
+                ModuleReference old = fromDependency.putIfAbsent(id, 
reference);
+                if (old == null) {
+                    coordinates = withoutVersion(coordinates);
+                    id = coordinates.getId();
+                    old = fromDependency.putIfAbsent(id, reference);
+                }
+                if (old != null && !old.equals(reference)) {
+                    mojo.log.warn("The \"" + id + "\" dependency is declared 
twice with different module names: \""
+                            + old.descriptor().name() + "\" and \""
+                            + reference.descriptor().name() + "\".");
+                }
+            }
+        }
+        builtModules = new HashMap<>(moduleRoots.size()); // TODO: use 
newHashMap with JDK19.
+        for (Path root : moduleRoots) {
+            root = root.toRealPath();
+            ModuleDescriptor descriptor = 
fromURI.get(root.toUri()).descriptor();

Review Comment:
   **[Important]** The `toRealPath()` fix from the previous review is correct, 
but `fromURI.get(root.toUri())` can still return `null` here — e.g., if 
`ModuleFinder` could not parse a corrupt `module-info.class`. Calling 
`.descriptor()` on `null` throws NPE before the null check on line 171 is 
reached.
   
   **Suggested fix:**
   ```java
   root = root.toRealPath();
   ModuleReference ref = fromURI.get(root.toUri());
   if (ref != null) {
       ModuleDescriptor descriptor = ref.descriptor();
       if (descriptor != null) {
           builtModules.put(
                   descriptor.name(),
                   ...
       }
   }
   ```



##########
src/main/java/org/apache/maven/plugins/jar/ExcludedFiles.java:
##########
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.jar;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+
+/**
+ * A list of files to temporarily move outside the directory to package in a 
<abbr>JAR</abbr> archive.
+ * This is used for excluding files from the <abbr>JAR</abbr> archive 
according include/exclude filters.
+ * We move these files for making possible to specify the whole directory to 
the {@code jar} tool.
+ * This approach is used instead of enumerating files in arguments given to 
the {@code jar} tool because
+ * such enumeration can not contain directory entries (otherwise the whole 
directory would be included).
+ * Some software such as Spring applications component scan relies on the 
presence of directory entries.
+ */
+final class ExcludedFiles implements Closeable {
+    /**
+     * The paths of files or directories to temporarily move in another 
directory.
+     */
+    private final Path[] original;
+
+    /**
+     * The paths where files or directories were moved.
+     * For each index <var>i</var>, the original path of {@code moved[i]} was 
{@code original[i]}.
+     */
+    private final Path[] moved;
+
+    /**
+     * Temporary directory which will contain the {@code moved} files.
+     * It should be a parent directory of {@link #original} files for
+     * increasing the chances that it is on the same file system.
+     */
+    private final Path temporaryDirectory;
+
+    /**
+     * Creates a new list of files to move in a temporary directory.
+     *
+     * @param directory the directory which was scanned for files to include 
in the <abbr>JAR</abbr>
+     * @param files paths of files or directories to temporarily move in 
another directory
+     * @throws IOException if an error occurred while creating the temporary 
directory.
+     */
+    ExcludedFiles(final Path directory, final List<Path> files) throws 
IOException {
+        original = files.toArray(Path[]::new);
+        moved = new Path[original.length];
+        temporaryDirectory = Files.createTempDirectory(directory, "excluded-");
+    }
+
+    /**
+     * Moves the files now. This method should be invoked inside the "try with 
resource" block.
+     *
+     * @throws IOException if an error occurred while moving a file.
+     */
+    public void move() throws IOException {
+        for (int i = 0; i < original.length; i++) {
+            final Path source = original[i];
+            String prefix = source.getFileName().toString();
+            String suffix = null;
+            int s = prefix.lastIndexOf('.');
+            if (s > 0) {
+                suffix = prefix.substring(s);
+                prefix = prefix.substring(0, s);
+            }
+            Path target = Files.createTempFile(temporaryDirectory, prefix, 
suffix);
+            try {
+                moved[i] = Files.move(source, target, 
StandardCopyOption.REPLACE_EXISTING);

Review Comment:
   **[Important]** `Files.move(source, target, REPLACE_EXISTING)` may fail when 
`source` is a **directory** and `target` is a regular file (created by 
`createTempFile` on line 83).
   
   On POSIX systems, `rename(2)` returns `ENOTDIR` when the old path is a 
directory and the new path is a non-directory. This code path is reachable: 
`FileCollector.preVisitDirectory()` adds excluded directories to the 
`exclusion` list (line 252), which are then passed to `ExcludedFiles`.
   
   Concrete scenario — an exclude pattern like `**/internal/**` matching a 
directory:
   ```
   FileCollector.preVisitDirectory() → exclusion.add(directory)
   → ExcludedFiles.move() → createTempFile (regular file) → Files.move(dir, 
file) → ENOTDIR
   ```
   
   The current ITs only exclude individual files (`ExcludedByFilter.class`), so 
this path is untested.
   
   **Suggested fix** — delete the temp file before moving when the source is a 
directory:
   ```java
   Path target = Files.createTempFile(temporaryDirectory, prefix, suffix);
   if (Files.isDirectory(source)) {
       Files.delete(target); // rename(dir → file) fails on POSIX
   }
   try {
       moved[i] = Files.move(source, target, 
StandardCopyOption.REPLACE_EXISTING);
   ```
   
   Or alternatively, use `createTempDirectory()` for directory sources.



##########
src/main/java/org/apache/maven/plugins/jar/PomDerivation.java:
##########
@@ -0,0 +1,528 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.maven.plugins.jar;
+
+import javax.xml.stream.XMLStreamException;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UncheckedIOException;
+import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleFinder;
+import java.lang.module.ModuleReference;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import org.apache.maven.api.DependencyCoordinates;
+import org.apache.maven.api.JavaPathType;
+import org.apache.maven.api.Session;
+import org.apache.maven.api.Type;
+import org.apache.maven.api.model.Dependency;
+import org.apache.maven.api.model.Model;
+import org.apache.maven.api.model.Parent;
+import org.apache.maven.api.plugin.MojoException;
+import org.apache.maven.api.services.DependencyCoordinatesFactory;
+import org.apache.maven.api.services.DependencyCoordinatesFactoryRequest;
+import org.apache.maven.api.services.DependencyResolver;
+import org.apache.maven.api.services.DependencyResolverRequest;
+import org.apache.maven.api.services.DependencyResolverResult;
+import org.apache.maven.api.services.ModelBuilderException;
+import org.apache.maven.model.v4.MavenStaxWriter;
+
+/**
+ * A mapper from Maven model dependencies to Java module names.
+ * A single instance of this class is created for a Maven project,
+ * then shared by all {@link ForModule} instances (one per module to archive).
+ */
+final class PomDerivation {
+    /**
+     * Whether to expand the list of transitive dependencies in the generated 
<abbr>POM</abbr>.
+     */
+    private static final boolean EXPAND_TRANSITIVE = false;
+
+    /**
+     * Copy of {@link AbstractJarMojo#session}.
+     */
+    private final Session session;
+
+    /**
+     * The project model, which includes dependencies of all modules.
+     */
+    private final Model projectModel;
+
+    /**
+     * The factory to use for creating temporary {@link DependencyCoordinates} 
instances.
+     */
+    private final DependencyCoordinatesFactory coordinateFactory;
+
+    /**
+     * Provide module descriptors from module names.
+     * May be {@code null} if no {@code module-info} was found.
+     */
+    private final ModuleFinder moduleFinder;
+
+    /**
+     * Module references from paths to the <abbr>JAR</abbr> file or root 
directory.
+     */
+    private final Map<URI, ModuleReference> fromURI;
+
+    /**
+     * Module names associated to Maven dependencies.
+     * This map contains {@link DependencyCoordinates#getId()} as keys and 
module references as values.
+     * This is used for detecting which dependencies are really used according 
{@code module-info.class}.
+     *
+     * @todo The keys should be instances of {@link DependencyCoordinates}. 
Unfortunately, as of Maven 4.0.0-rc-5
+     *       that interface does not define the {@code equals} and {@code 
hashCode} contracts.
+     */
+    private final Map<String, ModuleReference> fromDependency;
+
+    /**
+     * Modules that are built by the project. Keys are module names.
+     */
+    private final Map<String, Dependency> builtModules;
+
+    /**
+     * Creates a new mapper from Maven dependency to module name.
+     *
+     * @param mojo the enclosing <abbr>MOJO</abbr>
+     * @param moduleRoots paths to root directories of each module to archive 
in a module hierarchy
+     * @throws IOException if an I/O error occurred while fetching dependencies
+     * @throws MavenException if an error occurred while fetching dependencies 
for a reason other than I/O.
+     */
+    PomDerivation(final AbstractJarMojo mojo, final List<Path> moduleRoots) 
throws IOException {
+        this.session = mojo.session;
+        projectModel = mojo.project.getModel();
+        coordinateFactory = 
session.getService(DependencyCoordinatesFactory.class);
+        DependencyResolver resolver = 
session.getService(DependencyResolver.class);
+        DependencyResolverResult result = 
resolver.resolve(DependencyResolverRequest.builder()
+                .session(session)
+                .project(mojo.project)
+                .requestType(DependencyResolverRequest.RequestType.RESOLVE)
+                .pathScope(mojo.getDependencyScope())
+                .pathTypeFilter(Set.of(JavaPathType.MODULES, 
JavaPathType.CLASSES))
+                .build());
+
+        rethrow(result);
+        final Map<org.apache.maven.api.Dependency, Path> dependencies = 
result.getDependencies();
+        final Path[] allModulePaths = toRealPaths(moduleRoots, 
dependencies.values());
+        fromURI = new HashMap<>(allModulePaths.length); // TODO: use 
newHashMap with JDK19.
+        moduleFinder = ModuleFinder.of(allModulePaths);
+        if (moduleFinder != null) {
+            for (ModuleReference reference : moduleFinder.findAll()) {
+                reference.location().ifPresent((location) -> 
fromURI.put(location, reference));
+            }
+        }
+        fromDependency = new HashMap<>(dependencies.size()); // TODO: use 
newHashMap with JDK19.
+        for (Map.Entry<org.apache.maven.api.Dependency, Path> entry : 
dependencies.entrySet()) {
+            Path modulePath = entry.getValue().toRealPath();
+            ModuleReference reference = fromURI.get(modulePath.toUri());
+            if (reference != null) {
+                DependencyCoordinates coordinates = 
entry.getKey().toCoordinates();
+                String id = coordinates.getId();
+                ModuleReference old = fromDependency.putIfAbsent(id, 
reference);
+                if (old == null) {
+                    coordinates = withoutVersion(coordinates);
+                    id = coordinates.getId();
+                    old = fromDependency.putIfAbsent(id, reference);
+                }
+                if (old != null && !old.equals(reference)) {
+                    mojo.log.warn("The \"" + id + "\" dependency is declared 
twice with different module names: \""
+                            + old.descriptor().name() + "\" and \""
+                            + reference.descriptor().name() + "\".");
+                }
+            }
+        }
+        builtModules = new HashMap<>(moduleRoots.size()); // TODO: use 
newHashMap with JDK19.
+        for (Path root : moduleRoots) {
+            root = root.toRealPath();
+            ModuleDescriptor descriptor = 
fromURI.get(root.toUri()).descriptor();

Review Comment:
   **[Important]** The `toRealPath()` fix from the previous review is correct, 
but `fromURI.get(root.toUri())` can still return `null` here — e.g., if 
`ModuleFinder` could not parse a corrupt `module-info.class`. Calling 
`.descriptor()` on `null` throws NPE before the null check on line 171 is 
reached.
   
   **Suggested fix:**
   ```java
   root = root.toRealPath();
   ModuleReference ref = fromURI.get(root.toUri());
   if (ref != null) {
       ModuleDescriptor descriptor = ref.descriptor();
       if (descriptor != null) {
           builtModules.put(
                   descriptor.name(),
                   ...
       }
   }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to