mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
修复VersionComparator违反传递问题
This commit is contained in:
parent
115b15f010
commit
1fba2ca52b
@ -12,22 +12,16 @@
|
|||||||
|
|
||||||
package org.dromara.hutool.core.comparator;
|
package org.dromara.hutool.core.comparator;
|
||||||
|
|
||||||
import org.dromara.hutool.core.convert.Convert;
|
import org.dromara.hutool.core.lang.Version;
|
||||||
import org.dromara.hutool.core.math.NumberUtil;
|
|
||||||
import org.dromara.hutool.core.regex.ReUtil;
|
|
||||||
import org.dromara.hutool.core.text.StrUtil;
|
|
||||||
import org.dromara.hutool.core.text.split.SplitUtil;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.List;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 版本比较器<br>
|
* 版本比较器<br>
|
||||||
* 比较两个版本的大小<br>
|
* 比较两个版本的大小<br>
|
||||||
* 排序时版本从小到大排序,即比较时小版本在前,大版本在后<br>
|
* 排序时版本从小到大排序,即比较时小版本在前,大版本在后<br>
|
||||||
* 支持如:1.3.20.8,6.82.20160101,8.5a/8.5c等版本形式<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
|
* @author Looly
|
||||||
* @since 4.0.2
|
* @since 4.0.2
|
||||||
@ -35,8 +29,6 @@ import java.util.regex.Pattern;
|
|||||||
public class VersionComparator extends NullComparator<String> implements Serializable {
|
public class VersionComparator extends NullComparator<String> implements Serializable {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private static final Pattern PATTERN_PRE_NUMBERS= Pattern.compile("^\\d+");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单例
|
* 单例
|
||||||
*/
|
*/
|
||||||
@ -78,40 +70,6 @@ public class VersionComparator extends NullComparator<String> implements Seriali
|
|||||||
* @param version2 版本2
|
* @param version2 版本2
|
||||||
*/
|
*/
|
||||||
private static int compareVersion(final String version1, final String version2) {
|
private static int compareVersion(final String version1, final String version2) {
|
||||||
final List<String> v1s = SplitUtil.splitTrim(version1, StrUtil.DOT);
|
return CompareUtil.compare(Version.of(version1), Version.of(version2));
|
||||||
final List<String> v2s = SplitUtil.splitTrim(version2, StrUtil.DOT);
|
|
||||||
|
|
||||||
int diff = 0;
|
|
||||||
final int minSize = Math.min(v1s.size(), v2s.size());// 取最小长度值
|
|
||||||
String v1;
|
|
||||||
String v2;
|
|
||||||
for (int i = 0; i < minSize; 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 org.dromara.hutool.core.lang;
|
||||||
|
|
||||||
|
import org.dromara.hutool.core.comparator.CompareUtil;
|
||||||
|
import org.dromara.hutool.core.text.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
|
||||||
|
}
|
@ -98,4 +98,27 @@ public class VersionComparatorTest {
|
|||||||
compare = VersionComparator.INSTANCE.compare("1.12.1c", "1.12.2");
|
compare = VersionComparator.INSTANCE.compare("1.12.1c", "1.12.2");
|
||||||
Assertions.assertTrue(compare < 0);
|
Assertions.assertTrue(compare < 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void equalsTest2() {
|
||||||
|
final int compare = VersionComparator.INSTANCE.compare("1.12.0", "1.12");
|
||||||
|
Assertions.assertEquals(0, compare);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void I8Z3VETest() {
|
||||||
|
// 传递性测试
|
||||||
|
int compare = VersionComparator.INSTANCE.compare("260", "a-34");
|
||||||
|
Assertions.assertTrue(compare > 0);
|
||||||
|
compare = VersionComparator.INSTANCE.compare("a-34", "a-3");
|
||||||
|
Assertions.assertTrue(compare > 0);
|
||||||
|
compare = VersionComparator.INSTANCE.compare("260", "a-3");
|
||||||
|
Assertions.assertTrue(compare > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void startWithNoneNumberTest() {
|
||||||
|
final int compare = VersionComparator.INSTANCE.compare("V1", "A1");
|
||||||
|
Assertions.assertTrue(compare > 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user