Fix #3051: 支持解析更多版本号 (#3058)

* Fix #3051: 支持解析更多版本号

* Update GameVersionNumberTest

* Update versions.txt

* Update GameVersionNumber

* Update GameVersionNumberTest

* Update GameVersionNumberTest

* Update GameVersionNumberTest
This commit is contained in:
Glavo 2024-07-20 04:58:51 +08:00 committed by GitHub
parent 5a4825d419
commit dbb614dce1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 149 additions and 172 deletions

View File

@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.versioning;
import org.jetbrains.annotations.NotNull;
@ -10,6 +27,9 @@ import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Glavo
*/
public abstract class GameVersionNumber implements Comparable<GameVersionNumber> {
public static String[] getDefaultGameVersions() {
@ -22,26 +42,21 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
char ch = version.charAt(0);
switch (ch) {
case 'r':
return Old.parsePreClassic(version);
case 'a':
case 'b':
case 'c':
return Old.parseAlphaBetaClassic(version);
case 'i':
return Old.parseInfdev(version);
return Old.parse(version);
}
if (version.equals("0.0")) {
if (version.equals("0.0"))
return Release.ZERO;
}
if (version.startsWith("1.")) {
if (version.startsWith("1."))
return Release.parse(version);
}
if (version.length() == 6 && version.charAt(2) == 'w') {
if (version.length() == 6 && version.charAt(2) == 'w')
return Snapshot.parse(version);
}
}
} catch (IllegalArgumentException ignore) {
}
@ -97,9 +112,8 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
@Override
public int compareTo(@NotNull GameVersionNumber other) {
if (this.getType() != other.getType()) {
if (this.getType() != other.getType())
return Integer.compare(this.getType().ordinal(), other.getType().ordinal());
}
return compareToImpl(other);
}
@ -110,27 +124,25 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
}
static final class Old extends GameVersionNumber {
private static final Pattern PATTERN = Pattern.compile("[abc](?<major>[0-9]+)\\.(?<minor>[0-9]+)(\\.(?<patch>[0-9]+))?([^0-9]*(?<additional>[0-9]+).*)?");
static Old parsePreClassic(String value) {
int version;
try {
version = Integer.parseInt(value.substring("rd-".length()));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(e);
}
return new Old(value, Type.PRE_CLASSIC, version, 0, 0, 0);
}
static Old parseAlphaBetaClassic(String value) {
Matcher matcher = PATTERN.matcher(value);
if (!matcher.matches()) {
throw new IllegalArgumentException(value);
}
static Old parse(String value) {
Type type;
int prefixLength = 1;
switch (value.charAt(0)) {
case 'r':
if (!value.startsWith("rd-")) {
throw new IllegalArgumentException(value);
}
type = Type.PRE_CLASSIC;
prefixLength = "rd-".length();
break;
case 'i':
if (!value.startsWith("inf-")) {
throw new IllegalArgumentException(value);
}
type = Type.INFDEV;
prefixLength = "inf-".length();
break;
case 'a':
type = Type.ALPHA;
break;
@ -141,60 +153,22 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
type = Type.CLASSIC;
break;
default:
throw new AssertionError(value);
}
int major = Integer.parseInt(matcher.group("major"));
int minor = Integer.parseInt(matcher.group("minor"));
String patchString = matcher.group("patch");
int patch = patchString != null ? Integer.parseInt(patchString) : 0;
String additionalString = matcher.group("additional");
int additional = additionalString != null ? Integer.parseInt(additionalString) : 0;
return new Old(value, type, major, minor, patch, additional);
}
static Old parseInfdev(String value) {
String version = value.substring("inf-".length());
int major;
int patch;
try {
major = Integer.parseInt(version);
patch = 0;
} catch (NumberFormatException e) {
int idx = version.indexOf('-');
if (idx >= 0) {
try {
major = Integer.parseInt(version.substring(0, idx));
patch = Integer.parseInt(version.substring(idx + 1));
} catch (NumberFormatException ignore) {
throw new IllegalArgumentException(value);
}
} else {
throw new IllegalArgumentException(value);
}
}
return new Old(value, Type.INFDEV, major, 0, patch, 0);
}
if (value.length() < prefixLength + 1 || !Character.isDigit(value.charAt(prefixLength)))
throw new IllegalArgumentException(value);
return new Old(value, type, VersionNumber.asVersion(value.substring(prefixLength)));
}
final Type type;
final int major;
final int minor;
final int patch;
final int additional;
final VersionNumber versionNumber;
private Old(String value, Type type, int major, int minor, int patch, int additional) {
private Old(String value, Type type, VersionNumber versionNumber) {
super(value);
this.type = type;
this.major = major;
this.minor = minor;
this.patch = patch;
this.additional = additional;
this.versionNumber = versionNumber;
}
@Override
@ -204,42 +178,26 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
@Override
int compareToImpl(@NotNull GameVersionNumber other) {
Old that = (Old) other;
int c = Integer.compare(this.major, that.major);
if (c != 0) {
return c;
}
c = Integer.compare(this.minor, that.minor);
if (c != 0) {
return c;
}
c = Integer.compare(this.patch, that.patch);
if (c != 0) {
return c;
}
return Integer.compare(this.additional, that.additional);
return this.versionNumber.compareTo(((Old) other).versionNumber);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!(o instanceof Old)) return false;
Old other = (Old) o;
return major == other.major && minor == other.minor && patch == other.patch && additional == other.additional && type == other.type;
return type == other.type && this.versionNumber.compareTo(other.versionNumber) == 0;
}
@Override
public int hashCode() {
return Objects.hash(type, major, minor, patch, additional);
return Objects.hash(type, versionNumber.hashCode());
}
}
static final class Release extends GameVersionNumber {
private static final Pattern PATTERN = Pattern.compile("1\\.(?<minor>[0-9]+)(\\.(?<patch>[0-9]+))?((?<eaType>(-[a-zA-Z]+| Pre-Release ))(?<eaVersion>[0-9]+))?");
private static final Pattern PATTERN = Pattern.compile("1\\.(?<minor>[0-9]+)(\\.(?<patch>[0-9]+))?((?<eaType>(-[a-zA-Z]+| Pre-Release ))(?<eaVersion>.+))?");
static final int TYPE_GA = Integer.MAX_VALUE;
@ -248,7 +206,7 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
static final int TYPE_PRE = 2;
static final int TYPE_RC = 3;
static final Release ZERO = new Release("0.0", 0, 0, 0, TYPE_GA, 0);
static final Release ZERO = new Release("0.0", 0, 0, 0, TYPE_GA, VersionNumber.ZERO);
static Release parse(String value) {
Matcher matcher = PATTERN.matcher(value);
@ -276,7 +234,7 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
}
String eaVersionString = matcher.group("eaVersion");
int eaVersion = eaVersionString == null ? 0 : Integer.parseInt(eaVersionString);
VersionNumber eaVersion = eaVersionString != null ? VersionNumber.asVersion(eaVersionString) : VersionNumber.ZERO;
return new Release(value, 1, minor, patch, eaType, eaVersion);
}
@ -286,9 +244,9 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
private final int patch;
private final int eaType;
private final int eaVersion;
private final VersionNumber eaVersion;
Release(String value, int major, int minor, int patch, int eaType, int eaVersion) {
Release(String value, int major, int minor, int patch, int eaType, VersionNumber eaVersion) {
super(value);
this.major = major;
this.minor = minor;
@ -323,36 +281,31 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
return c;
}
return Integer.compare(this.eaVersion, other.eaVersion);
return this.eaVersion.compareTo(other.eaVersion);
}
int compareToSnapshot(Snapshot other) {
int idx = Arrays.binarySearch(Versions.SNAPSHOT_INTS, other.intValue);
if (idx >= 0) {
if (idx >= 0)
return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;
}
idx = -(idx + 1);
if (idx == Versions.SNAPSHOT_INTS.length) {
if (idx == Versions.SNAPSHOT_INTS.length)
return -1;
}
return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;
}
@Override
int compareToImpl(@NotNull GameVersionNumber other) {
if (other instanceof Release) {
if (other instanceof Release)
return compareToRelease((Release) other);
}
if (other instanceof Snapshot) {
if (other instanceof Snapshot)
return compareToSnapshot((Snapshot) other);
}
if (other instanceof Special) {
if (other instanceof Special)
return -((Special) other).compareToReleaseOrSnapshot(this);
}
throw new AssertionError(other.getClass());
}
@ -367,15 +320,14 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Release other = (Release) o;
return major == other.major && minor == other.minor && patch == other.patch && eaType == other.eaType && eaVersion == other.eaVersion;
return major == other.major && minor == other.minor && patch == other.patch && eaType == other.eaType && eaVersion.equals(other.eaVersion);
}
}
static final class Snapshot extends GameVersionNumber {
static Snapshot parse(String value) {
if (value.length() != 6 || value.charAt(2) != 'w') {
if (value.length() != 6 || value.charAt(2) != 'w')
throw new IllegalArgumentException(value);
}
int year;
int week;
@ -387,9 +339,8 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
}
char suffix = value.charAt(5);
if ((suffix < 'a' || suffix > 'z') && suffix != '~') {
if ((suffix < 'a' || suffix > 'z') && suffix != '~')
throw new IllegalArgumentException(value);
}
return new Snapshot(value, year, week, suffix);
}
@ -412,17 +363,14 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
@Override
int compareToImpl(@NotNull GameVersionNumber other) {
if (other instanceof Release) {
if (other instanceof Release)
return -((Release) other).compareToSnapshot(this);
}
if (other instanceof Snapshot) {
if (other instanceof Snapshot)
return Integer.compare(this.intValue, ((Snapshot) other).intValue);
}
if (other instanceof Special) {
if (other instanceof Special)
return -((Special) other).compareToReleaseOrSnapshot(this);
}
throw new AssertionError(other.getClass());
}
@ -460,9 +408,8 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
}
VersionNumber asVersionNumber() {
if (versionNumber != null) {
if (versionNumber != null)
return versionNumber;
}
return versionNumber = VersionNumber.asVersion(value);
}
@ -473,9 +420,7 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
v = ((Special) v).prev;
}
if (v == null) {
throw new AssertionError("version: " + value);
}
if (v == null) throw new AssertionError("version: " + value);
return v;
}
@ -493,28 +438,23 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
}
int compareToSpecial(Special other) {
if (this.isUnknown()) {
if (this.isUnknown())
return other.isUnknown() ? this.asVersionNumber().compareTo(other.asVersionNumber()) : 1;
}
if (other.isUnknown()) {
if (other.isUnknown())
return -1;
}
if (this.value.equals(other.value)) {
if (this.value.equals(other.value))
return 0;
}
int c = this.getPrevNormalVersion().compareTo(other.getPrevNormalVersion());
if (c != 0) {
if (c != 0)
return c;
}
GameVersionNumber v = prev;
while (v instanceof Special) {
if (v == other) {
if (v == other)
return 1;
}
v = ((Special) v).prev;
}
@ -524,17 +464,14 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
@Override
int compareToImpl(@NotNull GameVersionNumber o) {
if (o instanceof Release) {
if (o instanceof Release)
return compareToReleaseOrSnapshot(o);
}
if (o instanceof Snapshot) {
if (o instanceof Snapshot)
return compareToReleaseOrSnapshot(o);
}
if (o instanceof Special) {
if (o instanceof Special)
return compareToSpecial((Special) o);
}
throw new AssertionError(o.getClass());
}
@ -566,7 +503,6 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
List<Snapshot> snapshots = new ArrayList<>(1024);
List<Release> snapshotPrev = new ArrayList<>(1024);
// Convert it to dynamic resource after the website is repaired?
try (BufferedReader reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream("/assets/game/versions.txt"), StandardCharsets.US_ASCII))) {
Release currentRelease = null;
GameVersionNumber prev = null;

View File

@ -33,6 +33,8 @@ import java.util.Objects;
*/
public final class VersionNumber implements Comparable<VersionNumber> {
public static final VersionNumber ZERO = asVersion("0");
public static VersionNumber asVersion(String version) {
Objects.requireNonNull(version);
return new VersionNumber(version);

View File

@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2024 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.versioning;
import org.junit.jupiter.api.Test;
@ -5,6 +22,7 @@ import org.junit.jupiter.api.Test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
@ -13,18 +31,25 @@ import static org.junit.jupiter.api.Assertions.*;
/**
* @author Glavo
*/
public class GameVersionNumberTest {
public final class GameVersionNumberTest {
@Test
public void testSortVersions() throws IOException {
private static List<String> readVersions() {
List<String> versions = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream("/assets/game/versions.txt"), StandardCharsets.UTF_8))) {
for (String line; (line = reader.readLine()) != null && !line.isEmpty(); ) {
versions.add(line);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return versions;
}
@Test
public void testSortVersions() {
List<String> versions = readVersions();
List<String> copied = new ArrayList<>(versions);
Collections.shuffle(copied, new Random(0));
copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion));
@ -42,52 +67,57 @@ public class GameVersionNumberTest {
private static void assertGameVersionEquals(String version1, String version2) {
assertEquals(0, GameVersionNumber.asGameVersion(version1).compareTo(version2), errorMessage(version1, version2));
assertEquals(GameVersionNumber.asGameVersion(version1), GameVersionNumber.asGameVersion(version2), errorMessage(version1, version2));
}
private static void assertLessThan(String version1, String version2) {
assertTrue(GameVersionNumber.asGameVersion(version1).compareTo(version2) < 0, errorMessage(version1, version2));
private static String toString(GameVersionNumber gameVersionNumber) {
return gameVersionNumber.getClass().getSimpleName();
}
private static void assertOrder(String... versions) {
for (int i = 0; i < versions.length - 1; i++) {
GameVersionNumber version1 = GameVersionNumber.asGameVersion(versions[i]);
//noinspection EqualsWithItself
assertEquals(0, version1.compareTo(version1), "version=" + versions[i]);
assertGameVersionEquals(versions[i]);
for (int j = i + 1; j < versions.length; j++) {
GameVersionNumber version2 = GameVersionNumber.asGameVersion(versions[j]);
assertEquals(-1, version1.compareTo(version2), String.format("version1=%s, version2=%s", versions[i], versions[j]));
assertEquals(1, version2.compareTo(version1), String.format("version1=%s, version2=%s", versions[i], versions[j]));
assertEquals(-1, version1.compareTo(version2), String.format("version1=%s (%s), version2=%s (%s)", versions[i], toString(version1), versions[j], toString(version2)));
assertEquals(1, version2.compareTo(version1), String.format("version1=%s (%s), version2=%s (%s)", versions[i], toString(version1), versions[j], toString(version2)));
}
}
assertGameVersionEquals(versions[versions.length - 1]);
}
private void assertOldVersion(String oldVersion, GameVersionNumber.Type type, String versionNumber) {
GameVersionNumber version = GameVersionNumber.asGameVersion(oldVersion);
assertInstanceOf(GameVersionNumber.Old.class, version);
GameVersionNumber.Old old = (GameVersionNumber.Old) version;
assertSame(type, old.type);
assertEquals(VersionNumber.asVersion(versionNumber), old.versionNumber);
}
@Test
public void testParseOld() {
{
GameVersionNumber version = GameVersionNumber.asGameVersion("b1.0");
assertInstanceOf(GameVersionNumber.Old.class, version);
GameVersionNumber.Old old = (GameVersionNumber.Old) version;
assertEquals(GameVersionNumber.Type.BETA, old.type);
assertEquals(1, old.major);
assertEquals(0, old.minor);
assertEquals(0, old.patch);
assertEquals(0, old.additional);
}
assertOldVersion("rd-132211", GameVersionNumber.Type.PRE_CLASSIC, "132211");
assertOldVersion("inf-20100618", GameVersionNumber.Type.INFDEV, "20100618");
assertOldVersion("inf-20100330-1", GameVersionNumber.Type.INFDEV, "20100330-1");
assertOldVersion("a1.0.6", GameVersionNumber.Type.ALPHA, "1.0.6");
assertOldVersion("a1.0.8_01", GameVersionNumber.Type.ALPHA, "1.0.8_01");
assertOldVersion("a1.0.13_01-1", GameVersionNumber.Type.ALPHA, "1.0.13_01-1");
assertOldVersion("b1.0", GameVersionNumber.Type.BETA, "1.0");
assertOldVersion("b1.0_01", GameVersionNumber.Type.BETA, "1.0_01");
assertOldVersion("b1.8-pre1-2", GameVersionNumber.Type.BETA, "1.8-pre1-2");
assertOldVersion("b1.9-pre1", GameVersionNumber.Type.BETA, "1.9-pre1");
}
{
GameVersionNumber version = GameVersionNumber.asGameVersion("b1.0_01");
assertInstanceOf(GameVersionNumber.Old.class, version);
GameVersionNumber.Old old = (GameVersionNumber.Old) version;
assertEquals(GameVersionNumber.Type.BETA, old.type);
assertEquals(1, old.major);
assertEquals(0, old.minor);
assertEquals(0, old.patch);
assertEquals(1, old.additional);
@Test
public void testParseNew() {
List<String> versions = readVersions();
for (String version : versions) {
assertFalse(GameVersionNumber.asGameVersion(version) instanceof GameVersionNumber.Old, "version=" + version);
}
}
@ -138,15 +168,24 @@ public class GameVersionNumberTest {
"inf-20100330-2",
"inf-20100618",
"a1.0.4",
"a1.0.8_01",
"a1.0.10",
"a1.0.13_01-1",
"a1.0.17_02",
"a1.0.17_04",
"a1.1.0",
"a1.1.1",
"b1.0",
"b1.0_01",
"b1.1_02",
"b1.2",
"b1.8-pre1-2",
"b1.8.1",
"0.0",
"1.0.0-rc1",
"1.0.0-rc2-1",
"1.0.0-rc2-2",
"1.0.0-rc2-3",
"1.0",
"11w47a",
"1.1",