diff --git a/versions-common/src/main/java/org/codehaus/mojo/versions/api/AbstractVersionDetails.java b/versions-common/src/main/java/org/codehaus/mojo/versions/api/AbstractVersionDetails.java index c16506c89..13e8966c8 100644 --- a/versions-common/src/main/java/org/codehaus/mojo/versions/api/AbstractVersionDetails.java +++ b/versions-common/src/main/java/org/codehaus/mojo/versions/api/AbstractVersionDetails.java @@ -20,11 +20,19 @@ */ import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.Objects; import java.util.Optional; +import java.util.TreeSet; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.maven.artifact.ArtifactUtils; import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; import org.apache.maven.artifact.versioning.Restriction; import org.apache.maven.artifact.versioning.VersionRange; import org.codehaus.mojo.versions.ordering.BoundArtifactVersion; @@ -47,6 +55,10 @@ public abstract class AbstractVersionDetails implements VersionDetails { + private static final Pattern PREVIEW_PATTERN = + Pattern.compile( "(?i)(?:.*[-.](alpha|a|beta|b|milestone|m|preview|rc)" + + "[-.]?(\\d{0,2}[a-z]?|\\d{6}\\.\\d{4})|\\d{8}(?:\\.?\\d{6})?)$" ); + /** * The current version. Guarded by {@link #currentVersionLock}. * @@ -61,6 +73,8 @@ public abstract class AbstractVersionDetails */ private boolean includeSnapshots = false; + protected boolean verboseDetail = true; + /** * Not sure if we need to be thread safe, but there's no harm being careful, after all we could be invoked from an * IDE. @@ -77,6 +91,24 @@ protected AbstractVersionDetails() public Restriction restrictionFor( Optional scope ) throws InvalidSegmentException { + // one range spec can have multiple restrictions, and multiple 'lower bound', we want the highest one. + // [1.0,2.0),[3.0,4.0) -> 3.0 + ArtifactVersion highestLowerBound = currentVersion; + if ( currentVersion != null ) + { + try + { + highestLowerBound = VersionRange.createFromVersionSpec( currentVersion.toString() ) + .getRestrictions().stream().map( Restriction::getLowerBound ).filter( Objects::nonNull ) + .max( getVersionComparator() ).orElse( currentVersion ); + } + catch ( InvalidVersionSpecificationException ignored ) + { + ignored.printStackTrace( System.err ); + } + } + + final ArtifactVersion currentVersion = highestLowerBound; ArtifactVersion nextVersion = scope .filter( s -> s.isMajorTo( SUBINCREMENTAL ) ) .map( s -> (ArtifactVersion) @@ -480,4 +512,87 @@ public boolean isVersionInRestriction( Restriction restriction, ArtifactVersion } return ( includeLower || lower != 0 ) && ( includeUpper || upper != 0 ); } + + /** + * Returns the latest version newer than the specified current version, and within the specified update scope, + * or null if no such version exists. + * @param updateScope the scope of updates to include. + * @return the newest version after currentVersion within the specified update scope, + * or null if no version is available. + */ + public final ArtifactVersion getReportNewestUpdate( Optional updateScope ) + { + return getArtifactVersionStream( updateScope ) + .min( Collections.reverseOrder( getVersionComparator() ) ).orElse( null ); + } + + /** + * Returns all versions newer than the specified current version, and within the specified update scope. + * @param updateScope the scope of updates to include. + * @return all versions after currentVersion within the specified update scope. + */ + public final ArtifactVersion[] getReportUpdates( Optional updateScope ) + { + TreeSet versions = getArtifactVersionStream( updateScope ) + .collect( Collectors.toCollection( () -> new TreeSet<>( getVersionComparator() ) ) ); + // filter out intermediate minor versions. + if ( !verboseDetail ) + { + int major = 0; + int minor = 0; + boolean needOneMore = false; + for ( Iterator it = versions.descendingIterator(); it.hasNext(); ) + { + ArtifactVersion version = it.next(); + boolean isPreview = PREVIEW_PATTERN.matcher( version.toString() ).matches(); + + // encountered a version in same Major.Minor version, remove it. + if ( version.getMajorVersion() == major && version.getMinorVersion() == minor ) + { + if ( needOneMore && !isPreview ) + { + needOneMore = false; + continue; + } + it.remove(); + continue; + } + + // encountered a new Major.Minor version, keep it. + major = version.getMajorVersion(); + minor = version.getMinorVersion(); + + // if version is a pre-release, also search for the last release. + needOneMore = isPreview; + + } + } + return versions.toArray( new ArtifactVersion[0] ); + } + + /** + * Returns all versions newer than the specified current version, and within the specified update scope. + * @param updateScope the scope of updates to include. + * @return all versions after currentVersion within the specified update scope. + */ + private Stream getArtifactVersionStream( Optional updateScope ) + { + if ( isCurrentVersionDefined() ) + { + try + { + Restriction restriction = restrictionFor( updateScope ); + + return Arrays.stream( getVersions() ).filter( + candidate -> ( isIncludeSnapshots() || !ArtifactUtils.isSnapshot( candidate.toString() ) ) + && isVersionInRestriction( restriction, candidate ) ); + } + catch ( InvalidSegmentException ignored ) + { + ignored.printStackTrace( System.err ); + } + } + return Stream.empty(); + } + } diff --git a/versions-maven-plugin/src/it/it-823-ranges-update-report-001/invoker.properties b/versions-maven-plugin/src/it/it-823-ranges-update-report-001/invoker.properties new file mode 100644 index 000000000..53663b3aa --- /dev/null +++ b/versions-maven-plugin/src/it/it-823-ranges-update-report-001/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals.1=${project.groupId}:${project.artifactId}:${project.version}:property-updates-report +invoker.goals.2=${project.groupId}:${project.artifactId}:${project.version}:dependency-updates-report diff --git a/versions-maven-plugin/src/it/it-823-ranges-update-report-001/pom.xml b/versions-maven-plugin/src/it/it-823-ranges-update-report-001/pom.xml new file mode 100644 index 000000000..1d60d7746 --- /dev/null +++ b/versions-maven-plugin/src/it/it-823-ranges-update-report-001/pom.xml @@ -0,0 +1,22 @@ + + 4.0.0 + localhost + it-823-ranges-update-report + 1.0 + pom + ranges-update-report + http://localhost/ + + + [1.1.2,3.0] + + + + localhost + dummy-api + ${verion.dummy-api} + + + + diff --git a/versions-maven-plugin/src/it/it-823-ranges-update-report-001/verify.groovy b/versions-maven-plugin/src/it/it-823-ranges-update-report-001/verify.groovy new file mode 100644 index 000000000..4c558ea25 --- /dev/null +++ b/versions-maven-plugin/src/it/it-823-ranges-update-report-001/verify.groovy @@ -0,0 +1,26 @@ + +propertyUpdatesReport = new File( basedir, "target/site/property-updates-report.html" ).text + .replaceAll( '<[^>]+>', ' ' ) + .replaceAll( '&[^;]+;', ' ' ) + .replaceAll( '\\s+', ' ' ) + +assert ! ( propertyUpdatesReport =~ /\b1\.1\.0-2\b/ ) +assert ! ( propertyUpdatesReport =~ /\b1\.1\.1-2\b/ ) +// Summary +assert propertyUpdatesReport =~ / \[1\.1\.2,3\.0\] 1\.1\.3 1\.3 3/ +// Detail +assert propertyUpdatesReport =~ /Newer versions 1\.1\.3 Latest Incremental/ +assert propertyUpdatesReport =~ /\b1\.2\.2 1\.3 Latest Minor 2\.0 2\.1 3\.0\b/ + +dependencyUpdatesReport = new File( basedir, "target/site/dependency-updates-report.html" ).text + .replaceAll( '<[^>]+>', ' ' ) + .replaceAll( '&[^;]+;', ' ' ) + .replaceAll( '\\s+', ' ' ) + +assert ! ( dependencyUpdatesReport =~ /\b1\.1\.0-2\b/ ) +assert ! ( dependencyUpdatesReport =~ /\b1\.1\.1-2\b/ ) +// Summary +assert propertyUpdatesReport =~ / \[1\.1\.2,3\.0\] 1\.1\.3 1\.3 3/ +// Detail +assert propertyUpdatesReport =~ /Newer versions 1\.1\.3 Latest Incremental/ +assert propertyUpdatesReport =~ /\b1\.2\.2 1\.3 Latest Minor 2\.0 2\.1 3\.0\b/ diff --git a/versions-maven-plugin/src/it/it-823-ranges-update-report-002/invoker.properties b/versions-maven-plugin/src/it/it-823-ranges-update-report-002/invoker.properties new file mode 100644 index 000000000..53663b3aa --- /dev/null +++ b/versions-maven-plugin/src/it/it-823-ranges-update-report-002/invoker.properties @@ -0,0 +1,2 @@ +invoker.goals.1=${project.groupId}:${project.artifactId}:${project.version}:property-updates-report +invoker.goals.2=${project.groupId}:${project.artifactId}:${project.version}:dependency-updates-report diff --git a/versions-maven-plugin/src/it/it-823-ranges-update-report-002/pom.xml b/versions-maven-plugin/src/it/it-823-ranges-update-report-002/pom.xml new file mode 100644 index 000000000..7078a1dbc --- /dev/null +++ b/versions-maven-plugin/src/it/it-823-ranges-update-report-002/pom.xml @@ -0,0 +1,22 @@ + + 4.0.0 + localhost + it-823-ranges-update-report + 1.0 + pom + ranges-update-report + http://localhost/ + + + [1.1.2,3.0) + + + + localhost + dummy-api + ${verion.dummy-api} + + + + diff --git a/versions-maven-plugin/src/it/it-823-ranges-update-report-002/verify.groovy b/versions-maven-plugin/src/it/it-823-ranges-update-report-002/verify.groovy new file mode 100644 index 000000000..c0bf5442e --- /dev/null +++ b/versions-maven-plugin/src/it/it-823-ranges-update-report-002/verify.groovy @@ -0,0 +1,26 @@ + +propertyUpdatesReport = new File( basedir, "target/site/property-updates-report.html" ).text + .replaceAll( '<[^>]+>', ' ' ) + .replaceAll( '&[^;]+;', ' ' ) + .replaceAll( '\\s+', ' ' ) + +assert ! ( propertyUpdatesReport =~ /\b1\.1\.0-2\b/ ) +assert ! ( propertyUpdatesReport =~ /\b1\.1\.1-2\b/ ) +// Summary +assert propertyUpdatesReport =~ / \[1\.1\.2,3\.0\) 1\.1\.3 1\.3 3/ +// Detail +assert propertyUpdatesReport =~ /Newer versions 1\.1\.3 Latest Incremental/ +assert propertyUpdatesReport =~ /\b1\.2\.2 1\.3 Latest Minor 2\.0 2\.1 \* 3\.0\b/ + +dependencyUpdatesReport = new File( basedir, "target/site/dependency-updates-report.html" ).text + .replaceAll( '<[^>]+>', ' ' ) + .replaceAll( '&[^;]+;', ' ' ) + .replaceAll( '\\s+', ' ' ) + +assert ! ( dependencyUpdatesReport =~ /\b1\.1\.0-2\b/ ) +assert ! ( dependencyUpdatesReport =~ /\b1\.1\.1-2\b/ ) +// Summary +assert propertyUpdatesReport =~ / \[1\.1\.2,3\.0\) 1\.1\.3 1\.3 3/ +// Detail +assert propertyUpdatesReport =~ /Newer versions 1\.1\.3 Latest Incremental/ +assert propertyUpdatesReport =~ /\b1\.2\.2 1\.3 Latest Minor 2\.0 2\.1 \* 3\.0\b/ diff --git a/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/reporting/AbstractVersionsReportRenderer.java b/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/reporting/AbstractVersionsReportRenderer.java index 95f370835..0b5117d9f 100644 --- a/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/reporting/AbstractVersionsReportRenderer.java +++ b/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/reporting/AbstractVersionsReportRenderer.java @@ -62,11 +62,11 @@ public abstract class AbstractVersionsReportRenderer extends VersionsReportRe */ protected T model; - protected ArtifactVersionsCache newestUpdateCache - = new ArtifactVersionsCache( AbstractVersionDetails::getNewestUpdate ); + protected final ArtifactVersionsCache newestUpdateCache + = new ArtifactVersionsCache( AbstractVersionDetails::getReportNewestUpdate ); - protected ArtifactVersionsCache allUpdatesCache - = new ArtifactVersionsCache( AbstractVersionDetails::getAllUpdates ); + protected final ArtifactVersionsCache allUpdatesCache + = new ArtifactVersionsCache( AbstractVersionDetails::getReportUpdates ); protected final SinkEventAttributes headerAttributes = new SinkEventAttributeSet( SinkEventAttributes.WIDTH, "30%" ); @@ -231,6 +231,7 @@ protected void renderSummaryTableHeader( boolean hasScope, boolean hasType ) protected void renderSummaryTableRow( Dependency artifact, ArtifactVersions details, boolean includeScope ) { + details.setCurrentVersion( artifact.getVersion() ); ArtifactVersion[] allUpdates = allUpdatesCache.get( details, empty() ); boolean upToDate = allUpdates == null || allUpdates.length == 0; @@ -263,7 +264,6 @@ protected void renderNewestVersions( AbstractVersionDetails details ) renderBoldCell( newestUpdateCache.get( details, of( MAJOR ) ) ); } - @SuppressWarnings( "checkstyle:MethodLength" ) protected void renderDependencyDetailTable( Dependency artifact, ArtifactVersions details, boolean includeScope ) { ArtifactVersion[] allUpdates = allUpdatesCache.get( details, empty() ); @@ -363,6 +363,12 @@ else if ( newestUpdateCache.get( details, of( MAJOR ) ) != null ) } } + /** + * Builds the list of restrictions for the given artifact or property, based on its version range. + * used to determine if a candidate version is outside the range, and if it should be displayed with a star. + * @param details the artifact or property for which to render the versions. + * @return the list of restrictions for the spec versions range. + */ private List getArtifactVersionRange( AbstractVersionDetails details ) { try @@ -381,7 +387,7 @@ private List getArtifactVersionRange( AbstractVersionDetails detail /** * Renders the list of versions that are available for the given artifact or property. * @param allUpdates the list of all updates available. - * @param details TODO. + * @param details the versions details for the given artifact or property. */ protected void renderVersions( ArtifactVersion[] allUpdates, AbstractVersionDetails details ) { diff --git a/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/reporting/PluginUpdatesReportRenderer.java b/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/reporting/PluginUpdatesReportRenderer.java index bf2fd24de..bd0e3c2b2 100644 --- a/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/reporting/PluginUpdatesReportRenderer.java +++ b/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/reporting/PluginUpdatesReportRenderer.java @@ -145,18 +145,22 @@ protected void renderSummaryTableRow( Dependency artifact, PluginUpdatesDetails boolean upToDate = !details.isUpdateAvailable(); sink.tableRow(); - sink.tableCell(); + sink.tableCell(); renderIcon( upToDate ); + sink.tableCell_(); + renderCells( artifact.getGroupId(), artifact.getArtifactId() ); renderBoldCell( upToDate, artifact.getVersion() ); renderNewestVersions( details ); + + sink.tableCell(); renderIcon( !details.isDependencyUpdateAvailable() ); + sink.tableCell_(); sink.tableRow_(); } - @SuppressWarnings( "checkstyle:MethodLength" ) private void renderPluginDetail( Dependency artifact, PluginUpdatesDetails details ) { sink.section2(); diff --git a/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/reporting/PropertyUpdatesReportRenderer.java b/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/reporting/PropertyUpdatesReportRenderer.java index e925ccb20..8b79da27b 100644 --- a/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/reporting/PropertyUpdatesReportRenderer.java +++ b/versions-maven-plugin/src/main/java/org/codehaus/mojo/versions/reporting/PropertyUpdatesReportRenderer.java @@ -119,7 +119,6 @@ private void renderPropertySummaryTableRow( Property property, PropertyVersions sink.tableRow_(); } - @SuppressWarnings( "checkstyle:MethodLength" ) protected void renderPropertyDetailTable( Property property, PropertyVersions details ) { ArtifactVersion[] allUpdates = allUpdatesCache.get( details, empty() );