|
29 | 29 | import org.openrewrite.xml.RemoveContentVisitor; |
30 | 30 | import org.openrewrite.xml.tree.Xml; |
31 | 31 |
|
| 32 | +import java.nio.file.Path; |
32 | 33 | import java.util.*; |
33 | 34 |
|
34 | 35 | import static java.util.Collections.max; |
|
41 | 42 |
|
42 | 43 | @Value |
43 | 44 | @EqualsAndHashCode(callSuper = false) |
44 | | -public class ChangeDependencyGroupIdAndArtifactId extends Recipe { |
| 45 | +public class ChangeDependencyGroupIdAndArtifactId extends ScanningRecipe<ChangeDependencyGroupIdAndArtifactId.Accumulator> { |
| 46 | + @EqualsAndHashCode.Exclude |
45 | 47 | transient MavenMetadataFailures metadataFailures = new MavenMetadataFailures(this); |
46 | 48 |
|
47 | 49 | @Option(displayName = "Old groupId", |
@@ -142,7 +144,93 @@ public Validated<Object> validate() { |
142 | 144 | } |
143 | 145 |
|
144 | 146 | @Override |
145 | | - public TreeVisitor<?, ExecutionContext> getVisitor() { |
| 147 | + public Accumulator getInitialValue(ExecutionContext ctx) { |
| 148 | + return new Accumulator(); |
| 149 | + } |
| 150 | + |
| 151 | + @Override |
| 152 | + public TreeVisitor<?, ExecutionContext> getScanner(Accumulator acc) { |
| 153 | + return new MavenIsoVisitor<ExecutionContext>() { |
| 154 | + final @Nullable VersionComparator versionComparator = newVersion != null ? Semver.validate(newVersion, versionPattern).getValue() : null; |
| 155 | + final boolean configuredToChangeManagedDependency = changeManagedDependency == null || changeManagedDependency; |
| 156 | + |
| 157 | + @Override |
| 158 | + public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { |
| 159 | + if (isDependencyTag(oldGroupId, oldArtifactId) || isPluginDependencyTag(oldGroupId, oldArtifactId) || isAnnotationProcessorPathTag(oldGroupId, oldArtifactId)) { |
| 160 | + if (newVersion != null && versionComparator != null) { |
| 161 | + String currentVersion = tag.getChildValue("version").orElse(null); |
| 162 | + if (currentVersion != null && isProperty(currentVersion)) { |
| 163 | + String propertyName = currentVersion.substring(2, currentVersion.length() - 1); |
| 164 | + if (!getResolutionResult().getPom().getRequested().getProperties().containsKey(propertyName)) { |
| 165 | + // Property is inherited from a parent POM; check if it is safe to change |
| 166 | + Set<String> safeProperties = getSafeVersionPlaceholdersToChange(oldGroupId, oldArtifactId, ctx); |
| 167 | + if (safeProperties.contains(currentVersion)) { |
| 168 | + try { |
| 169 | + String groupId = Optional.ofNullable(newGroupId).orElse(tag.getChildValue("groupId").orElse(oldGroupId)); |
| 170 | + String artifactId = Optional.ofNullable(newArtifactId).orElse(tag.getChildValue("artifactId").orElse(oldArtifactId)); |
| 171 | + String resolvedVersion = resolveSemverVersion(ctx, groupId, artifactId, currentVersion); |
| 172 | + storeParentPomProperty(getResolutionResult().getParent(), propertyName, resolvedVersion, acc); |
| 173 | + } catch (MavenDownloadingException e) { |
| 174 | + return e.warn(tag); |
| 175 | + } |
| 176 | + } |
| 177 | + } |
| 178 | + } |
| 179 | + } |
| 180 | + } |
| 181 | + return super.visitTag(tag, ctx); |
| 182 | + } |
| 183 | + |
| 184 | + private Set<String> getSafeVersionPlaceholdersToChange(String groupId, String artifactId, ExecutionContext ctx) { |
| 185 | + MavenResolutionResult result = getResolutionResult(); |
| 186 | + ResolvedPom resolvedPom = result.getPom(); |
| 187 | + Pom requestedPom = resolvedPom.getRequested(); |
| 188 | + Set<String> relevantProperties = requestedPom.getDependencies().stream() |
| 189 | + .filter(d -> isProperty(d.getVersion()) && |
| 190 | + matchesGlob(resolvedPom.getValue(d.getGroupId()), groupId) && |
| 191 | + matchesGlob(resolvedPom.getValue(d.getArtifactId()), artifactId)) |
| 192 | + .map(Dependency::getVersion) |
| 193 | + .collect(toSet()); |
| 194 | + relevantProperties = filterPropertiesWithOverlapInDependencies(relevantProperties, groupId, artifactId, requestedPom, resolvedPom, configuredToChangeManagedDependency); |
| 195 | + relevantProperties = filterPropertiesWithOverlapInChildren(relevantProperties, groupId, artifactId, result, configuredToChangeManagedDependency); |
| 196 | + return filterPropertiesWithOverlapInParents(relevantProperties, groupId, artifactId, result, configuredToChangeManagedDependency, ctx); |
| 197 | + } |
| 198 | + |
| 199 | + @SuppressWarnings("ConstantConditions") |
| 200 | + private String resolveSemverVersion(ExecutionContext ctx, String groupId, String artifactId, @Nullable String currentVersion) throws MavenDownloadingException { |
| 201 | + if (versionComparator == null) { |
| 202 | + return newVersion; |
| 203 | + } |
| 204 | + String finalCurrentVersion = currentVersion != null ? currentVersion : newVersion; |
| 205 | + List<String> availableVersions = new ArrayList<>(); |
| 206 | + MavenMetadata mavenMetadata = metadataFailures.insertRows(ctx, () -> downloadMetadata(groupId, artifactId, ctx)); |
| 207 | + for (String v : mavenMetadata.getVersioning().getVersions()) { |
| 208 | + if (versionComparator.isValid(finalCurrentVersion, v)) { |
| 209 | + availableVersions.add(v); |
| 210 | + } |
| 211 | + } |
| 212 | + return availableVersions.isEmpty() ? newVersion : max(availableVersions, versionComparator); |
| 213 | + } |
| 214 | + |
| 215 | + private void storeParentPomProperty(@Nullable MavenResolutionResult parent, String propertyName, String newValue, Accumulator acc) { |
| 216 | + if (parent == null) { |
| 217 | + return; |
| 218 | + } |
| 219 | + Pom pom = parent.getPom().getRequested(); |
| 220 | + if (pom.getSourcePath() == null) { |
| 221 | + return; |
| 222 | + } |
| 223 | + if (pom.getProperties().containsKey(propertyName)) { |
| 224 | + acc.getPomProperties().add(new PomProperty(pom.getSourcePath(), propertyName, newValue)); |
| 225 | + return; |
| 226 | + } |
| 227 | + storeParentPomProperty(parent.getParent(), propertyName, newValue, acc); |
| 228 | + } |
| 229 | + }; |
| 230 | + } |
| 231 | + |
| 232 | + @Override |
| 233 | + public TreeVisitor<?, ExecutionContext> getVisitor(Accumulator acc) { |
146 | 234 | return new MavenVisitor<ExecutionContext>() { |
147 | 235 | @Nullable |
148 | 236 | final VersionComparator versionComparator = newVersion != null ? Semver.validate(newVersion, versionPattern).getValue() : null; |
@@ -176,6 +264,22 @@ public Xml visitDocument(Xml.Document document, ExecutionContext ctx) { |
176 | 264 | @Override |
177 | 265 | public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) { |
178 | 266 | Xml.Tag t = (Xml.Tag) super.visitTag(tag, ctx); |
| 267 | + |
| 268 | + // Update version properties in parent POMs based on scanner results |
| 269 | + if (isPropertyTag()) { |
| 270 | + Path pomSourcePath = getResolutionResult().getPom().getRequested().getSourcePath(); |
| 271 | + for (PomProperty prop : acc.getPomProperties()) { |
| 272 | + if (prop.getPomFilePath().equals(pomSourcePath) && prop.getPropertyName().equals(tag.getName())) { |
| 273 | + Optional<String> value = tag.getValue(); |
| 274 | + if (!value.isPresent() || !value.get().equals(prop.getNewValue())) { |
| 275 | + doAfterVisit(new ChangeTagValueVisitor<>(tag, prop.getNewValue())); |
| 276 | + maybeUpdateModel(); |
| 277 | + } |
| 278 | + break; |
| 279 | + } |
| 280 | + } |
| 281 | + } |
| 282 | + |
179 | 283 | boolean isOldDependencyTag = isDependencyTag(oldGroupId, oldArtifactId); |
180 | 284 | if (isOldDependencyTag && isNewDependencyPresent) { |
181 | 285 | doAfterVisit(new RemoveContentVisitor<>(tag, true, true)); |
@@ -321,4 +425,16 @@ private String resolveSemverVersion(ExecutionContext ctx, String groupId, String |
321 | 425 | } |
322 | 426 | }; |
323 | 427 | } |
| 428 | + |
| 429 | + @Value |
| 430 | + public static class Accumulator { |
| 431 | + Set<PomProperty> pomProperties = new HashSet<>(); |
| 432 | + } |
| 433 | + |
| 434 | + @Value |
| 435 | + public static class PomProperty { |
| 436 | + Path pomFilePath; |
| 437 | + String propertyName; |
| 438 | + String newValue; |
| 439 | + } |
324 | 440 | } |
0 commit comments