Skip to content

Commit

Permalink
display versions outside range with star
Browse files Browse the repository at this point in the history
  • Loading branch information
sultan committed Nov 24, 2022
1 parent acc5dbd commit 35b5cc4
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 104 deletions.
Expand Up @@ -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;
Expand All @@ -46,6 +54,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}.
*
Expand All @@ -60,6 +72,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.
Expand Down Expand Up @@ -441,7 +455,7 @@ protected Optional<String> getLowerBound( ArtifactVersion version, Optional<Segm
* @param candidate the version to check.
* @return true if the candidate version is within the range of the restriction parameter.
*/
private boolean isVersionInRestriction( Restriction restriction, ArtifactVersion candidate )
public boolean isVersionInRestriction( Restriction restriction, ArtifactVersion candidate )
{
ArtifactVersion lowerBound = restriction.getLowerBound();
ArtifactVersion upperBound = restriction.getUpperBound();
Expand All @@ -456,4 +470,92 @@ private 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 <code>null</code> 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 <code>null</code> if no version is available.
*/
public final ArtifactVersion getReportNewestUpdate( Optional<Segment> 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<Segment> updateScope )
{
TreeSet<ArtifactVersion> 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<ArtifactVersion> 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<ArtifactVersion> getArtifactVersionStream( Optional<Segment> updateScope )
{
if ( isCurrentVersionDefined() )
{
try
{
// 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 = VersionRange.createFromVersionSpec( getCurrentVersion().toString() )
.getRestrictions().stream().map( Restriction::getLowerBound ).filter( Objects::nonNull )
.max( getVersionComparator() ).orElse( getCurrentVersion() );
Restriction restriction = getVersionComparator().restrictionFor( highestLowerBound, updateScope );

return Arrays.stream( getVersions() ).filter(
candidate -> ( isIncludeSnapshots() || !ArtifactUtils.isSnapshot( candidate.toString() ) )
&& isVersionInRestriction( restriction, candidate ) );
}
catch ( InvalidVersionSpecificationException | InvalidSegmentException ignored )
{
// ignored
}
}
return Stream.empty();
}

}
@@ -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
@@ -0,0 +1,22 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>localhost</groupId>
<artifactId>it-823-ranges-update-report</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<name>ranges-update-report</name>
<url>http://localhost/</url>

<properties>
<verion.dummy-api>[1.1.2,3.4)</verion.dummy-api>
</properties>
<dependencies>
<dependency>
<groupId>localhost</groupId>
<artifactId>dummy-api</artifactId>
<version>${verion.dummy-api}</version>
</dependency>
</dependencies>

</project>
@@ -0,0 +1,28 @@

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\.4\) 1\.1\.3 1\.3 3/
// Detail
assert propertyUpdatesReport =~ /Newer versions 1\.1\.3 Latest Incremental/
assert propertyUpdatesReport =~ / \* 1\.2\.2 \* 1\.3 Latest Minor/
assert propertyUpdatesReport =~ / \* 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\.4\) 1\.1\.3 1\.3 3/
// Detail
assert propertyUpdatesReport =~ /Newer versions 1\.1\.3 Latest Incremental/
assert propertyUpdatesReport =~ / \* 1\.2\.2 \* 1\.3 Latest Minor/
assert propertyUpdatesReport =~ / \* 2\.0 \* 2\.1 \* 3\.0\b/
Expand Up @@ -20,10 +20,16 @@
*/

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.Restriction;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.sink.SinkEventAttributes;
import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
Expand All @@ -32,6 +38,8 @@
import org.codehaus.mojo.versions.api.ArtifactVersions;
import org.codehaus.mojo.versions.api.ArtifactVersionsCache;
import org.codehaus.mojo.versions.api.ReportRenderer;
import org.codehaus.mojo.versions.api.Segment;
import org.codehaus.mojo.versions.ordering.BoundArtifactVersion;
import org.codehaus.plexus.i18n.I18N;

import static java.util.Optional.empty;
Expand All @@ -58,11 +66,11 @@ public abstract class AbstractVersionsReportRenderer<T> 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 AbstractVersionsReportRenderer( I18N i18n, Sink sink, Locale locale, String bundleName, T model )
{
Expand Down Expand Up @@ -139,6 +147,12 @@ protected <Q extends OverviewStats> void renderOverviewTableRow( Q stats )
renderStatRow( "report.overview.numNewerMajorAvailable", stats.getMajor(), false );
}

/**
* Renders one table row for the given statistics.
* @param textKey the key of the text to be rendered.
* @param statCount the number of artifacts with the given stat.
* @param forceSuccessIcon if true, the success icon will be rendered regardless.
*/
protected void renderStatRow( String textKey, int statCount, boolean forceSuccessIcon )
{
sink.tableRow();
Expand Down Expand Up @@ -207,9 +221,9 @@ protected void renderSummaryTableHeader( boolean hasScope, boolean hasType )
"report.latestIncremental", "report.latestMinor", "report.latestMajor" );
}

protected void renderSummaryTableRow( Dependency artifact, ArtifactVersions details,
boolean includeScope )
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;

Expand Down Expand Up @@ -392,27 +406,7 @@ else if ( newestUpdateCache.get( details, of( MAJOR ) ) != null )
sink.text( getText( "report.updateVersions" ) );
sink.tableHeaderCell_();
sink.tableCell( cellAttributes );
for ( int i = 0; i < allUpdates.length; i++ )
{
if ( i > 0 )
{
sink.lineBreak();
}
String label = getLabel( allUpdates[i], details );
if ( label != null )
{
safeBold();
}
sink.text( allUpdates[i].toString() );
if ( label != null )
{
safeBold_();
sink.nonBreakingSpace();
safeItalic();
sink.text( label );
safeItalic_();
}
}
renderVersions( allUpdates, details );
sink.tableCell_();
sink.tableRow_();
}
Expand All @@ -437,6 +431,65 @@ protected void renderTableHeaderCells( String... keys )
} );
}

/**
* Renders the list of versions that are available for the given artifact or property.
* @param allUpdates the list of all updates available.
* @param details the versions details for the given artifact or property.
*/
protected void renderVersions( ArtifactVersion[] allUpdates, AbstractVersionDetails details )
{
List<Restriction> versionRange = getArtifactVersionRange( details );
boolean someNotAllowed = false;
for ( int i = 0; i < allUpdates.length; i++ )
{
if ( i > 0 )
{
sink.lineBreak();
}
// if candidate version in range, display no star.
ArtifactVersion candidate = allUpdates[i];
boolean allowed = versionRange.stream().anyMatch( restriction ->
details.isVersionInRestriction( restriction, candidate ) );
String label = getLabel( allUpdates[i], details );
if ( !allowed )
{
sink.text( "* " );
someNotAllowed = true;
}
if ( allowed && label != null )
{
safeBold();
}
sink.text( allUpdates[i].toString() );
if ( label != null )
{
if ( allowed )
{
safeBold_();
}
sink.nonBreakingSpace();
safeItalic();
sink.text( label );
safeItalic_();
}
}
if ( someNotAllowed )
{
sink.lineBreak();
sink.lineBreak();
sink.text( "* " );
safeItalic();
sink.text( getText( "report.excludedVersion" ) );
safeItalic_();
}
}

/**
* Returns a text label to describe if the given version is a major, minor, incremental or subincremental update.
* @param version the version to describe.
* @param details the artifact for which to render the versions.
* @return a text label to describe if the given version is a major, minor, incremental or subincremental update.
*/
protected String getLabel( ArtifactVersion version, AbstractVersionDetails details )
{

Expand All @@ -462,4 +515,36 @@ protected String getLabel( ArtifactVersion version, AbstractVersionDetails detai

return 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<Restriction> getArtifactVersionRange( AbstractVersionDetails details )
{
String spec = details.getCurrentVersion().toString();
try
{
VersionRange range = VersionRange.createFromVersionSpec( spec );
return range.getRestrictions().stream().map(
restriction -> new Restriction(
restriction.getLowerBound() == null ? null
: new BoundArtifactVersion( restriction.getLowerBound(), Segment.of(
details.getVersionComparator().getSegmentCount( restriction.getLowerBound() ) - 0 ) ),
restriction.isLowerBoundInclusive(),
restriction.getLowerBound() == null ? null
: new BoundArtifactVersion( restriction.getLowerBound(), Segment.of(
details.getVersionComparator().getSegmentCount( restriction.getLowerBound() ) - 1 ) ),
restriction.isUpperBoundInclusive() )
).collect( Collectors.toList() );
}
catch ( InvalidVersionSpecificationException ignored )
{
// ignored
}
return Collections.EMPTY_LIST;
}

}

0 comments on commit 35b5cc4

Please sign in to comment.