mirror of
https://gitee.com/dromara/hutool.git
synced 2025-04-05 17:37:59 +08:00
修复VersionComparator违反传递问题
This commit is contained in:
parent
422aaeb96a
commit
058c801442
@ -7,6 +7,7 @@
|
||||
### 🐣新特性
|
||||
* 【db 】 RedisDS增加user支持(issue#I8XEQ4@Gitee)
|
||||
* 【core 】 MapUtil增加partition方法(pr#1170@Gitee)
|
||||
* 【core 】 增加Version类(issue#I8Z3VE@Gitee)
|
||||
|
||||
### 🐞Bug修复
|
||||
* 【crypto】 修复BouncyCastleProvider导致graalvm应用报错UnsupportedFeatureError(pr#3464@Github)
|
||||
@ -17,6 +18,7 @@
|
||||
* 【core 】 修复CollUtil.containsAll在coll2长度大于coll1时逻辑歧义问题(issue#I8Z2Q4@Gitee)
|
||||
* 【poi 】 修复当sheetName 不存在时,ExcelUtil.getReader方法不会释放文件问题(issue#I8ZIQC@Gitee)
|
||||
* 【crypto】 通过添加系统属性hutool.crypto.decodeHex强制关闭hex识别以解决hex和Base64歧义问题(issue#I90M9D@Gitee)
|
||||
* 【core 】 修复VersionComparator违反传递问题(issue#I8Z3VE@Gitee)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.8.25(2024-01-11)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package cn.hutool.core.comparator;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.Version;
|
||||
import cn.hutool.core.util.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
@ -13,7 +14,7 @@ import java.util.regex.Pattern;
|
||||
* 比较两个版本的大小<br>
|
||||
* 排序时版本从小到大排序,即比较时小版本在前,大版本在后<br>
|
||||
* 支持如:1.3.20.8,6.82.20160101,8.5a/8.5c等版本形式<br>
|
||||
* 参考:https://www.cnblogs.com/shihaiming/p/6286575.html
|
||||
* 参考:java.lang.module.ModuleDescriptor.Version
|
||||
*
|
||||
* @author Looly
|
||||
* @since 4.0.2
|
||||
@ -21,8 +22,6 @@ import java.util.regex.Pattern;
|
||||
public class VersionComparator implements Comparator<String>, Serializable {
|
||||
private static final long serialVersionUID = 8083701245147495562L;
|
||||
|
||||
private static final Pattern PATTERN_PRE_NUMBERS= Pattern.compile("^\\d+");
|
||||
|
||||
/** 单例 */
|
||||
public static final VersionComparator INSTANCE = new VersionComparator();
|
||||
|
||||
@ -64,39 +63,6 @@ public class VersionComparator implements Comparator<String>, Serializable {
|
||||
return 1;
|
||||
}
|
||||
|
||||
final List<String> v1s = StrUtil.split(version1, CharUtil.DOT);
|
||||
final List<String> v2s = StrUtil.split(version2, CharUtil.DOT);
|
||||
|
||||
int diff = 0;
|
||||
int minLength = Math.min(v1s.size(), v2s.size());// 取最小长度值
|
||||
String v1;
|
||||
String v2;
|
||||
for (int i = 0; i < minLength; i++) {
|
||||
v1 = v1s.get(i);
|
||||
v2 = v2s.get(i);
|
||||
// 先比较长度
|
||||
diff = v1.length() - v2.length();
|
||||
if (0 == diff) {
|
||||
diff = v1.compareTo(v2);
|
||||
}else {
|
||||
// 不同长度,且含有字母
|
||||
if(!NumberUtil.isNumber(v1) || !NumberUtil.isNumber(v2)){
|
||||
//不同长度的先比较前面的数字;前面数字不相等时,按数字大小比较;数字相等的时候,继续按长度比较,类似于 103 > 102a
|
||||
final int v1Num = Convert.toInt(ReUtil.get(PATTERN_PRE_NUMBERS, v1, 0), 0);
|
||||
final int v2Num = Convert.toInt(ReUtil.get(PATTERN_PRE_NUMBERS, v2, 0), 0);
|
||||
final int diff1 = v1Num - v2Num;
|
||||
if (diff1 != 0) {
|
||||
diff = diff1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(diff != 0) {
|
||||
//已有结果,结束
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已经分出大小,则直接返回,如果未分出大小,则再比较位数,有子版本的为大;
|
||||
return (diff != 0) ? diff : v1s.size() - v2s.size();
|
||||
return CompareUtil.compare(Version.of(version1), Version.of(version2));
|
||||
}
|
||||
}
|
||||
|
279
hutool-core/src/main/java/cn/hutool/core/lang/Version.java
Normal file
279
hutool-core/src/main/java/cn/hutool/core/lang/Version.java
Normal file
@ -0,0 +1,279 @@
|
||||
/*
|
||||
* Copyright (c) 2024. looly(loolly@aliyun.com)
|
||||
* Hutool is licensed under Mulan PSL v2.
|
||||
* You can use this software according to the terms and conditions of the Mulan PSL v2.
|
||||
* You may obtain a copy of Mulan PSL v2 at:
|
||||
* https://license.coscl.org.cn/MulanPSL2
|
||||
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
|
||||
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
|
||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
package cn.hutool.core.lang;
|
||||
|
||||
import cn.hutool.core.comparator.CompareUtil;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 字符串版本表示,用于解析版本号的不同部分并比较大小。<br>
|
||||
* 来自:java.lang.module.ModuleDescriptor.Version
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class Version implements Comparable<Version>, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 解析版本字符串为Version对象
|
||||
*
|
||||
* @param v 版本字符串
|
||||
* @return The resulting {@code Version}
|
||||
* @throws IllegalArgumentException 如果 {@code v} 为 {@code null}或 ""或无法解析的字符串,抛出此异常
|
||||
*/
|
||||
public static Version of(final String v) {
|
||||
return new Version(v);
|
||||
}
|
||||
|
||||
private final String version;
|
||||
|
||||
private final List<Object> sequence;
|
||||
private final List<Object> pre;
|
||||
private final List<Object> build;
|
||||
|
||||
/**
|
||||
* 版本对象,格式:tok+ ( '-' tok+)? ( '+' tok+)?,版本之间使用'.'或'-'分隔,版本号可能包含'+'<br>
|
||||
* 数字部分按照大小比较,字符串按照字典顺序比较。
|
||||
*
|
||||
* <ol>
|
||||
* <li>sequence: 主版本号</li>
|
||||
* <li>pre: 次版本号</li>
|
||||
* <li>build: 构建版本</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param v 版本字符串
|
||||
*/
|
||||
public Version(final String v) {
|
||||
Assert.notNull(v, "Null version string");
|
||||
final int n = v.length();
|
||||
if (n == 0){
|
||||
throw new IllegalArgumentException("Empty version string");
|
||||
}
|
||||
this.version = v;
|
||||
this.sequence = new ArrayList<>(4);
|
||||
this.pre = new ArrayList<>(2);
|
||||
this.build = new ArrayList<>(2);
|
||||
|
||||
int i = 0;
|
||||
char c = v.charAt(i);
|
||||
// 不检查开头字符为数字,字母按照字典顺序的数字对待
|
||||
|
||||
final List<Object> sequence = this.sequence;
|
||||
final List<Object> pre = this.pre;
|
||||
final List<Object> build = this.build;
|
||||
|
||||
// 解析主版本
|
||||
i = takeNumber(v, i, sequence);
|
||||
|
||||
while (i < n) {
|
||||
c = v.charAt(i);
|
||||
if (c == '.') {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (c == '-' || c == '+') {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
if (CharUtil.isNumber(c)){
|
||||
i = takeNumber(v, i, sequence);
|
||||
}else{
|
||||
i = takeString(v, i, sequence);
|
||||
}
|
||||
}
|
||||
|
||||
if (c == '-' && i >= n){
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析次版本
|
||||
while (i < n) {
|
||||
c = v.charAt(i);
|
||||
if (c >= '0' && c <= '9')
|
||||
i = takeNumber(v, i, pre);
|
||||
else
|
||||
i = takeString(v, i, pre);
|
||||
if (i >= n){
|
||||
break;
|
||||
}
|
||||
c = v.charAt(i);
|
||||
if (c == '.' || c == '-') {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (c == '+') {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (c == '+' && i >= n){
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析build版本
|
||||
while (i < n) {
|
||||
c = v.charAt(i);
|
||||
if (c >= '0' && c <= '9') {
|
||||
i = takeNumber(v, i, build);
|
||||
}else {
|
||||
i = takeString(v, i, build);
|
||||
}
|
||||
if (i >= n){
|
||||
break;
|
||||
}
|
||||
c = v.charAt(i);
|
||||
if (c == '.' || c == '-' || c == '+') {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final Version that) {
|
||||
int c = compareTokens(this.sequence, that.sequence);
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
if (this.pre.isEmpty()) {
|
||||
if (!that.pre.isEmpty()) {
|
||||
return +1;
|
||||
}
|
||||
} else {
|
||||
if (that.pre.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
c = compareTokens(this.pre, that.pre);
|
||||
if (c != 0) {
|
||||
return c;
|
||||
}
|
||||
return compareTokens(this.build, that.build);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object ob) {
|
||||
if (!(ob instanceof Version)){
|
||||
return false;
|
||||
}
|
||||
return compareTo((Version) ob) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return version.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return version;
|
||||
}
|
||||
|
||||
// region ----- private methods
|
||||
/**
|
||||
* 获取字符串中从位置i开始的数字,并加入到acc中<br>
|
||||
* 如 a123b,则从1开始,解析到acc中为[1, 2, 3]
|
||||
*
|
||||
* @param s 字符串
|
||||
* @param i 位置
|
||||
* @param acc 数字列表
|
||||
* @return 结束位置(不包含)
|
||||
*/
|
||||
private static int takeNumber(final String s, int i, final List<Object> acc) {
|
||||
char c = s.charAt(i);
|
||||
int d = (c - '0');
|
||||
final int n = s.length();
|
||||
while (++i < n) {
|
||||
c = s.charAt(i);
|
||||
if (CharUtil.isNumber(c)) {
|
||||
d = d * 10 + (c - '0');
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
acc.add(d);
|
||||
return i;
|
||||
}
|
||||
|
||||
// Take a string token starting at position i
|
||||
// Append it to the given list
|
||||
// Return the index of the first character not taken
|
||||
// Requires: s.charAt(i) is not '.'
|
||||
//
|
||||
|
||||
/**
|
||||
* 获取字符串中从位置i开始的字符串,并加入到acc中<br>
|
||||
* 字符串结束的位置为'.'、'-'、'+'和数字
|
||||
*
|
||||
* @param s 版本字符串
|
||||
* @param i 开始位置
|
||||
* @param acc 字符串列表
|
||||
* @return 结束位置(不包含)
|
||||
*/
|
||||
private static int takeString(final String s, int i, final List<Object> acc) {
|
||||
final int b = i;
|
||||
final int n = s.length();
|
||||
while (++i < n) {
|
||||
final char c = s.charAt(i);
|
||||
if (c != '.' && c != '-' && c != '+' && !(c >= '0' && c <= '9')){
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
acc.add(s.substring(b, i));
|
||||
return i;
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较节点
|
||||
* @param ts1 节点1
|
||||
* @param ts2 节点2
|
||||
* @return 比较结果
|
||||
*/
|
||||
private int compareTokens(final List<Object> ts1, final List<Object> ts2) {
|
||||
final int n = Math.min(ts1.size(), ts2.size());
|
||||
for (int i = 0; i < n; i++) {
|
||||
final Object o1 = ts1.get(i);
|
||||
final Object o2 = ts2.get(i);
|
||||
if ((o1 instanceof Integer && o2 instanceof Integer)
|
||||
|| (o1 instanceof String && o2 instanceof String)) {
|
||||
final int c = CompareUtil.compare(o1, o2, null);
|
||||
if (c == 0){
|
||||
continue;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
// Types differ, so convert number to string form
|
||||
final int c = o1.toString().compareTo(o2.toString());
|
||||
if (c == 0){
|
||||
continue;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
final List<Object> rest = ts1.size() > ts2.size() ? ts1 : ts2;
|
||||
final int e = rest.size();
|
||||
for (int i = n; i < e; i++) {
|
||||
final Object o = rest.get(i);
|
||||
if (o instanceof Integer && ((Integer) o) == 0){
|
||||
continue;
|
||||
}
|
||||
return ts1.size() - ts2.size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// endregion
|
||||
}
|
@ -88,4 +88,27 @@ public class VersionComparatorTest {
|
||||
compare = VersionComparator.INSTANCE.compare("1.12.1c", "1.12.2");
|
||||
Assert.assertTrue(compare < 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalsTest2() {
|
||||
final int compare = VersionComparator.INSTANCE.compare("1.12.0", "1.12");
|
||||
Assert.assertEquals(0, compare);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void I8Z3VETest() {
|
||||
// 传递性测试
|
||||
int compare = VersionComparator.INSTANCE.compare("260", "a-34");
|
||||
Assert.assertTrue(compare > 0);
|
||||
compare = VersionComparator.INSTANCE.compare("a-34", "a-3");
|
||||
Assert.assertTrue(compare > 0);
|
||||
compare = VersionComparator.INSTANCE.compare("260", "a-3");
|
||||
Assert.assertTrue(compare > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startWithNoneNumberTest() {
|
||||
final int compare = VersionComparator.INSTANCE.compare("V1", "A1");
|
||||
Assert.assertTrue(compare > 0);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user