Skip to content

Commit

Permalink
Add convenience parsing methods to SemanticVersion.
Browse files Browse the repository at this point in the history
  • Loading branch information
baron1405 committed Jun 3, 2024
1 parent 5438c4d commit f51bdaf
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 14 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 48 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand All @@ -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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,32 +35,34 @@

public class SemanticVersionTest {

private static final List<String> EMPTY = List.of();

enum Order {
LT,
EQ,
GT,
}

static Stream<Arguments> parsingProvider() {
static Stream<Arguments> 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<String> preRelease,
final List<String> 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<String> preRelease,
final List<String> build) throws VersionParsingException {
final SemanticVersion version = SemanticVersion.parse(versionStr);
assertThat(version).hasToString(rep).isInstanceOf(Version.class);
assertThat(version.getOriginalVersion()).isEqualTo(rep);
Expand All @@ -73,6 +75,71 @@ public void testParsing(final String versionStr, final String rep, final String
assertThat(version.getBuild()).isEqualTo(build);
}

static Stream<Arguments> 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<String> 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<Arguments> 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",
Expand Down

0 comments on commit f51bdaf

Please sign in to comment.