aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xapi/src/main/java/org/semver/Version.java176
-rwxr-xr-xapi/src/test/java/org/semver/VersionTest.java49
2 files changed, 213 insertions, 12 deletions
diff --git a/api/src/main/java/org/semver/Version.java b/api/src/main/java/org/semver/Version.java
index 51922de..09046b8 100755
--- a/api/src/main/java/org/semver/Version.java
+++ b/api/src/main/java/org/semver/Version.java
@@ -18,6 +18,7 @@ package org.semver;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -28,7 +29,7 @@ import org.apache.commons.lang.StringUtils;
/**
*
* Version following semantic defined by <a href="http://semver.org/">Semantic Versioning</a> document.
- *
+ *
*/
@Immutable
public final class Version implements Comparable<Version> {
@@ -43,13 +44,15 @@ public final class Version implements Comparable<Version> {
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);
@@ -70,7 +73,18 @@ public final class Version implements Comparable<Version> {
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;
}
/**
@@ -99,7 +113,7 @@ public final class Version implements Comparable<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}
@@ -111,11 +125,23 @@ public final class Version implements Comparable<Version> {
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+">");
}
@@ -130,7 +156,7 @@ public final class Version implements Comparable<Version> {
}
public boolean isSnapshot() {
- return this.special != null && this.special.endsWith(Version.SNAPSHOT_VERSION_SUFFIX);
+ return this.special != null && this.special.isSnapshot();
}
@Override
@@ -156,6 +182,136 @@ public final class Version implements Comparable<Version> {
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)) {
@@ -174,9 +330,9 @@ public final class Version implements Comparable<Version> {
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
}
}
diff --git a/api/src/test/java/org/semver/VersionTest.java b/api/src/test/java/org/semver/VersionTest.java
index 76dd9a3..bdefa5a 100755
--- a/api/src/test/java/org/semver/VersionTest.java
+++ b/api/src/test/java/org/semver/VersionTest.java
@@ -16,6 +16,9 @@
*/
package org.semver;
+import java.util.ArrayList;
+import java.util.List;
+
import org.junit.Assert;
import org.junit.Test;
@@ -54,6 +57,8 @@ public class VersionTest {
Version.parse("1.2-RC-SNAPSHOT");
}
+
+
@Test(expected=IllegalArgumentException.class)
public void shouldInvalidVersion1NotBeParsed() {
Version.parse("invalid");
@@ -103,6 +108,27 @@ public class VersionTest {
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
@@ -117,6 +143,25 @@ public class VersionTest {
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;
@@ -125,6 +170,6 @@ public class VersionTest {
final Version version = new Version(major, minor, patch);
version.next(null);
- }
-
+ }
+
}