Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -259,14 +259,21 @@ public TreeVisitor<?, ExecutionContext> getVisitor(Accumulator acc) {
final VersionComparator versionComparator = newVersion != null ? Semver.validate(newVersion, versionPattern).getValue() : null;
@Nullable
private Collection<String> availableVersions;
private boolean isNewDependencyPresent;
private Set<String> safeVersionPlaceholdersToChange = new HashSet<>();
private final String effectiveNewGroupId = renamedGroupId(oldGroupId);
private final String effectiveNewArtifactId = renamedArtifactId(oldArtifactId);
private final boolean dedupeEnabled = !effectiveNewGroupId.equals(oldGroupId) || !effectiveNewArtifactId.equals(oldArtifactId);
private List<ResolvedDependency> existingNewDirectDependencies = new ArrayList<>();
private List<ResolvedDependency> existingOldDirectDependencies = new ArrayList<>();
private final boolean configuredToOverrideManagedVersion = overrideManagedVersion != null && overrideManagedVersion; // False by default
private final boolean configuredToChangeManagedDependency = changeManagedDependency == null || changeManagedDependency; // True by default

@Override
public Xml visitDocument(Xml.Document document, ExecutionContext ctx) {
isNewDependencyPresent = checkIfNewDependencyPresent(newGroupId, newArtifactId, newVersion);
if (dedupeEnabled) {
existingNewDirectDependencies = directDependencies(effectiveNewGroupId, effectiveNewArtifactId);
existingOldDirectDependencies = directDependencies(oldGroupId, oldArtifactId);
}
safeVersionPlaceholdersToChange = getSafeVersionPlaceholdersToChange(oldGroupId, oldArtifactId, ctx);
if (configuredToChangeManagedDependency) {
doAfterVisit(new ChangeManagedDependencyGroupIdAndArtifactId(
Expand All @@ -277,8 +284,6 @@ public Xml visitDocument(Xml.Document document, ExecutionContext ctx) {
}
// Add sibling exclusions for the new coordinates alongside existing old exclusions
if (newGroupId != null || newArtifactId != null) {
String effectiveNewGroupId = newGroupId != null ? newGroupId : oldGroupId;
String effectiveNewArtifactId = newArtifactId != null ? newArtifactId : oldArtifactId;
doAfterVisit(new MavenVisitor<ExecutionContext>() {
@Override
public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) {
Expand Down Expand Up @@ -333,15 +338,18 @@ public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) {
}

boolean isOldDependencyTag = isDependencyTag(oldGroupId, oldArtifactId);
if (isOldDependencyTag && isNewDependencyPresent) {
boolean isNewDependencyTag = dedupeEnabled && isDependencyTag(effectiveNewGroupId, effectiveNewArtifactId);
if (isOldDependencyTag && !isNewDependencyTag && renamedOldTagWouldDuplicate(t)) {
doAfterVisit(new RemoveContentVisitor<>(tag, true, true));
maybeUpdateModel();
return t;
}
boolean isSurvivingNewDependencyTag = isNewDependencyTag && newVersion != null &&
isSurvivorOfRemovedOldDependency(t);
boolean isPluginDependency = isPluginDependencyTag(oldGroupId, oldArtifactId);
boolean isAnnotationProcessorPath = isAnnotationProcessorPathTag(oldGroupId, oldArtifactId);
boolean deferUpdate = false;
if (isOldDependencyTag || isPluginDependency || isAnnotationProcessorPath) {
if (isOldDependencyTag || isPluginDependency || isAnnotationProcessorPath || isSurvivingNewDependencyTag) {
if (newVersion != null) {
String currentVersionValue = t.getChildValue("version").orElse(null);
if (isImplicitlyDefinedVersionProperty(currentVersionValue)) {
Expand Down Expand Up @@ -373,7 +381,7 @@ public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) {
boolean versionTagPresent = versionTag.isPresent();
// dependencyManagement does not apply to plugin dependencies or annotation processor paths
boolean oldDependencyDefinedManaged = isOldDependencyTag && canAffectManagedDependency(getResolutionResult(), scope, oldGroupId, oldArtifactId);
boolean newDependencyManaged = isOldDependencyTag && isDependencyManaged(scope, groupId, artifactId);
boolean newDependencyManaged = (isOldDependencyTag || isSurvivingNewDependencyTag) && isDependencyManaged(scope, groupId, artifactId);
if (versionTagPresent) {
// If the previous dependency had a version but the new artifact is managed, removed the version tag.
if (!configuredToOverrideManagedVersion && newDependencyManaged || (oldDependencyDefinedManaged && configuredToChangeManagedDependency)) {
Expand Down Expand Up @@ -412,15 +420,58 @@ public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) {
return t;
}

private boolean checkIfNewDependencyPresent(@Nullable String groupId, @Nullable String artifactId, @Nullable String version) {
if ((groupId == null) || (artifactId == null)) {
return false;
private List<ResolvedDependency> directDependencies(String groupId, String artifactId) {
List<ResolvedDependency> direct = new ArrayList<>();
for (ResolvedDependency rd : findDependencies(groupId, artifactId)) {
if (rd.isDirect()) {
direct.add(rd);
}
}
return direct;
}

private boolean renamedOldTagWouldDuplicate(Xml.Tag oldTag) {
String groupId = renamedGroupId(oldTag.getChildValue("groupId").orElse(null));
String artifactId = renamedArtifactId(oldTag.getChildValue("artifactId").orElse(null));
for (ResolvedDependency rd : existingNewDirectDependencies) {
if (Objects.equals(groupId, rd.getGroupId()) &&
Objects.equals(artifactId, rd.getArtifactId()) &&
sameClassifierTypeScope(oldTag, rd)) {
return true;
}
}
return false;
}

private boolean isSurvivorOfRemovedOldDependency(Xml.Tag newTag) {
String newTagGroupId = newTag.getChildValue("groupId").orElse(null);
String newTagArtifactId = newTag.getChildValue("artifactId").orElse(null);
for (ResolvedDependency rd : existingOldDirectDependencies) {
if (Objects.equals(newTagGroupId, renamedGroupId(rd.getGroupId())) &&
Objects.equals(newTagArtifactId, renamedArtifactId(rd.getArtifactId())) &&
sameClassifierTypeScope(newTag, rd)) {
return true;
}
}
List<ResolvedDependency> dependencies = findDependencies(groupId, artifactId);
return dependencies.stream()
.filter(ResolvedDependency::isDirect)
.anyMatch(rd -> version == null ||
versionComparator != null && versionComparator.compare(null, version, rd.getVersion()) <= 0);
return false;
}

private boolean sameClassifierTypeScope(Xml.Tag tag, ResolvedDependency rd) {
return Objects.equals(emptyToNull(tag.getChildValue("classifier").orElse(null)), emptyToNull(rd.getClassifier())) &&
tag.getChildValue("type").orElse("jar").equals(rd.getType()) &&
Scope.fromName(tag.getChildValue("scope").orElse(null)) == Scope.fromName(rd.getRequested().getScope());
}

private @Nullable String renamedGroupId(@Nullable String fallback) {
return newGroupId != null ? newGroupId : fallback;
}

private @Nullable String renamedArtifactId(@Nullable String fallback) {
return newArtifactId != null ? newArtifactId : fallback;
}

private @Nullable String emptyToNull(@Nullable String value) {
return isBlank(value) ? null : value;
}

private boolean isDependencyManaged(Scope scope, String groupId, String artifactId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ void shouldNotAddNewIfDependencyAlreadyExists2() {

@Issue("https://github.com/openrewrite/rewrite/issues/4514")
@Test
void shouldAddNewIfDependencyAlreadyExistsInOlderVersion() {
void shouldUpgradeExistingInPlaceIfDependencyAlreadyExistsInOlderVersion() {
rewriteRun(
spec -> spec.recipe(new ChangeDependencyGroupIdAndArtifactId(
"javax.activation",
Expand Down Expand Up @@ -321,10 +321,166 @@ void shouldAddNewIfDependencyAlreadyExistsInOlderVersion() {
<artifactId>jakarta.activation-api</artifactId>
<version>1.2.2</version>
</dependency>
</dependencies>
</project>
"""
)
);
}

@Issue("https://github.com/openrewrite/rewrite-migrate-java/pull/1153")
@Test
void shouldNotLeaveDuplicateWhenExistingNewDependencyIsAtLowerVersion() {
rewriteRun(
spec -> spec.recipe(new ChangeDependencyGroupIdAndArtifactId(
"jakarta.jws",
"jakarta.jws-api",
"jakarta.xml.ws",
"jakarta.xml.ws-api",
"4.0.0",
null
)),
pomXml(
"""
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>jakarta.jws</groupId>
<artifactId>jakarta.jws-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>jakarta.xml.ws</groupId>
<artifactId>jakarta.xml.ws-api</artifactId>
<version>3.0.1</version>
</dependency>
</dependencies>
</project>
""",
"""
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>jakarta.xml.ws</groupId>
<artifactId>jakarta.xml.ws-api</artifactId>
<version>4.0.0</version>
</dependency>
</dependencies>
</project>
"""
)
);
}

@Test
void shouldNotDeduplicateWhenClassifierDiffers() {
rewriteRun(
spec -> spec.recipe(new ChangeDependencyGroupIdAndArtifactId(
"javax.activation",
"javax.activation-api",
"jakarta.activation",
"jakarta.activation-api",
"1.2.2",
null
)),
pomXml(
"""
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1</version>
<dependencies>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>javax.activation-api</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>1.2.1</version>
<classifier>sources</classifier>
</dependency>
</dependencies>
</project>
""",
"""
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1</version>
<dependencies>
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>1.2.1</version>
<classifier>sources</classifier>
</dependency>
</dependencies>
</project>
"""
)
);
}

@Test
void shouldDeduplicateWhenOnlyArtifactIdChanges() {
rewriteRun(
spec -> spec.recipe(new ChangeDependencyGroupIdAndArtifactId(
"org.junit.jupiter",
"junit-jupiter-api",
null,
"junit-jupiter-engine",
"5.9.0",
null
)),
pomXml(
"""
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.8.2</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.2</version>
</dependency>
</dependencies>
</project>
""",
"""
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.9.0</version>
</dependency>
</dependencies>
</project>
Expand Down
Loading