From af6bb34889c663a629ec683f665d61d589029b89 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Thu, 19 Mar 2026 23:36:02 +0100 Subject: [PATCH 1/6] Add JavadocToMarkdownDocComment recipe for JEP 467 Convert traditional Javadoc comments (/** ... */) to Markdown documentation comments (///) as supported by Java 23+. Transforms HTML constructs like
, , , 

, and lists to their Markdown equivalents, and converts inline tags like {@code} and {@link} to Markdown syntax. --- .../lang/JavadocToMarkdownDocComment.java | 634 ++++++++++++++++++ .../resources/META-INF/rewrite/recipes.csv | 11 +- .../lang/JavadocToMarkdownDocCommentTest.java | 474 +++++++++++++ 3 files changed, 1114 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java create mode 100644 src/test/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocCommentTest.java diff --git a/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java b/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java new file mode 100644 index 0000000000..aa57037234 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java @@ -0,0 +1,634 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.openrewrite.java.migrate.lang; + +import lombok.Getter; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaPrinter; +import org.openrewrite.java.search.UsesJavaVersion; +import org.openrewrite.java.tree.*; +import org.openrewrite.marker.Markers; + +import java.util.*; + +public class JavadocToMarkdownDocComment extends Recipe { + + @Getter + final String displayName = "Convert Javadoc to Markdown documentation comments"; + + @Getter + final String description = "Convert traditional Javadoc comments (`/** ... */`) to Markdown documentation comments (`///`) " + + "as supported by JEP 467 in Java 23+. Transforms HTML constructs like `

`, ``, ``, `

`, and lists " + + "to their Markdown equivalents, and converts inline tags like `{@code}` and `{@link}` to Markdown syntax."; + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesJavaVersion<>(23), new JavaIsoVisitor() { + @Override + public Space visitSpace(Space space, Space.Location loc, ExecutionContext ctx) { + List comments = space.getComments(); + boolean hasJavadoc = false; + for (Comment comment : comments) { + if (comment instanceof Javadoc.DocComment) { + hasJavadoc = true; + break; + } + } + if (!hasJavadoc) { + return space; + } + String spaceWhitespace = space.getWhitespace(); + return space.withComments(ListUtils.flatMap(comments, comment -> { + if (comment instanceof Javadoc.DocComment) { + return convertDocComment((Javadoc.DocComment) comment, spaceWhitespace); + } + return comment; + })); + } + }); + } + + private static List convertDocComment(Javadoc.DocComment docComment, String spaceWhitespace) { + // Derive the indentation from the space whitespace (e.g., "\n " → " ") + String indentation; + int lastNewline = spaceWhitespace.lastIndexOf('\n'); + if (lastNewline >= 0) { + indentation = spaceWhitespace.substring(lastNewline + 1); + } else { + indentation = spaceWhitespace; + } + + JavadocToMarkdownConverter converter = new JavadocToMarkdownConverter(); + converter.convert(docComment.getBody()); + + List lines = converter.getLines(); + + // Strip exactly one leading space from each line (from the space after * in javadoc) + // Also right-trim each line + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + if (line.startsWith(" ")) { + line = line.substring(1); + } + lines.set(i, rtrim(line)); + } + + // Skip leading empty lines (from the LineBreak after /**) + int start = 0; + while (start < lines.size() && lines.get(start).isEmpty()) { + start++; + } + + // Find end excluding trailing empty lines + int end = lines.size(); + while (end > start && lines.get(end - 1).isEmpty()) { + end--; + } + + // Collapse consecutive blank lines into a single blank line + List collapsed = new ArrayList<>(); + boolean prevBlank = false; + for (int i = start; i < end; i++) { + String line = lines.get(i); + if (line.isEmpty()) { + if (!prevBlank) { + collapsed.add(line); + } + prevBlank = true; + } else { + collapsed.add(line); + prevBlank = false; + } + } + lines = collapsed; + + if (lines.isEmpty()) { + lines.add(""); + } + + List result = new ArrayList<>(); + String suffix = docComment.getSuffix(); + + for (int i = 0; i < lines.size(); i++) { + String lineContent = lines.get(i); + String lineSuffix; + if (i < lines.size() - 1) { + lineSuffix = "\n" + indentation; + } else { + lineSuffix = suffix; + } + // TextComment(false, text, suffix, markers) prints as "// + text" + // So text="/ content" produces "/// content" + String text = lineContent.isEmpty() ? "/" : "/ " + lineContent; + result.add(new TextComment(false, text, lineSuffix, Markers.EMPTY)); + } + + return result; + } + + private static String rtrim(String s) { + int end = s.length(); + while (end > 0 && s.charAt(end - 1) == ' ') { + end--; + } + return s.substring(0, end); + } + + static class JavadocToMarkdownConverter { + private final List lines = new ArrayList<>(); + private StringBuilder currentLine = new StringBuilder(); + private boolean inPre = false; + private final Deque listStack = new ArrayDeque<>(); + private final Deque listCounterStack = new ArrayDeque<>(); + + List getLines() { + flushLine(); + return lines; + } + + void convert(List body) { + for (Javadoc node : body) { + convertNode(node); + } + } + + private void convertNode(Javadoc node) { + if (node instanceof Javadoc.Text) { + convertText((Javadoc.Text) node); + } else if (node instanceof Javadoc.LineBreak) { + convertLineBreak((Javadoc.LineBreak) node); + } else if (node instanceof Javadoc.Literal) { + convertLiteral((Javadoc.Literal) node); + } else if (node instanceof Javadoc.Link) { + convertLink((Javadoc.Link) node); + } else if (node instanceof Javadoc.StartElement) { + convertStartElement((Javadoc.StartElement) node); + } else if (node instanceof Javadoc.EndElement) { + convertEndElement((Javadoc.EndElement) node); + } else if (node instanceof Javadoc.Parameter) { + convertParameter((Javadoc.Parameter) node); + } else if (node instanceof Javadoc.Return) { + convertReturn((Javadoc.Return) node); + } else if (node instanceof Javadoc.Throws) { + convertThrows((Javadoc.Throws) node); + } else if (node instanceof Javadoc.See) { + convertSee((Javadoc.See) node); + } else if (node instanceof Javadoc.Since) { + convertSince((Javadoc.Since) node); + } else if (node instanceof Javadoc.Author) { + convertAuthor((Javadoc.Author) node); + } else if (node instanceof Javadoc.Deprecated) { + convertDeprecated((Javadoc.Deprecated) node); + } else if (node instanceof Javadoc.InheritDoc) { + currentLine.append("{@inheritDoc}"); + } else if (node instanceof Javadoc.Snippet) { + convertSnippet((Javadoc.Snippet) node); + } else if (node instanceof Javadoc.DocRoot) { + currentLine.append("{@docRoot}"); + } else if (node instanceof Javadoc.InlinedValue) { + convertInlinedValue((Javadoc.InlinedValue) node); + } else if (node instanceof Javadoc.Version) { + convertVersion((Javadoc.Version) node); + } else if (node instanceof Javadoc.Hidden) { + convertHidden((Javadoc.Hidden) node); + } else if (node instanceof Javadoc.Index) { + convertIndex((Javadoc.Index) node); + } else if (node instanceof Javadoc.Summary) { + convertSummary((Javadoc.Summary) node); + } else if (node instanceof Javadoc.UnknownBlock) { + convertUnknownBlock((Javadoc.UnknownBlock) node); + } else if (node instanceof Javadoc.UnknownInline) { + convertUnknownInline((Javadoc.UnknownInline) node); + } else if (node instanceof Javadoc.Erroneous) { + currentLine.append(((Javadoc.Erroneous) node).getText()); + } else if (node instanceof Javadoc.Reference) { + currentLine.append(printReference((Javadoc.Reference) node)); + } + } + + private void convertText(Javadoc.Text text) { + currentLine.append(decodeHtmlEntities(text.getText())); + } + + private void convertLineBreak(Javadoc.LineBreak lineBreak) { + lines.add(currentLine.toString()); + currentLine = new StringBuilder(); + } + + private void flushLine() { + if (currentLine.length() > 0) { + lines.add(currentLine.toString()); + currentLine = new StringBuilder(); + } else if (lines.isEmpty()) { + lines.add(currentLine.toString()); + currentLine = new StringBuilder(); + } + } + + private void convertLiteral(Javadoc.Literal literal) { + String content = stripLeadingSpace(renderInline(literal.getDescription())); + if (literal.isCode()) { + if (content.contains("\n")) { + // Multi-line: use fenced code block + currentLine.append("```"); + lines.add(currentLine.toString()); + for (String line : content.split("\n", -1)) { + lines.add(line); + } + currentLine = new StringBuilder("```"); + } else { + currentLine.append('`').append(content).append('`'); + } + } else { + currentLine.append(content); + } + } + + private void convertLink(Javadoc.Link link) { + String ref = printReference(link.getTreeReference()); + String label = stripLeadingSpace(renderInline(link.getLabel())).trim(); + + if (!label.isEmpty()) { + currentLine.append('[').append(label).append("][").append(ref).append(']'); + } else { + currentLine.append('[').append(ref).append(']'); + } + } + + private void convertStartElement(Javadoc.StartElement element) { + String name = element.getName().toLowerCase(); + if (inPre && !"pre".equals(name)) { + renderHtmlStartElement(element); + return; + } + switch (name) { + case "pre": + inPre = true; + currentLine.append("```"); + break; + case "code": + if (!inPre) { + currentLine.append('`'); + } + break; + case "p": + // Blank line for paragraph + lines.add(currentLine.toString()); + lines.add(""); + currentLine = new StringBuilder(); + break; + case "em": + case "i": + currentLine.append('_'); + break; + case "strong": + case "b": + currentLine.append("**"); + break; + case "ul": + listStack.push("ul"); + lines.add(currentLine.toString()); + currentLine = new StringBuilder(); + break; + case "ol": + listStack.push("ol"); + listCounterStack.push(1); + lines.add(currentLine.toString()); + currentLine = new StringBuilder(); + break; + case "li": + if (!listStack.isEmpty()) { + String listType = listStack.peek(); + if ("ol".equals(listType)) { + int count = listCounterStack.pop(); + currentLine.append(count).append(". "); + listCounterStack.push(count + 1); + } else { + currentLine.append("- "); + } + } + break; + default: + renderHtmlStartElement(element); + break; + } + } + + private void renderHtmlStartElement(Javadoc.StartElement element) { + currentLine.append('<').append(element.getName()); + for (Javadoc attr : element.getAttributes()) { + if (attr instanceof Javadoc.Attribute) { + Javadoc.Attribute a = (Javadoc.Attribute) attr; + currentLine.append(' ').append(a.getName()); + List value = a.getValue(); + if (value != null && !value.isEmpty()) { + currentLine.append('=').append(renderInline(value)); + } + } + } + if (element.isSelfClosing()) { + currentLine.append('/'); + } + currentLine.append('>'); + } + + private void convertEndElement(Javadoc.EndElement element) { + String name = element.getName().toLowerCase(); + if (inPre && !"pre".equals(name)) { + currentLine.append("'); + return; + } + switch (name) { + case "pre": + inPre = false; + currentLine.append("```"); + break; + case "code": + if (!inPre) { + currentLine.append('`'); + } + break; + case "em": + case "i": + currentLine.append('_'); + break; + case "strong": + case "b": + currentLine.append("**"); + break; + case "ul": + if (!listStack.isEmpty()) { + listStack.pop(); + } + break; + case "ol": + if (!listStack.isEmpty()) { + listStack.pop(); + } + if (!listCounterStack.isEmpty()) { + listCounterStack.pop(); + } + break; + case "li": + // End of list item handled naturally by line breaks + break; + case "p": + //

is often implicit, ignore + break; + default: + // Pass through unknown end elements + currentLine.append("'); + break; + } + } + + private void convertParameter(Javadoc.Parameter param) { + currentLine.append("@param"); + convert(param.getSpaceBeforeName()); + J name = param.getName(); + if (name != null) { + currentLine.append(printJ(name)); + } + Javadoc.Reference nameRef = param.getNameReference(); + if (nameRef != null && nameRef.getTree() != null && name == null) { + currentLine.append(printJ(nameRef.getTree())); + } + convert(param.getDescription()); + } + + private void convertReturn(Javadoc.Return ret) { + currentLine.append("@return"); + convert(ret.getDescription()); + } + + private void convertThrows(Javadoc.Throws thr) { + currentLine.append(thr.isThrowsKeyword() ? "@throws " : "@exception "); + J exceptionName = thr.getExceptionName(); + if (exceptionName != null) { + currentLine.append(printJ(exceptionName)); + } + convert(thr.getDescription()); + } + + private void convertSee(Javadoc.See see) { + currentLine.append("@see"); + for (Javadoc node : see.getSpaceBeforeTree()) { + convertNode(node); + } + Javadoc.Reference treeRef = see.getTreeReference(); + if (treeRef != null) { + currentLine.append(printReference(treeRef)); + } else { + J tree = see.getTree(); + if (tree != null) { + currentLine.append(printJRef(tree)); + } + } + convert(see.getReference()); + } + + private void convertSince(Javadoc.Since since) { + currentLine.append("@since"); + convert(since.getDescription()); + } + + private void convertAuthor(Javadoc.Author author) { + currentLine.append("@author"); + convert(author.getName()); + } + + private void convertDeprecated(Javadoc.Deprecated deprecated) { + currentLine.append("@deprecated"); + convert(deprecated.getDescription()); + } + + private void convertSnippet(Javadoc.Snippet snippet) { + currentLine.append("{@snippet"); + convert(snippet.getAttributes()); + convert(snippet.getContent()); + currentLine.append('}'); + } + + private void convertInlinedValue(Javadoc.InlinedValue value) { + currentLine.append("{@value"); + J tree = value.getTree(); + if (tree != null) { + currentLine.append(' ').append(printJ(tree)); + } + currentLine.append('}'); + } + + private void convertVersion(Javadoc.Version version) { + currentLine.append("@version"); + convert(version.getBody()); + } + + private void convertHidden(Javadoc.Hidden hidden) { + currentLine.append("@hidden"); + convert(hidden.getBody()); + } + + private void convertIndex(Javadoc.Index index) { + currentLine.append("{@index"); + convert(index.getSearchTerm()); + convert(index.getDescription()); + currentLine.append('}'); + } + + private void convertSummary(Javadoc.Summary summary) { + currentLine.append("{@summary"); + convert(summary.getSummary()); + currentLine.append('}'); + } + + private void convertUnknownBlock(Javadoc.UnknownBlock block) { + currentLine.append('@').append(block.getName()); + convert(block.getContent()); + } + + private void convertUnknownInline(Javadoc.UnknownInline inline) { + currentLine.append("{@").append(inline.getName()); + convert(inline.getContent()); + currentLine.append('}'); + } + + private String renderInline(List body) { + JavadocToMarkdownConverter inlineConverter = new JavadocToMarkdownConverter(); + inlineConverter.inPre = this.inPre; + inlineConverter.convert(body); + List inlineLines = inlineConverter.getLines(); + return String.join("\n", inlineLines); + } + + private static String printReference(Javadoc.Reference ref) { + if (ref == null) { + return ""; + } + J tree = ref.getTree(); + if (tree == null) { + return ""; + } + return printJRef(tree); + } + + /** + * Print a J tree as a Javadoc-style reference (using # for members instead of .) + */ + private static String printJRef(J tree) { + if (tree instanceof J.Identifier) { + return ((J.Identifier) tree).getSimpleName(); + } + if (tree instanceof J.FieldAccess) { + J.FieldAccess fa = (J.FieldAccess) tree; + String target = printJRef(fa.getTarget()); + String name = fa.getSimpleName(); + if (target.isEmpty()) { + return "#" + name; + } + return target + "#" + name; + } + if (tree instanceof J.MemberReference) { + J.MemberReference mr = (J.MemberReference) tree; + return printJRef(mr.getContaining()) + "#" + printJRef(mr.getReference()); + } + if (tree instanceof J.MethodInvocation) { + J.MethodInvocation mi = (J.MethodInvocation) tree; + StringBuilder sb = new StringBuilder(); + if (mi.getSelect() != null) { + sb.append(printJRef(mi.getSelect())); + sb.append('#'); + } + sb.append(mi.getSimpleName()); + sb.append('('); + List args = mi.getArguments(); + for (int i = 0; i < args.size(); i++) { + if (i > 0) sb.append(", "); + Expression arg = args.get(i); + if (!(arg instanceof J.Empty)) { + sb.append(printJRef(arg)); + } + } + sb.append(')'); + return sb.toString(); + } + return printJ(tree); + } + + private static String printJ(J tree) { + if (tree instanceof J.Identifier) { + return ((J.Identifier) tree).getSimpleName(); + } + if (tree instanceof J.FieldAccess) { + J.FieldAccess fa = (J.FieldAccess) tree; + return printJ(fa.getTarget()) + "." + fa.getSimpleName(); + } + if (tree instanceof J.MemberReference) { + J.MemberReference mr = (J.MemberReference) tree; + return printJ(mr.getContaining()) + "#" + printJ(mr.getReference()); + } + if (tree instanceof J.ParameterizedType) { + J.ParameterizedType pt = (J.ParameterizedType) tree; + StringBuilder sb = new StringBuilder(printJ(pt.getClazz())); + if (pt.getTypeParameters() != null) { + sb.append('<'); + for (int i = 0; i < pt.getTypeParameters().size(); i++) { + if (i > 0) sb.append(", "); + sb.append(printJ((J) pt.getTypeParameters().get(i))); + } + sb.append('>'); + } + return sb.toString(); + } + if (tree instanceof J.ArrayType) { + return printJ(((J.ArrayType) tree).getElementType()) + "[]"; + } + if (tree instanceof J.Primitive) { + return ((J.Primitive) tree).getType().getKeyword(); + } + try { + return tree.print(new JavaPrinter<>()).trim(); + } catch (Exception e) { + return tree.toString(); + } + } + + private static String stripLeadingSpace(String s) { + if (s.startsWith(" ")) { + return s.substring(1); + } + return s; + } + + private static String decodeHtmlEntities(String text) { + if (!text.contains("&")) { + return text; + } + return text + .replace("<", "<") + .replace(">", ">") + .replace("&", "&") + .replace(""", "\"") + .replace("'", "'") + .replace(" ", " ") + .replace("@", "@"); + } + } +} diff --git a/src/main/resources/META-INF/rewrite/recipes.csv b/src/main/resources/META-INF/rewrite/recipes.csv index 4bee6c6837..4335a6441e 100644 --- a/src/main/resources/META-INF/rewrite/recipes.csv +++ b/src/main/resources/META-INF/rewrite/recipes.csv @@ -49,7 +49,7 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.U maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.InternalBindPackages,Use `com.sun.xml.bind.*` instead of `com.sun.xml.internal.bind.*`,Do not use APIs from `com.sun.xml.internal.bind.*` packages.,2,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.RemovedPolicy,Replace `javax.security.auth.Policy` with `java.security.Policy`,The `javax.security.auth.Policy` class is not available from Java SE 11 onwards.,2,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.ThreadStopDestroy,Remove `Thread.destroy()` and `Thread.stop(Throwable)`,"The `java.lang.Thread.destroy()` method was never implemented, and the `java.lang.Thread.stop(java.lang.Throwable)` method has been unusable since Java SE 8. This recipe removes any usage of these methods from your application.",3,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeToJava17,Migrate to Java 17,"This recipe will apply changes commonly needed when migrating to Java 17. Specifically, for those applications that are built on Java 8, this recipe will update and add dependencies on J2EE libraries that are no longer directly bundled with the JDK. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy. Build files will also be updated to use Java 17 as the target/source and plugins will be also be upgraded to versions that are compatible with Java 17.",4488,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeToJava17,Migrate to Java 17,"This recipe will apply changes commonly needed when migrating to Java 17. Specifically, for those applications that are built on Java 8, this recipe will update and add dependencies on J2EE libraries that are no longer directly bundled with the JDK. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy. Build files will also be updated to use Java 17 as the target/source and plugins will be also be upgraded to versions that are compatible with Java 17.",4491,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeBuildToJava17,Upgrade build to Java 17,Updates build files to use Java 17 as the target/source.,3158,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradePluginsForJava17,Upgrade plugins to Java 17 compatible versions,Updates plugins to version compatible with Java 17.,8,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.DeprecatedJavaxSecurityCert,Use `java.security.cert` instead of `javax.security.cert`,The `javax.security.cert` package has been deprecated for removal.,2,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, @@ -70,7 +70,7 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.R maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.RemovedRuntimeTraceMethods,Remove `Runtime.traceInstructions(boolean)` and `Runtime.traceMethodCalls` methods,The `traceInstructions` and `traceMethodCalls` methods in `java.lang.Runtime` were deprecated in Java SE 9 and are no longer available in Java SE 13 and later. The recipe removes the invocations of these methods since the method invocations do nothing functionally.,3,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.AddLombokMapstructBinding,Add `lombok-mapstruct-binding` when both MapStruct and Lombok are used,Add the `lombok-mapstruct-binding` annotation processor as needed when both MapStruct and Lombok are used.,5,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.AddLombokMapstructBindingMavenDependencyOnly,Add `lombok-mapstruct-binding` dependency for Maven when both MapStruct and Lombok are used,"Add the `lombok-mapstruct-binding` when both MapStruct and Lombok are used, and the dependency does not already exist. Only to be called from `org.openrewrite.java.migrate.AddLombokMapstructBinding` to reduce redundant checks.",2,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeToJava21,Migrate to Java 21,This recipe will apply changes commonly needed when migrating to Java 21. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy. Build files will also be updated to use Java 21 as the target/source and plugins will be also be upgraded to versions that are compatible with Java 21.,9081,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeToJava21,Migrate to Java 21,This recipe will apply changes commonly needed when migrating to Java 21. This recipe will also replace deprecated API with equivalents when there is a clear migration strategy. Build files will also be updated to use Java 21 as the target/source and plugins will be also be upgraded to versions that are compatible with Java 21.,9084,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradeBuildToJava21,Upgrade build to Java 21,Updates build files to use Java 21 as the target/source.,4558,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.UpgradePluginsForJava21,Upgrade plugins to Java 21 compatible versions,Updates plugins and dependencies to version compatible with Java 21.,6,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.RemovedSubjectMethods,Adopt `javax.security.auth.Subject.current()` and `javax.security.auth.Subject.callAs()` methods`,Replaces the `javax.security.auth.Subject.getSubject()` and `javax.security.auth.Subject.doAs()` methods with `javax.security.auth.Subject.current()` and `javax.security.auth.Subject.callAs()`.,3,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, @@ -166,7 +166,7 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.j maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.UpdateGetRealPath,Updates `getRealPath()` to call `getContext()` followed by `getRealPath()`,Updates `getRealPath()` for `jakarta.servlet.ServletRequest` and `jakarta.servlet.ServletRequestWrapper` to use `ServletContext.getRealPath(String)`.,1,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.UpdateManagedBeanToNamed,Update Faces `@ManagedBean` to use CDI `@Named`,Faces ManagedBean was deprecated in JSF 2.3 (EE8) and removed in Jakarta Faces 4.0 (EE10). Replace `@ManagedBean` with `@Named` for CDI-based bean management.,1,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.HasNoJakartaAnnotations,Project has no Jakarta annotations,Mark all source as found per `JavaProject` where no Jakarta annotations are found. This is useful mostly as a precondition for recipes that require Jakarta annotations to be present.,1,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JakartaEE10,Migrate to Jakarta EE 10,"These recipes help with the Migration to Jakarta EE 10, flagging and updating deprecated methods.",611,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JakartaEE10,Migrate to Jakarta EE 10,"These recipes help with the Migration to Jakarta EE 10, flagging and updating deprecated methods.",613,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.MigrationToJakarta10Apis,Migrate Jakarta EE 9 api dependencies to Jakarta EE 10 versions,Jakarta EE 10 updates some apis compared to Jakarta EE 9.,26,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.UpdateFileupload2Dependencies,Update Apache Commons FileUpload2 package for EE10,Update Apache Commons FileUpload2 package for EE10.,3,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.ServletCookieBehaviorChangeRFC6265,Remove `getComment` and `getVersion` methods,"Jakarta Servlet methods have been deprecated for removal in Jakarta Servlet 6.0 to align with RFC 6265. In addition, the behavior of these methods has been changed so the setters no longer have any effect, the getComment methods return null, and the getVersion method returns 0. The deprecated methods are removed.",7,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, @@ -187,10 +187,10 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.j maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.UpdateApacheShiroDependencies,Update Apache Shiro Dependencies to 2.0.x,Update Apache Shiro Dependencies to 2.0.x.,11,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.UpdateEclipseLinkDependencies,Update EclipseLink Dependencies to 4.x,Update EclipseLink Dependencies to 4.x.,2,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.UpdateYassonDependencies,Update Eclipse Yasson Dependencies to 3.0.x,Update Eclipse Yasson Dependencies to 3.0.x.,2,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JettyUpgradeEE10,Update Jetty EE9 to Jetty EE10,"Update Jetty dependencies from EE9 to EE10, changing the groupId and artifactIds as needed.",12,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JettyUpgradeEE10,Update Jetty EE9 to Jetty EE10,"Update Jetty dependencies from EE9 to EE10, changing the groupId and artifactIds as needed.",14,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.MigratePluginsForJakarta10,Update Plugins for Jakarta EE 10,Update plugin to be compatible with Jakarta EE 10.,3,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.MigrateFastjsonForJakarta10,Update Fastjson for Jakarta EE 10,Update Fastjson to be compatible with Jakarta EE 10.,19,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JakartaEE11,Migrate to Jakarta EE 11,"These recipes help with the Migration to Jakarta EE 11, flagging and updating deprecated methods.",833,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JakartaEE11,Migrate to Jakarta EE 11,"These recipes help with the Migration to Jakarta EE 11, flagging and updating deprecated methods.",835,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.UpdateJakartaPlatform11,Update Jakarta EE Platform Dependencies to 11.0.x,Update Jakarta EE Platform Dependencies to 11.0.x.,2,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta,Migrate to Jakarta EE 9,Jakarta EE 9 is the first version of Jakarta EE that uses the new `jakarta` namespace.,319,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxActivationMigrationToJakartaActivation,Migrate deprecated `javax.activation` packages to `jakarta.activation`,"Java EE has been rebranded to Jakarta EE, necessitating a package relocation.",5,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, @@ -305,6 +305,7 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.j maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.javax.openJPAToEclipseLink,Migrate from OpenJPA to EclipseLink JPA,These recipes help migrate Java Persistence applications using OpenJPA to EclipseLink JPA.,10,,`javax` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.ExplicitRecordImport,Add explicit import for `Record` classes,"Add explicit import for `Record` classes when upgrading past Java 14+, to avoid conflicts with `java.lang.Record`.",1,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.IfElseIfConstructToSwitch,If-else-if-else to switch,"Replace if-else-if-else with switch statements. In order to be replaced with a switch, all conditions must be on the same variable and there must be at least three cases.",1,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.JavadocToMarkdownDocComment,Convert Javadoc to Markdown documentation comments,"Convert traditional Javadoc comments (`/** ... */`) to Markdown documentation comments (`///`) as supported by JEP 467 in Java 23+. Transforms HTML constructs like `
`, ``, ``, `

`, and lists to their Markdown equivalents, and converts inline tags like `{@code}` and `{@link}` to Markdown syntax.",1,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.MigrateClassLoaderDefineClass,"Use `ClassLoader#defineClass(String, byte[], int, int)`","Use `ClassLoader#defineClass(String, byte[], int, int)` instead of the deprecated `ClassLoader#defineClass(byte[], int, int)` in Java 1.1 or higher.",1,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.MigrateClassNewInstanceToGetDeclaredConstructorNewInstance,Use `Class#getDeclaredConstructor().newInstance()`,Use `Class#getDeclaredConstructor().newInstance()` instead of the deprecated `Class#newInstance()` in Java 9 or higher.,1,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.lang.MigrateMainMethodToInstanceMain,Migrate `public static void main(String[] args)` to instance `void main()`,"Migrate `public static void main(String[] args)` method to instance `void main()` method when the `args` parameter is unused, as supported by JEP 512 in Java 25+.",1,,`java.lang` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, diff --git a/src/test/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocCommentTest.java b/src/test/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocCommentTest.java new file mode 100644 index 0000000000..ca5e8b444d --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocCommentTest.java @@ -0,0 +1,474 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * 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.openrewrite.java.migrate.lang; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.java.Assertions.version; + +class JavadocToMarkdownDocCommentTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new JavadocToMarkdownDocComment()); + spec.allSources(source -> version(source, 23)); + } + + @DocumentExample + @Test + void multiLineJavadocWithTags() { + rewriteRun( + java( + """ + public class A { + /** + * Computes the sum of two numbers. + * + * @param a the first number + * @param b the second number + * @return the sum of a and b + */ + public int add(int a, int b) { + return a + b; + } + } + """, + """ + public class A { + /// Computes the sum of two numbers. + /// + /// @param a the first number + /// @param b the second number + /// @return the sum of a and b + public int add(int a, int b) { + return a + b; + } + } + """ + ) + ); + } + + @Test + void singleLineJavadoc() { + rewriteRun( + java( + """ + public class A { + /** Returns the name. */ + public String getName() { + return "name"; + } + } + """, + """ + public class A { + /// Returns the name. + public String getName() { + return "name"; + } + } + """ + ) + ); + } + + @Test + void javadocWithCodeTag() { + rewriteRun( + java( + """ + public class A { + /** + * Use the {@code List} interface for ordered collections. + */ + public void m() {} + } + """, + """ + public class A { + /// Use the `List` interface for ordered collections. + public void m() {} + } + """ + ) + ); + } + + @Test + void javadocWithLinkTag() { + rewriteRun( + java( + """ + import java.util.List; + public class A { + /** + * Returns a {@link List} of items. + */ + public void m() {} + } + """, + """ + import java.util.List; + public class A { + /// Returns a [List] of items. + public void m() {} + } + """ + ) + ); + } + + @Test + void javadocWithParagraph() { + rewriteRun( + java( + """ + public class A { + /** + * First paragraph. + * + *

Second paragraph. + */ + public void m() {} + } + """, + """ + public class A { + /// First paragraph. + /// + /// Second paragraph. + public void m() {} + } + """ + ) + ); + } + + @Test + void javadocWithEmphasis() { + rewriteRun( + java( + """ + public class A { + /** + * This is important text. + */ + public void m() {} + } + """, + """ + public class A { + /// This is _important_ text. + public void m() {} + } + """ + ) + ); + } + + @Test + void javadocWithStrong() { + rewriteRun( + java( + """ + public class A { + /** + * This is very important text. + */ + public void m() {} + } + """, + """ + public class A { + /// This is **very important** text. + public void m() {} + } + """ + ) + ); + } + + @Test + void javadocOnClass() { + rewriteRun( + java( + """ + /** + * A simple class. + * + * @since 1.0 + */ + public class A { + } + """, + """ + /// A simple class. + /// + /// @since 1.0 + public class A { + } + """ + ) + ); + } + + @Test + void javadocWithThrows() { + rewriteRun( + java( + """ + public class A { + /** + * Does something. + * + * @throws IllegalArgumentException if argument is invalid + */ + public void m() {} + } + """, + """ + public class A { + /// Does something. + /// + /// @throws IllegalArgumentException if argument is invalid + public void m() {} + } + """ + ) + ); + } + + @Test + void javadocWithHtmlEntities() { + rewriteRun( + java( + """ + public class A { + /** + * Checks if a < b && c > d. + */ + public void m() {} + } + """, + """ + public class A { + /// Checks if a < b && c > d. + public void m() {} + } + """ + ) + ); + } + + @Test + void javadocWithInheritDoc() { + rewriteRun( + java( + """ + public class A { + /** + * {@inheritDoc} + */ + public void m() {} + } + """, + """ + public class A { + /// {@inheritDoc} + public void m() {} + } + """ + ) + ); + } + + @Test + void noChangeForRegularComments() { + rewriteRun( + java( + """ + public class A { + // line comment + /* block comment */ + public void m() {} + } + """ + ) + ); + } + + @Test + void javadocOnField() { + rewriteRun( + java( + """ + public class A { + /** + * The name of the entity. + */ + private String name; + } + """, + """ + public class A { + /// The name of the entity. + private String name; + } + """ + ) + ); + } + + @Test + void javadocWithPreBlock() { + rewriteRun( + java( + """ + public class A { + /** + * Example usage: + *

+                   * List<String> items = getItems();
+                   * items.forEach(System.out::println);
+                   * 
+ */ + public void m() {} + } + """, + """ + public class A { + /// Example usage: + /// ``` + /// List items = getItems(); + /// items.forEach(System.out::println); + /// ``` + public void m() {} + } + """ + ) + ); + } + + @Test + void javadocWithUnorderedList() { + rewriteRun( + java( + """ + public class A { + /** + * Features: + *
    + *
  • Fast
  • + *
  • Reliable
  • + *
+ */ + public void m() {} + } + """, + """ + public class A { + /// Features: + /// + /// - Fast + /// - Reliable + public void m() {} + } + """ + ) + ); + } + + @Test + void javadocWithDeprecated() { + rewriteRun( + java( + """ + public class A { + /** + * @deprecated Use {@link #newMethod()} instead. + */ + public void m() {} + public void newMethod() {} + } + """, + """ + public class A { + /// @deprecated Use [#newMethod()] instead. + public void m() {} + public void newMethod() {} + } + """ + ) + ); + } + + @Test + void javadocWithSee() { + rewriteRun( + java( + """ + public class A { + /** + * Does something. + * + * @see Object#toString() + */ + public void m() {} + } + """, + """ + public class A { + /// Does something. + /// + /// @see Object#toString() + public void m() {} + } + """ + ) + ); + } + + @Test + void javadocPreservesIndentationInNestedClass() { + rewriteRun( + java( + """ + public class Outer { + public static class Inner { + /** + * Inner method. + */ + public void m() {} + } + } + """, + """ + public class Outer { + public static class Inner { + /// Inner method. + public void m() {} + } + } + """ + ) + ); + } +} From 0094cb3efc02f7c4421cebd3dd972ca72da00752 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Fri, 20 Mar 2026 23:33:36 +0100 Subject: [PATCH 2/6] Add working-set directories to .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 467dffd57d..090408ceda 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ out/ .DS_Store bin/ -.claude/settings.local.json \ No newline at end of file +.claude/settings.local.json working-set*/ From a3bf697afde2498d812ba98c8ee1e45efd751eba Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Fri, 20 Mar 2026 23:34:33 +0100 Subject: [PATCH 3/6] Update .gitignore to include new patterns --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 090408ceda..d2f7806c6b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,5 @@ out/ .settings/ .DS_Store bin/ - -.claude/settings.local.json working-set*/ +.claude/settings.local.json +working-set*/ From ae3e60701f08ce6c9e2345ae6e8339b029fd45d5 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Fri, 20 Mar 2026 23:47:33 +0100 Subject: [PATCH 4/6] Address PR review comments on JavadocToMarkdownDocComment - Remove redundant hasJavadoc early-return check; ListUtils.flatMap already returns the same list if nothing changes - Extract normalizeLines() and toTextComments() helper methods - Use ListUtils.mapLast() to set the final comment suffix instead of manual index tracking --- .../lang/JavadocToMarkdownDocComment.java | 102 +++++++----------- 1 file changed, 37 insertions(+), 65 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java b/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java index aa57037234..4b3149f928 100644 --- a/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java +++ b/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java @@ -44,19 +44,8 @@ public TreeVisitor getVisitor() { return Preconditions.check(new UsesJavaVersion<>(23), new JavaIsoVisitor() { @Override public Space visitSpace(Space space, Space.Location loc, ExecutionContext ctx) { - List comments = space.getComments(); - boolean hasJavadoc = false; - for (Comment comment : comments) { - if (comment instanceof Javadoc.DocComment) { - hasJavadoc = true; - break; - } - } - if (!hasJavadoc) { - return space; - } String spaceWhitespace = space.getWhitespace(); - return space.withComments(ListUtils.flatMap(comments, comment -> { + return space.withComments(ListUtils.flatMap(space.getComments(), comment -> { if (comment instanceof Javadoc.DocComment) { return convertDocComment((Javadoc.DocComment) comment, spaceWhitespace); } @@ -68,90 +57,73 @@ public Space visitSpace(Space space, Space.Location loc, ExecutionContext ctx) { private static List convertDocComment(Javadoc.DocComment docComment, String spaceWhitespace) { // Derive the indentation from the space whitespace (e.g., "\n " → " ") - String indentation; int lastNewline = spaceWhitespace.lastIndexOf('\n'); - if (lastNewline >= 0) { - indentation = spaceWhitespace.substring(lastNewline + 1); - } else { - indentation = spaceWhitespace; - } + String indentation = lastNewline >= 0 ? spaceWhitespace.substring(lastNewline + 1) : spaceWhitespace; JavadocToMarkdownConverter converter = new JavadocToMarkdownConverter(); converter.convert(docComment.getBody()); - List lines = converter.getLines(); + // Strip one leading space (from the space after * in javadoc), right-trim, drop + // leading/trailing blank lines, and collapse consecutive blank lines + List lines = normalizeLines(converter.getLines()); - // Strip exactly one leading space from each line (from the space after * in javadoc) - // Also right-trim each line - for (int i = 0; i < lines.size(); i++) { - String line = lines.get(i); - if (line.startsWith(" ")) { - line = line.substring(1); + if (lines.isEmpty()) { + lines = Collections.singletonList(""); + } + + String interLineSuffix = "\n" + indentation; + List result = toTextComments(lines, interLineSuffix); + return ListUtils.mapLast(result, comment -> comment.withSuffix(docComment.getSuffix())); + } + + private static List normalizeLines(List raw) { + // Strip one leading space and right-trim + List lines = new ArrayList<>(raw.size()); + for (String line : raw) { + String stripped = line.startsWith(" ") ? line.substring(1) : line; + // Right-trim trailing spaces + int end = stripped.length(); + while (end > 0 && stripped.charAt(end - 1) == ' ') { + end--; } - lines.set(i, rtrim(line)); + lines.add(end < stripped.length() ? stripped.substring(0, end) : stripped); } - // Skip leading empty lines (from the LineBreak after /**) + // Trim leading and trailing blank lines int start = 0; while (start < lines.size() && lines.get(start).isEmpty()) { start++; } - - // Find end excluding trailing empty lines int end = lines.size(); while (end > start && lines.get(end - 1).isEmpty()) { end--; } - // Collapse consecutive blank lines into a single blank line - List collapsed = new ArrayList<>(); + // Collapse consecutive blank lines + List result = new ArrayList<>(); boolean prevBlank = false; for (int i = start; i < end; i++) { String line = lines.get(i); - if (line.isEmpty()) { - if (!prevBlank) { - collapsed.add(line); - } - prevBlank = true; - } else { - collapsed.add(line); - prevBlank = false; + boolean blank = line.isEmpty(); + if (!blank || !prevBlank) { + result.add(line); } + prevBlank = blank; } - lines = collapsed; - - if (lines.isEmpty()) { - lines.add(""); - } - - List result = new ArrayList<>(); - String suffix = docComment.getSuffix(); + return result; + } - for (int i = 0; i < lines.size(); i++) { - String lineContent = lines.get(i); - String lineSuffix; - if (i < lines.size() - 1) { - lineSuffix = "\n" + indentation; - } else { - lineSuffix = suffix; - } + private static List toTextComments(List lines, String suffix) { + List result = new ArrayList<>(lines.size()); + for (String lineContent : lines) { // TextComment(false, text, suffix, markers) prints as "// + text" // So text="/ content" produces "/// content" String text = lineContent.isEmpty() ? "/" : "/ " + lineContent; - result.add(new TextComment(false, text, lineSuffix, Markers.EMPTY)); + result.add(new TextComment(false, text, suffix, Markers.EMPTY)); } - return result; } - private static String rtrim(String s) { - int end = s.length(); - while (end > 0 && s.charAt(end - 1) == ' ') { - end--; - } - return s.substring(0, end); - } - static class JavadocToMarkdownConverter { private final List lines = new ArrayList<>(); private StringBuilder currentLine = new StringBuilder(); From 42665f92c74a6f8fd7005297bf2e883d676bc7e5 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sat, 21 Mar 2026 00:25:55 +0100 Subject: [PATCH 5/6] Slight polish --- .../lang/JavadocToMarkdownDocComment.java | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java b/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java index 4b3149f928..1fc62ceab0 100644 --- a/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java +++ b/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java @@ -16,6 +16,7 @@ package org.openrewrite.java.migrate.lang; import lombok.Getter; +import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.Preconditions; import org.openrewrite.Recipe; @@ -136,6 +137,16 @@ List getLines() { return lines; } + private void flushLine() { + if (currentLine.length() > 0) { + lines.add(currentLine.toString()); + currentLine = new StringBuilder(); + } else if (lines.isEmpty()) { + lines.add(currentLine.toString()); + currentLine = new StringBuilder(); + } + } + void convert(List body) { for (Javadoc node : body) { convertNode(node); @@ -144,9 +155,10 @@ void convert(List body) { private void convertNode(Javadoc node) { if (node instanceof Javadoc.Text) { - convertText((Javadoc.Text) node); + currentLine.append(decodeHtmlEntities(((Javadoc.Text) node).getText())); } else if (node instanceof Javadoc.LineBreak) { - convertLineBreak((Javadoc.LineBreak) node); + lines.add(currentLine.toString()); + currentLine = new StringBuilder(); } else if (node instanceof Javadoc.Literal) { convertLiteral((Javadoc.Literal) node); } else if (node instanceof Javadoc.Link) { @@ -196,25 +208,6 @@ private void convertNode(Javadoc node) { } } - private void convertText(Javadoc.Text text) { - currentLine.append(decodeHtmlEntities(text.getText())); - } - - private void convertLineBreak(Javadoc.LineBreak lineBreak) { - lines.add(currentLine.toString()); - currentLine = new StringBuilder(); - } - - private void flushLine() { - if (currentLine.length() > 0) { - lines.add(currentLine.toString()); - currentLine = new StringBuilder(); - } else if (lines.isEmpty()) { - lines.add(currentLine.toString()); - currentLine = new StringBuilder(); - } - } - private void convertLiteral(Javadoc.Literal literal) { String content = stripLeadingSpace(renderInline(literal.getDescription())); if (literal.isCode()) { @@ -490,7 +483,7 @@ private String renderInline(List body) { return String.join("\n", inlineLines); } - private static String printReference(Javadoc.Reference ref) { + private static String printReference(Javadoc.@Nullable Reference ref) { if (ref == null) { return ""; } @@ -583,10 +576,7 @@ private static String printJ(J tree) { } private static String stripLeadingSpace(String s) { - if (s.startsWith(" ")) { - return s.substring(1); - } - return s; + return s.startsWith(" ") ? s.substring(1) : s; } private static String decodeHtmlEntities(String text) { From e9a12163667e6ec79dc5934cb27c6c743673b1f3 Mon Sep 17 00:00:00 2001 From: Tim te Beek Date: Sat, 21 Mar 2026 12:13:42 +0100 Subject: [PATCH 6/6] Address PR review comments: simplify flatMap, right-trim, and mapLast --- .../lang/JavadocToMarkdownDocComment.java | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java b/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java index 1fc62ceab0..f950c1b6ad 100644 --- a/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java +++ b/src/main/java/org/openrewrite/java/migrate/lang/JavadocToMarkdownDocComment.java @@ -46,12 +46,10 @@ public TreeVisitor getVisitor() { @Override public Space visitSpace(Space space, Space.Location loc, ExecutionContext ctx) { String spaceWhitespace = space.getWhitespace(); - return space.withComments(ListUtils.flatMap(space.getComments(), comment -> { - if (comment instanceof Javadoc.DocComment) { - return convertDocComment((Javadoc.DocComment) comment, spaceWhitespace); - } - return comment; - })); + return space.withComments(ListUtils.flatMap(space.getComments(), comment -> + comment instanceof Javadoc.DocComment ? + convertDocComment((Javadoc.DocComment) comment, spaceWhitespace) : + comment)); } }); } @@ -73,8 +71,8 @@ private static List convertDocComment(Javadoc.DocComment docComment, St } String interLineSuffix = "\n" + indentation; - List result = toTextComments(lines, interLineSuffix); - return ListUtils.mapLast(result, comment -> comment.withSuffix(docComment.getSuffix())); + return ListUtils.mapLast(toTextComments(lines, interLineSuffix), + comment -> comment.withSuffix(docComment.getSuffix())); } private static List normalizeLines(List raw) { @@ -82,12 +80,7 @@ private static List normalizeLines(List raw) { List lines = new ArrayList<>(raw.size()); for (String line : raw) { String stripped = line.startsWith(" ") ? line.substring(1) : line; - // Right-trim trailing spaces - int end = stripped.length(); - while (end > 0 && stripped.charAt(end - 1) == ' ') { - end--; - } - lines.add(end < stripped.length() ? stripped.substring(0, end) : stripped); + lines.add(stripped.replaceAll("\\s+$", "")); } // Trim leading and trailing blank lines