diff --git a/README.md b/README.md index 698a44226..8250e37d9 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,25 @@ -![Logo](https://github.com/openrewrite/rewrite/raw/main/doc/logo-oss.png) - -### Migrate to later Java versions. Automatically. - +

+ + + + + OpenRewrite Logo + + +

+ +
+

rewrite-migrate-java

+
+ +
+ + [![ci](https://github.com/openrewrite/rewrite-migrate-java/actions/workflows/ci.yml/badge.svg)](https://github.com/openrewrite/rewrite-migrate-java/actions/workflows/ci.yml) [![Maven Central](https://img.shields.io/maven-central/v/org.openrewrite.recipe/rewrite-migrate-java.svg)](https://mvnrepository.com/artifact/org.openrewrite.recipe/rewrite-migrate-java) [![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.openrewrite.org/scans) +[![Contributing Guide](https://img.shields.io/badge/Contributing-Guide-informational)](https://github.com/openrewrite/.github/blob/main/CONTRIBUTING.md) +
### What is this? diff --git a/build.gradle.kts b/build.gradle.kts index af572b74a..8fbf549af 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,6 +35,8 @@ dependencies { runtimeOnly("org.openrewrite:rewrite-java-17") runtimeOnly("org.openrewrite:rewrite-java-21") + runtimeOnly("tech.picnic.error-prone-support:error-prone-contrib:latest.release:recipes") + testImplementation("org.junit.jupiter:junit-jupiter-api:latest.release") testImplementation("org.junit.jupiter:junit-jupiter-params:latest.release") testImplementation("org.junit-pioneer:junit-pioneer:2.0.0") @@ -54,8 +56,13 @@ dependencies { testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jsr353") testRuntimeOnly("com.fasterxml.jackson.core:jackson-core") testRuntimeOnly("com.fasterxml.jackson.core:jackson-databind") + testRuntimeOnly("com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider") + testRuntimeOnly("com.fasterxml.jackson.module:jackson-module-jaxb-annotations") + testRuntimeOnly("commons-logging:commons-logging:1.3.2") + testRuntimeOnly("org.apache.logging.log4j:log4j-api:2.23.1") testRuntimeOnly("org.apache.johnzon:johnzon-core:1.2.18") testRuntimeOnly("org.codehaus.groovy:groovy:latest.release") + testRuntimeOnly("org.jboss.logging:jboss-logging:3.6.0.Final") testRuntimeOnly("jakarta.annotation:jakarta.annotation-api:2.1.1") testRuntimeOnly("org.springframework:spring-core:6.1.13") testRuntimeOnly("com.google.code.findbugs:jsr305:3.0.2") diff --git a/src/main/java/org/openrewrite/java/migrate/UpdateSdkMan.java b/src/main/java/org/openrewrite/java/migrate/UpdateSdkMan.java index 5acbc8b8b..d25130360 100644 --- a/src/main/java/org/openrewrite/java/migrate/UpdateSdkMan.java +++ b/src/main/java/org/openrewrite/java/migrate/UpdateSdkMan.java @@ -25,16 +25,17 @@ import org.openrewrite.text.PlainText; import org.openrewrite.text.PlainTextParser; +import java.io.BufferedReader; import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Paths; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toList; @Value @EqualsAndHashCode(callSuper = false) @@ -102,15 +103,14 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { } private List readSdkmanJavaCandidates() { - URL resource = getClass().getResource("/sdkman-java.csv"); - if (resource != null) { - try { - return Files.readAllLines(Paths.get(resource.toURI())); - } catch (IOException | URISyntaxException e) { - throw new RuntimeException(e); - } + try (InputStream resourceAsStream = UpdateSdkMan.class.getResourceAsStream("/sdkman-java.csv"); + InputStreamReader inputStreamReader = new InputStreamReader(resourceAsStream, StandardCharsets.UTF_8); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) { + return bufferedReader.lines().collect(toList()); + } catch (IOException e) { + throw new RuntimeException(e); } - throw new IllegalStateException("Could not find /sdkman-java.csv file"); + } }; return Preconditions.check(new FindSourceFiles(".sdkmanrc"), visitor); diff --git a/src/main/java/org/openrewrite/java/migrate/guava/NoMapsAndSetsWithExpectedSize.java b/src/main/java/org/openrewrite/java/migrate/guava/NoMapsAndSetsWithExpectedSize.java new file mode 100644 index 000000000..76398631d --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/guava/NoMapsAndSetsWithExpectedSize.java @@ -0,0 +1,101 @@ +/* + * 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.guava; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.search.UsesJavaVersion; +import org.openrewrite.java.search.UsesMethod; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaCoordinates; + +public class NoMapsAndSetsWithExpectedSize extends Recipe { + + private static final MethodMatcher NEW_HASHMAP = new MethodMatcher("com.google.common.collect.Maps newHashMapWithExpectedSize(int)", false); + private static final MethodMatcher NEW_LINKED_HASHMAP = new MethodMatcher("com.google.common.collect.Maps newLinkedHashMapWithExpectedSize(int)", false); + private static final MethodMatcher NEW_HASHSET = new MethodMatcher("com.google.common.collect.Sets newHashSetWithExpectedSize(int)", false); + private static final MethodMatcher NEW_LINKED_HASHSET = new MethodMatcher("com.google.common.collect.Sets newLinkedHashSetWithExpectedSize(int)", false); + + @Override + public String getDisplayName() { + return "Prefer JDK methods for Maps and Sets of an expected size"; + } + + @Override + public String getDescription() { + return "Prefer Java 19+ methods to create Maps and Sets of an expected size instead of using Guava methods."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check( + Preconditions.and( + new UsesJavaVersion<>(19), + Preconditions.or( + new UsesMethod<>(NEW_HASHMAP), + new UsesMethod<>(NEW_LINKED_HASHMAP), + new UsesMethod<>(NEW_HASHSET), + new UsesMethod<>(NEW_LINKED_HASHSET) + ) + ), + new JavaVisitor() { + @Override + public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + J.MethodInvocation j = (J.MethodInvocation) super.visitMethodInvocation(method, ctx); + if (NEW_HASHMAP.matches(j)) { + maybeRemoveImport("com.google.common.collect.Maps"); + maybeAddImport("java.util.HashMap"); + JavaCoordinates coordinates = j.getCoordinates().replace(); + return JavaTemplate.builder("new HashMap<>(#{any()})") + .imports("java.util.HashMap") + .build() + .apply(getCursor(), coordinates, j.getArguments().toArray()); + } else if (NEW_LINKED_HASHMAP.matches(j)) { + maybeRemoveImport("com.google.common.collect.Maps"); + maybeAddImport("java.util.LinkedHashMap"); + JavaCoordinates coordinates = j.getCoordinates().replace(); + return JavaTemplate.builder("new LinkedHashMap<>(#{any()})") + .imports("java.util.LinkedHashMap") + .build() + .apply(getCursor(), coordinates, j.getArguments().toArray()); + } else if (NEW_HASHSET.matches(j)) { + maybeRemoveImport("com.google.common.collect.Sets"); + maybeAddImport("java.util.HashSet"); + JavaCoordinates coordinates = j.getCoordinates().replace(); + return JavaTemplate.builder("new HashSet<>(#{any()})") + .imports("java.util.HashSet") + .build() + .apply(getCursor(), coordinates, j.getArguments().toArray()); + } else if (NEW_LINKED_HASHSET.matches(j)) { + maybeRemoveImport("com.google.common.collect.Sets"); + maybeAddImport("java.util.LinkedHashSet"); + JavaCoordinates coordinates = j.getCoordinates().replace(); + return JavaTemplate.builder("new LinkedHashSet<>(#{any()})") + .imports("java.util.LinkedHashSet") + .build() + .apply(getCursor(), coordinates, j.getArguments().toArray()); + } + return j; + } + } + ); + } +} diff --git a/src/main/java/org/openrewrite/java/migrate/javax/AddColumnAnnotation.java b/src/main/java/org/openrewrite/java/migrate/javax/AddColumnAnnotation.java index ee0b32946..aa614b3ec 100644 --- a/src/main/java/org/openrewrite/java/migrate/javax/AddColumnAnnotation.java +++ b/src/main/java/org/openrewrite/java/migrate/javax/AddColumnAnnotation.java @@ -90,7 +90,7 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m // Update existing @Column annotation J.VariableDeclarations updatedVariable = (J.VariableDeclarations) new AddOrUpdateAnnotationAttribute( - "javax.persistence.Column", "name", "element", true, null) + "javax.persistence.Column", "name", "element", null, true, null) .getVisitor().visit(multiVariable, ctx, getCursor().getParentTreeCursor()); return super.visitVariableDeclarations(updatedVariable, ctx); } diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java b/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java new file mode 100644 index 000000000..630041410 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructor.java @@ -0,0 +1,82 @@ +/* + * 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.lombok; + +import lombok.AccessLevel; +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.TypeUtils; + +import static java.util.Comparator.comparing; + +@Value +@EqualsAndHashCode(callSuper = false) +public class UseNoArgsConstructor extends Recipe { + + @Override + public String getDisplayName() { + //language=markdown + return "Use `@NoArgsConstructor` where applicable"; + } + + @Override + public String getDescription() { + //language=markdown + return "Prefer the Lombok `@NoArgsConstructor` annotation over explicitly written out constructors."; + } + + @Override + public TreeVisitor getVisitor() { + return new JavaIsoVisitor() { + @Override + public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + if (method.isConstructor() && + method.getParameters().get(0) instanceof J.Empty && + method.getBody() != null && method.getBody().getStatements().isEmpty()) { + J.ClassDeclaration enclosing = getCursor().firstEnclosing(J.ClassDeclaration.class); + AccessLevel accessLevel = LombokUtils.getAccessLevel(method); + doAfterVisit(new JavaIsoVisitor() { + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + if (TypeUtils.isOfType(classDecl.getType(), enclosing.getType())) { + String template = "@NoArgsConstructor" + (accessLevel == AccessLevel.PUBLIC ? + "" : "(access = AccessLevel." + accessLevel.name() + ")"); + maybeAddImport("lombok.AccessLevel"); + maybeAddImport("lombok.NoArgsConstructor"); + return JavaTemplate.builder(template) + .imports("lombok.*") + .javaParser(JavaParser.fromJavaVersion().classpath("lombok")) + .build() + .apply(getCursor(), classDecl.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))); + } + return super.visitClassDeclaration(classDecl, ctx); + } + }); + return null; + } + return super.visitMethodDeclaration(method, ctx); + } + }; + } +} diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/log/LogVisitor.java b/src/main/java/org/openrewrite/java/migrate/lombok/log/LogVisitor.java new file mode 100644 index 000000000..363d0a8b5 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/log/LogVisitor.java @@ -0,0 +1,106 @@ +/* + * 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.lombok.log; + +import lombok.EqualsAndHashCode; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.java.*; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.TypeUtils; + +import static java.util.Comparator.comparing; + +@EqualsAndHashCode(callSuper = false) +class LogVisitor extends JavaIsoVisitor { + + private final String logType; + private final String factoryType; + private final MethodMatcher factoryMethodMatcher; + private final String logAnnotation; + @Nullable + private final String fieldName; + + LogVisitor(String logType, String factoryMethodPattern, String logAnnotation, @Nullable String fieldName) { + this.logType = logType; + this.factoryType = factoryMethodPattern.substring(0, factoryMethodPattern.indexOf(' ')); + this.factoryMethodMatcher = new MethodMatcher(factoryMethodPattern); + this.logAnnotation = logAnnotation; + this.fieldName = fieldName; + } + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) { + J.ClassDeclaration visitClassDeclaration = super.visitClassDeclaration(classDecl, ctx); + if (visitClassDeclaration != classDecl) { + maybeRemoveImport(logType); + maybeRemoveImport(factoryType); + maybeAddImport(logAnnotation); + return JavaTemplate + .builder("@" + logAnnotation.substring(logAnnotation.lastIndexOf('.') + 1) + "\n") + .javaParser(JavaParser.fromJavaVersion().classpath("lombok")) + .imports(logAnnotation) + .build() + .apply( + updateCursor(visitClassDeclaration), + visitClassDeclaration.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName))); + } + return classDecl; + } + + @Override + public J.@Nullable VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { + if (!multiVariable.hasModifier(J.Modifier.Type.Private) || + !multiVariable.hasModifier(J.Modifier.Type.Static) || + !multiVariable.hasModifier(J.Modifier.Type.Final) || + multiVariable.getVariables().size() != 1 || + !TypeUtils.isAssignableTo(logType, multiVariable.getType())) { + return multiVariable; + } + + // name needs to match the name of the field that lombok creates + J.VariableDeclarations.NamedVariable var = multiVariable.getVariables().get(0); + if (fieldName != null && !fieldName.equals(var.getSimpleName())) { + return multiVariable; + } + + if (!factoryMethodMatcher.matches(var.getInitializer())) { + return multiVariable; + } + + J.ClassDeclaration classDeclaration = getCursor().firstEnclosing(J.ClassDeclaration.class); + if (classDeclaration == null || classDeclaration.getType() == null) { + return multiVariable; + } + + J.MethodInvocation methodCall = (J.MethodInvocation) var.getInitializer(); + if (methodCall.getArguments().size() != 1 || + !getFactoryParameter(classDeclaration.getSimpleName()) + .equals(methodCall.getArguments().get(0).toString())) { + return multiVariable; + } + + if (!"log".equals(var.getSimpleName())) { + doAfterVisit(new ChangeFieldName<>(classDeclaration.getType().getFullyQualifiedName(), var.getSimpleName(), "log")); + } + + return null; + } + + protected String getFactoryParameter(String className) { + return className + ".class"; + } +} diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/log/UseCommonsLog.java b/src/main/java/org/openrewrite/java/migrate/lombok/log/UseCommonsLog.java new file mode 100644 index 000000000..94d1ed7b8 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/log/UseCommonsLog.java @@ -0,0 +1,55 @@ +/* + * 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.lombok.log; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.TreeVisitor; + +@Value +@EqualsAndHashCode(callSuper = false) +public class UseCommonsLog extends UseLogRecipeTemplate { + + @Override + public String getDisplayName() { + return getDisplayName("@CommonsLog"); + } + + @Override + public String getDescription() { + return getDescription("@CommonsLog", "org.apache.commons.logging.Log"); + } + + @Option(displayName = "Name of the log field", + description = FIELD_NAME_DESCRIPTION, + example = "LOGGER", + required = false) + @Nullable + String fieldName; + + @Override + public TreeVisitor getVisitor() { + return new LogVisitor( + "org.apache.commons.logging.Log", + "org.apache.commons.logging.LogFactory getLog(..)", + "lombok.extern.apachecommons.CommonsLog", + fieldName); + } + +} diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/log/UseJBossLog.java b/src/main/java/org/openrewrite/java/migrate/lombok/log/UseJBossLog.java new file mode 100644 index 000000000..5019d631e --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/log/UseJBossLog.java @@ -0,0 +1,55 @@ +/* + * 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.lombok.log; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.TreeVisitor; + +@Value +@EqualsAndHashCode(callSuper = false) +public class UseJBossLog extends UseLogRecipeTemplate { + + @Override + public String getDisplayName() { + return getDisplayName("@JBossLog"); + } + + @Override + public String getDescription() { + return getDescription("@JBossLog", "org.jboss.logging.Logger"); + } + + @Option(displayName = "Name of the log field", + description = FIELD_NAME_DESCRIPTION, + example = "LOGGER", + required = false) + @Nullable + String fieldName; + + @Override + public TreeVisitor getVisitor() { + return new LogVisitor( + "org.jboss.logging.Logger", + "org.jboss.logging.Logger getLogger(..)", + "lombok.extern.jbosslog.JBossLog", + fieldName); + } + +} diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/log/UseLog.java b/src/main/java/org/openrewrite/java/migrate/lombok/log/UseLog.java new file mode 100644 index 000000000..3078193af --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/log/UseLog.java @@ -0,0 +1,60 @@ +/* + * 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.lombok.log; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.TreeVisitor; + +@Value +@EqualsAndHashCode(callSuper = false) +public class UseLog extends UseLogRecipeTemplate { + + @Override + public String getDisplayName() { + return getDisplayName("@Log"); + } + + @Override + public String getDescription() { + return getDescription("@Log", "java.util.logging.Logger"); + } + + @Option(displayName = "Name of the log field", + description = FIELD_NAME_DESCRIPTION, + example = "LOGGER", + required = false) + @Nullable + String fieldName; + + @Override + public TreeVisitor getVisitor() { + return new LogVisitor( + "java.util.logging.Logger", + "java.util.logging.Logger getLogger(String)", + "lombok.extern.java.Log", + fieldName) { + + @Override + protected String getFactoryParameter(String className) { + return className + ".class.getName()"; + } + }; + } +} diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/log/UseLog4j2.java b/src/main/java/org/openrewrite/java/migrate/lombok/log/UseLog4j2.java new file mode 100644 index 000000000..479c5cd83 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/log/UseLog4j2.java @@ -0,0 +1,55 @@ +/* + * 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.lombok.log; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.TreeVisitor; + +@Value +@EqualsAndHashCode(callSuper = false) +public class UseLog4j2 extends UseLogRecipeTemplate { + + @Override + public String getDisplayName() { + return getDisplayName("@Log4j2"); + } + + @Override + public String getDescription() { + return getDescription("@Log4j2", "org.apache.logging.log4j.Logger"); + } + + @Option(displayName = "Name of the log field", + description = FIELD_NAME_DESCRIPTION, + example = "LOGGER", + required = false) + @Nullable + String fieldName; + + @Override + public TreeVisitor getVisitor() { + return new LogVisitor( + "org.apache.logging.log4j.Logger", + "org.apache.logging.log4j.LogManager getLogger(..)", + "lombok.extern.log4j.Log4j2", + fieldName); + } + +} diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/log/UseLogRecipeTemplate.java b/src/main/java/org/openrewrite/java/migrate/lombok/log/UseLogRecipeTemplate.java new file mode 100644 index 000000000..4e6b8a02e --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/log/UseLogRecipeTemplate.java @@ -0,0 +1,34 @@ +/* + * 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.lombok.log; + +import org.openrewrite.Recipe; + +abstract class UseLogRecipeTemplate extends Recipe { + + protected static final String FIELD_NAME_DESCRIPTION = "Name of the log field to replace. " + + "If not specified, the field name is not checked and any field that satisfies the other checks is converted."; + + protected String getDisplayName(String annotation) { + return String.format("Use `%s` instead of explicit fields", annotation); + } + + protected String getDescription(String annotation, String pathToLogger) { + //language=markdown + return String.format("Prefer the lombok annotation `%s` over explicitly written out `%s` fields.", annotation, pathToLogger); + } + +} diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/log/UseSlf4j.java b/src/main/java/org/openrewrite/java/migrate/lombok/log/UseSlf4j.java new file mode 100644 index 000000000..7dfd9e60c --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/log/UseSlf4j.java @@ -0,0 +1,54 @@ +/* + * 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.lombok.log; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Option; +import org.openrewrite.TreeVisitor; + +@Value +@EqualsAndHashCode(callSuper = false) +public class UseSlf4j extends UseLogRecipeTemplate { + + @Override + public String getDisplayName() { + return getDisplayName("@Slf4"); + } + + @Override + public String getDescription() { + return getDescription("@Slf4", "org.slf4j.Logger"); + } + + @Option(displayName = "Name of the log field", + description = FIELD_NAME_DESCRIPTION, + example = "LOGGER", + required = false) + @Nullable + String fieldName; + + @Override + public TreeVisitor getVisitor() { + return new LogVisitor("org.slf4j.Logger", + "org.slf4j.LoggerFactory getLogger(..)", + "lombok.extern.slf4j.Slf4j", + fieldName); + } + +} diff --git a/src/main/java/org/openrewrite/java/migrate/lombok/log/package-info.java b/src/main/java/org/openrewrite/java/migrate/lombok/log/package-info.java new file mode 100644 index 000000000..2a10bcbe0 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/lombok/log/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ +@NullMarked +@NonNullFields +package org.openrewrite.java.migrate.lombok.log; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/resources/META-INF/rewrite/jakarta-ee-9.yml b/src/main/resources/META-INF/rewrite/jakarta-ee-9.yml index af337163f..3883b3339 100644 --- a/src/main/resources/META-INF/rewrite/jakarta-ee-9.yml +++ b/src/main/resources/META-INF/rewrite/jakarta-ee-9.yml @@ -918,6 +918,12 @@ recipeList: - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: com.fasterxml.jackson.datatype.jsr353.JSR353Module newFullyQualifiedTypeName: com.fasterxml.jackson.datatype.jsonp.JSONPModule + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider + newFullyQualifiedTypeName: com.fasterxml.jackson.jakarta.rs.json.JacksonXmlBindJsonProvider + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule + newFullyQualifiedTypeName: com.fasterxml.jackson.module.jakarta.xmlbind.JakartaXmlBindAnnotationModule --- type: specs.openrewrite.org/v1beta/recipe diff --git a/src/main/resources/META-INF/rewrite/java-ee-8.yml b/src/main/resources/META-INF/rewrite/java-ee-8.yml index 98d19339a..2625b1125 100644 --- a/src/main/resources/META-INF/rewrite/java-ee-8.yml +++ b/src/main/resources/META-INF/rewrite/java-ee-8.yml @@ -23,6 +23,12 @@ tags: - javaee8 - deprecated recipeList: + - org.openrewrite.java.dependencies.ChangeDependency: + oldGroupId: javax.servlet + oldArtifactId: servlet-api + newGroupId: javax.servlet + newArtifactId: javax.servlet-api + newVersion: 3.x - org.openrewrite.java.migrate.javaee7 - org.openrewrite.java.migrate.javaee8.ServletIsRequestedSessionIdFromURL - org.openrewrite.java.migrate.javaee8.ApacheDefaultProvider @@ -42,8 +48,8 @@ type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.migrate.javaee8.ApacheDefaultProvider displayName: Flags any `org.apache.bval.jsr*` (bval 1.1) and `org.apache.bval.jsr303*` (bval 1.0) package references description: >- - This recipe flags any `org.apache.bval.jsr*` (bval 1.1) and `org.apache.bval.jsr303*` (bval 1.0) package references in validation.xml deployment descriptors. - Bean Validation 2.0 and later use the Hibernate Validator implementation instead of the + This recipe flags any `org.apache.bval.jsr*` (bval 1.1) and `org.apache.bval.jsr303*` (bval 1.0) package references in validation.xml deployment descriptors. + Bean Validation 2.0 and later use the Hibernate Validator implementation instead of the Apache BVal implementation which was used for Bean Validation 1.0 and 1.1. recipeList: - org.openrewrite.xml.ChangeTagValue: @@ -60,4 +66,3 @@ recipeList: newValue: org.hibernate.validator.engine.ConstraintValidatorFactoryImpl - org.openrewrite.xml.RemoveXmlTag: xPath: /validation-config/parameter-name-provider - \ No newline at end of file diff --git a/src/main/resources/META-INF/rewrite/lombok.yml b/src/main/resources/META-INF/rewrite/lombok.yml index 554b8c427..9e384f14b 100644 --- a/src/main/resources/META-INF/rewrite/lombok.yml +++ b/src/main/resources/META-INF/rewrite/lombok.yml @@ -13,7 +13,29 @@ # See the License for the specific language governing permissions and # limitations under the License. # - +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.migrate.lombok.LombokBestPractices +displayName: Lombok Best Practices +description: >- + Applies all recipes that enforce best practices for using Lombok. +recipeList: + - org.openrewrite.java.migrate.lombok.UpdateLombokToJava11 + - org.openrewrite.java.migrate.lombok.log.UseLombokLogAnnotations + - org.openrewrite.java.migrate.lombok.UseLombokGetter + - org.openrewrite.java.migrate.lombok.UseLombokSetter + - org.openrewrite.java.migrate.lombok.UseNoArgsConstructor + - org.openrewrite.maven.ChangeDependencyScope: + groupId: org.projectlombok + artifactId: lombok + newScope: provided + - org.openrewrite.maven.ChangeDependencyScope: + groupId: org.projectlombok + artifactId: lombok-mapstruct-binding + newScope: provided + - org.openrewrite.maven.ExcludeDependency: + groupId: org.projectlombok + artifactId: lombok --- type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.migrate.lombok.UpdateLombokToJava11 @@ -43,3 +65,16 @@ recipeList: oldFullyQualifiedTypeName: lombok.experimental.val newFullyQualifiedTypeName: lombok.val - org.openrewrite.java.migrate.lombok.LombokValToFinalVar + +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.openrewrite.java.migrate.lombok.log.UseLombokLogAnnotations +displayName: Use Lombok logger annotations instead of explicit fields +description: >- + Applies all recipes that replace logger declarations with class level annotations. +recipeList: + - org.openrewrite.java.migrate.lombok.log.UseCommonsLog + - org.openrewrite.java.migrate.lombok.log.UseJBossLog + - org.openrewrite.java.migrate.lombok.log.UseLog + - org.openrewrite.java.migrate.lombok.log.UseLog4j2 + - org.openrewrite.java.migrate.lombok.log.UseSlf4j diff --git a/src/main/resources/META-INF/rewrite/no-guava.yml b/src/main/resources/META-INF/rewrite/no-guava.yml index 82f7f90a3..56e204bf7 100644 --- a/src/main/resources/META-INF/rewrite/no-guava.yml +++ b/src/main/resources/META-INF/rewrite/no-guava.yml @@ -25,6 +25,8 @@ description: >- tags: - guava recipeList: + - org.openrewrite.java.migrate.guava.NoGuavaJava11 + - org.openrewrite.java.migrate.guava.NoGuavaJava21 - org.openrewrite.java.migrate.guava.NoGuavaCreateTempDir - org.openrewrite.java.migrate.guava.NoGuavaDirectExecutor - org.openrewrite.java.migrate.guava.NoGuavaListsNewArrayList @@ -62,6 +64,8 @@ recipeList: - org.openrewrite.java.migrate.guava.PreferMathMultiplyExact - org.openrewrite.java.migrate.guava.NoGuavaAtomicsNewReference + - tech.picnic.errorprone.refasterrules.InputStreamRulesRecipes + --- type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.migrate.guava.NoGuavaJava11 @@ -73,8 +77,10 @@ description: >- tags: - guava - java11 +preconditions: + - org.openrewrite.java.search.HasJavaVersion: + version: "[11,)" recipeList: - - org.openrewrite.java.migrate.guava.NoGuava - org.openrewrite.java.migrate.guava.NoGuavaImmutableListOf - org.openrewrite.java.migrate.guava.NoGuavaImmutableMapOf - org.openrewrite.java.migrate.guava.NoGuavaImmutableSetOf @@ -95,9 +101,13 @@ description: >- tags: - guava - java21 +preconditions: + - org.openrewrite.java.search.HasJavaVersion: + version: "[21,)" recipeList: - - org.openrewrite.java.migrate.guava.NoGuavaJava11 + - org.openrewrite.java.migrate.guava.NoMapsAndSetsWithExpectedSize - org.openrewrite.java.migrate.guava.PreferMathClamp + --- type: specs.openrewrite.org/v1beta/recipe name: org.openrewrite.java.migrate.guava.PreferJavaNioCharsetStandardCharsets diff --git a/src/main/resources/sdkman-java.csv b/src/main/resources/sdkman-java.csv index ed0b5eee7..332f5dcd5 100644 --- a/src/main/resources/sdkman-java.csv +++ b/src/main/resources/sdkman-java.csv @@ -76,15 +76,23 @@ 24.ea.23-graal 24.ea.24-graal 24.ea.25-graal +24.ea.26-graal 24.ea.26-open +24.ea.27-graal 24.ea.27-open 24.ea.28-open 24.ea.29-open +24.ea.30-open +24.ea.31-open 25.ea.1-graal 25.ea.1-open 25.ea.2-graal 25.ea.2-open +25.ea.3-graal 25.ea.3-open +25.ea.4-graal +25.ea.4-open +25.ea.5-open 6.0.119-zulu 7.0.352-zulu 8.0.282-trava diff --git a/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaJava21Test.java b/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaJava21Test.java index f59f1a57d..8eed3f18d 100644 --- a/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaJava21Test.java +++ b/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaJava21Test.java @@ -17,23 +17,17 @@ import org.junit.jupiter.api.Test; import org.openrewrite.Issue; -import org.openrewrite.config.Environment; import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; -import static org.openrewrite.java.Assertions.java; -import static org.openrewrite.java.Assertions.version; +import static org.openrewrite.java.Assertions.*; class NoGuavaJava21Test implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe( - Environment.builder() - .scanRuntimeClasspath("org.openrewrite.java.migrate.guava") - .build() - .activateRecipes("org.openrewrite.java.migrate.guava.NoGuavaJava21") - ) + spec + .recipeFromResource("/META-INF/rewrite/no-guava.yml", "org.openrewrite.java.migrate.guava.NoGuava") .parser(JavaParser.fromJavaVersion().classpath("guava")); } @@ -122,32 +116,30 @@ public float testMethod() { @Test void noGuavaImmutableOfException() { rewriteRun( - version( - //language=java - java( - """ - import com.google.common.collect.ImmutableSet; - import com.google.common.collect.ImmutableMap; + //language=java + java( + """ + import com.google.common.collect.ImmutableSet; + import com.google.common.collect.ImmutableMap; - class A { - public Object getMap() { - return ImmutableMap.of("key", ImmutableSet.of("value1", "value2")); - } - } - """, - """ - import com.google.common.collect.ImmutableSet; + class A { + public Object getMap() { + return ImmutableMap.of("key", ImmutableSet.of("value1", "value2")); + } + } + """, + """ + import com.google.common.collect.ImmutableSet; - import java.util.Map; + import java.util.Map; - class A { - public Object getMap() { - return Map.of("key", ImmutableSet.of("value1", "value2")); - } - } - """ - ), - 21 + class A { + public Object getMap() { + return Map.of("key", ImmutableSet.of("value1", "value2")); + } + } + """, + spec -> spec.markers(javaVersion(21)) ) ); } diff --git a/src/test/java/org/openrewrite/java/migrate/guava/PreferJavaUtilObjectsTest.java b/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaRefasterTest.java similarity index 85% rename from src/test/java/org/openrewrite/java/migrate/guava/PreferJavaUtilObjectsTest.java rename to src/test/java/org/openrewrite/java/migrate/guava/NoGuavaRefasterTest.java index f3df9d5b3..7ebd43113 100644 --- a/src/test/java/org/openrewrite/java/migrate/guava/PreferJavaUtilObjectsTest.java +++ b/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaRefasterTest.java @@ -23,7 +23,7 @@ import static org.openrewrite.java.Assertions.java; -class PreferJavaUtilObjectsTest implements RewriteTest { +class NoGuavaRefasterTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { spec.recipe(new NoGuavaRefasterRecipes()) @@ -184,31 +184,4 @@ Object foo(Object obj) { ) ); } - - @Test - void moreObjectsFirstNonNullToObjectsRequireNonNullElse() { - rewriteRun(spec -> spec.recipeFromResource("/META-INF/rewrite/no-guava.yml", "org.openrewrite.java.migrate.guava.NoGuavaJava11"), - //language=java - java( - """ - import com.google.common.base.MoreObjects; - - class A { - Object foo(Object obj) { - return MoreObjects.firstNonNull(obj, "default"); - } - } - """, - """ - import java.util.Objects; - - class A { - Object foo(Object obj) { - return Objects.requireNonNullElse(obj, "default"); - } - } - """ - ) - ); - } } diff --git a/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaTest.java b/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaTest.java index 5701794a8..f53de64aa 100644 --- a/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaTest.java +++ b/src/test/java/org/openrewrite/java/migrate/guava/NoGuavaTest.java @@ -16,26 +16,52 @@ package org.openrewrite.java.migrate.guava; import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; import org.openrewrite.Issue; -import org.openrewrite.config.Environment; import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.java.Assertions.javaVersion; class NoGuavaTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { - spec.recipe( - Environment.builder() - .scanRuntimeClasspath("org.openrewrite.java.migrate.guava") - .build() - .activateRecipes("org.openrewrite.java.migrate.guava.NoGuava") - ) + spec + .recipeFromResource("/META-INF/rewrite/no-guava.yml", "org.openrewrite.java.migrate.guava.NoGuava") .parser(JavaParser.fromJavaVersion().classpath("guava")); } + @DocumentExample + @Test + void moreObjectsFirstNonNullToObjectsRequireNonNullElse() { + rewriteRun( + //language=java + java( + """ + import com.google.common.base.MoreObjects; + + class A { + Object foo(Object obj) { + return MoreObjects.firstNonNull(obj, "default"); + } + } + """, + """ + import java.util.Objects; + + class A { + Object foo(Object obj) { + return Objects.requireNonNullElse(obj, "default"); + } + } + """, + spec -> spec.markers(javaVersion(11)) + ) + ); + } + @Test @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/39#issuecomment-910673213") void preferJavaUtilObjectsHashCode() { diff --git a/src/test/java/org/openrewrite/java/migrate/guava/NoMapsAndSetsWithExpectedSizeTest.java b/src/test/java/org/openrewrite/java/migrate/guava/NoMapsAndSetsWithExpectedSizeTest.java new file mode 100644 index 000000000..aad926d65 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/guava/NoMapsAndSetsWithExpectedSizeTest.java @@ -0,0 +1,69 @@ +/* + * 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.guava; + +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.javaVersion; + +class NoMapsAndSetsWithExpectedSizeTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new NoMapsAndSetsWithExpectedSize()); + } + + @DocumentExample + @Test + void noMapSetWithExpectedSize() { + rewriteRun( + //language=java + java( + """ + import com.google.common.collect.Maps; + import com.google.common.collect.Sets; + import java.util.Map; + import java.util.Set; + + class A { + void method() { + Map a = Maps.newHashMapWithExpectedSize(1); + Map b = Maps.newLinkedHashMapWithExpectedSize(1); + Set c = Sets.newHashSetWithExpectedSize(1); + Set d = Sets.newLinkedHashSetWithExpectedSize(1); + } + } + """, + """ + import java.util.*; + + class A { + void method() { + Map a = new HashMap<>(1); + Map b = new LinkedHashMap<>(1); + Set c = new HashSet<>(1); + Set d = new LinkedHashSet<>(1); + } + } + """, + spec -> spec.markers(javaVersion(21)) + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/migrate/jakarta/JacksonJavaxtoJakartaTest.java b/src/test/java/org/openrewrite/java/migrate/jakarta/JacksonJavaxtoJakartaTest.java index c818ed55a..d35ebf18f 100644 --- a/src/test/java/org/openrewrite/java/migrate/jakarta/JacksonJavaxtoJakartaTest.java +++ b/src/test/java/org/openrewrite/java/migrate/jakarta/JacksonJavaxtoJakartaTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; import org.openrewrite.config.Environment; import org.openrewrite.java.JavaParser; import org.openrewrite.test.RecipeSpec; @@ -294,4 +295,63 @@ private JSONPModule getModule() { ) ); } + + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/652") + @Test + void thatJaxbAnnotationModuleIsRewritten() { + rewriteRun( + spec -> spec.parser(JavaParser.fromJavaVersion().classpath( + "jackson-core", + "jackson-databind", + "jackson-module-jaxb-annotations")), + //language=java + java( + """ + import com.fasterxml.jackson.databind.ObjectMapper; + import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule; + + public class JacksonTest { + void foo() { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JaxbAnnotationModule()); + } + } + """, + """ + import com.fasterxml.jackson.databind.ObjectMapper; + import com.fasterxml.jackson.module.jakarta.xmlbind.JakartaXmlBindAnnotationModule; + + public class JacksonTest { + void foo() { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JakartaXmlBindAnnotationModule()); + } + } + """ + ) + ); + } + + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/653") + @Test + void thatJaxbJsonProviderIsRewritten() { + rewriteRun( + spec -> spec.parser(JavaParser.fromJavaVersion().classpath( + "jackson-databind", + "jackson-jaxrs-json-provider")), + //language=java + java( + """ + import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; + + public class A extends JacksonJaxbJsonProvider {} + """, + """ + import com.fasterxml.jackson.jakarta.rs.json.JacksonXmlBindJsonProvider; + + public class A extends JacksonXmlBindJsonProvider {} + """ + ) + ); + } } diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/LombokBestPracticesTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/LombokBestPracticesTest.java new file mode 100644 index 000000000..72a1dad87 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/lombok/LombokBestPracticesTest.java @@ -0,0 +1,188 @@ +/* + * 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.lombok; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.SourceSpec; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.java.Assertions.mavenProject; +import static org.openrewrite.maven.Assertions.pomXml; + +class LombokBestPracticesTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipeFromResource("/META-INF/rewrite/lombok.yml", + "org.openrewrite.java.migrate.lombok.LombokBestPractices"); + } + + @DocumentExample + @Test + void providedScope() { + rewriteRun( + pomXml( + //language=xml + """ + + 4.0.0 + com.example + example + 1.0.0 + + + org.projectlombok + lombok + 1.18.6 + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + + + + """, + spec -> spec.after(pom -> { + Matcher version = Pattern.compile("1.[1-9]\\d+(.\\d+)?").matcher(pom); + assertThat(version.find()).isTrue(); + //language=xml + return """ + + 4.0.0 + com.example + example + 1.0.0 + + + org.projectlombok + lombok + %s + provided + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + provided + + + + """.formatted(version.group(0)); + }) + ) + ); + } + + @Test + void excludeTransitiveLombok() { + //language=xml + rewriteRun( + mavenProject( + "parent", + pomXml( + """ + + 4.0.0 + com.example + parent + 1.0.0 + + library + project + + + """ + ), + mavenProject("library", + pomXml( + """ + + 4.0.0 + + com.example + parent + 1.0.0 + + library + + + org.projectlombok + lombok + 1.18.6 + + + + """, + SourceSpec::skip // Keep as is, such that we trigger exclude below + ) + ), + mavenProject("project", + pomXml( + """ + + 4.0.0 + + com.example + parent + 1.0.0 + + project + + + com.example + library + 1.0.0 + + + + """, + """ + + 4.0.0 + + com.example + parent + 1.0.0 + + project + + + com.example + library + 1.0.0 + + + org.projectlombok + lombok + + + + + + """ + ) + ) + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java new file mode 100644 index 000000000..92ec7a702 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/lombok/UseNoArgsConstructorTest.java @@ -0,0 +1,138 @@ +/* + * Copyright 2024 the original author or authors. + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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.lombok; + +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; + +class UseNoArgsConstructorTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new UseNoArgsConstructor()); + } + + @DocumentExample + @Test + void replaceEmptyPublicConstructor() { + rewriteRun(// language=java + java( + """ + class A { + public A() {} + } + """, + """ + import lombok.NoArgsConstructor; + + @NoArgsConstructor + class A { + } + """ + ) + ); + } + + @Test + void keepNonEmptyPublicConstructor() { + rewriteRun( + //language=java + java( + """ + class A { + + int foo; + + public A() { + foo = 7; + } + } + """ + ) + ); + } + + @Test + void replaceEmptyProtectedConstructor() { + rewriteRun( + //language=java + java( + """ + class A { + protected A() {} + } + """, + """ + import lombok.AccessLevel; + import lombok.NoArgsConstructor; + + @NoArgsConstructor(access = AccessLevel.PROTECTED) + class A { + } + """ + ) + ); + } + + @Test + void replaceEmptyPrivateConstructor() { + rewriteRun( + //language=java + java( + """ + class A { + private A() {} + } + """, + """ + import lombok.AccessLevel; + import lombok.NoArgsConstructor; + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + class A { + } + """ + ) + ); + } + + @Test + void replaceEmptyPackageConstructor() { + rewriteRun( + //language=java + java( + """ + class A { + A() {} + } + """, + """ + import lombok.AccessLevel; + import lombok.NoArgsConstructor; + + @NoArgsConstructor(access = AccessLevel.PACKAGE) + class A { + } + """ + ) + ); + } + +} diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/log/ConvertAnyLogTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/log/ConvertAnyLogTest.java new file mode 100644 index 000000000..3a1428633 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/lombok/log/ConvertAnyLogTest.java @@ -0,0 +1,122 @@ +/* + * 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.lombok.log; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class ConvertAnyLogTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipeFromResources("org.openrewrite.java.migrate.lombok.log.UseLombokLogAnnotations") + .parser(JavaParser.fromJavaVersion() + .classpath("slf4j-api", "log4j-api", "jboss-logging", "commons-logging")); + } + + @DocumentExample + @Test + void replaceAllLoggers() { + rewriteRun( + // language=java + java( + """ + class A { + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(A.class); + } + """, + """ + import lombok.extern.slf4j.Slf4j; + + @Slf4j + class A { + } + """ + ), + // language=java + java( + """ + import org.apache.logging.log4j.Logger; + import org.apache.logging.log4j.LogManager; + class B { + private static final Logger log = LogManager.getLogger(B.class); + } + """, + """ + import lombok.extern.log4j.Log4j2; + + @Log4j2 + class B { + } + """ + ), + // language=java + java( + """ + import java.util.logging.Logger; + class C { + private static final Logger log = Logger.getLogger(C.class.getName()); + } + """, + """ + import lombok.extern.java.Log; + + @Log + class C { + } + """ + ), + // language=java + java( + """ + import org.jboss.logging.Logger; + class D { + private static final Logger log = Logger.getLogger(D.class); + } + """, + """ + import lombok.extern.jbosslog.JBossLog; + + @JBossLog + class D { + } + """ + ), + // language=java + java( + """ + import org.apache.commons.logging.Log; + import org.apache.commons.logging.LogFactory; + class E { + private static final Log log = LogFactory.getLog(E.class); + } + """, + """ + import lombok.extern.apachecommons.CommonsLog; + + @CommonsLog + class E { + } + """ + ) + ); + } +} diff --git a/src/test/java/org/openrewrite/java/migrate/lombok/log/ConvertToLogAnnotationTest.java b/src/test/java/org/openrewrite/java/migrate/lombok/log/ConvertToLogAnnotationTest.java new file mode 100644 index 000000000..aa2461907 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/lombok/log/ConvertToLogAnnotationTest.java @@ -0,0 +1,322 @@ +/* + * 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.lombok.log; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.openrewrite.DocumentExample; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class ConvertToLogAnnotationTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new UseSlf4j(null)) + .parser(JavaParser.fromJavaVersion() + .classpath("slf4j-api")); + } + + @DocumentExample + @Test + void replaceSlf4j() { + rewriteRun(// language=java + java( + """ + class A { + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(A.class); + } + """, + """ + import lombok.extern.slf4j.Slf4j; + + @Slf4j + class A { + } + """ + ) + ); + } + + @Test + void replaceSlf4jAndRenameFieldUsages() { + rewriteRun( + // language=java + java( + """ + class A { + private static final org.slf4j.Logger renamed = org.slf4j.LoggerFactory.getLogger(A.class); + + void test() { + renamed.info("test"); + } + } + """, + """ + import lombok.extern.slf4j.Slf4j; + + @Slf4j + class A { + + void test() { + log.info("test"); + } + } + """ + ) + ); + } + + @Test + void replaceSlf4jImportedType() { + rewriteRun(// language=java + java( + """ + import org.slf4j.Logger; + class A { + private static final Logger log = org.slf4j.LoggerFactory.getLogger(A.class); + } + """, + """ + import lombok.extern.slf4j.Slf4j; + + @Slf4j + class A { + } + """ + ) + ); + } + + @Test + void replaceSlf4jImportedLogger() { + rewriteRun(// language=java + java( + """ + import org.slf4j.LoggerFactory; + class A { + private static final org.slf4j.Logger log = LoggerFactory.getLogger(A.class); + } + """, + """ + import lombok.extern.slf4j.Slf4j; + + @Slf4j + class A { + } + """ + ) + ); + } + + @Test + void replaceSlf4jStaticallyImportedLogger() { + rewriteRun(// language=java + java( + """ + import static org.slf4j.LoggerFactory.*; + class A { + private static final org.slf4j.Logger log = getLogger(A.class); + } + """, + """ + import lombok.extern.slf4j.Slf4j; + + @Slf4j + class A { + } + """ + ) + ); + } + + @Test + void shouldNotReplaceWhenFieldNameDiffersFromSpecifiedName() { + rewriteRun( + spec -> spec.recipe(new UseSlf4j("log")) + .parser(JavaParser.fromJavaVersion() + .classpath("slf4j-api")), + + // language=java + java( + """ + class A { + private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(A.class); + } + """ + ) + ); + } + + @ParameterizedTest + @ValueSource(strings = { + "public static final", + "protected static final", + "static final", + "private final", + "private static", + }) + void shouldNotReplaceWhenNotPrivateStaticFinal(String modifiers) { + rewriteRun( + spec -> spec.recipe(new UseSlf4j(null)) + .parser(JavaParser.fromJavaVersion() + .classpath("slf4j-api")), + + // language=java + java( + """ + class A { + %s org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(A.class); + } + """.formatted(modifiers) + ) + ); + } + + @Test + void replaceSlf4jWithPackage() { + rewriteRun(// language=java + java( + """ + package com.yourorg.yourapp; + class A { + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(A.class); + } + """, + """ + package com.yourorg.yourapp; + + import lombok.extern.slf4j.Slf4j; + + @Slf4j + class A { + } + """ + ) + ); + } + + @Test + void replaceLog4j() { + rewriteRun( + spec -> spec.recipe(new UseLog4j2(null)) + .parser(JavaParser.fromJavaVersion() + .logCompilationWarningsAndErrors(true) + .classpath("log4j-api")), + // language=java + java( + """ + import org.apache.logging.log4j.Logger; + import org.apache.logging.log4j.LogManager; + class A { + private static final Logger log = LogManager.getLogger(A.class); + } + """, + """ + import lombok.extern.log4j.Log4j2; + + @Log4j2 + class A { + } + """ + ) + ); + } + + @Test + void replaceLog() { + rewriteRun( + spec -> spec.recipe(new UseLog(null)) + .parser(JavaParser.fromJavaVersion() + .logCompilationWarningsAndErrors(true)), + + // language=java + java( + """ + import java.util.logging.Logger; + class A { + private static final Logger log = Logger.getLogger(A.class.getName()); + } + """, + """ + import lombok.extern.java.Log; + + @Log + class A { + } + """ + ) + ); + } + + @Test + void replaceJbossLog() { + rewriteRun( + spec -> spec.recipe(new UseJBossLog(null)) + .parser(JavaParser.fromJavaVersion() + .logCompilationWarningsAndErrors(true) + .classpath("jboss-logging")), + // language=java + java( + """ + import org.jboss.logging.Logger; + class A { + private static final Logger log = Logger.getLogger(A.class); + } + """, + """ + import lombok.extern.jbosslog.JBossLog; + + @JBossLog + class A { + } + """ + ) + ); + } + + @Test + void replaceCommonsLog() { + rewriteRun( + spec -> spec.recipe(new UseCommonsLog(null)) + .parser(JavaParser.fromJavaVersion() + .logCompilationWarningsAndErrors(true) + .classpath("commons-logging")), + // language=java + java( + """ + import org.apache.commons.logging.Log; + import org.apache.commons.logging.LogFactory; + class A { + private static final Log log = LogFactory.getLog(A.class); + } + """, + """ + import lombok.extern.apachecommons.CommonsLog; + + @CommonsLog + class A { + } + """ + ) + ); + } + +}