Enable beta version comparable (#1011)

* Ensure whitespace after comma

* Provide version resolver

* Make version comparable

* Refactor version util with version resolver
pull/1040/head
John Niang 2020-08-03 02:07:03 +08:00 committed by GitHub
parent 60b2527003
commit 3bfb60d5fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 402 additions and 5 deletions

View File

@ -80,7 +80,7 @@
<!--whitespace-->
<module name="GenericWhitespace"/>
<module name="NoWhitespaceBefore"/>
<module name="NoWhitespaceAfter"/>
<module name="WhitespaceAfter"/>
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyMethods" value="true"/>

View File

@ -0,0 +1,208 @@
package run.halo.app.utils;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import run.halo.app.model.support.HaloConst;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Version domain.
*
* @author johnniang
*/
@Getter
@ToString
@EqualsAndHashCode
@Slf4j
public class Version implements Comparable<Version> {
/**
* Regex expression.
*/
private static final String REGEX = "^" +
"(?<major>0|[1-9]\\d*)\\." + // major number
"(?<minor>0|[1-9]\\d*)\\." + // minor number
"(?<patch>0|[1-9]\\d*)" + // patch number
"(?:-" + // pre-release start
"(?<preRelease>beta|alpha|rc)\\." + // pre-release type
"(?<preReleaseMajor>0|[1-9]\\d*)" + // pre-release major number
")?$"; // pre-release end
/**
* Pattern.
*/
private static final Pattern PATTERN = Pattern.compile(REGEX);
/**
* Empty version.
*/
private static final Version EMPTY_VERSION = new Version(0, 0, 0);
/**
* Major number.
*/
private final long major;
/**
* Minor number.
*/
private final long minor;
/**
* Patch number.
*/
private final long patch;
/**
* Pre-release.
*/
private final PreRelease preRelease;
/**
* Pre-release major number.
*/
private final long preReleaseMajor;
public Version() {
this(0, 0, 0);
}
public Version(long major, long minor, long patch) {
this(major, minor, patch, null, null);
}
public Version(long major, long minor, long patch, @Nullable PreRelease preRelease, @Nullable Long preReleaseMajor) {
if (major < 0) {
major = 0L;
}
if (minor < 0L) {
minor = 0;
}
if (patch < 0) {
minor = 0L;
}
this.major = major;
this.minor = minor;
this.patch = patch;
this.preRelease = preRelease;
if (preRelease != null) {
preReleaseMajor = preReleaseMajor == null ? Integer.MAX_VALUE : preReleaseMajor;
if (preReleaseMajor < 0) {
preReleaseMajor = 0L;
}
this.preReleaseMajor = preReleaseMajor;
} else {
this.preReleaseMajor = Integer.MAX_VALUE;
}
}
/**
* Empty version.
*
* @return empty version
*/
@NonNull
public static Version emptyVersion() {
return EMPTY_VERSION;
}
/**
* Resolve version.
*
* @param version version could be blank
* @return an optional corresponding version
*/
@NonNull
public static Optional<Version> resolve(@Nullable String version) {
if (StringUtils.isBlank(version)) {
return Optional.empty();
}
// handle unknown version
if (StringUtils.equalsIgnoreCase(version, HaloConst.UNKNOWN_VERSION)) {
return Optional.of(new Version());
}
// get matcher for version
Matcher matcher = PATTERN.matcher(version);
if (!matcher.matches()) {
// if mismatches
log.warn("Version: [{}] didn't match version format", version);
return Optional.empty();
}
// get all groups
String major = matcher.group("major");
String minor = matcher.group("minor");
String patch = matcher.group("patch");
String preRelease = matcher.group("preRelease");
String preReleaseMajor = matcher.group("preReleaseMajor");
// build full version
return Optional.of(new Version(Long.parseLong(major),
Long.parseLong(minor),
Long.parseLong(patch),
PreRelease.of(preRelease),
StringUtils.isNotBlank(preReleaseMajor) ? Long.parseLong(preReleaseMajor) : null));
}
@Override
public int compareTo(@NotNull Version anotherVersion) {
// compare major
int majorCompare = Long.compare(major, anotherVersion.major);
if (majorCompare != 0) {
return majorCompare;
}
// compare minor
int minorCompare = Long.compare(minor, anotherVersion.minor);
if (minorCompare != 0) {
return minorCompare;
}
// compare patch
int patchCompare = Long.compare(patch, anotherVersion.patch);
if (patchCompare != 0) {
return patchCompare;
}
// if all the major, minor and patch are the same, then compare pre release number
return Long.compare(preReleaseMajor, anotherVersion.preReleaseMajor);
}
/**
* Pre release enum.
*
* @author johnniang
*/
public enum PreRelease {
/**
* Beta.
*/
BETA,
/**
* Alpha.
*/
ALPHA,
/**
* Release candidate.
*/
RC;
@Nullable
static PreRelease of(@Nullable String preReleaseStr) {
PreRelease[] preReleases = PreRelease.values();
for (PreRelease preRelease : preReleases) {
if (preRelease.name().equalsIgnoreCase(preReleaseStr)) {
return preRelease;
}
}
return null;
}
}
}

View File

@ -8,9 +8,12 @@ import java.util.Objects;
import java.util.StringTokenizer;
/**
* Version utility.
*
* @author ryanwang
* @author johnniang
* @date 2020-02-03
* @see "com.sun.xml.internal.ws.util.VersionUtil"
* @date 2020-08-03
*/
@Slf4j
public class VersionUtil {
@ -20,6 +23,7 @@ public class VersionUtil {
private VersionUtil() {
}
@Deprecated
public static int[] getCanonicalVersion(String version) {
Assert.hasText(version, "Version must not be blank");
@ -66,6 +70,7 @@ public class VersionUtil {
return canonicalVersion;
}
@Deprecated
public static int compare(String version1, String version2) {
log.debug("Comparing version [{}] with [{}]", version1, version2);
@ -98,6 +103,9 @@ public class VersionUtil {
* @return true or false.
*/
public static boolean compareVersion(String current, String require) {
return compare(current, require) >= 0;
Version leftVersion = Version.resolve(current).orElse(Version.emptyVersion());
Version rightVersion = Version.resolve(require).orElse(Version.emptyVersion());
return leftVersion.compareTo(rightVersion) >= 0;
}
}

View File

@ -0,0 +1,181 @@
package run.halo.app.utils;
import org.apache.commons.lang3.RandomUtils;
import org.junit.jupiter.api.Test;
import org.springframework.lang.NonNull;
import run.halo.app.model.support.HaloConst;
import java.util.Optional;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
/**
* Version resolve test.
*
* @author johnniang
*/
class VersionTest {
/**
* Invalid versions.
*/
String[] invalidVersions = new String[] {
null,
"",
"1",
"1.2",
"1.2.3-0123",
"1.2.3-0123.0123",
"1.1.2+.123",
"+invalid",
"-invalid",
"-invalid+invalid",
"-invalid.01",
"alpha",
"alpha.beta",
"alpha.beta.1",
"alpha.1",
"alpha+beta",
"alpha_beta",
"alpha.",
"alpha..",
"beta",
"1.0.0-alpha_beta",
"-alpha.",
"1.0.0-alpha..",
"1.0.0-alpha..1",
"1.0.0-alpha...1",
"1.0.0-alpha....1",
"1.0.0-alpha.....1",
"1.0.0-alpha......1",
"1.0.0-alpha.......1",
"01.1.1",
"1.01.1",
"1.1.01",
"1.2",
"1.2.3.DEV",
"1.2-SNAPSHOT",
"1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788",
"1.2-RC-SNAPSHOT",
"-1.0.3-gamma+b7718",
"+justmeta",
"9.8.7+meta+meta",
"9.8.7-whatever+meta+meta",
"99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12",
"1.0.0-0A.is.legal",
"1.0.0-alpha+beta",
"1.2.3----RC-SNAPSHOT.12.9.1--.12+788",
"1.2.3----R-S.12.9.1--.12+meta",
"1.2.3----RC-SNAPSHOT.12.9.1--.12",
"1.0.0+0.build.1-rc.10000aaa-kk-0.1",
"1.0.0-0A.is.legal",
"2.0.0+build.1848",
"1.0.0-alpha0.valid",
"1.0.0-alpha.0valid",
"1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay",
"1.0.0-rc.1+build.1",
"2.0.0-rc.1+build.123",
"1.2.3-beta",
"10.2.3-DEV-SNAPSHOT",
"1.2.3-SNAPSHOT-123",
"1.1.2-prerelease+meta",
"1.1.2+meta",
"1.1.2+meta-valid",
"1.0.0-alpha",
"1.0.0-beta",
"1.0.0-alpha.beta",
"1.0.0-alpha.beta.1"
};
@Test
void invalidVersionResolve() {
Stream.of(invalidVersions).forEach(invalidVersion -> {
Optional<Version> versionOpt = Version.resolve(invalidVersion);
assertFalse(versionOpt.isPresent());
});
}
@Test
void releaseVersionResolve() {
long major = RandomUtils.nextLong();
long minor = RandomUtils.nextLong();
long patch = RandomUtils.nextLong();
String version = major + "." + minor + "." + patch;
Optional<Version> versionOpt = Version.resolve(version);
assertTrue(versionOpt.isPresent());
assertEquals(new Version(major, minor, patch), versionOpt.get());
}
@Test
void preReleaseVersionResolve() {
long major = RandomUtils.nextLong();
long minor = RandomUtils.nextLong();
long patch = RandomUtils.nextLong();
long preReleaseMajor = RandomUtils.nextLong();
String version = major + "." + minor + "." + patch + "-alpha." + preReleaseMajor;
Optional<Version> versionOpt = Version.resolve(version);
assertTrue(versionOpt.isPresent());
assertEquals(new Version(major, minor, patch, Version.PreRelease.ALPHA, preReleaseMajor),
versionOpt.get());
version = major + "." + minor + "." + patch + "-beta." + preReleaseMajor;
versionOpt = Version.resolve(version);
assertTrue(versionOpt.isPresent());
assertEquals(new Version(major, minor, patch, Version.PreRelease.BETA, preReleaseMajor),
versionOpt.get());
version = major + "." + minor + "." + patch + "-rc." + preReleaseMajor;
versionOpt = Version.resolve(version);
assertTrue(versionOpt.isPresent());
assertEquals(new Version(major, minor, patch, Version.PreRelease.RC, preReleaseMajor),
versionOpt.get());
}
@Test
void unknownVersionTest() {
Optional<Version> unknownVersionOpt = Version.resolve(HaloConst.UNKNOWN_VERSION);
assertTrue(unknownVersionOpt.isPresent());
assertEquals(new Version(0, 0, 0), unknownVersionOpt.get());
}
@Test
void compareTest() {
Version leftVersion = getVersion("1.2.3");
// compare with own
assertEquals(0, leftVersion.compareTo(leftVersion));
// compare with others
Version rightVersion = getVersion("1.2.4");
assertTrue(leftVersion.compareTo(rightVersion) < 0);
rightVersion = getVersion("1.3.3");
assertTrue(leftVersion.compareTo(rightVersion) < 0);
rightVersion = getVersion("2.2.3");
assertTrue(leftVersion.compareTo(rightVersion) < 0);
rightVersion = getVersion("0.2.3");
assertTrue(leftVersion.compareTo(rightVersion) > 0);
rightVersion = getVersion("1.1.3");
assertTrue(leftVersion.compareTo(rightVersion) > 0);
rightVersion = getVersion("1.2.2");
assertTrue(leftVersion.compareTo(rightVersion) > 0);
rightVersion = getVersion("1.2.3-alpha.0");
assertTrue(leftVersion.compareTo(rightVersion) > 0);
rightVersion = getVersion("1.2.4-alpha.0");
assertTrue(leftVersion.compareTo(rightVersion) < 0);
// compare with unkown version
assertTrue(leftVersion.compareTo(getVersion(HaloConst.UNKNOWN_VERSION)) > 0);
}
@NonNull
Version getVersion(String version) {
return Version.resolve(version).orElseThrow(() -> new IllegalArgumentException("Invalid version"));
}
}

View File

@ -16,7 +16,7 @@ class VersionUtilTest {
void compareVersion() {
assertTrue(VersionUtil.compareVersion("1.2.0", "1.1.1"));
assertTrue(VersionUtil.compareVersion("1.2.1", "1.2.0"));
assertTrue(VersionUtil.compareVersion("1.2.0", "1.1.1.0"));
assertTrue(VersionUtil.compareVersion("1.2.0", "1.1.1"));
assertTrue(VersionUtil.compareVersion("1.2.0", "0.4.4"));
assertFalse(VersionUtil.compareVersion("1.1.1", "1.2.0"));
assertFalse(VersionUtil.compareVersion("0.0.1", "1.2.0"));
@ -29,7 +29,7 @@ class VersionUtilTest {
RandomStringUtils.randomNumeric(1),
RandomStringUtils.randomNumeric(2),
RandomStringUtils.randomNumeric(3));
VersionUtil.compareVersion(HaloConst.UNKNOWN_VERSION, randomVersion);
assertFalse(VersionUtil.compareVersion(HaloConst.UNKNOWN_VERSION, randomVersion));
}
@Test