diff --git a/src/main/java/org/openrewrite/java/template/internal/TemplateCode.java b/src/main/java/org/openrewrite/java/template/internal/TemplateCode.java
new file mode 100644
index 00000000..589b3181
--- /dev/null
+++ b/src/main/java/org/openrewrite/java/template/internal/TemplateCode.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.openrewrite.java.template.internal;
+
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.tree.JCTree.JCIdent;
+import com.sun.tools.javac.tree.Pretty;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.UncheckedIOException;
+import java.io.Writer;
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class TemplateCode {
+
+ public static String process(T tree, List parameters, boolean fullyQualified) {
+ StringWriter writer = new StringWriter();
+ TemplateCodePrinter printer = new TemplateCodePrinter(writer, parameters, fullyQualified);
+ try {
+ printer.printExpr(tree);
+ StringBuilder builder = new StringBuilder("JavaTemplate\n");
+ builder
+ .append(" .builder(\"")
+ .append(writer.toString().replace("\\", "\\\\").replace("\"", "\\\""))
+ .append("\")");
+ if (!printer.imports.isEmpty()) {
+ builder.append("\n .imports(").append(printer.imports.stream().map(i -> '"' + i + '"').collect(Collectors.joining(", "))).append(")");
+ }
+ if (!printer.staticImports.isEmpty()) {
+ builder.append("\n .staticImports(").append(printer.staticImports.stream().map(i -> '"' + i + '"').collect(Collectors.joining(", "))).append(")");
+ }
+ List imports = ImportDetector.imports(tree);
+ String classpath = ClasspathJarNameDetector.classpathFor(tree, imports);
+ if (!classpath.isEmpty()) {
+ builder.append("\n .javaParser(JavaParser.fromJavaVersion().classpath(").append(classpath).append("))");
+ }
+ return builder.toString();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static class TemplateCodePrinter extends Pretty {
+
+ private static final String PRIMITIVE_ANNOTATION = "org.openrewrite.java.template.Primitive";
+ private final List declaredParameters;
+ private final boolean fullyQualified;
+ private final Set seenParameters = new HashSet<>();
+ private final TreeSet imports = new TreeSet<>();
+ private final TreeSet staticImports = new TreeSet<>();
+
+ public TemplateCodePrinter(Writer writer, List declaredParameters, boolean fullyQualified) {
+ super(writer, true);
+ this.declaredParameters = declaredParameters;
+ this.fullyQualified = fullyQualified;
+ }
+
+ @Override
+ public void visitIdent(JCIdent jcIdent) {
+ try {
+ Symbol sym = jcIdent.sym;
+ Optional param = declaredParameters.stream().filter(p -> p.sym == sym).findFirst();
+ if (param.isPresent()) {
+ print("#{" + sym.name);
+ if (seenParameters.add(param.get())) {
+ String type = param.get().type.toString();
+ if (param.get().getModifiers().getAnnotations().stream().anyMatch(a -> a.attribute.type.tsym.getQualifiedName().toString().equals(PRIMITIVE_ANNOTATION))) {
+ type = getUnboxedPrimitive(type);
+ }
+ print(":any(" + type + ")");
+ }
+ print("}");
+ } else if (sym != null) {
+ print(sym);
+ } else {
+ print(jcIdent.name);
+ }
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ void print(Symbol sym) throws IOException {
+ if (sym instanceof Symbol.ClassSymbol) {
+ if (fullyQualified) {
+ print(sym.packge().fullname.contentEquals("java.lang") ? sym.name : sym.getQualifiedName());
+ } else {
+ print(sym.name);
+ if (!sym.packge().fullname.contentEquals("java.lang")) {
+ imports.add(sym.getQualifiedName().toString());
+ }
+ }
+ } else if (sym instanceof Symbol.MethodSymbol || sym instanceof Symbol.VarSymbol) {
+ if (fullyQualified) {
+ print(sym.owner);
+ print('.');
+ print(sym.name);
+ } else {
+ print(sym.name);
+ if (!sym.packge().fullname.contentEquals("java.lang")) {
+ staticImports.add(sym.owner.getQualifiedName() + "." + sym.name);
+ }
+ }
+ } else if (sym instanceof Symbol.PackageSymbol) {
+ print(sym.getQualifiedName());
+ }
+ }
+
+ private String getUnboxedPrimitive(String paramType) {
+ switch (paramType) {
+ case "java.lang.Boolean":
+ return "boolean";
+ case "java.lang.Byte":
+ return "byte";
+ case "java.lang.Character":
+ return "char";
+ case "java.lang.Double":
+ return "double";
+ case "java.lang.Float":
+ return "float";
+ case "java.lang.Integer":
+ return "int";
+ case "java.lang.Long":
+ return "long";
+ case "java.lang.Short":
+ return "short";
+ case "java.lang.Void":
+ return "void";
+ }
+ return paramType;
+ }
+ }
+}
diff --git a/src/main/java/org/openrewrite/java/template/processor/TemplateProcessor.java b/src/main/java/org/openrewrite/java/template/processor/TemplateProcessor.java
index ce51a9c9..dba28953 100644
--- a/src/main/java/org/openrewrite/java/template/processor/TemplateProcessor.java
+++ b/src/main/java/org/openrewrite/java/template/processor/TemplateProcessor.java
@@ -24,9 +24,8 @@
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.TreeScanner;
import com.sun.tools.javac.util.Context;
-import org.openrewrite.java.template.internal.ClasspathJarNameDetector;
-import org.openrewrite.java.template.internal.ImportDetector;
import org.openrewrite.java.template.internal.JavacResolution;
+import org.openrewrite.java.template.internal.TemplateCode;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
@@ -34,11 +33,14 @@
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaFileObject;
-import java.io.*;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.Writer;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
-import static java.util.Collections.*;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
/**
* For steps to debug this annotation processor, see
@@ -46,17 +48,6 @@
*/
@SupportedAnnotationTypes("*")
public class TemplateProcessor extends TypeAwareProcessor {
- private static final String PRIMITIVE_ANNOTATION = "org.openrewrite.java.template.Primitive";
-
- private final String javaFileContent;
-
- public TemplateProcessor(String javaFileContent) {
- this.javaFileContent = javaFileContent;
- }
-
- public TemplateProcessor() {
- this(null);
- }
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
@@ -99,71 +90,18 @@ public void visitApply(JCTree.JCMethodInvocation tree) {
JCTree.JCLambda template = arg2 instanceof JCTree.JCLambda ? (JCTree.JCLambda) arg2 : (JCTree.JCLambda) ((JCTree.JCTypeCast) arg2).getExpression();
- NavigableMap parameterPositions;
List parameters;
if (template.getParameters().isEmpty()) {
- parameterPositions = emptyNavigableMap();
parameters = emptyList();
} else {
- parameterPositions = new TreeMap<>();
Map parameterResolution = res.resolveAll(context, cu, template.getParameters());
parameters = new ArrayList<>(template.getParameters().size());
for (VariableTree p : template.getParameters()) {
parameters.add((JCTree.JCVariableDecl) parameterResolution.get((JCTree) p));
}
- JCTree.JCLambda resolvedTemplate = (JCTree.JCLambda) parameterResolution.get(template);
-
- new TreeScanner() {
- @Override
- public void visitIdent(JCTree.JCIdent ident) {
- for (JCTree.JCVariableDecl parameter : parameters) {
- if (parameter.sym == ident.sym) {
- parameterPositions.put(ident.getStartPosition(), parameter);
- }
- }
- }
- }.scan(resolvedTemplate.getBody());
}
- try (InputStream inputStream = javaFileContent == null ?
- cu.getSourceFile().openInputStream() : new ByteArrayInputStream(javaFileContent.getBytes())) {
- //noinspection ResultOfMethodCallIgnored
- inputStream.skip(template.getBody().getStartPosition());
-
- byte[] templateSourceBytes = new byte[template.getBody().getEndPosition(cu.endPositions) - template.getBody().getStartPosition()];
-
- //noinspection ResultOfMethodCallIgnored
- inputStream.read(templateSourceBytes);
-
- String templateSource = new String(templateSourceBytes);
- templateSource = templateSource.replace("\\", "\\\\").replace("\"", "\\\"");
-
- for (Map.Entry paramPos : parameterPositions.descendingMap().entrySet()) {
- JCTree.JCVariableDecl param = paramPos.getValue();
-
- String typeDef = "";
-
- // identify whether this is the leftmost occurrence of this parameter name
- if (Objects.equals(parameterPositions.entrySet().stream().filter(p -> p.getValue() == param)
- .map(Map.Entry::getKey)
- .findFirst().orElse(null), paramPos.getKey())) {
- String type = param.type.toString();
- for (JCTree.JCAnnotation annotation : param.getModifiers().getAnnotations()) {
- if (annotation.type.tsym.getQualifiedName().contentEquals(PRIMITIVE_ANNOTATION)) {
- type = getUnboxedPrimitive(param.type.toString());
- // don't generate the annotation into the source code
- param.mods.annotations = com.sun.tools.javac.util.List.filter(param.mods.annotations, annotation);
- }
- }
- typeDef = ":any(" + type + ")";
- }
-
- templateSource = templateSource.substring(0, paramPos.getKey() - template.getBody().getStartPosition()) +
- "#{" + param.getName().toString() + typeDef + "}" +
- templateSource.substring((paramPos.getKey() - template.getBody().getStartPosition()) +
- param.name.length());
- }
-
+ try {
JCTree.JCLiteral templateName = (JCTree.JCLiteral) tree.getArguments().get(1);
if (templateName.value == null) {
processingEnv.getMessager().printMessage(Kind.WARNING, "Can't compile a template with a null name.");
@@ -200,6 +138,8 @@ public void visitIdent(JCTree.JCIdent ident) {
}
}
+ String templateCode = TemplateCode.process(resolved.get(template.getBody()), parameters, false);
+
JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(templateFqn);
try (Writer out = new BufferedWriter(builderFile.openWriter())) {
out.write("package " + classDecl.sym.packge().toString() + ";\n");
@@ -228,25 +168,7 @@ public void visitIdent(JCTree.JCIdent ident) {
out.write(" * @return the JavaTemplate builder.\n");
out.write(" */\n");
out.write(" public static JavaTemplate.Builder getTemplate() {\n");
- out.write(" return JavaTemplate\n");
- out.write(" .builder(\"" + templateSource + "\")");
-
- List imports = ImportDetector.imports(resolved.get(template));
- String classpath = ClasspathJarNameDetector.classpathFor(resolved.get(template), imports);
- if (!classpath.isEmpty()) {
- out.write("\n .javaParser(JavaParser.fromJavaVersion().classpath(" +
- classpath + "))");
- }
-
- for (Symbol anImport : imports) {
- if (anImport instanceof Symbol.ClassSymbol && !anImport.getQualifiedName().toString().startsWith("java.lang.")) {
- out.write("\n .imports(\"" + ((Symbol.ClassSymbol) anImport).fullname.toString().replace('$', '.') + "\")");
- } else if (anImport instanceof Symbol.VarSymbol || anImport instanceof Symbol.MethodSymbol) {
- out.write("\n .staticImports(\"" + anImport.owner.getQualifiedName().toString().replace('$', '.') + '.' + anImport.flatName().toString() + "\")");
- }
- }
-
- out.write(";\n");
+ out.write(" return " + indent(templateCode, 12) + ";\n");
out.write(" }\n");
out.write("}\n");
out.flush();
@@ -259,6 +181,13 @@ public void visitIdent(JCTree.JCIdent ident) {
super.visitApply(tree);
}
+
+ private String indent(String code, int width) {
+ char[] indent = new char[width];
+ Arrays.fill(indent, ' ');
+ String replacement = "$1" + new String(indent);
+ return code.replaceAll("(?m)(\\R)", replacement);
+ }
}.scan(cu);
}
diff --git a/src/main/java/org/openrewrite/java/template/processor/TypeAwareProcessor.java b/src/main/java/org/openrewrite/java/template/processor/TypeAwareProcessor.java
index e35b6f95..2761b4dc 100644
--- a/src/main/java/org/openrewrite/java/template/processor/TypeAwareProcessor.java
+++ b/src/main/java/org/openrewrite/java/template/processor/TypeAwareProcessor.java
@@ -224,30 +224,6 @@ protected Object tryGetProxyDelegateToField(Object instance) {
}
- protected String getUnboxedPrimitive(String paramType) {
- switch (paramType) {
- case "java.lang.Boolean":
- return "boolean";
- case "java.lang.Byte":
- return "byte";
- case "java.lang.Character":
- return "char";
- case "java.lang.Double":
- return "double";
- case "java.lang.Float":
- return "float";
- case "java.lang.Integer":
- return "int";
- case "java.lang.Long":
- return "long";
- case "java.lang.Short":
- return "short";
- case "java.lang.Void":
- return "void";
- }
- return paramType;
- }
-
protected String getBoxedPrimitive(String paramType) {
switch (paramType) {
case "boolean":
diff --git a/src/test/java/org/openrewrite/java/template/TemplateProcessorTest.java b/src/test/java/org/openrewrite/java/template/TemplateProcessorTest.java
index 5f7ae112..23c13edf 100644
--- a/src/test/java/org/openrewrite/java/template/TemplateProcessorTest.java
+++ b/src/test/java/org/openrewrite/java/template/TemplateProcessorTest.java
@@ -30,6 +30,7 @@ class TemplateProcessorTest {
@ValueSource(strings = {
"Unqualified",
"FullyQualified",
+ "FullyQualifiedField",
"Primitive",
})
void qualification(String qualifier) {
diff --git a/src/test/resources/template/ShouldAddClasspath.java b/src/test/resources/template/ShouldAddClasspath.java
index 8a69474e..bb06e944 100644
--- a/src/test/resources/template/ShouldAddClasspath.java
+++ b/src/test/resources/template/ShouldAddClasspath.java
@@ -20,6 +20,11 @@
import org.openrewrite.java.template.Primitive;
import org.slf4j.LoggerFactory;
+import java.util.regex.Pattern;
+
+import static java.util.regex.Pattern.DOTALL;
+import static org.slf4j.LoggerFactory.getLogger;
+
public class ShouldAddClasspath {
class Unqualified {
@@ -30,7 +35,7 @@ void before(String message) {
@AfterTemplate
void after(String message) {
- LoggerFactory.getLogger(message);
+ getLogger(message);
}
}
@@ -46,6 +51,18 @@ void after(String message) {
}
}
+ class FullyQualifiedField {
+ @BeforeTemplate
+ void before(String message) {
+ Pattern.compile(message, DOTALL);
+ }
+
+ @AfterTemplate
+ void after(String message) {
+ System.out.println(message);
+ }
+ }
+
class Primitive {
@BeforeTemplate
void before(@org.openrewrite.java.template.Primitive int i) {
diff --git a/src/test/resources/template/ShouldAddClasspathRecipe$FullyQualifiedFieldRecipe$1_after.java b/src/test/resources/template/ShouldAddClasspathRecipe$FullyQualifiedFieldRecipe$1_after.java
new file mode 100644
index 00000000..2cc3d8a1
--- /dev/null
+++ b/src/test/resources/template/ShouldAddClasspathRecipe$FullyQualifiedFieldRecipe$1_after.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package foo;
+import org.openrewrite.java.*;
+
+@SuppressWarnings("all")
+public class ShouldAddClasspathRecipes$FullyQualifiedFieldRecipe$1_after {
+ public ShouldAddClasspathRecipes$FullyQualifiedFieldRecipe$1_after() {}
+
+ public static JavaTemplate.Builder getTemplate() {
+ return JavaTemplate
+ .builder("System.out.println(#{message:any(java.lang.String)})");
+ }
+}
diff --git a/src/test/resources/template/ShouldAddClasspathRecipe$FullyQualifiedFieldRecipe$1_before.java b/src/test/resources/template/ShouldAddClasspathRecipe$FullyQualifiedFieldRecipe$1_before.java
new file mode 100644
index 00000000..105b4d03
--- /dev/null
+++ b/src/test/resources/template/ShouldAddClasspathRecipe$FullyQualifiedFieldRecipe$1_before.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package foo;
+import org.openrewrite.java.*;
+
+@SuppressWarnings("all")
+public class ShouldAddClasspathRecipes$FullyQualifiedFieldRecipe$1_before {
+ public ShouldAddClasspathRecipes$FullyQualifiedFieldRecipe$1_before() {}
+
+ public static JavaTemplate.Builder getTemplate() {
+ return JavaTemplate
+ .builder("java.util.regex.Pattern.compile(#{message:any(java.lang.String)}, DOTALL)")
+ .staticImports("java.util.regex.Pattern.DOTALL");
+ }
+}
diff --git a/src/test/resources/template/ShouldAddClasspathRecipe$UnqualifiedRecipe$1_after.java b/src/test/resources/template/ShouldAddClasspathRecipe$UnqualifiedRecipe$1_after.java
index 335c1b94..c200125e 100644
--- a/src/test/resources/template/ShouldAddClasspathRecipe$UnqualifiedRecipe$1_after.java
+++ b/src/test/resources/template/ShouldAddClasspathRecipe$UnqualifiedRecipe$1_after.java
@@ -22,7 +22,8 @@ public class ShouldAddClasspathRecipes$UnqualifiedRecipe$1_after {
public static JavaTemplate.Builder getTemplate() {
return JavaTemplate
- .builder("org.slf4j.LoggerFactory.getLogger(#{message:any(java.lang.String)})")
+ .builder("getLogger(#{message:any(java.lang.String)})")
+ .staticImports("org.slf4j.LoggerFactory.getLogger")
.javaParser(JavaParser.fromJavaVersion().classpath("slf4j-api"));
}
}