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);