1919import org .jspecify .annotations .Nullable ;
2020import org .openrewrite .ExecutionContext ;
2121import org .openrewrite .Validated ;
22+ import org .openrewrite .marker .Markup ;
2223import org .openrewrite .maven .internal .InsertDependencyComparator ;
2324import org .openrewrite .maven .table .MavenMetadataFailures ;
24- import org .openrewrite .maven .tree .MavenMetadata ;
25- import org .openrewrite .maven .tree .ResolvedDependency ;
26- import org .openrewrite .maven .tree .Scope ;
27- import org .openrewrite .maven .tree .Version ;
25+ import org .openrewrite .maven .tree .*;
2826import org .openrewrite .semver .ExactVersion ;
2927import org .openrewrite .semver .LatestRelease ;
3028import org .openrewrite .semver .Semver ;
3937import java .util .regex .Pattern ;
4038
4139import static java .util .Collections .emptyList ;
40+ import static java .util .Objects .requireNonNull ;
4241
4342@ RequiredArgsConstructor
4443public class AddDependencyVisitor extends MavenIsoVisitor <ExecutionContext > {
@@ -89,38 +88,86 @@ public AddDependencyVisitor(String groupId, String artifactId, String version,
8988
9089 @ Override
9190 public Xml .Tag visitTag (Xml .Tag tag , ExecutionContext executionContext ) {
92- if (isDependencyTag () &&
93- groupId .equals (tag .getChildValue ("groupId" ).orElse (null )) &&
94- artifactId .equals (tag .getChildValue ("artifactId" ).orElse (null )) &&
95- Scope .fromName (scope ) == Scope .fromName (tag .getChildValue ("scope" ).orElse (null ))) {
96- getCursor ().putMessageOnFirstEnclosing (Xml .Document .class , "alreadyHasDependency" , true );
97- return tag ;
91+ if (isDependencyTag ()) {
92+ ResolvedPom resolvedPom = getResolutionResult ().getPom ();
93+ String existingGroupId = resolvedPom .getValue (tag .getChildValue ("groupId" ).orElse (null ));
94+ String existingArtifactId = resolvedPom .getValue (tag .getChildValue ("artifactId" ).orElse (null ));
95+ if (groupId .equals (existingGroupId ) && artifactId .equals (existingArtifactId )) {
96+ Scope requestedScope = Scope .fromName (scope );
97+ Scope existingScope = Scope .fromName (resolvedPom .getValue (tag .getChildValue ("scope" ).orElse (null )));
98+ if (tag .getMarkers ().getMarkers ().stream ()
99+ .anyMatch (m -> m instanceof Markup .Warn &&
100+ ((Markup .Warn ) m ).getDetail ().startsWith ("org.openrewrite.maven.MavenDownloadingException" ))
101+ ) {
102+ getCursor ().putMessageOnFirstEnclosing (Xml .Document .class , "existingDependencyFailure" , true );
103+ } else if (requestedScope != existingScope &&
104+ // Scope reduction
105+ ((existingScope .isInClasspathOf (requestedScope ) &&
106+ Scope .maxPrecedence (existingScope , requestedScope ) == existingScope ) ||
107+ // System / Import / Invalid which we don't support changing to
108+ (!existingScope .isInClasspathOf (requestedScope ) &&
109+ !requestedScope .isInClasspathOf (existingScope ) &&
110+ Scope .Test != requestedScope &&
111+ Scope .maxPrecedence (Scope .Test , requestedScope ) == Scope .Test ))
112+ ) {
113+ getCursor ().putMessageOnFirstEnclosing (Xml .Document .class , "doNotAlterDependency" , true );
114+ } else {
115+ String versionToUse = null ;
116+ String managedVersion = getResolutionResult ().getPom ().getManagedVersion (groupId , artifactId , type , classifier );
117+ if (managedVersion == null || (versionComparator != null && !versionComparator .isValid (version , managedVersion ))) {
118+ versionToUse = tryGetFamilyVersion ();
119+ if (versionToUse == null ) {
120+ try {
121+ versionToUse = findVersionToUse (executionContext );
122+ } catch (MavenDownloadingException e ) {
123+ return e .warn (tag );
124+ }
125+ }
126+ }
127+ if (versionToUse != null ) {
128+ getCursor ().putMessageOnFirstEnclosing (Xml .Document .class , "requestedVersionChange" , true );
129+ getCursor ().putMessageOnFirstEnclosing (Xml .Document .class , "newResolvedVersion" , versionToUse );
130+ }
131+ if (requestedScope != existingScope ) {
132+ getCursor ().putMessageOnFirstEnclosing (Xml .Document .class , "requestedScopeChange" , true );
133+ getCursor ().putMessageOnFirstEnclosing (Xml .Document .class , "oldScope" , existingScope );
134+ }
135+ }
136+ return tag ;
137+ }
98138 }
99139 return super .visitTag (tag , executionContext );
100140 }
101141
102142
103143 @ Override
104144 public Xml .Document visitDocument (Xml .Document document , ExecutionContext executionContext ) {
145+ Validated <VersionComparator > versionValidation = Semver .validate (version , versionPattern );
146+ if (versionValidation .isValid ()) {
147+ versionComparator = versionValidation .getValue ();
148+ }
149+
105150 Xml .Document maven = super .visitDocument (document , executionContext );
106151
107- if (getCursor ().getMessage ("alreadyHasDependency" , false )) {
152+ if (getCursor ().getMessage ("doNotAlterDependency" , false ) ||
153+ getCursor ().getMessage ("existingDependencyFailure" , false )
154+ ) {
108155 return document ;
109156 }
110157
111- Scope resolvedScope = scope == null ? Scope .Compile : Scope .fromName (scope );
158+ boolean requestedVersionChange = getCursor ().getMessage ("requestedVersionChange" , false );
159+ Scope resolvedScope = Scope .fromName (scope );
112160 Map <Scope , List <ResolvedDependency >> dependencies = getResolutionResult ().getDependencies ();
113161 if (dependencies .containsKey (resolvedScope )) {
114162 for (ResolvedDependency d : dependencies .get (resolvedScope )) {
115163 if (d .isDirect () && groupId .equals (d .getGroupId ()) && artifactId .equals (d .getArtifactId ())) {
164+ if (requestedVersionChange ) {
165+ checkVersionUpdate (d .getVersion ());
166+ }
116167 return maven ;
117168 }
118169 }
119- }
120170
121- Validated <VersionComparator > versionValidation = Semver .validate (version , versionPattern );
122- if (versionValidation .isValid ()) {
123- versionComparator = versionValidation .getValue ();
124171 }
125172
126173 Xml .Tag root = maven .getRoot ();
@@ -129,7 +176,43 @@ public Xml.Document visitDocument(Xml.Document document, ExecutionContext execut
129176 new MavenTagInsertionComparator (root .getContent () == null ? emptyList () : root .getContent ())));
130177 }
131178
132- doAfterVisit (new InsertDependencyInOrder (scope ));
179+ boolean isUpdating = false ;
180+ if (getCursor ().getMessage ("requestedScopeChange" , false )) {
181+ isUpdating = true ;
182+ Scope oldScope = getCursor ().getMessage ("oldScope" );
183+ if (dependencies .containsKey (oldScope )) {
184+ for (ResolvedDependency d : dependencies .get (oldScope )) {
185+ if (d .isDirect () && groupId .equals (d .getGroupId ()) && artifactId .equals (d .getArtifactId ())) {
186+ if (requestedVersionChange ) {
187+ checkVersionUpdate (d .getVersion ());
188+ }
189+ doAfterVisit (new ChangeDependencyScope (groupId , artifactId , scope ).getVisitor ());
190+ maybeUpdateModel ();
191+ break ;
192+ }
193+ }
194+ } else { // Going from System / Import / Invalid to something else
195+ ResolvedPom resolvedPom = getResolutionResult ().getPom ();
196+ for (Dependency d : resolvedPom .getRequestedDependencies ()) {
197+ if (groupId .equals (resolvedPom .getValue (d .getGroupId ())) && artifactId .equals (resolvedPom .getValue (d .getArtifactId ()))) {
198+ // This is run in this order because `ChangeDependencyScope` can end up moving a dependency from just requested to an actual dependency
199+ // whereas updating the version relies on something being in the dependencies
200+ doAfterVisit (new ChangeDependencyScope (groupId , artifactId , scope ).getVisitor ());
201+ maybeUpdateModel ();
202+ if (requestedVersionChange && d .getVersion () != null ) {
203+ checkVersionUpdate (requireNonNull (resolvedPom .getValue (d .getVersion ())));
204+ maybeUpdateModel ();
205+ }
206+ break ;
207+ }
208+ }
209+
210+ }
211+ }
212+
213+ if (!isUpdating ) {
214+ doAfterVisit (new InsertDependencyInOrder (scope ));
215+ }
133216
134217 return maven ;
135218 }
@@ -144,21 +227,19 @@ private class InsertDependencyInOrder extends MavenVisitor<ExecutionContext> {
144227 public Xml visitTag (Xml .Tag tag , ExecutionContext ctx ) {
145228 if (DEPENDENCIES_MATCHER .matches (getCursor ())) {
146229 String versionToUse = null ;
147-
148- if (getResolutionResult ().getPom ().getManagedVersion (groupId , artifactId , type , classifier ) == null ) {
149- if (familyRegex != null ) {
150- versionToUse = findDependencies (d -> familyRegex .matcher (d .getGroupId ()).matches ()).stream ()
151- .max (Comparator .comparing (d -> new Version (d .getVersion ())))
152- .map (d -> d .getRequested ().getVersion ())
153- .orElse (null );
154- }
230+ String managedVersion = getResolutionResult ().getPom ().getManagedVersion (groupId , artifactId , type , classifier );
231+ boolean scheduleVersionUpgrade = false ;
232+ if (managedVersion == null ) {
233+ versionToUse = tryGetFamilyVersion ();
155234 if (versionToUse == null ) {
156235 try {
157236 versionToUse = findVersionToUse (ctx );
158237 } catch (MavenDownloadingException e ) {
159238 return e .warn (tag );
160239 }
161240 }
241+ } else if (versionComparator != null && !versionComparator .isValid (version , managedVersion )) {
242+ scheduleVersionUpgrade = true ;
162243 }
163244
164245 Xml .Tag dependencyTag = Xml .Tag .build (
@@ -179,36 +260,56 @@ public Xml visitTag(Xml.Tag tag, ExecutionContext ctx) {
179260
180261 doAfterVisit (new AddToTagVisitor <>(tag , dependencyTag , new InsertDependencyComparator (tag .getContent () == null ? emptyList () : tag .getContent (), dependencyTag )));
181262 maybeUpdateModel ();
263+ if (scheduleVersionUpgrade ) {
264+ doAfterVisit (new UpgradeDependencyVersion (groupId , artifactId , version , versionPattern , true , null ).getVisitor ());
265+ }
182266
183267 return tag ;
184268 }
185269
186270 return super .visitTag (tag , ctx );
187271 }
272+ }
188273
189- private String findVersionToUse (ExecutionContext ctx ) throws MavenDownloadingException {
190- if (resolvedVersion == null ) {
191- if (versionComparator == null || versionComparator instanceof ExactVersion ) {
192- resolvedVersion = version ;
193- } else {
194- MavenMetadata mavenMetadata = metadataFailures == null ?
195- downloadMetadata (groupId , artifactId , ctx ) :
196- metadataFailures .insertRows (ctx , () -> downloadMetadata (groupId , artifactId , ctx ));
197- // TODO This is hacky, but the class structure of LatestRelease is suboptimal, see https://github.com/openrewrite/rewrite/pull/5029
198- // Fix it when we have a chance to refactor the code.
199- if ("LatestRelease" .equals (versionComparator .getClass ().getSimpleName ()) && mavenMetadata .getVersioning ().getRelease () != null ) {
200- return mavenMetadata .getVersioning ().getRelease ();
201- }
202- LatestRelease latest = new LatestRelease (versionPattern );
203- resolvedVersion = mavenMetadata .getVersioning ().getVersions ().stream ()
204- .filter (v -> versionComparator .isValid (null , v ))
205- .filter (v -> !Boolean .TRUE .equals (releasesOnly ) || latest .isValid (null , v ))
206- .max ((v1 , v2 ) -> versionComparator .compare (null , v1 , v2 ))
207- .orElse (version );
274+ private @ Nullable String tryGetFamilyVersion () {
275+ if (familyRegex != null ) {
276+ return findDependencies (d -> familyRegex .matcher (d .getGroupId ()).matches ()).stream ()
277+ .max (Comparator .comparing (d -> new Version (d .getVersion ())))
278+ .map (d -> d .getRequested ().getVersion ())
279+ .orElse (null );
280+ }
281+ return null ;
282+ }
283+
284+ private void checkVersionUpdate ( String existingVersion ) {
285+ String newResolvedVersion = requireNonNull (getCursor ().getMessage ("newResolvedVersion" ));
286+ if (!existingVersion .equals (getResolutionResult ().getPom ().getValue (newResolvedVersion ))) {
287+ doAfterVisit (new UpgradeDependencyVersion (groupId , artifactId , newResolvedVersion , versionPattern , true , null ).getVisitor ());
288+ }
289+ }
290+
291+ private String findVersionToUse (ExecutionContext ctx ) throws MavenDownloadingException {
292+ if (resolvedVersion == null ) {
293+ if (versionComparator == null || versionComparator instanceof ExactVersion ) {
294+ resolvedVersion = version ;
295+ } else {
296+ MavenMetadata mavenMetadata = metadataFailures == null ?
297+ downloadMetadata (groupId , artifactId , ctx ) :
298+ metadataFailures .insertRows (ctx , () -> downloadMetadata (groupId , artifactId , ctx ));
299+ // TODO This is hacky, but the class structure of LatestRelease is suboptimal, see https://github.com/openrewrite/rewrite/pull/5029
300+ // Fix it when we have a chance to refactor the code.
301+ if ("LatestRelease" .equals (versionComparator .getClass ().getSimpleName ()) && mavenMetadata .getVersioning ().getRelease () != null ) {
302+ return mavenMetadata .getVersioning ().getRelease ();
208303 }
304+ LatestRelease latest = new LatestRelease (versionPattern );
305+ resolvedVersion = mavenMetadata .getVersioning ().getVersions ().stream ()
306+ .filter (v -> versionComparator .isValid (null , v ))
307+ .filter (v -> !Boolean .TRUE .equals (releasesOnly ) || latest .isValid (null , v ))
308+ .max ((v1 , v2 ) -> versionComparator .compare (null , v1 , v2 ))
309+ .orElse (version );
209310 }
210-
211- return resolvedVersion ;
212311 }
312+
313+ return resolvedVersion ;
213314 }
214315}
0 commit comments