From 3c630ebbbc543e7b22b266960951b74af43d2f03 Mon Sep 17 00:00:00 2001 From: Zafar Khaja Date: Mon, 5 Nov 2012 22:05:59 +0400 Subject: [PATCH] Create Version class and Tests So far only basic functionality is implemented, such as parsing version strings, incrementing major, minor and patch versions, as well as comparing one Version to another. --- .../com/github/zafarkhaja/semver/Version.java | 190 ++++++++++++++++++ .../github/zafarkhaja/semver/VersionTest.java | 160 +++++++++++++++ 2 files changed, 350 insertions(+) create mode 100644 src/main/java/com/github/zafarkhaja/semver/Version.java create mode 100644 src/test/java/com/github/zafarkhaja/semver/VersionTest.java 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)); + } +}