Add support for SemVer and CalVer versions

This commit improves the version parser to handle qualifiers that are
separated by either a `.` or a `-`. This makes the parsing of
`1.2.0-RC1` or `2020.0.0-M1` possible.

Closes gh-1083
This commit is contained in:
Stephane Nicoll
2020-05-26 11:25:32 +02:00
parent 9255ae9fbe
commit 0b7614c4f0
5 changed files with 107 additions and 71 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -44,7 +44,7 @@ class SpringBootVersionRepositoriesBuildCustomizer implements BuildCustomizer<Bu
@Override
public void customize(Build build) {
build.repositories().add("maven-central");
String qualifier = this.springBootVersion.getQualifier().getQualifier();
String qualifier = this.springBootVersion.getQualifier().getId();
if (!"RELEASE".equals(qualifier)) {
addMilestoneRepository(build);
if ("BUILD-SNAPSHOT".equals(qualifier)) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;
import org.springframework.util.StringUtils;
@@ -190,9 +192,15 @@ public final class Version implements Serializable, Comparable<Version> {
@Override
public String toString() {
return this.major + "." + this.minor + "." + this.patch + ((this.qualifier != null)
? "." + this.qualifier.qualifier + ((this.qualifier.version != null) ? this.qualifier.version : "")
: "");
StringBuilder sb = new StringBuilder().append(this.major).append(".").append(this.minor).append(".")
.append(this.patch);
if (this.qualifier != null) {
sb.append(this.qualifier.getSeparator()).append(this.qualifier.getId());
if (this.qualifier.getVersion() != null) {
sb.append(this.qualifier.getVersion());
}
}
return sb.toString();
}
/**
@@ -200,74 +208,56 @@ public final class Version implements Serializable, Comparable<Version> {
*/
public static class Qualifier implements Serializable {
public Qualifier(String qualifier) {
this.qualifier = qualifier;
private final String id;
private final Integer version;
private final String separator;
public Qualifier(String id) {
this(id, null, ".");
}
private String qualifier;
private Integer version;
public String getQualifier() {
return this.qualifier;
public Qualifier(String id, Integer version, String separator) {
this.id = id;
this.version = version;
this.separator = separator;
}
public void setQualifier(String qualifier) {
this.qualifier = qualifier;
public String getId() {
return this.id;
}
public Integer getVersion() {
return this.version;
}
public void setVersion(Integer version) {
this.version = version;
public String getSeparator() {
return this.separator;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (obj == null) {
if (o == null || getClass() != o.getClass()) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Qualifier other = (Qualifier) obj;
if (this.qualifier == null) {
if (other.qualifier != null) {
return false;
}
}
else if (!this.qualifier.equals(other.qualifier)) {
return false;
}
if (this.version == null) {
if (other.version != null) {
return false;
}
}
else if (!this.version.equals(other.version)) {
return false;
}
return true;
Qualifier qualifier = (Qualifier) o;
return this.id.equals(qualifier.id) && Objects.equals(this.version, qualifier.version)
&& Objects.equals(this.separator, qualifier.separator);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.qualifier == null) ? 0 : this.qualifier.hashCode());
result = prime * result + ((this.version == null) ? 0 : this.version.hashCode());
return result;
return Objects.hash(this.id, this.version, this.separator);
}
@Override
public String toString() {
return "Qualifier [" + ((this.qualifier != null) ? "qualifier=" + this.qualifier + ", " : "")
+ ((this.version != null) ? "version=" + this.version : "") + "]";
return new StringJoiner(", ", Qualifier.class.getSimpleName() + "[", "]").add("id='" + this.id + "'")
.add("version=" + this.version).add("separator='" + this.separator + "'").toString();
}
}
@@ -275,11 +265,12 @@ public final class Version implements Serializable, Comparable<Version> {
private static class VersionQualifierComparator implements Comparator<Qualifier> {
static final String RELEASE = "RELEASE";
static final String SNAPSHOT = "BUILD-SNAPSHOT";
static final String BUILD_SNAPSHOT = "BUILD-SNAPSHOT";
static final String SNAPSHOT = "SNAPSHOT";
static final String MILESTONE = "M";
static final String RC = "RC";
static final List<String> KNOWN_QUALIFIERS = Arrays.asList(MILESTONE, RC, SNAPSHOT, RELEASE);
static final List<String> KNOWN_QUALIFIERS = Arrays.asList(MILESTONE, RC, BUILD_SNAPSHOT, SNAPSHOT, RELEASE);
@Override
public int compare(Qualifier o1, Qualifier o2) {
@@ -297,12 +288,12 @@ public final class Version implements Serializable, Comparable<Version> {
}
private static int compareQualifier(Qualifier first, Qualifier second) {
int firstIndex = getQualifierIndex(first.qualifier);
int secondIndex = getQualifierIndex(second.qualifier);
int firstIndex = getQualifierIndex(first.getId());
int secondIndex = getQualifierIndex(second.getId());
// Unknown qualifier, alphabetic ordering
if (firstIndex == -1 && secondIndex == -1) {
return first.qualifier.compareTo(second.qualifier);
return first.getId().compareTo(second.getId());
}
else {
return Integer.compare(firstIndex, secondIndex);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -48,7 +48,7 @@ public class VersionParser {
public static final VersionParser DEFAULT = new VersionParser(Collections.emptyList());
private static final Pattern VERSION_REGEX = Pattern
.compile("^(\\d+)\\.(\\d+|x)\\.(\\d+|x)(?:\\.([^0-9]+)(\\d+)?)?$");
.compile("^(\\d+)\\.(\\d+|x)\\.(\\d+|x)(?:([.|-])([^0-9]+)(\\d+)?)?$");
private static final Pattern RANGE_REGEX = Pattern.compile("(\\(|\\[)(.*),(.*)(\\)|\\])");
@@ -71,20 +71,12 @@ public class VersionParser {
Matcher matcher = VERSION_REGEX.matcher(text.trim());
if (!matcher.matches()) {
throw new InvalidVersionException("Could not determine version based on '" + text + "': version format "
+ "is Major.Minor.Patch.Qualifier " + "(e.g. 1.0.5.RELEASE)");
+ "is Major.Minor.Patch and an optional Qualifier " + "(e.g. 1.0.5.RELEASE)");
}
Integer major = Integer.valueOf(matcher.group(1));
String minor = matcher.group(2);
String patch = matcher.group(3);
Qualifier qualifier = null;
String qualifierId = matcher.group(4);
if (StringUtils.hasText(qualifierId)) {
qualifier = new Version.Qualifier(qualifierId);
String o = matcher.group(5);
if (o != null) {
qualifier.setVersion(Integer.valueOf(o));
}
}
Qualifier qualifier = parseQualifier(matcher);
if ("x".equals(minor) || "x".equals(patch)) {
Integer minorInt = ("x".equals(minor) ? null : Integer.parseInt(minor));
Version latest = findLatestVersion(major, minorInt, qualifier);
@@ -99,6 +91,17 @@ public class VersionParser {
}
}
private Qualifier parseQualifier(Matcher matcher) {
String qualifierSeparator = matcher.group(4);
String qualifierId = matcher.group(5);
if (StringUtils.hasText(qualifierSeparator) && StringUtils.hasText(qualifierId)) {
String versionString = matcher.group(6);
return new Qualifier(qualifierId, (versionString != null) ? Integer.valueOf(versionString) : null,
qualifierSeparator);
}
return null;
}
/**
* Parse safely the specified string representation of a {@link Version}.
* <p>

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,23 +35,35 @@ class VersionParserTests {
private VersionParser parser = new VersionParser(Collections.emptyList());
@Test
void noQualifierString() {
void versionWithNoQualifier() {
Version version = this.parser.parse("1.2.0");
assertThat(version.toString()).isEqualTo("1.2.0");
}
@Test
void withQualifierString() {
void versionWithQualifierAndDotSeparator() {
Version version = this.parser.parse("1.2.0.RELEASE");
assertThat(version.toString()).isEqualTo("1.2.0.RELEASE");
}
@Test
void withQualifierAndVersionString() {
void versionWithQualifierAndDashSeparator() {
Version version = this.parser.parse("1.2.0-SNAPSHOT");
assertThat(version.toString()).isEqualTo("1.2.0-SNAPSHOT");
}
@Test
void versionWithQualifierVersionAndDotSeparator() {
Version version = this.parser.parse("1.2.0.RC2");
assertThat(version.toString()).isEqualTo("1.2.0.RC2");
}
@Test
void versionWithQualifierVersionAndDashSeparator() {
Version version = this.parser.parse("1.2.0-M3");
assertThat(version.toString()).isEqualTo("1.2.0-M3");
}
@Test
void parseInvalidVersion() {
assertThatExceptionOfType(InvalidVersionException.class).isThrownBy(() -> this.parser.parse("foo"));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,9 @@
package io.spring.initializr.generator.version;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
@@ -123,6 +126,33 @@ class VersionTests {
assertThat(parse("1.2.0.BUILD-SNAPSHOT")).isLessThan(parse("1.2.0.RELEASE"));
}
@Test
void orderVersionSchemeWithQualifiedVersions() {
List<String> sortedVersions = Stream
.of("2.3.0.BUILD-SNAPSHOT", "2.3.0.RC1", "2.3.0.M2", "2.3.0.M1", "2.3.0.RELEASE", "2.3.0.RC2")
.map(this::parse).sorted().map(Version::toString).collect(Collectors.toList());
assertThat(sortedVersions).containsExactly("2.3.0.M1", "2.3.0.M2", "2.3.0.RC1", "2.3.0.RC2",
"2.3.0.BUILD-SNAPSHOT", "2.3.0.RELEASE");
}
@Test
void orderVersionSchemeWithSemVer() {
List<String> sortedVersions = Stream
.of("2.3.0-SNAPSHOT", "2.3.0-RC1", "2.3.0-M2", "2.3.0-M1", "2.3.0", "2.3.0-RC2").map(this::parse)
.sorted().map(Version::toString).collect(Collectors.toList());
assertThat(sortedVersions).containsExactly("2.3.0-M1", "2.3.0-M2", "2.3.0-RC1", "2.3.0-RC2", "2.3.0-SNAPSHOT",
"2.3.0");
}
@Test
void orderVersionSchemeWithCalVer() {
List<String> sortedVersions = Stream
.of("2020.0.0-SNAPSHOT", "2020.0.0-RC1", "2020.0.0-M2", "2020.0.0-M1", "2020.0.0", "2020.0.0-RC2")
.map(this::parse).sorted().map(Version::toString).collect(Collectors.toList());
assertThat(sortedVersions).containsExactly("2020.0.0-M1", "2020.0.0-M2", "2020.0.0-RC1", "2020.0.0-RC2",
"2020.0.0-SNAPSHOT", "2020.0.0");
}
private Version parse(String text) {
return this.parser.parse(text);
}