org.codehaus.mojo.versions.api.PropertyVersions Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of versions-common Show documentation
Show all versions of versions-common Show documentation
Common components for the Versions Maven Plugin
package org.codehaus.mojo.versions.api;
/*
* 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
import org.apache.maven.artifact.versioning.OverConstrainedVersionException;
import org.apache.maven.artifact.versioning.Restriction;
import org.apache.maven.artifact.versioning.VersionRange;
import org.apache.maven.project.MavenProject;
import org.codehaus.mojo.versions.ordering.BoundArtifactVersion;
import org.codehaus.mojo.versions.ordering.InvalidSegmentException;
import org.codehaus.mojo.versions.ordering.VersionComparator;
import org.codehaus.mojo.versions.utils.DefaultArtifactVersionCache;
import static java.util.Optional.empty;
import static org.codehaus.mojo.versions.api.Segment.SUBINCREMENTAL;
/**
* Manages a property that is associated with one or more artifacts.
*
* @author Stephen Connolly
* @since 1.0-alpha-3
*/
public class PropertyVersions extends AbstractVersionDetails {
private final String name;
private final String profileId;
private final Set associations;
private final VersionsHelper helper;
/**
* The available versions.
*
* @since 1.0-beta-1
*/
private final SortedSet versions;
private final PropertyVersions.PropertyVersionComparator comparator;
PropertyVersions(String profileId, String name, VersionsHelper helper, Set associations)
throws VersionRetrievalException {
this.profileId = profileId;
this.name = name;
this.helper = helper;
this.associations = new TreeSet<>(associations);
this.comparator = new PropertyVersionComparator();
this.versions = resolveAssociatedVersions(helper, associations, comparator);
}
private static SortedSet resolveAssociatedVersions(
VersionsHelper helper, Set associations, VersionComparator versionComparator)
throws VersionRetrievalException {
SortedSet versions = null;
for (ArtifactAssociation association : associations) {
final ArtifactVersions associatedVersions =
helper.lookupArtifactVersions(association.getArtifact(), association.isUsePluginRepositories());
if (versions != null) {
final ArtifactVersion[] artifactVersions = associatedVersions.getVersions(true);
// since ArtifactVersion does not override equals, we have to do this the hard way
// result.retainAll( Arrays.asList( artifactVersions ) );
Iterator j = versions.iterator();
while (j.hasNext()) {
boolean contains = false;
ArtifactVersion version = j.next();
for (ArtifactVersion artifactVersion : artifactVersions) {
if (version.compareTo(artifactVersion) == 0) {
contains = true;
break;
}
}
if (!contains) {
j.remove();
}
}
} else {
versions = new TreeSet<>(versionComparator);
versions.addAll(Arrays.asList(associatedVersions.getVersions(true)));
}
}
if (versions == null) {
versions = new TreeSet<>(versionComparator);
}
return Collections.unmodifiableSortedSet(versions);
}
/**
* Gets the rule for version comparison of this artifact.
*
* @return the rule for version comparison of this artifact.
* @since 1.0-beta-1
*/
public VersionComparator getVersionComparator() {
return comparator;
}
public ArtifactAssociation[] getAssociations() {
return associations.toArray(new ArtifactAssociation[0]);
}
private VersionComparator[] lookupComparators() {
return associations.stream()
.map(association -> helper.getVersionComparator(association.getArtifact()))
.distinct()
.toArray(VersionComparator[]::new);
}
/**
* Uses the supplied {@link Collection} of {@link Artifact} instances to see if an ArtifactVersion can be provided.
*
* @param artifacts The {@link Collection} of {@link Artifact} instances .
* @return The versions that can be resolved from the supplied Artifact instances or an empty array if no version
* can be resolved (i.e. the property is not associated with any of the supplied artifacts or the property
* is also associated to an artifact that has not been provided).
* @since 1.0-alpha-3
*/
public ArtifactVersion[] getVersions(Collection artifacts) {
List result = new ArrayList<>();
// go through all the associations
// see if they are met from the collection
// add the version if they are
// go through all the versions
// see if the version is available for all associations
for (ArtifactAssociation association : associations) {
for (Artifact artifact : artifacts) {
if (association.getGroupId().equals(artifact.getGroupId())
&& association.getArtifactId().equals(artifact.getArtifactId())) {
try {
result.add(artifact.getSelectedVersion());
} catch (OverConstrainedVersionException e) {
// ignore this one as we cannot resolve a valid version
}
}
}
}
// we now have a list of all the versions that partially satisfy the association requirements
Iterator k = result.iterator();
versions:
while (k.hasNext()) {
ArtifactVersion candidate = k.next();
associations:
for (ArtifactAssociation association : associations) {
for (Artifact artifact : artifacts) {
if (association.getGroupId().equals(artifact.getGroupId())
&& association.getArtifactId().equals(artifact.getArtifactId())) {
try {
if (candidate
.toString()
.equals(artifact.getSelectedVersion().toString())) {
// this association can be met, try the next
continue associations;
}
} catch (OverConstrainedVersionException e) {
// ignore this one again
}
}
}
// candidate is not valid as at least one association cannot be met
k.remove();
continue versions;
}
}
return asArtifactVersionArray(result);
}
/**
* Uses the {@link DefaultVersionsHelper} to find all available versions that match all the associations with this
* property.
*
* @param includeSnapshots Whether to include snapshot versions in our search.
* @return The (possibly empty) array of versions.
*/
public synchronized ArtifactVersion[] getVersions(boolean includeSnapshots) {
Set result;
if (includeSnapshots) {
result = versions;
} else {
result = new TreeSet<>(getVersionComparator());
for (ArtifactVersion candidate : versions) {
if (ArtifactUtils.isSnapshot(candidate.toString())) {
continue;
}
result.add(candidate);
}
}
return asArtifactVersionArray(result);
}
private ArtifactVersion[] asArtifactVersionArray(Collection result) {
if (result == null || result.isEmpty()) {
return new ArtifactVersion[0];
} else {
final ArtifactVersion[] answer = result.toArray(new ArtifactVersion[0]);
VersionComparator[] rules = lookupComparators();
assert rules.length > 0;
Arrays.sort(answer, rules[0]);
if (rules.length == 1 || answer.length == 1) {
// only one rule...
return answer;
}
ArtifactVersion[] alt = answer.clone();
for (int j = 1; j < rules.length; j++) {
Arrays.sort(alt, rules[j]);
if (!Arrays.equals(alt, answer)) {
throw new IllegalStateException("Property " + name + " is associated with multiple artifacts"
+ " and these artifacts use different version sorting rules and these rules are effectively"
+ " incompatible for the set of versions available to this property.\nFirst rule says: "
+ Arrays.asList(answer) + "\nSecond rule says: "
+ Arrays.asList(alt));
}
}
return answer;
}
}
public String getName() {
return name;
}
public String getProfileId() {
return profileId;
}
public boolean isAssociated() {
return !associations.isEmpty();
}
public String toString() {
return "PropertyVersions{" + (profileId == null ? "" : "profileId='" + profileId + "', ") + "name='" + name
+ '\'' + ", associations=" + associations + '}';
}
public ArtifactVersion getNewestVersion(
String currentVersion,
Property property,
boolean allowSnapshots,
List reactorProjects,
VersionsHelper helper)
throws InvalidVersionSpecificationException, InvalidSegmentException {
return getNewestVersion(currentVersion, property, allowSnapshots, reactorProjects, helper, false, empty());
}
/**
* Retrieves the newest artifact version for the given property-denoted artifact or {@code null} if no newer
* version could be found.
*
* @param versionString current version of the artifact
* @param property property name indicating the artifact
* @param allowSnapshots whether snapshots should be considered
* @param reactorProjects collection of reactor projects
* @param helper VersionHelper object
* @param allowDowngrade whether downgrades should be allowed
* @param upperBoundSegment the upper bound segment; empty() means no upper bound
* @return newest artifact version fulfilling the criteria or null if no newer version could be found
* @throws InvalidSegmentException thrown if the {@code unchangedSegment} is not valid (e.g. greater than the number
* of segments in the version string)
* @throws InvalidVersionSpecificationException thrown if the version string in the property is not valid
*/
public ArtifactVersion getNewestVersion(
String versionString,
Property property,
boolean allowSnapshots,
Collection reactorProjects,
VersionsHelper helper,
boolean allowDowngrade,
Optional upperBoundSegment)
throws InvalidSegmentException, InvalidVersionSpecificationException {
final boolean includeSnapshots = !property.isBanSnapshots() && allowSnapshots;
helper.getLog().debug("getNewestVersion(): includeSnapshots='" + includeSnapshots + "'");
helper.getLog()
.debug("Property ${" + property.getName() + "}: Set of valid available versions is "
+ Arrays.asList(getVersions(includeSnapshots)));
VersionRange range =
property.getVersion() != null ? VersionRange.createFromVersionSpec(property.getVersion()) : null;
helper.getLog().debug("Property ${" + property.getName() + "}: Restricting results to " + range);
ArtifactVersion currentVersion = DefaultArtifactVersionCache.of(versionString);
ArtifactVersion lowerBound = allowDowngrade
? getLowerBound(currentVersion, upperBoundSegment)
.map(DefaultArtifactVersionCache::of)
.orElse(null)
: currentVersion;
if (helper.getLog().isDebugEnabled()) {
helper.getLog().debug("lowerBoundArtifactVersion: " + lowerBound);
}
ArtifactVersion upperBound = !upperBoundSegment.isPresent()
? null
: upperBoundSegment
.map(s -> (ArtifactVersion) new BoundArtifactVersion(
currentVersion, s.isMajorTo(SUBINCREMENTAL) ? Segment.minorTo(s) : s))
.orElse(null);
if (helper.getLog().isDebugEnabled()) {
helper.getLog().debug("Property ${" + property.getName() + "}: upperBound is: " + upperBound);
}
Restriction restriction = new Restriction(lowerBound, allowDowngrade, upperBound, allowDowngrade);
ArtifactVersion result = getNewestVersion(range, restriction, includeSnapshots);
helper.getLog().debug("Property ${" + property.getName() + "}: Current winner is: " + result);
if (property.isSearchReactor()) {
helper.getLog().debug("Property ${" + property.getName() + "}: Searching reactor for a valid version...");
Set reactorArtifacts = helper.extractArtifacts(reactorProjects);
ArtifactVersion[] reactorVersions = getVersions(reactorArtifacts);
helper.getLog()
.debug("Property ${" + property.getName()
+ "}: Set of valid available versions from the reactor is "
+ Arrays.asList(reactorVersions));
ArtifactVersion fromReactor = null;
if (reactorVersions.length > 0) {
for (int j = reactorVersions.length - 1; j >= 0; j--) {
if (range == null || ArtifactVersions.isVersionInRange(reactorVersions[j], range)) {
fromReactor = reactorVersions[j];
helper.getLog()
.debug("Property ${" + property.getName() + "}: Reactor has version " + fromReactor);
break;
}
}
}
if (fromReactor != null && (result != null || !currentVersion.equals(fromReactor.toString()))) {
if (property.isPreferReactor()) {
helper.getLog()
.debug("Property ${" + property.getName()
+ "}: Reactor has a version and we prefer the reactor");
result = fromReactor;
} else {
if (result == null) {
helper.getLog().debug("Property ${" + property.getName() + "}: Reactor has the only version");
result = fromReactor;
} else if (getVersionComparator().compare(result, fromReactor) < 0) {
helper.getLog().debug("Property ${" + property.getName() + "}: Reactor has a newer version");
result = fromReactor;
} else {
helper.getLog()
.debug("Property ${" + property.getName() + "}: Reactor has the same or older version");
}
}
}
}
return result;
}
private final class PropertyVersionComparator implements VersionComparator {
public int compare(ArtifactVersion v1, ArtifactVersion v2) {
return innerCompare(v1, v2);
}
private int innerCompare(ArtifactVersion v1, ArtifactVersion v2) {
if (!isAssociated()) {
throw new IllegalStateException("Cannot compare versions for a property with no associations");
}
VersionComparator[] comparators = lookupComparators();
assert comparators.length >= 1 : "we have at least one association => at least one comparator";
int result = comparators[0].compare(v1, v2);
for (int i = 1; i < comparators.length; i++) {
int alt = comparators[i].compare(v1, v2);
if (result != alt && (result >= 0 && alt < 0) || (result <= 0 && alt > 0)) {
throw new IllegalStateException("Property " + name + " is associated with multiple artifacts"
+ " and these artifacts use different version sorting rules and these rules are effectively"
+ " incompatible for the two of versions being compared.\nFirst rule says compare(\""
+ v1
+ "\", \"" + v2 + "\") = " + result
+ "\nSecond rule says compare(\"" + v1 + "\", \"" + v2
+ "\") = " + alt);
}
}
return result;
}
public int getSegmentCount(ArtifactVersion v) {
if (!isAssociated()) {
throw new IllegalStateException("Cannot compare versions for a property with no associations");
}
VersionComparator[] comparators = lookupComparators();
assert comparators.length >= 1 : "we have at least one association => at least one comparator";
int result = comparators[0].getSegmentCount(v);
for (int i = 1; i < comparators.length; i++) {
int alt = comparators[i].getSegmentCount(v);
if (result != alt) {
throw new IllegalStateException("Property " + name + " is associated with multiple artifacts"
+ " and these artifacts use different version sorting rules and these rules are effectively"
+ " incompatible for the two of versions being compared.\n"
+ "First rule says getSegmentCount(\""
+ v + "\") = " + result
+ "\nSecond rule says getSegmentCount(\"" + v + "\") = "
+ alt);
}
}
return result;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy