Skip to content

Commit 1a50a7b

Browse files
MBoegersJoan Viladrosa
andauthored
236 var for methods (#249)
* fix missing imports in VarBaseTest * add Tests to prove issue #236 us already covered (except generics) * add new Recipe to cover var for generics add test harness * add missing licences * Implement var for generic constructor invocations and move general method invocation to own recipe * Update UseVarKeywordTest to be compliant with child test UseVarForGenericsConstructorsTest * Modify Generic Method Invocations * Update name of UseVarForGenericMethodInvocations to reflect action * Fix yml recipe java-lang-var.yml * update tests to reflect shortcomings of open rewrite regarding generic method invocations * Apply suggestions from code review Co-authored-by: Joan Viladrosa <joan@moderne.io> --------- Co-authored-by: Joan Viladrosa <joan@moderne.io>
1 parent f26901c commit 1a50a7b

File tree

8 files changed

+1045
-179
lines changed

8 files changed

+1045
-179
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright 2021 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.lang.var;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.stream.Collectors;
21+
22+
import org.openrewrite.*;
23+
import org.openrewrite.java.JavaIsoVisitor;
24+
import org.openrewrite.java.JavaParser;
25+
import org.openrewrite.java.JavaTemplate;
26+
import org.openrewrite.java.search.UsesJavaVersion;
27+
import org.openrewrite.java.tree.*;
28+
import org.openrewrite.marker.Markers;
29+
30+
public class UseVarForGenericMethodInvocations extends Recipe {
31+
@Override
32+
public String getDisplayName() {
33+
//language=markdown
34+
return "Apply `var` to Generic Method Invocations";
35+
}
36+
37+
@Override
38+
public String getDescription() {
39+
//language=markdown
40+
return "Apply `var` to variables initialized by invocations of Generic Methods. " +
41+
"This recipe ignores generic factory methods without parameters, because open rewrite cannot handle them correctly ATM.";
42+
}
43+
44+
@Override
45+
public TreeVisitor<?, ExecutionContext> getVisitor() {
46+
return Preconditions.check(
47+
new UsesJavaVersion<>(10),
48+
new UseVarForGenericMethodInvocations.UseVarForGenericsVisitor());
49+
}
50+
51+
static final class UseVarForGenericsVisitor extends JavaIsoVisitor<ExecutionContext> {
52+
private final JavaTemplate template = JavaTemplate.builder("var #{} = #{any()}")
53+
.javaParser(JavaParser.fromJavaVersion()).build();
54+
55+
@Override
56+
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations vd, ExecutionContext ctx) {
57+
vd = super.visitVariableDeclarations(vd, ctx);
58+
59+
boolean isGeneralApplicable = DeclarationCheck.isVarApplicable(this.getCursor(), vd);
60+
if (!isGeneralApplicable) return vd;
61+
62+
// recipe specific
63+
boolean isPrimitive = DeclarationCheck.isPrimitive(vd);
64+
boolean usesNoGenerics = !DeclarationCheck.useGenerics(vd);
65+
boolean usesTernary = DeclarationCheck.initializedByTernary(vd);
66+
if (isPrimitive || usesTernary || usesNoGenerics) return vd;
67+
68+
//now we deal with generics, check for method invocations
69+
Expression initializer = vd.getVariables().get(0).getInitializer();
70+
boolean isMethodInvocation = initializer != null && initializer.unwrap() instanceof J.MethodInvocation;
71+
if (!isMethodInvocation) return vd;
72+
73+
//if no type paramters are present and no arguments we assume the type is hard to determine a needs manual action
74+
boolean hasNoTypeParams = ((J.MethodInvocation) initializer).getTypeParameters() == null;
75+
boolean argumentsEmpty = ((J.MethodInvocation) initializer).getArguments().stream().allMatch(p -> p instanceof J.Empty);
76+
if (hasNoTypeParams && argumentsEmpty) return vd;
77+
78+
return transformToVar(vd, new ArrayList<>(), new ArrayList<>());
79+
}
80+
81+
private J.VariableDeclarations transformToVar(J.VariableDeclarations vd, List<JavaType> leftTypes, List<JavaType> rightTypes) {
82+
Expression initializer = vd.getVariables().get(0).getInitializer();
83+
String simpleName = vd.getVariables().get(0).getSimpleName();
84+
85+
// if left is defined but not right, copy types to initializer
86+
if(rightTypes.isEmpty() && !leftTypes.isEmpty()) {
87+
// we need to switch type infos from left to right here
88+
List<Expression> typeArgument = leftTypes.stream()
89+
.map(t ->
90+
new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, ((JavaType.Class)t).getClassName(), t, null))
91+
.collect(Collectors.toList());
92+
J.ParameterizedType typedInitializerClazz = ((J.ParameterizedType) ((J.NewClass) initializer).getClazz()).withTypeParameters(typeArgument);
93+
initializer = ((J.NewClass) initializer).withClazz(typedInitializerClazz);
94+
}
95+
96+
J.VariableDeclarations result = template.<J.VariableDeclarations>apply(getCursor(), vd.getCoordinates().replace(), simpleName, initializer)
97+
.withPrefix(vd.getPrefix());
98+
99+
// apply modifiers like final
100+
List<J.Modifier> modifiers = vd.getModifiers();
101+
boolean hasModifiers = !modifiers.isEmpty();
102+
if (hasModifiers) {
103+
result = result.withModifiers(modifiers);
104+
}
105+
106+
// apply prefix to type expression
107+
TypeTree resultingTypeExpression = result.getTypeExpression();
108+
boolean resultHasTypeExpression = resultingTypeExpression != null;
109+
if (resultHasTypeExpression) {
110+
result = result.withTypeExpression(resultingTypeExpression.withPrefix(vd.getTypeExpression().getPrefix()));
111+
}
112+
113+
return result;
114+
}
115+
}
116+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Copyright 2021 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.lang.var;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.Objects;
21+
import java.util.stream.Collectors;
22+
23+
import org.openrewrite.*;
24+
import org.openrewrite.internal.lang.Nullable;
25+
import org.openrewrite.java.JavaIsoVisitor;
26+
import org.openrewrite.java.JavaParser;
27+
import org.openrewrite.java.JavaTemplate;
28+
import org.openrewrite.java.search.UsesJavaVersion;
29+
import org.openrewrite.java.tree.*;
30+
import org.openrewrite.marker.Markers;
31+
32+
public class UseVarForGenericsConstructors extends Recipe {
33+
@Override
34+
public String getDisplayName() {
35+
//language=markdown
36+
return "Apply `var` to Generic Constructors";
37+
}
38+
39+
@Override
40+
public String getDescription() {
41+
//language=markdown
42+
return "Apply `var` to generics variables initialized by constructor calls.";
43+
}
44+
45+
@Override
46+
public TreeVisitor<?, ExecutionContext> getVisitor() {
47+
return Preconditions.check(
48+
new UsesJavaVersion<>(10),
49+
new UseVarForGenericsConstructors.UseVarForGenericsVisitor());
50+
}
51+
52+
static final class UseVarForGenericsVisitor extends JavaIsoVisitor<ExecutionContext> {
53+
private final JavaTemplate template = JavaTemplate.builder("var #{} = #{any()}")
54+
.javaParser(JavaParser.fromJavaVersion()).build();
55+
56+
@Override
57+
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations vd, ExecutionContext ctx) {
58+
vd = super.visitVariableDeclarations(vd, ctx);
59+
60+
boolean isGeneralApplicable = DeclarationCheck.isVarApplicable(this.getCursor(), vd);
61+
if (!isGeneralApplicable) return vd;
62+
63+
// recipe specific
64+
boolean isPrimitive = DeclarationCheck.isPrimitive(vd);
65+
boolean usesNoGenerics = !DeclarationCheck.useGenerics(vd);
66+
boolean usesTernary = DeclarationCheck.initializedByTernary(vd);
67+
if (isPrimitive || usesTernary || usesNoGenerics) return vd;
68+
69+
//now we deal with generics
70+
J.VariableDeclarations.NamedVariable variable = vd.getVariables().get(0);
71+
List<JavaType> leftTypes = extractParameters(variable.getVariableType());
72+
List<JavaType> rightTypes = extractParameters(variable.getInitializer());
73+
if (rightTypes == null || (leftTypes.isEmpty() && rightTypes.isEmpty())) return vd;
74+
75+
return transformToVar(vd, leftTypes, rightTypes);
76+
}
77+
78+
/**
79+
* Tries to extract the genric parameters from the expression,
80+
* if the Initializer is no new class or not of a parameterized type, returns null to signale "no info".
81+
* if the initializer uses empty diamonds use an empty list to signale no type information
82+
* @param initializer to extract parameters from
83+
* @return null or list of type parameters in diamond
84+
*/
85+
private @Nullable List<JavaType> extractParameters(@Nullable Expression initializer) {
86+
if (initializer instanceof J.NewClass) {
87+
TypeTree clazz = ((J.NewClass) initializer).getClazz();
88+
if (clazz instanceof J.ParameterizedType) {
89+
List<Expression> typeParameters = ((J.ParameterizedType) clazz).getTypeParameters();
90+
if (typeParameters != null) {
91+
return typeParameters
92+
.stream()
93+
.map(Expression::getType)
94+
.filter(Objects::nonNull)
95+
.collect(Collectors.toList());
96+
} else {
97+
return new ArrayList<>();
98+
}
99+
}
100+
}
101+
return null;
102+
}
103+
104+
private List<JavaType> extractParameters(@Nullable JavaType.Variable variableType) {
105+
if (variableType != null && variableType.getType() instanceof JavaType.Parameterized) {
106+
return ((JavaType.Parameterized) variableType.getType()).getTypeParameters();
107+
} else {
108+
return new ArrayList<>();
109+
}
110+
}
111+
112+
private J.VariableDeclarations transformToVar(J.VariableDeclarations vd, List<JavaType> leftTypes, List<JavaType> rightTypes) {
113+
Expression initializer = vd.getVariables().get(0).getInitializer();
114+
String simpleName = vd.getVariables().get(0).getSimpleName();
115+
116+
// if left is defined but not right, copy types to initializer
117+
if(rightTypes.isEmpty() && !leftTypes.isEmpty()) {
118+
// we need to switch type infos from left to right here
119+
List<Expression> typeArgument = leftTypes.stream()
120+
.map(t ->
121+
new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, ((JavaType.Class)t).getClassName(), t, null))
122+
.collect(Collectors.toList());
123+
J.ParameterizedType typedInitializerClazz = ((J.ParameterizedType) ((J.NewClass) initializer).getClazz()).withTypeParameters(typeArgument);
124+
initializer = ((J.NewClass) initializer).withClazz(typedInitializerClazz);
125+
}
126+
127+
J.VariableDeclarations result = template.<J.VariableDeclarations>apply(getCursor(), vd.getCoordinates().replace(), simpleName, initializer)
128+
.withPrefix(vd.getPrefix());
129+
130+
// apply modifiers like final
131+
List<J.Modifier> modifiers = vd.getModifiers();
132+
boolean hasModifiers = !modifiers.isEmpty();
133+
if (hasModifiers) {
134+
result = result.withModifiers(modifiers);
135+
}
136+
137+
// apply prefix to type expression
138+
TypeTree resultingTypeExpression = result.getTypeExpression();
139+
boolean resultHasTypeExpression = resultingTypeExpression != null;
140+
if (resultHasTypeExpression) {
141+
result = result.withTypeExpression(resultingTypeExpression.withPrefix(vd.getTypeExpression().getPrefix()));
142+
}
143+
144+
return result;
145+
}
146+
}
147+
}

src/main/resources/META-INF/rewrite/java-lang-var.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@
1717
type: specs.openrewrite.org/v1beta/recipe
1818
name: org.openrewrite.java.migrate.lang.UseVar
1919
displayName: Use local variable type inference
20-
description: Apply local variable type inference (`var`) for primitives and objects.
20+
description: Apply local variable type inference (`var`) for primitives and objects. These recipes can cause unused
21+
imports, be advised to run `org.openrewrite.java.RemoveUnusedImports afterwards.
2122
tags:
2223
- refactoring
2324
- java10
2425
- var
2526
recipeList:
2627
- org.openrewrite.java.migrate.lang.var.UseVarForObject
2728
- org.openrewrite.java.migrate.lang.var.UseVarForPrimitive
29+
- org.openrewrite.java.migrate.lang.var.UseVarForGenericsConstructors
30+
- org.openrewrite.java.migrate.lang.var.UseVarForGenericMethodInvocations

0 commit comments

Comments
 (0)