forked from plusone/plusone-commons
refactor: 优化 SemVer 实现
This commit is contained in:
@@ -30,9 +30,7 @@ import com.google.common.base.Splitter;
|
|||||||
|
|
||||||
import xyz.zhouxy.plusone.commons.util.StringTools;
|
import xyz.zhouxy.plusone.commons.util.StringTools;
|
||||||
|
|
||||||
// TODO [优化] 优化正则表达式
|
|
||||||
// TODO [补充] 完善单元测试
|
// TODO [补充] 完善单元测试
|
||||||
// TODO [doc] javadoc、README.md
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SemVer 语义版本号
|
* SemVer 语义版本号
|
||||||
@@ -48,23 +46,42 @@ public class SemVer implements Comparable<SemVer>, Serializable {
|
|||||||
private final String value;
|
private final String value;
|
||||||
|
|
||||||
private final int[] versionNumbers;
|
private final int[] versionNumbers;
|
||||||
private final String[] preReleaseVersion;
|
@Nullable
|
||||||
|
private final String preReleaseVersion;
|
||||||
|
@Nullable
|
||||||
private final String buildMetadata;
|
private final String buildMetadata;
|
||||||
|
|
||||||
private static final String VERSION_NUMBERS = "(?<numbers>(?<major>0|[1-9]\\d*)\\.(?<minor>0|[1-9]\\d*)\\.(?<patch>0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){0,2})";
|
private static final String VERSION_NUMBERS = "(?<numbers>(?<major>0|[1-9]\\d*)\\.(?<minor>0|[1-9]\\d*)\\.(?<patch>0|[1-9]\\d*)(\\.(0|[1-9]\\d*)){0,2})";
|
||||||
private static final String PRE_RELEASE_VERSION = "(?:-(?<prerelease>(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?";
|
private static final String PRE_RELEASE_VERSION = "(?:-(?<prerelease>(?:0|[1-9]\\d{0,41}|\\d{0,18}[a-zA-Z-][0-9a-zA-Z-]{0,18})(?:\\.(?:0|[1-9]\\d{0,41}|\\d{0,18}[a-zA-Z-][0-9a-zA-Z-]{0,18})){0,18}))?";
|
||||||
private static final String BUILD_METADATA = "(?:\\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?";
|
private static final String BUILD_METADATA = "(?:\\+(?<buildmetadata>[0-9a-zA-Z-]{1,18}(?:\\.[0-9a-zA-Z-]{1,18}){0,18}))?";
|
||||||
|
|
||||||
private static final Pattern PATTERN = Pattern.compile(
|
private static final Pattern PATTERN = Pattern.compile(
|
||||||
"^" + VERSION_NUMBERS + PRE_RELEASE_VERSION + BUILD_METADATA + "$");
|
"^" + VERSION_NUMBERS + PRE_RELEASE_VERSION + BUILD_METADATA + "$");
|
||||||
|
|
||||||
private SemVer(String value, int[] versionNumbers, @Nullable String[] preReleaseVersion, @Nullable String buildMetadata) {
|
/**
|
||||||
|
* 创建语义化版本号的值对象
|
||||||
|
*
|
||||||
|
* @param value 字符串值
|
||||||
|
* @param versionNumbers 主版本号、次版本号、修订号
|
||||||
|
* @param preReleaseVersion 先行版本号
|
||||||
|
* @param buildMetadata 版本编译信息
|
||||||
|
*/
|
||||||
|
private SemVer(String value,
|
||||||
|
int[] versionNumbers,
|
||||||
|
@Nullable String preReleaseVersion,
|
||||||
|
@Nullable String buildMetadata) {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.versionNumbers = versionNumbers;
|
this.versionNumbers = versionNumbers;
|
||||||
this.preReleaseVersion = preReleaseVersion;
|
this.preReleaseVersion = preReleaseVersion;
|
||||||
this.buildMetadata = buildMetadata;
|
this.buildMetadata = buildMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 SemVer 对象
|
||||||
|
*
|
||||||
|
* @param value 语义化版本号
|
||||||
|
* @return SemVer 对象
|
||||||
|
*/
|
||||||
public static SemVer of(final String value) {
|
public static SemVer of(final String value) {
|
||||||
checkArgument(StringTools.isNotBlank(value), "版本号不能为空");
|
checkArgument(StringTools.isNotBlank(value), "版本号不能为空");
|
||||||
final Matcher matcher = PATTERN.matcher(value);
|
final Matcher matcher = PATTERN.matcher(value);
|
||||||
@@ -81,30 +98,57 @@ public class SemVer implements Comparable<SemVer>, Serializable {
|
|||||||
// 必须都是数字
|
// 必须都是数字
|
||||||
.mapToInt(Integer::parseInt)
|
.mapToInt(Integer::parseInt)
|
||||||
.toArray();
|
.toArray();
|
||||||
|
return new SemVer(value, versionNumbers, preReleaseVersionPart, buildMetadataPart);
|
||||||
final String[] preReleaseVersion = preReleaseVersionPart != null
|
|
||||||
? Splitter.on('.').splitToStream(preReleaseVersionPart).toArray(String[]::new)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return new SemVer(value, versionNumbers, preReleaseVersion, buildMetadataPart);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取主版本号
|
||||||
|
*
|
||||||
|
* @return 主版本号
|
||||||
|
*/
|
||||||
public int getMajor() {
|
public int getMajor() {
|
||||||
return this.versionNumbers[0];
|
return this.versionNumbers[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取次版本号
|
||||||
|
*
|
||||||
|
* @return 次版本号
|
||||||
|
*/
|
||||||
public int getMinor() {
|
public int getMinor() {
|
||||||
return this.versionNumbers[1];
|
return this.versionNumbers[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取修订号
|
||||||
|
*
|
||||||
|
* @return 修订号
|
||||||
|
*/
|
||||||
public int getPatch() {
|
public int getPatch() {
|
||||||
return this.versionNumbers[2];
|
return this.versionNumbers[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取先行版本号
|
||||||
|
*
|
||||||
|
* @return 先行版本号
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String getPreReleaseVersion() {
|
||||||
|
return this.preReleaseVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取版本编译信息
|
||||||
|
*
|
||||||
|
* @return 版本编译信息
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
public String getBuildMetadata() {
|
public String getBuildMetadata() {
|
||||||
return buildMetadata;
|
return buildMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(@Nullable SemVer that) {
|
public int compareTo(@Nullable SemVer that) {
|
||||||
if (that == null) {
|
if (that == null) {
|
||||||
@@ -117,22 +161,26 @@ public class SemVer implements Comparable<SemVer>, Serializable {
|
|||||||
return comparePreReleaseVersion(that);
|
return comparePreReleaseVersion(that);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字符串值
|
||||||
|
*
|
||||||
|
* @return 版本字符串
|
||||||
|
*/
|
||||||
public String getValue() {
|
public String getValue() {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int prime = 31;
|
final int prime = 31;
|
||||||
int result = 1;
|
int result = 1;
|
||||||
result = prime * result + Arrays.hashCode(versionNumbers);
|
result = prime * result + Arrays.hashCode(versionNumbers);
|
||||||
result = prime * result + Arrays.hashCode(preReleaseVersion);
|
result = prime * result + Objects.hash(value, preReleaseVersion, buildMetadata);
|
||||||
result = prime * result + Objects.hash(value, buildMetadata);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(@Nullable Object obj) {
|
public boolean equals(@Nullable Object obj) {
|
||||||
if (this == obj)
|
if (this == obj)
|
||||||
@@ -141,15 +189,21 @@ public class SemVer implements Comparable<SemVer>, Serializable {
|
|||||||
return false;
|
return false;
|
||||||
SemVer other = (SemVer) obj;
|
SemVer other = (SemVer) obj;
|
||||||
return Objects.equals(value, other.value) && Arrays.equals(versionNumbers, other.versionNumbers)
|
return Objects.equals(value, other.value) && Arrays.equals(versionNumbers, other.versionNumbers)
|
||||||
&& Arrays.equals(preReleaseVersion, other.preReleaseVersion)
|
&& Objects.equals(preReleaseVersion, other.preReleaseVersion)
|
||||||
&& Objects.equals(buildMetadata, other.buildMetadata);
|
&& Objects.equals(buildMetadata, other.buildMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 SemVer 的字符串表示。如 {@code v1.2.3-alpha.1+build.1234}
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return 'v' + value;
|
return 'v' + value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较主版本号、次版本号、修订号
|
||||||
|
*/
|
||||||
private int compareVersionNumbers(SemVer that) {
|
private int compareVersionNumbers(SemVer that) {
|
||||||
final int minLength = Integer.min(this.versionNumbers.length, that.versionNumbers.length);
|
final int minLength = Integer.min(this.versionNumbers.length, that.versionNumbers.length);
|
||||||
|
|
||||||
@@ -163,15 +217,24 @@ public class SemVer implements Comparable<SemVer>, Serializable {
|
|||||||
return this.versionNumbers.length - that.versionNumbers.length;
|
return this.versionNumbers.length - that.versionNumbers.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较先行版本号
|
||||||
|
*/
|
||||||
private int comparePreReleaseVersion(SemVer that) {
|
private int comparePreReleaseVersion(SemVer that) {
|
||||||
final String[] preReleaseVersionOfThis = this.preReleaseVersion;
|
int thisWithoutPreReleaseVersionFlag = bool2Int(this.preReleaseVersion == null);
|
||||||
final String[] preReleaseVersionOfThat = that.preReleaseVersion;
|
int thatWithoutPreReleaseVersionFlag = bool2Int(that.preReleaseVersion == null);
|
||||||
byte thisWithoutPreReleaseVersionFlag = preReleaseVersionOfThis == null ? (byte) 1 : (byte) 0;
|
if (isTrue(thisWithoutPreReleaseVersionFlag | thatWithoutPreReleaseVersionFlag)) {
|
||||||
byte thatWithoutPreReleaseVersionFlag = preReleaseVersionOfThat == null ? (byte) 1 : (byte) 0;
|
|
||||||
if ((thisWithoutPreReleaseVersionFlag | thatWithoutPreReleaseVersionFlag) == 1) {
|
|
||||||
return thisWithoutPreReleaseVersionFlag - thatWithoutPreReleaseVersionFlag;
|
return thisWithoutPreReleaseVersionFlag - thatWithoutPreReleaseVersionFlag;
|
||||||
}
|
}
|
||||||
@SuppressWarnings("null")
|
|
||||||
|
Splitter splitter = Splitter.on('.');
|
||||||
|
|
||||||
|
final String[] preReleaseVersionOfThis = splitter
|
||||||
|
.splitToStream(this.preReleaseVersion) // NOSONAR
|
||||||
|
.toArray(String[]::new);
|
||||||
|
final String[] preReleaseVersionOfThat = splitter
|
||||||
|
.splitToStream(that.preReleaseVersion) // NOSONAR
|
||||||
|
.toArray(String[]::new);
|
||||||
final int minLength = Integer.min(preReleaseVersionOfThis.length, preReleaseVersionOfThat.length);
|
final int minLength = Integer.min(preReleaseVersionOfThis.length, preReleaseVersionOfThat.length);
|
||||||
for (int i = 0; i < minLength; i++) {
|
for (int i = 0; i < minLength; i++) {
|
||||||
int r = comparePartOfPreReleaseVersion(preReleaseVersionOfThis[i], preReleaseVersionOfThat[i]);
|
int r = comparePartOfPreReleaseVersion(preReleaseVersionOfThis[i], preReleaseVersionOfThat[i]);
|
||||||
@@ -182,6 +245,9 @@ public class SemVer implements Comparable<SemVer>, Serializable {
|
|||||||
return preReleaseVersionOfThis.length - preReleaseVersionOfThat.length;
|
return preReleaseVersionOfThis.length - preReleaseVersionOfThat.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较先行版本号的组成部分
|
||||||
|
*/
|
||||||
private static int comparePartOfPreReleaseVersion(String p1, String p2) {
|
private static int comparePartOfPreReleaseVersion(String p1, String p2) {
|
||||||
boolean p1IsNumber = isAllDigits(p1);
|
boolean p1IsNumber = isAllDigits(p1);
|
||||||
boolean p2IsNumber = isAllDigits(p2);
|
boolean p2IsNumber = isAllDigits(p2);
|
||||||
@@ -195,11 +261,10 @@ public class SemVer implements Comparable<SemVer>, Serializable {
|
|||||||
return p2IsNumber ? 1 : p1.compareTo(p2);
|
return p2IsNumber ? 1 : p1.compareTo(p2);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isAllDigits(@Nullable String str) {
|
/**
|
||||||
if (str == null || str.isEmpty()) {
|
* 判断字符串是否全为数字
|
||||||
return false;
|
*/
|
||||||
}
|
private static boolean isAllDigits(String str) {
|
||||||
|
|
||||||
for (int i = 0; i < str.length(); i++) {
|
for (int i = 0; i < str.length(); i++) {
|
||||||
char c = str.charAt(i);
|
char c = str.charAt(i);
|
||||||
if (c < '0' || c > '9') {
|
if (c < '0' || c > '9') {
|
||||||
@@ -208,4 +273,12 @@ public class SemVer implements Comparable<SemVer>, Serializable {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int bool2Int(boolean expression) {
|
||||||
|
return expression ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isTrue(int b) {
|
||||||
|
return b != 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user