Skip to content
This repository has been archived by the owner on Sep 26, 2021. It is now read-only.

Commit

Permalink
Merge pull request jeluard#39 from julienledem/fix_pre_version_compar…
Browse files Browse the repository at this point in the history
…ison

Fix pre version comparison.
  • Loading branch information
jeluard committed Aug 28, 2014
2 parents 85b4ccf + 0dcfda5 commit 9c66d07
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 12 deletions.
176 changes: 166 additions & 10 deletions api/src/main/java/org/semver/Version.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
Expand All @@ -28,7 +29,7 @@
/**
*
* Version following semantic defined by <a href="http://semver.org/">Semantic Versioning</a> document.
*
*
*/
@Immutable
public final class Version implements Comparable<Version> {
Expand All @@ -43,13 +44,15 @@ public enum Element {
private static final String FORMAT = "(\\d+)\\.(\\d+)(?:\\.)?(\\d*)(\\.|-|\\+)?([0-9A-Za-z-.]*)?";
private static final Pattern PATTERN = Pattern.compile(Version.FORMAT);

private static final Pattern DIGITS_ONLY = Pattern.compile("\\d+");

private static final String SNAPSHOT_VERSION_SUFFIX = "-SNAPSHOT";

private final int major;
private final int minor;
private final int patch;
private final String separator;
private final String special;
private final Special special;

public Version(@Nonnegative final int major, @Nonnegative final int minor, @Nonnegative final int patch) {
this(major, minor, patch, null, null);
Expand All @@ -70,7 +73,18 @@ public Version(@Nonnegative final int major, @Nonnegative final int minor, @Nonn
this.minor = minor;
this.patch = patch;
this.separator = separator;
this.special = special;
this.special = parseSpecial(special);
}

private Special parseSpecial(String specialString) {
if (specialString == null) {
return null;
}
Special special = new Special(specialString);
if (special.ids.length == 0) {
return null;
}
return special;
}

/**
Expand Down Expand Up @@ -99,7 +113,7 @@ public static Version parse(@Nonnull final String version) {
final String special = matcher.group(5);
return new Version(major, minor, patch, separator, "".equals(special) ? null : special);
}

/**
* @param type
* @return next {@link Version} regarding specified {@link Version.Element}
Expand All @@ -111,11 +125,23 @@ public Version next(@Nonnull final Version.Element element) {

switch (element) {
case MAJOR:
return new Version(this.major+1, 0, 0);
if (special == null || this.minor != 0 || this.patch != 0) {
return new Version(this.major + 1, 0, 0);
} else {
return new Version(this.major, 0, 0);
}
case MINOR:
return new Version(this.major, this.minor+1, 0);
if (special == null || this.patch != 0) {
return new Version(this.major, this.minor + 1, 0);
} else {
return new Version(this.major, this.minor, 0);
}
case PATCH:
return new Version(this.major, this.minor, this.patch+1);
if (special == null) {
return new Version(this.major, this.minor, this.patch + 1);
} else {
return new Version(this.major, this.minor, this.patch);
}
default:
throw new IllegalArgumentException("Unknown element <"+element+">");
}
Expand All @@ -130,7 +156,7 @@ public boolean isStable() {
}

public boolean isSnapshot() {
return this.special != null && this.special.endsWith(Version.SNAPSHOT_VERSION_SUFFIX);
return this.special != null && this.special.isSnapshot();
}

@Override
Expand All @@ -156,6 +182,136 @@ public boolean equals(@Nullable final Object object) {
return (this.special == null) ? other.special == null : this.special.equals(other.special);
}


private static SpecialId parseSpecialId(String id) {
Matcher matcher = DIGITS_ONLY.matcher(id);
if (matcher.matches()) {
return new IntId(Integer.parseInt(id));
} else {
return new StringId(id);
}
}

abstract private static class SpecialId implements Comparable<SpecialId> {

abstract public boolean isSnapshot();

abstract public int compareTo(IntId other);
abstract public int compareTo(StringId other);
}

private static class StringId extends SpecialId {
private final String id;
private StringId(String id) {
this.id = id;
}
@Override
public boolean isSnapshot() {
return id.endsWith(SNAPSHOT_VERSION_SUFFIX);
}

@Override
public int compareTo(SpecialId other) {
return - other.compareTo(this);
}

@Override
public String toString() {
return id;
}
@Override
public int compareTo(IntId other) {
// Numeric identifiers always have lower precedence than non-numeric identifiers.
return 1;
}
@Override
public int compareTo(StringId other) {
return id.compareTo(other.id);
}
}

private static class IntId extends SpecialId {
private final int id;
public IntId(int id) {
this.id = id;
}
@Override
public boolean isSnapshot() {
return false;
}

@Override
public String toString() {
return String.valueOf(id);
}
@Override
public int compareTo(SpecialId other) {
return - other.compareTo(this);
}

@Override
public int compareTo(IntId other) {
return id - other.id;
}
@Override
public int compareTo(StringId other) {
//Numeric identifiers always have lower precedence than non-numeric identifiers.
return -1;
}
}

private static class Special implements Comparable<Special> {
private final SpecialId[] ids;
Special(String s) {
String[] split = s.split("\\.");
ids = new SpecialId[split.length];
for (int i = 0; i < split.length; i++) {
ids[i] = parseSpecialId(split[i]);
}
}

public SpecialId last() {
return ids[ids.length - 1];
}

public boolean isSnapshot() {
return last().isSnapshot();
}

@Override
public int compareTo(Special other) {
int min = Math.min(other.ids.length, ids.length);
for (int i = 0; i < min; i++) {
int c = ids[i].compareTo(other.ids[i]);
if (c != 0) {
return c;
}
}
int max = Math.max(other.ids.length, ids.length);
if (max != min) {
if (ids.length > other.ids.length) {
return 1;
} else {
return -1;
}
}
return 0;
}

@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < ids.length; i++) {
SpecialId s = ids[i];
if (i != 0) {
builder.append(".");
}
builder.append(s);
}
return builder.toString();
}
}

@Override
public int compareTo(final Version other) {
if (equals(other)) {
Expand All @@ -174,9 +330,9 @@ public int compareTo(final Version other) {
if (this.special != null && other.special != null) {
return this.special.compareTo(other.special);
} else if (other.special != null) {
return -1;
return 1;
} else if (this.special != null) {
return 1;
return -1;
} // else handled by previous equals check
}
}
Expand Down
49 changes: 47 additions & 2 deletions api/src/test/java/org/semver/VersionTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
*/
package org.semver;

import java.util.ArrayList;
import java.util.List;

import org.junit.Assert;
import org.junit.Test;

Expand Down Expand Up @@ -54,6 +57,8 @@ public void shouldValidVersionBeParsed() {
Version.parse("1.2-RC-SNAPSHOT");
}



@Test(expected=IllegalArgumentException.class)
public void shouldInvalidVersion1NotBeParsed() {
Version.parse("invalid");
Expand Down Expand Up @@ -103,6 +108,27 @@ public void isNewer() {
Assert.assertTrue(Version.parse("1.0.0Beta").compareTo(Version.parse("1.0.0Alpha")) > 0);
Assert.assertFalse(Version.parse("0.0.0").compareTo(Version.parse("0.0.0")) > 0);
Assert.assertFalse(Version.parse("0.0.0").compareTo(Version.parse("0.0.1")) > 0);
// based on http://semver.org/
// Example: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.
String[] versions = { "1.0.0-alpha", "1.0.0-alpha.1", "1.0.0-alpha.beta", "1.0.0-beta", "1.0.0-beta.2", "1.0.0-beta.11", "1.0.0-rc.1", "1.0.0" };
assertTotalOrder(versions);
}

private void assertTotalOrder(String[] versions) {
List<String> problems = new ArrayList<String>();
for (int i = 0; i < versions.length - 1; i++) {
Version v1 = Version.parse(versions[i]);
for (int j = (i + 1); j < versions.length; j++) {
Version v2 = Version.parse(versions[j]);
int compare = v1.compareTo(v2);
if (compare >= 0 ) {
problems.add(v1 + ( compare == 0 ? " = " : " > ") + v2);
}
}
}
if (problems.size() > 0) {
Assert.fail("incorrect comparisons: " + problems);
}
}

@Test
Expand All @@ -117,6 +143,25 @@ public void next() {
Assert.assertEquals(version.next(Version.Element.PATCH), new Version(major, minor, patch+1));
}

@Test
public void nextFromPre() {
final Version version1 = new Version(1, 0, 0, "-", "rc1");
Assert.assertEquals(new Version(1, 0, 0), version1.next(Version.Element.MAJOR));
Assert.assertEquals(new Version(1, 0, 0), version1.next(Version.Element.MINOR));
Assert.assertEquals(new Version(1, 0, 0), version1.next(Version.Element.PATCH));

final Version version2 = new Version(1, 1, 0, "-", "rc1");
Assert.assertEquals(new Version(2, 0, 0), version2.next(Version.Element.MAJOR));
Assert.assertEquals(new Version(1, 1, 0), version2.next(Version.Element.MINOR));
Assert.assertEquals(new Version(1, 1, 0), version2.next(Version.Element.PATCH));

final Version version3 = new Version(1, 1, 1, "-", "rc1");
Assert.assertEquals(new Version(2, 0, 0), version3.next(Version.Element.MAJOR));
Assert.assertEquals(new Version(1, 2, 0), version3.next(Version.Element.MINOR));
Assert.assertEquals(new Version(1, 1, 1), version3.next(Version.Element.PATCH));
}


@Test(expected=IllegalArgumentException.class)
public void shouldNextWithNullComparisonTypeFail() {
final int major = 1;
Expand All @@ -125,6 +170,6 @@ public void shouldNextWithNullComparisonTypeFail() {
final Version version = new Version(major, minor, patch);

version.next(null);
}
}

}

0 comments on commit 9c66d07

Please sign in to comment.