From 8b181681f18a3f616805b73b62d9a77287c9d21c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=B6kdemir?= Date: Fri, 25 Sep 2026 13:57:08 +0300 Subject: [PATCH 1/4] fix: preserve closing braces in javadoc code tags --- .../tools/plugin/generator/GeneratorUtils.java | 2 +- .../plugin/generator/GeneratorUtilsTest.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/GeneratorUtils.java b/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/GeneratorUtils.java index 5a7fd89fd..48cdd5ab5 100644 --- a/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/GeneratorUtils.java +++ b/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/GeneratorUtils.java @@ -172,7 +172,7 @@ static String decodeJavadocTags(String description) { StringBuffer decoded = new StringBuffer(description.length() + 1024); - Matcher matcher = Pattern.compile("\\{@(\\w+)\\s*([^\\}]*)\\}").matcher(description); + Matcher matcher = Pattern.compile("\\{@(\\w+)\\s*((?:\\{[^\\}]*\\}|[^\\}])*)\\}").matcher(description); while (matcher.find()) { String tag = matcher.group(1); String text = matcher.group(2); diff --git a/maven-plugin-tools-generators/src/test/java/org/apache/maven/tools/plugin/generator/GeneratorUtilsTest.java b/maven-plugin-tools-generators/src/test/java/org/apache/maven/tools/plugin/generator/GeneratorUtilsTest.java index 2bb0b78c0..3e25665df 100644 --- a/maven-plugin-tools-generators/src/test/java/org/apache/maven/tools/plugin/generator/GeneratorUtilsTest.java +++ b/maven-plugin-tools-generators/src/test/java/org/apache/maven/tools/plugin/generator/GeneratorUtilsTest.java @@ -86,4 +86,21 @@ void excludeProvidedScopeFormComponentDependencies() { assertEquals(a3.getVersion(), componentDependency2.getVersion()); assertEquals(a3.getType(), componentDependency2.getType()); } + + @Test + void decodeJavadocCodeWithNestedClosingBrace() { + String description = "{@code \n" + + " \n" + + " ${project.basedir}/lib/agent.jar\n" + + " \n" + + "\n} after"; + + assertEquals( + "<javaAgents>\n" + + " <agent>\n" + + " <agentPath>${project.basedir}/lib/agent.jar</agentPath>\n" + + " </agent>\n" + + "</javaAgents>\n after", + GeneratorUtils.decodeJavadocTags(description)); + } } From c61286fa00799f600b5e0b0a408d43c9b43a9d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=B6kdemir?= Date: Sat, 17 Oct 2026 10:04:12 +0300 Subject: [PATCH 2/4] style: apply Spotless formatting to Javadoc tag fix --- .../apache/maven/tools/plugin/generator/GeneratorUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/GeneratorUtils.java b/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/GeneratorUtils.java index 48cdd5ab5..3fcf6dff5 100644 --- a/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/GeneratorUtils.java +++ b/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/GeneratorUtils.java @@ -172,7 +172,8 @@ static String decodeJavadocTags(String description) { StringBuffer decoded = new StringBuffer(description.length() + 1024); - Matcher matcher = Pattern.compile("\\{@(\\w+)\\s*((?:\\{[^\\}]*\\}|[^\\}])*)\\}").matcher(description); + Matcher matcher = + Pattern.compile("\\{@(\\w+)\\s*((?:\\{[^\\}]*\\}|[^\\}])*)\\}").matcher(description); while (matcher.find()) { String tag = matcher.group(1); String text = matcher.group(2); From 363604380dd6e91aefb9789ea0059a6a0ac4425d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=B6kdemir?= Date: Fri, 30 Oct 2026 02:52:02 +0300 Subject: [PATCH 3/4] Handle nested braces in Javadoc inline tags Track balanced braces and escaped delimiters when decoding legacy inline tags. Add a regression covering nested property expressions and adjacent Javadoc tags. --- .../plugin/generator/GeneratorUtils.java | 79 +++++++++---------- .../plugin/generator/GeneratorUtilsTest.java | 12 +++ 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/GeneratorUtils.java b/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/GeneratorUtils.java index 3fcf6dff5..5acc9b7d9 100644 --- a/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/GeneratorUtils.java +++ b/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/GeneratorUtils.java @@ -122,40 +122,6 @@ public static List toComponentDependencies(CollectionString for the specified String. This method - * produces a String that will work as a literal replacement s in the - * appendReplacement method of the {@link Matcher} class. The String produced will - * match the sequence of characters in s treated as a literal sequence. Slashes ('\') and dollar - * signs ('$') will be given no special meaning. TODO: copied from Matcher class of Java 1.5, remove once target - * platform can be upgraded - * - * @see java.util.regex.Matcher - * @param s The string to be literalized - * @return A literal string replacement - */ - private static String quoteReplacement(String s) { - if ((s.indexOf('\\') == -1) && (s.indexOf('$') == -1)) { - return s; - } - - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - if (c == '\\') { - sb.append('\\'); - sb.append('\\'); - } else if (c == '$') { - sb.append('\\'); - sb.append('$'); - } else { - sb.append(c); - } - } - - return sb.toString(); - } - /** * Decodes javadoc inline tags into equivalent HTML tags. For instance, the inline tag "{@code }" should be * rendered as "<A&B>". @@ -170,13 +136,20 @@ static String decodeJavadocTags(String description) { return ""; } - StringBuffer decoded = new StringBuffer(description.length() + 1024); + StringBuilder decoded = new StringBuilder(description.length() + 1024); + + Matcher matcher = Pattern.compile("\\{@(\\w+)\\s*").matcher(description); + int copiedUntil = 0; + int searchFrom = 0; + while (matcher.find(searchFrom)) { + int tagEnd = findInlineTagEnd(description, matcher.end()); + if (tagEnd < 0) { + searchFrom = matcher.start() + 2; + continue; + } - Matcher matcher = - Pattern.compile("\\{@(\\w+)\\s*((?:\\{[^\\}]*\\}|[^\\}])*)\\}").matcher(description); - while (matcher.find()) { String tag = matcher.group(1); - String text = matcher.group(2); + String text = description.substring(matcher.end(), tagEnd); text = text.replace("&", "&"); text = text.replace("<", "<"); text = text.replace(">", ">"); @@ -211,13 +184,37 @@ static String decodeJavadocTags(String description) { text = "" + text + ""; } } - matcher.appendReplacement(decoded, (text != null) ? quoteReplacement(text) : ""); + + decoded.append(description, copiedUntil, matcher.start()); + decoded.append((text != null) ? text : ""); + copiedUntil = tagEnd + 1; + searchFrom = copiedUntil; } - matcher.appendTail(decoded); + decoded.append(description, copiedUntil, description.length()); return decoded.toString(); } + private static int findInlineTagEnd(String description, int contentStart) { + int braceDepth = 1; + int precedingBackslashes = 0; + for (int i = contentStart; i < description.length(); i++) { + char c = description.charAt(i); + if (c == '\\') { + precedingBackslashes++; + continue; + } + boolean escaped = (precedingBackslashes & 1) != 0; + precedingBackslashes = 0; + if (!escaped && c == '{') { + braceDepth++; + } else if (!escaped && c == '}' && --braceDepth == 0) { + return i; + } + } + return -1; + } + /** * Fixes some javadoc comment to become a valid XHTML snippet. * diff --git a/maven-plugin-tools-generators/src/test/java/org/apache/maven/tools/plugin/generator/GeneratorUtilsTest.java b/maven-plugin-tools-generators/src/test/java/org/apache/maven/tools/plugin/generator/GeneratorUtilsTest.java index 3e25665df..fa80926a9 100644 --- a/maven-plugin-tools-generators/src/test/java/org/apache/maven/tools/plugin/generator/GeneratorUtilsTest.java +++ b/maven-plugin-tools-generators/src/test/java/org/apache/maven/tools/plugin/generator/GeneratorUtilsTest.java @@ -103,4 +103,16 @@ void decodeJavadocCodeWithNestedClosingBrace() { + "</javaAgents>\n after", GeneratorUtils.decodeJavadocTags(description)); } + + @Test + void decodeJavadocCodeWithMultipleLevelsOfNestedBraces() { + String description = "before {@link java.lang.String label} {@code ${outer${inner}} and " + + "\\} and \\{escaped } after {@link java.lang.Object second}"; + + assertEquals( + "before java.lang.String label " + + "${outer${inner}} and \\} and \\{escaped <x> after " + + "java.lang.Object second", + GeneratorUtils.decodeJavadocTags(description)); + } } From 07ae945444a47abc84b74c30afc4d379eacb638b Mon Sep 17 00:00:00 2001 From: Efe Date: Tue, 3 Nov 2026 21:21:24 +0300 Subject: [PATCH 4/4] Preserve literal backslashes in Javadoc inline tags --- .../tools/plugin/generator/GeneratorUtils.java | 12 +++--------- .../plugin/generator/GeneratorUtilsTest.java | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/GeneratorUtils.java b/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/GeneratorUtils.java index 5acc9b7d9..565a6d60a 100644 --- a/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/GeneratorUtils.java +++ b/maven-plugin-tools-generators/src/main/java/org/apache/maven/tools/plugin/generator/GeneratorUtils.java @@ -197,18 +197,12 @@ static String decodeJavadocTags(String description) { private static int findInlineTagEnd(String description, int contentStart) { int braceDepth = 1; - int precedingBackslashes = 0; for (int i = contentStart; i < description.length(); i++) { char c = description.charAt(i); - if (c == '\\') { - precedingBackslashes++; - continue; - } - boolean escaped = (precedingBackslashes & 1) != 0; - precedingBackslashes = 0; - if (!escaped && c == '{') { + // Backslashes are literal characters in Javadoc; only braces affect tag nesting. + if (c == '{') { braceDepth++; - } else if (!escaped && c == '}' && --braceDepth == 0) { + } else if (c == '}' && --braceDepth == 0) { return i; } } diff --git a/maven-plugin-tools-generators/src/test/java/org/apache/maven/tools/plugin/generator/GeneratorUtilsTest.java b/maven-plugin-tools-generators/src/test/java/org/apache/maven/tools/plugin/generator/GeneratorUtilsTest.java index fa80926a9..ad521e091 100644 --- a/maven-plugin-tools-generators/src/test/java/org/apache/maven/tools/plugin/generator/GeneratorUtilsTest.java +++ b/maven-plugin-tools-generators/src/test/java/org/apache/maven/tools/plugin/generator/GeneratorUtilsTest.java @@ -107,12 +107,23 @@ void decodeJavadocCodeWithNestedClosingBrace() { @Test void decodeJavadocCodeWithMultipleLevelsOfNestedBraces() { String description = "before {@link java.lang.String label} {@code ${outer${inner}} and " - + "\\} and \\{escaped } after {@link java.lang.Object second}"; + + "\\{braces\\} and } after {@link java.lang.Object second}"; assertEquals( "before java.lang.String label " - + "${outer${inner}} and \\} and \\{escaped <x> after " + + "${outer${inner}} and \\{braces\\} and <x> after " + "java.lang.Object second", GeneratorUtils.decodeJavadocTags(description)); } + + @Test + void decodeJavadocCodeEndingWithBackslashes() { + assertEquals("before C:\\ after", GeneratorUtils.decodeJavadocTags("before {@code C:\\} after")); + assertEquals("C:\\\\", GeneratorUtils.decodeJavadocTags("{@code C:\\\\}")); + } + + @Test + void decodeJavadocLiteralEndingWithBackslash() { + assertEquals("before C:\\ after", GeneratorUtils.decodeJavadocTags("before {@literal C:\\} after")); + } }