新增辅助方法 GameVersionNumber#isAtLeast(String, String) (#4458)

This commit is contained in:
Glavo 2025-09-12 22:12:39 +08:00 committed by GitHub
parent 8708e7f8b1
commit 356960cc82
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 153 additions and 34 deletions

View File

@ -31,7 +31,7 @@ import java.util.regex.Pattern;
/**
* @author Glavo
*/
public abstract class GameVersionNumber implements Comparable<GameVersionNumber> {
public abstract sealed class GameVersionNumber implements Comparable<GameVersionNumber> {
public static String[] getDefaultGameVersions() {
return Versions.DEFAULT_GAME_VERSIONS;
@ -103,8 +103,7 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
if (this instanceof Special)
return true;
if (this instanceof Snapshot) {
Snapshot snapshot = (Snapshot) this;
if (this instanceof Snapshot snapshot) {
return snapshot.intValue == Snapshot.toInt(15, 14, 'a');
}
@ -131,6 +130,39 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
return compareToImpl(other);
}
/// @see #isAtLeast(String, String, boolean)
public boolean isAtLeast(@NotNull String releaseVersion, @NotNull String snapshotVersion) {
return isAtLeast(releaseVersion, snapshotVersion, false);
}
/// When comparing between Release Version and Snapshot Version, it is necessary to load `/assets/game/versions.txt` and perform a lookup, which is less efficient.
/// Therefore, when checking whether a version contains a certain feature, you should use this method and provide both the first release version and the exact snapshot version that introduced the feature,
/// so that the comparison can be performed quickly without a lookup.
///
/// For example, the datapack feature was introduced in Minecraft 1.13, and more specifically in snapshot `17w43a`.
/// So you can test whether a game version supports datapacks like this:
///
/// ```java
/// GameVersionNumber.asVersion("...").isAtLeast("1.13", "17w43a");
/// ```
///
/// @param strictReleaseVersion When `strictReleaseVersion` is `false`, `releaseVersion` is considered less than
/// its corresponding pre/rc versions.
public boolean isAtLeast(@NotNull String releaseVersion, @NotNull String snapshotVersion, boolean strictReleaseVersion) {
if (this instanceof Release self) {
Release other;
if (strictReleaseVersion) {
other = Release.parse(releaseVersion);
} else {
other = Release.parseSimple(releaseVersion);
}
return self.compareToRelease(other) >= 0;
} else {
return this.compareTo(Snapshot.parse(snapshotVersion)) >= 0;
}
}
@Override
public String toString() {
return value;
@ -201,9 +233,7 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Old)) return false;
Old other = (Old) o;
return type == other.type && this.versionNumber.compareTo(other.versionNumber) == 0;
return o instanceof Old other && type == other.type && this.versionNumber.compareTo(other.versionNumber) == 0;
}
@Override
@ -256,6 +286,52 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
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++;
}
return current - offset;
}
/// 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.
///
/// @see GameVersionNumber#isAtLeast(String, String)
static Release parseSimple(String value) {
if (!value.startsWith("1."))
throw new IllegalArgumentException(value);
final int minorOffset = 2;
int minorLength = getNumberLength(value, minorOffset);
if (minorLength == 0)
throw new IllegalArgumentException(value);
try {
int minor = Integer.parseInt(value.substring(minorOffset, minorOffset + minorLength));
int patch = 0;
if (minorOffset + minorLength < value.length()) {
int patchOffset = minorOffset + minorLength + 1;
if (patchOffset >= value.length() || value.charAt(patchOffset - 1) != '.')
throw new IllegalArgumentException(value);
patch = Integer.parseInt(value.substring(patchOffset));
}
return new Release(value, 1, minor, patch, TYPE_UNKNOWN, VersionNumber.ZERO);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(value);
}
}
private final int major;
private final int minor;
private final int patch;
@ -280,24 +356,20 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
int compareToRelease(Release other) {
int c = Integer.compare(this.major, other.major);
if (c != 0) {
if (c != 0)
return c;
}
c = Integer.compare(this.minor, other.minor);
if (c != 0) {
if (c != 0)
return c;
}
c = Integer.compare(this.patch, other.patch);
if (c != 0) {
if (c != 0)
return c;
}
c = Integer.compare(this.eaType, other.eaType);
if (c != 0) {
if (c != 0)
return c;
}
return this.eaVersion.compareTo(other.eaVersion);
}
@ -356,9 +428,12 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
@Override
public boolean equals(Object o) {
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.equals(other.eaVersion);
return o instanceof Release other
&& major == other.major
&& minor == other.minor
&& patch == other.patch
&& eaType == other.eaType
&& eaVersion.equals(other.eaVersion);
}
}
@ -428,9 +503,7 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Snapshot other = (Snapshot) o;
return this.intValue == other.intValue;
return o instanceof Snapshot other && this.intValue == other.intValue;
}
@Override
@ -534,9 +607,7 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Special other = (Special) o;
return Objects.equals(this.value, other.value);
return o instanceof Special other && this.value.equals(other.value);
}
}
@ -566,8 +637,7 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
if (currentRelease == null)
currentRelease = (Release) version;
if (version instanceof Snapshot) {
Snapshot snapshot = (Snapshot) version;
if (version instanceof Snapshot snapshot) {
snapshots.add(snapshot);
snapshotPrev.add(currentRelease);
} else if (version instanceof Release) {
@ -576,8 +646,7 @@ public abstract class GameVersionNumber implements Comparable<GameVersionNumber>
if (currentRelease.eaType == Release.TYPE_GA) {
defaultGameVersions.addFirst(currentRelease.value);
}
} else if (version instanceof Special) {
Special special = (Special) version;
} else if (version instanceof Special special) {
special.prev = prev;
SPECIALS.put(special.value, special);
} else

View File

@ -26,6 +26,7 @@ import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static org.jackhuang.hmcl.util.versioning.GameVersionNumber.asGameVersion;
import static org.junit.jupiter.api.Assertions.*;
/**
@ -66,8 +67,8 @@ public final 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));
assertEquals(0, asGameVersion(version1).compareTo(version2), errorMessage(version1, version2));
assertEquals(asGameVersion(version1), asGameVersion(version2), errorMessage(version1, version2));
}
private static String toString(GameVersionNumber gameVersionNumber) {
@ -76,12 +77,12 @@ public final class GameVersionNumberTest {
private static void assertOrder(String... versions) {
for (int i = 0; i < versions.length - 1; i++) {
GameVersionNumber version1 = GameVersionNumber.asGameVersion(versions[i]);
GameVersionNumber version1 = asGameVersion(versions[i]);
assertGameVersionEquals(versions[i]);
for (int j = i + 1; j < versions.length; j++) {
GameVersionNumber version2 = GameVersionNumber.asGameVersion(versions[j]);
GameVersionNumber version2 = asGameVersion(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)));
@ -92,7 +93,7 @@ public final class GameVersionNumberTest {
}
private void assertOldVersion(String oldVersion, GameVersionNumber.Type type, String versionNumber) {
GameVersionNumber version = GameVersionNumber.asGameVersion(oldVersion);
GameVersionNumber version = asGameVersion(oldVersion);
assertInstanceOf(GameVersionNumber.Old.class, version);
GameVersionNumber.Old old = (GameVersionNumber.Old) version;
assertSame(type, old.type);
@ -100,7 +101,7 @@ public final class GameVersionNumberTest {
}
private static boolean isAprilFools(String version) {
return GameVersionNumber.asGameVersion(version).isAprilFools();
return asGameVersion(version).isAprilFools();
}
@Test
@ -142,10 +143,36 @@ public final class GameVersionNumberTest {
public void testParseNew() {
List<String> versions = readVersions();
for (String version : versions) {
assertFalse(GameVersionNumber.asGameVersion(version) instanceof GameVersionNumber.Old, "version=" + version);
assertFalse(asGameVersion(version) instanceof GameVersionNumber.Old, "version=" + version);
}
}
private static void assertSimpleReleaseVersion(String simpleReleaseVersion, int minor, int patch) {
GameVersionNumber.Release release = GameVersionNumber.Release.parseSimple(simpleReleaseVersion);
assertAll("Assert Simple Release Version " + simpleReleaseVersion,
() -> assertEquals(1, release.getMajor()),
() -> assertEquals(minor, release.getMinor()),
() -> assertEquals(patch, release.getPatch()),
() -> assertEquals(GameVersionNumber.Release.TYPE_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);
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("2.0"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1..0"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1.0."));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1.a"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1.1a"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1.0a"));
assertThrows(IllegalArgumentException.class, () -> GameVersionNumber.Release.parseSimple("1.0.0.0"));
}
@Test
public void testCompareRelease() {
assertGameVersionEquals("0.0");
@ -281,4 +308,27 @@ public final class GameVersionNumberTest {
"1.1"
);
}
@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"));
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"));
}
}