You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by sl...@apache.org on 2026/09/11 22:16:48 UTC

(maven-plugin-tools) 06/10: MPLUGIN-421: Close the source model on parse failure and index nested types

This is an automated email from the ASF dual-hosted git repository.

slachiewicz pushed a commit to branch maven-plugin-tools-3.x
in repository https://gitbox.apache.org/repos/asf/maven-plugin-tools.git

commit da8e5160169e32f8b997231bbca9ec08601df193
Author: Sylwester Lachiewicz <sl...@apache.org>
AuthorDate: Sun Aug 9 11:54:40 2026 +0200

    MPLUGIN-421: Close the source model on parse failure and index nested types
    
    JavaSourceModel.parse() allocates the URLClassLoader backing the model before
    it parses any source, and it throws on the first parse problem. Calling it from
    scanJavadoc() meant the failure happened before the try-with-resources took
    ownership, leaking the loader and keeping every dependency and reactor jar open
    - on Windows, locked. Parse inside the try-with-resources instead.
    
    The annotation scanner keys classes by their binary name (pkg.Outer$Inner)
    while JavaParser reports the canonical one (pkg.Outer.Inner), so the Javadoc
    lookup missed every mojo declared as a nested class and silently dropped its
    description, @since and @deprecated. QDox reported binary names, so this
    worked before. Index nested types under both spellings.
    
    (cherry picked from commit 9be90951a2a2a6292eeba9f079085e798818f0a2)
---
 .../JavaAnnotationsMojoDescriptorExtractor.java    | 35 ++++++++++++++++++++--
 ...JavaAnnotationsMojoDescriptorExtractorTest.java | 30 +++++++++++++++++++
 2 files changed, 63 insertions(+), 2 deletions(-)

diff --git a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor.java b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor.java
index 34357c85..6fd7737c 100644
--- a/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor.java
+++ b/maven-plugin-tools-annotations/src/main/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractor.java
@@ -221,7 +221,11 @@ public class JavaAnnotationsMojoDescriptorExtractor implements MojoDescriptorExt
             linkGenerator = null;
         }
 
+        // parse() allocates the class loader backing the model, so it must run inside the
+        // try-with-resources: a parse failure would otherwise leak it together with every
+        // dependency and reactor jar it holds open.
         try (JavaSourceModel sourceModel = scanJavadoc(request, mojoAnnotatedClasses.values())) {
+            sourceModel.parse();
             Map<String, TypeDeclaration<?>> javaClassesMap = discoverClasses(sourceModel);
             populateDataFromJavadoc(sourceModel, mojoAnnotatedClasses, javaClassesMap, linkGenerator);
         } catch (IOException e) {
@@ -296,7 +300,6 @@ public class JavaAnnotationsMojoDescriptorExtractor implements MojoDescriptorExt
             extendJavaSourceModel(request, sourceModel, mavenProject);
         }
 
-        sourceModel.parse();
         return sourceModel;
     }
 
@@ -485,11 +488,39 @@ public class JavaAnnotationsMojoDescriptorExtractor implements MojoDescriptorExt
     protected Map<String, TypeDeclaration<?>> discoverClasses(JavaSourceModel sourceModel) {
         Map<String, TypeDeclaration<?>> result = new HashMap<>();
         for (TypeDeclaration<?> type : sourceModel.getTypes()) {
-            type.getFullyQualifiedName().ifPresent(name -> result.put(name, type));
+            type.getFullyQualifiedName().ifPresent(name -> {
+                result.put(name, type);
+                // The annotation scanner keys classes by their binary name ("pkg.Outer$Inner"), while
+                // JavaParser reports the canonical one ("pkg.Outer.Inner"). Index both so that mojos
+                // declared as nested classes still find their Javadoc.
+                binaryName(type).ifPresent(binary -> result.put(binary, type));
+            });
         }
         return result;
     }
 
+    private static Optional<String> binaryName(TypeDeclaration<?> type) {
+        return type.getFullyQualifiedName().flatMap(fullyQualifiedName -> {
+            String canonicalNestedName = type.getNameAsString();
+            Node current = type;
+            while (current.getParentNode().isPresent()
+                    && current.getParentNode().get() instanceof TypeDeclaration) {
+                TypeDeclaration<?> parent =
+                        (TypeDeclaration<?>) current.getParentNode().get();
+                canonicalNestedName = parent.getNameAsString() + "." + canonicalNestedName;
+                current = parent;
+            }
+            if (!canonicalNestedName.contains(".")) {
+                // top level type: binary and canonical names are identical
+                return Optional.empty();
+            }
+            String binaryNestedName = canonicalNestedName.replace('.', '$');
+            return Optional.of(
+                    fullyQualifiedName.substring(0, fullyQualifiedName.length() - canonicalNestedName.length())
+                            + binaryNestedName);
+        });
+    }
+
     protected void extendJavaSourceModelWithSourcesJar(
             JavaSourceModel sourceModel, Artifact artifact, PluginToolsRequest request, String classifier)
             throws ExtractionException, IOException {
diff --git a/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractorTest.java b/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractorTest.java
index 10d48ae3..54b53d92 100644
--- a/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractorTest.java
+++ b/maven-plugin-tools-annotations/src/test/java/org/apache/maven/tools/plugin/extractor/annotations/JavaAnnotationsMojoDescriptorExtractorTest.java
@@ -20,11 +20,14 @@ package org.apache.maven.tools.plugin.extractor.annotations;
 
 import java.io.IOException;
 import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
+import java.util.Map;
 
+import com.github.javaparser.ast.body.TypeDeclaration;
 import org.apache.maven.artifact.Artifact;
 import org.apache.maven.artifact.DefaultArtifact;
 import org.apache.maven.plugin.AbstractMojo;
@@ -40,6 +43,7 @@ import org.junit.jupiter.api.io.TempDir;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class JavaAnnotationsMojoDescriptorExtractorTest {
     @TempDir
@@ -65,6 +69,32 @@ class JavaAnnotationsMojoDescriptorExtractorTest {
         return mojoDescriptors.get(0);
     }
 
+    @Test
+    void discoverClassesIndexesNestedTypesUnderTheirBinaryNameAsWell() throws Exception {
+        Path sourceDirectory = Files.createDirectories(targetDir.resolve("sources"));
+        Path source = sourceDirectory.resolve("example/Outer.java");
+        Files.createDirectories(source.getParent());
+        Files.write(
+                source,
+                "package example; public class Outer { public static class Inner {} }\n"
+                        .getBytes(StandardCharsets.UTF_8));
+
+        try (JavaSourceModel sourceModel = new JavaSourceModel(StandardCharsets.UTF_8)) {
+            sourceModel.addSourceDirectory(sourceDirectory.toFile());
+            sourceModel.parse();
+
+            Map<String, TypeDeclaration<?>> classes =
+                    new JavaAnnotationsMojoDescriptorExtractor().discoverClasses(sourceModel);
+
+            assertTrue(classes.containsKey("example.Outer"));
+            // the annotation scanner keys classes by their binary name, so nested types must be
+            // reachable as "example.Outer$Inner" too, otherwise their Javadoc is silently dropped
+            assertTrue(classes.containsKey("example.Outer.Inner"));
+            assertTrue(classes.containsKey("example.Outer$Inner"));
+            assertEquals(classes.get("example.Outer.Inner"), classes.get("example.Outer$Inner"));
+        }
+    }
+
     @Test
     void assertFooMojo() throws Exception {
         MojoDescriptor mojoDescriptor = extractDescriptorFromMojoClass(FooMojo.class);