Skip to content

Use classpathFromResources in recipes generated for Refaster rules#139

Merged
timtebeek merged 12 commits intomainfrom
1136-use-classpathfromresources-instead-of-runtimeclasspath-in-remaster-templates
Aug 15, 2025
Merged

Use classpathFromResources in recipes generated for Refaster rules#139
timtebeek merged 12 commits intomainfrom
1136-use-classpathfromresources-instead-of-runtimeclasspath-in-remaster-templates

Conversation

@JohannisK
Copy link
Copy Markdown
Contributor

@JohannisK JohannisK commented May 2, 2025

What's changed?

Changed implementation in TemplateCode when generating refaster recipes to use classPathFromResource

What's your motivation?

Ensures Refaster template based recipes are able to match and use external types when running outside of build plugins.

@github-project-automation github-project-automation bot moved this to In Progress in OpenRewrite May 2, 2025
builder.append("\n .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath()))");
// builder.append("\n .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath()))");
builder.append("\n .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, ");
builder.append(jarNames.stream().map(jarName -> '"' + jarName + '"').collect(joining(", ")));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jarNames is constructed from ClasspathJarNameDetector. Do we need to check if the resulting list is correct and complete, including dependencies from type tables?

if ((matcher = JavaTemplate
.builder("String.format(\"\\\"%s\\\"\", com.sun.tools.javac.util.Convert.quote(#{value:any(java.lang.String)}))")
.javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath()))
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "tools"))
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we "know" that tools is in fact on the classpath or in a type table?
How do we handle situations where the names listed do not match the class path/typetables?

@timtebeek timtebeek changed the title Added implementation for classpathFromResource in refasten templates Added implementation for classpathFromResource in Refaster templates May 2, 2025
@JohannisK
Copy link
Copy Markdown
Contributor Author

There are several tests for TemplateProcessor that fail, because the ExecutionContext is not available there.

@timtebeek timtebeek marked this pull request as draft May 2, 2025 17:19
@timtebeek timtebeek added bug Something isn't working enhancement New feature or request labels Jun 14, 2025
JohannisK and others added 3 commits August 14, 2025 23:02
diff --git c/src/main/java/org/openrewrite/java/template/internal/TemplateCode.java i/src/main/java/org/openrewrite/java/template/internal/TemplateCode.java
index a9a22d2..58f1dea 100644
--- c/src/main/java/org/openrewrite/java/template/internal/TemplateCode.java
+++ i/src/main/java/org/openrewrite/java/template/internal/TemplateCode.java
@@ -68,12 +68,9 @@ public class TemplateCode {
                 jarNames.addAll(ClasspathJarNameDetector.classpathFor(parameter, ImportDetector.imports(parameter)));
             }
             if (!jarNames.isEmpty()) {
-                // It might be preferable to enumerate exactly the needed dependencies rather than the full classpath
-                // But this is expedient
-                // See #86
-                // String classpath = jarNames.stream().map(jarName -> '"' + jarName + '"').sorted().collect(joining(", "));
-                // builder.append("\n        .javaParser(JavaParser.fromJavaVersion().classpath(").append(classpath).append("))");
-                builder.append("\n        .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath()))");
+                builder.append("\n        .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, ");
+                builder.append(jarNames.stream().map(jarName -> '"' + jarName + '"').collect(joining(", ")));
+                builder.append("))\n        ");
             }
             return builder.toString();
         } catch (IOException e) {
diff --git c/src/main/java/org/openrewrite/java/template/internal/ClasspathJarNameDetector.java i/src/main/java/org/openrewrite/java/template/internal/ClasspathJarNameDetector.java
index ab59a8a..8a4b126 100644
--- c/src/main/java/org/openrewrite/java/template/internal/ClasspathJarNameDetector.java
+++ i/src/main/java/org/openrewrite/java/template/internal/ClasspathJarNameDetector.java
@@ -19,6 +19,7 @@ import com.sun.tools.javac.code.Symbol;
 import com.sun.tools.javac.tree.JCTree;
 import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
 import com.sun.tools.javac.tree.TreeScanner;
+import org.jspecify.annotations.Nullable;

 import javax.tools.JavaFileObject;
 import java.util.LinkedHashSet;
@@ -38,7 +39,7 @@ public class ClasspathJarNameDetector {
     public static Set<String> classpathFor(JCTree input, List<Symbol> imports) {
         Set<String> jarNames = new LinkedHashSet<String>() {
             @OverRide
-            public boolean add(String s) {
+            public boolean add(@nullable String s) {
                 return s != null && super.add(s);
             }
         };
@@ -64,7 +65,7 @@ public class ClasspathJarNameDetector {
     }

-    private static String jarNameFor(Symbol anImport) {
+    private static @nullable String jarNameFor(Symbol anImport) {
         Symbol.ClassSymbol enclClass = anImport instanceof Symbol.ClassSymbol ? (Symbol.ClassSymbol) anImport : anImport.enclClass();
         while (enclClass.enclClass() != null && enclClass.enclClass() != enclClass) {
             enclClass = enclClass.enclClass();
diff --git c/src/main/java/org/openrewrite/java/template/internal/TemplateCode.java i/src/main/java/org/openrewrite/java/template/internal/TemplateCode.java
index 58f1dea..337a65a 100644
--- c/src/main/java/org/openrewrite/java/template/internal/TemplateCode.java
+++ i/src/main/java/org/openrewrite/java/template/internal/TemplateCode.java
@@ -68,9 +68,11 @@ public class TemplateCode {
                 jarNames.addAll(ClasspathJarNameDetector.classpathFor(parameter, ImportDetector.imports(parameter)));
             }
             if (!jarNames.isEmpty()) {
-                builder.append("\n        .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, ");
-                builder.append(jarNames.stream().map(jarName -> '"' + jarName + '"').collect(joining(", ")));
-                builder.append("))\n        ");
+                String joinedJarNames = jarNames.stream().collect(joining(", ", "\"", "\""));
+                builder.append("\n    .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, ")
+                        .append(joinedJarNames)
+                        .append("))\n    ");
+
             }
             return builder.toString();
         } catch (IOException e) {
@timtebeek timtebeek force-pushed the 1136-use-classpathfromresources-instead-of-runtimeclasspath-in-remaster-templates branch from 643ef43 to fc56e32 Compare August 14, 2025 21:08
@timtebeek timtebeek self-assigned this Aug 14, 2025
@timtebeek timtebeek moved this from In Progress to Ready to Review in OpenRewrite Aug 14, 2025
@timtebeek timtebeek marked this pull request as ready for review August 14, 2025 21:21
@timtebeek timtebeek changed the title Added implementation for classpathFromResource in Refaster templates Use classpathFromResource in Refaster templates Aug 14, 2025
@timtebeek timtebeek changed the title Use classpathFromResource in Refaster templates Use classpathFromResources in recipes generated for Refaster rules Aug 14, 2025
@knutwannheden
Copy link
Copy Markdown
Contributor

If I understand this PR correctly, then it just assumes that whatever dependencies are referenced via types in the Refaster template are also present in the jars / type tables of the recipe artifact's resources. I think this seems reasonable.

We should however also make an effort to check that any of the types which are directly referenced by the referenced classes are also loaded. So if we for example reference a class A extends B {} then we need to make sure that we both load the jars for A and B. Otherwise the template compilation will almost certainly fail.

if (numberOfArguments < 3 || 4 < numberOfArguments) {
return;
}
boolean classpathFromResources = numberOfArguments == 4;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the usage site of Semantics you can pass in an initial ExecutionContext where possible to determine if classpathFromResources should be used. This is preferable over a annotation processor option here, as not all usage sites have access to an ExecutionContext, as seen here:
https://github.com/openrewrite/rewrite-apache/blob/df33d276534aaaea4c2f2f93ad681954084e09b2/src/main/java/org/openrewrite/apache/commons/lang/IsNotEmptyToJdk.java#L74-L77

It's also not necessary to pass in an ExecutionContext for every type of use, as seen here for the standard JDK class:
https://github.com/openrewrite/rewrite-migrate-java/blob/6b98403236ad24c7d949750cd1c7af5fb94eebd8/src/main/java/org/openrewrite/java/migrate/UseJavaUtilBase64.java#L126-L127

Comment on lines +28 to +29
JavaTemplate.Builder logger = Semantics.expression(ctx, this, "logger", (String s) -> LoggerFactory.getLogger(s));
JavaTemplate.Builder info = Semantics.statement(ctx, this, "info", (Logger l, String s) -> l.info(s));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shows intended use when you do want to pass in an ExecutionContext.

@timtebeek timtebeek merged commit 3c21be2 into main Aug 15, 2025
2 checks passed
@timtebeek timtebeek deleted the 1136-use-classpathfromresources-instead-of-runtimeclasspath-in-remaster-templates branch August 15, 2025 13:47
@github-project-automation github-project-automation bot moved this from Ready to Review to Done in OpenRewrite Aug 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

3 participants