diff --git a/src/main/java/com/github/zafarkhaja/semver/Version.java b/src/main/java/com/github/zafarkhaja/semver/Version.java new file mode 100644 index 0000000..994446d --- /dev/null +++ b/src/main/java/com/github/zafarkhaja/semver/Version.java @@ -0,0 +1,190 @@ +/* + * The MIT License + * + * Copyright 2012 Zafar Khaja . + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.github.zafarkhaja.semver; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * + * @author Zafar Khaja + */ +public class Version implements Comparable { + + private int major; + private int minor; + private int patch; + private String preRelease; + private String build; + + private static final String NORMAL_VERSION = + "((?\\d+)\\.(?\\d+)\\.(?\\d+))"; + + private static final String PRE_RELEASE_VERSION = + "(?:-(?[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?"; + + private static final String BUILD_VERSION = + "(?:\\+(?[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?"; + + private static final Pattern SEMVER_PATTERN = Pattern.compile( + "^" + NORMAL_VERSION + PRE_RELEASE_VERSION + BUILD_VERSION + "$" + ); + + public Version(String version) { + Matcher matcher = SEMVER_PATTERN.matcher(version); + if (!matcher.matches()) { + throw new IllegalArgumentException( + "Illegal version format" + ); + } + major = Integer.parseInt(matcher.group("major")); + minor = Integer.parseInt(matcher.group("minor")); + patch = Integer.parseInt(matcher.group("patch")); + + preRelease = matcher.group("preRelease"); + build = matcher.group("build"); + } + + public int getMajor() { + return major; + } + + public int getMinor() { + return minor; + } + + public int getPatch() { + return patch; + } + + public String getPreRelease() { + return preRelease; + } + + public String getBuild() { + return build; + } + + public void bumpMajor() { + major = major + 1; + minor = 0; + patch = 0; + } + + public void bumpMinor() { + minor = minor + 1; + patch = 0; + } + + public void bumpPatch() { + patch = patch + 1; + } + + @Override + public int compareTo(Version other) { + int result = compareNormalVersions(other); + if (result == 0 && preRelease != null) { + result = compareAlphaNumericVersions( + preRelease, + other.getPreRelease() + ); + } + if (result == 0 && build != null) { + result = compareAlphaNumericVersions( + build, + other.getBuild() + ); + } + return result; + } + + private int compareNormalVersions(Version other) { + int result = compareInts(major, other.getMajor()); + if (result == 0) { + result = compareInts(minor, other.getMinor()); + if (result == 0) { + result = compareInts(patch, other.getPatch()); + } + } + return result; + } + + private int compareInts(int thisOp, int otherOp) { + return (thisOp == otherOp) ? 0 : ((thisOp > otherOp) ? 1 : -1); + } + + private int compareAlphaNumericVersions(String thisOp, String otherOp) { + String[] thisIdents = thisOp.split("\\."); + String[] otherIdents = otherOp.split("\\."); + + int result = compareIdentifierArrays(thisIdents, otherIdents); + if (result == 0 && thisIdents.length != otherIdents.length) { + result = (thisIdents.length > otherIdents.length) ? 1 : -1; + } + return result; + } + + private int compareIdentifierArrays(String[] thisArr, String[] otherArr) { + int result = 0; + int loopCount = getSmallestArrayLength(thisArr, otherArr); + for (int i = 0; i < loopCount; i++) { + result = compareIdentifiers(thisArr[i], otherArr[i]); + if (result != 0) { + break; + } + } + return result; + } + + private int getSmallestArrayLength(String[] thisArr, String[] otherArr) { + if (thisArr.length <= otherArr.length) { + return thisArr.length; + } else { + return otherArr.length; + } + } + + private int compareIdentifiers(String thisIdent, String otherIdent) { + if (isInt(thisIdent) && isInt(otherIdent)) { + return compareInts( + Integer.parseInt(thisIdent), + Integer.parseInt(otherIdent) + ); + } else if (isInt(thisIdent) || isInt(otherIdent)) { + return isInt(thisIdent) ? -1 : 1; + } else { + return thisIdent.compareTo(otherIdent); + } + } + + private boolean isInt(String str) { + try { + Integer.parseInt(str); + } catch (NumberFormatException e) { + return false; + } + return true; + } +} diff --git a/src/test/java/com/github/zafarkhaja/semver/VersionTest.java b/src/test/java/com/github/zafarkhaja/semver/VersionTest.java new file mode 100644 index 0000000..9588895 --- /dev/null +++ b/src/test/java/com/github/zafarkhaja/semver/VersionTest.java @@ -0,0 +1,160 @@ +/* + * The MIT License + * + * Copyright 2012 Zafar Khaja . + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package com.github.zafarkhaja.semver; + +import static org.junit.Assert.*; +import org.junit.Test; + +/** + * + * @author Zafar Khaja + */ +public class VersionTest { + + @Test public void + mustConsistOfMajorMinorAndPatchVersions() { + Version version = new Version("1.2.3"); + assertNotNull(version.getMajor()); + assertNotNull(version.getMinor()); + assertNotNull(version.getPatch()); + } + + @Test public void + mustTakeTheFormOfXDotYDotZWhereXyzAreNonNegativeIntegers() { + Version version = new Version("1.2.3"); + assertEquals(1, version.getMajor()); + assertEquals(2, version.getMinor()); + assertEquals(3, version.getPatch()); + } + + @Test public void + shouldAcceptOnlyNonNegativeMajorMinorAndPatchVersions() { + String[] versionStrings = new String[] {"-1.2.3", "1.-2.3", "1.2.-3"}; + for (String illegalVersion : versionStrings) { + try { + Version version = new Version(illegalVersion); + } catch (IllegalArgumentException e) { + continue; + } + fail("An expected exception has not been thrown."); + } + } + + @Test public void + mustIncreaseEachElementNumericallyByIncrementsOfOne() { + Version version = new Version("1.2.3"); + version.bumpPatch(); + assertEquals(4, version.getPatch()); + version.bumpMinor(); + assertEquals(3, version.getMinor()); + version.bumpMajor(); + assertEquals(2, version.getMajor()); + } + + @Test public void + mustResetToZeroMinorAndPatchVersionsWhenMajorVersionIsIncremented() { + Version version = new Version("1.2.3"); + version.bumpMajor(); + assertEquals(2, version.getMajor()); + assertEquals(0, version.getMinor()); + assertEquals(0, version.getPatch()); + } + + @Test public void + mustResetToZeroPatchVersionWhenMinorVersionIsIncremented() { + Version version = new Version("1.2.3"); + version.bumpMinor(); + assertEquals(1, version.getMajor()); + assertEquals(3, version.getMinor()); + assertEquals(0, version.getPatch()); + } + + @Test public void + mayHavePreReleaseVersionFollowingPatchVersionAppendedWithDash() { + Version version = new Version("1.2.3-alpha"); + assertEquals("alpha", version.getPreRelease()); + } + + @Test public void + preReleaseVersionMustCompriseDotSeparatedIdentifiersOfAlphaNumericsAndDash() { + Version version = new Version("1.0.0-x.7.z.92"); + assertEquals("x.7.z.92", version.getPreRelease()); + } + + @Test public void + mayHaveBuildVersionFollowingPatchOrPreReleaseVersionsAppendedWithPlus() { + Version version = new Version("1.2.3+build"); + assertEquals("build", version.getBuild()); + } + + @Test public void + buildVersionMustCompriseDotSeparatedIdentifiersOfAlphaNumericsAndDash() { + Version version = new Version("1.3.7+build.11.e0f985a"); + assertEquals("build.11.e0f985a", version.getBuild()); + } + + @Test public void + mustCompareMajorMinorAndPatchVersionsNumerically() { + Version version = new Version("1.2.3"); + assertEquals(1, version.compareTo(new Version("0.2.3"))); + assertEquals(0, version.compareTo(new Version("1.2.3"))); + assertEquals(-1, version.compareTo(new Version("1.2.4"))); + } + + @Test public void + mustComparePreReleaseAndBuildVersionsByComparingEachIdentifierSeparately() { + Version version1 = new Version("1.3.7+build.2.b8f12d7"); + Version version2 = new Version("1.3.7+build.11.e0f985a"); + assertEquals(-1, version1.compareTo(version2)); + } + + @Test public void + shouldComparePreReleaseVersionsIfNormalVersionsAreEqual() { + Version version1 = new Version("1.3.7-beta"); + Version version2 = new Version("1.3.7-alpha"); + assertEquals(1, version1.compareTo(version2)); + } + + @Test public void + shouldCompareBuildVersionsIfNormalAndPreReleaseVersionsAreEqual() { + Version version1 = new Version("1.3.7-beta+build.1"); + Version version2 = new Version("1.3.7-beta+build.2"); + assertEquals(-1, version1.compareTo(version2)); + } + + @Test public void + shouldCompareAccordingToIdentifiersCountIfCommonIdentifiersAreEqual() { + Version version1 = new Version("1.3.7-beta+build.3"); + Version version2 = new Version("1.3.7-beta+build"); + assertEquals(1, version1.compareTo(version2)); + } + + @Test public void + numericIdentifiersShouldHaveLowerPrecedenceThanNonNumericIdentfiers() { + Version version1 = new Version("1.3.7-beta+build.3"); + Version version2 = new Version("1.3.7-beta+build.a"); + assertEquals(-1, version1.compareTo(version2)); + } +}