Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement usage of SemVer ordering rules for resolving previous artifact #172

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,9 @@ private Artifact getComparisonArtifact(MavenParameters mavenParameters, PluginPa
VersionRange versionRange;
try {
versionRange = VersionRange.createFromVersionSpec(mavenParameters.getVersionRangeWithProjectVersion());
if (pluginParameters.getParameterParam().isUseSemanticVersioningOrderingToResolvePreviousVersion()) {
versionRange = SemanticVersionRange.getFromExistingVersionRange(versionRange);
}
} catch (InvalidVersionSpecificationException e) {
throw new MojoFailureException("Invalid version versionRange: " + e.getMessage(), e);
}
Expand Down
10 changes: 10 additions & 0 deletions japicmp-maven-plugin/src/main/java/japicmp/maven/Parameter.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public class Parameter {
private List<String> excludeModules;
@org.apache.maven.plugins.annotations.Parameter(required = false, defaultValue = "false")
private boolean breakBuildBasedOnSemanticVersioningForMajorVersionZero;
@org.apache.maven.plugins.annotations.Parameter(required = false, defaultValue = "false")
private boolean useSemanticVersioningOrderingToResolvePreviousVersion;

public String getNoAnnotations() {
return noAnnotations;
Expand Down Expand Up @@ -292,4 +294,12 @@ public boolean isBreakBuildBasedOnSemanticVersioningForMajorVersionZero() {
public void setBreakBuildBasedOnSemanticVersioningForMajorVersionZero(boolean breakBuildBasedOnSemanticVersioningForMajorVersionZero) {
this.breakBuildBasedOnSemanticVersioningForMajorVersionZero = breakBuildBasedOnSemanticVersioningForMajorVersionZero;
}

public boolean isUseSemanticVersioningOrderingToResolvePreviousVersion() {
return useSemanticVersioningOrderingToResolvePreviousVersion;
}

public void setUseSemanticVersioningOrderingToResolvePreviousVersion(boolean useSemanticVersioningOrderingToResolvePreviousVersion) {
this.useSemanticVersioningOrderingToResolvePreviousVersion = useSemanticVersioningOrderingToResolvePreviousVersion;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package japicmp.maven;

import japicmp.versioning.SemanticVersion;
import org.apache.maven.artifact.versioning.ArtifactVersion;

public class SemanticArtifactVersion implements ArtifactVersion {
private SemanticVersion comparable;

public SemanticArtifactVersion(String version) {
parseVersion(version);
}

public SemanticArtifactVersion(ArtifactVersion otherVersion) {
this(otherVersion.toString());
}

public int compareTo(ArtifactVersion otherVersion) {
if(otherVersion instanceof SemanticArtifactVersion) {
return this.comparable.compareTo(((SemanticArtifactVersion) otherVersion).comparable);
}
else {
try {
// If comparing to a non-semantic version, try to transform it into semantic and compare.
SemanticArtifactVersion otherSemVer = new SemanticArtifactVersion(otherVersion.toString());
return this.comparable.compareTo(otherSemVer.comparable);
}
catch(Exception e) {
// Else, other version not semantic. Consider it older by default.
return 1;
}
}
}

public int getMajorVersion() {
return comparable.getMajor();
}

public int getMinorVersion() {
return comparable.getMinor();
}

public int getIncrementalVersion() {
return comparable.getPatch();
}

public int getBuildNumber() {
return 0;
}

public String getQualifier() {
return comparable.getPreReleaseIdentifier();
}

public final void parseVersion(String version) {
comparable = new SemanticVersion(version);
}

@Override
public String toString() {
return comparable.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package japicmp.maven;

import java.util.List;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.apache.maven.artifact.versioning.Restriction;
import org.apache.maven.artifact.versioning.VersionRange;

public class SemanticVersionRange {

/**
Return a semantic version range from an existing maven VersionRange, by converting .

This is a bit of a dirty hack, necessary because :
- VersionRange hardcodes construction of DefaultArtifactVersion (not making it possible to use other implementations of ArtifactVersion)
- All the internals of VersionRange are private, so it cannot be extended easily.

This is not reliable either : since it requires an existing maven range to have been created, and VersionRange.parseRestriction checks ordering of restriction bounds (using DefaultArtifactVersion order), some ranges could not be generated when the ordering differs.

However, in the current case, it is only meant to be used on the versionRangeWithProjectVersion setting, which has only one bound : (,${project.version}).
Therefore this implementation is enough, and avoids rewriting all of VersionRange's parsing logic.
*/
public static VersionRange getFromExistingVersionRange(VersionRange existingRange) {
if(existingRange.getRecommendedVersion() != null && !(existingRange.getRecommendedVersion() instanceof SemanticArtifactVersion)) {
throw new UnsupportedOperationException("Cannot convert an already decided version range to semantic in current implementation.");
}
VersionRange newRange = existingRange.cloneOf();
List<Restriction> restrictions = newRange.getRestrictions();
for(int i = 0; i < restrictions.size(); i++) {
Restriction currentRestriction = restrictions.get(i);

restrictions.set(i, new Restriction(
new SemanticArtifactVersion(currentRestriction.getLowerBound()),
currentRestriction.isLowerBoundInclusive(),
new SemanticArtifactVersion(currentRestriction.getUpperBound()),
currentRestriction.isUpperBoundInclusive()
));
}

return newRange;
}

}
6 changes: 6 additions & 0 deletions japicmp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
<artifactId>airline</artifactId>
<version>0.7</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
139 changes: 137 additions & 2 deletions japicmp/src/main/java/japicmp/versioning/SemanticVersion.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package japicmp.versioning;

import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Splitter;
import java.util.List;
import java.util.ArrayList;

public class SemanticVersion {
public class SemanticVersion implements Comparable<SemanticVersion> {
private final int major;
private final int minor;
private final int patch;
private final List<String> preReleaseIdentifiers;
private final String buildMetadata;

public enum ChangeType {
MAJOR(3),
Expand All @@ -28,6 +34,54 @@ public SemanticVersion(int major, int minor, int patch) {
this.major = major;
this.minor = minor;
this.patch = patch;
this.preReleaseIdentifiers = new ArrayList<String>();
this.buildMetadata = null;
}

/**
Parses a semantic version string formatted as per http://semver.org/spec/v2.0.0.html
*/
public SemanticVersion(String versionDescription) {

int firstDotPos = versionDescription.indexOf('.');
int secondDotPos = versionDescription.indexOf('.', firstDotPos + 1);
int hyphenPos = versionDescription.indexOf('-', secondDotPos + 1);
int plusSignPos = -1;

int patchEndPos = -1;
int idListEndPos = -1;

this.major = Integer.valueOf(versionDescription.substring(0, firstDotPos));
this.minor = Integer.valueOf(versionDescription.substring(firstDotPos + 1, secondDotPos));

if (hyphenPos != -1) {
patchEndPos = hyphenPos;
plusSignPos = versionDescription.indexOf('+', hyphenPos + 1);
}
else {
plusSignPos = versionDescription.indexOf('+', secondDotPos + 1);
}

if (plusSignPos != -1) {
if (hyphenPos != -1) {
idListEndPos = plusSignPos;
}
else {
patchEndPos = plusSignPos;
}
}
else {
if (hyphenPos != -1) {
idListEndPos = versionDescription.length();
}
else {
patchEndPos = versionDescription.length();
}
}

this.patch = Integer.valueOf(versionDescription.substring(secondDotPos + 1, patchEndPos));
this.preReleaseIdentifiers = (hyphenPos == -1) ? new ArrayList<String>() : Splitter.on('.').splitToList(versionDescription.substring(hyphenPos + 1, idListEndPos));
this.buildMetadata = (plusSignPos == -1) ? null : versionDescription.substring(plusSignPos + 1);
}

public int getMajor() {
Expand All @@ -41,6 +95,10 @@ public int getMinor() {
public int getPatch() {
return patch;
}

public String getPreReleaseIdentifier() {
return Joiner.on('.').join(preReleaseIdentifiers);
}

@Override
public boolean equals(Object o) {
Expand Down Expand Up @@ -73,6 +131,83 @@ public int hashCode() {

@Override
public String toString() {
return major + "." + minor + "." + patch;
return major + "." + minor + "." + patch +
(preReleaseIdentifiers.size() > 0 ? "-" + Joiner.on('.').join(preReleaseIdentifiers) : "") +
(buildMetadata != null ? "+" + buildMetadata : "");
}

private boolean isInteger(String str) {
for(int i = 0; i < str.length(); i++)
{
if(!Character.isDigit(str.charAt(i))) {
return false;
}
}
return true;
}

/**
Compares two semantic version following the specifications at http://semver.org/spec/v2.0.0.html#spec-item-11
*/
@Override
public int compareTo(SemanticVersion other) {
if(this.major == other.major) {
if(this.minor == other.minor) {
if(this.patch == other.patch) {
// "When major, minor, and patch are equal, a pre-release version has lower precedence than a normal version" as per spec.
if(this.preReleaseIdentifiers.size() == 0 && other.preReleaseIdentifiers.size() > 0) {
return 1;
}
else if(this.preReleaseIdentifiers.size() > 0 && other.preReleaseIdentifiers.size() == 0) {
return -1;
}

// Compare all ids field by field, stopping at first difference.
for(int i = 0; i < Math.min(this.preReleaseIdentifiers.size(), other.preReleaseIdentifiers.size()); i++) {
String thisId = this.preReleaseIdentifiers.get(i);
String otherId = other.preReleaseIdentifiers.get(i);

if (isInteger(thisId)) {
if (isInteger(otherId)) {
// Two integer identifiers : compare them.
int difference = Integer.valueOf(thisId) - Integer.valueOf(otherId);
if (difference != 0) {
return difference;
}
}
else {
// "Numeric identifiers always have lower precedence than non-numeric identifiers" as per spec.
return -1;
}
}
else {
if (isInteger(otherId)) {
// Same as above : int greater than string.
return 1;
}
else {
// Compare strings in lexicographic order.
int difference = thisId.compareTo(otherId);
if (difference != 0) {
return difference;
}
}
}
}

// "A larger set of pre-release fields has a higher precedence than a smaller set, if all of the preceding identifiers are equal", as per spec.
return this.preReleaseIdentifiers.size() - other.preReleaseIdentifiers.size();
}
else {
return this.patch - other.patch;
}
}
else {
return this.minor - other.minor;
}
}
else {
return this.major - other.major;
}
}
}
Loading