diff options
-rwxr-xr-x | api/src/main/java/org/semver/Version.java | 176 | ||||
-rwxr-xr-x | api/src/test/java/org/semver/VersionTest.java | 49 |
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); - } - + } + } |