diff --git a/src/main/java/org/codehaus/mojo/versions/AbstractVersionsUpdaterMojo.java b/src/main/java/org/codehaus/mojo/versions/AbstractVersionsUpdaterMojo.java
index 83121bda2..113f73344 100644
--- a/src/main/java/org/codehaus/mojo/versions/AbstractVersionsUpdaterMojo.java
+++ b/src/main/java/org/codehaus/mojo/versions/AbstractVersionsUpdaterMojo.java
@@ -289,6 +289,27 @@ public void execute()
*/
protected ArtifactVersion findLatestVersion( Artifact artifact, VersionRange versionRange,
Boolean allowingSnapshots, boolean usePluginRepositories )
+ throws ArtifactMetadataRetrievalException, MojoExecutionException
+ {
+ return findLatestVersion( artifact, versionRange, allowingSnapshots, usePluginRepositories, false );
+ }
+
+ /**
+ * Finds the latest version of the specified artifact that matches the version range.
+ *
+ * @param artifact The artifact.
+ * @param versionRange The version range.
+ * @param allowingSnapshots null
for no override, otherwise the local override to apply.
+ * @param usePluginRepositories Use plugin repositories
+ * @return The latest version of the specified artifact that matches the specified version range or
+ * null
if no matching version could be found.
+ * @throws ArtifactMetadataRetrievalException If the artifact metadata could not be found.
+ * @throws MojoExecutionException if something goes wrong.
+ * @since 1.0-alpha-1
+ */
+ protected ArtifactVersion findLatestVersion( Artifact artifact, VersionRange versionRange,
+ Boolean allowingSnapshots, boolean usePluginRepositories,
+ boolean allowDowngrade )
throws ArtifactMetadataRetrievalException, MojoExecutionException
{
boolean includeSnapshots = this.allowSnapshots;
@@ -301,7 +322,8 @@ protected ArtifactVersion findLatestVersion( Artifact artifact, VersionRange ver
includeSnapshots = false;
}
final ArtifactVersions artifactVersions = getHelper().lookupArtifactVersions( artifact, usePluginRepositories );
- return artifactVersions.getNewestVersion( versionRange, includeSnapshots );
+ return artifactVersions.getNewestVersion( versionRange, null, null, includeSnapshots,
+ true, true, allowDowngrade );
}
/**
diff --git a/src/main/java/org/codehaus/mojo/versions/UpdateParentMojo.java b/src/main/java/org/codehaus/mojo/versions/UpdateParentMojo.java
index bfa125301..68893603f 100644
--- a/src/main/java/org/codehaus/mojo/versions/UpdateParentMojo.java
+++ b/src/main/java/org/codehaus/mojo/versions/UpdateParentMojo.java
@@ -65,6 +65,17 @@ public class UpdateParentMojo extends AbstractVersionsUpdaterMojo
@Parameter( property = "forceUpdate", defaultValue = "false" )
protected boolean forceUpdate = false;
+ /**
+ *
Whether to downgrade a snapshot dependency if allowSnapshots
is false
+ * and there exists a version within the range fulfilling the criteria.
+ * Default false
+ *
+ * @since 2.12.0
+ */
+ @Parameter( property = "allowDowngrade",
+ defaultValue = "false" )
+ protected boolean allowDowngrade;
+
// -------------------------- OTHER METHODS --------------------------
/**
@@ -98,33 +109,40 @@ protected void update( ModifiedPomXMLEventReader pom )
version = parentVersion;
}
+ Dependency dependency = new Dependency();
+ dependency.setGroupId( getProject().getParent().getGroupId() );
+ dependency.setArtifactId( getProject().getParent().getArtifactId() );
+ dependency.setVersion( version );
+ dependency.setType( "pom" );
+ Artifact artifact = getHelper().createDependencyArtifact( dependency );
+
VersionRange versionRange;
try
{
versionRange = VersionRange.createFromVersionSpec( version );
+ if ( versionRange.getRecommendedVersion() != null )
+ {
+ versionRange = versionRange.restrict(
+ VersionRange.createFromVersionSpec( "[" + versionRange.getRecommendedVersion() + ",)" ) );
+ }
}
catch ( InvalidVersionSpecificationException e )
{
throw new MojoExecutionException( "Invalid version range specification: " + version, e );
}
- Dependency dependency = new Dependency();
- dependency.setGroupId( getProject().getParent().getGroupId() );
- dependency.setArtifactId( getProject().getParent().getArtifactId() );
- dependency.setVersion( version );
- dependency.setType( "pom" );
- Artifact artifact = getHelper().createDependencyArtifact( dependency );
-
ArtifactVersion artifactVersion;
try
{
- artifactVersion = findLatestVersion( artifact, versionRange, null, false );
+ artifactVersion = findLatestVersion( artifact, versionRange, false, true,
+ allowDowngrade );
}
catch ( ArtifactMetadataRetrievalException e )
{
throw new MojoExecutionException( e.getMessage(), e );
}
+
if ( !shouldApplyUpdate( artifact, currentVersion, artifactVersion, forceUpdate ) )
{
return;
diff --git a/src/main/java/org/codehaus/mojo/versions/api/AbstractVersionDetails.java b/src/main/java/org/codehaus/mojo/versions/api/AbstractVersionDetails.java
index ee2a954df..1cd11640f 100644
--- a/src/main/java/org/codehaus/mojo/versions/api/AbstractVersionDetails.java
+++ b/src/main/java/org/codehaus/mojo/versions/api/AbstractVersionDetails.java
@@ -19,8 +19,11 @@
* under the License.
*/
+import java.util.Arrays;
+import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
+import java.util.stream.Collectors;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.versioning.ArtifactVersion;
@@ -156,11 +159,26 @@ public final ArtifactVersion getNewestVersion( VersionRange versionRange, Artifa
ArtifactVersion upperBound, boolean includeSnapshots,
boolean includeLower, boolean includeUpper )
{
- ArtifactVersion latest = null;
+ return getNewestVersion( versionRange, lowerBound, upperBound, includeSnapshots, includeLower,
+ includeUpper, false );
+ }
+
+ private static Iterable reverse( T[] array )
+ {
+ return Arrays.stream( array ).sorted( Collections.reverseOrder() ).collect( Collectors.toList() );
+ }
+
+ public final ArtifactVersion getNewestVersion( VersionRange versionRange, ArtifactVersion lowerBound,
+ ArtifactVersion upperBound, boolean includeSnapshots,
+ boolean includeLower, boolean includeUpper, boolean allowDowngrade )
+ {
final VersionComparator versionComparator = getVersionComparator();
- for ( ArtifactVersion candidate : getVersions( includeSnapshots ) )
+ // reverse( getVersions( ... ) ) will contain versions sorted from latest to oldest,
+ // so we only need to find the first candidate fulfilling the criteria
+ for ( ArtifactVersion candidate : reverse( getVersions( includeSnapshots ) ) )
{
- if ( versionRange != null && !ArtifactVersions.isVersionInRange( candidate, versionRange ) )
+ if ( !allowDowngrade && versionRange != null
+ && !ArtifactVersions.isVersionInRange( candidate, versionRange ) )
{
continue;
}
@@ -178,17 +196,9 @@ public final ArtifactVersion getNewestVersion( VersionRange versionRange, Artifa
{
continue;
}
- if ( latest == null )
- {
- latest = candidate;
- }
- else if ( versionComparator.compare( latest, candidate ) < 0 )
- {
- latest = candidate;
- }
-
+ return candidate;
}
- return latest;
+ return null;
}
public final ArtifactVersion getNewestVersion( ArtifactVersion lowerBound, ArtifactVersion upperBound,
diff --git a/src/main/java/org/codehaus/mojo/versions/api/ArtifactVersions.java b/src/main/java/org/codehaus/mojo/versions/api/ArtifactVersions.java
index bb15f2ec1..e5d9df192 100644
--- a/src/main/java/org/codehaus/mojo/versions/api/ArtifactVersions.java
+++ b/src/main/java/org/codehaus/mojo/versions/api/ArtifactVersions.java
@@ -20,7 +20,6 @@
*/
import java.util.List;
-import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -96,7 +95,8 @@ public static boolean isVersionInRange( ArtifactVersion version, VersionRange ra
{
return false;
}
- for ( Restriction r : ( (List) range.getRestrictions() ) )
+
+ for ( Restriction r : range.getRestrictions() )
{
if ( r.containsVersion( version ) )
{
@@ -157,24 +157,10 @@ public String getArtifactId()
public ArtifactVersion[] getVersions( boolean includeSnapshots )
{
- Set result;
- if ( includeSnapshots )
- {
- result = versions;
- }
- else
- {
- result = new TreeSet<>( versionComparator );
- for ( ArtifactVersion candidate : versions )
- {
- if ( ArtifactUtils.isSnapshot( candidate.toString() ) )
- {
- continue;
- }
- result.add( candidate );
- }
- }
- return result.toArray( new ArtifactVersion[0] );
+ return includeSnapshots
+ ? versions.toArray( new ArtifactVersion[0] )
+ : versions.stream().filter( v -> !ArtifactUtils.isSnapshot( v.toString() ) )
+ .toArray( ArtifactVersion[]::new );
}
public VersionComparator getVersionComparator()
diff --git a/src/test/java/org/codehaus/mojo/versions/UpdateParentMojoTest.java b/src/test/java/org/codehaus/mojo/versions/UpdateParentMojoTest.java
index e5a25b1d3..0faeec399 100644
--- a/src/test/java/org/codehaus/mojo/versions/UpdateParentMojoTest.java
+++ b/src/test/java/org/codehaus/mojo/versions/UpdateParentMojoTest.java
@@ -2,47 +2,238 @@
import javax.xml.stream.XMLStreamException;
+import java.util.Arrays;
import java.util.Collections;
import org.apache.maven.artifact.Artifact;
-import org.apache.maven.artifact.versioning.ArtifactVersion;
+import org.apache.maven.artifact.DefaultArtifact;
+import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException;
+import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
+import org.apache.maven.artifact.resolver.ArtifactResolver;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
+import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.VersionRange;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Model;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.project.MavenProject;
-import org.apache.maven.project.artifact.ProjectArtifact;
import org.apache.maven.repository.RepositorySystem;
+import org.codehaus.mojo.versions.api.PomHelper;
+import org.codehaus.mojo.versions.change.VersionChange;
+import org.codehaus.mojo.versions.utils.TestChangeRecorder;
+import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
+import org.mockito.MockedStatic;
+import static org.apache.maven.artifact.Artifact.SCOPE_COMPILE;
+import static org.apache.maven.plugin.testing.ArtifactStubFactory.setVariableValueToObject;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;
public class UpdateParentMojoTest
{
+ private TestChangeRecorder changeRecorder;
+
+ private UpdateParentMojo mojo;
+
+ private ArtifactResolver artifactResolver;
+
+ private static RepositorySystem repositorySystem;
+
+ @SuppressWarnings( "deprecation" )
+ private static ArtifactMetadataSource artifactMetadataSource;
+
+ @BeforeClass
+ @SuppressWarnings( "deprecation" )
+ public static void setUpStatic() throws ArtifactMetadataRetrievalException
+ {
+ repositorySystem = mockRepositorySystem();
+ artifactMetadataSource = mockArtifactMetaDataSource();
+ }
+
+ @Before
+ public void setUp() throws IllegalAccessException
+ {
+ changeRecorder = new TestChangeRecorder();
+ artifactResolver = mock( ArtifactResolver.class );
+
+ mojo = new UpdateParentMojo()
+ {{
+ setProject( createProject() );
+ reactorProjects = Collections.emptyList();
+ repositorySystem = UpdateParentMojoTest.repositorySystem;
+ artifactMetadataSource = UpdateParentMojoTest.artifactMetadataSource;
+ resolver = UpdateParentMojoTest.this.artifactResolver;
+
+ setVariableValueToObject( this, "changeRecorder", changeRecorder );
+ }};
+ }
+
+ private MavenProject createProject()
+ {
+ return new MavenProject()
+ {{
+ setModel( new Model()
+ {{
+ setGroupId( "default-group" );
+ setArtifactId( "project-artifact" );
+ setVersion( "1.0.1-SNAPSHOT" );
+ }} );
+
+ setParent( new MavenProject()
+ {{
+ setGroupId( "default-group" );
+ setArtifactId( "parent-artifact" );
+ setVersion( "1.0.1-SNAPSHOT" );
+ }} );
+ }};
+ }
+
+ private static RepositorySystem mockRepositorySystem()
+ {
+ RepositorySystem repositorySystem = mock( RepositorySystem.class );
+ when( repositorySystem.createDependencyArtifact( any( Dependency.class ) ) ).thenAnswer( invocation ->
+ {
+ Dependency dependency = invocation.getArgument( 0 );
+ return new DefaultArtifact( dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(),
+ dependency.getScope(), dependency.getType(), dependency.getClassifier() != null
+ ? dependency.getClassifier() : "default", null );
+ } );
+ return repositorySystem;
+ }
+
+ @SuppressWarnings( "deprecation" )
+ private static ArtifactMetadataSource mockArtifactMetaDataSource() throws ArtifactMetadataRetrievalException
+ {
+ ArtifactMetadataSource artifactMetadataSource = mock( ArtifactMetadataSource.class );
+ when( artifactMetadataSource.retrieveAvailableVersions( any( Artifact.class ), any(), any() ) ).then(
+ invocation ->
+ {
+ Artifact artifact = invocation.getArgument( 0 );
+ if ( "parent-artifact".equals( artifact.getArtifactId() ) )
+ {
+ return Arrays.asList( new DefaultArtifactVersion( "1.0.1-SNAPSHOT" ),
+ new DefaultArtifactVersion( "1.0.0" ),
+ new DefaultArtifactVersion( "0.9.0" ) );
+ }
+ else if ( "unknown-artifact".equals( artifact.getArtifactId() ) )
+ {
+ return Collections.emptyList();
+ }
+ fail();
+ return null;
+ } );
+ return artifactMetadataSource;
+ }
@Test
- public void testArtifactIdDoesNotExist() throws MojoExecutionException, XMLStreamException, MojoFailureException
+ @SuppressWarnings( "deprecation" )
+ public void testArtifactIdDoesNotExist()
+ throws ArtifactMetadataRetrievalException, MojoExecutionException,
+ XMLStreamException, MojoFailureException, InvalidVersionSpecificationException
{
- UpdateParentMojo mojo = new UpdateParentMojo()
+ mojo.getProject().setParent( new MavenProject()
+ {{
+ setGroupId( "default-group" );
+ setArtifactId( "unknown-artifact" );
+ setVersion( "1.0.1-SNAPSHOT" );
+ }} );
+
+ Artifact artifact =
+ new DefaultArtifact( "default-group", "unknown-artifact", "1.0.1-SNAPSHOT", SCOPE_COMPILE, "pom",
+ "default", null );
+ assertThat(
+ mojo.findLatestVersion( artifact, VersionRange.createFromVersionSpec( "1.0.1-SNAPSHOT" ), null, false ),
+ is( nullValue() ) );
+
+ try ( MockedStatic pomHelper = mockStatic( PomHelper.class ) )
+ {
+ pomHelper.when( () -> PomHelper.setProjectParentVersion( any(), any() ) ).thenReturn( true );
+ mojo.update( null );
+ }
+ }
+
+ @Test
+ public void testParentDowngradeAllowed()
+ throws MojoExecutionException, XMLStreamException, MojoFailureException
+ {
+ mojo.allowDowngrade = true;
+ try ( MockedStatic pomHelper = mockStatic( PomHelper.class ) )
+ {
+ pomHelper.when( () -> PomHelper.setProjectParentVersion( any(), any() ) )
+ .thenReturn( true );
+ mojo.update( null );
+ }
+ assertThat( changeRecorder.getChanges(),
+ hasItem( new VersionChange( "default-group", "parent-artifact", "1.0.1-SNAPSHOT",
+ "1.0.0" ) ) );
+ }
+
+ @Test
+ public void testParentDowngradeForbidden()
+ throws MojoExecutionException, XMLStreamException, MojoFailureException
+ {
+ mojo.allowDowngrade = false;
+ try ( MockedStatic pomHelper = mockStatic( PomHelper.class ) )
+ {
+ pomHelper.when( () -> PomHelper.setProjectParentVersion( any(), any() ) )
+ .thenReturn( true );
+ mojo.update( null );
+ }
+ assertThat( changeRecorder.getChanges(), is( empty() ) );
+ }
+
+ @Test
+ public void testParentDowngradeAllowedWithRange()
+ throws MojoExecutionException, XMLStreamException, MojoFailureException
+ {
+ mojo.allowDowngrade = true;
+ mojo.getProject().setParent( new MavenProject()
+ {{
+ setGroupId( "default-group" );
+ setArtifactId( "parent-artifact" );
+ setVersion( "[1.0.1-SNAPSHOT,)" );
+ }} );
+
+ try ( MockedStatic pomHelper = mockStatic( PomHelper.class ) )
+ {
+ pomHelper.when( () -> PomHelper.setProjectParentVersion( any(), any() ) )
+ .thenReturn( true );
+ mojo.update( null );
+ }
+ assertThat( changeRecorder.getChanges(),
+ hasItem( new VersionChange( "default-group", "parent-artifact", "[1.0.1-SNAPSHOT,)",
+ "1.0.0" ) ) );
+ }
+
+ @Test
+ public void testParentDowngradeForbiddenWithRange()
+ throws MojoExecutionException, XMLStreamException, MojoFailureException
+ {
+ mojo.allowDowngrade = false;
+ mojo.getProject().setParent( new MavenProject()
+ {{
+ setGroupId( "default-group" );
+ setArtifactId( "parent-artifact" );
+ setVersion( "[1.0.1-SNAPSHOT,)" );
+ }} );
+
+ try ( MockedStatic pomHelper = mockStatic( PomHelper.class ) )
{
- {
- project = new MavenProject();
- project.setParent( new MavenProject() );
- reactorProjects = Collections.emptyList();
- forceUpdate = true;
-
- repositorySystem = mock( RepositorySystem.class );
- when( repositorySystem.createDependencyArtifact( any() ) )
- .thenReturn( new ProjectArtifact( project ) );
- }
-
- protected ArtifactVersion findLatestVersion( Artifact artifact, VersionRange versionRange,
- Boolean allowingSnapshots, boolean usePluginRepositories )
- {
- return null;
- }
- };
- mojo.update( null );
+ pomHelper.when( () -> PomHelper.setProjectParentVersion( any(), any() ) )
+ .thenReturn( true );
+ mojo.update( null );
+ }
+ assertThat( changeRecorder.getChanges(), is( empty() ) );
}
}
diff --git a/src/test/java/org/codehaus/mojo/versions/UseLatestVersionsMojoTest.java b/src/test/java/org/codehaus/mojo/versions/UseLatestVersionsMojoTest.java
index 4d64826b3..5ac43e263 100644
--- a/src/test/java/org/codehaus/mojo/versions/UseLatestVersionsMojoTest.java
+++ b/src/test/java/org/codehaus/mojo/versions/UseLatestVersionsMojoTest.java
@@ -2,11 +2,8 @@
import javax.xml.stream.XMLStreamException;
-import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.DefaultArtifact;
@@ -22,7 +19,7 @@
import org.apache.maven.repository.RepositorySystem;
import org.codehaus.mojo.versions.api.PomHelper;
import org.codehaus.mojo.versions.change.VersionChange;
-import org.codehaus.mojo.versions.recording.ChangeRecorder;
+import org.codehaus.mojo.versions.utils.TestChangeRecorder;
import org.junit.Before;
import org.junit.Test;
import org.mockito.MockedStatic;
@@ -70,8 +67,6 @@ public void setUp() throws Exception
return null;
} );
- changeRecorder = new TestChangeRecorder();
-
mojo = new UseLatestVersionsMojo()
{{
MavenProject project = new MavenProject()
@@ -95,31 +90,12 @@ public void setUp() throws Exception
setProject( project );
repositorySystem = repositorySystemMock;
artifactMetadataSource = artifactMetadataSourceMock;
+
+ changeRecorder = new TestChangeRecorder();
setVariableValueToObject( this, "changeRecorder", changeRecorder );
}};
}
- private static class TestChangeRecorder implements ChangeRecorder
- {
- private final List changes = new LinkedList<>();
-
- @Override
- public void recordUpdate( String kind, String groupId, String artifactId, String oldVersion, String newVersion )
- {
- changes.add( new VersionChange( groupId, artifactId, oldVersion, newVersion ) );
- }
-
- @Override
- public void serialize( OutputStream outputStream )
- {
- }
-
- public List getChanges()
- {
- return changes;
- }
- }
-
@Test
public void testDependenciesDowngradeIncremental()
throws MojoExecutionException, XMLStreamException, MojoFailureException, IllegalAccessException
diff --git a/src/test/java/org/codehaus/mojo/versions/utils/TestChangeRecorder.java b/src/test/java/org/codehaus/mojo/versions/utils/TestChangeRecorder.java
new file mode 100644
index 000000000..2966cafe6
--- /dev/null
+++ b/src/test/java/org/codehaus/mojo/versions/utils/TestChangeRecorder.java
@@ -0,0 +1,48 @@
+package org.codehaus.mojo.versions.utils;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://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.
+ */
+
+import java.io.OutputStream;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.codehaus.mojo.versions.change.VersionChange;
+import org.codehaus.mojo.versions.recording.ChangeRecorder;
+
+public class TestChangeRecorder implements ChangeRecorder
+{
+ private final List changes = new LinkedList<>();
+
+ @Override
+ public void recordUpdate( String kind, String groupId, String artifactId, String oldVersion, String newVersion )
+ {
+ changes.add( new VersionChange( groupId, artifactId, oldVersion, newVersion ) );
+ }
+
+ @Override
+ public void serialize( OutputStream outputStream )
+ {
+ }
+
+ public List getChanges()
+ {
+ return changes;
+ }
+}
\ No newline at end of file