diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c82242..3cf670e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +### Added + +- The `SemanticVersion.parse(String coreVersion, String preReleaseIdentifier)` and + `SemanticVersion.parse(String coreVersion, boolean snapshot)` convenience methods. + ## [4.3.0] - 2024-05-25 ### Added diff --git a/README.md b/README.md index 56d57aa..ef08015 100644 --- a/README.md +++ b/README.md @@ -194,7 +194,7 @@ assertThat(version1.compareTo(version2)).isEqualTo(-1); Pre-release and build metadata is supported, and a "v" prefix is ignored. ```java -final SemanticVersion version = SemanticVersion.parse("v1.2.3-beta.1+56709") +final SemanticVersion version = SemanticVersion.parse("v1.2.3-beta.1+56709"); assertThat(version.getOriginalVersion()).isEqualTo("v1.2.3-beta.1+56709"); assertThat(version).hasToString("v1.2.3-beta.1+56709"); @@ -208,6 +208,53 @@ assertThat(version.getPreReleaseIdentifiers()).containsExactly("beta", "1"); assertThat(version.getBuild()).containsExactly("56709"); ``` +A convenience parsing method is provided for the common case of specifying a core version +and a separate pre-release identifier. + +```java +final SemanticVersion version = SemanticVersion.parse("1.2.3", "beta.1"); + +assertThat(version.getOriginalVersion()).isEqualTo("1.2.3-beta.1"); +assertThat(version).hasToString("1.2.3-beta.1"); +assertThat(version.getNormalizedVersion()).isEqualTo("1.2.3-beta.1"); +assertThat(version.getCoreVersion()).isEqualTo("1.2.3"); +assertThat(version.isPreRelease()).isTrue(); +assertThat(version.getMajor()).isEqualTo(1); +assertThat(version.getMinor()).isEqualTo(2); +assertThat(version.getPatch()).isEqualTo(3); +assertThat(version.getPreReleaseIdentifiers()).containsExactly("beta", "1"); +``` + +Another convenience parsing method is provided to accommodate snapshot builds. This method takes a core version +and a flag to indicate whether the version represents a snapshot. Snapshot versions are given a pre-release +component which is the number of milliseconds since the Unix Epoch. + +```java +final SemanticVersion version1 = SemanticVersion("1.2.3", true); + +assertThat(version1.getOriginalVersion()).isEqualTo("1.2.3-1717386681940"); +assertThat(version1).hasToString("1.2.3-1717386681940"); +assertThat(version1.getNormalizedVersion()).isEqualTo("1.2.3-1717386681940"); +assertThat(version1.getCoreVersion()).isEqualTo("1.2.3"); +assertThat(version1.isPreRelease()).isTrue(); +assertThat(version1.getMajor()).isEqualTo(1); +assertThat(version1.getMinor()).isEqualTo(2); +assertThat(version1.getPatch()).isEqualTo(3); +assertThat(version1.getPreReleaseIdentifiers()).containsExactly("1717386681940"); + +final SemanticVersion version2 = SemanticVersion("1.2.3", false); + +assertThat(version2.getOriginalVersion()).isEqualTo("1.2.3"); +assertThat(version2).hasToString("1.2.3"); +assertThat(version2.getNormalizedVersion()).isEqualTo("1.2.3"); +assertThat(version2.getCoreVersion()).isEqualTo("1.2.3"); +assertThat(version2.isPreRelease()).isFalse(); +assertThat(version2.getMajor()).isEqualTo(1); +assertThat(version2.getMinor()).isEqualTo(2); +assertThat(version2.getPatch()).isEqualTo(3); +assertThat(version2.getPreReleaseIdentifiers()).isEmpty(); +``` + ### Calendar Versioning Support is provided for parsing calendar versions. diff --git a/src/main/java/org/cthing/versionparser/semver/SemanticVersion.java b/src/main/java/org/cthing/versionparser/semver/SemanticVersion.java index 9092344..e329139 100644 --- a/src/main/java/org/cthing/versionparser/semver/SemanticVersion.java +++ b/src/main/java/org/cthing/versionparser/semver/SemanticVersion.java @@ -43,6 +43,7 @@ import java.math.BigInteger; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Objects; @@ -146,7 +147,7 @@ private SemanticVersion(final String version, final int major, final int minor, * Parses the specified version string and returns a new instance of this class. * * @param version Version string to parse - * @return Version object + * @return Semantic version object * @throws VersionParsingException if there is a problem parsing the version */ public static SemanticVersion parse(final String version) throws VersionParsingException { @@ -166,6 +167,47 @@ public static SemanticVersion parse(final String version) throws VersionParsingE return new SemanticVersion(trimmedVersion, major, minor, patch, preRelease, build); } + /** + * Constructs a semantic version from the specified core version (i.e. major.minor.patch) and the specified + * pre-release identifier. Calling this method is equivalent to calling the {@link #parse(String)} method + * with a string in the format {@code major.minor.patch-preReleaseIdentifier}. + * + * @param coreVersion Core version in the format {@code major.minor.patch} + * @param preReleaseIdentifier Pre-release portion of the semantic version. If a blank string is specified, + * no pre-release identifier will be added to the returned semantic version. + * @return Semantic version object + * @throws VersionParsingException if there is a problem parsing the version + */ + @SuppressWarnings("ParameterHidesMemberVariable") + public static SemanticVersion parse(final String coreVersion, final String preReleaseIdentifier) + throws VersionParsingException { + return parse(preReleaseIdentifier.isBlank() + ? coreVersion + : coreVersion.trim() + "-" + preReleaseIdentifier.trim()); + } + + /** + * Constructs a semantic version from the specified core version (i.e. major.minor.patch) and a flag indicating + * whether the version represents a release or snapshot. If {@code snapshot} is {@code false}, a semantic + * version is constructed using only the core version. If {@code snapshot} is {@code true}, a pre-release + * identifier is appended to the core version. The pre-release identifier is the number of milliseconds since + * the Unix Epoch. Calling this method is equivalent to calling the {@link #parse(String)} method with a string + * in the format {@code major.minor.patch-preReleaseIdentifier} where {@code preReleaseIdentifier} is equal to + * {@code new Date().getTime()}. + * + * @param coreVersion Core version in the format {@code major.minor.patch} + * @param snapshot If {@code true}, a pre-release identifier equal to the current Unix time in milliseconds + * is appended to the core version. If {@code false}, no pre-release identifier is appended to the core + * version. + * @return Semantic version object + * @throws VersionParsingException if there is a problem parsing the version + */ + @SuppressWarnings("ParameterHidesMemberVariable") + public static SemanticVersion parse(final String coreVersion, final boolean snapshot) + throws VersionParsingException { + return snapshot ? parse(coreVersion, Long.toString(new Date().getTime())) : parse(coreVersion); + } + /** * Obtains the major version number. * diff --git a/src/test/java/org/cthing/versionparser/semver/SemanticVersionTest.java b/src/test/java/org/cthing/versionparser/semver/SemanticVersionTest.java index d2da935..4b26c59 100644 --- a/src/test/java/org/cthing/versionparser/semver/SemanticVersionTest.java +++ b/src/test/java/org/cthing/versionparser/semver/SemanticVersionTest.java @@ -35,32 +35,34 @@ public class SemanticVersionTest { + private static final List EMPTY = List.of(); + enum Order { LT, EQ, GT, } - static Stream parsingProvider() { + static Stream parsingSingleProvider() { return Stream.of( - arguments("1.2.3", "1.2.3", "1.2.3", "1.2.3", 1, 2, 3, List.of(), List.of()), - arguments(" 1.2.3 ", "1.2.3", "1.2.3", "1.2.3", 1, 2, 3, List.of(), List.of()), - arguments("1.2.3", "1.2.3", "1.2.3", "1.2.3", 1, 2, 3, List.of(), List.of()), - arguments("v1.2.3", "v1.2.3", "1.2.3", "1.2.3", 1, 2, 3, List.of(), List.of()), - arguments("1.2.3-abc", "1.2.3-abc", "1.2.3-abc", "1.2.3", 1, 2, 3, List.of("abc"), List.of()), + arguments("1.2.3", "1.2.3", "1.2.3", "1.2.3", 1, 2, 3, EMPTY, EMPTY), + arguments(" 1.2.3 ", "1.2.3", "1.2.3", "1.2.3", 1, 2, 3, EMPTY, EMPTY), + arguments("1.2.3", "1.2.3", "1.2.3", "1.2.3", 1, 2, 3, EMPTY, EMPTY), + arguments("v1.2.3", "v1.2.3", "1.2.3", "1.2.3", 1, 2, 3, EMPTY, EMPTY), + arguments("1.2.3-abc", "1.2.3-abc", "1.2.3-abc", "1.2.3", 1, 2, 3, List.of("abc"), EMPTY), arguments("1.2.3-abc.2.foo", "1.2.3-abc.2.foo", "1.2.3-abc.2.foo", "1.2.3", 1, 2, 3, - List.of("abc", "2", "foo"), List.of()), - arguments("1.2.3+1234", "1.2.3+1234", "1.2.3+1234", "1.2.3", 1, 2, 3, List.of(), List.of("1234")), + List.of("abc", "2", "foo"), EMPTY), + arguments("1.2.3+1234", "1.2.3+1234", "1.2.3+1234", "1.2.3", 1, 2, 3, EMPTY, List.of("1234")), arguments("1.2.3-abc+1234", "1.2.3-abc+1234", "1.2.3-abc+1234", "1.2.3", 1, 2, 3, List.of("abc"), List.of("1234")) ); } @ParameterizedTest - @MethodSource("parsingProvider") - public void testParsing(final String versionStr, final String rep, final String value, final String core, - final int major, final int minor, final int patch, final List preRelease, - final List build) throws VersionParsingException { + @MethodSource("parsingSingleProvider") + public void testSingleParsing(final String versionStr, final String rep, final String value, final String core, + final int major, final int minor, final int patch, final List preRelease, + final List build) throws VersionParsingException { final SemanticVersion version = SemanticVersion.parse(versionStr); assertThat(version).hasToString(rep).isInstanceOf(Version.class); assertThat(version.getOriginalVersion()).isEqualTo(rep); @@ -73,6 +75,71 @@ public void testParsing(final String versionStr, final String rep, final String assertThat(version.getBuild()).isEqualTo(build); } + static Stream parsingPreReleaseProvider() { + return Stream.of( + arguments("1.2.3", "", "1.2.3", "1.2.3", "1.2.3", 1, 2, 3, EMPTY), + arguments("1.2.3", " ", "1.2.3", "1.2.3", "1.2.3", 1, 2, 3, EMPTY), + arguments(" 1.2.3 ", "", "1.2.3", "1.2.3", "1.2.3", 1, 2, 3, EMPTY), + arguments("1.2.3", "", "1.2.3", "1.2.3", "1.2.3", 1, 2, 3, EMPTY), + arguments("v1.2.3", "", "v1.2.3", "1.2.3", "1.2.3", 1, 2, 3, EMPTY), + arguments("1.2.3", "abc", "1.2.3-abc", "1.2.3-abc", "1.2.3", 1, 2, 3, List.of("abc")), + arguments("1.2.3", "abc.2.foo", "1.2.3-abc.2.foo", "1.2.3-abc.2.foo", "1.2.3", 1, 2, 3, + List.of("abc", "2", "foo")) + ); + } + + @ParameterizedTest + @MethodSource("parsingPreReleaseProvider") + public void testPreReleaseParsing(final String baseVersion, final String preReleaseIdentifier, final String rep, + final String value, final String core, final int major, final int minor, + final int patch, final List preRelease) + throws VersionParsingException { + final SemanticVersion version = SemanticVersion.parse(baseVersion, preReleaseIdentifier); + assertThat(version).hasToString(rep).isInstanceOf(Version.class); + assertThat(version.getOriginalVersion()).isEqualTo(rep); + assertThat(version.getCoreVersion()).isEqualTo(core); + assertThat(version.getNormalizedVersion()).isEqualTo(value); + assertThat(version.getMajor()).isEqualTo(major); + assertThat(version.getMinor()).isEqualTo(minor); + assertThat(version.getPatch()).isEqualTo(patch); + assertThat(version.getPreReleaseIdentifiers()).isEqualTo(preRelease); + assertThat(version.getBuild()).isEmpty(); + } + + static Stream parsingSnapshotProvider() { + return Stream.of( + arguments("1.2.3", false, "1\\.2\\.3", "1\\.2\\.3", "1.2.3", 1, 2, 3), + arguments(" 1.2.3 ", false, "1\\.2\\.3", "1\\.2\\.3", "1.2.3", 1, 2, 3), + arguments("v1.2.3", false, "v1\\.2\\.3", "1\\.2\\.3", "1.2.3", 1, 2, 3), + arguments("1.2.3", true, "1\\.2\\.3-\\d+", "1\\.2\\.3-\\d+", "1.2.3", 1, 2, 3), + arguments(" v1.2.3 ", true, "v1\\.2\\.3-\\d+", "1\\.2\\.3-\\d+", "1.2.3", 1, 2, 3) + ); + } + + @ParameterizedTest + @MethodSource("parsingSnapshotProvider") + public void testSnapshotParsing(final String baseVersion, final boolean snapshot, final String rep, + final String value, final String core, final int major, final int minor, + final int patch) + throws VersionParsingException { + final SemanticVersion version = SemanticVersion.parse(baseVersion, snapshot); + assertThat(version).isInstanceOf(Version.class); + assertThat(version.toString()).matches(rep); + assertThat(version.getOriginalVersion()).matches(rep); + assertThat(version.getCoreVersion()).isEqualTo(core); + assertThat(version.getNormalizedVersion()).matches(value); + assertThat(version.getMajor()).isEqualTo(major); + assertThat(version.getMinor()).isEqualTo(minor); + assertThat(version.getPatch()).isEqualTo(patch); + assertThat(version.getBuild()).isEmpty(); + if (snapshot) { + assertThat(version.getPreReleaseIdentifiers()).hasSize(1); + assertThat(version.getPreReleaseIdentifiers().get(0)).matches("\\d+"); + } else { + assertThat(version.getPreReleaseIdentifiers()).isEmpty(); + } + } + @ParameterizedTest @ValueSource(strings = { "1.Y.3",