Skip to content

Commit 4d5d678

Browse files
authored
Merge branch 'openrewrite:main' into lombok/convert-equals-negligently
2 parents 4194a9d + ef63ef1 commit 4d5d678

28 files changed

+1689
-90
lines changed

README.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
1-
![Logo](https://github.com/openrewrite/rewrite/raw/main/doc/logo-oss.png)
2-
3-
### Migrate to later Java versions. Automatically.
4-
1+
<p align="center">
2+
<a href="https://docs.openrewrite.org">
3+
<picture>
4+
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/openrewrite/rewrite/raw/main/doc/logo-oss-dark.svg">
5+
<source media="(prefers-color-scheme: light)" srcset="https://github.com/openrewrite/rewrite/raw/main/doc/logo-oss-light.svg">
6+
<img alt="OpenRewrite Logo" src="https://github.com/openrewrite/rewrite/raw/main/doc/logo-oss-light.svg" width='600px'>
7+
</picture>
8+
</a>
9+
</p>
10+
11+
<div align="center">
12+
<h1>rewrite-migrate-java</h1>
13+
</div>
14+
15+
<div align="center">
16+
17+
<!-- Keep the gap above this line, otherwise they won't render correctly! -->
518
[![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)
619
[![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)
720
[![Revved up by Develocity](https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A)](https://ge.openrewrite.org/scans)
21+
[![Contributing Guide](https://img.shields.io/badge/Contributing-Guide-informational)](https://github.com/openrewrite/.github/blob/main/CONTRIBUTING.md)
22+
</div>
823

924
### What is this?
1025

build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ dependencies {
3535
runtimeOnly("org.openrewrite:rewrite-java-17")
3636
runtimeOnly("org.openrewrite:rewrite-java-21")
3737

38+
runtimeOnly("tech.picnic.error-prone-support:error-prone-contrib:latest.release:recipes")
39+
3840
testImplementation("org.junit.jupiter:junit-jupiter-api:latest.release")
3941
testImplementation("org.junit.jupiter:junit-jupiter-params:latest.release")
4042
testImplementation("org.junit-pioneer:junit-pioneer:2.0.0")
@@ -54,8 +56,13 @@ dependencies {
5456
testRuntimeOnly("com.fasterxml.jackson.datatype:jackson-datatype-jsr353")
5557
testRuntimeOnly("com.fasterxml.jackson.core:jackson-core")
5658
testRuntimeOnly("com.fasterxml.jackson.core:jackson-databind")
59+
testRuntimeOnly("com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider")
60+
testRuntimeOnly("com.fasterxml.jackson.module:jackson-module-jaxb-annotations")
61+
testRuntimeOnly("commons-logging:commons-logging:1.3.2")
62+
testRuntimeOnly("org.apache.logging.log4j:log4j-api:2.23.1")
5763
testRuntimeOnly("org.apache.johnzon:johnzon-core:1.2.18")
5864
testRuntimeOnly("org.codehaus.groovy:groovy:latest.release")
65+
testRuntimeOnly("org.jboss.logging:jboss-logging:3.6.0.Final")
5966
testRuntimeOnly("jakarta.annotation:jakarta.annotation-api:2.1.1")
6067
testRuntimeOnly("org.springframework:spring-core:6.1.13")
6168
testRuntimeOnly("com.google.code.findbugs:jsr305:3.0.2")

src/main/java/org/openrewrite/java/migrate/UpdateSdkMan.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,17 @@
2525
import org.openrewrite.text.PlainText;
2626
import org.openrewrite.text.PlainTextParser;
2727

28+
import java.io.BufferedReader;
2829
import java.io.IOException;
29-
import java.net.URISyntaxException;
30-
import java.net.URL;
31-
import java.nio.file.Files;
32-
import java.nio.file.Paths;
30+
import java.io.InputStream;
31+
import java.io.InputStreamReader;
32+
import java.nio.charset.StandardCharsets;
3333
import java.util.List;
3434
import java.util.regex.Matcher;
3535
import java.util.regex.Pattern;
3636

3737
import static java.util.Objects.requireNonNull;
38+
import static java.util.stream.Collectors.toList;
3839

3940
@Value
4041
@EqualsAndHashCode(callSuper = false)
@@ -102,15 +103,14 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
102103
}
103104

104105
private List<String> readSdkmanJavaCandidates() {
105-
URL resource = getClass().getResource("/sdkman-java.csv");
106-
if (resource != null) {
107-
try {
108-
return Files.readAllLines(Paths.get(resource.toURI()));
109-
} catch (IOException | URISyntaxException e) {
110-
throw new RuntimeException(e);
111-
}
106+
try (InputStream resourceAsStream = UpdateSdkMan.class.getResourceAsStream("/sdkman-java.csv");
107+
InputStreamReader inputStreamReader = new InputStreamReader(resourceAsStream, StandardCharsets.UTF_8);
108+
BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
109+
return bufferedReader.lines().collect(toList());
110+
} catch (IOException e) {
111+
throw new RuntimeException(e);
112112
}
113-
throw new IllegalStateException("Could not find /sdkman-java.csv file");
113+
114114
}
115115
};
116116
return Preconditions.check(new FindSourceFiles(".sdkmanrc"), visitor);
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.migrate.guava;
17+
18+
import org.openrewrite.ExecutionContext;
19+
import org.openrewrite.Preconditions;
20+
import org.openrewrite.Recipe;
21+
import org.openrewrite.TreeVisitor;
22+
import org.openrewrite.java.JavaTemplate;
23+
import org.openrewrite.java.JavaVisitor;
24+
import org.openrewrite.java.MethodMatcher;
25+
import org.openrewrite.java.search.UsesJavaVersion;
26+
import org.openrewrite.java.search.UsesMethod;
27+
import org.openrewrite.java.tree.J;
28+
import org.openrewrite.java.tree.JavaCoordinates;
29+
30+
public class NoMapsAndSetsWithExpectedSize extends Recipe {
31+
32+
private static final MethodMatcher NEW_HASHMAP = new MethodMatcher("com.google.common.collect.Maps newHashMapWithExpectedSize(int)", false);
33+
private static final MethodMatcher NEW_LINKED_HASHMAP = new MethodMatcher("com.google.common.collect.Maps newLinkedHashMapWithExpectedSize(int)", false);
34+
private static final MethodMatcher NEW_HASHSET = new MethodMatcher("com.google.common.collect.Sets newHashSetWithExpectedSize(int)", false);
35+
private static final MethodMatcher NEW_LINKED_HASHSET = new MethodMatcher("com.google.common.collect.Sets newLinkedHashSetWithExpectedSize(int)", false);
36+
37+
@Override
38+
public String getDisplayName() {
39+
return "Prefer JDK methods for Maps and Sets of an expected size";
40+
}
41+
42+
@Override
43+
public String getDescription() {
44+
return "Prefer Java 19+ methods to create Maps and Sets of an expected size instead of using Guava methods.";
45+
}
46+
47+
@Override
48+
public TreeVisitor<?, ExecutionContext> getVisitor() {
49+
return Preconditions.check(
50+
Preconditions.and(
51+
new UsesJavaVersion<>(19),
52+
Preconditions.or(
53+
new UsesMethod<>(NEW_HASHMAP),
54+
new UsesMethod<>(NEW_LINKED_HASHMAP),
55+
new UsesMethod<>(NEW_HASHSET),
56+
new UsesMethod<>(NEW_LINKED_HASHSET)
57+
)
58+
),
59+
new JavaVisitor<ExecutionContext>() {
60+
@Override
61+
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
62+
J.MethodInvocation j = (J.MethodInvocation) super.visitMethodInvocation(method, ctx);
63+
if (NEW_HASHMAP.matches(j)) {
64+
maybeRemoveImport("com.google.common.collect.Maps");
65+
maybeAddImport("java.util.HashMap");
66+
JavaCoordinates coordinates = j.getCoordinates().replace();
67+
return JavaTemplate.builder("new HashMap<>(#{any()})")
68+
.imports("java.util.HashMap")
69+
.build()
70+
.apply(getCursor(), coordinates, j.getArguments().toArray());
71+
} else if (NEW_LINKED_HASHMAP.matches(j)) {
72+
maybeRemoveImport("com.google.common.collect.Maps");
73+
maybeAddImport("java.util.LinkedHashMap");
74+
JavaCoordinates coordinates = j.getCoordinates().replace();
75+
return JavaTemplate.builder("new LinkedHashMap<>(#{any()})")
76+
.imports("java.util.LinkedHashMap")
77+
.build()
78+
.apply(getCursor(), coordinates, j.getArguments().toArray());
79+
} else if (NEW_HASHSET.matches(j)) {
80+
maybeRemoveImport("com.google.common.collect.Sets");
81+
maybeAddImport("java.util.HashSet");
82+
JavaCoordinates coordinates = j.getCoordinates().replace();
83+
return JavaTemplate.builder("new HashSet<>(#{any()})")
84+
.imports("java.util.HashSet")
85+
.build()
86+
.apply(getCursor(), coordinates, j.getArguments().toArray());
87+
} else if (NEW_LINKED_HASHSET.matches(j)) {
88+
maybeRemoveImport("com.google.common.collect.Sets");
89+
maybeAddImport("java.util.LinkedHashSet");
90+
JavaCoordinates coordinates = j.getCoordinates().replace();
91+
return JavaTemplate.builder("new LinkedHashSet<>(#{any()})")
92+
.imports("java.util.LinkedHashSet")
93+
.build()
94+
.apply(getCursor(), coordinates, j.getArguments().toArray());
95+
}
96+
return j;
97+
}
98+
}
99+
);
100+
}
101+
}

src/main/java/org/openrewrite/java/migrate/javax/AddColumnAnnotation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations m
9090

9191
// Update existing @Column annotation
9292
J.VariableDeclarations updatedVariable = (J.VariableDeclarations) new AddOrUpdateAnnotationAttribute(
93-
"javax.persistence.Column", "name", "element", true, null)
93+
"javax.persistence.Column", "name", "element", null, true, null)
9494
.getVisitor().visit(multiVariable, ctx, getCursor().getParentTreeCursor());
9595
return super.visitVariableDeclarations(updatedVariable, ctx);
9696
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.migrate.lombok;
17+
18+
import lombok.AccessLevel;
19+
import lombok.EqualsAndHashCode;
20+
import lombok.Value;
21+
import org.jspecify.annotations.Nullable;
22+
import org.openrewrite.ExecutionContext;
23+
import org.openrewrite.Recipe;
24+
import org.openrewrite.TreeVisitor;
25+
import org.openrewrite.java.JavaIsoVisitor;
26+
import org.openrewrite.java.JavaParser;
27+
import org.openrewrite.java.JavaTemplate;
28+
import org.openrewrite.java.tree.J;
29+
import org.openrewrite.java.tree.TypeUtils;
30+
31+
import static java.util.Comparator.comparing;
32+
33+
@Value
34+
@EqualsAndHashCode(callSuper = false)
35+
public class UseNoArgsConstructor extends Recipe {
36+
37+
@Override
38+
public String getDisplayName() {
39+
//language=markdown
40+
return "Use `@NoArgsConstructor` where applicable";
41+
}
42+
43+
@Override
44+
public String getDescription() {
45+
//language=markdown
46+
return "Prefer the Lombok `@NoArgsConstructor` annotation over explicitly written out constructors.";
47+
}
48+
49+
@Override
50+
public TreeVisitor<?, ExecutionContext> getVisitor() {
51+
return new JavaIsoVisitor<ExecutionContext>() {
52+
@Override
53+
public J.@Nullable MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
54+
if (method.isConstructor() &&
55+
method.getParameters().get(0) instanceof J.Empty &&
56+
method.getBody() != null && method.getBody().getStatements().isEmpty()) {
57+
J.ClassDeclaration enclosing = getCursor().firstEnclosing(J.ClassDeclaration.class);
58+
AccessLevel accessLevel = LombokUtils.getAccessLevel(method);
59+
doAfterVisit(new JavaIsoVisitor<ExecutionContext>() {
60+
@Override
61+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
62+
if (TypeUtils.isOfType(classDecl.getType(), enclosing.getType())) {
63+
String template = "@NoArgsConstructor" + (accessLevel == AccessLevel.PUBLIC ?
64+
"" : "(access = AccessLevel." + accessLevel.name() + ")");
65+
maybeAddImport("lombok.AccessLevel");
66+
maybeAddImport("lombok.NoArgsConstructor");
67+
return JavaTemplate.builder(template)
68+
.imports("lombok.*")
69+
.javaParser(JavaParser.fromJavaVersion().classpath("lombok"))
70+
.build()
71+
.apply(getCursor(), classDecl.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName)));
72+
}
73+
return super.visitClassDeclaration(classDecl, ctx);
74+
}
75+
});
76+
return null;
77+
}
78+
return super.visitMethodDeclaration(method, ctx);
79+
}
80+
};
81+
}
82+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.migrate.lombok.log;
17+
18+
import lombok.EqualsAndHashCode;
19+
import org.jspecify.annotations.Nullable;
20+
import org.openrewrite.ExecutionContext;
21+
import org.openrewrite.java.*;
22+
import org.openrewrite.java.tree.J;
23+
import org.openrewrite.java.tree.TypeUtils;
24+
25+
import static java.util.Comparator.comparing;
26+
27+
@EqualsAndHashCode(callSuper = false)
28+
class LogVisitor extends JavaIsoVisitor<ExecutionContext> {
29+
30+
private final String logType;
31+
private final String factoryType;
32+
private final MethodMatcher factoryMethodMatcher;
33+
private final String logAnnotation;
34+
@Nullable
35+
private final String fieldName;
36+
37+
LogVisitor(String logType, String factoryMethodPattern, String logAnnotation, @Nullable String fieldName) {
38+
this.logType = logType;
39+
this.factoryType = factoryMethodPattern.substring(0, factoryMethodPattern.indexOf(' '));
40+
this.factoryMethodMatcher = new MethodMatcher(factoryMethodPattern);
41+
this.logAnnotation = logAnnotation;
42+
this.fieldName = fieldName;
43+
}
44+
45+
@Override
46+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
47+
J.ClassDeclaration visitClassDeclaration = super.visitClassDeclaration(classDecl, ctx);
48+
if (visitClassDeclaration != classDecl) {
49+
maybeRemoveImport(logType);
50+
maybeRemoveImport(factoryType);
51+
maybeAddImport(logAnnotation);
52+
return JavaTemplate
53+
.builder("@" + logAnnotation.substring(logAnnotation.lastIndexOf('.') + 1) + "\n")
54+
.javaParser(JavaParser.fromJavaVersion().classpath("lombok"))
55+
.imports(logAnnotation)
56+
.build()
57+
.apply(
58+
updateCursor(visitClassDeclaration),
59+
visitClassDeclaration.getCoordinates().addAnnotation(comparing(J.Annotation::getSimpleName)));
60+
}
61+
return classDecl;
62+
}
63+
64+
@Override
65+
public J.@Nullable VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) {
66+
if (!multiVariable.hasModifier(J.Modifier.Type.Private) ||
67+
!multiVariable.hasModifier(J.Modifier.Type.Static) ||
68+
!multiVariable.hasModifier(J.Modifier.Type.Final) ||
69+
multiVariable.getVariables().size() != 1 ||
70+
!TypeUtils.isAssignableTo(logType, multiVariable.getType())) {
71+
return multiVariable;
72+
}
73+
74+
// name needs to match the name of the field that lombok creates
75+
J.VariableDeclarations.NamedVariable var = multiVariable.getVariables().get(0);
76+
if (fieldName != null && !fieldName.equals(var.getSimpleName())) {
77+
return multiVariable;
78+
}
79+
80+
if (!factoryMethodMatcher.matches(var.getInitializer())) {
81+
return multiVariable;
82+
}
83+
84+
J.ClassDeclaration classDeclaration = getCursor().firstEnclosing(J.ClassDeclaration.class);
85+
if (classDeclaration == null || classDeclaration.getType() == null) {
86+
return multiVariable;
87+
}
88+
89+
J.MethodInvocation methodCall = (J.MethodInvocation) var.getInitializer();
90+
if (methodCall.getArguments().size() != 1 ||
91+
!getFactoryParameter(classDeclaration.getSimpleName())
92+
.equals(methodCall.getArguments().get(0).toString())) {
93+
return multiVariable;
94+
}
95+
96+
if (!"log".equals(var.getSimpleName())) {
97+
doAfterVisit(new ChangeFieldName<>(classDeclaration.getType().getFullyQualifiedName(), var.getSimpleName(), "log"));
98+
}
99+
100+
return null;
101+
}
102+
103+
protected String getFactoryParameter(String className) {
104+
return className + ".class";
105+
}
106+
}

0 commit comments

Comments
 (0)