All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.hutool.core.lang.Version Maven / Gradle / Ivy

There is a newer version: 5.8.33
Show newest version
/*
 * Copyright (c) 2024. looly([email protected])
 * 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;

/**
 * 字符串版本表示,用于解析版本号的不同部分并比较大小。
* 来自:java.lang.module.ModuleDescriptor.Version * * @author Looly */ public class Version implements Comparable, 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 sequence; private final List pre; private final List build; /** * 版本对象,格式:tok+ ( '-' tok+)? ( '+' tok+)?,版本之间使用'.'或'-'分隔,版本号可能包含'+'
* 数字部分按照大小比较,字符串按照字典顺序比较。 * *
    *
  1. sequence: 主版本号
  2. *
  3. pre: 次版本号
  4. *
  5. build: 构建版本
  6. *
* * @param v 版本字符串 */ public Version(final String v) { Assert.notNull(v, "Null version string"); final int n = v.length(); this.version = v; this.sequence = new ArrayList<>(4); this.pre = new ArrayList<>(2); this.build = new ArrayList<>(2); if (n == 0){ return; } int i = 0; char c = v.charAt(i); // 不检查开头字符为数字,字母按照字典顺序的数字对待 final List sequence = this.sequence; final List pre = this.pre; final List 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中
* 如 a123b,则从1开始,解析到acc中为[1, 2, 3] * * @param s 字符串 * @param i 位置 * @param acc 数字列表 * @return 结束位置(不包含) */ private static int takeNumber(final String s, int i, final List 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中
* 字符串结束的位置为'.'、'-'、'+'和数字 * * @param s 版本字符串 * @param i 开始位置 * @param acc 字符串列表 * @return 结束位置(不包含) */ private static int takeString(final String s, int i, final List 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 ts1, final List 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 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 }