You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2022/03/28 12:23:09 UTC
[sling-slingfeature-maven-plugin] branch master updated: SLING-11227 : Remove dependency to maven versions plugin
This is an automated email from the ASF dual-hosted git repository.
cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-slingfeature-maven-plugin.git
The following commit(s) were added to refs/heads/master by this push:
new 5055ec0 SLING-11227 : Remove dependency to maven versions plugin
5055ec0 is described below
commit 5055ec01e1a89d7a974135c94c6794b0d2707578
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Mon Mar 28 14:23:03 2022 +0200
SLING-11227 : Remove dependency to maven versions plugin
---
pom.xml | 5 -
.../feature/maven/mojos/UpdateVersionsMojo.java | 648 ++++++++++++++++-----
2 files changed, 505 insertions(+), 148 deletions(-)
diff --git a/pom.xml b/pom.xml
index bc33dbb..00baccd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -273,11 +273,6 @@
<version>1.9.0</version>
</dependency>
<dependency>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>versions-maven-plugin</artifactId>
- <version>2.8.1</version>
- </dependency>
- <dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.8.0</version>
diff --git a/src/main/java/org/apache/sling/feature/maven/mojos/UpdateVersionsMojo.java b/src/main/java/org/apache/sling/feature/maven/mojos/UpdateVersionsMojo.java
index 6acdbd3..03ae5d3 100644
--- a/src/main/java/org/apache/sling/feature/maven/mojos/UpdateVersionsMojo.java
+++ b/src/main/java/org/apache/sling/feature/maven/mojos/UpdateVersionsMojo.java
@@ -31,19 +31,28 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.SortedSet;
import java.util.TreeSet;
-
-import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException;
-import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.maven.artifact.ArtifactUtils;
+import org.apache.maven.artifact.DefaultArtifact;
+import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.versioning.ArtifactVersion;
-import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
-import org.apache.maven.model.Dependency;
+import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.repository.legacy.metadata.ArtifactMetadataSource;
import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.Artifacts;
@@ -53,17 +62,11 @@ import org.apache.sling.feature.Feature;
import org.apache.sling.feature.io.json.FeatureJSONReader;
import org.apache.sling.feature.maven.JSONFeatures;
import org.apache.sling.feature.maven.ProjectHelper;
-import org.codehaus.mojo.versions.api.ArtifactVersions;
-import org.codehaus.mojo.versions.api.DefaultVersionsHelper;
-import org.codehaus.mojo.versions.api.UpdateScope;
-import org.codehaus.mojo.versions.api.VersionsHelper;
-import org.codehaus.mojo.versions.utils.DependencyComparator;
import org.codehaus.plexus.util.StringUtils;
/**
* Update the bundles/artifact versions
*/
-@SuppressWarnings("deprecation")
@Mojo(
name = "update-feature-versions",
threadSafe = true
@@ -108,24 +111,12 @@ public class UpdateVersionsMojo extends AbstractIncludingFeatureMojo {
@Parameter(property = "classifiers")
private String classifiers;
- @Component
- protected org.apache.maven.artifact.factory.ArtifactFactory artifactFactory;
-
- @Component
+ @Component(role = org.apache.maven.artifact.metadata.ArtifactMetadataSource.class)
protected ArtifactMetadataSource artifactMetadataSource;
@Parameter(defaultValue = "${project.remoteArtifactRepositories}", readonly = true)
protected List<ArtifactRepository> remoteArtifactRepositories;
- @Parameter(defaultValue = "${localRepository}", readonly = true)
- protected ArtifactRepository localRepository;
-
- private VersionsHelper getHelper() throws MojoExecutionException {
- return new DefaultVersionsHelper(artifactFactory, artifactResolver, artifactMetadataSource,
- remoteArtifactRepositories, null, localRepository, null, null, null,
- null, getLog(), this.mavenSession, null);
- }
-
private List<String[]> parseMatches(final String value, final String matchType) throws MojoExecutionException {
List<String[]> matches = null;
if (value != null) {
@@ -177,22 +168,39 @@ public class UpdateVersionsMojo extends AbstractIncludingFeatureMojo {
return features;
}
- private void addDependencies(final Set<Dependency> dependencies, final List<Artifact> artifacts,
+ /**
+ * Get all dependencies to check for a list of artifacts
+ * @param dependencies The result
+ * @param artifacts The artifacts to check
+ * @param cfg The configuration
+ */
+ private void addDependencies(final Set<ArtifactHolder> dependencies,
+ final List<Artifact> artifacts,
final UpdateConfig cfg) {
for (final Artifact a : artifacts) {
- final String versionInfo = shouldHandle(a.getId(), cfg);
- if (versionInfo != null) {
- final Dependency dep = ProjectHelper.toDependency(a.getId(),
- org.apache.maven.artifact.Artifact.SCOPE_PROVIDED);
- // we store the version info as system path as this seems to be very useful...
- dep.setSystemPath(versionInfo);
- dependencies.add(dep);
+ final String include = shouldHandle(a.getId(), cfg);
+ if (include != null) {
+ String newVersion = null;
+ Scope scope = cfg.defaultScope;
+ if (!include.trim().isEmpty()) {
+ scope = getScope(include);
+ if (scope == null) {
+ newVersion = include;
+ }
+ }
+ dependencies.add(this.createArtifactHolder(a, scope, newVersion));
}
}
}
- private Set<Dependency> getDependencies(final Map<String, Feature> features, final UpdateConfig cfg) {
- final Set<Dependency> dependencies = new TreeSet<>(new DependencyComparator());
+ /**
+ * Get all dependencies to check
+ * @param features The map of features
+ * @param cfg The configuration
+ * @return The set of dependencies
+ */
+ private Set<ArtifactHolder> getDependencies(final Map<String, Feature> features, final UpdateConfig cfg) {
+ final Set<ArtifactHolder> dependencies = new TreeSet<>();
for (final Map.Entry<String, Feature> entry : features.entrySet()) {
addDependencies(dependencies, entry.getValue().getBundles(), cfg);
for (final Extension ext : entry.getValue().getExtensions()) {
@@ -216,6 +224,11 @@ public class UpdateVersionsMojo extends AbstractIncludingFeatureMojo {
}
}
+ /**
+ * Create the update configuration
+ * @return The configuration
+ * @throws MojoExecutionException If configuration is invalid
+ */
private UpdateConfig createConfiguration() throws MojoExecutionException {
final UpdateConfig cfg = new UpdateConfig();
@@ -237,6 +250,11 @@ public class UpdateVersionsMojo extends AbstractIncludingFeatureMojo {
}
cfg.excludes = parseMatches(updatesExcludesList, "excludes");
+ cfg.defaultScope = getScope(this.versionScope);
+ if (cfg.defaultScope == null) {
+ throw new MojoExecutionException("Invalid update scope specified: " + this.versionScope);
+ }
+
return cfg;
}
@@ -250,16 +268,9 @@ public class UpdateVersionsMojo extends AbstractIncludingFeatureMojo {
// Create config
final UpdateConfig cfg = this.createConfiguration();
- // Calculate dependencies for features
- final Set<Dependency> dependencies = getDependencies(features, cfg);
-
- // Get updates
- try {
- cfg.updateInfos = calculateUpdateInfos(getHelper().lookupDependenciesUpdates(dependencies, false));
- } catch (ArtifactMetadataRetrievalException
- | InvalidVersionSpecificationException e) {
- throw new MojoExecutionException("Unable to calculate updates", e);
- }
+ // Calculate dependencies for features and get updates
+ cfg.updateInfos = this.getDependencies(features, cfg);
+ this.lookupVersionUpdates(cfg.updateInfos);
final Map<String, UpdateResult> results = new LinkedHashMap<>();
@@ -418,28 +429,20 @@ public class UpdateVersionsMojo extends AbstractIncludingFeatureMojo {
return null;
}
- private String update(final Artifact artifact, final List<Map.Entry<Dependency, String>> updates)
+ private String update(final Artifact artifact, final Set<ArtifactHolder> updates)
throws MojoExecutionException {
getLog().debug("Searching for updates of " + artifact.getId().toMvnId());
- String updated = null;
-
// check updates
String found = null;
- for (final Map.Entry<Dependency, String> entry : updates) {
- if (artifact.getId().getGroupId().equals(entry.getKey().getGroupId())
- && artifact.getId().getArtifactId().equals(entry.getKey().getArtifactId())
- && artifact.getId().getType().equals(entry.getKey().getType())
- && !artifact.getId().getVersion().equals(entry.getValue())
- && ((artifact.getId().getClassifier() == null && entry.getKey().getClassifier() == null)
- || (artifact.getId().getClassifier() != null
- && artifact.getId().getClassifier().equals(entry.getKey().getClassifier())))) {
- found = entry.getValue();
+ for (final ArtifactHolder entry : updates) {
+ if (artifact.getId().equals(entry.getArtifact().getId()) ) {
+ found = entry.getUpdate();
break;
}
-
}
+ String updated = null;
if ( found != null ) {
getLog().debug("Updating " + artifact.getId().toMvnId() + " to " + found);
@@ -457,7 +460,9 @@ public class UpdateVersionsMojo extends AbstractIncludingFeatureMojo {
public List<String> includeVersionInfo;
- List<Map.Entry<Dependency, String>> updateInfos;
+ Set<ArtifactHolder> updateInfos;
+
+ public Scope defaultScope;
}
public static final class ArtifactUpdate {
@@ -471,91 +476,6 @@ public class UpdateVersionsMojo extends AbstractIncludingFeatureMojo {
public Map<String, String> propertyUpdates = new HashMap<>();
}
- private String getVersion(final Map.Entry<Dependency, ArtifactVersions> entry, final UpdateScope scope) {
- ArtifactVersion latest;
- if (entry.getValue().isCurrentVersionDefined()) {
- latest = entry.getValue().getNewestUpdate(scope, false);
- } else {
- ArtifactVersion newestVersion = entry.getValue()
- .getNewestVersion(entry.getValue().getArtifact().getVersionRange(), false);
- latest = newestVersion == null ? null
- : entry.getValue().getNewestUpdate(newestVersion, scope, false);
- if (latest != null
- && ArtifactVersions.isVersionInRange(latest, entry.getValue().getArtifact().getVersionRange())) {
- latest = null;
- }
- }
- return latest != null ? latest.toString() : null;
- }
-
- private UpdateScope getScope(final String versionInfo) {
- final UpdateScope scope;
- if (versionInfo == null || "ANY".equalsIgnoreCase(versionInfo)) {
- scope = UpdateScope.ANY;
- } else if ("MAJOR".equalsIgnoreCase(versionInfo)) {
- scope = UpdateScope.MAJOR;
- } else if ("MINOR".equalsIgnoreCase(versionInfo)) {
- scope = UpdateScope.MINOR;
- } else if ("INCREMENTAL".equalsIgnoreCase(versionInfo)) {
- scope = UpdateScope.INCREMENTAL;
- } else if ("SUBINCREMENTAL".equalsIgnoreCase(versionInfo)) {
- scope = UpdateScope.SUBINCREMENTAL;
- } else {
- scope = null;
- }
- return scope;
- }
-
- private List<Map.Entry<Dependency, String>> calculateUpdateInfos(
- final Map<Dependency, ArtifactVersions> updateInfos) throws MojoExecutionException {
- final UpdateScope defaultScope = getScope(this.versionScope);
- if (defaultScope == null) {
- throw new MojoExecutionException("Invalid update scope specified: " + this.versionScope);
- }
- final List<Map.Entry<Dependency, String>> updates = new ArrayList<>();
- for (final Map.Entry<Dependency, ArtifactVersions> entry : updateInfos.entrySet()) {
- UpdateScope scope = defaultScope;
- final String versionInfo = entry.getKey().getSystemPath();
- String newVersion = null;
- if (versionInfo != null && !versionInfo.trim().isEmpty()) {
- scope = getScope(versionInfo);
- if (scope == null) {
- getLog().debug(
- "Using provided version " + versionInfo + " for " + ProjectHelper.toString(entry.getKey()));
- newVersion = versionInfo;
- }
- }
- if (newVersion == null) {
- newVersion = getVersion(entry, scope);
- getLog().debug("Detected new version " + newVersion + " using scope " + scope.toString() + " for "
- + ProjectHelper.toString(entry.getKey()));
-
- }
- if (newVersion != null) {
- final String version = newVersion;
- updates.add(new Map.Entry<Dependency, String>() {
-
- @Override
- public String setValue(final String value) {
- throw new IllegalStateException();
- }
-
- @Override
- public String getValue() {
- return version;
- }
-
- @Override
- public Dependency getKey() {
- return entry.getKey();
- }
- });
- }
- }
-
- return updates;
- }
-
private boolean updateVersions(final String fileName, final Feature rawFeature, final UpdateResult result,
final Map<String, Set<String>> globalPropertyUpdates) throws MojoExecutionException {
// update artifacts
@@ -656,4 +576,446 @@ public class UpdateVersionsMojo extends AbstractIncludingFeatureMojo {
return updates;
}
+
+ /**
+ * Create a holder
+ * @param artifact The artifact
+ * @param scope The scope
+ * @param newVersion The optional new version
+ * @return The holder
+ */
+ private ArtifactHolder createArtifactHolder(final Artifact artifact,
+ final Scope scope,
+ final String newVersion) {
+ return new ArtifactHolder(artifact, scope, newVersion);
+ }
+
+ /**
+ * Lookup the version updates
+ * @param dependencies The set of dependencies to check for version updates
+ */
+ private void lookupVersionUpdates( final Set<ArtifactHolder> dependencies )
+ throws MojoExecutionException {
+
+ final List<Callable<Void>> requestsForDetails = new ArrayList<>( dependencies.size() );
+ for ( final ArtifactHolder dependency : dependencies ) {
+ requestsForDetails.add( () -> {
+ final ArtifactId id = dependency.getArtifact().getId();
+
+ getLog().debug( "Checking " + id.getGroupId() + ":" + id.getArtifactId() + " for updates newer than " + id.getVersion() );
+
+ final ArtifactHandler handler = artifactHandlerManager.getArtifactHandler( id.getType() );
+
+ final org.apache.maven.artifact.Artifact artifact = new DefaultArtifact( id.getGroupId(), id.getArtifactId(), id.getVersion(), org.apache.maven.artifact.Artifact.SCOPE_PROVIDED,
+ id.getType(), id.getClassifier(), handler);
+ dependency.setVersions(artifactMetadataSource.retrieveAvailableVersions( artifact,
+ this.mavenSession.getLocalRepository(), this.remoteArtifactRepositories ));
+ return null;
+ });
+ }
+
+ // Lookup details in parallel...
+ final ExecutorService executor = Executors.newFixedThreadPool( 5 );
+ try {
+ final List<Future<Void>> responseForDetails = executor.invokeAll( requestsForDetails );
+
+ // Construct the final results...
+ for ( final Future<Void> details : responseForDetails ) {
+ details.get();
+ }
+ } catch ( final ExecutionException | InterruptedException ie ) {
+ throw new MojoExecutionException( "Unable to acquire metadata for dependencies " + dependencies
+ + ": " + ie.getMessage(), ie );
+ } finally {
+ executor.shutdownNow();
+ }
+ }
+
+ /** Enumeration for the update scopes */
+ public enum Scope {
+ ANY,
+ MAJOR,
+ MINOR,
+ INCREMENTAL,
+ SUBINCREMENTAL
+ }
+
+ /**
+ * Get the update scope
+ * @param versionInfo The version info
+ * @return The scope or {@code null}
+ */
+ private static Scope getScope(final String versionInfo) {
+ final Scope scope;
+ if (versionInfo == null || "ANY".equalsIgnoreCase(versionInfo)) {
+ scope = Scope.ANY;
+ } else if ("MAJOR".equalsIgnoreCase(versionInfo)) {
+ scope = Scope.MAJOR;
+ } else if ("MINOR".equalsIgnoreCase(versionInfo)) {
+ scope = Scope.MINOR;
+ } else if ("INCREMENTAL".equalsIgnoreCase(versionInfo)) {
+ scope = Scope.INCREMENTAL;
+ } else if ("SUBINCREMENTAL".equalsIgnoreCase(versionInfo)) {
+ scope = Scope.SUBINCREMENTAL;
+ } else {
+ scope = null;
+ }
+ return scope;
+ }
+ /**
+ * Holds the results of a search for versions of an artifact.
+ */
+ private static class ArtifactHolder implements Comparable<ArtifactHolder> {
+
+ /**
+ * All versions - this is updated dynamically
+ */
+ private final SortedSet<ArtifactVersion> versions = new TreeSet<>();
+
+ /**
+ * The current version.
+ */
+ private final ArtifactVersion currentVersion;
+
+ /**
+ * The update scope to use
+ */
+ private final Scope updateScope;
+
+ /**
+ * The artifact
+ */
+ private final Artifact artifact;
+
+ /**
+ * Optional version to use for the update
+ */
+ private final String newVersion;
+
+ /**
+ * Constructor
+ * @param artifact The artifact.
+ * @param updateScope The update scope.
+ * @param newVersion optional new version info
+ */
+ public ArtifactHolder( final Artifact artifact, final Scope updateScope, final String newVersion ) {
+ this.artifact = artifact;
+ this.updateScope = updateScope;
+ this.currentVersion = new DefaultArtifactVersion(artifact.getId().getVersion());
+ this.newVersion = newVersion;
+ }
+
+ /**
+ * Get the artifact
+ * @return The artifact
+ */
+ public Artifact getArtifact() {
+ return this.artifact;
+ }
+
+ /**
+ * Set the versions
+ * @param versions List of versions
+ */
+ public void setVersions(final List<ArtifactVersion> versions) {
+ // filter out snapshots
+ for ( final ArtifactVersion candidate : versions ) {
+ if ( ArtifactUtils.isSnapshot( candidate.toString() ) ) {
+ continue;
+ }
+ this.versions.add( candidate );
+ }
+ }
+
+ private final ArtifactVersion getNewestVersion( ArtifactVersion lowerBound,
+ ArtifactVersion upperBound,
+ boolean includeLower, boolean includeUpper ) {
+ ArtifactVersion latest = null;
+ for ( final ArtifactVersion candidate : this.versions ) {
+ final int lower = lowerBound == null ? -1 : lowerBound.compareTo( candidate );
+ final int upper = upperBound == null ? +1 : upperBound.compareTo( candidate );
+ if ( lower > 0 || upper < 0 ) {
+ continue;
+ }
+ if ( ( !includeLower && lower == 0 ) || ( !includeUpper && upper == 0 ) ) {
+ continue;
+ }
+ if ( ArtifactUtils.isSnapshot( candidate.toString() ) ) {
+ continue;
+ }
+ if ( latest == null ) {
+ latest = candidate;
+ } else if ( latest.compareTo( candidate ) < 0 ) {
+ latest = candidate;
+ }
+
+ }
+ return latest;
+ }
+
+ public final String getUpdate() {
+ if ( this.newVersion != null ) {
+ return this.newVersion;
+ }
+ ArtifactVersion v = null;
+ switch ( updateScope ) {
+ case SUBINCREMENTAL :
+ v = getSegmentCount( currentVersion ) < 3 ? null
+ : this.getNewestVersion( currentVersion,
+ incrementSegment( currentVersion, 2 ),
+ false, false );
+ break;
+ case INCREMENTAL :
+ v = getSegmentCount( currentVersion ) < 3 ? null
+ : this.getNewestVersion( incrementSegment( currentVersion, 2 ),
+ incrementSegment( currentVersion, 1 ),
+ true, false );
+ break;
+ case MINOR :
+ v = getSegmentCount( currentVersion ) < 2 ? null
+ : this.getNewestVersion( incrementSegment( currentVersion, 1 ),
+ incrementSegment( currentVersion, 0 ),
+ true, false );
+ break;
+ case MAJOR :
+ v = getSegmentCount( currentVersion ) < 1 ? null
+ : this.getNewestVersion( incrementSegment( currentVersion, 0 ),
+ null, true, false );
+ break;
+ case ANY :
+ v = this.getNewestVersion( currentVersion, null, false, false );
+ break;
+ }
+ return v != null ? v.toString() : null;
+ }
+
+ @Override
+ public int compareTo(final ArtifactHolder o) {
+ return this.artifact.compareTo(o.getArtifact());
+ }
+ }
+
+ private static final Pattern SNAPSHOT_PATTERN = Pattern.compile( "(-((\\d{8}\\.\\d{6})-(\\d+))|(SNAPSHOT))$" );
+
+ private static final int getSegmentCount( final ArtifactVersion v ) {
+ if ( v == null ) {
+ return 0;
+ }
+ if ( ArtifactUtils.isSnapshot( v.toString() ) ) {
+ return innerGetSegmentCount( stripSnapshot( v ) );
+ }
+ return innerGetSegmentCount( v );
+ }
+
+ private static ArtifactVersion incrementSegment( final ArtifactVersion v, final int segment ) {
+ if ( ArtifactUtils.isSnapshot( v.toString() ) ) {
+ return copySnapshot( v, innerIncrementSegment( stripSnapshot( v ), segment ) );
+ }
+ return innerIncrementSegment( v, segment );
+ }
+
+ private static int innerGetSegmentCount( final ArtifactVersion v ) {
+ // if the version does not match the maven rules, then we have only one segment
+ // i.e. the qualifier
+ if ( v.getBuildNumber() != 0 ) {
+ // the version was successfully parsed, and we have a build number
+ // have to have four segments
+ return 4;
+ }
+ if ( ( v.getMajorVersion() != 0 || v.getMinorVersion() != 0 || v.getIncrementalVersion() != 0 )
+ && v.getQualifier() != null ) {
+ // the version was successfully parsed, and we have a qualifier
+ // have to have four segments
+ return 4;
+ }
+ final String version = v.toString();
+ if ( version.indexOf( '-' ) != -1 ) {
+ // the version has parts and was not parsed successfully
+ // have to have one segment
+ return version.equals( v.getQualifier() ) ? 1 : 4;
+ }
+ if ( version.indexOf( '.' ) != -1 ) {
+ // the version has parts and was not parsed successfully
+ // have to have one segment
+ return version.equals( v.getQualifier() ) ? 1 : 3;
+ }
+ if ( StringUtils.isEmpty( version ) ) {
+ return 3;
+ }
+ try {
+ Integer.parseInt( version );
+ return 3;
+ } catch ( final NumberFormatException e ) {
+ return 1;
+ }
+ }
+
+ private static ArtifactVersion innerIncrementSegment( final ArtifactVersion v, final int segment ) {
+ int segmentCount = innerGetSegmentCount( v );
+ if ( segment < 0 || segment >= segmentCount ) {
+ throw new IllegalArgumentException( v.toString() );
+ }
+ String version = v.toString();
+ if ( segmentCount == 1 ) {
+ // only the qualifier
+ version = alphaNumIncrement( version );
+ return new DefaultArtifactVersion( version );
+ } else {
+ int major = v.getMajorVersion();
+ int minor = v.getMinorVersion();
+ int incremental = v.getIncrementalVersion();
+ int build = v.getBuildNumber();
+ String qualifier = v.getQualifier();
+
+ int minorIndex = version.indexOf( '.' );
+ boolean haveMinor = minorIndex != -1;
+ int incrementalIndex = haveMinor ? version.indexOf( '.', minorIndex + 1 ) : -1;
+ boolean haveIncremental = incrementalIndex != -1;
+ int buildIndex = version.indexOf( '-' );
+ boolean haveBuild = buildIndex != -1 && qualifier == null;
+ boolean haveQualifier = buildIndex != -1 && qualifier != null;
+
+ switch ( segment ) {
+ case 0:
+ major++;
+ minor = 0;
+ incremental = 0;
+ build = 0;
+ qualifier = null;
+ break;
+ case 1:
+ minor++;
+ incremental = 0;
+ build = 0;
+ if ( haveQualifier && qualifier.endsWith( "SNAPSHOT" ) ) {
+ qualifier = "SNAPSHOT";
+ }
+ break;
+ case 2:
+ incremental++;
+ build = 0;
+ qualifier = null;
+ break;
+ case 3:
+ if ( haveQualifier ) {
+ qualifier = qualifierIncrement( qualifier );
+ } else {
+ build++;
+ }
+ break;
+ }
+ final StringBuilder result = new StringBuilder();
+ result.append( major );
+ if ( haveMinor || minor > 0 || incremental > 0 ) {
+ result.append( '.' );
+ result.append( minor );
+ }
+ if ( haveIncremental || incremental > 0 ) {
+ result.append( '.' );
+ result.append( incremental );
+ }
+ if ( haveQualifier && qualifier != null ) {
+ result.append( '-' );
+ result.append( qualifier );
+ } else if ( haveBuild || build > 0 ) {
+ result.append( '-' );
+ result.append( build );
+ }
+ return new DefaultArtifactVersion( result.toString() );
+ }
+ }
+
+ private static String qualifierIncrement( final String qualifier ) {
+ if ( qualifier.toLowerCase().startsWith( "alpha" ) ) {
+ return qualifier.substring( 0, 5 ) + alphaNumIncrement( qualifier.substring( 5 ) );
+ }
+ if ( qualifier.toLowerCase().startsWith( "beta" ) ) {
+ return qualifier.substring( 0, 4 ) + alphaNumIncrement( qualifier.substring( 4 ) );
+ }
+ if ( qualifier.toLowerCase().startsWith( "milestone" ) ) {
+ return qualifier.substring( 0, 8 ) + alphaNumIncrement( qualifier.substring( 8 ) );
+ }
+ if ( qualifier.toLowerCase().startsWith( "cr" ) || qualifier.toLowerCase().startsWith( "rc" ) || qualifier.toLowerCase().startsWith( "sp" ) ) {
+ return qualifier.substring( 0, 2 ) + alphaNumIncrement( qualifier.substring( 2 ) );
+ }
+ return alphaNumIncrement( qualifier );
+ }
+
+ private static String alphaNumIncrement( String token ) {
+ String newToken;
+ int i = token.length();
+ boolean done = false;
+ newToken = token;
+ while ( !done && i > 0 ) {
+ i--;
+ char c = token.charAt( i );
+ if ( '0' <= c && c < '9' ) {
+ c++;
+ newToken = newToken.substring( 0, i ) + c + ( i + 1 < newToken.length() ? newToken.substring( i + 1 ) : "" );
+ done = true;
+ } else if ( c == '9' ) {
+ c = '0';
+ newToken = newToken.substring( 0, i ) + c + ( i + 1 < newToken.length() ? newToken.substring( i + 1 ) : "" );
+ } else if ( 'A' <= c && c < 'Z' ) {
+ c++;
+ newToken = newToken.substring( 0, i ) + c + ( i + 1 < newToken.length() ? newToken.substring( i + 1 ) : "" );
+ done = true;
+ } else if ( c == 'Z' ) {
+ c = 'A';
+ newToken = newToken.substring( 0, i ) + c + ( i + 1 < newToken.length() ? newToken.substring( i + 1 ) : "" );
+ } else if ( 'a' <= c && c < 'z' ) {
+ c++;
+ newToken = newToken.substring( 0, i ) + c + ( i + 1 < newToken.length() ? newToken.substring( i + 1 ) : "" );
+ done = true;
+ } else if ( c == 'z' ) {
+ c = 'a';
+ newToken = newToken.substring( 0, i ) + c + ( i + 1 < newToken.length() ? newToken.substring( i + 1 ) : "" );
+ }
+ }
+ if ( done ) {
+ return newToken;
+ } else {
+ // ok this is roll-over time
+ boolean lastNumeric = false;
+ boolean lastAlpha = false;
+ boolean lastUpper = false;
+ i = token.length();
+ while ( !lastAlpha && !lastNumeric && i > 0 ) {
+ i--;
+ char c = token.charAt( i );
+ lastAlpha = Character.isLetter( c );
+ lastUpper = c == Character.toUpperCase( c );
+ lastNumeric = Character.isDigit( c );
+ }
+ if ( lastAlpha ) {
+ if ( lastUpper ) {
+ return token + 'A';
+ }
+ return token + 'a';
+ }
+ return token + '0';
+ }
+ }
+
+ private static ArtifactVersion stripSnapshot( ArtifactVersion v ) {
+ final String version = v.toString();
+ final Matcher matcher = SNAPSHOT_PATTERN.matcher( version );
+ if ( matcher.find() ) {
+ return new DefaultArtifactVersion( version.substring( 0, matcher.start( 1 ) - 1 ) );
+ }
+ return v;
+ }
+
+ private static ArtifactVersion copySnapshot( ArtifactVersion source, ArtifactVersion destination ) {
+ if ( ArtifactUtils.isSnapshot( destination.toString() ) ) {
+ destination = stripSnapshot( destination );
+ }
+ final Pattern matchSnapshotRegex = SNAPSHOT_PATTERN;
+ final Matcher matcher = matchSnapshotRegex.matcher( source.toString() );
+ if ( matcher.find() ) {
+ return new DefaultArtifactVersion( destination.toString() + "-" + matcher.group( 0 ) );
+ } else {
+ return new DefaultArtifactVersion( destination.toString() + "-SNAPSHOT" );
+ }
+ }
}