Skip to content

Commit 447e28a

Browse files
committed
Add expression type
1 parent f6d5893 commit 447e28a

File tree

5 files changed

+178
-11
lines changed

5 files changed

+178
-11
lines changed

rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateGenericsTest.java

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,88 @@ void test() {
163163
);
164164
}
165165

166+
@Test
167+
void setsDifferenceMultimapRecipe() {
168+
rewriteRun(
169+
spec -> spec.parser(JavaParser.fromJavaVersion().classpath("guava"))
170+
.recipe(toRecipe(() -> new JavaIsoVisitor<>() {
171+
final JavaTemplate template = JavaTemplate.builder("#{set:any(java.util.Set<T>)}.stream().filter(java.util.function.Predicate.not(#{multimap:any(com.google.common.collect.Multimap<K, V>)}::containsKey)).collect(com.google.common.collect.ImmutableSet.toImmutableSet())")
172+
.expressionType("com.google.common.collect.ImmutableSet<T>")
173+
.genericTypes("T", "K", "V")
174+
.javaParser(JavaParser.fromJavaVersion().classpath("guava"))
175+
.build();
176+
177+
@Override
178+
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) {
179+
return template.matches(getCursor()) ? SearchResult.found(method) : super.visitMethodInvocation(method, executionContext);
180+
}
181+
})),
182+
java(
183+
"""
184+
import com.google.common.collect.ImmutableSet;
185+
import com.google.common.collect.ImmutableSetMultimap;
186+
187+
import java.util.function.Predicate;
188+
189+
class Test {
190+
void test() {
191+
ImmutableSet.of(1).stream().filter(Predicate.not(ImmutableSetMultimap.of(2, 3)::containsKey)).collect(ImmutableSet.toImmutableSet());
192+
}
193+
}
194+
""",
195+
"""
196+
import com.google.common.collect.ImmutableSet;
197+
import com.google.common.collect.ImmutableSetMultimap;
198+
199+
import java.util.function.Predicate;
200+
201+
class Test {
202+
void test() {
203+
/*~~>*/ImmutableSet.of(1).stream().filter(Predicate.not(ImmutableSetMultimap.of(2, 3)::containsKey)).collect(ImmutableSet.toImmutableSet());
204+
}
205+
}
206+
"""
207+
)
208+
);
209+
}
210+
211+
@Test
212+
void emptyStreamRecipe() {
213+
rewriteRun(
214+
spec -> spec.recipe(toRecipe(() -> new JavaIsoVisitor<>() {
215+
final JavaTemplate template = JavaTemplate.builder("java.util.stream.Stream.of()")
216+
.expressionType("java.util.stream.Stream<T>")
217+
.genericTypes("T")
218+
.build();
219+
220+
@Override
221+
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) {
222+
return template.matches(getCursor()) ? SearchResult.found(method) : super.visitMethodInvocation(method, executionContext);
223+
}
224+
})),
225+
java(
226+
"""
227+
import java.util.stream.Stream;
228+
229+
class Test {
230+
Stream<String> test() {
231+
return Stream.of();
232+
}
233+
}
234+
""",
235+
"""
236+
import java.util.stream.Stream;
237+
238+
class Test {
239+
Stream<String> test() {
240+
return /*~~>*/Stream.of();
241+
}
242+
}
243+
"""
244+
)
245+
);
246+
}
247+
166248
@Test
167249
void methodModifiersMismatch() {
168250
rewriteRun(

rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateMatchTest.java

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,4 +1046,62 @@ Predicate<Object> test() {
10461046
)
10471047
);
10481048
}
1049+
1050+
@Test
1051+
void matchMemberReferenceAndLambda() {
1052+
//noinspection Convert2MethodRef
1053+
rewriteRun(
1054+
spec -> spec
1055+
.expectedCyclesThatMakeChanges(1).cycles(1)
1056+
.recipe(toRecipe(() -> new JavaIsoVisitor<>() {
1057+
final JavaTemplate refTemplate = JavaTemplate.builder("String::valueOf")
1058+
.expressionType("java.util.function.Function<Object, String>")
1059+
.build();
1060+
final JavaTemplate lambdaTemplate = JavaTemplate.builder("(e)->e.toString()")
1061+
.expressionType("java.util.function.Function<Object, String>")
1062+
.build();
1063+
1064+
@Override
1065+
public J.MemberReference visitMemberReference(J.MemberReference memberRef, ExecutionContext executionContext) {
1066+
return refTemplate.matches(getCursor()) ? SearchResult.found(memberRef, "ref") : super.visitMemberReference(memberRef, executionContext);
1067+
}
1068+
1069+
@Override
1070+
public J.Lambda visitLambda(J.Lambda lambda, ExecutionContext executionContext) {
1071+
return lambdaTemplate.matches(getCursor()) ? SearchResult.found(lambda, "lambda") : super.visitLambda(lambda, executionContext);
1072+
}
1073+
})),
1074+
//language=java
1075+
java(
1076+
"""
1077+
import java.util.function.Function;
1078+
1079+
class Foo {
1080+
void test() {
1081+
test(String::valueOf);
1082+
test(e -> e.toString());
1083+
test(x -> x.toString());
1084+
}
1085+
1086+
void test(Function<Object, String> fn) {
1087+
}
1088+
}
1089+
""",
1090+
"""
1091+
import java.util.function.Function;
1092+
1093+
class Foo {
1094+
void test() {
1095+
test(/*~~(ref)~~>*/String::valueOf);
1096+
test(/*~~(lambda)~~>*/e -> e.toString());
1097+
test(/*~~(lambda)~~>*/x -> x.toString());
1098+
}
1099+
1100+
void test(Function<Object, String> fn) {
1101+
}
1102+
}
1103+
"""
1104+
)
1105+
);
1106+
}
10491107
}

rewrite-java/src/main/java/org/openrewrite/java/JavaTemplate.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ protected static Path getTemplateClasspathDir() {
8787
private final Consumer<String> onAfterVariableSubstitution;
8888
private final JavaTemplateParser templateParser;
8989

90-
private JavaTemplate(boolean contextSensitive, JavaParser.Builder<?, ?> parser, String code, Set<String> imports,
90+
private JavaTemplate(boolean contextSensitive, JavaParser.Builder<?, ?> parser, String code, String expressionType, Set<String> imports,
9191
Set<String> genericTypes, Consumer<String> onAfterVariableSubstitution, Consumer<String> onBeforeParseTemplate) {
92-
this(code, genericTypes, onAfterVariableSubstitution, new JavaTemplateParser(contextSensitive, augmentClasspath(parser), onAfterVariableSubstitution, onBeforeParseTemplate, imports));
92+
this(code, genericTypes, onAfterVariableSubstitution, new JavaTemplateParser(contextSensitive, augmentClasspath(parser), onAfterVariableSubstitution, onBeforeParseTemplate, imports, expressionType));
9393
}
9494

9595
private static JavaParser.Builder<?, ?> augmentClasspath(JavaParser.Builder<?, ?> parserBuilder) {
@@ -181,6 +181,7 @@ public static class Builder {
181181
private final Set<String> genericTypes = new HashSet<>();
182182

183183
private boolean contextSensitive;
184+
private String expressionType = "Object";
184185

185186
private JavaParser.Builder<?, ?> parser = org.openrewrite.java.JavaParser.fromJavaVersion();
186187

@@ -212,6 +213,25 @@ public Builder contextSensitive() {
212213
return this;
213214
}
214215

216+
/**
217+
* In context-free templates involving generic types, the type often cannot be inferred automatically.
218+
* <p>
219+
* Common examples include:
220+
* <ul>
221+
* <li>{@code new ArrayList<>()}</li>
222+
* <li>{@code Collections.emptyList()}</li>
223+
* <li>{@code String::valueOf}</li>
224+
* </ul>
225+
* In such cases, the type must be specified manually.
226+
*/
227+
public Builder expressionType(String expressionType) {
228+
if (StringUtils.isBlank(expressionType)) {
229+
throw new IllegalArgumentException("Type must not be blank");
230+
}
231+
this.expressionType = expressionType;
232+
return this;
233+
}
234+
215235
public Builder imports(String... fullyQualifiedTypeNames) {
216236
for (String typeName : fullyQualifiedTypeNames) {
217237
validateImport(typeName);
@@ -259,7 +279,7 @@ public Builder doBeforeParseTemplate(Consumer<String> beforeParseTemplate) {
259279
}
260280

261281
public JavaTemplate build() {
262-
return new JavaTemplate(contextSensitive, parser.clone(), code, imports, genericTypes,
282+
return new JavaTemplate(contextSensitive, parser.clone(), code, expressionType, imports, genericTypes,
263283
onAfterVariableSubstitution, onBeforeParseTemplate);
264284
}
265285
}

rewrite-java/src/main/java/org/openrewrite/java/internal/template/BlockStatementTemplateGenerator.java

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ public class BlockStatementTemplateGenerator {
5151

5252
protected final Set<String> imports;
5353
private final boolean contextSensitive;
54+
private final String expressionType;
55+
56+
public BlockStatementTemplateGenerator(Set<String> imports, boolean contextSensitive) {
57+
this(imports, contextSensitive, "Object");
58+
}
5459

5560
public String template(Cursor cursor, String template, Collection<JavaType.GenericTypeVariable> typeVariables, Space.Location location, JavaCoordinates.Mode mode) {
5661
//noinspection ConstantConditions
@@ -205,24 +210,26 @@ private boolean isTemplateStopComment(Comment comment) {
205210
protected void contextFreeTemplate(Cursor cursor, J j, Collection<JavaType.GenericTypeVariable> typeVariables, StringBuilder before, StringBuilder after) {
206211
String classDeclaration = typeVariables.isEmpty() ? "Template" :
207212
"Template<" + typeVariables.stream().map(TypeUtils::toGenericTypeString).collect(Collectors.joining(", ")) + ">";
208-
if (j instanceof J.Lambda) {
213+
if (j instanceof J.Lambda && "Object".equals(expressionType)) {
209214
throw new IllegalArgumentException(
210215
"Templating a lambda requires a cursor so that it can be properly parsed and type-attributed. " +
211-
"Mark this template as context-sensitive by calling JavaTemplate.Builder#contextSensitive().");
212-
} else if (j instanceof J.MemberReference) {
216+
"Mark this template as context-sensitive by calling JavaTemplate.Builder#contextSensitive() or " +
217+
"specify the type by calling JavaTemplate.Builder#expressionType()");
218+
} else if (j instanceof J.MemberReference && "Object".equals(expressionType)) {
213219
throw new IllegalArgumentException(
214220
"Templating a method reference requires a cursor so that it can be properly parsed and type-attributed. " +
215-
"Mark this template as context-sensitive by calling JavaTemplate.Builder#contextSensitive().");
221+
"Mark this template as context-sensitive by calling JavaTemplate.Builder#contextSensitive() or " +
222+
"specify the type by calling JavaTemplate.Builder#expressionType()");
216223
} else if (j instanceof J.MethodInvocation) {
217224
before.insert(0, String.format("class %s {{\n", classDeclaration));
218225
JavaType.Method methodType = ((J.MethodInvocation) j).getMethodType();
219226
if (methodType == null || methodType.getReturnType() != JavaType.Primitive.Void) {
220-
before.append("Object o = ");
227+
before.append(expressionType).append(" o = ");
221228
}
222229
after.append(";\n}}");
223230
} else if (j instanceof Expression && !(j instanceof J.Assignment)) {
224231
before.insert(0, String.format("class %s {\n", classDeclaration));
225-
before.append("Object o = ");
232+
before.append(expressionType).append(" o = ");
226233
after.append(";\n}");
227234
} else if ((j instanceof J.MethodDeclaration || j instanceof J.VariableDeclarations || j instanceof J.Block || j instanceof J.ClassDeclaration) &&
228235
cursor.getValue() instanceof J.Block &&

rewrite-java/src/main/java/org/openrewrite/java/internal/template/JavaTemplateParser.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,14 @@ public class JavaTemplateParser {
6363
private final AnnotationTemplateGenerator annotationTemplateGenerator;
6464

6565
public JavaTemplateParser(boolean contextSensitive, Parser.Builder parser, Consumer<String> onAfterVariableSubstitution,
66-
Consumer<String> onBeforeParseTemplate, Set<String> imports) {
66+
Consumer<String> onBeforeParseTemplate, Set<String> imports, String expressionType) {
6767
this(
6868
parser,
6969
onAfterVariableSubstitution,
7070
onBeforeParseTemplate,
7171
imports,
7272
contextSensitive,
73-
new BlockStatementTemplateGenerator(imports, contextSensitive),
73+
new BlockStatementTemplateGenerator(imports, contextSensitive, expressionType),
7474
new AnnotationTemplateGenerator(imports)
7575
);
7676
}

0 commit comments

Comments
 (0)