更新 GameVersionNumber 解析规则 (#4917)

1. 支持 [Minecraft
新版本号方案](https://www.minecraft.net/en-us/article/minecraft-new-version-numbering-system)
2. 支持对版本号进行归一化处理。
This commit is contained in:
Glavo
2025-12-06 20:55:11 +08:00
committed by GitHub
parent 7ac22ad567
commit 456d13959e
19 changed files with 674 additions and 251 deletions

View File

@@ -24,6 +24,7 @@ import com.github.steveice10.opennbt.tag.builtin.StringTag;
import com.github.steveice10.opennbt.tag.builtin.Tag;
import javafx.scene.image.Image;
import org.jackhuang.hmcl.util.io.*;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
@@ -48,7 +49,7 @@ public final class World {
private final Path file;
private String fileName;
private String worldName;
private String gameVersion;
private GameVersionNumber gameVersion;
private long lastPlayed;
private Image icon;
private Long seed;
@@ -108,7 +109,7 @@ public final class World {
return lastPlayed;
}
public String getGameVersion() {
public @Nullable GameVersionNumber getGameVersion() {
return gameVersion;
}
@@ -188,7 +189,7 @@ public final class World {
CompoundTag version = data.get("Version");
if (version.get("Name") instanceof StringTag)
gameVersion = version.<StringTag>get("Name").getValue();
gameVersion = GameVersionNumber.asGameVersion(version.<StringTag>get("Name").getValue());
}
Tag worldGenSettings = data.get("WorldGenSettings");

View File

@@ -17,7 +17,7 @@
*/
package org.jackhuang.hmcl.util.versioning;
import org.intellij.lang.annotations.MagicConstant;
import org.jackhuang.hmcl.util.ToStringBuilder;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
@@ -28,6 +28,8 @@ import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
/**
* @author Glavo
*/
@@ -38,6 +40,10 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
}
public static GameVersionNumber asGameVersion(String version) {
GameVersionNumber versionNumber = Versions.SPECIALS.get(version);
if (versionNumber != null)
return versionNumber;
try {
if (!version.isEmpty()) {
char ch = version.charAt(0);
@@ -53,20 +59,15 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
if (version.equals("0.0"))
return Release.ZERO;
if (version.startsWith("1."))
return Release.parse(version);
if (version.length() >= 6 && version.charAt(2) == 'w')
return LegacySnapshot.parse(version);
if (version.length() == 6 && version.charAt(2) == 'w')
return Snapshot.parse(version);
return Release.parse(version);
}
} catch (IllegalArgumentException ignore) {
} catch (Throwable ignore) {
}
Special special = Versions.SPECIALS.get(version);
if (special == null) {
special = new Special(version);
}
return special;
return new Special(version, version);
}
public static GameVersionNumber asGameVersion(Optional<String> version) {
@@ -94,17 +95,22 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
}
final String value;
final String normalized;
GameVersionNumber(String value) {
GameVersionNumber(String value, String normalized) {
this.value = value;
this.normalized = normalized;
}
public boolean isAprilFools() {
if (this instanceof Special && !value.endsWith("_unobfuscated"))
return true;
if (this instanceof Special) {
String normalizedVersion = this.toNormalizedString();
return !normalizedVersion.startsWith("1.") && !normalizedVersion.equals("13w12~")
|| normalizedVersion.equals("1.RV-Pre1");
}
if (this instanceof Snapshot snapshot) {
return snapshot.intValue == Snapshot.toInt(15, 14, 'a');
if (this instanceof LegacySnapshot snapshot) {
return snapshot.intValue == LegacySnapshot.toInt(15, 14, 'a', false);
}
return false;
@@ -144,10 +150,10 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
///
/// ```java
/// GameVersionNumber.asVersion("...").isAtLeast("1.13", "17w43a");
/// ```
///```
///
/// @param strictReleaseVersion When `strictReleaseVersion` is `false`, `releaseVersion` is considered less than
/// its corresponding pre/rc versions.
/// its corresponding pre/rc versions.
public boolean isAtLeast(@NotNull String releaseVersion, @NotNull String snapshotVersion, boolean strictReleaseVersion) {
if (this instanceof Release self) {
Release other;
@@ -159,17 +165,35 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
return self.compareToRelease(other) >= 0;
} else {
return this.compareTo(Snapshot.parse(snapshotVersion)) >= 0;
return this.compareTo(LegacySnapshot.parse(snapshotVersion)) >= 0;
}
}
public String toNormalizedString() {
return normalized;
}
@Override
public String toString() {
return value;
}
protected ToStringBuilder buildDebugString() {
return new ToStringBuilder(this)
.append("value", value)
.append("normalized", normalized)
.append("type", getType());
}
public final String toDebugString() {
return buildDebugString().toString();
}
public static final class Old extends GameVersionNumber {
static Old parse(String value) {
if (value.isEmpty())
throw new IllegalArgumentException("Empty old version number");
Type type;
int prefixLength = 1;
switch (value.charAt(0)) {
@@ -215,7 +239,7 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
final VersionNumber versionNumber;
private Old(String value, Type type, VersionNumber versionNumber) {
super(value);
super(value, value);
this.type = type;
this.versionNumber = versionNumber;
}
@@ -231,83 +255,137 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
return o instanceof Old other && type == other.type && this.versionNumber.compareTo(other.versionNumber) == 0;
public int hashCode() {
return Objects.hash(type, versionNumber);
}
@Override
public int hashCode() {
return Objects.hash(type, versionNumber.hashCode());
public boolean equals(Object o) {
return o instanceof Old that
&& this.type == that.type
&& this.versionNumber.equals(that.versionNumber);
}
}
public static final class Release extends GameVersionNumber {
private static final int MINIMUM_YEAR_MAJOR_VERSION = 25;
private static final Pattern PATTERN = Pattern.compile("1\\.(?<minor>[0-9]+)(\\.(?<patch>[0-9]+))?((?<eaType>(-[a-zA-Z]+| Pre-Release ))(?<eaVersion>.+))?");
public enum ReleaseType {
UNKNOWN(""),
SNAPSHOT("-snapshot-"),
PRE_RELEASE("-pre"),
RELEASE_CANDIDATE("-rc"),
GA("");
private final String infix;
public static final int TYPE_GA = Integer.MAX_VALUE;
ReleaseType(String infix) {
this.infix = infix;
}
}
public static final int TYPE_UNKNOWN = 0;
public static final int TYPE_EXP = 1;
public static final int TYPE_PRE = 2;
public static final int TYPE_RC = 3;
public enum Additional {
NONE(""), UNOBFUSCATED("_unobfuscated");
private final String suffix;
static final Release ZERO = new Release("0.0", 0, 0, 0, TYPE_GA, VersionNumber.ZERO);
Additional(String suffix) {
this.suffix = suffix;
}
}
static final Release ZERO = new Release(
"0.0", "0.0",
0, 0, 0,
ReleaseType.UNKNOWN, VersionNumber.ZERO, Additional.NONE
);
private static final Pattern VERSION_PATTERN = Pattern.compile("(?<prefix>(?<major>1|[1-9]\\d+)\\.(?<minor>\\d+)(\\.(?<patch>[0-9]+))?)(?<suffix>.*)");
static Release parse(String value) {
Matcher matcher = PATTERN.matcher(value);
Matcher matcher = VERSION_PATTERN.matcher(value);
if (!matcher.matches()) {
throw new IllegalArgumentException(value);
}
int major = Integer.parseInt(matcher.group("major"));
if (major != 1 && major < MINIMUM_YEAR_MAJOR_VERSION)
throw new IllegalArgumentException(value);
int minor = Integer.parseInt(matcher.group("minor"));
String patchString = matcher.group("patch");
int patch = patchString != null ? Integer.parseInt(patchString) : 0;
String eaTypeString = matcher.group("eaType");
int eaType;
if (eaTypeString == null) {
eaType = TYPE_GA;
} else if ("-pre".equals(eaTypeString) || " Pre-Release ".equals(eaTypeString)) {
eaType = TYPE_PRE;
} else if ("-rc".equals(eaTypeString)) {
eaType = TYPE_RC;
} else if ("-exp".equals(eaTypeString)) {
eaType = TYPE_EXP;
String suffix = matcher.group("suffix");
ReleaseType releaseType;
VersionNumber eaVersion;
Additional additional = Additional.NONE;
boolean needNormalize = false;
if (suffix.endsWith("_unobfuscated")) {
suffix = suffix.substring(0, suffix.length() - "_unobfuscated".length());
additional = Additional.UNOBFUSCATED;
} else if (suffix.endsWith(" Unobfuscated")) {
needNormalize = true;
suffix = suffix.substring(0, suffix.length() - " Unobfuscated".length());
additional = Additional.UNOBFUSCATED;
}
if (suffix.isEmpty()) {
releaseType = ReleaseType.GA;
eaVersion = VersionNumber.ZERO;
} else if (suffix.startsWith("-snapshot-")) {
releaseType = ReleaseType.SNAPSHOT;
eaVersion = VersionNumber.asVersion(suffix.substring("-snapshot-".length()));
} else if (suffix.startsWith("-pre")) {
releaseType = ReleaseType.PRE_RELEASE;
eaVersion = VersionNumber.asVersion(suffix.substring("-pre".length()));
} else if (suffix.startsWith(" Pre-Release ")) {
needNormalize = true;
releaseType = ReleaseType.PRE_RELEASE;
eaVersion = VersionNumber.asVersion(suffix.substring(" Pre-Release ".length()));
} else if (suffix.startsWith("-rc")) {
releaseType = ReleaseType.RELEASE_CANDIDATE;
eaVersion = VersionNumber.asVersion(suffix.substring("-rc".length()));
} else if (suffix.startsWith(" Release Candidate ")) {
needNormalize = true;
releaseType = ReleaseType.RELEASE_CANDIDATE;
eaVersion = VersionNumber.asVersion(suffix.substring(" Release Candidate ".length()));
} else {
eaType = TYPE_UNKNOWN;
throw new IllegalArgumentException(value);
}
String eaVersionString = matcher.group("eaVersion");
VersionNumber eaVersion = eaVersionString != null ? VersionNumber.asVersion(eaVersionString) : VersionNumber.ZERO;
return new Release(value, 1, minor, patch, eaType, eaVersion);
}
private static int getNumberLength(String value, int offset) {
int current = offset;
while (current < value.length()) {
char ch = value.charAt(current);
if (ch < '0' || ch > '9')
break;
current++;
String normalized;
if (needNormalize) {
StringBuilder builder = new StringBuilder(value.length());
builder.append(matcher.group("prefix"));
if (releaseType != ReleaseType.GA) {
builder.append(releaseType.infix);
builder.append(eaVersion);
}
builder.append(additional.suffix);
normalized = builder.toString();
} else {
normalized = value;
}
return current - offset;
return new Release(value, normalized, major, minor, patch, releaseType, eaVersion, additional);
}
/// Quickly parses a simple format (`1\.[0-9]+(\.[0-9]+)?`) release version.
/// The returned [#eaType] will be set to [#TYPE_UNKNOWN], meaning it will be less than all pre/rc and official versions of this version.
/// Quickly parses a simple format (`[1-9][0-9]+\.[0-9]+(\.[0-9]+)?`) release version.
/// The returned [#eaType] will be set to [ReleaseType#UNKNOWN], meaning it will be less than all pre/rc and official versions of this version.
///
/// @see GameVersionNumber#isAtLeast(String, String)
static Release parseSimple(String value) {
if (!value.startsWith("1."))
int majorLength = getNumberLength(value, 0);
if (majorLength == 0 || value.length() < majorLength + 2 || value.charAt(majorLength) != '.')
throw new IllegalArgumentException(value);
final int minorOffset = 2;
int major = Integer.parseInt(value.substring(0, majorLength));
if (major != 1 && major < MINIMUM_YEAR_MAJOR_VERSION)
throw new IllegalArgumentException(value);
final int minorOffset = majorLength + 1;
int minorLength = getNumberLength(value, minorOffset);
if (minorLength == 0)
@@ -326,27 +404,41 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
patch = Integer.parseInt(value.substring(patchOffset));
}
return new Release(value, 1, minor, patch, TYPE_UNKNOWN, VersionNumber.ZERO);
return new Release(value, value, major, minor, patch, ReleaseType.UNKNOWN, VersionNumber.ZERO, Additional.NONE);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(value);
}
}
private static int getNumberLength(String value, int offset) {
int current = offset;
while (current < value.length()) {
char ch = value.charAt(current);
if (ch < '0' || ch > '9')
break;
current++;
}
return current - offset;
}
private final int major;
private final int minor;
private final int patch;
@MagicConstant(intValues = {TYPE_GA, TYPE_UNKNOWN, TYPE_EXP, TYPE_PRE, TYPE_RC})
private final int eaType;
private final ReleaseType eaType;
private final VersionNumber eaVersion;
private final Additional additional;
Release(String value, int major, int minor, int patch, int eaType, VersionNumber eaVersion) {
super(value);
Release(String value, String normalized, int major, int minor, int patch, ReleaseType eaType, VersionNumber eaVersion, Additional additional) {
super(value, normalized);
this.major = major;
this.minor = minor;
this.patch = patch;
this.eaType = eaType;
this.eaVersion = eaVersion;
this.additional = additional;
}
@Override
@@ -367,35 +459,45 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
if (c != 0)
return c;
c = Integer.compare(this.eaType, other.eaType);
c = this.eaType.compareTo(other.eaType);
if (c != 0)
return c;
return this.eaVersion.compareTo(other.eaVersion);
c = this.eaVersion.compareTo(other.eaVersion);
if (c != 0)
return c;
return this.additional.compareTo(other.additional);
}
int compareToSnapshot(Snapshot other) {
int idx = Arrays.binarySearch(Versions.SNAPSHOT_INTS, other.intValue);
if (idx >= 0)
return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;
idx = -(idx + 1);
if (idx == Versions.SNAPSHOT_INTS.length)
int compareToSnapshot(LegacySnapshot other) {
if (major == 0) {
return -1;
} else if (major == 1) {
int idx = Arrays.binarySearch(Versions.SNAPSHOT_INTS, other.intValue);
if (idx >= 0)
return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;
return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;
idx = -(idx + 1);
if (idx == Versions.SNAPSHOT_INTS.length)
return -1;
return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;
} else {
return 1;
}
}
@Override
int compareToImpl(@NotNull GameVersionNumber other) {
if (other instanceof Release)
return compareToRelease((Release) other);
if (other instanceof Release release)
return compareToRelease(release);
if (other instanceof Snapshot)
return compareToSnapshot((Snapshot) other);
if (other instanceof LegacySnapshot snapshot)
return compareToSnapshot(snapshot);
if (other instanceof Special)
return -((Special) other).compareToReleaseOrSnapshot(this);
if (other instanceof Special special)
return -special.compareToReleaseOrSnapshot(this);
throw new AssertionError(other.getClass());
}
@@ -412,7 +514,7 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
return patch;
}
public int getEaType() {
public ReleaseType getEaType() {
return eaType;
}
@@ -420,28 +522,65 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
return eaVersion;
}
public Additional getAdditional() {
return additional;
}
@Override
public int hashCode() {
return Objects.hash(major, minor, patch, eaType, eaVersion);
return Objects.hash(major, minor, patch, eaType, eaVersion, additional);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
return o instanceof Release other
&& major == other.major
&& minor == other.minor
&& patch == other.patch
&& eaType == other.eaType
&& eaVersion.equals(other.eaVersion);
return o instanceof Release that
&& this.major == that.major
&& this.minor == that.minor
&& this.patch == that.patch
&& this.eaType == that.eaType
&& this.eaVersion.equals(that.eaVersion)
&& this.additional == that.additional;
}
@Override
protected ToStringBuilder buildDebugString() {
return super.buildDebugString()
.append("major", major)
.append("minor", minor)
.append("patch", patch)
.append("eaType", eaType)
.append("eaVersion", eaVersion)
.append("additional", additional);
}
}
public static final class Snapshot extends GameVersionNumber {
static Snapshot parse(String value) {
if (value.length() != 6 || value.charAt(2) != 'w')
/// Legacy snapshot version numbers like `25w46a`.
public static final class LegacySnapshot extends GameVersionNumber {
static LegacySnapshot parse(String value) {
if (value.length() < 6 || value.charAt(2) != 'w')
throw new IllegalArgumentException(value);
int prefixLength;
boolean unobfuscated;
String normalized;
if (value.endsWith("_unobfuscated")) {
prefixLength = value.length() - "_unobfuscated".length();
unobfuscated = true;
normalized = value;
} else if (value.endsWith(" Unobfuscated")) {
prefixLength = value.length() - " Unobfuscated".length();
unobfuscated = true;
normalized = value.substring(0, prefixLength) + "_unobfuscated";
} else {
prefixLength = value.length();
unobfuscated = false;
normalized = value;
}
if (prefixLength != 6) {
throw new IllegalArgumentException(value);
}
int year;
int week;
try {
@@ -452,21 +591,21 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
}
char suffix = value.charAt(5);
if ((suffix < 'a' || suffix > 'z') && suffix != '~')
if (suffix < 'a' || suffix > 'z')
throw new IllegalArgumentException(value);
return new Snapshot(value, year, week, suffix);
return new LegacySnapshot(value, normalized, year, week, suffix, unobfuscated);
}
static int toInt(int year, int week, char suffix) {
return (year << 16) | (week << 8) | suffix;
static int toInt(int year, int week, char suffix, boolean unobfuscated) {
return (year << 24) | (week << 16) | (suffix << 8) | (unobfuscated ? 1 : 0);
}
final int intValue;
Snapshot(String value, int year, int week, char suffix) {
super(value);
this.intValue = toInt(year, week, suffix);
LegacySnapshot(String value, String normalized, int year, int week, char suffix, boolean unobfuscated) {
super(value, normalized);
this.intValue = toInt(year, week, suffix, unobfuscated);
}
@Override
@@ -476,40 +615,52 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
@Override
int compareToImpl(@NotNull GameVersionNumber other) {
if (other instanceof Release)
return -((Release) other).compareToSnapshot(this);
if (other instanceof Release otherRelease)
return -otherRelease.compareToSnapshot(this);
if (other instanceof Snapshot)
return Integer.compare(this.intValue, ((Snapshot) other).intValue);
if (other instanceof LegacySnapshot otherSnapshot)
return Integer.compare(this.intValue, otherSnapshot.intValue);
if (other instanceof Special)
return -((Special) other).compareToReleaseOrSnapshot(this);
if (other instanceof Special otherSpecial)
return -otherSpecial.compareToReleaseOrSnapshot(this);
throw new AssertionError(other.getClass());
}
public int getYear() {
return (intValue >> 16) & 0xff;
return (intValue >> 24) & 0xff;
}
public int getWeek() {
return (intValue >> 8) & 0xff;
return (intValue >> 16) & 0xff;
}
public char getSuffix() {
return (char) (intValue & 0xff);
return (char) ((intValue >> 8) & 0xff);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
return o instanceof Snapshot other && this.intValue == other.intValue;
public boolean isUnobfuscated() {
return (intValue & 0b00000001) != 0;
}
@Override
public int hashCode() {
return intValue;
}
@Override
public boolean equals(Object o) {
return o instanceof LegacySnapshot that && this.intValue == that.intValue;
}
@Override
protected ToStringBuilder buildDebugString() {
return super.buildDebugString()
.append("year", getYear())
.append("week", getWeek())
.append("suffix", getSuffix())
.append("unobfuscated", isUnobfuscated());
}
}
public static final class Special extends GameVersionNumber {
@@ -517,8 +668,8 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
private GameVersionNumber prev;
Special(String value) {
super(value);
Special(String value, String normalized) {
super(value, normalized);
}
@Override
@@ -534,13 +685,13 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
if (versionNumber != null)
return versionNumber;
return versionNumber = VersionNumber.asVersion(value);
return versionNumber = VersionNumber.asVersion(normalized);
}
GameVersionNumber getPrevNormalVersion() {
GameVersionNumber v = prev;
while (v instanceof Special) {
v = ((Special) v).prev;
while (v instanceof Special special) {
v = special.prev;
}
if (v == null) throw new AssertionError("version: " + value);
@@ -567,7 +718,7 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
if (other.isUnknown())
return -1;
if (this.value.equals(other.value))
if (this.normalized.equals(other.normalized))
return 0;
int c = this.getPrevNormalVersion().compareTo(other.getPrevNormalVersion());
@@ -575,11 +726,11 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
return c;
GameVersionNumber v = prev;
while (v instanceof Special) {
while (v instanceof Special special) {
if (v == other)
return 1;
v = ((Special) v).prev;
v = special.prev;
}
return -1;
@@ -587,27 +738,23 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
@Override
int compareToImpl(@NotNull GameVersionNumber o) {
if (o instanceof Release)
if (o instanceof Release || o instanceof LegacySnapshot)
return compareToReleaseOrSnapshot(o);
if (o instanceof Snapshot)
return compareToReleaseOrSnapshot(o);
if (o instanceof Special)
return compareToSpecial((Special) o);
if (o instanceof Special special)
return compareToSpecial(special);
throw new AssertionError(o.getClass());
}
@Override
public int hashCode() {
return value.hashCode();
public boolean equals(Object o) {
return o instanceof Special that && this.normalized.equals(that.normalized);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
return o instanceof Special other && this.value.equals(other.value);
public int hashCode() {
return normalized.hashCode();
}
}
@@ -621,10 +768,11 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
static {
ArrayDeque<String> defaultGameVersions = new ArrayDeque<>(64);
List<Snapshot> snapshots = new ArrayList<>(1024);
List<LegacySnapshot> snapshots = new ArrayList<>(1024);
List<Release> snapshotPrev = new ArrayList<>(1024);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream("/assets/game/versions.txt"), StandardCharsets.US_ASCII))) {
//noinspection DataFlowIssue
try (var reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream("/assets/game/versions.txt"), StandardCharsets.US_ASCII))) {
Release currentRelease = null;
GameVersionNumber prev = null;
@@ -637,13 +785,13 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
if (currentRelease == null)
currentRelease = (Release) version;
if (version instanceof Snapshot snapshot) {
if (version instanceof LegacySnapshot snapshot) {
snapshots.add(snapshot);
snapshotPrev.add(currentRelease);
} else if (version instanceof Release) {
currentRelease = (Release) version;
} else if (version instanceof Release release) {
currentRelease = release;
if (currentRelease.eaType == Release.TYPE_GA) {
if (currentRelease.eaType == Release.ReleaseType.GA) {
defaultGameVersions.addFirst(currentRelease.value);
}
} else if (version instanceof Special special) {
@@ -658,6 +806,36 @@ public abstract sealed class GameVersionNumber implements Comparable<GameVersion
throw new AssertionError(e);
}
//noinspection DataFlowIssue
try (var reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream("/assets/game/version-alias.csv"), StandardCharsets.US_ASCII))) {
for (String line; (line = reader.readLine()) != null; ) {
if (line.isEmpty())
continue;
String[] parts = line.split(",");
if (parts.length < 2) {
LOG.warning("Invalid line: " + line);
continue;
}
String normalized = parts[0];
Special normalizedVersion = SPECIALS.get(normalized);
if (normalizedVersion == null) {
LOG.warning("Unknown special version: " + normalized);
continue;
}
for (int i = 1; i < parts.length; i++) {
String version = parts[i];
Special versionNumber = new Special(version, normalized);
versionNumber.prev = normalizedVersion.prev;
SPECIALS.put(version, versionNumber);
}
}
} catch (IOException e) {
throw new AssertionError(e);
}
DEFAULT_GAME_VERSIONS = defaultGameVersions.toArray(new String[0]);
SNAPSHOT_INTS = new int[snapshots.size()];

View File

@@ -92,63 +92,63 @@
"releaseTime": "2021-07-13T12:54:19+00:00"
},
{
"id": "1_16_combat-6",
"id": "1.16_combat-6",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-6/1_16_combat-6.json",
"time": "2020-08-26T06:24:28+00:00",
"releaseTime": "2020-08-26T06:24:28+00:00"
},
{
"id": "1_16_combat-5",
"id": "1.16_combat-5",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-5/1_16_combat-5.json",
"time": "2020-08-21T09:23:13+00:00",
"releaseTime": "2020-08-21T09:23:13+00:00"
},
{
"id": "1_16_combat-4",
"id": "1.16_combat-4",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-4/1_16_combat-4.json",
"time": "2020-08-19T11:14:58+00:00",
"releaseTime": "2020-08-19T11:14:58+00:00"
},
{
"id": "1_16_combat-3",
"id": "1.16_combat-3",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-3/1_16_combat-3.json",
"time": "2020-08-14T09:02:15+00:00",
"releaseTime": "2020-08-14T09:02:15+00:00"
},
{
"id": "1_16_combat-2",
"id": "1.16_combat-2",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-2/1_16_combat-2.json",
"time": "2020-08-13T11:56:30+00:00",
"releaseTime": "2020-08-13T11:56:30+00:00"
},
{
"id": "1_16_combat-1",
"id": "1.16_combat-1",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-1/1_16_combat-1.json",
"time": "2020-08-12T14:07:25+00:00",
"releaseTime": "2020-08-12T14:07:25+00:00"
},
{
"id": "1_16_combat-0",
"id": "1.16_combat-0",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_16_combat-0/1_16_combat-0.json",
"time": "2020-08-07T10:44:47+00:00",
"releaseTime": "2020-08-07T10:44:47+00:00"
},
{
"id": "1_15_combat-6",
"id": "1.15_combat-6",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_15_combat-6/1_15_combat-6.json",
"time": "2020-01-15T09:46:35+00:00",
"releaseTime": "2020-01-15T09:46:35+00:00"
},
{
"id": "1_15_combat-1",
"id": "1.15_combat-1",
"type": "pending",
"url": "https://zkitefly.github.io/unlisted-versions-of-minecraft/files/1_15_combat-1/1_15_combat-1.json",
"time": "2019-11-29T15:41:39+00:00",

View File

@@ -0,0 +1,20 @@
1.14_combat-212796,1.14.3 - Combat Test
1.14_combat-0,Combat Test 2
1.14_combat-3,Combat Test 3
1.15_combat-1,Combat Test 4
1.15_combat-6,Combat Test 5
1.16_combat-0,Combat Test 6
1.16_combat-1,Combat Test 7
1.16_combat-2,Combat Test 7b
1.16_combat-3,Combat Test 7c
1.16_combat-4,Combat Test 8
1.16_combat-5,Combat Test 8b
1.16_combat-6,Combat Test 8c
1.18_experimental-snapshot-1,1.18 Experimental Snapshot 1
1.18_experimental-snapshot-2,1.18 Experimental Snapshot 2
1.18_experimental-snapshot-3,1.18 Experimental Snapshot 3
1.18_experimental-snapshot-4,1.18 Experimental Snapshot 4
1.18_experimental-snapshot-5,1.18 Experimental Snapshot 5
1.18_experimental-snapshot-6,1.18 Experimental Snapshot 6
1.19_deep_dark_experimental_snapshot-1,Deep Dark Experimental Snapshot 1
20w14infinite,20w14~
1 1.14_combat-212796 1.14.3 - Combat Test
2 1.14_combat-0 Combat Test 2
3 1.14_combat-3 Combat Test 3
4 1.15_combat-1 Combat Test 4
5 1.15_combat-6 Combat Test 5
6 1.16_combat-0 Combat Test 6
7 1.16_combat-1 Combat Test 7
8 1.16_combat-2 Combat Test 7b
9 1.16_combat-3 Combat Test 7c
10 1.16_combat-4 Combat Test 8
11 1.16_combat-5 Combat Test 8b
12 1.16_combat-6 Combat Test 8c
13 1.18_experimental-snapshot-1 1.18 Experimental Snapshot 1
14 1.18_experimental-snapshot-2 1.18 Experimental Snapshot 2
15 1.18_experimental-snapshot-3 1.18 Experimental Snapshot 3
16 1.18_experimental-snapshot-4 1.18 Experimental Snapshot 4
17 1.18_experimental-snapshot-5 1.18 Experimental Snapshot 5
18 1.18_experimental-snapshot-6 1.18 Experimental Snapshot 6
19 1.19_deep_dark_experimental_snapshot-1 Deep Dark Experimental Snapshot 1
20 20w14infinite 20w14~

View File

@@ -452,24 +452,25 @@
3D Shareware v1.34
19w14a
19w14b
1.14 Pre-Release 1
1.14 Pre-Release 2
1.14 Pre-Release 3
1.14 Pre-Release 4
1.14 Pre-Release 5
1.14-pre1
1.14-pre2
1.14-pre3
1.14-pre4
1.14-pre5
1.14
1.14.1 Pre-Release 1
1.14.1 Pre-Release 2
1.14.1-pre1
1.14.1-pre2
1.14.1
1.14.2 Pre-Release 1
1.14.2 Pre-Release 2
1.14.2 Pre-Release 3
1.14.2 Pre-Release 4
1.14.2-pre1
1.14.2-pre2
1.14.2-pre3
1.14.2-pre4
1.14.2
1.14.3-pre1
1.14.3-pre2
1.14.3-pre3
1.14.3-pre4
1.14_combat-212796
1.14.3
1.14.4-pre1
1.14.4-pre2
@@ -479,6 +480,8 @@
1.14.4-pre6
1.14.4-pre7
1.14.4
1.14_combat-0
1.14_combat-3
19w34a
19w35a
19w36a
@@ -497,6 +500,7 @@
1.15-pre1
1.15-pre2
1.15-pre3
1.15_combat-1
1.15-pre4
1.15-pre5
1.15-pre6
@@ -506,6 +510,7 @@
1.15.1
1.15.2-pre1
1.15.2-pre2
1.15_combat-6
1.15.2
20w06a
20w07a
@@ -545,9 +550,16 @@
1.16.2-pre1
1.16.2-pre2
1.16.2-pre3
1.16_combat-0
1.16.2-rc1
1.16.2-rc2
1.16.2
1.16_combat-1
1.16_combat-2
1.16_combat-3
1.16_combat-4
1.16_combat-5
1.16_combat-6
1.16.3-rc1
1.16.3
1.16.4-pre1
@@ -592,6 +604,13 @@
1.17.1-rc1
1.17.1-rc2
1.17.1
1.18_experimental-snapshot-1
1.18_experimental-snapshot-2
1.18_experimental-snapshot-3
1.18_experimental-snapshot-4
1.18_experimental-snapshot-5
1.18_experimental-snapshot-6
1.18_experimental-snapshot-7
21w37a
21w38a
21w39a
@@ -622,6 +641,7 @@
22w05a
22w06a
22w07a
1.19_deep_dark_experimental_snapshot-1
1.18.2-pre1
1.18.2-pre2
1.18.2-pre3

View File

@@ -25,6 +25,7 @@ import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Supplier;
import static org.jackhuang.hmcl.util.versioning.GameVersionNumber.asGameVersion;
import static org.junit.jupiter.api.Assertions.*;
@@ -34,6 +35,8 @@ import static org.junit.jupiter.api.Assertions.*;
*/
public final class GameVersionNumberTest {
//region Helpers
private static List<String> readVersions() {
List<String> versions = new ArrayList<>();
@@ -48,18 +51,8 @@ public final class GameVersionNumberTest {
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));
assertIterableEquals(versions, copied);
}
private static String errorMessage(String version1, String version2) {
return String.format("version1=%s, version2=%s", version1, version2);
private static Supplier<String> errorMessage(GameVersionNumber version1, GameVersionNumber version2) {
return () -> "version1=%s, version2=%s".formatted(version1.toDebugString(), version2.toDebugString());
}
private static void assertGameVersionEquals(String version) {
@@ -67,25 +60,42 @@ public final class GameVersionNumberTest {
}
private static void assertGameVersionEquals(String version1, String version2) {
assertEquals(0, asGameVersion(version1).compareTo(version2), errorMessage(version1, version2));
assertEquals(asGameVersion(version1), asGameVersion(version2), errorMessage(version1, version2));
}
private static String toString(GameVersionNumber gameVersionNumber) {
return gameVersionNumber.getClass().getSimpleName();
GameVersionNumber gameVersion1 = asGameVersion(version1);
GameVersionNumber gameVersion2 = asGameVersion(version2);
assertEquals(0, gameVersion1.compareTo(gameVersion2), errorMessage(gameVersion1, gameVersion2));
assertEquals(0, gameVersion2.compareTo(gameVersion1), errorMessage(gameVersion1, gameVersion2));
assertEquals(gameVersion1, gameVersion2, errorMessage(gameVersion1, gameVersion2));
assertEquals(gameVersion2, gameVersion1, errorMessage(gameVersion1, gameVersion2));
assertEquals(gameVersion1.hashCode(), gameVersion2.hashCode(), errorMessage(gameVersion1, gameVersion2));
}
private static void assertOrder(String... versions) {
var gameVersionNumbers = new GameVersionNumber[versions.length];
for (int i = 0; i < versions.length; i++) {
gameVersionNumbers[i] = asGameVersion(versions[i]);
}
for (int i = 0; i < versions.length - 1; i++) {
GameVersionNumber version1 = asGameVersion(versions[i]);
GameVersionNumber version1 = gameVersionNumbers[i];
for (int j = 0; j < i; j++) {
GameVersionNumber version2 = gameVersionNumbers[j];
assertTrue(version1.compareTo(version2) > 0, errorMessage(version1, version2));
assertTrue(version2.compareTo(version1) < 0, errorMessage(version1, version2));
assertNotEquals(version1, version2, errorMessage(version1, version2));
assertNotEquals(version2, version1, errorMessage(version1, version2));
}
assertGameVersionEquals(versions[i]);
for (int j = i + 1; j < versions.length; j++) {
GameVersionNumber version2 = asGameVersion(versions[j]);
GameVersionNumber version2 = gameVersionNumbers[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)));
assertTrue(version1.compareTo(version2) < 0, errorMessage(version1, version2));
assertTrue(version2.compareTo(version1) > 0, errorMessage(version1, version2));
assertNotEquals(version1, version2, errorMessage(version1, version2));
assertNotEquals(version2, version1, errorMessage(version1, version2));
}
}
@@ -100,6 +110,8 @@ public final class GameVersionNumberTest {
assertEquals(VersionNumber.asVersion(versionNumber), old.versionNumber);
}
//endregion Helpers
private static boolean isAprilFools(String version) {
return asGameVersion(version).isAprilFools();
}
@@ -124,6 +136,31 @@ public final class GameVersionNumberTest {
assertFalse(isAprilFools("25w45a_unobfuscated"));
}
@Test
public void testSortVersions() {
List<String> versions = readVersions();
{
List<String> copied = new ArrayList<>(versions);
copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion));
assertIterableEquals(versions, copied);
}
{
List<String> copied = new ArrayList<>(versions);
Collections.reverse(copied);
copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion));
assertIterableEquals(versions, copied);
}
for (int randomSeed = 0; randomSeed < 5; randomSeed++) {
List<String> copied = new ArrayList<>(versions);
Collections.shuffle(copied, new Random(randomSeed));
copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion));
assertIterableEquals(versions, copied);
}
}
@Test
public void testParseOld() {
assertOldVersion("rd-132211", GameVersionNumber.Type.PRE_CLASSIC, "132211");
@@ -136,36 +173,96 @@ public final class GameVersionNumberTest {
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.6-tb3", GameVersionNumber.Type.BETA, "1.6-tb3");
assertOldVersion("b1.8-pre1-2", GameVersionNumber.Type.BETA, "1.8-pre1-2");
assertOldVersion("b1.9-pre1", GameVersionNumber.Type.BETA, "1.9-pre1");
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse(""));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("1.21"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("r-132211"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("rd-"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("rd-a"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("i-20100223"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("in-"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("in-a"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("inf-"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Old.parse("inf-a"));
}
private static void testParseLegacySnapshot(int year, int week, char suffix) {
String raw = "%02dw%02d%s".formatted(year, week, suffix);
var rawVersion = (GameVersionNumber.LegacySnapshot) asGameVersion(raw);
assertInstanceOf(GameVersionNumber.LegacySnapshot.class, rawVersion);
assertEquals(raw, rawVersion.toString());
assertEquals(raw, rawVersion.toNormalizedString());
assertEquals(year, rawVersion.getYear());
assertEquals(week, rawVersion.getWeek());
assertEquals(suffix, rawVersion.getSuffix());
assertFalse(rawVersion.isUnobfuscated());
var unobfuscated = raw + "_unobfuscated";
var unobfuscatedVersion = (GameVersionNumber.LegacySnapshot) asGameVersion(unobfuscated);
assertInstanceOf(GameVersionNumber.LegacySnapshot.class, rawVersion);
assertEquals(unobfuscated, unobfuscatedVersion.toString());
assertEquals(unobfuscated, unobfuscatedVersion.toNormalizedString());
assertEquals(year, unobfuscatedVersion.getYear());
assertEquals(week, unobfuscatedVersion.getWeek());
assertEquals(suffix, unobfuscatedVersion.getSuffix());
assertTrue(unobfuscatedVersion.isUnobfuscated());
var unobfuscated2 = raw + " Unobfuscated";
var unobfuscatedVersion2 = (GameVersionNumber.LegacySnapshot) asGameVersion(unobfuscated2);
assertInstanceOf(GameVersionNumber.LegacySnapshot.class, rawVersion);
assertEquals(unobfuscated2, unobfuscatedVersion2.toString());
assertEquals(unobfuscated, unobfuscatedVersion2.toNormalizedString());
assertEquals(year, unobfuscatedVersion2.getYear());
assertEquals(week, unobfuscatedVersion2.getWeek());
assertEquals(suffix, unobfuscatedVersion2.getSuffix());
assertTrue(unobfuscatedVersion2.isUnobfuscated());
}
@Test
public void testParseNew() {
List<String> versions = readVersions();
for (String version : versions) {
assertFalse(asGameVersion(version) instanceof GameVersionNumber.Old, "version=" + version);
GameVersionNumber gameVersion = asGameVersion(version);
assertFalse(gameVersion instanceof GameVersionNumber.Old, "version=" + gameVersion.toDebugString());
}
testParseLegacySnapshot(25, 46, 'a');
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parse("2.1"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse("1.0"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse("1.100.1"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse("aawbba"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse("13w12A"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.LegacySnapshot.parse("13w12~"));
}
private static void assertSimpleReleaseVersion(String simpleReleaseVersion, int minor, int patch) {
private static void assertSimpleReleaseVersion(String simpleReleaseVersion, int major, int minor, int patch) {
GameVersionNumber.Release release = GameVersionNumber.Release.parseSimple(simpleReleaseVersion);
assertAll("Assert Simple Release Version " + simpleReleaseVersion,
() -> assertEquals(1, release.getMajor()),
() -> assertEquals(major, release.getMajor()),
() -> assertEquals(minor, release.getMinor()),
() -> assertEquals(patch, release.getPatch()),
() -> assertEquals(GameVersionNumber.Release.TYPE_UNKNOWN, release.getEaType()),
() -> assertEquals(GameVersionNumber.Release.ReleaseType.UNKNOWN, release.getEaType()),
() -> assertEquals(VersionNumber.ZERO, release.getEaVersion())
);
}
@Test
public void testParseSimpleRelease() {
assertSimpleReleaseVersion("1.0", 0, 0);
assertSimpleReleaseVersion("1.13", 13, 0);
assertSimpleReleaseVersion("1.21.8", 21, 8);
assertSimpleReleaseVersion("1.0", 1, 0, 0);
assertSimpleReleaseVersion("1.13", 1, 13, 0);
assertSimpleReleaseVersion("1.21.8", 1, 21, 8);
assertSimpleReleaseVersion("26.1", 26, 1, 0);
assertSimpleReleaseVersion("26.1.1", 26, 1, 1);
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("26"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("24.0.0"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("24.0"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("2.0"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1..0"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1.0."));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1.a"));
@@ -186,13 +283,13 @@ public final class GameVersionNumberTest {
"0.0",
"1.0",
"1.99",
"1.99.1-unknown1",
"1.99.1-pre1",
"1.99.1 Pre-Release 2",
"1.99.1-rc1",
"1.99.1",
"1.100",
"1.100.1"
"1.100.1",
"26.1"
);
}
@@ -202,7 +299,6 @@ public final class GameVersionNumberTest {
"90w01a",
"90w01b",
"90w01e",
"90w01~",
"90w02a"
);
}
@@ -257,6 +353,7 @@ public final class GameVersionNumberTest {
"1.14",
"1.15.2",
"20w06a",
"20w13b",
"20w14infinite",
"20w22a",
"1.16-pre1",
@@ -273,10 +370,21 @@ public final class GameVersionNumberTest {
"24w13a",
"24w14potato",
"24w14a",
"25w45a",
"25w45a_unobfuscated",
"Unknown",
"100.0"
"25w46a",
"25w46a_unobfuscated",
"1.21.11-pre1",
"1.21.11-pre1_unobfuscated",
"1.21.11-pre2",
"1.21.11-pre2_unobfuscated",
"99w99a",
"26.1-snapshot-1",
"26.1-snapshot-2",
"26.1",
"26.2-snapshot-1",
"26.2-snapshot-2",
"26.2",
"100.0",
"Unknown"
);
}
@@ -312,26 +420,83 @@ public final class GameVersionNumberTest {
);
}
private static void assertNormalized(String normalized, String version) {
assertGameVersionEquals(version);
assertGameVersionEquals(normalized, version);
assertEquals(normalized, asGameVersion(version).toNormalizedString());
}
@Test
public void testToNormalizedString() {
for (String version : readVersions()) {
assertNormalized(version, version);
}
assertNormalized("1.21.11-pre3", "1.21.11 Pre-Release 3");
assertNormalized("1.21.11-pre3_unobfuscated", "1.21.11 Pre-Release 3 Unobfuscated");
assertNormalized("1.21.11-pre3_unobfuscated", "1.21.11-pre3 Unobfuscated");
assertNormalized("1.21.11-rc1", "1.21.11 Release Candidate 1");
assertNormalized("1.21.11-rc1_unobfuscated", "1.21.11 Release Candidate 1 Unobfuscated");
assertNormalized("1.14_combat-212796", "1.14.3 - Combat Test");
assertNormalized("1.14_combat-0", "Combat Test 2");
assertNormalized("1.14_combat-3", "Combat Test 3");
assertNormalized("1.15_combat-1", "Combat Test 4");
assertNormalized("1.15_combat-6", "Combat Test 5");
assertNormalized("1.16_combat-0", "Combat Test 6");
assertNormalized("1.16_combat-1", "Combat Test 7");
assertNormalized("1.16_combat-2", "Combat Test 7b");
assertNormalized("1.16_combat-3", "Combat Test 7c");
assertNormalized("1.16_combat-4", "Combat Test 8");
assertNormalized("1.16_combat-5", "Combat Test 8b");
assertNormalized("1.16_combat-6", "Combat Test 8c");
assertNormalized("1.18_experimental-snapshot-1", "1.18 Experimental Snapshot 1");
assertNormalized("1.18_experimental-snapshot-2", "1.18 Experimental Snapshot 2");
assertNormalized("1.18_experimental-snapshot-3", "1.18 Experimental Snapshot 3");
assertNormalized("1.18_experimental-snapshot-4", "1.18 Experimental Snapshot 4");
assertNormalized("1.18_experimental-snapshot-5", "1.18 Experimental Snapshot 5");
assertNormalized("1.18_experimental-snapshot-6", "1.18 Experimental Snapshot 6");
assertNormalized("1.19_deep_dark_experimental_snapshot-1", "Deep Dark Experimental Snapshot 1");
assertNormalized("20w14infinite", "20w14~");
}
@Test
public void isAtLeast() {
assertTrue(asGameVersion("1.13").isAtLeast("1.13", "17w43a"));
assertTrue(asGameVersion("1.13.1").isAtLeast("1.13", "17w43a"));
assertTrue(asGameVersion("1.14").isAtLeast("1.13", "17w43a"));
assertTrue(asGameVersion("1.13-rc1").isAtLeast("1.13", "17w43a"));
assertTrue(asGameVersion("1.13-pre1").isAtLeast("1.13", "17w43a"));
assertTrue(asGameVersion("17w43a").isAtLeast("1.13", "17w43a"));
assertTrue(asGameVersion("17w43b").isAtLeast("1.13", "17w43a"));
assertTrue(asGameVersion("17w45a").isAtLeast("1.13", "17w43a"));
assertTrue(asGameVersion("1.13").isAtLeast("1.13", "17w43a", true));
assertTrue(asGameVersion("1.13").isAtLeast("1.13", "17w43a", false));
assertTrue(asGameVersion("1.13.1").isAtLeast("1.13", "17w43a", true));
assertTrue(asGameVersion("1.13.1").isAtLeast("1.13", "17w43a", false));
assertTrue(asGameVersion("1.14").isAtLeast("1.13", "17w43a", true));
assertTrue(asGameVersion("1.14").isAtLeast("1.13", "17w43a", false));
assertTrue(asGameVersion("1.13-rc1").isAtLeast("1.13", "17w43a", false));
assertTrue(asGameVersion("1.13-pre1").isAtLeast("1.13", "17w43a", false));
assertTrue(asGameVersion("17w43a").isAtLeast("1.13", "17w43a", true));
assertTrue(asGameVersion("17w43a").isAtLeast("1.13", "17w43a", false));
assertTrue(asGameVersion("17w43b").isAtLeast("1.13", "17w43a", true));
assertTrue(asGameVersion("17w43b").isAtLeast("1.13", "17w43a", false));
assertTrue(asGameVersion("17w45a").isAtLeast("1.13", "17w43a", true));
assertTrue(asGameVersion("17w45a").isAtLeast("1.13", "17w43a", false));
assertFalse(asGameVersion("17w31a").isAtLeast("1.13", "17w43a"));
assertFalse(asGameVersion("1.12").isAtLeast("1.13", "17w43a"));
assertFalse(asGameVersion("1.12.2").isAtLeast("1.13", "17w43a"));
assertFalse(asGameVersion("1.12.2-pre1").isAtLeast("1.13", "17w43a"));
assertFalse(asGameVersion("rd-132211").isAtLeast("1.13", "17w43a"));
assertFalse(asGameVersion("a1.0.6").isAtLeast("1.13", "17w43a"));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("1.13").isAtLeast("17w43a", "17w43a"));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "1.13"));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "22w13oneblockatatime"));
assertFalse(asGameVersion("1.13-rc1").isAtLeast("1.13", "17w43a", true));
assertFalse(asGameVersion("1.13-pre1").isAtLeast("1.13", "17w43a", true));
assertFalse(asGameVersion("17w31a").isAtLeast("1.13", "17w43a", true));
assertFalse(asGameVersion("17w31a").isAtLeast("1.13", "17w43a", false));
assertFalse(asGameVersion("1.12").isAtLeast("1.13", "17w43a", true));
assertFalse(asGameVersion("1.12").isAtLeast("1.13", "17w43a", false));
assertFalse(asGameVersion("1.12.2").isAtLeast("1.13", "17w43a", true));
assertFalse(asGameVersion("1.12.2").isAtLeast("1.13", "17w43a", false));
assertFalse(asGameVersion("1.12.2-pre1").isAtLeast("1.13", "17w43a", true));
assertFalse(asGameVersion("1.12.2-pre1").isAtLeast("1.13", "17w43a", false));
assertFalse(asGameVersion("rd-132211").isAtLeast("1.13", "17w43a", true));
assertFalse(asGameVersion("rd-132211").isAtLeast("1.13", "17w43a", false));
assertFalse(asGameVersion("a1.0.6").isAtLeast("1.13", "17w43a", true));
assertFalse(asGameVersion("a1.0.6").isAtLeast("1.13", "17w43a", false));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("1.13").isAtLeast("17w43a", "17w43a", true));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("1.13").isAtLeast("17w43a", "17w43a", false));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "1.13", true));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "1.13", false));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "22w13oneblockatatime", true));
assertThrows(IllegalArgumentException.class, () -> asGameVersion("17w43a").isAtLeast("1.13", "22w13oneblockatatime", false));
}
}