Skip to content

Add additional jar names to classpathFromResources for types referenced from types used#160

Merged
timtebeek merged 56 commits intomainfrom
transitive-jarnames
Aug 19, 2025
Merged

Add additional jar names to classpathFromResources for types referenced from types used#160
timtebeek merged 56 commits intomainfrom
transitive-jarnames

Conversation

@timtebeek
Copy link
Copy Markdown
Member

@timtebeek timtebeek commented Aug 15, 2025

Right now we add the transitive jar names only when referred to from types we're using.

I've wondered whether we should unconditionally add say "opentest4j" whenever any method from JUnit is used, but figured that might quickly result in a lot of classpathFromResources entries that would then need to be added, so I've opted to for now only add those when referred by the types we use in the templates, as can be seen for the difference between the tests we now have for ClasspathJarNameDetector.

JohannisK and others added 12 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 self-assigned this Aug 15, 2025
@github-project-automation github-project-automation bot moved this to In Progress in OpenRewrite Aug 15, 2025
Base automatically changed from 1136-use-classpathfromresources-instead-of-runtimeclasspath-in-remaster-templates to main August 15, 2025 13:47
@timtebeek timtebeek added bug Something isn't working enhancement New feature or request labels Aug 15, 2025
github-actions[bot]

This comment was marked as outdated.

github-actions[bot]

This comment was marked as outdated.

github-actions[bot]

This comment was marked as outdated.

@knutwannheden
Copy link
Copy Markdown
Contributor

I think what is required is to include the jars of all types directly referenced as either supertypes or in method signatures of the types referenced by the template. I don't think the full transitive closure is required.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Some suggestions could not be made:

  • src/main/java/org/openrewrite/java/template/internal/ClasspathJarNameDetector.java
    • lines 26-26

Comment on lines +56 to +57
// Retain major version number, to avoid `log4j` conflict between `log4j-1` and `log4j2-1`
.replaceAll("(-\\d+).*?$", "$1");
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

it's subtle, but since we match on prefixes on the other side we need a way to tell these apart. I've deliberately not pinned to more specific versions, as we use latest.release often, and I don't see overlap yet beyond major versions even for rewrite-spring.

Alternatively we could on the other end match based on "jarname" + '-' to tell these apart without pinning to major versions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this is good enough! There's always the option to handle this differently in another PR 😉.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Indeed looks good as a first step. Possibly we can restrict it a bit more (untested), but I am also fine with leaving it as-is:

Suggested change
// Retain major version number, to avoid `log4j` conflict between `log4j-1` and `log4j2-1`
.replaceAll("(-\\d+).*?$", "$1");
// Retain major version number, to avoid `log4j` conflict between `log4j-1` and `log4j2-1`
.replaceFirst("(-\\d+)", "$1");

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks! With that original suggestion the tests fail with the following:

value of      : classpathForSource(...)
missing (2)   : junit-jupiter-api-5, opentest4j-1
unexpected (2): junit-jupiter-api-5.13.3, opentest4j-1.3.0

Note that my intention was to only retain the major version, if present, and discard the rest, hence why I've included the non greedy match with end of string: .*?$. I did apply your suggestion to use replaceFirst, even if the inclusion of $ would result in the same.

Comment on lines +56 to +57
// Retain major version number, to avoid `log4j` conflict between `log4j-1` and `log4j2-1`
.replaceAll("(-\\d+).*?$", "$1");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Indeed looks good as a first step. Possibly we can restrict it a bit more (untested), but I am also fine with leaving it as-is:

Suggested change
// Retain major version number, to avoid `log4j` conflict between `log4j-1` and `log4j2-1`
.replaceAll("(-\\d+).*?$", "$1");
// Retain major version number, to avoid `log4j` conflict between `log4j-1` and `log4j2-1`
.replaceFirst("(-\\d+)", "$1");

@timtebeek timtebeek merged commit 28c1ded into main Aug 19, 2025
2 checks passed
@timtebeek timtebeek deleted the transitive-jarnames branch August 19, 2025 09:53
@github-project-automation github-project-automation bot moved this from Ready to Review to Done in OpenRewrite Aug 19, 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.

4 participants