Skip to content

Commit b352e2d

Browse files
Joan Viladrosatimtebeek
andauthored
Fix/no guava immutable recipes (#258)
* Fix and Abstract NoGuavaImmutable recipes * format & imports * Use constructors to pass guavaType and javaType * Reduce visibility * Make constructors public --------- Co-authored-by: Tim te Beek <tim@moderne.io>
1 parent c5383a5 commit b352e2d

File tree

7 files changed

+297
-499
lines changed

7 files changed

+297
-499
lines changed
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (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://www.apache.org/licenses/LICENSE-2.0
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.internal.lang.Nullable;
23+
import org.openrewrite.java.JavaTemplate;
24+
import org.openrewrite.java.JavaVisitor;
25+
import org.openrewrite.java.MethodMatcher;
26+
import org.openrewrite.java.search.UsesJavaVersion;
27+
import org.openrewrite.java.search.UsesType;
28+
import org.openrewrite.java.tree.*;
29+
30+
import java.time.Duration;
31+
import java.util.Objects;
32+
import java.util.stream.Collectors;
33+
34+
abstract class AbstractNoGuavaImmutableOf extends Recipe {
35+
36+
private final String guavaType;
37+
private final String javaType;
38+
39+
AbstractNoGuavaImmutableOf(String guavaType, String javaType) {
40+
this.guavaType = guavaType;
41+
this.javaType = javaType;
42+
}
43+
44+
private String getShortType(String fullyQualifiedType) {
45+
return fullyQualifiedType.substring(javaType.lastIndexOf(".") + 1);
46+
}
47+
48+
@Override
49+
public String getDisplayName() {
50+
return "Prefer `" + getShortType(javaType) + ".of(..)` in Java 9 or higher";
51+
}
52+
53+
@Override
54+
public String getDescription() {
55+
return "Replaces `" + getShortType(guavaType) + ".of(..)` if the returned type is immediately down-cast.";
56+
}
57+
58+
@Override
59+
public Duration getEstimatedEffortPerOccurrence() {
60+
return Duration.ofMinutes(10);
61+
}
62+
63+
@Override
64+
public TreeVisitor<?, ExecutionContext> getVisitor() {
65+
TreeVisitor<?, ExecutionContext> check = Preconditions.and(new UsesJavaVersion<>(9),
66+
new UsesType<>(guavaType, false));
67+
final MethodMatcher IMMUTABLE_MATCHER = new MethodMatcher(guavaType + " of(..)");
68+
return Preconditions.check(check, new JavaVisitor<ExecutionContext>() {
69+
@Override
70+
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
71+
if (IMMUTABLE_MATCHER.matches(method) && isParentTypeDownCast()) {
72+
maybeRemoveImport(guavaType);
73+
maybeAddImport(javaType);
74+
75+
String template = method.getArguments().stream()
76+
.map(arg -> {
77+
if (arg.getType() instanceof JavaType.Primitive) {
78+
String type = "";
79+
if (JavaType.Primitive.Boolean == arg.getType()) {
80+
type = "Boolean";
81+
} else if (JavaType.Primitive.Byte == arg.getType()) {
82+
type = "Byte";
83+
} else if (JavaType.Primitive.Char == arg.getType()) {
84+
type = "Character";
85+
} else if (JavaType.Primitive.Double == arg.getType()) {
86+
type = "Double";
87+
} else if (JavaType.Primitive.Float == arg.getType()) {
88+
type = "Float";
89+
} else if (JavaType.Primitive.Int == arg.getType()) {
90+
type = "Integer";
91+
} else if (JavaType.Primitive.Long == arg.getType()) {
92+
type = "Long";
93+
} else if (JavaType.Primitive.Short == arg.getType()) {
94+
type = "Short";
95+
} else if (JavaType.Primitive.String == arg.getType()) {
96+
type = "String";
97+
}
98+
return TypeUtils.asFullyQualified(JavaType.buildType("java.lang." + type));
99+
} else {
100+
return TypeUtils.asFullyQualified(arg.getType());
101+
}
102+
})
103+
.filter(Objects::nonNull)
104+
.map(type -> "#{any(" + type.getFullyQualifiedName() + ")}")
105+
.collect(Collectors.joining(",", getShortType(javaType) + ".of(", ")"));
106+
107+
return JavaTemplate.builder(template)
108+
.contextSensitive()
109+
.imports(javaType)
110+
.build()
111+
.apply(getCursor(),
112+
method.getCoordinates().replace(),
113+
method.getArguments().get(0) instanceof J.Empty ? new Object[]{} : method.getArguments().toArray());
114+
}
115+
return super.visitMethodInvocation(method, ctx);
116+
}
117+
118+
private boolean isParentTypeDownCast() {
119+
J parent = getCursor().dropParentUntil(J.class::isInstance).getValue();
120+
boolean isParentTypeDownCast = false;
121+
if (parent instanceof J.VariableDeclarations.NamedVariable) {
122+
isParentTypeDownCast = isParentTypeMatched(((J.VariableDeclarations.NamedVariable) parent).getType());
123+
} else if (parent instanceof J.Assignment) {
124+
J.Assignment a = (J.Assignment) parent;
125+
if (a.getVariable() instanceof J.Identifier && ((J.Identifier) a.getVariable()).getFieldType() != null) {
126+
isParentTypeDownCast = isParentTypeMatched(((J.Identifier) a.getVariable()).getFieldType().getType());
127+
} else if (a.getVariable() instanceof J.FieldAccess) {
128+
isParentTypeDownCast = isParentTypeMatched(a.getVariable().getType());
129+
}
130+
} else if (parent instanceof J.Return) {
131+
// Does not currently support returns in lambda expressions.
132+
J j = getCursor().dropParentUntil(is -> is instanceof J.MethodDeclaration || is instanceof J.CompilationUnit).getValue();
133+
if (j instanceof J.MethodDeclaration) {
134+
TypeTree returnType = ((J.MethodDeclaration) j).getReturnTypeExpression();
135+
if (returnType != null) {
136+
isParentTypeDownCast = isParentTypeMatched(returnType.getType());
137+
}
138+
}
139+
} else if (parent instanceof J.MethodInvocation) {
140+
J.MethodInvocation m = (J.MethodInvocation) parent;
141+
if (m.getMethodType() != null) {
142+
int index = 0;
143+
for (Expression argument : m.getArguments()) {
144+
if (IMMUTABLE_MATCHER.matches(argument)) {
145+
break;
146+
}
147+
index++;
148+
}
149+
isParentTypeDownCast = isParentTypeMatched(m.getMethodType().getParameterTypes().get(index));
150+
}
151+
} else if (parent instanceof J.NewClass) {
152+
J.NewClass c = (J.NewClass) parent;
153+
int index = 0;
154+
if (c.getConstructorType() != null) {
155+
for (Expression argument : c.getArguments()) {
156+
if (IMMUTABLE_MATCHER.matches(argument)) {
157+
break;
158+
}
159+
index++;
160+
}
161+
if (c.getConstructorType() != null) {
162+
isParentTypeDownCast = isParentTypeMatched(c.getConstructorType().getParameterTypes().get(index));
163+
}
164+
}
165+
} else if (parent instanceof J.NewArray) {
166+
J.NewArray a = (J.NewArray) parent;
167+
JavaType arrayType = a.getType();
168+
while (arrayType instanceof JavaType.Array) {
169+
arrayType = ((JavaType.Array) arrayType).getElemType();
170+
}
171+
172+
isParentTypeDownCast = isParentTypeMatched(arrayType);
173+
}
174+
return isParentTypeDownCast;
175+
}
176+
177+
private boolean isParentTypeMatched(@Nullable JavaType type) {
178+
JavaType.FullyQualified fq = TypeUtils.asFullyQualified(type);
179+
return TypeUtils.isOfClassType(fq, javaType)
180+
|| TypeUtils.isOfClassType(fq, "java.lang.Object");
181+
}
182+
});
183+
}
184+
}

src/main/java/org/openrewrite/java/migrate/guava/NoGuavaImmutableListOf.java

Lines changed: 3 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -15,165 +15,8 @@
1515
*/
1616
package org.openrewrite.java.migrate.guava;
1717

18-
import org.openrewrite.Preconditions;
19-
import org.openrewrite.ExecutionContext;
20-
import org.openrewrite.Recipe;
21-
import org.openrewrite.TreeVisitor;
22-
import org.openrewrite.internal.lang.Nullable;
23-
import org.openrewrite.java.JavaTemplate;
24-
import org.openrewrite.java.JavaVisitor;
25-
import org.openrewrite.java.MethodMatcher;
26-
import org.openrewrite.java.search.UsesJavaVersion;
27-
import org.openrewrite.java.search.UsesType;
28-
import org.openrewrite.java.tree.*;
29-
30-
import java.time.Duration;
31-
import java.util.*;
32-
import java.util.stream.Collectors;
33-
34-
public class NoGuavaImmutableListOf extends Recipe {
35-
private static final MethodMatcher IMMUTABLE_LIST_MATCHER = new MethodMatcher("com.google.common.collect.ImmutableList of(..)");
36-
37-
@Override
38-
public String getDisplayName() {
39-
return "Prefer `List.of(..)` in Java 9 or higher";
40-
}
41-
42-
@Override
43-
public String getDescription() {
44-
return "Replaces `ImmutableList.of(..)` if the returned type is immediately down-cast.";
45-
}
46-
47-
@Override
48-
public Set<String> getTags() {
49-
return new HashSet<>(Arrays.asList("RSPEC-4738", "guava"));
50-
}
51-
52-
@Override
53-
public Duration getEstimatedEffortPerOccurrence() {
54-
return Duration.ofMinutes(10);
55-
}
56-
57-
// Code is shared between `NoGuavaImmutableMapOf`, `NoGuavaImmutableListOf`, and `NoGuavaImmutableSetOf`.
58-
// Updates to either may apply to each of the recipes.
59-
@Override
60-
public TreeVisitor<?, ExecutionContext> getVisitor() {
61-
TreeVisitor<?, ExecutionContext> check = Preconditions.and(new UsesJavaVersion<>(9),
62-
new UsesType<>("com.google.common.collect.ImmutableList", false));
63-
return Preconditions.check(check, new JavaVisitor<ExecutionContext>() {
64-
@Override
65-
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
66-
if (IMMUTABLE_LIST_MATCHER.matches(method) && isParentTypeDownCast()) {
67-
maybeRemoveImport("com.google.common.collect.ImmutableList");
68-
maybeAddImport("java.util.List");
69-
70-
String template = method.getArguments().stream()
71-
.map(arg -> {
72-
if (arg.getType() instanceof JavaType.Primitive) {
73-
String type = "";
74-
if (JavaType.Primitive.Boolean == arg.getType()) {
75-
type = "Boolean";
76-
} else if (JavaType.Primitive.Byte == arg.getType()) {
77-
type = "Byte";
78-
} else if (JavaType.Primitive.Char == arg.getType()) {
79-
type = "Character";
80-
} else if (JavaType.Primitive.Double == arg.getType()) {
81-
type = "Double";
82-
} else if (JavaType.Primitive.Float == arg.getType()) {
83-
type = "Float";
84-
} else if (JavaType.Primitive.Int == arg.getType()) {
85-
type = "Integer";
86-
} else if (JavaType.Primitive.Long == arg.getType()) {
87-
type = "Long";
88-
} else if (JavaType.Primitive.Short == arg.getType()) {
89-
type = "Short";
90-
} else if (JavaType.Primitive.String == arg.getType()) {
91-
type = "String";
92-
}
93-
return TypeUtils.asFullyQualified(JavaType.buildType("java.lang." + type));
94-
} else {
95-
return TypeUtils.asFullyQualified(arg.getType());
96-
}
97-
})
98-
.filter(Objects::nonNull)
99-
.map(type -> "#{any(" + type.getFullyQualifiedName() + ")}")
100-
.collect(Collectors.joining(",", "List.of(", ")"));
101-
102-
return JavaTemplate.builder(template)
103-
.contextSensitive()
104-
.imports("java.util.List")
105-
.build()
106-
.apply(getCursor(),
107-
method.getCoordinates().replace(),
108-
method.getArguments().get(0) instanceof J.Empty ? new Object[]{} : method.getArguments().toArray());
109-
}
110-
return super.visitMethodInvocation(method, ctx);
111-
}
112-
113-
private boolean isParentTypeDownCast() {
114-
J parent = getCursor().dropParentUntil(J.class::isInstance).getValue();
115-
boolean isParentTypeDownCast = false;
116-
if (parent instanceof J.VariableDeclarations.NamedVariable) {
117-
isParentTypeDownCast = isParentTypeMatched(((J.VariableDeclarations.NamedVariable) parent).getType());
118-
} else if (parent instanceof J.Assignment) {
119-
J.Assignment a = (J.Assignment) parent;
120-
if (a.getVariable() instanceof J.Identifier && ((J.Identifier) a.getVariable()).getFieldType() != null) {
121-
isParentTypeDownCast = isParentTypeMatched(((J.Identifier) a.getVariable()).getFieldType().getType());
122-
} else if (a.getVariable() instanceof J.FieldAccess) {
123-
isParentTypeDownCast = isParentTypeMatched(a.getVariable().getType());
124-
}
125-
} else if (parent instanceof J.Return) {
126-
// Does not currently support returns in lambda expressions.
127-
J j = getCursor().dropParentUntil(is -> is instanceof J.MethodDeclaration || is instanceof J.CompilationUnit).getValue();
128-
if (j instanceof J.MethodDeclaration) {
129-
TypeTree returnType = ((J.MethodDeclaration) j).getReturnTypeExpression();
130-
if (returnType != null) {
131-
isParentTypeDownCast = isParentTypeMatched(returnType.getType());
132-
}
133-
}
134-
} else if (parent instanceof J.MethodInvocation) {
135-
J.MethodInvocation m = (J.MethodInvocation) parent;
136-
int index = 0;
137-
for (Expression argument : m.getArguments()) {
138-
if (IMMUTABLE_LIST_MATCHER.matches(argument)) {
139-
break;
140-
}
141-
index++;
142-
}
143-
if (m.getMethodType() != null) {
144-
isParentTypeDownCast = isParentTypeMatched(m.getMethodType().getParameterTypes().get(index));
145-
}
146-
} else if (parent instanceof J.NewClass) {
147-
J.NewClass c = (J.NewClass) parent;
148-
int index = 0;
149-
if (c.getConstructorType() != null && c.getArguments() != null) {
150-
for (Expression argument : c.getArguments()) {
151-
if (IMMUTABLE_LIST_MATCHER.matches(argument)) {
152-
break;
153-
}
154-
index++;
155-
}
156-
if (c.getConstructorType() != null) {
157-
isParentTypeDownCast = isParentTypeMatched(c.getConstructorType().getParameterTypes().get(index));
158-
}
159-
}
160-
} else if (parent instanceof J.NewArray) {
161-
J.NewArray a = (J.NewArray) parent;
162-
JavaType arrayType = a.getType();
163-
while (arrayType instanceof JavaType.Array) {
164-
arrayType = ((JavaType.Array) arrayType).getElemType();
165-
}
166-
167-
isParentTypeDownCast = isParentTypeMatched(arrayType);
168-
}
169-
return isParentTypeDownCast;
170-
}
171-
172-
private boolean isParentTypeMatched(@Nullable JavaType type) {
173-
JavaType.FullyQualified fq = TypeUtils.asFullyQualified(type);
174-
return TypeUtils.isOfClassType(fq, "java.util.List") || TypeUtils.isOfType(fq, JavaType.ShallowClass.build("java.lang.Object"));
175-
}
176-
});
18+
public class NoGuavaImmutableListOf extends AbstractNoGuavaImmutableOf {
19+
public NoGuavaImmutableListOf(){
20+
super("com.google.common.collect.ImmutableList", "java.util.List");
17721
}
17822
}
179-

0 commit comments

Comments
 (0)