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..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 @@ -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,12 +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(">", ">"); @@ -210,13 +184,31 @@ 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; + for (int i = contentStart; i < description.length(); i++) { + char c = description.charAt(i); + // Backslashes are literal characters in Javadoc; only braces affect tag nesting. + if (c == '{') { + braceDepth++; + } else if (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 2bb0b78c0..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 @@ -86,4 +86,44 @@ 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)); + } + + @Test + void decodeJavadocCodeWithMultipleLevelsOfNestedBraces() { + String description = "before {@link java.lang.String label} {@code ${outer${inner}} and " + + "\\{braces\\} and } after {@link java.lang.Object second}"; + + assertEquals( + "before java.lang.String label " + + "${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")); + } }