From 1b6b30058968b8424240f696c7eae60ad99a5b77 Mon Sep 17 00:00:00 2001 From: Linus Gasser Date: Fri, 28 Aug 2020 12:15:35 +0200 Subject: [PATCH] Adding semver Adds the semver class with its tests. See #226 Once this is merged, #213 and #215 need to be rebased on this. --- .../org/dpppt/backend/sdk/semver/README.md | 25 ++ .../org/dpppt/backend/sdk/semver/Version.java | 249 ++++++++++++++++++ .../backend/sdk/ws/util/SemverTests.java | 134 ++++++++++ 3 files changed, 408 insertions(+) create mode 100644 dpppt-backend-sdk/dpppt-backend-sdk-model/src/main/java/org/dpppt/backend/sdk/semver/README.md create mode 100644 dpppt-backend-sdk/dpppt-backend-sdk-model/src/main/java/org/dpppt/backend/sdk/semver/Version.java create mode 100644 dpppt-backend-sdk/dpppt-backend-sdk-ws/src/test/java/org/dpppt/backend/sdk/ws/util/SemverTests.java diff --git a/dpppt-backend-sdk/dpppt-backend-sdk-model/src/main/java/org/dpppt/backend/sdk/semver/README.md b/dpppt-backend-sdk/dpppt-backend-sdk-model/src/main/java/org/dpppt/backend/sdk/semver/README.md new file mode 100644 index 00000000..44f4f571 --- /dev/null +++ b/dpppt-backend-sdk/dpppt-backend-sdk-model/src/main/java/org/dpppt/backend/sdk/semver/README.md @@ -0,0 +1,25 @@ +# Semver + +## Introduction +This implementation follows the official specification found for [Semver 2.0 ](https://semver.org/). +The regular expression used for matching is taken from the official specification ans was slightly adjusted. +The following changes were made, to allow using extended SemVer: + +- it is allowed to have a prefix specifying the OS used. The following regex is used + > (?:(?ios|android)-)? + +- Only the major version is required. If minor or patch version are not given, a value of `0` i assumed. This was added as a `?` in the original regex for the minor and patch version. + +## IsAndroid/IsIos + +To allow for simple OS testing the following two implementations are added: + +```java +public boolean isAndroid() { + return platform.contains("android") || metaInfo.contains("android"); +} +public boolean isIOS() { + return platform.contains("ios") || metaInfo.contains("ios"); +} +``` +Whereas in SemVer it would be normal to specify further information in the `metaInfo` field, the dp3t clients use the prefix `ios` or `android`. This implementation though should be compatible with a more SemVer approach. diff --git a/dpppt-backend-sdk/dpppt-backend-sdk-model/src/main/java/org/dpppt/backend/sdk/semver/Version.java b/dpppt-backend-sdk/dpppt-backend-sdk-model/src/main/java/org/dpppt/backend/sdk/semver/Version.java new file mode 100644 index 00000000..f732150d --- /dev/null +++ b/dpppt-backend-sdk/dpppt-backend-sdk-model/src/main/java/org/dpppt/backend/sdk/semver/Version.java @@ -0,0 +1,249 @@ +package org.dpppt.backend.sdk.semver; + +import java.util.Objects; +import java.util.regex.Pattern; + +public class Version implements Comparable { + private Integer major; + private Integer minor; + private Integer patch; + private String preReleaseString = ""; + private String metaInfo = ""; + private String platform = ""; + + private final Pattern semVerPattern = + Pattern.compile( + "^(?:(?ios|android)-)?(?0|[1-9]\\d*)(\\.(?0|[1-9]\\d*))?(\\.(?0|[1-9]\\d*))?(?:-(?(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"); + + public Version() {} + + public Version(String versionString) { + if (versionString == null) { + this.setInvalidValue(); + return; + } + this.major = -1; + this.minor = 0; + this.patch = 0; + + var matches = semVerPattern.matcher(versionString.trim()); + if (matches.find()) { + this.major = Integer.parseInt(matches.group("major")); + if (matches.group("minor") != null) { + this.minor = Integer.parseInt(matches.group("minor")); + } + if (matches.group("patch") != null) { + this.patch = Integer.parseInt(matches.group("patch")); + } + if (matches.group("platform") != null) { + this.platform = matches.group("platform"); + } + if (matches.group("prerelease") != null) { + this.preReleaseString = matches.group("prerelease"); + } + if (matches.group("buildmetadata") != null) { + this.metaInfo = matches.group("buildmetadata"); + } + } else { + this.setInvalidValue(); + } + } + + public Version(Integer major, Integer minor, Integer patch) { + this.major = major; + this.minor = minor; + this.patch = patch; + this.preReleaseString = ""; + this.metaInfo = ""; + } + + public Version(Integer major, Integer minor) { + this.major = major; + this.minor = minor; + this.patch = 0; + this.preReleaseString = ""; + this.metaInfo = ""; + } + + public Version(Integer major) { + this.major = major; + this.minor = 0; + this.patch = 0; + this.preReleaseString = ""; + this.metaInfo = ""; + } + + public Version( + Integer major, Integer minor, Integer patch, String preReleaseString, String metaInfo) { + this.major = major; + this.minor = minor; + this.patch = patch; + this.preReleaseString = preReleaseString; + this.metaInfo = metaInfo; + } + + private void setInvalidValue() { + this.major = -1; + this.minor = -1; + this.patch = -1; + this.preReleaseString = ""; + this.metaInfo = ""; + } + + public boolean isValid() { + return major.compareTo(Integer.valueOf(0)) >= 0 + && minor.compareTo(Integer.valueOf(0)) >= 0 + && patch.compareTo(Integer.valueOf(0)) >= 0; + } + + public Integer getMajor() { + return this.major; + } + + public void setMajor(Integer major) { + this.major = major; + } + + public Integer getMinor() { + return this.minor; + } + + public void setMinor(Integer minor) { + this.minor = minor; + } + + public Integer getPatch() { + return this.patch; + } + + public void setPatch(Integer patch) { + this.patch = patch; + } + + public String getPreReleaseString() { + return this.preReleaseString; + } + + public void setPreReleaseString(String preReleaseString) { + this.preReleaseString = preReleaseString; + } + + public String getMetaInfo() { + return this.metaInfo; + } + + public void setMetaInfo(String metaInfo) { + this.metaInfo = metaInfo; + } + + public String getPlatform() { + return this.platform; + } + + public void setPlatform(String platform) { + this.platform = platform; + } + + public Version major(Integer major) { + this.major = major; + return this; + } + + public Version minor(Integer minor) { + this.minor = minor; + return this; + } + + public Version patch(Integer patch) { + this.patch = patch; + return this; + } + + public Version preReleaseString(String preReleaseString) { + this.preReleaseString = preReleaseString; + return this; + } + + public Version metaInfo(String metaInfo) { + this.metaInfo = metaInfo; + return this; + } + + public boolean isPrerelease() { + return !preReleaseString.isEmpty(); + } + + public boolean isAndroid() { + return platform.contains("android") || metaInfo.contains("android"); + } + + public boolean isIOS() { + return platform.contains("ios") || metaInfo.contains("ios"); + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof Version)) { + return false; + } + Version version = (Version) o; + return Objects.equals(major, version.major) + && Objects.equals(minor, version.minor) + && Objects.equals(patch, version.patch) + && Objects.equals(preReleaseString, version.preReleaseString) + && Objects.equals(metaInfo, version.metaInfo) + && Objects.equals(platform, version.platform); + } + + @Override + public int hashCode() { + return Objects.hash(major, minor, patch, preReleaseString, metaInfo); + } + + @Override + public String toString() { + return getMajor() + + "." + + getMinor() + + "." + + getPatch() + + (getPreReleaseString().isEmpty() ? "" : "-" + getPreReleaseString()) + + (getMetaInfo().isEmpty() ? "" : "+" + getMetaInfo()); + } + + @Override + public int compareTo(Version o) { + if (this.major.compareTo(o.major) != 0) { + return this.major.compareTo(o.major); + } + if (this.minor.compareTo(o.minor) != 0) { + return this.minor.compareTo(o.minor); + } + if (this.patch.compareTo(o.patch) != 0) { + return this.patch.compareTo(o.patch); + } + if (this.isPrerelease() && o.isPrerelease()) { + if (this.preReleaseString.compareTo(o.preReleaseString) != 0) { + return this.preReleaseString.compareTo(o.preReleaseString); + } + } else if (this.isPrerelease() && !o.isPrerelease()) { + return -1; + } else if (!this.isPrerelease() && o.isPrerelease()) { + return 1; + } + return 0; + } + + public boolean isSmallerVersionThan(Version other) { + return this.compareTo(other) < 0; + } + + public boolean isLargerVersionThan(Version other) { + return this.compareTo(other) > 0; + } + + public boolean isSameVersionAs(Version other) { + return this.compareTo(other) == 0; + } +} diff --git a/dpppt-backend-sdk/dpppt-backend-sdk-ws/src/test/java/org/dpppt/backend/sdk/ws/util/SemverTests.java b/dpppt-backend-sdk/dpppt-backend-sdk-ws/src/test/java/org/dpppt/backend/sdk/ws/util/SemverTests.java new file mode 100644 index 00000000..5664739f --- /dev/null +++ b/dpppt-backend-sdk/dpppt-backend-sdk-ws/src/test/java/org/dpppt/backend/sdk/ws/util/SemverTests.java @@ -0,0 +1,134 @@ +package org.dpppt.backend.sdk.ws.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import java.util.List; +import org.dpppt.backend.sdk.semver.Version; +import org.junit.Test; + +public class SemverTests { + + @Test + public void testToString() throws Exception { + var v = new Version("ios-1.1.3-test+meta"); + assertEquals("1.1.3-test+meta", v.toString()); + v = new Version("1.1.3+meta"); + assertEquals("1.1.3+meta", v.toString()); + v = new Version("ios-1.1.3-meta"); + assertEquals("1.1.3-meta", v.toString()); + v = new Version("ios-1.1.3"); + assertEquals("1.1.3", v.toString()); + v = new Version("1.1.3"); + assertEquals("1.1.3", v.toString()); + } + + @Test + public void testVersionFromString() throws Exception { + var cases = + List.of( + new Version("ios-0.1.0"), + new Version("android-0.1.1"), + new Version("0.2.0"), + new Version("1.0.0-prerelease"), + new Version("1.0.0"), + new Version("1.0.1+ios")); + for (int i = 0; i < cases.size(); i++) { + var currentVersion = cases.get(i); + assertTrue(currentVersion.isSameVersionAs(currentVersion)); + for (int j = 0; j < i; j++) { + var olderVersion = cases.get(j); + assertTrue(currentVersion.isLargerVersionThan(olderVersion)); + } + } + var releaseVersion = new Version("1.0.0"); + var metaInfoVersion = new Version("1.0.0+ios"); + assertTrue(releaseVersion.isSameVersionAs(metaInfoVersion)); + assertNotEquals(metaInfoVersion, releaseVersion); + var sameIosVersion = new Version("1.0.0+ios"); + assertEquals(sameIosVersion, metaInfoVersion); + } + + @Test + public void testPlatform() throws Exception { + var iosNonStandard = new Version("ios-1.0.0"); + var iosStandard = new Version("1.0.0+ios"); + assertTrue(iosNonStandard.isIOS()); + assertTrue(iosStandard.isIOS()); + assertFalse(iosNonStandard.isAndroid()); + assertFalse(iosStandard.isAndroid()); + + var androidNonStandard = new Version("android-1.0.0"); + var androidStandard = new Version("1.0.0+android"); + assertFalse(androidNonStandard.isIOS()); + assertFalse(androidStandard.isIOS()); + assertTrue(androidNonStandard.isAndroid()); + assertTrue(androidStandard.isAndroid()); + + var random = new Version("1.0.0"); + assertFalse(random.isAndroid()); + assertFalse(random.isIOS()); + } + + @Test + public void testVersionFromExplicit() throws Exception { + var cases = + List.of( + new Version(0, 1, 0), + new Version(0, 1, 1), + new Version(0, 2, 0), + new Version(1, 0, 0, "prerelease", ""), + new Version(1, 0, 0), + new Version(1, 0, 1, "", "ios")); + for (int i = 0; i < cases.size(); i++) { + var currentVersion = cases.get(i); + assertTrue(currentVersion.isSameVersionAs(currentVersion)); + for (int j = 0; j < i; j++) { + var olderVersion = cases.get(j); + assertTrue(currentVersion.isLargerVersionThan(olderVersion)); + } + } + var releaseVersion = new Version(1, 0, 0); + var metaInfoVersion = new Version(1, 0, 0, "", "ios"); + assertTrue(releaseVersion.isSameVersionAs(metaInfoVersion)); + assertNotEquals(metaInfoVersion, releaseVersion); + var sameIosVersion = new Version(1, 0, 0, "", "ios"); + assertEquals(sameIosVersion, metaInfoVersion); + } + + @Test + public void testMissingMinorOrPatch() throws Exception { + var apiLevel = "29"; + var iosVersion = "13.6"; + var apiLevelWithMeta = "29+test"; + var iosVersionWithMeta = "13.6+test"; + var apiLevelVersion = new Version(apiLevel); + assertTrue( + apiLevelVersion.getMajor().equals(29) + && apiLevelVersion.getMinor().equals(0) + && apiLevelVersion.getPatch().equals(0)); + + var iosVersionVersion = new Version(iosVersion); + assertTrue( + iosVersionVersion.getMajor() == 13 + && iosVersionVersion.getMinor() == 6 + && iosVersionVersion.getPatch() == 0); + + var apiLevelWithMetaVersion = new Version(apiLevelWithMeta); + assertTrue( + apiLevelWithMetaVersion.getMajor().equals(29) + && apiLevelWithMetaVersion.getMinor().equals(0) + && apiLevelWithMetaVersion.getPatch().equals(0) + && apiLevelWithMetaVersion.getMetaInfo().equals("test")); + + var iosVersionVersionMeta = new Version(iosVersionWithMeta); + + assertTrue( + iosVersionVersionMeta.getMajor().equals(13) + && iosVersionVersionMeta.getMinor().equals(6) + && iosVersionVersionMeta.getPatch().equals(0) + && iosVersionVersionMeta.getMetaInfo().equals("test")); + } +}