From bc0a29bf760e6529d25f755b032404766a6715de Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Mon, 30 Mar 2026 11:26:17 -0400 Subject: [PATCH 1/7] Add star import ambiguity detection to ChangePackage --- rewrite-java-test/build.gradle.kts | 3 + .../META-INF/rewrite/classpath.tsv.gz | Bin 16026 -> 16022 bytes .../openrewrite/java/ChangePackageTest.java | 128 +++++++++++++++ .../META-INF/rewrite/classpath.tsv.gz | Bin 0 -> 74081 bytes .../org/openrewrite/java/ChangePackage.java | 148 +++++++++++++++++- 5 files changed, 272 insertions(+), 7 deletions(-) create mode 100644 rewrite-java-test/src/test/resources/META-INF/rewrite/classpath.tsv.gz diff --git a/rewrite-java-test/build.gradle.kts b/rewrite-java-test/build.gradle.kts index 427a9f6c582..54aa4ba0892 100644 --- a/rewrite-java-test/build.gradle.kts +++ b/rewrite-java-test/build.gradle.kts @@ -4,6 +4,9 @@ plugins { recipeDependencies { parserClasspath("jakarta.persistence:jakarta.persistence-api:3.1.0") + testParserClasspath("jakarta.validation:jakarta.validation-api:3.0.2") + testParserClasspath("javax.validation:validation-api:1.1.0.Final") + testParserClasspath("org.hibernate:hibernate-validator:5.4.3.Final") } dependencies { diff --git a/rewrite-java-test/src/main/resources/META-INF/rewrite/classpath.tsv.gz b/rewrite-java-test/src/main/resources/META-INF/rewrite/classpath.tsv.gz index b15940eeccacaeddc4a184c989f7d39cb1afb1d8..45dd8332c3bdd09b3dec1214de824efb59b06f7d 100644 GIT binary patch literal 16022 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 U+#M~{mKTHke?J%jr~rlq0C2J3&j0`b diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java index ebf408fcb13..2ebe2bbd9b5 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java @@ -18,6 +18,7 @@ import org.intellij.lang.annotations.Language; import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Issue; import org.openrewrite.java.search.FindTypes; import org.openrewrite.java.tree.J; @@ -28,11 +29,16 @@ import org.openrewrite.test.RewriteTest; import org.openrewrite.test.SourceSpec; +import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; +import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.openrewrite.java.Assertions.addTypesToSourceSet; import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.java.Assertions.srcMainJava; import static org.openrewrite.properties.Assertions.properties; import static org.openrewrite.test.SourceSpecs.text; import static org.openrewrite.xml.Assertions.xml; @@ -666,6 +672,92 @@ T method(T t) { ); } + @Test + void changePackageExpandsStarImportWhenItWouldCreateAmbiguity() { + InMemoryExecutionContext ctx = new InMemoryExecutionContext(); + List classpath = JavaParser.dependenciesFromResources(ctx, + "validation-api", "jakarta.validation-api", "hibernate-validator"); + rewriteRun( + spec -> spec.recipe(new ChangePackage("javax.validation.constraints", "jakarta.validation.constraints", true)) + .parser(JavaParser.fromJavaVersion().classpathFromResources(ctx, + "validation-api", "hibernate-validator")) + .beforeRecipe(addTypesToSourceSet("main", + emptyList(), classpath)), + srcMainJava( + java( + """ + package xyz; + + import javax.validation.constraints.*; + import org.hibernate.validator.constraints.*; + + class A { + @NotNull + private String someField; + @NotEmpty + private String otherField; + } + """, + """ + package xyz; + + import jakarta.validation.constraints.NotNull; + import org.hibernate.validator.constraints.*; + + class A { + @NotNull + private String someField; + @NotEmpty + private String otherField; + } + """ + ) + ) + ); + } + + @Test + void changePackagePreservesStarImportWhenNoAmbiguity() { + InMemoryExecutionContext ctx = new InMemoryExecutionContext(); + List classpath = JavaParser.dependenciesFromResources(ctx, + "validation-api", "jakarta.validation-api"); + rewriteRun( + spec -> spec.recipe(new ChangePackage("javax.validation.constraints", "jakarta.validation.constraints", true)) + .parser(JavaParser.fromJavaVersion().classpathFromResources(ctx, + "validation-api")) + .beforeRecipe(addTypesToSourceSet("main", + emptyList(), classpath)), + srcMainJava( + java( + """ + package xyz; + + import javax.validation.constraints.*; + + class A { + @NotNull + private String someField; + @Size(max = 100) + private String otherField; + } + """, + """ + package xyz; + + import jakarta.validation.constraints.*; + + class A { + @NotNull + private String someField; + @Size(max = 100) + private String otherField; + } + """ + ) + ) + ); + } + @Test void annotation() { rewriteRun( @@ -2059,4 +2151,40 @@ void changePackageInServiceProviderFileContentOnly() { ) ); } + + /** + * Enrich each source file's JavaSourceSet marker with types declared in other source files, + * so that classpath-based ambiguity detection works in tests where types come from source + * files rather than JARs. + */ + private static UncheckedConsumer> withSourceTypesOnClasspath() { + return sourceFiles -> { + List sourceTypes = new ArrayList<>(); + for (SourceFile sf : sourceFiles) { + if (sf instanceof JavaSourceFile) { + for (J.ClassDeclaration classDecl : ((JavaSourceFile) sf).getClasses()) { + JavaType.FullyQualified type = classDecl.getType(); + if (type != null) { + sourceTypes.add(type); + } + } + } + } + for (int i = 0; i < sourceFiles.size(); i++) { + SourceFile sf = sourceFiles.get(i); + Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); + JavaSourceSet ss; + if (maybeSourceSet.isPresent()) { + ss = maybeSourceSet.get(); + List enriched = new ArrayList<>(ss.getClasspath()); + enriched.addAll(sourceTypes); + ss = ss.withClasspath(enriched); + } else { + ss = new JavaSourceSet(java.util.UUID.randomUUID(), "main", sourceTypes, java.util.Collections.emptyMap()); + } + sourceFiles.set(i, sf.withMarkers( + sf.getMarkers().computeByType(ss, (orig, upd) -> upd))); + } + }; + } } diff --git a/rewrite-java-test/src/test/resources/META-INF/rewrite/classpath.tsv.gz b/rewrite-java-test/src/test/resources/META-INF/rewrite/classpath.tsv.gz new file mode 100644 index 0000000000000000000000000000000000000000..78735adfd16fbbaf5d3e3b03b2b2c978586eb921 GIT binary patch literal 74081 zcmX_nQ*b6s7iMfsY-eKIwrx9kW81cE+qRQQCbn(c$_`5 z{ap53cy4es(tT?0=w9-?B|JJ4PdX&JZsf2w(vY8zhnZ4d=bFqs4=L3DT|%uOqHNm& z9B=aj@gxy~;|qYGE*B&zkSaW>6)2R0z(`MSR+C5N8gZ`Y6ms3xA@(=2E}T|$8f^%x zg126Me+g*J*~PTBi|Z$J#hxyn0au_{Et8dRmWy2@0UL*~_? z!G1&Vi{z~H-j)ZpfK~+hEe*h6PR}yPh&vE~(~A>u$pe0UwZZ%xP=q=ao#Nd7WGvr5 z^*rNg)br26em|cJ&b5RDT6lcC?>=R%m}eH8_6)szgz>&g^1C+>bccVp0>4By%(5D< z{TO{(QeNP^sMq)RJbC^U@lg2a2K4Fs{t)DIB|u6E81s|1y;#i-{XD!--c2bhZxN#Q z?^7JabQsYFI6Z*}8RxKw+pV#Gm92aqgY*FAkdy&fEmJXDx*KLKdy-#NP5+p`f*k|v`z-uP=r z3(s#|yA+yIS1PC&kG|Px=Z_NDQ861`89Ne<1!93qBb98y`Vq3`ejspD#q)q3FDUYX zE&{n-a(TY)X_Y+=6^s~?jhH$h01J+xvnA}QTL!4r4tC`?Q-=gzp)jNj?EEt z{Aj)ujrl)zFwg2hx%9+O-Fuexg}L;+;J;6>@;>S2=BGxVNxc1nQ+36Q!^Zf%Zw+3b z<@vC*sO38=XihobwY30sn{2Vzdrww8IPxWRgs}mD8TImFs-tf3O;gZskmt#gvHB#? zNYJ=~>i4A^W&h%=sv?c-vm&eJ0;9JMWI#c_$yfD%* z4)jt|yWpm+9er8xcBaSrW`2udVdlV@CnboGBj#-EkUhw^B|Yqo-N_KA1S~$ebA9@F zQ{74KIh{_AvppquN1f_yKj{^(n9yg9MXjsjAey3@uwy`~D8~0Kc)g{XMkhwQIzGC(+ME?1ie*$P$k zs;qK>g|t@X!Is{R?fJ^5lQkBE;3vA~@N8-T{ehdZJT5u6mP3pKejqj;HzE;ux6;*D z6sgRN=(~(kxA=WvpvDQrZhrQ+-@9e(HwfDg(ruA^d`qcX*jMVf7h$a|GO#-75M(~i zbsCZ9K$EssaUaf3cH@g#47LhY6>SrpO2jYdkAJw-Sl2bs85YSq8dv`iEj_p9z?`c; zAP<)Xe}NQJ&EV4US2mI^?-x!S>EuHH|ehJoyfUl0^5w%?J-0qs5@Dn4gkOhk@E^fIn_X4c8GwAko(e4XE=K`WH zkeajAyg-#AgEyfQ(^@o3F4+{Ms8e1xHiU^TaiSj!z=f1SE0GaGb{p6Fsr!77@#ex7 zZJEX6!&4p5GF25KiTGmXhe65kucbj*qFahSsxT8ILn1&5m?en#W7kyEROM}djz^T` z{mIgoqrWXTW}Xbm*5=$IeM&U@%NlOq

b)^V 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 k6^{efp * NOTE: Does not currently transform all possible type references, and accomplishing this would be non-trivial. - * For example, a method invocation select might refer to field `A a` whose type has now changed to `A2`, and so the type - * on the select should change as well. But how do we identify the set of all method selects which refer to `a`? Suppose - * it were prefixed like `this.a`, or `MyClass.this.a`, or indirectly via a separate method call like `getA()` where `getA()` + * For example, a method invocation select might refer to field {@code A a} whose type has now changed to {@code A2}, and so the type + * on the select should change as well. But how do we identify the set of all method selects which refer to {@code a}? Suppose + * it were prefixed like {@code this.a}, or {@code MyClass.this.a}, or indirectly via a separate method call like {@code getA()} where {@code getA()} * is defined on the super class. */ @Value @@ -278,12 +277,147 @@ public J postVisit(J tree, ExecutionContext ctx) { } } + // Expand changed star imports that would create ambiguity with other star imports + sf = maybeExpandStarImport(sf, newPackageName, oldPackageName); + if (changingTo != null && !changingTo.equals(newPackageName)) { + String oldSubPkg = oldPackageName + changingTo.substring(newPackageName.length()); + sf = maybeExpandStarImport(sf, changingTo, oldSubPkg); + } + if (Boolean.TRUE.equals(recursive)) { + for (J.Import anImport : sf.getImports()) { + if (!anImport.isStatic() && "*".equals(anImport.getQualid().getSimpleName())) { + String pkg = anImport.getPackageName(); + if (pkg.startsWith(newPackageName + ".")) { + String oldPkg = oldPackageName + pkg.substring(newPackageName.length()); + sf = maybeExpandStarImport(sf, pkg, oldPkg); + } + } + } + } + j = sf; } //noinspection DataFlowIssue return j; } + /** + * If a star import for {@code changedPackage} exists alongside other star imports, + * and types from {@code changedPackage} share simple names with types from those + * other packages, expand the star import into explicit imports to avoid ambiguity. + * + * @param changedPackage the new package name (after rename) + * @param originalPackage the old package name (before rename), used to find types on classpath + */ + private JavaSourceFile maybeExpandStarImport(JavaSourceFile sf, String changedPackage, String originalPackage) { + J.Import changedStarImport = null; + Set otherStarPackages = new LinkedHashSet<>(); + for (J.Import anImport : sf.getImports()) { + if (anImport.isStatic() || !"*".equals(anImport.getQualid().getSimpleName())) { + continue; + } + if (anImport.getPackageName().equals(changedPackage)) { + changedStarImport = anImport; + } else { + otherStarPackages.add(anImport.getPackageName()); + } + } + + if (changedStarImport == null || otherStarPackages.isEmpty()) { + return sf; + } + + if (!hasAmbiguity(sf, changedPackage, originalPackage, otherStarPackages)) { + return sf; + } + + // Collect simple names of types used from the changed package + Set usedFromChangedPackage = new TreeSet<>(); + for (JavaType type : sf.getTypesInUse().getTypesInUse()) { + if (type instanceof JavaType.FullyQualified) { + JavaType.FullyQualified fq = (JavaType.FullyQualified) type; + if (fq.getPackageName().equals(changedPackage)) { + usedFromChangedPackage.add(fq.getClassName()); + } + } + } + + if (usedFromChangedPackage.isEmpty()) { + return sf; + } + + // Expand the changed star import into explicit imports for used types + J.Import starImport = changedStarImport; + return sf.withImports(ListUtils.flatMap(sf.getImports(), anImport -> { + if (anImport == starImport) { + List expanded = new ArrayList<>(usedFromChangedPackage.size()); + int i = 0; + for (String simpleName : usedFromChangedPackage) { + J.FieldAccess newQualid = starImport.getQualid() + .withName(starImport.getQualid().getName().withSimpleName(simpleName)); + String fqn = changedPackage + "." + simpleName; + newQualid = newQualid.withType(findType(fqn, sf)); + J.Import explicit = starImport.withQualid(newQualid).withId(randomId()); + expanded.add(i++ == 0 ? explicit : explicit.withPrefix(Space.format("\n"))); + } + return expanded; + } + return anImport; + })); + } + + /** + * Checks whether types in the changed package share simple names with types in + * any of the other star-imported packages, using the JavaSourceSet classpath. + * Checks both the new package name and the original package name, since the + * classpath may still have types under the old package name. + */ + private boolean hasAmbiguity(JavaSourceFile sf, String changedPackage, String originalPackage, Set otherStarPackages) { + Optional sourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); + if (!sourceSet.isPresent()) { + return false; + } + + Set typesInChangedPackage = new HashSet<>(); + Set typesInOtherPackages = new HashSet<>(); + for (JavaType.FullyQualified fq : sourceSet.get().getClasspath()) { + String pkg = fq.getPackageName(); + String className = fq.getClassName(); + if (pkg.equals(changedPackage) || pkg.equals(originalPackage)) { + typesInChangedPackage.add(className); + } else if (otherStarPackages.contains(pkg)) { + typesInOtherPackages.add(className); + } + } + + for (String typeName : typesInChangedPackage) { + if (typesInOtherPackages.contains(typeName)) { + return true; + } + } + return false; + } + + private JavaType.FullyQualified findType(String fqn, JavaSourceFile cu) { + for (JavaType type : cu.getTypesInUse().getTypesInUse()) { + if (type instanceof JavaType.FullyQualified) { + JavaType.FullyQualified fq = (JavaType.FullyQualified) type; + if (TypeUtils.fullyQualifiedNamesAreEqual(fq.getFullyQualifiedName(), fqn)) { + return fq; + } + } + } + Optional sourceSet = cu.getMarkers().findFirst(JavaSourceSet.class); + if (sourceSet.isPresent()) { + for (JavaType.FullyQualified fq : sourceSet.get().getClasspath()) { + if (TypeUtils.fullyQualifiedNamesAreEqual(fq.getFullyQualifiedName(), fqn)) { + return fq; + } + } + } + return JavaType.ShallowClass.build(fqn); + } + private @Nullable JavaType updateType(@Nullable JavaType oldType) { if (oldType == null || oldType instanceof JavaType.Unknown) { return oldType; From f2449d6b5732954db74054057dfc55d432c4d6c4 Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Mon, 6 Apr 2026 14:24:15 -0400 Subject: [PATCH 2/7] Update JavaSourceSet marker from dependency-modifying recipes --- .../org/openrewrite/gradle/AddDependency.java | 82 ++++++- .../openrewrite/gradle/ChangeDependency.java | 66 +++++- .../openrewrite/gradle/RemoveDependency.java | 33 ++- .../gradle/UpgradeDependencyVersion.java | 80 ++++++- .../openrewrite/java/ChangePackageTest.java | 6 + .../java/marker/JavaSourceSet.java | 2 +- .../org/openrewrite/maven/AddDependency.java | 81 ++++++- .../ChangeDependencyGroupIdAndArtifactId.java | 153 ++++++++++++ .../openrewrite/maven/RemoveDependency.java | 41 +++- .../maven/UpgradeDependencyVersion.java | 120 +++++++++- .../maven/utilities/JavaSourceSetUpdater.java | 217 ++++++++++++++++++ ...ngeDependencyGroupIdAndArtifactIdTest.java | 184 ++++++++++++++- 12 files changed, 1041 insertions(+), 24 deletions(-) create mode 100644 rewrite-maven/src/main/java/org/openrewrite/maven/utilities/JavaSourceSetUpdater.java diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java index 675259b88c5..ab1c9419e2f 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java @@ -22,6 +22,7 @@ import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.gradle.search.FindJVMTestSuites; import org.openrewrite.gradle.trait.JvmTestSuite; +import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.StringUtils; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.marker.JavaProject; @@ -30,9 +31,15 @@ import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; +import org.openrewrite.kotlin.tree.K; +import org.openrewrite.maven.MavenDownloadingException; +import org.openrewrite.maven.MavenExecutionContextView; +import org.openrewrite.maven.internal.MavenPomDownloader; import org.openrewrite.maven.table.MavenMetadataFailures; -import org.openrewrite.maven.tree.GroupArtifact; +import org.openrewrite.maven.tree.*; +import org.openrewrite.maven.utilities.JavaSourceSetUpdater; import org.openrewrite.semver.Semver; +import org.openrewrite.semver.VersionComparator; import java.util.*; @@ -138,6 +145,9 @@ public Validated validate() { public static class Scanned { Map usingType = new HashMap<>(); Map> configurationsByProject = new HashMap<>(); + @Nullable + String resolvedVersion; + List repositories = new ArrayList<>(); } @Override @@ -185,6 +195,24 @@ private boolean usesType(SourceFile sourceFile, ExecutionContext ctx) { sourceFile.getMarkers().findFirst(JavaSourceSet.class).ifPresent(sourceSet -> configurations.add("main".equals(sourceSet.getName()) ? "implementation" : sourceSet.getName() + "Implementation")); } + + // Resolve version once for JavaSourceSet updates + if (acc.resolvedVersion == null && version != null) { + Optional maybeGp = sourceFile.getMarkers().findFirst(GradleProject.class); + if (maybeGp.isPresent()) { + try { + DependencyVersionSelector selector = new DependencyVersionSelector(metadataFailures, maybeGp.get(), null); + acc.resolvedVersion = selector.select( + new GroupArtifact(groupId, artifactId), "implementation", + version, versionPattern, ctx); + if (acc.resolvedVersion != null) { + acc.repositories = maybeGp.get().getMavenRepositories(); + } + } catch (MavenDownloadingException e) { + // Version resolution failed + } + } + } }); return tree; } @@ -196,7 +224,7 @@ public TreeVisitor getVisitor(Scanned acc) { // Allow when configuration is explicitly provided, when onlyIfUsing is not set (default to "implementation"), // or when source files were scanned boolean hasExplicitConfiguration = !StringUtils.isBlank(configuration); - return Preconditions.check(hasExplicitConfiguration || onlyIfUsing == null || !acc.configurationsByProject.isEmpty(), + TreeVisitor gradleVisitor = Preconditions.check(hasExplicitConfiguration || onlyIfUsing == null || !acc.configurationsByProject.isEmpty(), Preconditions.check(new IsBuildGradle<>(true), new JavaIsoVisitor() { @Override @@ -280,5 +308,55 @@ private boolean isTopLevel(Cursor cursor) { } }) ); + + if (acc.configurationsByProject.isEmpty() || acc.resolvedVersion == null) { + return gradleVisitor; + } + + return new TreeVisitor() { + @Nullable + private JavaSourceSetUpdater updater; + + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { + return gradleVisitor.isAcceptable(sourceFile, ctx) || sourceFile instanceof JavaSourceFile; + } + + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (!(tree instanceof SourceFile)) { + return tree; + } + SourceFile sf = (SourceFile) tree; + if (gradleVisitor.isAcceptable(sf, ctx)) { + return gradleVisitor.visit(tree, ctx); + } + if (sf instanceof JavaSourceFile + && !(sf instanceof G.CompilationUnit) && !(sf instanceof K.CompilationUnit)) { + return updateJavaSourceSet(sf, ctx); + } + return tree; + } + + private SourceFile updateJavaSourceSet(SourceFile sf, ExecutionContext ctx) { + Optional maybeJp = sf.getMarkers().findFirst(JavaProject.class); + if (!maybeJp.isPresent() || !acc.configurationsByProject.containsKey(maybeJp.get())) { + return sf; + } + Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); + if (!maybeSourceSet.isPresent() || maybeSourceSet.get().getGavToTypes().isEmpty()) { + return sf; + } + if (updater == null) { + updater = new JavaSourceSetUpdater(ctx); + } + JavaSourceSet updated = updater.addDependency(maybeSourceSet.get(), + groupId, artifactId, acc.resolvedVersion, acc.repositories); + if (updated != maybeSourceSet.get()) { + return sf.withMarkers(sf.getMarkers().setByType(updated)); + } + return sf; + } + }; } } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java index 9f5c752d643..dd622df7ef7 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java @@ -30,6 +30,8 @@ import org.openrewrite.internal.StringUtils; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.marker.JavaProject; +import org.openrewrite.java.marker.JavaSourceSet; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.kotlin.tree.K; @@ -37,6 +39,7 @@ import org.openrewrite.maven.MavenDownloadingException; import org.openrewrite.maven.tree.*; import org.openrewrite.maven.table.MavenMetadataFailures; +import org.openrewrite.maven.utilities.JavaSourceSetUpdater; import org.openrewrite.properties.PropertiesVisitor; import org.openrewrite.properties.tree.Properties; import org.openrewrite.semver.DependencyMatcher; @@ -171,6 +174,7 @@ public static class Accumulator { Map versionVariableUpdates = new HashMap<>(); Map> versionVariableUsages = new HashMap<>(); Set failedResolutions = new HashSet<>(); + Map modulesWithOldDependency = new HashMap<>(); } @Override @@ -197,6 +201,20 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { if (gradleProject == null) { return (J) tree; } + // Record this module if it has the old dependency + Optional maybeJp = tree.getMarkers().findFirst(JavaProject.class); + if (maybeJp.isPresent() && !acc.modulesWithOldDependency.containsKey(maybeJp.get())) { + outer: + for (GradleDependencyConfiguration config : gradleProject.getConfigurations()) { + for (ResolvedDependency resolved : config.getDirectResolved()) { + if (StringUtils.matchesGlob(resolved.getGroupId(), oldGroupId) && + StringUtils.matchesGlob(resolved.getArtifactId(), oldArtifactId)) { + acc.modulesWithOldDependency.put(maybeJp.get(), resolved); + break outer; + } + } + } + } } return super.visit(tree, ctx); } @@ -480,14 +498,22 @@ private GradleProject updateGradleModel(GradleProject gp, ExecutionContext ctx) }); DependencyMatcher propsMatcher = requireNonNull(DependencyMatcher.build(oldGroupId + ":" + oldArtifactId).getValue()); + boolean hasModulesWithOldDep = !acc.modulesWithOldDependency.isEmpty(); return new TreeVisitor() { + @Nullable + private JavaSourceSetUpdater updater; + @Override public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { if (sourceFile instanceof Properties.File) { return sourceFile.getSourcePath().endsWith(GRADLE_PROPERTIES_FILE_NAME); } - return (sourceFile instanceof G.CompilationUnit || sourceFile instanceof K.CompilationUnit) - && sourceFile.getMarkers().findFirst(GradleProject.class).isPresent(); + if ((sourceFile instanceof G.CompilationUnit || sourceFile instanceof K.CompilationUnit) + && sourceFile.getMarkers().findFirst(GradleProject.class).isPresent()) { + return true; + } + // Accept Java source files for JavaSourceSet updates + return hasModulesWithOldDep && sourceFile instanceof JavaSourceFile; } @Override @@ -517,8 +543,44 @@ public Properties visitEntry(Properties.Entry entry, ExecutionContext ctx) { } return tree; } + // For non-Gradle Java source files, update JavaSourceSet marker + if (hasModulesWithOldDep && tree instanceof JavaSourceFile && + !(tree instanceof G.CompilationUnit) && !(tree instanceof K.CompilationUnit)) { + return updateJavaSourceSet((SourceFile) tree, ctx); + } return gradleVisitor.visit(tree, ctx); } + + private SourceFile updateJavaSourceSet(SourceFile sf, ExecutionContext ctx) { + Optional maybeJp = sf.getMarkers().findFirst(JavaProject.class); + if (!maybeJp.isPresent()) { + return sf; + } + ResolvedDependency oldDep = acc.modulesWithOldDependency.get(maybeJp.get()); + if (oldDep == null) { + return sf; + } + Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); + if (!maybeSourceSet.isPresent()) { + return sf; + } + if (updater == null) { + updater = new JavaSourceSetUpdater(ctx); + } + String effectiveNewGroupId = newGroupId != null ? newGroupId : oldDep.getGroupId(); + String effectiveNewArtifactId = newArtifactId != null ? newArtifactId : oldDep.getArtifactId(); + ResolvedGroupArtifactVersion newGav = new ResolvedGroupArtifactVersion( + oldDep.getGav().getRepository(), + effectiveNewGroupId, effectiveNewArtifactId, oldDep.getVersion(), null); + ResolvedDependency newDep = oldDep + .withGav(newGav) + .withRepository(MavenRepository.MAVEN_CENTRAL); + JavaSourceSet updated = updater.changeDependency(maybeSourceSet.get(), oldDep, newDep); + if (updated != maybeSourceSet.get()) { + return sf.withMarkers(sf.getMarkers().setByType(updated)); + } + return sf; + } }; } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveDependency.java index 44117bbf50d..34dc9084ef1 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveDependency.java @@ -26,9 +26,11 @@ import org.openrewrite.internal.ListUtils; import org.openrewrite.internal.StringUtils; import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.marker.JavaSourceSet; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.kotlin.tree.K; +import org.openrewrite.maven.utilities.JavaSourceSetUpdater; import org.openrewrite.semver.DependencyMatcher; import java.util.HashMap; @@ -69,7 +71,7 @@ public String getInstanceNameSuffix() { @Override public TreeVisitor getVisitor() { - return Preconditions.check(new IsBuildGradle<>(), new JavaIsoVisitor() { + TreeVisitor gradleVisitor = Preconditions.check(new IsBuildGradle<>(), new JavaIsoVisitor() { final GradleDependency.Matcher gradleDependencyMatcher = new GradleDependency.Matcher() .configuration(configuration) .groupId(groupId) @@ -157,5 +159,34 @@ private GradleProject updateGradleModel(GradleProject gp) { return gp; } }); + + return new TreeVisitor() { + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { + return gradleVisitor.isAcceptable(sourceFile, ctx) || sourceFile instanceof JavaSourceFile; + } + + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (!(tree instanceof SourceFile)) { + return tree; + } + SourceFile sf = (SourceFile) tree; + if (gradleVisitor.isAcceptable(sf, ctx)) { + return gradleVisitor.visit(tree, ctx); + } + if (sf instanceof JavaSourceFile) { + Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); + if (maybeSourceSet.isPresent()) { + JavaSourceSet updated = JavaSourceSetUpdater.removeTypesMatching( + maybeSourceSet.get(), groupId, artifactId); + if (updated != maybeSourceSet.get()) { + return sf.withMarkers(sf.getMarkers().setByType(updated)); + } + } + } + return tree; + } + }; } } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java index 237089d9c0c..b8855540153 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/UpgradeDependencyVersion.java @@ -33,18 +33,18 @@ import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.marker.JavaProject; +import org.openrewrite.java.marker.JavaSourceSet; import org.openrewrite.java.tree.Expression; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.kotlin.tree.K; +import org.openrewrite.maven.utilities.JavaSourceSetUpdater; import org.openrewrite.marker.Markup; import org.openrewrite.maven.MavenDownloadingException; import org.openrewrite.maven.internal.MavenPomDownloader; import org.openrewrite.maven.table.MavenMetadataFailures; -import org.openrewrite.maven.tree.Dependency; -import org.openrewrite.maven.tree.GroupArtifact; -import org.openrewrite.maven.tree.GroupArtifactVersion; -import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.maven.tree.*; import org.openrewrite.properties.PropertiesVisitor; import org.openrewrite.properties.tree.Properties; import org.openrewrite.semver.DependencyMatcher; @@ -134,6 +134,8 @@ public static class DependencyVersionState { Map gaToNewVersion = new HashMap<>(); Map>> configurationPerGAPerModule = new HashMap<>(); + + Map> modulesWithDependency = new HashMap<>(); } @Override @@ -159,6 +161,24 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { public @Nullable J visit(@Nullable Tree tree, ExecutionContext ctx) { if (tree instanceof JavaSourceFile) { gradleProject = tree.getMarkers().findFirst(GradleProject.class).orElse(null); + // Record modules with matching resolved dependencies for JavaSourceSet updates + if (gradleProject != null) { + Optional maybeJp = tree.getMarkers().findFirst(JavaProject.class); + if (maybeJp.isPresent() && !acc.modulesWithDependency.containsKey(maybeJp.get())) { + DependencyMatcher depMatcher = new DependencyMatcher(groupId, artifactId, null); + Set matched = new HashSet<>(); + for (GradleDependencyConfiguration config : gradleProject.getConfigurations()) { + for (ResolvedDependency resolved : config.getDirectResolved()) { + if (depMatcher.matches(resolved.getGroupId(), resolved.getArtifactId())) { + matched.add(resolved); + } + } + } + if (!matched.isEmpty()) { + acc.modulesWithDependency.put(maybeJp.get(), matched); + } + } + } } return super.visit(tree, ctx); } @@ -291,13 +311,20 @@ private void gatherVariables(GradleDependency gradleDependency) { @Override public TreeVisitor getVisitor(DependencyVersionState acc) { + boolean hasModulesWithDep = !acc.modulesWithDependency.isEmpty(); return new TreeVisitor() { private final UpdateGradle updateGradle = new UpdateGradle(acc); private final UpdateProperties updateProperties = new UpdateProperties(acc); + @Nullable + private JavaSourceSetUpdater jssUpdater; @Override public boolean isAcceptable(SourceFile sf, ExecutionContext ctx) { - return updateProperties.isAcceptable(sf, ctx) || updateGradle.isAcceptable(sf, ctx); + if (updateProperties.isAcceptable(sf, ctx) || updateGradle.isAcceptable(sf, ctx)) { + return true; + } + return hasModulesWithDep && sf instanceof JavaSourceFile + && !(sf instanceof G.CompilationUnit) && !(sf instanceof K.CompilationUnit); } @Override @@ -306,6 +333,11 @@ public boolean isAcceptable(SourceFile sf, ExecutionContext ctx) { Tree t = tree; if (t instanceof SourceFile) { SourceFile sf = (SourceFile) t; + // Handle regular Java source files for JavaSourceSet updates + if (hasModulesWithDep && sf instanceof JavaSourceFile + && !(sf instanceof G.CompilationUnit) && !(sf instanceof K.CompilationUnit)) { + return updateJavaSourceSet(sf, ctx); + } if (updateProperties.isAcceptable(sf, ctx)) { t = updateProperties.visitNonNull(t, ctx); } else if (updateGradle.isAcceptable(sf, ctx)) { @@ -361,6 +393,44 @@ public boolean isAcceptable(SourceFile sf, ExecutionContext ctx) { } return t; } + + private SourceFile updateJavaSourceSet(SourceFile sf, ExecutionContext ctx) { + Optional maybeJp = sf.getMarkers().findFirst(JavaProject.class); + if (!maybeJp.isPresent()) { + return sf; + } + Set oldDeps = acc.modulesWithDependency.get(maybeJp.get()); + if (oldDeps == null || oldDeps.isEmpty()) { + return sf; + } + Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); + if (!maybeSourceSet.isPresent() || maybeSourceSet.get().getGavToTypes().isEmpty()) { + return sf; + } + if (jssUpdater == null) { + jssUpdater = new JavaSourceSetUpdater(ctx); + } + JavaSourceSet sourceSet = maybeSourceSet.get(); + for (ResolvedDependency oldDep : oldDeps) { + GroupArtifact ga = new GroupArtifact(oldDep.getGroupId(), oldDep.getArtifactId()); + Object newVersionObj = acc.gaToNewVersion.get(ga); + if (!(newVersionObj instanceof String)) { + continue; + } + String resolvedNewVersion = (String) newVersionObj; + ResolvedGroupArtifactVersion newGav = new ResolvedGroupArtifactVersion( + oldDep.getGav().getRepository(), + oldDep.getGroupId(), oldDep.getArtifactId(), resolvedNewVersion, null); + ResolvedDependency newDep = oldDep + .withGav(newGav) + .withRepository(MavenRepository.MAVEN_CENTRAL); + sourceSet = jssUpdater.changeDependency(sourceSet, oldDep, newDep); + } + if (sourceSet != maybeSourceSet.get()) { + return sf.withMarkers(sf.getMarkers().setByType(sourceSet)); + } + return sf; + } }; } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java index 2ebe2bbd9b5..d853ca81315 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java @@ -20,18 +20,24 @@ import org.openrewrite.DocumentExample; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Issue; +import org.openrewrite.SourceFile; +import org.openrewrite.java.marker.JavaSourceSet; import org.openrewrite.java.search.FindTypes; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.java.tree.NameTree; import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import org.openrewrite.test.SourceSpec; +import org.openrewrite.test.UncheckedConsumer; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; diff --git a/rewrite-java/src/main/java/org/openrewrite/java/marker/JavaSourceSet.java b/rewrite-java/src/main/java/org/openrewrite/java/marker/JavaSourceSet.java index 97c82154eac..8dd18db919f 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/marker/JavaSourceSet.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/marker/JavaSourceSet.java @@ -269,7 +269,7 @@ public static JavaSourceSet build(String sourceSetName, Collection classpa // Worth caching as there is typically substantial overlap in dependencies in use within the same repository // Even a single module project will typically have at least two source sets, main and test - private static List typesFromPath(Path path, @Nullable String acceptPackage) { + public static List typesFromPath(Path path, @Nullable String acceptPackage) { List types = new ArrayList<>(); try { // Paths will be to either directories of class files or jar files diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java index da61d6ec3a6..4195211cc89 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java @@ -26,10 +26,8 @@ import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.maven.table.MavenMetadataFailures; -import org.openrewrite.maven.tree.MavenResolutionResult; -import org.openrewrite.maven.tree.ResolvedDependency; -import org.openrewrite.maven.tree.ResolvedGroupArtifactVersion; -import org.openrewrite.maven.tree.Scope; +import org.openrewrite.maven.tree.*; +import org.openrewrite.maven.utilities.JavaSourceSetUpdater; import org.openrewrite.semver.Semver; import org.openrewrite.semver.VersionComparator; import org.openrewrite.xml.tree.Xml; @@ -162,6 +160,9 @@ public static class Scanned { boolean usingType; Map scopeByProject = new HashMap<>(); Set pomsDefinedInCurrentRepository = new HashSet<>(); + @Nullable + String resolvedVersion; + List repositories = new ArrayList<>(); } @Override @@ -199,6 +200,27 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { return sourceFile; } acc.pomsDefinedInCurrentRepository.add(mrr.getPom().getGav()); + // Resolve the version once for JavaSourceSet updates + if (acc.resolvedVersion == null && version != null) { + try { + List repos = mrr.getPom().getRepositories(); + VersionComparator vc = requireNonNull(Semver.validate(version, versionPattern).getValue()); + MavenExecutionContextView mctx = MavenExecutionContextView.view(ctx); + org.openrewrite.maven.internal.MavenPomDownloader downloader = new org.openrewrite.maven.internal.MavenPomDownloader( + Collections.emptyMap(), ctx, mctx.getSettings(), null); + MavenMetadata metadata = downloader.downloadMetadata( + new GroupArtifact(groupId, artifactId), null, repos); + acc.resolvedVersion = metadata.getVersioning().getVersions().stream() + .filter(v -> vc.isValid(null, v)) + .max((v1, v2) -> vc.compare(null, v1, v2)) + .orElse(null); + if (acc.resolvedVersion != null) { + acc.repositories = repos; + } + } catch (Exception e) { + // Version resolution failed; JavaSourceSet won't be updated + } + } } return sourceFile; } @@ -207,7 +229,7 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { @Override public TreeVisitor getVisitor(Scanned acc) { - return Preconditions.check(onlyIfUsing == null || acc.usingType && !acc.scopeByProject.isEmpty(), new MavenVisitor() { + TreeVisitor mavenVisitor = Preconditions.check(onlyIfUsing == null || acc.usingType && !acc.scopeByProject.isEmpty(), new MavenVisitor() { @Nullable final Pattern familyPatternCompiled = familyPattern == null ? null : Pattern.compile(familyPattern.replace("*", ".*")); @@ -284,6 +306,55 @@ private boolean isAggregatorNotUsedAsParent() { } }); + + if (acc.scopeByProject.isEmpty() || acc.resolvedVersion == null) { + return mavenVisitor; + } + + return new TreeVisitor() { + @Nullable + private JavaSourceSetUpdater updater; + + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { + return mavenVisitor.isAcceptable(sourceFile, ctx) || sourceFile instanceof JavaSourceFile; + } + + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (!(tree instanceof SourceFile)) { + return tree; + } + SourceFile sf = (SourceFile) tree; + if (sf instanceof Xml.Document) { + return mavenVisitor.visit(tree, ctx); + } + if (sf instanceof JavaSourceFile) { + return updateJavaSourceSet(sf, ctx); + } + return tree; + } + + private SourceFile updateJavaSourceSet(SourceFile sf, ExecutionContext ctx) { + Optional maybeJp = sf.getMarkers().findFirst(JavaProject.class); + if (!maybeJp.isPresent() || !acc.scopeByProject.containsKey(maybeJp.get())) { + return sf; + } + Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); + if (!maybeSourceSet.isPresent() || maybeSourceSet.get().getGavToTypes().isEmpty()) { + return sf; + } + if (updater == null) { + updater = new JavaSourceSetUpdater(ctx); + } + JavaSourceSet updated = updater.addDependency(maybeSourceSet.get(), + groupId, artifactId, acc.resolvedVersion, acc.repositories); + if (updated != maybeSourceSet.get()) { + return sf.withMarkers(sf.getMarkers().setByType(updated)); + } + return sf; + } + }; } private boolean hasAcceptableTransitivity(ResolvedDependency d, Scanned acc) { diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java index 0d036878a7e..f689cd49c09 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java @@ -20,8 +20,13 @@ import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; +import org.openrewrite.java.marker.JavaProject; +import org.openrewrite.java.marker.JavaSourceSet; +import org.openrewrite.java.tree.JavaSourceFile; +import org.openrewrite.maven.internal.MavenPomDownloader; import org.openrewrite.maven.table.MavenMetadataFailures; import org.openrewrite.maven.tree.*; +import org.openrewrite.maven.utilities.JavaSourceSetUpdater; import org.openrewrite.semver.Semver; import org.openrewrite.semver.VersionComparator; import org.openrewrite.xml.AddToTagVisitor; @@ -156,6 +161,68 @@ public Accumulator getInitialValue(ExecutionContext ctx) { @Override public TreeVisitor getScanner(Accumulator acc) { + TreeVisitor mavenScanner = getMavenScanner(acc); + @Nullable VersionComparator scannerVersionComparator = newVersion != null ? + Semver.validate(newVersion, versionPattern).getValue() : null; + + // Wrap to also scan pom.xml files for modules that have the old dependency + return new TreeVisitor() { + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { + return sourceFile instanceof Xml.Document || sourceFile instanceof JavaSourceFile; + } + + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (!(tree instanceof SourceFile)) { + return tree; + } + SourceFile sf = (SourceFile) tree; + if (sf instanceof Xml.Document && mavenScanner.isAcceptable(sf, ctx)) { + mavenScanner.visit(tree, ctx); + // Also check if this pom has the old dependency + sf.getMarkers().findFirst(MavenResolutionResult.class).ifPresent(mrr -> { + List deps = mrr.findDependencies(oldGroupId, oldArtifactId, null); + if (!deps.isEmpty()) { + sf.getMarkers().findFirst(JavaProject.class).ifPresent(jp -> { + acc.getModulesWithOldDependency().put(jp, deps.get(0)); + acc.getModuleRepositories().put(jp, mrr.getPom().getRepositories()); + // Resolve the new version for JavaSourceSet updates + if (newVersion != null) { + String effectiveGroupId = newGroupId != null ? newGroupId : deps.get(0).getGroupId(); + String effectiveArtifactId = newArtifactId != null ? newArtifactId : deps.get(0).getArtifactId(); + try { + MavenMetadata metadata = metadataFailures.insertRows(ctx, () -> + new MavenPomDownloader(mrr.getProjectPoms(), ctx, mrr.getMavenSettings(), mrr.getActiveProfiles()) + .downloadMetadata(new GroupArtifactVersion(effectiveGroupId, effectiveArtifactId, null), null, mrr.getPom().getRepositories())); + String resolved = newVersion; + if (scannerVersionComparator != null) { + List available = new ArrayList<>(); + for (String v : metadata.getVersioning().getVersions()) { + if (scannerVersionComparator.isValid(deps.get(0).getVersion(), v)) { + available.add(v); + } + } + if (!available.isEmpty()) { + resolved = max(available, scannerVersionComparator); + } + } + acc.getResolvedNewVersions().put(jp, resolved); + } catch (Exception e) { + // Version resolution failure is not fatal for JavaSourceSet updates + } + } + }); + } + }); + } + // Java files are no-op in scanner + return tree; + } + }; + } + + private TreeVisitor getMavenScanner(Accumulator acc) { if (newVersion == null) { return TreeVisitor.noop(); } @@ -254,6 +321,89 @@ private void storeParentPomProperty(@Nullable MavenResolutionResult parent, Stri @Override public TreeVisitor getVisitor(Accumulator acc) { + MavenVisitor mavenVisitor = getMavenVisitor(acc); + + if (acc.getModulesWithOldDependency().isEmpty()) { + // No modules have the old dependency; skip Java file processing + return mavenVisitor; + } + + return new TreeVisitor() { + @Nullable + private JavaSourceSetUpdater updater; + + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { + return mavenVisitor.isAcceptable(sourceFile, ctx) || sourceFile instanceof JavaSourceFile; + } + + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (!(tree instanceof SourceFile)) { + return tree; + } + SourceFile sf = (SourceFile) tree; + if (mavenVisitor.isAcceptable(sf, ctx)) { + return mavenVisitor.visit(tree, ctx); + } + if (sf instanceof JavaSourceFile) { + return updateJavaSourceSet(sf, ctx); + } + return tree; + } + + private SourceFile updateJavaSourceSet(SourceFile sf, ExecutionContext ctx) { + Optional maybeJp = sf.getMarkers().findFirst(JavaProject.class); + if (!maybeJp.isPresent()) { + return sf; + } + ResolvedDependency oldDep = acc.getModulesWithOldDependency().get(maybeJp.get()); + if (oldDep == null) { + return sf; + } + Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); + if (!maybeSourceSet.isPresent()) { + return sf; + } + if (updater == null) { + updater = new JavaSourceSetUpdater(ctx); + } + // Build a synthetic ResolvedDependency for the new coordinates + String effectiveNewGroupId = newGroupId != null ? newGroupId : oldDep.getGroupId(); + String effectiveNewArtifactId = newArtifactId != null ? newArtifactId : oldDep.getArtifactId(); + // Use the resolved new version if available, otherwise fall back to old version + String resolvedVersion = acc.getResolvedNewVersions().get(maybeJp.get()); + String effectiveVersion = resolvedVersion != null ? resolvedVersion : oldDep.getVersion(); + ResolvedGroupArtifactVersion newGav = new ResolvedGroupArtifactVersion( + oldDep.getGav().getRepository(), + effectiveNewGroupId, effectiveNewArtifactId, effectiveVersion, null); + // Use a remote repository for downloading (local file:// repos won't have the new artifact) + ResolvedDependency newDep = oldDep + .withGav(newGav) + .withRepository(findRemoteRepository(maybeJp.get())); + JavaSourceSet updated = updater.changeDependency(maybeSourceSet.get(), oldDep, newDep); + if (updated != maybeSourceSet.get()) { + return sf.withMarkers(sf.getMarkers().setByType(updated)); + } + return sf; + } + + private MavenRepository findRemoteRepository(JavaProject jp) { + List repos = acc.getModuleRepositories().get(jp); + if (repos != null) { + for (MavenRepository repo : repos) { + String uri = repo.getUri(); + if (uri != null && (uri.startsWith("http://") || uri.startsWith("https://"))) { + return repo; + } + } + } + return MavenRepository.MAVEN_CENTRAL; + } + }; + } + + private MavenVisitor getMavenVisitor(Accumulator acc) { return new MavenVisitor() { @Nullable final VersionComparator versionComparator = newVersion != null ? Semver.validate(newVersion, versionPattern).getValue() : null; @@ -497,6 +647,9 @@ private String resolveSemverVersion(ExecutionContext ctx, String groupId, String @Value public static class Accumulator { Set pomProperties = new HashSet<>(); + Map modulesWithOldDependency = new HashMap<>(); + Map> moduleRepositories = new HashMap<>(); + Map resolvedNewVersions = new HashMap<>(); } @Value diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveDependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveDependency.java index 3ab6d8d9458..62392d751a6 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveDependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveDependency.java @@ -18,15 +18,17 @@ import lombok.EqualsAndHashCode; import lombok.Value; import org.jspecify.annotations.Nullable; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Option; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; +import org.openrewrite.*; +import org.openrewrite.java.marker.JavaSourceSet; +import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.maven.tree.ResolvedDependency; import org.openrewrite.maven.tree.Scope; +import org.openrewrite.maven.utilities.JavaSourceSetUpdater; import org.openrewrite.xml.RemoveContentVisitor; import org.openrewrite.xml.tree.Xml; +import java.util.Optional; + @Value @EqualsAndHashCode(callSuper = false) public class RemoveDependency extends Recipe { @@ -62,7 +64,7 @@ public String getInstanceNameSuffix() { @Override public TreeVisitor getVisitor() { - return new MavenIsoVisitor() { + MavenIsoVisitor mavenVisitor = new MavenIsoVisitor() { @Override public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { if (isDependencyTag(groupId, artifactId)) { @@ -77,6 +79,35 @@ public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { return super.visitTag(tag, ctx); } }; + + return new TreeVisitor() { + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { + return mavenVisitor.isAcceptable(sourceFile, ctx) || sourceFile instanceof JavaSourceFile; + } + + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (!(tree instanceof SourceFile)) { + return tree; + } + SourceFile sf = (SourceFile) tree; + if (mavenVisitor.isAcceptable(sf, ctx)) { + return mavenVisitor.visit(tree, ctx); + } + if (sf instanceof JavaSourceFile) { + Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); + if (maybeSourceSet.isPresent()) { + JavaSourceSet updated = JavaSourceSetUpdater.removeTypesMatching( + maybeSourceSet.get(), groupId, artifactId); + if (updated != maybeSourceSet.get()) { + return sf.withMarkers(sf.getMarkers().setByType(updated)); + } + } + } + return tree; + } + }; } } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeDependencyVersion.java b/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeDependencyVersion.java index 88e1532dbd5..4239a1dbaad 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeDependencyVersion.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/UpgradeDependencyVersion.java @@ -19,10 +19,14 @@ import lombok.Value; import org.jspecify.annotations.Nullable; import org.openrewrite.*; +import org.openrewrite.java.marker.JavaProject; +import org.openrewrite.java.marker.JavaSourceSet; +import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.maven.internal.MavenPomDownloader; import org.openrewrite.maven.table.MavenMetadataFailures; import org.openrewrite.maven.trait.MavenDependency; import org.openrewrite.maven.tree.*; +import org.openrewrite.maven.utilities.JavaSourceSetUpdater; import org.openrewrite.maven.utilities.RetainVersions; import org.openrewrite.semver.LatestRelease; import org.openrewrite.semver.Semver; @@ -127,7 +131,7 @@ public Accumulator getInitialValue(ExecutionContext ctx) { @Override public TreeVisitor getScanner(Accumulator accumulator) { - return new MavenIsoVisitor() { + MavenIsoVisitor mavenScanner = new MavenIsoVisitor() { private final VersionComparator versionComparator = requireNonNull(Semver.validate(newVersion, versionPattern).getValue()); @@ -135,6 +139,28 @@ public TreeVisitor getScanner(Accumulator accumulator) { public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { ResolvedPom pom = getResolutionResult().getPom(); accumulator.projectArtifacts.add(new GroupArtifact(pom.getGroupId(), pom.getArtifactId())); + + // Record modules that have the matching dependency for JavaSourceSet updates + Optional maybeJp = document.getMarkers().findFirst(JavaProject.class); + if (maybeJp.isPresent() && !accumulator.modulesWithDependency.containsKey(maybeJp.get())) { + for (ResolvedDependency dep : getResolutionResult().findDependencies(groupId, artifactId, null)) { + if (dep.getRepository() != null) { + accumulator.modulesWithDependency.put(maybeJp.get(), dep); + accumulator.moduleRepositories.put(maybeJp.get(), pom.getRepositories()); + try { + String newerVersion = MavenDependency.findNewerVersion(dep.getGroupId(), dep.getArtifactId(), dep.getVersion(), getResolutionResult(), metadataFailures, + versionComparator, ctx); + if (newerVersion != null) { + accumulator.resolvedNewVersions.put(maybeJp.get(), newerVersion); + } + } catch (MavenDownloadingException e) { + // Version resolution failed; don't record new version + } + break; + } + } + } + return super.visitDocument(document, ctx); } @@ -190,11 +216,23 @@ private void storeParentPomProperty(@Nullable MavenResolutionResult currentMaven storeParentPomProperty(currentMavenResolutionResult.getParent(), propertyName, newerVersion); } }; + + return new TreeVisitor() { + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { + return mavenScanner.isAcceptable(sourceFile, ctx); + } + + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + return mavenScanner.visit(tree, ctx); + } + }; } @Override public TreeVisitor getVisitor(Accumulator accumulator) { - return new MavenIsoVisitor() { + MavenIsoVisitor mavenVisitor = new MavenIsoVisitor() { private final VersionComparator versionComparator = requireNonNull(Semver.validate(newVersion, versionPattern).getValue()); @Override @@ -570,12 +608,90 @@ null, null, getResolutionResult().getPom().getRepositories() return null; } }; + + if (accumulator.modulesWithDependency.isEmpty()) { + return mavenVisitor; + } + + return new TreeVisitor() { + @Nullable + private JavaSourceSetUpdater updater; + + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { + return mavenVisitor.isAcceptable(sourceFile, ctx) || sourceFile instanceof JavaSourceFile; + } + + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + if (!(tree instanceof SourceFile)) { + return tree; + } + SourceFile sf = (SourceFile) tree; + if (mavenVisitor.isAcceptable(sf, ctx)) { + return mavenVisitor.visit(tree, ctx); + } + if (sf instanceof JavaSourceFile) { + return updateJavaSourceSet(sf, ctx); + } + return tree; + } + + private SourceFile updateJavaSourceSet(SourceFile sf, ExecutionContext ctx) { + Optional maybeJp = sf.getMarkers().findFirst(JavaProject.class); + if (!maybeJp.isPresent()) { + return sf; + } + ResolvedDependency oldDep = accumulator.modulesWithDependency.get(maybeJp.get()); + if (oldDep == null) { + return sf; + } + String newVersion = accumulator.resolvedNewVersions.get(maybeJp.get()); + if (newVersion == null) { + return sf; + } + Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); + if (!maybeSourceSet.isPresent() || maybeSourceSet.get().getGavToTypes().isEmpty()) { + return sf; + } + if (updater == null) { + updater = new JavaSourceSetUpdater(ctx); + } + ResolvedGroupArtifactVersion newGav = new ResolvedGroupArtifactVersion( + oldDep.getGav().getRepository(), + oldDep.getGroupId(), oldDep.getArtifactId(), newVersion, null); + ResolvedDependency newDep = oldDep + .withGav(newGav) + .withRepository(findRemoteRepository(maybeJp.get())); + JavaSourceSet updated = updater.changeDependency(maybeSourceSet.get(), oldDep, newDep); + if (updated != maybeSourceSet.get()) { + return sf.withMarkers(sf.getMarkers().setByType(updated)); + } + return sf; + } + + private MavenRepository findRemoteRepository(JavaProject jp) { + List repos = accumulator.moduleRepositories.get(jp); + if (repos != null) { + for (MavenRepository repo : repos) { + String uri = repo.getUri(); + if (uri != null && (uri.startsWith("http://") || uri.startsWith("https://"))) { + return repo; + } + } + } + return MavenRepository.MAVEN_CENTRAL; + } + }; } @Value public static class Accumulator { Set projectArtifacts = new HashSet<>(); Set pomProperties = new HashSet<>(); + Map modulesWithDependency = new HashMap<>(); + Map resolvedNewVersions = new HashMap<>(); + Map> moduleRepositories = new HashMap<>(); } @Value diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/JavaSourceSetUpdater.java b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/JavaSourceSetUpdater.java new file mode 100644 index 00000000000..e400e4aab7b --- /dev/null +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/JavaSourceSetUpdater.java @@ -0,0 +1,217 @@ +/* + * Copyright 2026 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.maven.utilities; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.HttpSenderExecutionContextView; +import org.openrewrite.ipc.http.HttpSender; +import org.openrewrite.java.marker.JavaSourceSet; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.maven.MavenExecutionContextView; +import org.openrewrite.maven.cache.LocalMavenArtifactCache; +import org.openrewrite.maven.cache.MavenArtifactCache; +import org.openrewrite.maven.tree.Dependency; +import org.openrewrite.maven.tree.GroupArtifactVersion; +import org.openrewrite.maven.tree.MavenRepository; +import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.maven.tree.ResolvedGroupArtifactVersion; + +import static org.openrewrite.internal.StringUtils.matchesGlob; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.function.Consumer; + +/** + * Updates {@link JavaSourceSet} markers to reflect dependency changes made by recipes. + *

+ * When dependency-modifying recipes (ChangeDependency, AddDependency) change a project's + * dependencies, the JavaSourceSet marker on Java source files becomes stale — it still + * reflects the pre-change classpath. This utility downloads the new dependency's JAR, + * scans it for type names, and updates the JavaSourceSet accordingly. + */ +public class JavaSourceSetUpdater { + private final MavenArtifactDownloader downloader; + + public JavaSourceSetUpdater(ExecutionContext ctx) { + MavenExecutionContextView mctx = MavenExecutionContextView.view(ctx); + HttpSender httpSender = HttpSenderExecutionContextView.view(ctx).getHttpSender(); + // Use a lenient error handler: download failures are not fatal for JavaSourceSet updates + Consumer onError = t -> {}; + Path tempDir; + try { + tempDir = Files.createTempDirectory("rewrite-artifact-cache"); + tempDir.toFile().deleteOnExit(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + MavenArtifactCache cache = new LocalMavenArtifactCache(tempDir); + this.downloader = new MavenArtifactDownloader(cache, mctx.getSettings(), httpSender, onError); + } + + /** + * Update a JavaSourceSet to reflect a dependency coordinate change. + * Removes types from the old dependency and adds types from the new dependency. + */ + public JavaSourceSet changeDependency(JavaSourceSet sourceSet, + ResolvedDependency oldDep, + ResolvedDependency newDep) { + String oldGavKey = gavKey(oldDep); + String newGavKey = gavKey(newDep); + // Idempotent: if already changed (old absent, new present), skip + if (!sourceSet.getGavToTypes().containsKey(oldGavKey) && + sourceSet.getGavToTypes().containsKey(newGavKey)) { + return sourceSet; + } + sourceSet = removeTypesForGav(sourceSet, oldGavKey); + List newTypes = downloadAndScanTypes(newDep); + if (!newTypes.isEmpty()) { + sourceSet = addTypesForGav(sourceSet, newGavKey, newTypes); + } + return sourceSet; + } + + /** + * Update a JavaSourceSet to reflect a newly added dependency. + * Tries each repository in order until the JAR is successfully downloaded. + */ + public JavaSourceSet addDependency(JavaSourceSet sourceSet, + String groupId, String artifactId, String version, + List repositories) { + String key = groupId + ":" + artifactId + ":" + version; + if (sourceSet.getGavToTypes().containsKey(key)) { + return sourceSet; + } + ResolvedGroupArtifactVersion gav = new ResolvedGroupArtifactVersion( + null, groupId, artifactId, version, null); + Dependency requested = Dependency.builder() + .gav(new GroupArtifactVersion(groupId, artifactId, version)) + .build(); + for (MavenRepository repo : repositories) { + ResolvedDependency dep = ResolvedDependency.builder() + .gav(gav) + .repository(repo) + .requested(requested) + .build(); + List newTypes = downloadAndScanTypes(dep); + if (!newTypes.isEmpty()) { + return addTypesForGav(sourceSet, key, newTypes); + } + } + return sourceSet; + } + + /** + * Update a JavaSourceSet to reflect a removed dependency. + */ + public JavaSourceSet removeDependency(JavaSourceSet sourceSet, + ResolvedDependency dep) { + return removeTypesForGav(sourceSet, gavKey(dep)); + } + + /** + * Remove types from a JavaSourceSet whose GAV keys match the given groupId and artifactId patterns. + * Does not require downloading; works entirely from existing gavToTypes data. + */ + public static JavaSourceSet removeTypesMatching(JavaSourceSet sourceSet, + String groupIdPattern, + String artifactIdPattern) { + Map> gavToTypes = sourceSet.getGavToTypes(); + if (gavToTypes.isEmpty()) { + return sourceSet; + } + List keysToRemove = new ArrayList<>(); + for (String key : gavToTypes.keySet()) { + String[] parts = key.split(":"); + if (parts.length >= 2 && + matchesGlob(parts[0], groupIdPattern) && + matchesGlob(parts[1], artifactIdPattern)) { + keysToRemove.add(key); + } + } + if (keysToRemove.isEmpty()) { + return sourceSet; + } + Set typesToRemove = new HashSet<>(); + for (String key : keysToRemove) { + typesToRemove.addAll(gavToTypes.get(key)); + } + List newClasspath = new ArrayList<>(sourceSet.getClasspath().size()); + for (JavaType.FullyQualified type : sourceSet.getClasspath()) { + if (!typesToRemove.contains(type)) { + newClasspath.add(type); + } + } + Map> newGavToTypes = new LinkedHashMap<>(gavToTypes); + for (String key : keysToRemove) { + newGavToTypes.remove(key); + } + return sourceSet.withClasspath(newClasspath).withGavToTypes(newGavToTypes); + } + + private List downloadAndScanTypes(ResolvedDependency dep) { + try { + Path jarPath = downloader.downloadArtifact(dep); + if (jarPath == null) { + return Collections.emptyList(); + } + return JavaSourceSet.typesFromPath(jarPath, null); + } catch (Exception e) { + // Graceful degradation: if download fails, return empty list + return Collections.emptyList(); + } + } + + private JavaSourceSet removeTypesForGav(JavaSourceSet sourceSet, String gavKey) { + Map> gavToTypes = sourceSet.getGavToTypes(); + if (gavToTypes.isEmpty() || !gavToTypes.containsKey(gavKey)) { + return sourceSet; + } + List oldTypes = gavToTypes.get(gavKey); + Set oldTypesSet = new HashSet<>(oldTypes); + + List newClasspath = new ArrayList<>(sourceSet.getClasspath().size()); + for (JavaType.FullyQualified type : sourceSet.getClasspath()) { + if (!oldTypesSet.contains(type)) { + newClasspath.add(type); + } + } + + Map> newGavToTypes = new LinkedHashMap<>(gavToTypes); + newGavToTypes.remove(gavKey); + + return sourceSet.withClasspath(newClasspath).withGavToTypes(newGavToTypes); + } + + private JavaSourceSet addTypesForGav(JavaSourceSet sourceSet, String gavKey, + List types) { + List newClasspath = new ArrayList<>(sourceSet.getClasspath()); + newClasspath.addAll(types); + + Map> newGavToTypes = new LinkedHashMap<>(sourceSet.getGavToTypes()); + newGavToTypes.put(gavKey, types); + + return sourceSet.withClasspath(newClasspath).withGavToTypes(newGavToTypes); + } + + private static String gavKey(ResolvedDependency dep) { + return dep.getGroupId() + ":" + dep.getArtifactId() + ":" + dep.getVersion(); + } +} diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactIdTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactIdTest.java index 5abd19c4516..7f556495c2b 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactIdTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactIdTest.java @@ -20,11 +20,16 @@ import org.openrewrite.DocumentExample; import org.openrewrite.Issue; import org.openrewrite.Validated; +import org.openrewrite.java.marker.JavaSourceSet; +import org.openrewrite.java.tree.JavaType; import org.openrewrite.test.RewriteTest; import org.openrewrite.test.SourceSpec; +import java.util.List; +import java.util.Map; + import static org.assertj.core.api.Assertions.assertThat; -import static org.openrewrite.java.Assertions.mavenProject; +import static org.openrewrite.java.Assertions.*; import static org.openrewrite.maven.Assertions.pomXml; class ChangeDependencyGroupIdAndArtifactIdTest implements RewriteTest { @@ -3539,4 +3544,181 @@ void shouldNotChangeDependencyWithImplicitlyDefinedVersionProperty() { ); } + @Test + void updatesJavaSourceSetMarkerOnJavaFiles() { + rewriteRun( + spec -> spec.recipe(new ChangeDependencyGroupIdAndArtifactId( + "javax.activation", + "javax.activation-api", + "jakarta.activation", + "jakarta.activation-api", + "2.0.1", + null, + false, + false + )), + mavenProject("project", + srcMainJava( + java( + "class A {}", + "class A {}", + s -> s.afterRecipe(cu -> { + JavaSourceSet jss = cu.getMarkers().findFirst(JavaSourceSet.class).orElseThrow(); + Map> gavToTypes = jss.getGavToTypes(); + // Old dependency types should be removed + assertThat(gavToTypes.keySet()) + .noneMatch(k -> k.startsWith("javax.activation:javax.activation-api:")); + // New dependency types should be present + assertThat(gavToTypes.keySet()) + .anyMatch(k -> k.startsWith("jakarta.activation:jakarta.activation-api:")); + // Classpath should contain types from the new dependency + assertThat(jss.getClasspath()) + .extracting(JavaType.FullyQualified::getFullyQualifiedName) + .anyMatch(fqn -> fqn.startsWith("jakarta.activation.")); + }) + ) + ), + pomXml( + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + javax.activation + javax.activation-api + 1.2.0 + + + + """, + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + jakarta.activation + jakarta.activation-api + 2.0.1 + + + + """ + ) + ) + ); + } + + @Test + void javaSourceSetUnchangedWhenModuleDoesNotHaveDependency() { + rewriteRun( + spec -> spec.recipe(new ChangeDependencyGroupIdAndArtifactId( + "javax.activation", + "javax.activation-api", + "jakarta.activation", + "jakarta.activation-api", + "2.0.1", + null, + false, + false + )), + mavenProject("project", + srcMainJava( + java( + "class A {}", + s -> s.afterRecipe(cu -> { + JavaSourceSet jss = cu.getMarkers().findFirst(JavaSourceSet.class).orElseThrow(); + // Module doesn't have the dependency, so no jakarta types should appear + assertThat(jss.getGavToTypes().keySet()) + .noneMatch(k -> k.contains("jakarta.activation")); + }) + ) + ), + pomXml( + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + com.google.guava + guava + 29.0-jre + + + + """ + ) + ) + ); + } + + @Test + void javaSourceSetGracefulWhenDownloadFails() { + rewriteRun( + spec -> spec.recipe(new ChangeDependencyGroupIdAndArtifactId( + "javax.activation", + "javax.activation-api", + "com.doesnotexist", + "doesnotexist", + null, + null, + false, + false + )), + mavenProject("project", + srcMainJava( + java( + "class A {}", + s -> s.afterRecipe(cu -> { + // Should not throw even though the new dependency JAR can't be downloaded + JavaSourceSet jss = cu.getMarkers().findFirst(JavaSourceSet.class).orElseThrow(); + assertThat(jss).isNotNull(); + }) + ) + ), + pomXml( + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + javax.activation + javax.activation-api + 1.2.0 + + + + """, + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + com.doesnotexist + doesnotexist + 1.2.0 + + + + """ + ) + ) + ); + } + } From 9930b9d190def2c1ec447fb435e2e141758f7f4f Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Tue, 7 Apr 2026 09:27:58 -0400 Subject: [PATCH 3/7] Add star import ambiguity detection to ChangeType When ChangeType moves a type into a package covered by a star import, and another star import provides a type with the same simple name, add an explicit import to disambiguate. This mirrors the ambiguity handling already present in ChangePackage. --- .../org/openrewrite/java/ChangeTypeTest.java | 91 +++++++++++++++++++ .../java/org/openrewrite/java/ChangeType.java | 78 ++++++++++++++++ 2 files changed, 169 insertions(+) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java index 1a4467c2876..f7dd6bdb1b9 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java @@ -19,14 +19,20 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.*; +import org.openrewrite.java.marker.JavaSourceSet; import org.openrewrite.java.search.FindTypes; import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.NameTree; import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import org.openrewrite.test.SourceSpec; +import org.openrewrite.test.UncheckedConsumer; + +import java.util.ArrayList; +import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -2596,4 +2602,89 @@ void changeTypeInServiceProviderFileName() { ) ); } + + @Test + void changeTypeAddsExplicitImportWhenStarImportsWouldBeAmbiguous() { + rewriteRun( + spec -> spec.recipe(new ChangeType("a.Ambiguous", "b.Ambiguous", true)) + .beforeRecipe(withSourceTypesOnClasspath()), + java( + """ + package a; + public class Ambiguous {} + """ + ), + java( + """ + package b; + public class Ambiguous {} + """ + ), + java( + """ + package b; + public class Other {} + """ + ), + java( + """ + package c; + public class Ambiguous {} + """ + ), + java( + """ + import a.Ambiguous; + import b.*; + import c.*; + + class Test { + Ambiguous a; + Other o; + } + """, + """ + import b.Ambiguous; + import b.*; + import c.*; + + class Test { + Ambiguous a; + Other o; + } + """ + ) + ); + } + + private static UncheckedConsumer> withSourceTypesOnClasspath() { + return sourceFiles -> { + java.util.List sourceTypes = new ArrayList<>(); + for (SourceFile sf : sourceFiles) { + if (sf instanceof JavaSourceFile) { + for (J.ClassDeclaration classDecl : ((JavaSourceFile) sf).getClasses()) { + JavaType.FullyQualified type = classDecl.getType(); + if (type != null) { + sourceTypes.add(type); + } + } + } + } + for (int i = 0; i < sourceFiles.size(); i++) { + SourceFile sf = sourceFiles.get(i); + Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); + JavaSourceSet ss; + if (maybeSourceSet.isPresent()) { + ss = maybeSourceSet.get(); + java.util.List enriched = new ArrayList<>(ss.getClasspath()); + enriched.addAll(sourceTypes); + ss = ss.withClasspath(enriched); + } else { + ss = new JavaSourceSet(java.util.UUID.randomUUID(), "main", sourceTypes, java.util.Collections.emptyMap()); + } + sourceFiles.set(i, sf.withMarkers( + sf.getMarkers().computeByType(ss, (orig, upd) -> upd))); + } + }; + } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java index 8c7491971e4..38afd091df6 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java @@ -20,6 +20,7 @@ import org.jspecify.annotations.Nullable; import org.openrewrite.*; import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.marker.JavaSourceSet; import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.*; import org.openrewrite.marker.Markers; @@ -276,6 +277,12 @@ private void addImport(JavaType.FullyQualified owningClass) { setCursor(cursor); } })); + + // If the new type is covered by a star import and another star import + // provides a type with the same simple name, add an explicit import + if (fullyQualifiedTarget != null) { + j = maybeAddExplicitImportForAmbiguity((JavaSourceFile) j, fullyQualifiedTarget); + } } return j; @@ -596,6 +603,77 @@ private JavaType.FullyQualified updateNestedType(JavaType.FullyQualified nestedT return JavaType.ShallowClass.build(newNestedFqn); } + /** + * When the new type is provided by a star import and another star import provides + * a type with the same simple name, add an explicit import to disambiguate. + */ + private JavaSourceFile maybeAddExplicitImportForAmbiguity(JavaSourceFile sf, JavaType.FullyQualified newType) { + String newPkg = newType.getPackageName(); + String simpleName = newType.getClassName(); + + // Check if the new type is covered by a star import + boolean coveredByStar = false; + Set otherStarPackages = new LinkedHashSet<>(); + for (J.Import anImport : sf.getImports()) { + if (anImport.isStatic() || !"*".equals(anImport.getQualid().getSimpleName())) { + // Check if there's already an explicit import for this type + if (!anImport.isStatic() && anImport.getTypeName().replace('$', '.').equals(newType.getFullyQualifiedName())) { + return sf; // Already has explicit import, no ambiguity possible + } + continue; + } + if (anImport.getPackageName().equals(newPkg)) { + coveredByStar = true; + } else { + otherStarPackages.add(anImport.getPackageName()); + } + } + + if (!coveredByStar || otherStarPackages.isEmpty()) { + return sf; + } + + // Check if any other star-imported package has a type with the same simple name + Optional sourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); + if (!sourceSet.isPresent()) { + return sf; + } + boolean ambiguous = false; + for (JavaType.FullyQualified fq : sourceSet.get().getClasspath()) { + if (fq.getClassName().equals(simpleName) && otherStarPackages.contains(fq.getPackageName())) { + ambiguous = true; + break; + } + } + + if (!ambiguous) { + return sf; + } + + // Add an explicit import to resolve the ambiguity + J.Import explicitImport = new J.Import(Tree.randomId(), + Space.EMPTY, + Markers.EMPTY, + new JLeftPadded<>(Space.EMPTY, false, Markers.EMPTY), + TypeTree.build(newType.getFullyQualifiedName()).withPrefix(Space.SINGLE_SPACE), + null); + + List> imports = new ArrayList<>(sf.getPadding().getImports()); + // Insert the explicit import right before the star import for its package + for (int i = 0; i < imports.size(); i++) { + J.Import imp = imports.get(i).getElement(); + if (!imp.isStatic() && "*".equals(imp.getQualid().getSimpleName()) && imp.getPackageName().equals(newPkg)) { + // Give the explicit import the star import's prefix (which has the leading newline) + JRightPadded padded = JRightPadded.build(explicitImport.withPrefix(imp.getPrefix())); + // Give the star import a fresh newline prefix + imports.set(i, imports.get(i).map(starImp -> starImp.withPrefix(Space.format("\n")))); + imports.add(i, padded); + break; + } + } + return sf.getPadding().withImports(imports); + } + private boolean hasNoConflictingImport(@Nullable JavaSourceFile sf) { JavaType.FullyQualified oldType = TypeUtils.asFullyQualified(originalType); JavaType.FullyQualified newType = TypeUtils.asFullyQualified(targetType); From edf0c1598565258a6d5c74ee5f1d6dae331def1b Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Tue, 7 Apr 2026 16:41:50 -0400 Subject: [PATCH 4/7] Extract withSourceTypesOnClasspath to shared Assertions helper, add integration test Move the withSourceTypesOnClasspath test helper from private methods in ChangePackageTest and ChangeTypeTest to a public static method in Assertions.java so it can be reused across test classes. Add an end-to-end integration test in ChangeDependencyGroupIdAndArtifactIdTest that composes ChangeDependencyGroupIdAndArtifactId with ChangePackage to verify that import renaming works correctly when the JavaSourceSet is updated by the dependency change recipe. --- .../openrewrite/java/ChangePackageTest.java | 42 +--------- .../org/openrewrite/java/ChangeTypeTest.java | 37 +-------- .../java/org/openrewrite/java/Assertions.java | 37 +++++++++ ...ngeDependencyGroupIdAndArtifactIdTest.java | 83 +++++++++++++++++++ 4 files changed, 122 insertions(+), 77 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java index d853ca81315..439a492bd6a 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangePackageTest.java @@ -20,24 +20,18 @@ import org.openrewrite.DocumentExample; import org.openrewrite.InMemoryExecutionContext; import org.openrewrite.Issue; -import org.openrewrite.SourceFile; -import org.openrewrite.java.marker.JavaSourceSet; import org.openrewrite.java.search.FindTypes; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaType; -import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.java.tree.NameTree; import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import org.openrewrite.test.SourceSpec; -import org.openrewrite.test.UncheckedConsumer; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; import java.util.List; -import java.util.Optional; import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; @@ -45,6 +39,7 @@ import static org.openrewrite.java.Assertions.addTypesToSourceSet; import static org.openrewrite.java.Assertions.java; import static org.openrewrite.java.Assertions.srcMainJava; +import static org.openrewrite.java.Assertions.withSourceTypesOnClasspath; import static org.openrewrite.properties.Assertions.properties; import static org.openrewrite.test.SourceSpecs.text; import static org.openrewrite.xml.Assertions.xml; @@ -2158,39 +2153,4 @@ void changePackageInServiceProviderFileContentOnly() { ); } - /** - * Enrich each source file's JavaSourceSet marker with types declared in other source files, - * so that classpath-based ambiguity detection works in tests where types come from source - * files rather than JARs. - */ - private static UncheckedConsumer> withSourceTypesOnClasspath() { - return sourceFiles -> { - List sourceTypes = new ArrayList<>(); - for (SourceFile sf : sourceFiles) { - if (sf instanceof JavaSourceFile) { - for (J.ClassDeclaration classDecl : ((JavaSourceFile) sf).getClasses()) { - JavaType.FullyQualified type = classDecl.getType(); - if (type != null) { - sourceTypes.add(type); - } - } - } - } - for (int i = 0; i < sourceFiles.size(); i++) { - SourceFile sf = sourceFiles.get(i); - Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); - JavaSourceSet ss; - if (maybeSourceSet.isPresent()) { - ss = maybeSourceSet.get(); - List enriched = new ArrayList<>(ss.getClasspath()); - enriched.addAll(sourceTypes); - ss = ss.withClasspath(enriched); - } else { - ss = new JavaSourceSet(java.util.UUID.randomUUID(), "main", sourceTypes, java.util.Collections.emptyMap()); - } - sourceFiles.set(i, sf.withMarkers( - sf.getMarkers().computeByType(ss, (orig, upd) -> upd))); - } - }; - } } diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java index f7dd6bdb1b9..41d4c580f9e 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/ChangeTypeTest.java @@ -19,24 +19,19 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.openrewrite.*; -import org.openrewrite.java.marker.JavaSourceSet; import org.openrewrite.java.search.FindTypes; import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.java.tree.JavaType; import org.openrewrite.java.tree.NameTree; import org.openrewrite.java.tree.TypeUtils; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; import org.openrewrite.test.SourceSpec; -import org.openrewrite.test.UncheckedConsumer; - -import java.util.ArrayList; -import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.openrewrite.java.Assertions.java; +import static org.openrewrite.java.Assertions.withSourceTypesOnClasspath; import static org.openrewrite.properties.Assertions.properties; import static org.openrewrite.test.SourceSpecs.text; import static org.openrewrite.xml.Assertions.xml; @@ -2657,34 +2652,4 @@ class Test { ); } - private static UncheckedConsumer> withSourceTypesOnClasspath() { - return sourceFiles -> { - java.util.List sourceTypes = new ArrayList<>(); - for (SourceFile sf : sourceFiles) { - if (sf instanceof JavaSourceFile) { - for (J.ClassDeclaration classDecl : ((JavaSourceFile) sf).getClasses()) { - JavaType.FullyQualified type = classDecl.getType(); - if (type != null) { - sourceTypes.add(type); - } - } - } - } - for (int i = 0; i < sourceFiles.size(); i++) { - SourceFile sf = sourceFiles.get(i); - Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); - JavaSourceSet ss; - if (maybeSourceSet.isPresent()) { - ss = maybeSourceSet.get(); - java.util.List enriched = new ArrayList<>(ss.getClasspath()); - enriched.addAll(sourceTypes); - ss = ss.withClasspath(enriched); - } else { - ss = new JavaSourceSet(java.util.UUID.randomUUID(), "main", sourceTypes, java.util.Collections.emptyMap()); - } - sourceFiles.set(i, sf.withMarkers( - sf.getMarkers().computeByType(ss, (orig, upd) -> upd))); - } - }; - } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java b/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java index 1e4ba58c09a..13845282b72 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/Assertions.java @@ -28,6 +28,7 @@ import org.openrewrite.java.search.FindMissingTypes; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; +import org.openrewrite.java.tree.JavaType; import org.openrewrite.test.SourceSpec; import org.openrewrite.test.SourceSpecs; import org.openrewrite.test.TypeValidation; @@ -265,6 +266,42 @@ public static UncheckedConsumer> addTypesToSourceSet(String sou return addTypesToSourceSet(sourceSetName, emptyList(), emptyList()); } + /** + * Enrich each source file's JavaSourceSet marker with types declared in other source files, + * so that classpath-based ambiguity detection works in tests where types come from source + * files rather than JARs. + */ + public static UncheckedConsumer> withSourceTypesOnClasspath() { + return sourceFiles -> { + List sourceTypes = new ArrayList<>(); + for (SourceFile sf : sourceFiles) { + if (sf instanceof JavaSourceFile) { + for (J.ClassDeclaration classDecl : ((JavaSourceFile) sf).getClasses()) { + JavaType.FullyQualified type = classDecl.getType(); + if (type != null) { + sourceTypes.add(type); + } + } + } + } + for (int i = 0; i < sourceFiles.size(); i++) { + SourceFile sf = sourceFiles.get(i); + Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); + JavaSourceSet ss; + if (maybeSourceSet.isPresent()) { + ss = maybeSourceSet.get(); + List enriched = new ArrayList<>(ss.getClasspath()); + enriched.addAll(sourceTypes); + ss = ss.withClasspath(enriched); + } else { + ss = new JavaSourceSet(Tree.randomId(), "main", sourceTypes, emptyMap()); + } + sourceFiles.set(i, sf.withMarkers( + sf.getMarkers().computeByType(ss, (orig, upd) -> upd))); + } + }; + } + public static JavaVersion javaVersion(int version) { return javaVersions.computeIfAbsent(version, v -> new JavaVersion(Tree.randomId(), "openjdk", "adoptopenjdk", diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactIdTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactIdTest.java index 7f556495c2b..88821c9cb92 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactIdTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactIdTest.java @@ -20,6 +20,7 @@ import org.openrewrite.DocumentExample; import org.openrewrite.Issue; import org.openrewrite.Validated; +import org.openrewrite.java.ChangePackage; import org.openrewrite.java.marker.JavaSourceSet; import org.openrewrite.java.tree.JavaType; import org.openrewrite.test.RewriteTest; @@ -3721,4 +3722,86 @@ void javaSourceSetGracefulWhenDownloadFails() { ); } + @Test + void composedWithChangePackageUpdatesImports() { + rewriteRun( + spec -> spec.recipes( + new ChangeDependencyGroupIdAndArtifactId( + "javax.activation", + "javax.activation-api", + "jakarta.activation", + "jakarta.activation-api", + "2.0.1", + null, + false, + false + ), + new ChangePackage("javax.activation", "jakarta.activation", true) + ), + mavenProject("project", + srcMainJava( + java( + """ + import javax.activation.DataHandler; + import javax.activation.MimeType; + + class A { + DataHandler handler; + MimeType type; + } + """, + """ + import jakarta.activation.DataHandler; + import jakarta.activation.MimeType; + + class A { + DataHandler handler; + MimeType type; + } + """, + s -> s.afterRecipe(cu -> { + JavaSourceSet jss = cu.getMarkers().findFirst(JavaSourceSet.class).orElseThrow(); + // New dependency types should be present on classpath + assertThat(jss.getClasspath()) + .extracting(JavaType.FullyQualified::getFullyQualifiedName) + .anyMatch(fqn -> fqn.startsWith("jakarta.activation.")); + }) + ) + ), + pomXml( + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + javax.activation + javax.activation-api + 1.2.0 + + + + """, + """ + + 4.0.0 + com.mycompany.app + my-app + 1 + + + jakarta.activation + jakarta.activation-api + 2.0.1 + + + + """ + ) + ) + ); + } + } From 18ffbdafd431bd21096920f7324c464589c32cef Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Wed, 8 Apr 2026 09:34:21 -0400 Subject: [PATCH 5/7] Address review feedback: warning markers, remove G/K guard, cache source set updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Attach Markup.warn to build files when version resolution for JavaSourceSet updates fails, so users know the classpath won't be updated - Remove counter-productive G.CompilationUnit/K.CompilationUnit exclusion in Gradle AddDependency and ChangeDependency — Groovy/Kotlin source files can have JavaSourceSet markers; Gradle build files won't have one and are already filtered by the maybeSourceSet.isPresent() check - Cache computed JavaSourceSet per source set in ChangeDependency (Gradle) and ChangeDependencyGroupIdAndArtifactId (Maven) to avoid redundant per-file recomputation when all files in a source set share identical markers --- .../org/openrewrite/gradle/AddDependency.java | 34 +++++++++++++++---- .../openrewrite/gradle/ChangeDependency.java | 12 +++++-- .../org/openrewrite/maven/AddDependency.java | 11 ++++-- .../ChangeDependencyGroupIdAndArtifactId.java | 7 ++++ .../openrewrite/maven/AddDependencyTest.java | 3 +- 5 files changed, 54 insertions(+), 13 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java index ab1c9419e2f..836b95d919f 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java @@ -22,7 +22,6 @@ import org.openrewrite.gradle.marker.GradleProject; import org.openrewrite.gradle.search.FindJVMTestSuites; import org.openrewrite.gradle.trait.JvmTestSuite; -import org.openrewrite.groovy.tree.G; import org.openrewrite.internal.StringUtils; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.marker.JavaProject; @@ -31,7 +30,7 @@ import org.openrewrite.java.search.UsesType; import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; -import org.openrewrite.kotlin.tree.K; +import org.openrewrite.marker.Markup; import org.openrewrite.maven.MavenDownloadingException; import org.openrewrite.maven.MavenExecutionContextView; import org.openrewrite.maven.internal.MavenPomDownloader; @@ -148,6 +147,8 @@ public static class Scanned { @Nullable String resolvedVersion; List repositories = new ArrayList<>(); + @Nullable + Exception versionResolutionFailure; } @Override @@ -186,7 +187,9 @@ private boolean usesType(SourceFile sourceFile, ExecutionContext ctx) { sourceFile == hasTestSourceSet.visit(sourceFile, ctx)) { return tree; } - sourceFile.getMarkers().findFirst(JavaProject.class).ifPresent(javaProject -> { + Optional maybeJavaProject = sourceFile.getMarkers().findFirst(JavaProject.class); + if (maybeJavaProject.isPresent()) { + JavaProject javaProject = maybeJavaProject.get(); boolean uses = usesType(sourceFile, ctx); acc.usingType.compute(javaProject, (jp, usingType) -> Boolean.TRUE.equals(usingType) || uses); @@ -209,11 +212,11 @@ private boolean usesType(SourceFile sourceFile, ExecutionContext ctx) { acc.repositories = maybeGp.get().getMavenRepositories(); } } catch (MavenDownloadingException e) { - // Version resolution failed + acc.versionResolutionFailure = e; } } } - }); + } return tree; } }; @@ -310,6 +313,24 @@ private boolean isTopLevel(Cursor cursor) { ); if (acc.configurationsByProject.isEmpty() || acc.resolvedVersion == null) { + if (acc.versionResolutionFailure != null) { + Exception failure = acc.versionResolutionFailure; + return new TreeVisitor() { + @Override + public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { + return gradleVisitor.isAcceptable(sourceFile, ctx); + } + + @Override + public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { + Tree result = gradleVisitor.visit(tree, ctx); + if (result != tree) { + result = Markup.warn(result, failure); + } + return result; + } + }; + } return gradleVisitor; } @@ -331,8 +352,7 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { if (gradleVisitor.isAcceptable(sf, ctx)) { return gradleVisitor.visit(tree, ctx); } - if (sf instanceof JavaSourceFile - && !(sf instanceof G.CompilationUnit) && !(sf instanceof K.CompilationUnit)) { + if (sf instanceof JavaSourceFile) { return updateJavaSourceSet(sf, ctx); } return tree; diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java index dd622df7ef7..1e2d1ffd7b3 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java @@ -502,6 +502,7 @@ private GradleProject updateGradleModel(GradleProject gp, ExecutionContext ctx) return new TreeVisitor() { @Nullable private JavaSourceSetUpdater updater; + private final Map updatedSourceSets = new HashMap<>(); @Override public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { @@ -543,9 +544,8 @@ public Properties visitEntry(Properties.Entry entry, ExecutionContext ctx) { } return tree; } - // For non-Gradle Java source files, update JavaSourceSet marker - if (hasModulesWithOldDep && tree instanceof JavaSourceFile && - !(tree instanceof G.CompilationUnit) && !(tree instanceof K.CompilationUnit)) { + // For Java source files, update JavaSourceSet marker + if (hasModulesWithOldDep && tree instanceof JavaSourceFile) { return updateJavaSourceSet((SourceFile) tree, ctx); } return gradleVisitor.visit(tree, ctx); @@ -564,6 +564,11 @@ private SourceFile updateJavaSourceSet(SourceFile sf, ExecutionContext ctx) { if (!maybeSourceSet.isPresent()) { return sf; } + String cacheKey = maybeJp.get().getId().toString() + ":" + maybeSourceSet.get().getName(); + JavaSourceSet cached = updatedSourceSets.get(cacheKey); + if (cached != null) { + return sf.withMarkers(sf.getMarkers().setByType(cached)); + } if (updater == null) { updater = new JavaSourceSetUpdater(ctx); } @@ -577,6 +582,7 @@ private SourceFile updateJavaSourceSet(SourceFile sf, ExecutionContext ctx) { .withRepository(MavenRepository.MAVEN_CENTRAL); JavaSourceSet updated = updater.changeDependency(maybeSourceSet.get(), oldDep, newDep); if (updated != maybeSourceSet.get()) { + updatedSourceSets.put(cacheKey, updated); return sf.withMarkers(sf.getMarkers().setByType(updated)); } return sf; diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java index 4195211cc89..aefdece3604 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java @@ -20,6 +20,7 @@ import lombok.With; import org.jspecify.annotations.Nullable; import org.openrewrite.*; +import org.openrewrite.marker.Markup; import org.openrewrite.java.marker.JavaProject; import org.openrewrite.java.marker.JavaSourceSet; import org.openrewrite.java.search.HasSourceSet; @@ -163,6 +164,8 @@ public static class Scanned { @Nullable String resolvedVersion; List repositories = new ArrayList<>(); + @Nullable + Exception versionResolutionFailure; } @Override @@ -218,7 +221,7 @@ public Tree visit(@Nullable Tree tree, ExecutionContext ctx) { acc.repositories = repos; } } catch (Exception e) { - // Version resolution failed; JavaSourceSet won't be updated + acc.versionResolutionFailure = e; } } } @@ -278,9 +281,13 @@ public Xml visitDocument(Xml.Document document, ExecutionContext ctx) { return maven; } - return new AddDependencyVisitor( + Xml result = new AddDependencyVisitor( groupId, artifactId, version, versionPattern, resolvedScope, releasesOnly, type, classifier, optional, familyPatternCompiled, metadataFailures).visitNonNull(document, ctx); + if (result != document && acc.versionResolutionFailure != null) { + result = Markup.warn(result, acc.versionResolutionFailure); + } + return result; } private boolean isSubprojectOfParentInRepository(Scanned acc) { diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java index f689cd49c09..adfd4ee43d4 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java @@ -331,6 +331,7 @@ public TreeVisitor getVisitor(Accumulator acc) { return new TreeVisitor() { @Nullable private JavaSourceSetUpdater updater; + private final Map updatedSourceSets = new HashMap<>(); @Override public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { @@ -365,6 +366,11 @@ private SourceFile updateJavaSourceSet(SourceFile sf, ExecutionContext ctx) { if (!maybeSourceSet.isPresent()) { return sf; } + String cacheKey = maybeJp.get().getId().toString() + ":" + maybeSourceSet.get().getName(); + JavaSourceSet cached = updatedSourceSets.get(cacheKey); + if (cached != null) { + return sf.withMarkers(sf.getMarkers().setByType(cached)); + } if (updater == null) { updater = new JavaSourceSetUpdater(ctx); } @@ -383,6 +389,7 @@ private SourceFile updateJavaSourceSet(SourceFile sf, ExecutionContext ctx) { .withRepository(findRemoteRepository(maybeJp.get())); JavaSourceSet updated = updater.changeDependency(maybeSourceSet.get(), oldDep, newDep); if (updated != maybeSourceSet.get()) { + updatedSourceSets.put(cacheKey, updated); return sf.withMarkers(sf.getMarkers().setByType(updated)); } return sf; diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/AddDependencyTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/AddDependencyTest.java index ef9278af337..8caca485a4d 100644 --- a/rewrite-maven/src/test/java/org/openrewrite/maven/AddDependencyTest.java +++ b/rewrite-maven/src/test/java/org/openrewrite/maven/AddDependencyTest.java @@ -96,7 +96,8 @@ void dontAddDuplicateIfUpdateModelOnPriorRecipeCycleFailed() { """, """ - + com.mycompany.app my-app 1 From 32bf4cd201b5a0f306d5320b9f643c72fee8f615 Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Wed, 8 Apr 2026 16:08:33 -0400 Subject: [PATCH 6/7] Use project repositories instead of hardcoded Maven Central in Gradle ChangeDependency Capture Maven repositories from GradleProject in the scanner and use them when downloading JARs for JavaSourceSet updates, falling back to Maven Central only if no remote repository is available. This matches the approach already used in Maven ChangeDependencyGroupIdAndArtifactId and both AddDependency recipes. --- .../openrewrite/gradle/ChangeDependency.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java index 1e2d1ffd7b3..f2a984e2f2d 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java @@ -175,6 +175,7 @@ public static class Accumulator { Map> versionVariableUsages = new HashMap<>(); Set failedResolutions = new HashSet<>(); Map modulesWithOldDependency = new HashMap<>(); + Map> moduleRepositories = new HashMap<>(); } @Override @@ -210,6 +211,7 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { if (StringUtils.matchesGlob(resolved.getGroupId(), oldGroupId) && StringUtils.matchesGlob(resolved.getArtifactId(), oldArtifactId)) { acc.modulesWithOldDependency.put(maybeJp.get(), resolved); + acc.moduleRepositories.put(maybeJp.get(), gradleProject.getMavenRepositories()); break outer; } } @@ -579,7 +581,7 @@ private SourceFile updateJavaSourceSet(SourceFile sf, ExecutionContext ctx) { effectiveNewGroupId, effectiveNewArtifactId, oldDep.getVersion(), null); ResolvedDependency newDep = oldDep .withGav(newGav) - .withRepository(MavenRepository.MAVEN_CENTRAL); + .withRepository(findRemoteRepository(maybeJp.get())); JavaSourceSet updated = updater.changeDependency(maybeSourceSet.get(), oldDep, newDep); if (updated != maybeSourceSet.get()) { updatedSourceSets.put(cacheKey, updated); @@ -587,6 +589,19 @@ private SourceFile updateJavaSourceSet(SourceFile sf, ExecutionContext ctx) { } return sf; } + + private MavenRepository findRemoteRepository(JavaProject jp) { + List repos = acc.moduleRepositories.get(jp); + if (repos != null) { + for (MavenRepository repo : repos) { + String uri = repo.getUri(); + if (uri != null && (uri.startsWith("http://") || uri.startsWith("https://"))) { + return repo; + } + } + } + return MavenRepository.MAVEN_CENTRAL; + } }; } From a5abf6235363a5d4e86c9514dee022cc90630d43 Mon Sep 17 00:00:00 2001 From: Steve Elliott Date: Thu, 9 Apr 2026 18:34:31 -0400 Subject: [PATCH 7/7] Move JavaSourceSet updating logic into JavaSourceSet, simplify recipes --- .../org/openrewrite/gradle/AddDependency.java | 13 +- .../openrewrite/gradle/ChangeDependency.java | 35 ++--- .../openrewrite/gradle/RemoveDependency.java | 11 +- .../java/marker/JavaSourceSet.java | 133 ++++++++++++++++++ .../org/openrewrite/maven/AddDependency.java | 13 +- .../ChangeDependencyGroupIdAndArtifactId.java | 42 ++---- .../openrewrite/maven/RemoveDependency.java | 13 +- .../maven/utilities/JavaSourceSetUpdater.java | 89 +----------- 8 files changed, 172 insertions(+), 177 deletions(-) diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java index 836b95d919f..29f2c2f9e46 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/AddDependency.java @@ -363,19 +363,12 @@ private SourceFile updateJavaSourceSet(SourceFile sf, ExecutionContext ctx) { if (!maybeJp.isPresent() || !acc.configurationsByProject.containsKey(maybeJp.get())) { return sf; } - Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); - if (!maybeSourceSet.isPresent() || maybeSourceSet.get().getGavToTypes().isEmpty()) { - return sf; - } if (updater == null) { updater = new JavaSourceSetUpdater(ctx); } - JavaSourceSet updated = updater.addDependency(maybeSourceSet.get(), - groupId, artifactId, acc.resolvedVersion, acc.repositories); - if (updated != maybeSourceSet.get()) { - return sf.withMarkers(sf.getMarkers().setByType(updated)); - } - return sf; + return JavaSourceSet.updateOnSourceFile(sf, sourceSet -> + sourceSet.getGavToTypes().isEmpty() ? sourceSet : + updater.addDependency(sourceSet, groupId, artifactId, acc.resolvedVersion, acc.repositories)); } }; } diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java index f2a984e2f2d..1471225e1e7 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/ChangeDependency.java @@ -562,32 +562,21 @@ private SourceFile updateJavaSourceSet(SourceFile sf, ExecutionContext ctx) { if (oldDep == null) { return sf; } - Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); - if (!maybeSourceSet.isPresent()) { - return sf; - } - String cacheKey = maybeJp.get().getId().toString() + ":" + maybeSourceSet.get().getName(); - JavaSourceSet cached = updatedSourceSets.get(cacheKey); - if (cached != null) { - return sf.withMarkers(sf.getMarkers().setByType(cached)); - } if (updater == null) { updater = new JavaSourceSetUpdater(ctx); } - String effectiveNewGroupId = newGroupId != null ? newGroupId : oldDep.getGroupId(); - String effectiveNewArtifactId = newArtifactId != null ? newArtifactId : oldDep.getArtifactId(); - ResolvedGroupArtifactVersion newGav = new ResolvedGroupArtifactVersion( - oldDep.getGav().getRepository(), - effectiveNewGroupId, effectiveNewArtifactId, oldDep.getVersion(), null); - ResolvedDependency newDep = oldDep - .withGav(newGav) - .withRepository(findRemoteRepository(maybeJp.get())); - JavaSourceSet updated = updater.changeDependency(maybeSourceSet.get(), oldDep, newDep); - if (updated != maybeSourceSet.get()) { - updatedSourceSets.put(cacheKey, updated); - return sf.withMarkers(sf.getMarkers().setByType(updated)); - } - return sf; + JavaProject jp = maybeJp.get(); + return JavaSourceSet.updateOnSourceFile(sf, updatedSourceSets, sourceSet -> { + String effectiveNewGroupId = newGroupId != null ? newGroupId : oldDep.getGroupId(); + String effectiveNewArtifactId = newArtifactId != null ? newArtifactId : oldDep.getArtifactId(); + ResolvedGroupArtifactVersion newGav = new ResolvedGroupArtifactVersion( + oldDep.getGav().getRepository(), + effectiveNewGroupId, effectiveNewArtifactId, oldDep.getVersion(), null); + ResolvedDependency newDep = oldDep + .withGav(newGav) + .withRepository(findRemoteRepository(jp)); + return updater.changeDependency(sourceSet, oldDep, newDep); + }); } private MavenRepository findRemoteRepository(JavaProject jp) { diff --git a/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveDependency.java b/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveDependency.java index 34dc9084ef1..2e8634d514a 100644 --- a/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveDependency.java +++ b/rewrite-gradle/src/main/java/org/openrewrite/gradle/RemoveDependency.java @@ -30,7 +30,6 @@ import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.kotlin.tree.K; -import org.openrewrite.maven.utilities.JavaSourceSetUpdater; import org.openrewrite.semver.DependencyMatcher; import java.util.HashMap; @@ -176,14 +175,8 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { return gradleVisitor.visit(tree, ctx); } if (sf instanceof JavaSourceFile) { - Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); - if (maybeSourceSet.isPresent()) { - JavaSourceSet updated = JavaSourceSetUpdater.removeTypesMatching( - maybeSourceSet.get(), groupId, artifactId); - if (updated != maybeSourceSet.get()) { - return sf.withMarkers(sf.getMarkers().setByType(updated)); - } - } + return JavaSourceSet.updateOnSourceFile(sf, + sourceSet -> sourceSet.removeTypesMatching(groupId, artifactId)); } return tree; } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/marker/JavaSourceSet.java b/rewrite-java/src/main/java/org/openrewrite/java/marker/JavaSourceSet.java index 8dd18db919f..f464c88c567 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/marker/JavaSourceSet.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/marker/JavaSourceSet.java @@ -24,6 +24,7 @@ import lombok.With; import org.jspecify.annotations.Nullable; import org.openrewrite.PathUtils; +import org.openrewrite.SourceFile; import org.openrewrite.java.internal.JavaTypeCache; import org.openrewrite.java.tree.JavaType; import org.openrewrite.marker.SourceSet; @@ -32,11 +33,13 @@ import java.net.URI; import java.nio.file.*; import java.util.*; +import java.util.function.Function; import java.util.jar.JarEntry; import java.util.jar.JarFile; import static java.util.Collections.emptyMap; import static org.openrewrite.Tree.randomId; +import static org.openrewrite.internal.StringUtils.matchesGlob; @Value @EqualsAndHashCode(onlyExplicitlyIncluded = true) @@ -55,6 +58,136 @@ public class JavaSourceSet implements SourceSet { */ Map> gavToTypes; + /** + * Add types for the given GAV key to this source set's classpath and gavToTypes mapping. + * + * @param gavKey a "group:artifact:version" string + * @param types the types provided by the artifact + * @return a new JavaSourceSet with the types added + */ + public JavaSourceSet addTypesForGav(String gavKey, List types) { + List newClasspath = new ArrayList<>(classpath); + newClasspath.addAll(types); + + Map> newGavToTypes = new LinkedHashMap<>(gavToTypes); + newGavToTypes.put(gavKey, types); + + return withClasspath(newClasspath).withGavToTypes(newGavToTypes); + } + + /** + * Remove all types associated with the given GAV key from this source set's classpath and gavToTypes mapping. + * + * @param gavKey a "group:artifact:version" string + * @return a new JavaSourceSet with the types removed, or this instance if the key is not present + */ + public JavaSourceSet removeTypesForGav(String gavKey) { + if (gavToTypes.isEmpty() || !gavToTypes.containsKey(gavKey)) { + return this; + } + Set oldTypesSet = new HashSet<>(gavToTypes.get(gavKey)); + + List newClasspath = new ArrayList<>(classpath.size()); + for (JavaType.FullyQualified type : classpath) { + if (!oldTypesSet.contains(type)) { + newClasspath.add(type); + } + } + + Map> newGavToTypes = new LinkedHashMap<>(gavToTypes); + newGavToTypes.remove(gavKey); + + return withClasspath(newClasspath).withGavToTypes(newGavToTypes); + } + + /** + * Remove types from this source set whose GAV keys match the given groupId and artifactId glob patterns. + * + * @param groupIdPattern glob pattern for groupId matching + * @param artifactIdPattern glob pattern for artifactId matching + * @return a new JavaSourceSet with matching types removed, or this instance if no keys match + */ + public JavaSourceSet removeTypesMatching(String groupIdPattern, String artifactIdPattern) { + if (gavToTypes.isEmpty()) { + return this; + } + List keysToRemove = new ArrayList<>(); + for (String key : gavToTypes.keySet()) { + String[] parts = key.split(":"); + if (parts.length >= 2 && + matchesGlob(parts[0], groupIdPattern) && + matchesGlob(parts[1], artifactIdPattern)) { + keysToRemove.add(key); + } + } + if (keysToRemove.isEmpty()) { + return this; + } + Set typesToRemove = new HashSet<>(); + for (String key : keysToRemove) { + typesToRemove.addAll(gavToTypes.get(key)); + } + List newClasspath = new ArrayList<>(classpath.size()); + for (JavaType.FullyQualified type : classpath) { + if (!typesToRemove.contains(type)) { + newClasspath.add(type); + } + } + Map> newGavToTypes = new LinkedHashMap<>(gavToTypes); + for (String key : keysToRemove) { + newGavToTypes.remove(key); + } + return withClasspath(newClasspath).withGavToTypes(newGavToTypes); + } + + /** + * Apply a transformation to the {@link JavaSourceSet} marker on a source file and replace it if changed. + * + * @param sf the source file to update + * @param transform a function that takes the current JavaSourceSet and returns an updated one + * @return the source file with the updated marker, or unchanged if no JavaSourceSet is present or the transform is a no-op + */ + public static SourceFile updateOnSourceFile(SourceFile sf, Function transform) { + Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); + if (!maybeSourceSet.isPresent()) { + return sf; + } + JavaSourceSet updated = transform.apply(maybeSourceSet.get()); + if (updated != maybeSourceSet.get()) { + return sf.withMarkers(sf.getMarkers().setByType(updated)); + } + return sf; + } + + /** + * Apply a transformation to the {@link JavaSourceSet} marker on a source file, using a cache keyed by + * {@link JavaProject} ID and source set name to avoid redundant recomputation across files in the same source set. + * + * @param sf the source file to update + * @param cache a mutable map used to cache updated JavaSourceSets across calls + * @param transform a function that takes the current JavaSourceSet and returns an updated one + * @return the source file with the updated marker, or unchanged if no JavaSourceSet/JavaProject is present + */ + public static SourceFile updateOnSourceFile(SourceFile sf, Map cache, + Function transform) { + Optional maybeJp = sf.getMarkers().findFirst(JavaProject.class); + Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); + if (!maybeJp.isPresent() || !maybeSourceSet.isPresent()) { + return sf; + } + String cacheKey = maybeJp.get().getId().toString() + ":" + maybeSourceSet.get().getName(); + JavaSourceSet cached = cache.get(cacheKey); + if (cached != null) { + return sf.withMarkers(sf.getMarkers().setByType(cached)); + } + JavaSourceSet updated = transform.apply(maybeSourceSet.get()); + if (updated != maybeSourceSet.get()) { + cache.put(cacheKey, updated); + return sf.withMarkers(sf.getMarkers().setByType(updated)); + } + return sf; + } + /** * Extract type information from the provided classpath. * Uses ClassGraph to compute the classpath. diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java index aefdece3604..cc77f7ed0e6 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/AddDependency.java @@ -347,19 +347,12 @@ private SourceFile updateJavaSourceSet(SourceFile sf, ExecutionContext ctx) { if (!maybeJp.isPresent() || !acc.scopeByProject.containsKey(maybeJp.get())) { return sf; } - Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); - if (!maybeSourceSet.isPresent() || maybeSourceSet.get().getGavToTypes().isEmpty()) { - return sf; - } if (updater == null) { updater = new JavaSourceSetUpdater(ctx); } - JavaSourceSet updated = updater.addDependency(maybeSourceSet.get(), - groupId, artifactId, acc.resolvedVersion, acc.repositories); - if (updated != maybeSourceSet.get()) { - return sf.withMarkers(sf.getMarkers().setByType(updated)); - } - return sf; + return JavaSourceSet.updateOnSourceFile(sf, sourceSet -> + sourceSet.getGavToTypes().isEmpty() ? sourceSet : + updater.addDependency(sourceSet, groupId, artifactId, acc.resolvedVersion, acc.repositories)); } }; } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java index adfd4ee43d4..7fbb1cb3e1d 100755 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/ChangeDependencyGroupIdAndArtifactId.java @@ -362,37 +362,23 @@ private SourceFile updateJavaSourceSet(SourceFile sf, ExecutionContext ctx) { if (oldDep == null) { return sf; } - Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); - if (!maybeSourceSet.isPresent()) { - return sf; - } - String cacheKey = maybeJp.get().getId().toString() + ":" + maybeSourceSet.get().getName(); - JavaSourceSet cached = updatedSourceSets.get(cacheKey); - if (cached != null) { - return sf.withMarkers(sf.getMarkers().setByType(cached)); - } if (updater == null) { updater = new JavaSourceSetUpdater(ctx); } - // Build a synthetic ResolvedDependency for the new coordinates - String effectiveNewGroupId = newGroupId != null ? newGroupId : oldDep.getGroupId(); - String effectiveNewArtifactId = newArtifactId != null ? newArtifactId : oldDep.getArtifactId(); - // Use the resolved new version if available, otherwise fall back to old version - String resolvedVersion = acc.getResolvedNewVersions().get(maybeJp.get()); - String effectiveVersion = resolvedVersion != null ? resolvedVersion : oldDep.getVersion(); - ResolvedGroupArtifactVersion newGav = new ResolvedGroupArtifactVersion( - oldDep.getGav().getRepository(), - effectiveNewGroupId, effectiveNewArtifactId, effectiveVersion, null); - // Use a remote repository for downloading (local file:// repos won't have the new artifact) - ResolvedDependency newDep = oldDep - .withGav(newGav) - .withRepository(findRemoteRepository(maybeJp.get())); - JavaSourceSet updated = updater.changeDependency(maybeSourceSet.get(), oldDep, newDep); - if (updated != maybeSourceSet.get()) { - updatedSourceSets.put(cacheKey, updated); - return sf.withMarkers(sf.getMarkers().setByType(updated)); - } - return sf; + JavaProject jp = maybeJp.get(); + return JavaSourceSet.updateOnSourceFile(sf, updatedSourceSets, sourceSet -> { + String effectiveNewGroupId = newGroupId != null ? newGroupId : oldDep.getGroupId(); + String effectiveNewArtifactId = newArtifactId != null ? newArtifactId : oldDep.getArtifactId(); + String resolvedVersion = acc.getResolvedNewVersions().get(jp); + String effectiveVersion = resolvedVersion != null ? resolvedVersion : oldDep.getVersion(); + ResolvedGroupArtifactVersion newGav = new ResolvedGroupArtifactVersion( + oldDep.getGav().getRepository(), + effectiveNewGroupId, effectiveNewArtifactId, effectiveVersion, null); + ResolvedDependency newDep = oldDep + .withGav(newGav) + .withRepository(findRemoteRepository(jp)); + return updater.changeDependency(sourceSet, oldDep, newDep); + }); } private MavenRepository findRemoteRepository(JavaProject jp) { diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveDependency.java b/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveDependency.java index 62392d751a6..b4f081673f4 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveDependency.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/RemoveDependency.java @@ -23,12 +23,9 @@ import org.openrewrite.java.tree.JavaSourceFile; import org.openrewrite.maven.tree.ResolvedDependency; import org.openrewrite.maven.tree.Scope; -import org.openrewrite.maven.utilities.JavaSourceSetUpdater; import org.openrewrite.xml.RemoveContentVisitor; import org.openrewrite.xml.tree.Xml; -import java.util.Optional; - @Value @EqualsAndHashCode(callSuper = false) public class RemoveDependency extends Recipe { @@ -96,14 +93,8 @@ public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { return mavenVisitor.visit(tree, ctx); } if (sf instanceof JavaSourceFile) { - Optional maybeSourceSet = sf.getMarkers().findFirst(JavaSourceSet.class); - if (maybeSourceSet.isPresent()) { - JavaSourceSet updated = JavaSourceSetUpdater.removeTypesMatching( - maybeSourceSet.get(), groupId, artifactId); - if (updated != maybeSourceSet.get()) { - return sf.withMarkers(sf.getMarkers().setByType(updated)); - } - } + return JavaSourceSet.updateOnSourceFile(sf, + sourceSet -> sourceSet.removeTypesMatching(groupId, artifactId)); } return tree; } diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/JavaSourceSetUpdater.java b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/JavaSourceSetUpdater.java index e400e4aab7b..e6c076b0a7a 100644 --- a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/JavaSourceSetUpdater.java +++ b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/JavaSourceSetUpdater.java @@ -15,7 +15,6 @@ */ package org.openrewrite.maven.utilities; -import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; import org.openrewrite.HttpSenderExecutionContextView; import org.openrewrite.ipc.http.HttpSender; @@ -30,8 +29,6 @@ import org.openrewrite.maven.tree.ResolvedDependency; import org.openrewrite.maven.tree.ResolvedGroupArtifactVersion; -import static org.openrewrite.internal.StringUtils.matchesGlob; - import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; @@ -80,10 +77,10 @@ public JavaSourceSet changeDependency(JavaSourceSet sourceSet, sourceSet.getGavToTypes().containsKey(newGavKey)) { return sourceSet; } - sourceSet = removeTypesForGav(sourceSet, oldGavKey); + sourceSet = sourceSet.removeTypesForGav(oldGavKey); List newTypes = downloadAndScanTypes(newDep); if (!newTypes.isEmpty()) { - sourceSet = addTypesForGav(sourceSet, newGavKey, newTypes); + sourceSet = sourceSet.addTypesForGav(newGavKey, newTypes); } return sourceSet; } @@ -112,60 +109,12 @@ public JavaSourceSet addDependency(JavaSourceSet sourceSet, .build(); List newTypes = downloadAndScanTypes(dep); if (!newTypes.isEmpty()) { - return addTypesForGav(sourceSet, key, newTypes); + return sourceSet.addTypesForGav(key, newTypes); } } return sourceSet; } - /** - * Update a JavaSourceSet to reflect a removed dependency. - */ - public JavaSourceSet removeDependency(JavaSourceSet sourceSet, - ResolvedDependency dep) { - return removeTypesForGav(sourceSet, gavKey(dep)); - } - - /** - * Remove types from a JavaSourceSet whose GAV keys match the given groupId and artifactId patterns. - * Does not require downloading; works entirely from existing gavToTypes data. - */ - public static JavaSourceSet removeTypesMatching(JavaSourceSet sourceSet, - String groupIdPattern, - String artifactIdPattern) { - Map> gavToTypes = sourceSet.getGavToTypes(); - if (gavToTypes.isEmpty()) { - return sourceSet; - } - List keysToRemove = new ArrayList<>(); - for (String key : gavToTypes.keySet()) { - String[] parts = key.split(":"); - if (parts.length >= 2 && - matchesGlob(parts[0], groupIdPattern) && - matchesGlob(parts[1], artifactIdPattern)) { - keysToRemove.add(key); - } - } - if (keysToRemove.isEmpty()) { - return sourceSet; - } - Set typesToRemove = new HashSet<>(); - for (String key : keysToRemove) { - typesToRemove.addAll(gavToTypes.get(key)); - } - List newClasspath = new ArrayList<>(sourceSet.getClasspath().size()); - for (JavaType.FullyQualified type : sourceSet.getClasspath()) { - if (!typesToRemove.contains(type)) { - newClasspath.add(type); - } - } - Map> newGavToTypes = new LinkedHashMap<>(gavToTypes); - for (String key : keysToRemove) { - newGavToTypes.remove(key); - } - return sourceSet.withClasspath(newClasspath).withGavToTypes(newGavToTypes); - } - private List downloadAndScanTypes(ResolvedDependency dep) { try { Path jarPath = downloader.downloadArtifact(dep); @@ -179,38 +128,6 @@ private List downloadAndScanTypes(ResolvedDependency de } } - private JavaSourceSet removeTypesForGav(JavaSourceSet sourceSet, String gavKey) { - Map> gavToTypes = sourceSet.getGavToTypes(); - if (gavToTypes.isEmpty() || !gavToTypes.containsKey(gavKey)) { - return sourceSet; - } - List oldTypes = gavToTypes.get(gavKey); - Set oldTypesSet = new HashSet<>(oldTypes); - - List newClasspath = new ArrayList<>(sourceSet.getClasspath().size()); - for (JavaType.FullyQualified type : sourceSet.getClasspath()) { - if (!oldTypesSet.contains(type)) { - newClasspath.add(type); - } - } - - Map> newGavToTypes = new LinkedHashMap<>(gavToTypes); - newGavToTypes.remove(gavKey); - - return sourceSet.withClasspath(newClasspath).withGavToTypes(newGavToTypes); - } - - private JavaSourceSet addTypesForGav(JavaSourceSet sourceSet, String gavKey, - List types) { - List newClasspath = new ArrayList<>(sourceSet.getClasspath()); - newClasspath.addAll(types); - - Map> newGavToTypes = new LinkedHashMap<>(sourceSet.getGavToTypes()); - newGavToTypes.put(gavKey, types); - - return sourceSet.withClasspath(newClasspath).withGavToTypes(newGavToTypes); - } - private static String gavKey(ResolvedDependency dep) { return dep.getGroupId() + ":" + dep.getArtifactId() + ":" + dep.getVersion(); }