Skip to content

Commit 4d09463

Browse files
BramliAKtimtebeek
andauthored
Fix Guava Immutable{Set|List|Map} (#586)
* issue-489: fix ImmutableList.of(1, 2).stream() issue-520: fix isParentTypeDownCast throws IndexOutOfBoundsException * Apply formatter to clear out automated suggestions * Drop IntelliJ annotation * Minimize diff due to line wrap * Replace mutable field with identity check * Format the tests while we're at it * Add missing newline between tests * Drop `.contextSensitive` by updating name.type too * Standardize variable use around JavaTemplate * Use withers on J.ParameterizedType in createNewTypeExpression * Drop nonNull as method invocation; only used as method reference --------- Co-authored-by: Tim te Beek <[email protected]>
1 parent 88e8ff2 commit 4d09463

File tree

5 files changed

+401
-139
lines changed

5 files changed

+401
-139
lines changed

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

Lines changed: 88 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,21 @@
1616
package org.openrewrite.java.migrate.guava;
1717

1818
import org.jspecify.annotations.Nullable;
19-
import org.openrewrite.ExecutionContext;
20-
import org.openrewrite.Preconditions;
21-
import org.openrewrite.Recipe;
22-
import org.openrewrite.TreeVisitor;
19+
import org.openrewrite.*;
20+
import org.openrewrite.internal.ListUtils;
2321
import org.openrewrite.java.JavaTemplate;
2422
import org.openrewrite.java.JavaVisitor;
2523
import org.openrewrite.java.MethodMatcher;
2624
import org.openrewrite.java.search.UsesJavaVersion;
2725
import org.openrewrite.java.search.UsesType;
2826
import org.openrewrite.java.tree.*;
27+
import org.openrewrite.marker.Markers;
2928

3029
import java.time.Duration;
30+
import java.util.ArrayList;
31+
import java.util.List;
32+
33+
import static java.util.Collections.emptyList;
3134

3235
abstract class AbstractNoGuavaImmutableOf extends Recipe {
3336

@@ -66,34 +69,86 @@ public TreeVisitor<?, ExecutionContext> getVisitor() {
6669
return Preconditions.check(check, new JavaVisitor<ExecutionContext>() {
6770
@Override
6871
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
69-
if (IMMUTABLE_MATCHER.matches(method) && isParentTypeDownCast(method)) {
70-
maybeRemoveImport(guavaType);
71-
maybeAddImport(javaType);
72-
73-
String template;
74-
Object[] args;
75-
if (method.getArguments().isEmpty() || method.getArguments().get(0) instanceof J.Empty) {
76-
template = getShortType(javaType) + ".of()";
77-
args = new Object[]{};
78-
} else if ("com.google.common.collect.ImmutableMap".equals(guavaType)) {
79-
template = getShortType(javaType) + ".of(#{any()}, #{any()})";
80-
args = new Object[]{method.getArguments().get(0), method.getArguments().get(1)};
81-
} else {
82-
template = getShortType(javaType) + ".of(#{any()})";
83-
args = new Object[]{method.getArguments().get(0)};
84-
}
72+
J.MethodInvocation mi = (J.MethodInvocation) super.visitMethodInvocation(method, ctx);
73+
if (!IMMUTABLE_MATCHER.matches(mi) || !isParentTypeDownCast(mi)) {
74+
return mi;
75+
}
76+
maybeRemoveImport(guavaType);
77+
maybeAddImport(javaType);
78+
79+
String template;
80+
Object[] templateArguments;
81+
List<Expression> methodArguments = mi.getArguments();
82+
if (methodArguments.isEmpty() || methodArguments.get(0) instanceof J.Empty) {
83+
template = getShortType(javaType) + ".of()";
84+
templateArguments = new Object[]{};
85+
} else if ("com.google.common.collect.ImmutableMap".equals(guavaType)) {
86+
template = getShortType(javaType) + ".of(#{any()}, #{any()})";
87+
templateArguments = new Object[]{methodArguments.get(0), methodArguments.get(1)};
88+
} else {
89+
template = getShortType(javaType) + ".of(#{any()})";
90+
templateArguments = new Object[]{methodArguments.get(0)};
91+
}
92+
93+
J.MethodInvocation m = JavaTemplate.builder(template)
94+
.imports(javaType)
95+
.build()
96+
.apply(getCursor(), mi.getCoordinates().replace(), templateArguments);
97+
m = m.getPadding().withArguments(mi.getPadding().getArguments());
98+
JavaType.Method newType = (JavaType.Method) visitType(mi.getMethodType(), ctx);
99+
m = m.withMethodType(newType).withName(m.getName().withType(newType));
100+
return super.visitMethodInvocation(m, ctx);
101+
}
102+
103+
@Override
104+
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) {
105+
J.VariableDeclarations mv = (J.VariableDeclarations) super.visitVariableDeclarations(multiVariable, ctx);
106+
107+
if (multiVariable != mv && TypeUtils.isOfClassType(mv.getType(), guavaType)) {
108+
JavaType newType = JavaType.buildType(javaType);
109+
mv = mv.withTypeExpression(mv.getTypeExpression() == null ?
110+
null : createNewTypeExpression(mv.getTypeExpression(), newType));
85111

86-
J.MethodInvocation templated = JavaTemplate.builder(template)
87-
.imports(javaType)
88-
.build()
89-
.apply(getCursor(),
90-
method.getCoordinates().replace(),
91-
args);
92-
return templated.getPadding().withArguments(method.getPadding().getArguments());
112+
mv = mv.withVariables(ListUtils.map(mv.getVariables(), variable -> {
113+
JavaType.FullyQualified varType = TypeUtils.asFullyQualified(variable.getType());
114+
if (varType != null && !varType.equals(newType)) {
115+
return variable.withType(newType).withName(variable.getName().withType(newType));
116+
}
117+
return variable;
118+
}));
93119
}
94-
return super.visitMethodInvocation(method, ctx);
120+
121+
return mv;
95122
}
96123

124+
private TypeTree createNewTypeExpression(TypeTree typeTree, JavaType newType) {
125+
if (typeTree instanceof J.ParameterizedType) {
126+
J.ParameterizedType parameterizedType = (J.ParameterizedType) typeTree;
127+
List<JRightPadded<Expression>> jRightPaddedList = new ArrayList<>();
128+
parameterizedType.getTypeParameters().forEach(
129+
expression -> {
130+
if (expression instanceof J.ParameterizedType && TypeUtils.isOfClassType(expression.getType(), guavaType)) {
131+
jRightPaddedList.add(JRightPadded.build(((J.ParameterizedType) createNewTypeExpression((TypeTree) expression, newType))));
132+
} else {
133+
jRightPaddedList.add(JRightPadded.build(expression));
134+
}
135+
});
136+
NameTree clazz = new J.Identifier(
137+
Tree.randomId(), Space.EMPTY, Markers.EMPTY, emptyList(), getShortType(javaType), null, null);
138+
return parameterizedType.withClazz(clazz).withType(newType).getPadding().withTypeParameters(JContainer.build(jRightPaddedList));
139+
}
140+
return new J.Identifier(
141+
typeTree.getId(),
142+
typeTree.getPrefix(),
143+
Markers.EMPTY,
144+
emptyList(),
145+
getShortType(javaType),
146+
newType,
147+
null
148+
);
149+
}
150+
151+
97152
private boolean isParentTypeDownCast(MethodCall immutableMethod) {
98153
J parent = getCursor().dropParentUntil(J.class::isInstance).getValue();
99154
boolean isParentTypeDownCast = false;
@@ -118,8 +173,10 @@ private boolean isParentTypeDownCast(MethodCall immutableMethod) {
118173
} else if (parent instanceof J.MethodInvocation) {
119174
J.MethodInvocation m = (J.MethodInvocation) parent;
120175
int index = m.getArguments().indexOf(immutableMethod);
121-
if (m.getMethodType() != null && index != -1) {
176+
if (m.getMethodType() != null && index != -1 && !m.getMethodType().getParameterTypes().isEmpty()) {
122177
isParentTypeDownCast = isParentTypeMatched(m.getMethodType().getParameterTypes().get(index));
178+
} else {
179+
isParentTypeDownCast = true;
123180
}
124181
} else if (parent instanceof J.NewClass) {
125182
J.NewClass c = (J.NewClass) parent;
@@ -150,7 +207,8 @@ private boolean isParentTypeDownCast(MethodCall immutableMethod) {
150207
private boolean isParentTypeMatched(@Nullable JavaType type) {
151208
JavaType.FullyQualified fq = TypeUtils.asFullyQualified(type);
152209
return TypeUtils.isOfClassType(fq, javaType) ||
153-
TypeUtils.isOfClassType(fq, "java.lang.Object");
210+
TypeUtils.isOfClassType(fq, "java.lang.Object") ||
211+
TypeUtils.isOfClassType(fq, guavaType);
154212
}
155213
});
156214
}

0 commit comments

Comments
 (0)