Added additional exception to parsing for SEMVER, so build information can be following the version numbers using '.'.

Not all projects follow semver in their version information, and if we are to use any sort of enforcable semantics for versioning, we have to permit PARSING more relaxed formats (using sensible defaults).  When *writing* this information, it will be compatible and correct
master
nathan 2020-08-05 02:36:49 +02:00
parent 2a76324345
commit 447cdfb68e
9 changed files with 363 additions and 286 deletions

View File

@ -1,4 +1,4 @@
- Version -
- Version - Java Semantic Versioning with patch info exclusion
https://git.dorkbox.com/dorkbox/Version
Copyright 2018 - MIT License
dorkbox, llc
@ -7,4 +7,3 @@
Larry Bordowitz <lbordowitz@yahoo-inc.com>
Martin Rüegg <martin.rueegg@bristolpound.org> <martin.rueegg@metaworx.ch>
Zafar Khaja <zafarkhaja@gmail.com>
Java Semantic Versioning with patch info exclusion

View File

@ -1,13 +1,20 @@
Java Semantic Versioning
============================
A Java implementation of the Semantic Versioning Specification, as per ([http://semver.org](http://semver.org/spec/v2.0.0.html)) **modified** to exclude the patch version information if zero and not specified. This is a breaking change when comparing strings to the original Semantic Versioning Specification by Tom Preston-Werner. When comparing Version objects, it is non-breaking.
A Java implementation of the Semantic Versioning Specification, as per ([http://semver.org](http://semver.org/spec/v2.0.0.html
)) **modified** to exclude the patch version information if zero or not specified. It is additionally **modified** to permit parsing
build information after a final '.', such that 4.1.0.Final will parse a build as "Final".
This is a breaking change when comparing strings to the original Semantic Versioning Specification by Tom Preston-Werner. When comparing
Version objects, it is non-breaking, and is breaking when writing Version strings.
### Versioning ###
Semantic Versioning Specification (SemVer v2.0.0-dorkbox)
Modified to exclude patch version information if zero.
1. Modified to exclude patch version information.
1. Modified to permit reading build metadata after a final . (with, or without the patch number)
Creative Commons - CC BY 3.0, by Tom Preston-Werner.
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in ([RFC 2119](http://tools.ietf.org/html/rfc2119)).
@ -60,7 +67,7 @@ To install the Java SemanticVersioning library add the following dependency to y
<dependency>
<groupId>com.dorkbox</groupId>
<artifactId>Version</artifactId>
<version>1.0</version>
<version>1.1</version>
</dependency>
~~~
@ -68,7 +75,7 @@ To install the Java SemanticVersioning library add the following dependency to y
~~~ xml
dependencies {
...
compile "com.dorkbox:Version:1.0.0"
compile "com.dorkbox:Version:1.1"
}
~~~

View File

@ -1,237 +0,0 @@
/*
* Copyright 2018 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.nio.file.Paths
import java.time.Instant
buildscript {
// load properties from custom location
def propsFile = Paths.get("${projectDir}/../../gradle.properties").normalize().toFile()
if (propsFile.canRead()) {
println("Loading custom property data from: ${propsFile}")
def props = new Properties()
propsFile.withInputStream {props.load(it)}
props.each {key, val -> project.ext.set(key, val)}
}
else {
ext.sonatypeUsername = ""
ext.sonatypePassword = ""
}
repositories {
maven {url "https://plugins.gradle.org/m2/"}
}
dependencies {
// for license sources
classpath "gradle.plugin.com.dorkbox:Licensing:1.2.2"
classpath "gradle.plugin.com.dorkbox:Licensing:1.2.2:sources"
}
}
plugins {
id 'java'
id 'maven-publish'
id 'signing'
id "org.jetbrains.kotlin.jvm" version "1.2.60"
// close and release on sonatype
id 'io.codearte.nexus-staging' version '0.11.0'
id "com.dorkbox.CrossCompile" version "1.0.1"
id "com.dorkbox.VersionUpdate" version "1.2"
}
// this is the only way to also get the source code for IDE auto-complete
apply plugin: "com.dorkbox.Licensing"
project.description = 'Java Semantic Versioning with patch info exclusion'
project.group = 'com.dorkbox'
project.version = '1.0'
project.ext.name = 'Version'
project.ext.url = 'https://git.dorkbox.com/dorkbox/Version'
sourceCompatibility = 1.6
targetCompatibility = 1.6
licensing {
license(License.MIT) {
author 'dorkbox, llc'
author 'G. Richard Bellamy'
author 'Kenduck'
author 'Larry Bordowitz <lbordowitz@yahoo-inc.com>'
author 'Martin Rüegg <martin.rueegg@bristolpound.org> <martin.rueegg@metaworx.ch>'
author 'Zafar Khaja <zafarkhaja@gmail.com>'
url project.ext.url
note project.description
}
}
tasks.withType(JavaCompile) {
options.encoding = 'UTF-8'
}
tasks.withType(Jar) {
duplicatesStrategy DuplicatesStrategy.FAIL
manifest {
attributes['Implementation-Version'] = project.version
attributes['Build-Date'] = Instant.now().toString()
}
}
repositories {
jcenter()
}
dependencies {
testCompile group: 'junit', name: 'junit', version:'4.12'
}
sourceSets {
main {
java {
setSrcDirs Collections.singletonList('src')
}
}
test {
java {
setSrcDirs Collections.singletonList('test')
}
}
}
/////////////////////////////
//// Maven Publishing + Release
/////////////////////////////
task sourceJar(type: Jar) {
description = "Creates a JAR that contains the source code."
from sourceSets.main.allSource
classifier = "sources"
}
task javaDocJar(type: Jar) {
description = "Creates a JAR that contains the javadocs."
classifier = "javadoc"
}
// for testing, we don't publish to maven central, but only to local maven
publishing {
publications {
SemanticVersioning(MavenPublication) {
from components.java
artifact(javaDocJar)
artifact(sourceJar)
groupId project.group
artifactId project.ext.name
version project.version
pom {
withXml {
// eliminate logback compile dependencies (no need in maven central POMs)
def root = asNode()
root.dependencies.'*'.findAll() {
it.groupId.text() == "ch.qos.logback" && it.artifactId.text() == "logback-classic"
}.each() {
it.parent().remove(it)
}
}
name = project.ext.name
url = project.ext.url
description = project.description
issueManagement {
url = "${project.ext.url}/issues".toString()
system = 'Gitea Issues'
}
organization {
name = 'dorkbox, llc'
url = 'https://dorkbox.com'
}
developers {
developer {
name = 'dorkbox, llc'
email = 'email@dorkbox.com'
}
}
scm {
url = project.ext.url
connection = "scm:${project.ext.url}.git".toString()
}
}
}
}
repositories {
maven {
url "https://oss.sonatype.org/service/local/staging/deploy/maven2"
credentials {
username sonatypeUsername
password sonatypePassword
}
}
}
}
signing {
required {hasProperty('sonatypeUsername')}
sign publishing.publications.SemanticVersioning
}
nexusStaging {
username sonatypeUsername
password sonatypePassword
}
// output the release URL in the console
releaseRepository.doLast {
def URL = 'https://oss.sonatype.org/content/repositories/releases/'
def projectName = project.group.toString().replaceAll('\\.', '/')
def name = project.ext.name
def version = project.version
println("Maven URL: ${URL}${projectName}/${name}/${version}/")
}
// we don't use maven with the plugin (it's uploaded separately to gradle plugins)
tasks.withType(PublishToMavenRepository) {
onlyIf {
repository == publishing.repositories.maven && publication == publishing.publications.SemanticVersioning
}
}
tasks.withType(PublishToMavenLocal) {
onlyIf {
publication == publishing.publications.SemanticVersioning
}
}

167
build.gradle.kts Normal file
View File

@ -0,0 +1,167 @@
/*
* Copyright 2020 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.time.Instant
gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS // always show the stacktrace!
gradle.startParameter.warningMode = WarningMode.All
plugins {
java
id("com.dorkbox.CrossCompile") version "1.0.1"
id("com.dorkbox.GradleUtils") version "1.8"
id("com.dorkbox.Licensing") version "1.4.37"
id("com.dorkbox.VersionUpdate") version "1.7"
id("com.dorkbox.GradlePublish") version "1.2"
kotlin("jvm") version "1.3.60"
}
object Extras {
// set for the project
const val description = "Java Semantic Versioning with exlusions. Patch number optional and build-after-final-dot permitted."
const val group = "com.dorkbox"
const val version = "1.1"
// set as project.ext
const val name = "Version"
const val id = "Version"
const val vendor = "Dorkbox LLC"
const val vendorUrl = "https://dorkbox.com"
const val url = "https://git.dorkbox.com/dorkbox/Version"
val buildDate = Instant.now().toString()
val JAVA_VERSION = JavaVersion.VERSION_1_6.toString()
}
///////////////////////////////
///// assign 'Extras'
///////////////////////////////
GradleUtils.load("$projectDir/../../gradle.properties", Extras)
GradleUtils.fixIntellijPaths()
sourceSets {
main {
java {
setSrcDirs(listOf("src"))
// want to include java files for the source. 'setSrcDirs' resets includes...
include("**/*.java")
}
}
test {
java {
setSrcDirs(listOf("test"))
// want to include java files for the source. 'setSrcDirs' resets includes...
include("**/*.java")
}
}
}
repositories {
mavenLocal() // this must be first!
jcenter()
}
///////////////////////////////
////// Task defaults
///////////////////////////////
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
sourceCompatibility = Extras.JAVA_VERSION
targetCompatibility = Extras.JAVA_VERSION
}
tasks.withType<Jar> {
duplicatesStrategy = DuplicatesStrategy.FAIL
}
tasks.compileJava.get().apply {
println("\tCompiling classes to Java $sourceCompatibility")
}
tasks.withType<Jar> {
duplicatesStrategy = DuplicatesStrategy.FAIL
}
tasks.jar.get().apply {
duplicatesStrategy = DuplicatesStrategy.FAIL
manifest {
// https://docs.oracle.com/javase/tutorial/deployment/jar/packageman.html
attributes["Name"] = Extras.name
attributes["Specification-Title"] = Extras.name
attributes["Specification-Version"] = Extras.version
attributes["Specification-Vendor"] = Extras.vendor
attributes["Implementation-Title"] = "${Extras.group}.${Extras.id}"
attributes["Implementation-Version"] = Extras.buildDate
attributes["Implementation-Vendor"] = Extras.vendor
attributes["Automatic-Module-Name"] = Extras.id
}
}
configurations.all {
resolutionStrategy {
// fail eagerly on version conflict (includes transitive dependencies)
// e.g. multiple different versions of the same dependency (group and name are equal)
failOnVersionConflict()
// if there is a version we specified, USE THAT VERSION (over transitive versions)
preferProjectModules()
// cache dynamic versions for 10 minutes
cacheDynamicVersionsFor(10 * 60, "seconds")
// don't cache changing modules at all
cacheChangingModulesFor(0, "seconds")
}
}
dependencies {
testImplementation("junit:junit:4.12")
}
publishToSonatype {
groupId = Extras.group
artifactId = Extras.id
version = Extras.version
name = Extras.name
description = Extras.description
url = Extras.url
vendor = Extras.vendor
vendorUrl = Extras.vendorUrl
issueManagement {
url = "${Extras.url}/issues"
nickname = "Gitea Issues"
}
developer {
id = "dorkbox"
name = Extras.vendor
email = "email@dorkbox.com"
}
}

View File

@ -1 +0,0 @@
rootProject.name = 'Version'

15
settings.gradle.kts Normal file
View File

@ -0,0 +1,15 @@
/*
* Copyright 2019 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

View File

@ -556,17 +556,45 @@ class VersionParser implements Parser<Version> {
MetadataVersion preRelease = MetadataVersion.NULL;
MetadataVersion build = MetadataVersion.NULL;
// if we have no patch information, then we have an EXCEPTION to SemVer
if (!normal.patchSpecified) {
// EXCEPTION TO SEMVER
if (checkNextCharacter(LETTER, DIGIT)) {
// NOTE: build INSTEAD OF patch is not valid for semver, however when parsing we want to ALLOW parsing as much.
// straight away to 4.1.Final
// this is not valid semver, but we want to parse it anyways.
// when writing this information, IT WILL NOT be in the format, as it will follow semver.
build = parseBuild();
}
// we can have other info.
if (!checkNextCharacter(HYPHEN, PLUS, EOI)) {
// fail. but we want to be specific with the error
consumeNextCharacter(DIGIT);
}
}
// EXCEPTION TO SEMVER
if (checkNextCharacter(DOT)) {
// NOTE: DOT is not valid for semver, however when parsing we want to ALLOW parsing as much.
consumeNextCharacter(DOT);
// straight away to 4.1.50.Final
// straight away to 4.5.4.201711221230-r
// this is not valid semver, but we want to parse it anyways.
// when writing this information, IT WILL NOT be in the format, as it will follow semver.
build = parseBuild();
}
Character next = consumeNextCharacter(HYPHEN, PLUS, EOI);
if (HYPHEN.isMatchedBy(next)) {
preRelease = parsePreRelease();
next = consumeNextCharacter(PLUS, EOI);
if (PLUS.isMatchedBy(next)) {
build = parseBuild();
}
}
else if (PLUS.isMatchedBy(next)) {
if (PLUS.isMatchedBy(next)) {
build = parseBuild();
}
consumeNextCharacter(EOI);
return new Version(normal, preRelease, build);
}
@ -576,7 +604,9 @@ class VersionParser implements Parser<Version> {
*
* <pre>
* {@literal
* <version core> ::= <major> "." <minor>
* <version core> ::= <major> "." <minor> "." <patch>
* <version core> ::= <major> "." <minor> "." <build>
* }
* </pre>
*
@ -591,12 +621,16 @@ class VersionParser implements Parser<Version> {
if (checkNextCharacter(DOT)) {
consumeNextCharacter(DOT);
long patch = Long.parseLong(numericIdentifier());
return new NormalVersion(major, minor, patch);
}
else {
return new NormalVersion(major, minor);
if (checkNextCharacter(DIGIT)) {
long patch = Long.parseLong(numericIdentifier());
return new NormalVersion(major, minor, patch);
} else if (checkNextCharacter(EOI)) {
throw new ParseException("Unexpected end of information");
}
}
return new NormalVersion(major, minor);
}
/**

View File

@ -53,33 +53,35 @@ class ParserErrorHandlingTest {
@Parameters(name = "{0}")
public static
Collection<Object[]> parameters() {
return Arrays.asList(new Object[][] {{"1", null, 1, new CharType[] {DOT}},
{"1 ", ' ', 1, new CharType[] {DOT}},
{"1.", null, 2, new CharType[] {DIGIT}},
// {"1.2", null, 3, new CharType[] {DOT}}, // exception to semver, is that 1.2 is permitted (ie: leaving out the patch number)
{"1.2.", null, 4, new CharType[] {DIGIT}},
{"a.b.c", 'a', 0, new CharType[] {DIGIT}},
{"1.b.c", 'b', 2, new CharType[] {DIGIT}},
{"1.2.c", 'c', 4, new CharType[] {DIGIT}},
{"!.2.3", '!', 0, new CharType[] {DIGIT}},
{"1.!.3", '!', 2, new CharType[] {DIGIT}},
{"1.2.!", '!', 4, new CharType[] {DIGIT}},
{"v1.2.3", 'v', 0, new CharType[] {DIGIT}},
{"1.2.3-", null, 6, new CharType[] {DIGIT, LETTER, HYPHEN}},
{"1.2. 3", ' ', 4, new CharType[] {DIGIT}},
{"1.2.3=alpha", '=', 5, new CharType[] {HYPHEN, PLUS, EOI}},
{"1.2.3~beta", '~', 5, new CharType[] {HYPHEN, PLUS, EOI}},
{"1.2.3-be$ta", '$', 8, new CharType[] {PLUS, EOI}},
{"1.2.3+b1+b2", '+', 8, new CharType[] {EOI}},
{"1.2.3-rc!", '!', 8, new CharType[] {PLUS, EOI}},
{"1.2.3-+", '+', 6, new CharType[] {DIGIT, LETTER, HYPHEN}},
{"1.2.3-@", '@', 6, new CharType[] {DIGIT, LETTER, HYPHEN}},
{"1.2.3+@", '@', 6, new CharType[] {DIGIT, LETTER, HYPHEN}},
{"1.2.3-rc.", null, 9, new CharType[] {DIGIT, LETTER, HYPHEN}},
{"1.2.3+b.", null, 8, new CharType[] {DIGIT, LETTER, HYPHEN}},
{"1.2.3-b.+b", '+', 8, new CharType[] {DIGIT, LETTER, HYPHEN}},
{"1.2.3-rc..", '.', 9, new CharType[] {DIGIT, LETTER, HYPHEN}},
{"1.2.3-a+b..", '.', 10, new CharType[] {DIGIT, LETTER, HYPHEN}},});
return Arrays.asList(new Object[][] {
{"1", null, 1, new CharType[] {DOT}},
{"1 ", ' ', 1, new CharType[] {DOT}},
{"1.", null, 2, new CharType[] {DIGIT}},
// {"1.2", null, 3, new CharType[] {DOT}}, // exception to semver, is that 1.2 is permitted (ie: leaving out the patch number)
{"1.2.", null, 4, new CharType[] {DIGIT}},
{"a.b.c", 'a', 0, new CharType[] {DIGIT}},
{"1.b.c", 'b', 2, new CharType[] {DIGIT}},
// {"1.2.c", 'c', 4, new CharType[] {DIGIT}}, // exception to semver. "c" can be the build (ie: when leaving out the patch number)
{"!.2.3", '!', 0, new CharType[] {DIGIT}},
{"1.!.3", '!', 2, new CharType[] {DIGIT}},
{"1.2.!", '!', 4, new CharType[] {DIGIT}},
{"v1.2.3", 'v', 0, new CharType[] {DIGIT}},
{"1.2.3-", null, 6, new CharType[] {DIGIT, LETTER, HYPHEN}},
{"1.2. 3", ' ', 4, new CharType[] {DIGIT}},
{"1.2.3=alpha", '=', 5, new CharType[] {HYPHEN, PLUS, EOI}},
{"1.2.3~beta", '~', 5, new CharType[] {HYPHEN, PLUS, EOI}},
{"1.2.3-be$ta", '$', 8, new CharType[] {PLUS, EOI}},
{"1.2.3+b1+b2", '+', 8, new CharType[] {EOI}},
{"1.2.3-rc!", '!', 8, new CharType[] {PLUS, EOI}},
{"1.2.3-+", '+', 6, new CharType[] {DIGIT, LETTER, HYPHEN}},
{"1.2.3-@", '@', 6, new CharType[] {DIGIT, LETTER, HYPHEN}},
{"1.2.3+@", '@', 6, new CharType[] {DIGIT, LETTER, HYPHEN}},
{"1.2.3-rc.", null, 9, new CharType[] {DIGIT, LETTER, HYPHEN}},
{"1.2.3+b.", null, 8, new CharType[] {DIGIT, LETTER, HYPHEN}},
{"1.2.3-b.+b", '+', 8, new CharType[] {DIGIT, LETTER, HYPHEN}},
{"1.2.3-rc..", '.', 9, new CharType[] {DIGIT, LETTER, HYPHEN}},
{"1.2.3-a+b..", '.', 10, new CharType[] {DIGIT, LETTER, HYPHEN}},
});
}
private final String invalidVersion;
private final Character unexpected;

View File

@ -52,6 +52,69 @@ class VersionTest {
public static
class CoreFunctionalityTest {
// MODIFY TEST
@Test
public
void mayHaveFinalDot() {
// not valid, but we should STILL be able to parse it (we just cannot write it).
Version v = Version.from("4.1.50.Final");
assertEquals("Final", v.getBuildMetadata());
}
// MODIFY TEST
@Test
public
void mayHaveFinalModifiedDot() {
// not valid, but we should STILL be able to parse it (we just cannot write it).
Version v = Version.from("4.1.Final");
assertEquals("Final", v.getBuildMetadata());
}
// MODIFY TEST
@Test
public
void mayHaveNumbersAsBuild() {
// not valid, but we should STILL be able to parse it (we just cannot write it).
Version v = Version.from("4.5.4.201711221230-r");
assertEquals("201711221230-r", v.getBuildMetadata());
}
// MODIFY TEST
@Test
public
void mayHaveFinalModifiedBuild() {
// not valid, but we should STILL be able to parse it (we just cannot write it).
Version v = Version.from("4.1+build");
assertEquals("build", v.getBuildMetadata());
}
// MODIFY TEST
@Test
public
void mayHaveBuildOrPreReleaseAppendedWithPlus() {
// not valid, but we should STILL be able to parse it (we just cannot write it).
Version v = Version.from("4.1-alpha");
assertEquals("alpha", v.getPreReleaseVersion());
}
// MODIFY TEST
@Test
public
void modifiedShouldBeAbleToCompareWithoutIgnoringBuildMetadata() {
Version v1 = Version.from("1.3-beta+build.1");
Version v2 = Version.from("1.3-beta+build.2");
assertTrue(0 == v1.compareTo(v2));
assertTrue(0 > v1.compareWithBuildsTo(v2));
}
@Test
public
void mayHavePreReleaseAppendedWithHyphen() {
Version v = Version.from("1.2-alpha");
assertEquals("alpha", v.getPreReleaseVersion());
}
@Test
public
void mayHaveBuildFollowingPatchOrPreReleaseAppendedWithPlus() {
@ -162,8 +225,17 @@ class VersionTest {
@Test
public
void shouldCorrectlyCompareAllVersionsFromSpecification() {
String[] versions = {"1.0.0-alpha", "1.0.0-alpha.1", "1.0.0-alpha.beta", "1.0.0-beta", "1.0.0-beta.2", "1.0.0-beta.11", "1.0.0-rc.1", "1.0.0",
"2.0.0", "2.1.0", "2.1.1"};
String[] versions = {"1.0.0-alpha",
"1.0.0-alpha.1",
"1.0.0-alpha.beta",
"1.0.0-beta",
"1.0.0-beta.2",
"1.0.0-beta.11",
"1.0.0-rc.1",
"1.0.0",
"2.0.0",
"2.1.0",
"2.1.1"};
for (int i = 1; i < versions.length; i++) {
Version v1 = Version.from(versions[i - 1]);
Version v2 = Version.from(versions[i]);
@ -551,8 +623,17 @@ class VersionTest {
@Test
public
void shouldCorrectlyCompareAllVersionsWithBuildMetadata() {
String[] versions = {"1.0.0-alpha", "1.0.0-alpha.1", "1.0.0-beta.2", "1.0.0-beta.11", "1.0.0-rc.1", "1.0.0-rc.1+build.1", "1.0.0", "1.0.0+0.3.7",
"1.3.7+build", "1.3.7+build.2.b8f12d7", "1.3.7+build.11.e0f985a"};
String[] versions = {"1.0.0-alpha",
"1.0.0-alpha.1",
"1.0.0-beta.2",
"1.0.0-beta.11",
"1.0.0-rc.1",
"1.0.0-rc.1+build.1",
"1.0.0",
"1.0.0+0.3.7",
"1.3.7+build",
"1.3.7+build.2.b8f12d7",
"1.3.7+build.11.e0f985a"};
for (int i = 1; i < versions.length; i++) {
Version v1 = Version.from(versions[i - 1]);
Version v2 = Version.from(versions[i]);
@ -584,8 +665,18 @@ class VersionTest {
@Test
public
void shouldSerializeAndDeserialize() throws IOException, ClassNotFoundException {
String[] versions = {"1.0.0-alpha", "1.0.0-alpha.1", "1.0.0-beta.2", "1.0.0-beta.11", "1.0.0-rc.1", "1.0.0-rc.1+build.1", "1.0.0", "1.0.0+0.3.7",
"1.3.7+build", "1.3.7+build.2.b8f12d7", "1.3.7+build.11.e0f985a"};
String[] versions = {"1.0.0-alpha",
"1.0.0-alpha.1",
"1.0.0-beta.2",
"1.0.0-beta.11",
"1.0.0-rc.1",
"1.0.0-rc.1+build.1",
"1.0.0",
"1.0.0+0.3.7",
"1.3.7+build",
"1.3.7+build.2.b8f12d7",
"1.3.7+build.11.e0f985a"};
for (int i = 1; i < versions.length; i++) {
Version v1a = Version.from(versions[i - 1]);
byte[] v1ba = pickle(v1a);