更新游戏版本比较规则 (#2700)

* Create GameVersionNumber

* Update GameVersionNumber

* Rename DefaultVersionNumber

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update versions.txt

* update

* update

* update
This commit is contained in:
Glavo 2024-02-13 13:46:01 +08:00 committed by GitHub
parent fe608e32f1
commit 95afaa5112
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 1802 additions and 263 deletions

View File

@ -25,7 +25,7 @@ import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.ManagedProcess;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.File;
import java.io.IOException;
@ -84,7 +84,7 @@ public final class HMCLGameLauncher extends DefaultLauncher {
1.11 ~ 12zh_cn 时正常zh_CN 时虽然显示了中文但语言设置会错误地显示选择英文
1.13+ zh_cn 时正常zh_CN 时自动切换为英文
*/
VersionNumber gameVersion = VersionNumber.asVersion(repository.getGameVersion(version).orElse("0.0"));
GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(repository.getGameVersion(version));
if (gameVersion.compareTo("1.1") < 0) {
lang = null;
} else if (gameVersion.compareTo("1.11") < 0) {

View File

@ -45,6 +45,7 @@ import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.io.ResponseCodeException;
import org.jackhuang.hmcl.util.platform.*;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.io.File;
@ -332,7 +333,7 @@ public final class LauncherHelper {
}
private static Task<JavaVersion> checkGameState(Profile profile, VersionSetting setting, Version version) {
VersionNumber gameVersion = VersionNumber.asVersion(profile.getRepository().getGameVersion(version).orElse("Unknown"));
GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(profile.getRepository().getGameVersion(version));
if (setting.isNotCheckJVM()) {
return Task.composeAsync(() -> setting.getJavaVersion(gameVersion, version))
@ -549,14 +550,14 @@ public final class LauncherHelper {
// Forge 2760~2773 will crash game with LiteLoader.
boolean hasForge2760 = forgeVersion != null && (forgeVersion.compareTo("1.12.2-14.23.5.2760") >= 0) && (forgeVersion.compareTo("1.12.2-14.23.5.2773") < 0);
boolean hasLiteLoader = version.getLibraries().stream().anyMatch(it -> it.is("com.mumfrey", "liteloader"));
if (hasForge2760 && hasLiteLoader && gameVersion.compareTo(VersionNumber.asVersion("1.12.2")) == 0) {
if (hasForge2760 && hasLiteLoader && gameVersion.compareTo("1.12.2") == 0) {
suggestions.add(i18n("launch.advice.forge2760_liteloader"));
}
// OptiFine 1.14.4 is not compatible with Forge 28.2.2 and later versions.
boolean hasForge28_2_2 = forgeVersion != null && (forgeVersion.compareTo("1.14.4-28.2.2") >= 0);
boolean hasOptiFine = version.getLibraries().stream().anyMatch(it -> it.is("optifine", "OptiFine"));
if (hasForge28_2_2 && hasOptiFine && gameVersion.compareTo(VersionNumber.asVersion("1.14.4")) == 0) {
if (hasForge28_2_2 && hasOptiFine && gameVersion.compareTo("1.14.4") == 0) {
suggestions.add(i18n("launch.advice.forge28_2_2_optifine"));
}

View File

@ -31,7 +31,7 @@ import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.Platform;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.IOException;
import java.lang.reflect.Type;
@ -644,11 +644,11 @@ public final class VersionSetting implements Cloneable {
launcherVisibilityProperty.set(launcherVisibility);
}
public Task<JavaVersion> getJavaVersion(VersionNumber gameVersion, Version version) {
public Task<JavaVersion> getJavaVersion(GameVersionNumber gameVersion, Version version) {
return getJavaVersion(gameVersion, version, true);
}
public Task<JavaVersion> getJavaVersion(VersionNumber gameVersion, Version version, boolean checkJava) {
public Task<JavaVersion> getJavaVersion(GameVersionNumber gameVersion, Version version, boolean checkJava) {
return Task.runAsync(Schedulers.javafx(), () -> {
if (StringUtils.isBlank(getJava())) {
setJava(StringUtils.isBlank(getJavaDir()) ? "Auto" : "Custom");

View File

@ -39,7 +39,7 @@ import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.setting.VersionIconType;
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.util.HashMap;
import java.util.HashSet;
@ -204,7 +204,7 @@ public class InstallerItem extends Control {
if (gameVersion == null) {
this.libraries = new InstallerItem[]{game, forge, neoForge, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi};
} else if (VersionNumber.compare(gameVersion, "1.13") < 0) {
} else if (GameVersionNumber.compare(gameVersion, "1.13") < 0) {
this.libraries = new InstallerItem[]{game, forge, liteLoader, optiFine};
} else {
this.libraries = new InstallerItem[]{game, forge, neoForge, optiFine, fabric, fabricApi, quilt, quiltApi};

View File

@ -120,7 +120,7 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
.sorted(Comparator
.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L)
: version.getReleaseTime())
.thenComparing(a -> VersionNumber.asVersion(a.getId())))
.thenComparing(version -> VersionNumber.asVersion(repository.getGameVersion(version).orElse(version.getId()))))
.collect(Collectors.toList());
runInFX(() -> {
if (profile == Profiles.getSelectedProfile())

View File

@ -37,7 +37,6 @@ import javafx.scene.control.Skin;
import javafx.scene.control.SkinBase;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.mod.RemoteMod;
import org.jackhuang.hmcl.mod.RemoteModRepository;
@ -57,8 +56,8 @@ import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@ -163,24 +162,23 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
retrySearch = null;
setLoading(true);
setFailed(false);
File versionJar = StringUtils.isNotBlank(version.get().getVersion())
? version.get().getProfile().getRepository().getVersionJar(version.get().getVersion())
: null;
if (executor != null && !executor.isCancelled()) {
executor.cancel();
}
executor = Task.supplyAsync(() -> {
String gameVersion;
if (StringUtils.isBlank(version.get().getVersion())) {
gameVersion = userGameVersion;
Profile.ProfileVersion version = this.version.get();
if (StringUtils.isBlank(version.getVersion())) {
return userGameVersion;
} else {
gameVersion = GameVersion.minecraftVersion(versionJar).orElse("");
return StringUtils.isNotBlank(version.getVersion())
? version.getProfile().getRepository().getGameVersion(version.getVersion()).orElse("")
: "";
}
return gameVersion;
}).thenApplyAsync(gameVersion -> {
return repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC);
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
}).thenApplyAsync(gameVersion ->
repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC)
).whenComplete(Schedulers.javafx(), (result, exception) -> {
setLoading(false);
if (exception == null) {
items.setAll(result.getResults().collect(Collectors.toList()));
@ -295,7 +293,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
JFXComboBox<String> gameVersionField = new JFXComboBox<>();
gameVersionField.setMaxWidth(Double.MAX_VALUE);
gameVersionField.setEditable(true);
gameVersionField.getItems().setAll(RemoteModRepository.DEFAULT_GAME_VERSIONS);
gameVersionField.getItems().setAll(GameVersionNumber.getDefaultGameVersions());
Label lblGameVersion = new Label(i18n("world.game_version"));
searchPane.addRow(rowIndex++, new Label(i18n("mods.name")), nameField, lblGameVersion, gameVersionField);

View File

@ -51,7 +51,7 @@ import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import org.jetbrains.annotations.Nullable;
import java.io.File;
@ -309,7 +309,7 @@ public class DownloadPage extends Control implements DecoratorPage {
}
for (String gameVersion : control.versions.keys().stream()
.sorted(Collections.reverseOrder(VersionNumber::compare))
.sorted(Collections.reverseOrder(GameVersionNumber::compare))
.collect(Collectors.toList())) {
ComponentList sublist = new ComponentList(() ->
control.versions.get(gameVersion).stream()

View File

@ -46,7 +46,7 @@ import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.File;
import java.nio.file.Path;
@ -611,10 +611,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
Task.composeAsync(Schedulers.javafx(), () -> {
if (versionId == null) {
return versionSetting.getJavaVersion(VersionNumber.asVersion("Unknown"), null);
return versionSetting.getJavaVersion(GameVersionNumber.unknown(), null);
} else {
return versionSetting.getJavaVersion(
VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(versionId)).orElse("Unknown")),
GameVersionNumber.asGameVersion(profile.getRepository().getGameVersion(versionId)),
profile.getRepository().getVersion(versionId));
}
}).thenAcceptAsync(Schedulers.javafx(), javaVersion -> javaSublist.setSubtitle(Optional.ofNullable(javaVersion)

View File

@ -29,7 +29,7 @@ import org.jackhuang.hmcl.game.World;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.File;
import java.time.Instant;
@ -94,8 +94,7 @@ public class WorldListItem extends Control {
public void manageDatapacks() {
if (world.getGameVersion() == null || // old game will not write game version to level.dat
(VersionNumber.isIntVersionNumber(world.getGameVersion()) // we don't parse snapshot version
&& VersionNumber.asVersion(world.getGameVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0)) {
GameVersionNumber.compare(world.getGameVersion(), "1.13") < 0) {
Controllers.dialog(i18n("world.datapack.1_13"));
return;
}

View File

@ -32,13 +32,12 @@ import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.versioning.VersionNumber.asVersion;
public final class UpdateChecker {
private UpdateChecker() {}
private static ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>();
private static BooleanBinding outdated = Bindings.createBooleanBinding(
private static final ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>();
private static final BooleanBinding outdated = Bindings.createBooleanBinding(
() -> {
RemoteVersion latest = latestVersion.get();
if (latest == null || isDevelopmentVersion(Metadata.VERSION)) {
@ -46,11 +45,11 @@ public final class UpdateChecker {
} else {
// We can update from development version to stable version,
// which can be downgrading.
return asVersion(latest.getVersion()).compareTo(asVersion(Metadata.VERSION)) != 0;
return !latest.getVersion().equals(Metadata.VERSION);
}
},
latestVersion);
private static ReadOnlyBooleanWrapper checkingUpdate = new ReadOnlyBooleanWrapper(false);
private static final ReadOnlyBooleanWrapper checkingUpdate = new ReadOnlyBooleanWrapper(false);
public static void init() {
requestCheckUpdate(UpdateChannel.getChannel());

View File

@ -8,7 +8,7 @@ import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.platform.Platform;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.IOException;
import java.io.InputStreamReader;
@ -45,7 +45,7 @@ public final class NativePatcher {
public static Version patchNative(Version version, String gameVersion, JavaVersion javaVersion, VersionSetting settings) {
if (settings.getNativesDirType() == NativesDirectoryType.CUSTOM) {
if (gameVersion != null && VersionNumber.compare(gameVersion, "1.19") < 0)
if (gameVersion != null && GameVersionNumber.compare(gameVersion, "1.19") < 0)
return version;
ArrayList<Library> newLibraries = new ArrayList<>();
@ -66,7 +66,7 @@ public final class NativePatcher {
final boolean useNativeOpenAL = settings.isUseNativeOpenAL();
if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && (useNativeGLFW || useNativeOpenAL)
&& VersionNumber.compare(gameVersion, "1.19") >= 0) {
&& GameVersionNumber.compare(gameVersion, "1.19") >= 0) {
version = version.setLibraries(version.getLibraries().stream()
.filter(library -> {
@ -88,7 +88,7 @@ public final class NativePatcher {
OperatingSystem os = javaVersion.getPlatform().getOperatingSystem();
Architecture arch = javaVersion.getArchitecture();
VersionNumber gameVersionNumber = gameVersion != null ? VersionNumber.asVersion(gameVersion) : null;
GameVersionNumber gameVersionNumber = gameVersion != null ? GameVersionNumber.asGameVersion(gameVersion) : null;
if (settings.isNotPatchNatives())
return version;

View File

@ -24,7 +24,7 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.IOException;
import java.nio.file.FileSystem;
@ -100,7 +100,7 @@ public final class ForgeInstallTask extends Task<Version> {
@Override
public void execute() throws IOException, VersionMismatchException, UnsupportedInstallationException {
String originalMainClass = version.resolve(dependencyManager.getGameRepository()).getMainClass();
if (VersionNumber.compare("1.13", remote.getGameVersion()) <= 0) {
if (GameVersionNumber.compare("1.13", remote.getGameVersion()) <= 0) {
// Forge 1.13 is not compatible with fabric.
if (!LibraryAnalyzer.VANILLA_MAIN.equals(originalMainClass)
&& !LibraryAnalyzer.MOD_LAUNCHER_MAIN.equals(originalMainClass)

View File

@ -22,7 +22,7 @@ import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.File;
import java.io.IOException;
@ -65,7 +65,7 @@ public final class GameVerificationFixTask extends Task<Void> {
File jar = dependencyManager.getGameRepository().getVersionJar(version);
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
if (jar.exists() && VersionNumber.compare(gameVersion, "1.6") < 0 && analyzer.has(LibraryAnalyzer.LibraryType.FORGE)) {
if (jar.exists() && GameVersionNumber.compare(gameVersion, "1.6") < 0 && analyzer.has(LibraryAnalyzer.LibraryType.FORGE)) {
try (FileSystem fs = CompressingUtils.createWritableZipFileSystem(jar.toPath(), StandardCharsets.UTF_8)) {
Files.deleteIfExists(fs.getPath("META-INF/MOJANG_C.DSA"));
Files.deleteIfExists(fs.getPath("META-INF/MOJANG_C.SF"));

View File

@ -190,7 +190,7 @@ public final class OptiFineInstallTask extends Task<Version> {
if (LibraryAnalyzer.BOOTSTRAP_LAUNCHER_MAIN.equals(originalMainClass)) {
// OptiFine H1 Pre2+ is compatible with Forge 1.17
if (buildofVer.compareTo(VersionNumber.asVersion("20210924-190833")) < 0) {
if (buildofVer.compareTo("20210924-190833") < 0) {
throw new UnsupportedInstallationException(UnsupportedInstallationException.FORGE_1_17_OPTIFINE_H1_PRE2);
}
}

View File

@ -42,7 +42,7 @@ import static org.jackhuang.hmcl.util.Logging.LOG;
/**
* @author huangyuhui
*/
public final class GameVersion {
final class GameVersion {
private GameVersion() {
}

View File

@ -22,6 +22,7 @@ import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.platform.Architecture;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.jackhuang.hmcl.util.versioning.VersionRange;
import org.jetbrains.annotations.Nullable;
@ -30,48 +31,47 @@ import java.util.List;
import java.util.Objects;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LAUNCH_WRAPPER_MAIN;
import static org.jackhuang.hmcl.util.versioning.VersionRange.*;
public enum JavaVersionConstraint {
// Minecraft>=1.13 requires Java 8
VANILLA_JAVA_8(JavaVersionConstraint.RULE_MANDATORY, atLeast("1.13"), atLeast("1.8")),
VANILLA_JAVA_8(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atLeast("1.13"), VersionNumber.atLeast("1.8")),
// Minecraft 1.17 requires Java 16
VANILLA_JAVA_16(JavaVersionConstraint.RULE_MANDATORY, atLeast("1.17"), atLeast("16")),
VANILLA_JAVA_16(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atLeast("1.17"), VersionNumber.atLeast("16")),
// Minecraft>=1.18 requires Java 17
VANILLA_JAVA_17(JavaVersionConstraint.RULE_MANDATORY, atLeast("1.18"), atLeast("17")),
VANILLA_JAVA_17(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atLeast("1.18"), VersionNumber.atLeast("17")),
// Minecraft<=1.7.2+Forge requires Java<=7, But LegacyModFixer may fix that problem. So only suggest user using Java 7.
MODDED_JAVA_7(JavaVersionConstraint.RULE_SUGGESTED, atMost("1.7.2"), atMost("1.7.999")) {
MODDED_JAVA_7(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.atMost("1.7.2"), VersionNumber.atMost("1.7.999")) {
@Override
protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version,
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
return version != null && analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE);
}
},
MODDED_JAVA_8(JavaVersionConstraint.RULE_SUGGESTED, between("1.7.10", "1.16.999"), between("1.8", "1.8.999")) {
MODDED_JAVA_8(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.between("1.7.10", "1.16.999"), VersionNumber.between("1.8", "1.8.999")) {
@Override
protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version,
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE);
}
},
MODDED_JAVA_16(JavaVersionConstraint.RULE_SUGGESTED, between("1.17", "1.17.999"), between("16", "16.999")) {
MODDED_JAVA_16(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.between("1.17", "1.17.999"), VersionNumber.between("16", "16.999")) {
@Override
protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version,
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE);
}
},
MODDED_JAVA_17(JavaVersionConstraint.RULE_SUGGESTED, atLeast("1.18"), between("17", "17.999")) {
MODDED_JAVA_17(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.atLeast("1.18"), VersionNumber.between("17", "17.999")) {
@Override
protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version,
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE);
}
},
// LaunchWrapper<=1.12 will crash because LaunchWrapper assumes the system class loader is an instance of URLClassLoader (Java 8)
LAUNCH_WRAPPER(JavaVersionConstraint.RULE_MANDATORY, atMost("1.12.999"), atMost("1.8.999")) {
LAUNCH_WRAPPER(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atMost("1.12.999"), VersionNumber.atMost("1.8.999")) {
@Override
protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version,
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
if (version == null) return false;
return LAUNCH_WRAPPER_MAIN.equals(version.getMainClass()) &&
@ -81,15 +81,15 @@ public enum JavaVersionConstraint {
}
},
// Minecraft>=1.13 may crash when generating world on Java [1.8,1.8.0_51)
VANILLA_JAVA_8_51(JavaVersionConstraint.RULE_SUGGESTED, atLeast("1.13"), atLeast("1.8.0_51")),
VANILLA_JAVA_8_51(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.atLeast("1.13"), VersionNumber.atLeast("1.8.0_51")),
// Minecraft with suggested java version recorded in game json is restrictedly constrained.
GAME_JSON(JavaVersionConstraint.RULE_MANDATORY, VersionRange.all(), VersionRange.all()) {
@Override
protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version,
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
if (version == null) return false;
// We only checks for 1.7.10 and above, since 1.7.2 with Forge can only run on Java 7, but it is recorded Java 8 in game json, which is not correct.
return gameVersionNumber.compareTo(VersionNumber.asVersion("1.7.10")) >= 0 && version.getJavaVersion() != null;
return gameVersionNumber.compareTo("1.7.10") >= 0 && version.getJavaVersion() != null;
}
@Override
@ -100,14 +100,14 @@ public enum JavaVersionConstraint {
} else {
javaVersion = "1." + version.getJavaVersion().getMajorVersion();
}
return atLeast(javaVersion);
return VersionNumber.atLeast(javaVersion);
}
},
// On Linux, JDK 9+ cannot launch Minecraft<=1.12.2, since JDK 9+ does not accept loading native library built in different arch.
// For example, JDK 9+ 64-bit cannot load 32-bit lwjgl native library.
VANILLA_LINUX_JAVA_8(JavaVersionConstraint.RULE_MANDATORY, atMost("1.12.999"), atMost("1.8.999")) {
VANILLA_LINUX_JAVA_8(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atMost("1.12.999"), VersionNumber.atMost("1.8.999")) {
@Override
protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version,
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
return OperatingSystem.CURRENT_OS == OperatingSystem.LINUX
&& Architecture.SYSTEM_ARCH == Architecture.X86_64
@ -115,33 +115,33 @@ public enum JavaVersionConstraint {
}
@Override
public boolean checkJava(VersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) {
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) {
return javaVersion.getArchitecture() != Architecture.X86_64 || super.checkJava(gameVersionNumber, version, javaVersion);
}
},
// Minecraft currently does not provide official support for architectures other than x86 and x86-64.
VANILLA_X86(JavaVersionConstraint.RULE_SUGGESTED, VersionRange.all(), VersionRange.all()) {
@Override
protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version,
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
if (javaVersion == null || javaVersion.getArchitecture() != Architecture.ARM64)
return false;
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS || OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
return gameVersionNumber.compareTo(VersionNumber.asVersion("1.6")) < 0;
return gameVersionNumber.compareTo("1.6") < 0;
return false;
}
@Override
public boolean checkJava(VersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) {
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) {
return javaVersion.getArchitecture().isX86();
}
},
// Minecraft 1.16+Forge with crash because JDK-8273826
MODLAUNCHER_8(JavaVersionConstraint.RULE_SUGGESTED, between("1.16.3", "1.17.1"), VersionRange.all()) {
MODLAUNCHER_8(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.between("1.16.3", "1.17.1"), VersionRange.all()) {
@Override
protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version,
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
if (version == null || javaVersion == null || analyzer == null) return false;
VersionNumber forgePatchVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE)
@ -158,14 +158,14 @@ public enum JavaVersionConstraint {
case "1.16.5":
return forgePatchVersion.compareTo(VersionNumber.asVersion("36.2.23")) <= 0;
case "1.17.1":
return between("37.0.60", "37.0.75").contains(forgePatchVersion);
return VersionNumber.between("37.0.60", "37.0.75").contains(forgePatchVersion);
default:
return false;
}
}
@Override
public boolean checkJava(VersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) {
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) {
int parsedJavaVersion = javaVersion.getParsedVersion();
if (parsedJavaVersion > 17) {
return false;
@ -181,13 +181,13 @@ public enum JavaVersionConstraint {
return true;
}
}
};;
};
private final int type;
private final VersionRange gameVersionRange;
private final VersionRange javaVersionRange;
private final VersionRange<GameVersionNumber> gameVersionRange;
private final VersionRange<VersionNumber> javaVersionRange;
JavaVersionConstraint(int type, VersionRange gameVersionRange, VersionRange javaVersionRange) {
JavaVersionConstraint(int type, VersionRange<GameVersionNumber> gameVersionRange, VersionRange<VersionNumber> javaVersionRange) {
this.type = type;
this.gameVersionRange = gameVersionRange;
this.javaVersionRange = javaVersionRange;
@ -197,39 +197,39 @@ public enum JavaVersionConstraint {
return type;
}
public VersionRange getGameVersionRange() {
public VersionRange<GameVersionNumber> getGameVersionRange() {
return gameVersionRange;
}
public VersionRange getJavaVersionRange(Version version) {
public VersionRange<VersionNumber> getJavaVersionRange(Version version) {
return javaVersionRange;
}
public final boolean appliesToVersion(@Nullable VersionNumber gameVersionNumber, @Nullable Version version,
public final boolean appliesToVersion(@Nullable GameVersionNumber gameVersionNumber, @Nullable Version version,
@Nullable JavaVersion javaVersion, LibraryAnalyzer analyzer) {
return gameVersionRange.contains(gameVersionNumber)
&& appliesToVersionImpl(gameVersionNumber, version, javaVersion, analyzer);
}
protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version,
protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version,
@Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) {
return true;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean checkJava(VersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) {
public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) {
return getJavaVersionRange(version).contains(javaVersion.getVersionNumber());
}
public static final List<JavaVersionConstraint> ALL = Lang.immutableListOf(values());
public static VersionRanges findSuitableJavaVersionRange(VersionNumber gameVersion, Version version) {
VersionRange mandatoryJavaRange = VersionRange.all();
VersionRange suggestedJavaRange = VersionRange.all();
public static VersionRanges findSuitableJavaVersionRange(GameVersionNumber gameVersion, Version version) {
VersionRange<VersionNumber> mandatoryJavaRange = VersionRange.all();
VersionRange<VersionNumber> suggestedJavaRange = VersionRange.all();
LibraryAnalyzer analyzer = version != null ? LibraryAnalyzer.analyze(version) : null;
for (JavaVersionConstraint java : ALL) {
if (java.appliesToVersion(gameVersion, version, null, analyzer)) {
VersionRange javaVersionRange = java.getJavaVersionRange(version);
VersionRange<VersionNumber> javaVersionRange = java.getJavaVersionRange(version);
if (java.type == RULE_MANDATORY) {
mandatoryJavaRange = mandatoryJavaRange.intersectionWith(javaVersionRange);
suggestedJavaRange = suggestedJavaRange.intersectionWith(javaVersionRange);
@ -242,12 +242,12 @@ public enum JavaVersionConstraint {
}
@Nullable
public static JavaVersion findSuitableJavaVersion(VersionNumber gameVersion, Version version) throws InterruptedException {
public static JavaVersion findSuitableJavaVersion(GameVersionNumber gameVersion, Version version) throws InterruptedException {
VersionRanges range = findSuitableJavaVersionRange(gameVersion, version);
boolean forceX86 = Architecture.SYSTEM_ARCH == Architecture.ARM64
&& (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS || OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
&& gameVersion.compareTo(VersionNumber.asVersion("1.6")) < 0;
&& gameVersion.compareTo("1.6") < 0;
JavaVersion mandatory = null;
JavaVersion suggested = null;
@ -298,19 +298,19 @@ public enum JavaVersionConstraint {
public static final int RULE_SUGGESTED = 2;
public static final class VersionRanges {
private final VersionRange mandatory;
private final VersionRange suggested;
private final VersionRange<VersionNumber> mandatory;
private final VersionRange<VersionNumber> suggested;
public VersionRanges(VersionRange mandatory, VersionRange suggested) {
public VersionRanges(VersionRange<VersionNumber> mandatory, VersionRange<VersionNumber> suggested) {
this.mandatory = mandatory;
this.suggested = suggested;
}
public VersionRange getMandatory() {
public VersionRange<VersionNumber> getMandatory() {
return mandatory;
}
public VersionRange getSuggested() {
public VersionRange<VersionNumber> getSuggested() {
return suggested;
}
}

View File

@ -28,7 +28,7 @@ import org.jackhuang.hmcl.util.io.IOUtils;
import org.jackhuang.hmcl.util.io.Unzipper;
import org.jackhuang.hmcl.util.platform.Bits;
import org.jackhuang.hmcl.util.platform.*;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import java.io.*;
import java.net.InetSocketAddress;
@ -230,7 +230,7 @@ public class DefaultLauncher extends Launcher {
Path tempNativeFolder = null;
if ((OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
&& !StringUtils.isASCII(nativeFolderPath)
&& gameVersion.isPresent() && VersionNumber.compare(gameVersion.get(), "1.19") < 0) {
&& gameVersion.isPresent() && GameVersionNumber.compare(gameVersion.get(), "1.19") < 0) {
tempNativeFolder = Paths.get("/", "tmp", "hmcl-natives-" + UUID.randomUUID());
nativeFolderPath = tempNativeFolder + File.pathSeparator + nativeFolderPath;
}
@ -259,7 +259,7 @@ public class DefaultLauncher extends Launcher {
if (StringUtils.isNotBlank(options.getServerIp())) {
String[] args = options.getServerIp().split(":");
if (VersionNumber.compare(gameVersion.orElse("0.0"), "1.20") < 0) {
if (GameVersionNumber.asGameVersion(gameVersion).compareTo("1.20") < 0) {
res.add("--server");
res.add(args[0]);
res.add("--port");
@ -357,7 +357,7 @@ public class DefaultLauncher extends Launcher {
}
private boolean isUsingLog4j() {
return VersionNumber.compare(repository.getGameVersion(version).orElse("1.7"), "1.7") >= 0;
return GameVersionNumber.compare(repository.getGameVersion(version).orElse("1.7"), "1.7") >= 0;
}
public File getLog4jConfigurationFile() {
@ -367,7 +367,7 @@ public class DefaultLauncher extends Launcher {
public void extractLog4jConfigurationFile() throws IOException {
File targetFile = getLog4jConfigurationFile();
InputStream source;
if (VersionNumber.compare(repository.getGameVersion(version).orElse("0.0"), "1.12") < 0) {
if (GameVersionNumber.asGameVersion(repository.getGameVersion(version)).compareTo("1.12") < 0) {
source = DefaultLauncher.class.getResourceAsStream("/assets/game/log4j2-1.7.xml");
} else {
source = DefaultLauncher.class.getResourceAsStream("/assets/game/log4j2-1.12.xml");

View File

@ -119,28 +119,4 @@ public interface RemoteModRepository {
return subcategories;
}
}
String[] DEFAULT_GAME_VERSIONS = new String[]{
"1.20.4", "1.20.3", "1.20.2", "1.20.1", "1.20",
"1.19.4", "1.19.3", "1.19.2", "1.19.1", "1.19",
"1.18.2", "1.18.1", "1.18",
"1.17.1", "1.17",
"1.16.5", "1.16.4", "1.16.3", "1.16.2", "1.16.1", "1.16",
"1.15.2", "1.15.1", "1.15",
"1.14.4", "1.14.3", "1.14.2", "1.14.1", "1.14",
"1.13.2", "1.13.1", "1.13",
"1.12.2", "1.12.1", "1.12",
"1.11.2", "1.11.1", "1.11",
"1.10.2", "1.10.1", "1.10",
"1.9.4", "1.9.3", "1.9.2", "1.9.1", "1.9",
"1.8.9", "1.8.8", "1.8.7", "1.8.6", "1.8.5", "1.8.4", "1.8.3", "1.8.2", "1.8.1", "1.8",
"1.7.10", "1.7.9", "1.7.8", "1.7.7", "1.7.6", "1.7.5", "1.7.4", "1.7.3", "1.7.2",
"1.6.4", "1.6.2", "1.6.1",
"1.5.2", "1.5.1",
"1.4.7", "1.4.6", "1.4.5", "1.4.4", "1.4.2",
"1.3.2", "1.3.1",
"1.2.5", "1.2.4", "1.2.3", "1.2.2", "1.2.1",
"1.1",
"1.0"
};
}

View File

@ -0,0 +1,616 @@
package org.jackhuang.hmcl.util.versioning;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class GameVersionNumber implements Comparable<GameVersionNumber> {
public static String[] getDefaultGameVersions() {
return Versions.DEFAULT_GAME_VERSIONS;
}
public static GameVersionNumber asGameVersion(String version) {
try {
if (!version.isEmpty()) {
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);
}
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 Snapshot.parse(version);
}
}
} catch (IllegalArgumentException ignore) {
}
Special special = Versions.SPECIALS.get(version);
if (special == null) {
special = new Special(version);
}
return special;
}
public static GameVersionNumber asGameVersion(Optional<String> version) {
return version.isPresent() ? asGameVersion(version.get()) : unknown();
}
public static GameVersionNumber unknown() {
return Release.ZERO;
}
public static int compare(String version1, String version2) {
return asGameVersion(version1).compareTo(asGameVersion(version2));
}
public static VersionRange<GameVersionNumber> between(String minimum, String maximum) {
return VersionRange.between(asGameVersion(minimum), asGameVersion(maximum));
}
public static VersionRange<GameVersionNumber> atLeast(String minimum) {
return VersionRange.atLeast(asGameVersion(minimum));
}
public static VersionRange<GameVersionNumber> atMost(String maximum) {
return VersionRange.atMost(asGameVersion(maximum));
}
final String value;
GameVersionNumber(String value) {
this.value = value;
}
enum Type {
PRE_CLASSIC, CLASSIC, INFDEV, ALPHA, BETA, NEW
}
abstract Type getType();
abstract int compareToImpl(@NotNull GameVersionNumber other);
public int compareTo(@NotNull String other) {
return this.compareTo(asGameVersion(other));
}
@Override
public int compareTo(@NotNull GameVersionNumber other) {
if (this.getType() != other.getType()) {
return Integer.compare(this.getType().ordinal(), other.getType().ordinal());
}
return compareToImpl(other);
}
@Override
public String toString() {
return value;
}
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);
}
Type type;
switch (value.charAt(0)) {
case 'a':
type = Type.ALPHA;
break;
case 'b':
type = Type.BETA;
break;
case 'c':
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);
}
final Type type;
final int major;
final int minor;
final int patch;
final int additional;
private Old(String value, Type type, int major, int minor, int patch, int additional) {
super(value);
this.type = type;
this.major = major;
this.minor = minor;
this.patch = patch;
this.additional = additional;
}
@Override
Type getType() {
return type;
}
@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);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Old other = (Old) o;
return major == other.major && minor == other.minor && patch == other.patch && additional == other.additional && type == other.type;
}
@Override
public int hashCode() {
return Objects.hash(type, major, minor, patch, additional);
}
}
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]+))?");
static final int TYPE_GA = Integer.MAX_VALUE;
static final int TYPE_UNKNOWN = 0;
static final int TYPE_EXP = 1;
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 Release parse(String value) {
Matcher matcher = PATTERN.matcher(value);
if (!matcher.matches()) {
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;
} else {
eaType = TYPE_UNKNOWN;
}
String eaVersionString = matcher.group("eaVersion");
int eaVersion = eaVersionString == null ? 0 : Integer.parseInt(eaVersionString);
return new Release(value, 1, minor, patch, eaType, eaVersion);
}
private final int major;
private final int minor;
private final int patch;
private final int eaType;
private final int eaVersion;
Release(String value, int major, int minor, int patch, int eaType, int eaVersion) {
super(value);
this.major = major;
this.minor = minor;
this.patch = patch;
this.eaType = eaType;
this.eaVersion = eaVersion;
}
@Override
Type getType() {
return Type.NEW;
}
int compareToRelease(Release other) {
int c = Integer.compare(this.major, other.major);
if (c != 0) {
return c;
}
c = Integer.compare(this.minor, other.minor);
if (c != 0) {
return c;
}
c = Integer.compare(this.patch, other.patch);
if (c != 0) {
return c;
}
c = Integer.compare(this.eaType, other.eaType);
if (c != 0) {
return c;
}
return Integer.compare(this.eaVersion, other.eaVersion);
}
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) {
return -1;
}
return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1;
}
@Override
int compareToImpl(@NotNull GameVersionNumber other) {
if (other instanceof Release) {
return compareToRelease((Release) other);
}
if (other instanceof Snapshot) {
return compareToSnapshot((Snapshot) other);
}
if (other instanceof Special) {
return -((Special) other).compareToReleaseOrSnapshot(this);
}
throw new AssertionError(other.getClass());
}
@Override
public int hashCode() {
return Objects.hash(major, minor, patch, eaType, eaVersion);
}
@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 == other.eaVersion;
}
}
static final class Snapshot extends GameVersionNumber {
static Snapshot parse(String value) {
if (value.length() != 6 || value.charAt(2) != 'w') {
throw new IllegalArgumentException(value);
}
int year;
int week;
try {
year = Integer.parseInt(value.substring(0, 2));
week = Integer.parseInt(value.substring(3, 5));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(value);
}
char suffix = value.charAt(5);
if ((suffix < 'a' || suffix > 'z') && suffix != '~') {
throw new IllegalArgumentException(value);
}
return new Snapshot(value, year, week, suffix);
}
static int toInt(int year, int week, char suffix) {
return (year << 16) | (week << 8) | suffix;
}
final int intValue;
Snapshot(String value, int year, int week, char suffix) {
super(value);
this.intValue = toInt(year, week, suffix);
}
@Override
Type getType() {
return Type.NEW;
}
@Override
int compareToImpl(@NotNull GameVersionNumber other) {
if (other instanceof Release) {
return -((Release) other).compareToSnapshot(this);
}
if (other instanceof Snapshot) {
return Integer.compare(this.intValue, ((Snapshot) other).intValue);
}
if (other instanceof Special) {
return -((Special) other).compareToReleaseOrSnapshot(this);
}
throw new AssertionError(other.getClass());
}
@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;
}
@Override
public int hashCode() {
return intValue;
}
}
static final class Special extends GameVersionNumber {
private VersionNumber versionNumber;
private GameVersionNumber prev;
Special(String value) {
super(value);
}
@Override
Type getType() {
return Type.NEW;
}
boolean isUnknown() {
return prev == null;
}
VersionNumber asVersionNumber() {
if (versionNumber != null) {
return versionNumber;
}
return versionNumber = VersionNumber.asVersion(value);
}
GameVersionNumber getPrevNormalVersion() {
GameVersionNumber v = prev;
while (v instanceof Special) {
v = ((Special) v).prev;
}
if (v == null) {
throw new AssertionError("version: " + value);
}
return v;
}
int compareToReleaseOrSnapshot(GameVersionNumber other) {
if (isUnknown()) {
return 1;
}
if (getPrevNormalVersion().compareTo(other) >= 0) {
return 1;
}
return -1;
}
int compareToSpecial(Special other) {
if (this.isUnknown()) {
return other.isUnknown() ? this.asVersionNumber().compareTo(other.asVersionNumber()) : 1;
}
if (other.isUnknown()) {
return -1;
}
if (this.value.equals(other.value)) {
return 0;
}
int c = this.getPrevNormalVersion().compareTo(other.getPrevNormalVersion());
if (c != 0) {
return c;
}
GameVersionNumber v = prev;
while (v instanceof Special) {
if (v == other) {
return 1;
}
v = ((Special) v).prev;
}
return -1;
}
@Override
int compareToImpl(@NotNull GameVersionNumber o) {
if (o instanceof Release) {
return compareToReleaseOrSnapshot(o);
}
if (o instanceof Snapshot) {
return compareToReleaseOrSnapshot(o);
}
if (o instanceof Special) {
return compareToSpecial((Special) o);
}
throw new AssertionError(o.getClass());
}
@Override
public int hashCode() {
return value.hashCode();
}
@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);
}
}
static final class Versions {
static final HashMap<String, Special> SPECIALS = new HashMap<>();
static final String[] DEFAULT_GAME_VERSIONS;
static final int[] SNAPSHOT_INTS;
static final Release[] SNAPSHOT_PREV;
static {
ArrayDeque<String> defaultGameVersions = new ArrayDeque<>(64);
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;
for (String line; (line = reader.readLine()) != null; ) {
if (line.isEmpty())
continue;
GameVersionNumber version = GameVersionNumber.asGameVersion(line);
if (currentRelease == null)
currentRelease = (Release) version;
if (version instanceof Snapshot) {
Snapshot snapshot = (Snapshot) version;
snapshots.add(snapshot);
snapshotPrev.add(currentRelease);
} else if (version instanceof Release) {
currentRelease = (Release) version;
if (currentRelease.eaType == Release.TYPE_GA) {
defaultGameVersions.addFirst(currentRelease.value);
}
} else if (version instanceof Special) {
Special special = (Special) version;
special.prev = prev;
SPECIALS.put(special.value, special);
} else
throw new AssertionError("version: " + version);
prev = version;
}
} catch (IOException e) {
throw new AssertionError(e);
}
DEFAULT_GAME_VERSIONS = defaultGameVersions.toArray(new String[0]);
SNAPSHOT_INTS = new int[snapshots.size()];
for (int i = 0; i < snapshots.size(); i++) {
SNAPSHOT_INTS[i] = snapshots.get(i).intValue;
}
SNAPSHOT_PREV = snapshotPrev.toArray(new Release[SNAPSHOT_INTS.length]);
}
}
}

View File

@ -83,6 +83,18 @@ public final class VersionNumber implements Comparable<VersionNumber> {
return true;
}
public static VersionRange<VersionNumber> between(String minimum, String maximum) {
return VersionRange.between(asVersion(minimum), asVersion(maximum));
}
public static VersionRange<VersionNumber> atLeast(String minimum) {
return VersionRange.atLeast(asVersion(minimum));
}
public static VersionRange<VersionNumber> atMost(String maximum) {
return VersionRange.atMost(asVersion(maximum));
}
private interface Item {
int LONG_ITEM = 0;
int BIGINTEGER_ITEM = 1;
@ -475,14 +487,6 @@ public final class VersionNumber implements Comparable<VersionNumber> {
return canonical;
}
public VersionNumber min(VersionNumber that) {
return this.compareTo(that) <= 0 ? this : that;
}
public VersionNumber max(VersionNumber that) {
return this.compareTo(that) >= 0 ? this : that;
}
@Override
public boolean equals(Object o) {
return o instanceof VersionNumber && canonical.equals(((VersionNumber) o).canonical);

View File

@ -2,58 +2,50 @@ package org.jackhuang.hmcl.util.versioning;
import java.util.Objects;
public final class VersionRange {
private static final VersionRange EMPTY = new VersionRange(null, null);
private static final VersionRange ALL = new VersionRange(null, null);
/**
* @author Glavo
*/
@SuppressWarnings("unchecked")
public final class VersionRange<T extends Comparable<T>> {
private static final VersionRange<?> EMPTY = new VersionRange<>(null, null);
private static final VersionRange<?> ALL = new VersionRange<>(null, null);
public static VersionRange empty() {
return EMPTY;
public static <T extends Comparable<T>> VersionRange<T> empty() {
return (VersionRange<T>) EMPTY;
}
public static VersionRange all() {
return ALL;
public static <T extends Comparable<T>> VersionRange<T> all() {
return (VersionRange<T>) ALL;
}
public static VersionRange between(String minimum, String maximum) {
return between(VersionNumber.asVersion(minimum), VersionNumber.asVersion(maximum));
}
public static VersionRange between(VersionNumber minimum, VersionNumber maximum) {
public static <T extends Comparable<T>> VersionRange<T> between(T minimum, T maximum) {
assert minimum.compareTo(maximum) <= 0;
return new VersionRange(minimum, maximum);
return new VersionRange<>(minimum, maximum);
}
public static VersionRange atLeast(String minimum) {
return atLeast(VersionNumber.asVersion(minimum));
}
public static VersionRange atLeast(VersionNumber minimum) {
public static <T extends Comparable<T>> VersionRange<T> atLeast(T minimum) {
assert minimum != null;
return new VersionRange(minimum, null);
return new VersionRange<>(minimum, null);
}
public static VersionRange atMost(String maximum) {
return atMost(VersionNumber.asVersion(maximum));
}
public static VersionRange atMost(VersionNumber maximum) {
public static <T extends Comparable<T>> VersionRange<T> atMost(T maximum) {
assert maximum != null;
return new VersionRange(null, maximum);
return new VersionRange<>(null, maximum);
}
private final VersionNumber minimum;
private final VersionNumber maximum;
private final T minimum;
private final T maximum;
private VersionRange(VersionNumber minimum, VersionNumber maximum) {
private VersionRange(T minimum, T maximum) {
this.minimum = minimum;
this.maximum = maximum;
}
public VersionNumber getMinimum() {
public T getMinimum() {
return minimum;
}
public VersionNumber getMaximum() {
public T getMaximum() {
return maximum;
}
@ -65,12 +57,7 @@ public final class VersionRange {
return !isEmpty() && minimum == null && maximum == null;
}
public boolean contains(String versionNumber) {
if (versionNumber == null) return false;
return contains(VersionNumber.asVersion(versionNumber));
}
public boolean contains(VersionNumber versionNumber) {
public boolean contains(T versionNumber) {
if (versionNumber == null) return false;
if (isEmpty()) return false;
if (isAll()) return true;
@ -78,7 +65,7 @@ public final class VersionRange {
return (minimum == null || minimum.compareTo(versionNumber) <= 0) && (maximum == null || maximum.compareTo(versionNumber) >= 0);
}
public boolean isOverlappedBy(final VersionRange that) {
public boolean isOverlappedBy(final VersionRange<T> that) {
if (this.isEmpty() || that.isEmpty())
return false;
@ -94,32 +81,32 @@ public final class VersionRange {
return that.contains(minimum) || that.contains(maximum) || (that.minimum != null && contains(that.minimum));
}
public VersionRange intersectionWith(VersionRange that) {
public VersionRange<T> intersectionWith(VersionRange<T> that) {
if (this.isAll())
return that;
if (that.isAll())
return this;
if (!isOverlappedBy(that))
return EMPTY;
return empty();
VersionNumber newMinimum;
T newMinimum;
if (this.minimum == null)
newMinimum = that.minimum;
else if (that.minimum == null)
newMinimum = this.minimum;
else
newMinimum = this.minimum.max(that.minimum);
newMinimum = this.minimum.compareTo(that.minimum) >= 0 ? this.minimum : that.minimum;
VersionNumber newMaximum;
T newMaximum;
if (this.maximum == null)
newMaximum = that.maximum;
else if (that.maximum == null)
newMaximum = this.maximum;
else
newMaximum = this.maximum.min(that.maximum);
newMaximum = this.maximum.compareTo(that.maximum) <= 0 ? this.maximum : that.maximum;
return new VersionRange(newMinimum, newMaximum);
return new VersionRange<>(newMinimum, newMaximum);
}
@Override
@ -139,7 +126,7 @@ public final class VersionRange {
if (!(obj instanceof VersionRange))
return false;
VersionRange that = (VersionRange) obj;
VersionRange<T> that = (VersionRange<T>) obj;
return this.isEmpty() == that.isEmpty() && this.isAll() == that.isAll()
&& Objects.equals(this.minimum, that.minimum)

View File

@ -0,0 +1,738 @@
1.0
11w47a
11w48a
11w49a
11w50a
12w01a
1.1
12w03a
12w04a
12w05a
12w05b
12w06a
12w07a
12w07b
12w08a
1.2
1.2.1
1.2.2
1.2.3
1.2.4
1.2.5
12w15a
12w16a
12w17a
12w18a
12w19a
12w21a
12w21b
12w22a
12w23a
12w23b
12w24a
12w25a
12w26a
12w27a
12w30a
12w30b
12w30c
12w30d
12w30e
1.3
1.3.1
1.3.2
12w32a
12w34a
12w34b
12w36a
12w37a
12w38a
12w38b
12w39a
12w39b
12w40a
12w40b
12w41a
12w41b
12w42a
12w42b
1.4
1.4.1
1.4.2
1.4.3
1.4.4
12w49a
12w50a
12w50b
1.4.5
1.4.6
1.4.7
13w01a
13w01b
13w02a
13w02b
13w03a
13w04a
13w05a
13w05b
13w06a
13w07a
13w09a
13w09b
13w09c
13w10a
13w10b
1.5
13w11a
13w12~
1.5.1
2.0
1.5.2
13w16a
13w16b
13w17a
13w18a
13w18b
13w18c
13w19a
13w21a
13w21b
13w22a
13w23a
13w23b
13w24a
13w24b
13w25a
13w25b
13w25c
13w26a
1.6
1.6.1
1.6.2
1.6.3
1.6.4
13w36a
13w36b
13w37a
13w37b
13w38a
13w38b
13w38c
13w39a
13w39b
13w41a
13w41b
13w42a
13w42b
13w43a
1.7
1.7.1
1.7.2
13w47a
13w47b
13w47c
13w47d
13w47e
13w48a
13w48b
13w49a
1.7.3
1.7.4
1.7.5
1.7.6-pre1
1.7.6-pre2
1.7.6
1.7.7
1.7.8
1.7.9
1.7.10-pre1
1.7.10-pre2
1.7.10-pre3
1.7.10-pre4
1.7.10
14w02a
14w02b
14w02c
14w03a
14w03b
14w04a
14w04b
14w05a
14w05b
14w06a
14w06b
14w07a
14w08a
14w10a
14w10b
14w10c
14w11a
14w11b
14w17a
14w18a
14w18b
14w19a
14w20a
14w20b
14w21a
14w21b
14w25a
14w25b
14w26a
14w26b
14w26c
14w27a
14w27b
14w28a
14w28b
14w29a
14w29b
14w30a
14w30b
14w30c
14w31a
14w32a
14w32b
14w32c
14w32d
14w33a
14w33b
14w33c
14w34a
14w34b
14w34c
14w34d
1.8-pre1
1.8-pre2
1.8-pre3
1.8
1.8.1-pre1
1.8.1-pre2
1.8.1-pre3
1.8.1-pre4
1.8.1-pre5
1.8.1
1.8.2-pre1
1.8.2-pre2
1.8.2-pre3
1.8.2-pre4
1.8.2-pre5
1.8.2-pre6
1.8.2-pre7
1.8.2
1.8.3
15w14a
1.8.4
1.8.5
1.8.6
1.8.7
1.8.8
1.8.9
15w31a
15w31b
15w31c
15w32a
15w32b
15w32c
15w33a
15w33b
15w33c
15w34a
15w34b
15w34c
15w34d
15w35a
15w35b
15w35c
15w35d
15w35e
15w36a
15w36b
15w36c
15w36d
15w37a
15w38a
15w38b
15w39a
15w39b
15w39c
15w40a
15w40b
15w41a
15w41b
15w42a
15w43a
15w43b
15w43c
15w44a
15w44b
15w45a
15w46a
15w47a
15w47b
15w47c
15w49a
15w49b
15w50a
15w51a
15w51b
16w02a
16w03a
16w04a
16w05a
16w05b
16w06a
16w07a
16w07b
1.9-pre1
1.9-pre2
1.9-pre3
1.9-pre4
1.9
1.9.1-pre1
1.9.1-pre2
1.9.1-pre3
1.9.1
1.9.2
1.RV-Pre1
16w14a
16w15a
16w15b
1.9.3-pre1
1.9.3-pre2
1.9.3-pre3
1.9.3
1.9.4
16w20a
16w21a
16w21b
1.10-pre1
1.10-pre2
1.10
1.10.1
1.10.2
16w32a
16w32b
16w33a
16w35a
16w36a
16w38a
16w39a
16w39b
16w39c
16w40a
16w41a
16w42a
16w43a
16w44a
1.11-pre1
1.11
16w50a
1.11.1
1.11.2
17w06a
17w13a
17w13b
17w14a
17w15a
17w16a
17w16b
17w17a
17w17b
17w18a
17w18b
1.12-pre1
1.12-pre2
1.12-pre3
1.12-pre4
1.12-pre5
1.12-pre6
1.12-pre7
1.12
17w31a
1.12.1-pre1
1.12.1
1.12.2-pre1
1.12.2-pre2
1.12.2
17w43a
17w43b
17w45a
17w45b
17w46a
17w47a
17w47b
17w48a
17w49a
17w49b
17w50a
18w01a
18w02a
18w03a
18w03b
18w05a
18w06a
18w07a
18w07b
18w07c
18w08a
18w08b
18w09a
18w10a
18w10b
18w10c
18w10d
18w11a
18w14a
18w14b
18w15a
18w16a
18w19a
18w19b
18w20a
18w20b
18w20c
18w21a
18w21b
18w22a
18w22b
18w22c
1.13-pre1
1.13-pre2
1.13-pre3
1.13-pre4
1.13-pre5
1.13-pre6
1.13-pre7
1.13-pre8
1.13-pre9
1.13-pre10
1.13
18w30a
18w30b
18w31a
18w32a
18w33a
1.13.1-pre1
1.13.1-pre2
1.13.1
1.13.2-pre1
1.13.2-pre2
1.13.2
18w43a
18w43b
18w43c
18w44a
18w45a
18w46a
18w47a
18w47b
18w48a
18w48b
18w49a
18w50a
19w02a
19w03a
19w03b
19w03c
19w04a
19w04b
19w05a
19w06a
19w07a
19w08a
19w08b
19w09a
19w11a
19w11b
19w12a
19w12b
19w13a
19w13b
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
1.14.1 Pre-Release 1
1.14.1 Pre-Release 2
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
1.14.3-pre1
1.14.3-pre2
1.14.3-pre3
1.14.3-pre4
1.14.3
1.14.4-pre1
1.14.4-pre2
1.14.4-pre3
1.14.4-pre4
1.14.4-pre5
1.14.4-pre6
1.14.4-pre7
1.14.4
19w34a
19w35a
19w36a
19w37a
19w38a
19w38b
19w39a
19w40a
19w41a
19w42a
19w44a
19w45a
19w45b
19w46a
19w46b
1.15-pre1
1.15-pre2
1.15-pre3
1.15-pre4
1.15-pre5
1.15-pre6
1.15-pre7
1.15
1.15.1-pre1
1.15.1
1.15.2-pre1
1.15.2-pre2
1.15.2
20w06a
20w07a
20w08a
20w09a
20w10a
20w11a
20w12a
20w13a
20w13b
20w14infinite
20w14a
20w15a
20w16a
20w17a
20w18a
20w19a
20w20a
20w20b
20w21a
20w22a
1.16-pre1
1.16-pre2
1.16-pre3
1.16-pre4
1.16-pre5
1.16-pre6
1.16-pre7
1.16-pre8
1.16-rc1
1.16
1.16.1
20w27a
20w28a
20w29a
20w30a
1.16.2-pre1
1.16.2-pre2
1.16.2-pre3
1.16.2-rc1
1.16.2-rc2
1.16.2
1.16.3-rc1
1.16.3
1.16.4-pre1
1.16.4-pre2
1.16.4-rc1
1.16.4
1.16.5-rc1
1.16.5
20w45a
20w46a
20w48a
20w49a
20w51a
21w03a
21w05a
21w05b
21w06a
21w07a
21w08a
21w08b
21w10a
21w11a
21w13a
21w14a
21w15a
21w16a
21w17a
21w18a
21w19a
21w20a
1.17-pre1
1.17-pre2
1.17-pre3
1.17-pre4
1.17-pre5
1.17-rc1
1.17-rc2
1.17
1.17.1-pre1
1.17.1-pre2
1.17.1-pre3
1.17.1-rc1
1.17.1-rc2
1.17.1
21w37a
21w38a
21w39a
21w40a
21w41a
21w42a
21w43a
21w44a
1.18-pre1
1.18-pre2
1.18-pre3
1.18-pre4
1.18-pre5
1.18-pre6
1.18-pre7
1.18-pre8
1.18-rc1
1.18-rc2
1.18-rc3
1.18-rc4
1.18
1.18.1-pre1
1.18.1-rc1
1.18.1-rc2
1.18.1-rc3
1.18.1
22w03a
22w05a
22w06a
22w07a
1.18.2-pre1
1.18.2-pre2
1.18.2-pre3
1.18.2-rc1
1.18.2
22w13oneblockatatime
22w11a
22w12a
22w13a
22w14a
22w15a
22w16a
22w16b
22w17a
22w18a
22w19a
1.19-pre1
1.19-pre2
1.19-pre3
1.19-pre4
1.19-pre5
1.19-rc1
1.19-rc2
1.19
22w24a
1.19.1-pre1
1.19.1-pre2
1.19.1-pre3
1.19.1-pre4
1.19.1-pre5
1.19.1-pre6
1.19.1-rc1
1.19.1-rc2
1.19.1-rc3
1.19.1
1.19.2-rc1
1.19.2-rc2
1.19.2
22w42a
22w43a
22w44a
22w45a
22w46a
1.19.3-pre1
1.19.3-pre2
1.19.3-pre3
1.19.3-rc1
1.19.3-rc2
1.19.3-rc3
1.19.3
23w03a
23w04a
23w05a
23w06a
23w07a
1.19.4-pre1
1.19.4-pre2
1.19.4-pre3
1.19.4-pre4
1.19.4-rc1
1.19.4-rc2
1.19.4-rc3
1.19.4
23w12a
23w13a
23w13a_or_b
23w14a
23w16a
23w17a
23w18a
1.20-pre1
1.20-pre2
1.20-pre3
1.20-pre4
1.20-pre5
1.20-pre6
1.20-pre7
1.20-rc1
1.20
1.20.1-rc1
1.20.1
23w31a
23w32a
23w33a
23w35a
1.20.2-pre1
1.20.2-pre2
1.20.2-pre3
1.20.2-pre4
1.20.2-rc1
1.20.2-rc2
1.20.2
23w40a
23w41a
23w42a
23w43a
23w43b
23w44a
23w45a
23w46a
1.20.3-pre1
1.20.3-pre2
1.20.3-pre3
1.20.3-pre4
1.20.3-rc1
1.20.3
1.20.4-rc1
1.20.4
23w51a
23w51b
24w03a
24w03b
24w04a
24w05a

View File

@ -17,8 +17,8 @@
*/
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import org.jackhuang.hmcl.util.versioning.VersionRange;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@ -28,10 +28,10 @@ public class JavaVersionConstraintTest {
@Test
public void vanillaJava16() {
JavaVersionConstraint.VersionRanges range = JavaVersionConstraint.findSuitableJavaVersionRange(
VersionNumber.asVersion("1.17"),
GameVersionNumber.asGameVersion("1.17"),
null
);
assertEquals(VersionRange.atLeast("16"), range.getMandatory());
assertEquals(VersionNumber.atLeast("16"), range.getMandatory());
}
}

View File

@ -0,0 +1,218 @@
package org.jackhuang.hmcl.util.versioning;
import org.junit.jupiter.api.Test;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author Glavo
*/
public class GameVersionNumberTest {
@Test
public void testSortVersions() throws IOException {
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);
}
}
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 void assertGameVersionEquals(String version) {
assertGameVersionEquals(version, version);
}
private static void assertGameVersionEquals(String version1, String version2) {
assertEquals(0, GameVersionNumber.asGameVersion(version1).compareTo(version2), errorMessage(version1, version2));
}
private static void assertLessThan(String version1, String version2) {
assertTrue(GameVersionNumber.asGameVersion(version1).compareTo(version2) < 0, errorMessage(version1, version2));
}
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]);
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]));
}
}
assertGameVersionEquals(versions[versions.length - 1]);
}
@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);
}
{
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 testCompareRelease() {
assertGameVersionEquals("0.0");
assertGameVersionEquals("1.100");
assertGameVersionEquals("1.100.1");
assertGameVersionEquals("1.100.1-pre1");
assertGameVersionEquals("1.100.1-pre1", "1.100.1 Pre-Release 1");
assertOrder(
"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"
);
}
@Test
public void testCompareSnapshot() {
assertOrder(
"90w01a",
"90w01b",
"90w01e",
"90w01~",
"90w02a"
);
}
@Test
public void testCompareMix() {
assertOrder(
"rd-132211",
"rd-161348",
"rd-20090515",
"c0.0.11a",
"c0.0.13a",
"c0.0.13a_03",
"c0.30_01c",
"inf-20100330-1",
"inf-20100330-2",
"inf-20100618",
"a1.0.4",
"a1.0.17_02",
"a1.0.17_04",
"a1.1.0",
"b1.0",
"b1.0_01",
"b1.1_02",
"b1.2",
"b1.8.1",
"0.0",
"1.0",
"11w47a",
"1.1",
"1.5.1",
"2.0",
"1.5.2",
"1.9.2",
"1.RV-Pre1",
"16w14a",
"1.9.3-pre1",
"1.13.2",
"19w13b",
"3D Shareware v1.34",
"19w14a",
"1.14 Pre-Release 1",
"1.14",
"1.15.2",
"20w06a",
"20w14infinite",
"20w22a",
"1.16-pre1",
"1.16",
"1.18.2",
"22w13oneblockatatime",
"22w11a",
"1.19-pre1",
"1.19.4",
"23w13a",
"23w13a_or_b",
"23w14a",
"1.20",
"Unknown",
"100.0"
);
}
@Test
public void testCompareUnknown() {
assertOrder(
"23w35a",
"1.20.2-pre1",
"1.20.2-rc1",
"1.20.2",
"23w35b", // fictional version number
"23w40a"
);
assertOrder(
"1.20.4",
"24w04a",
"1.100" // fictional version number
);
assertOrder(
"1.19.4",
"23w18a", // fictional version number
"1.19.5",
"1.20"
);
assertOrder(
"1.0",
"10w47a", // fictional version number
"11w47a",
"1.1"
);
}
}

View File

@ -9,43 +9,46 @@ public class VersionRangeTest {
@Test
public void testContains() {
assertTrue(between("10", "20").contains("10"));
assertTrue(between("10", "20").contains("15"));
assertTrue(between("10", "20").contains("20"));
assertFalse(between("10", "20").contains("5"));
assertFalse(between("10", "20").contains("25"));
VersionRange<VersionNumber> empty = VersionRange.empty();
VersionRange<VersionNumber> all = all();
assertTrue(between("10", "10").contains("10"));
assertFalse(between("10", "10").contains("5"));
assertFalse(between("10", "10").contains("15"));
assertTrue(VersionNumber.between("10", "20").contains(VersionNumber.asVersion("10")));
assertTrue(VersionNumber.between("10", "20").contains(VersionNumber.asVersion("15")));
assertTrue(VersionNumber.between("10", "20").contains(VersionNumber.asVersion("20")));
assertFalse(VersionNumber.between("10", "20").contains(VersionNumber.asVersion("5")));
assertFalse(VersionNumber.between("10", "20").contains(VersionNumber.asVersion("25")));
assertTrue(atLeast("10").contains("10"));
assertTrue(atLeast("10").contains("20"));
assertFalse(atLeast("10").contains("5"));
assertTrue(VersionNumber.between("10", "10").contains(VersionNumber.asVersion("10")));
assertFalse(VersionNumber.between("10", "10").contains(VersionNumber.asVersion("5")));
assertFalse(VersionNumber.between("10", "10").contains(VersionNumber.asVersion("15")));
assertTrue(atMost("10").contains("10"));
assertTrue(atMost("10").contains("5"));
assertFalse(atMost("10").contains("20"));
assertTrue(VersionNumber.atLeast("10").contains(VersionNumber.asVersion("10")));
assertTrue(VersionNumber.atLeast("10").contains(VersionNumber.asVersion("20")));
assertFalse(VersionNumber.atLeast("10").contains(VersionNumber.asVersion("5")));
assertFalse(empty().contains("0"));
assertFalse(empty().contains("10"));
assertTrue(VersionNumber.atMost("10").contains(VersionNumber.asVersion("10")));
assertTrue(VersionNumber.atMost("10").contains(VersionNumber.asVersion("5")));
assertFalse(VersionNumber.atMost("10").contains(VersionNumber.asVersion("20")));
assertTrue(all().contains("0"));
assertTrue(all().contains("10"));
assertFalse(empty.contains(VersionNumber.asVersion("0")));
assertFalse(empty.contains(VersionNumber.asVersion("10")));
assertFalse(all().contains((String) null));
assertFalse(empty().contains((String) null));
assertFalse(between("0", "10").contains((String) null));
assertFalse(atLeast("10").contains((String) null));
assertFalse(atMost("10").contains((String) null));
assertFalse(all().contains((VersionNumber) null));
assertFalse(empty().contains((VersionNumber) null));
assertFalse(between("0", "10").contains((VersionNumber) null));
assertFalse(atLeast("10").contains((VersionNumber) null));
assertFalse(atMost("10").contains((VersionNumber) null));
assertTrue(all.contains(VersionNumber.asVersion("0")));
assertTrue(all.contains(VersionNumber.asVersion("10")));
assertFalse(all.contains(null));
assertFalse(empty.contains( null));
assertFalse(VersionNumber.between("0", "10").contains(null));
assertFalse(VersionNumber.atLeast("10").contains(null));
assertFalse(VersionNumber.atMost("10").contains(null));
assertFalse(all.contains(null));
assertFalse(empty.contains(null));
assertFalse(VersionNumber.between("0", "10").contains(null));
assertFalse(VersionNumber.atLeast("10").contains(null));
assertFalse(VersionNumber.atMost("10").contains(null));
}
private static void assertIsOverlappedBy(boolean value, VersionRange range1, VersionRange range2) {
private static void assertIsOverlappedBy(boolean value, VersionRange<VersionNumber> range1, VersionRange<VersionNumber> range2) {
assertEquals(value, range1.isOverlappedBy(range2));
assertEquals(value, range2.isOverlappedBy(range1));
}
@ -56,38 +59,38 @@ public class VersionRangeTest {
assertIsOverlappedBy(false, all(), empty());
assertIsOverlappedBy(false, empty(), empty());
assertIsOverlappedBy(true, all(), between("10", "20"));
assertIsOverlappedBy(true, all(), atLeast("10"));
assertIsOverlappedBy(true, all(), atMost("10"));
assertIsOverlappedBy(true, all(), VersionNumber.between("10", "20"));
assertIsOverlappedBy(true, all(), VersionNumber.atLeast("10"));
assertIsOverlappedBy(true, all(), VersionNumber.atMost("10"));
assertIsOverlappedBy(false, empty(), between("10", "20"));
assertIsOverlappedBy(false, empty(), atLeast("10"));
assertIsOverlappedBy(false, empty(), atMost("10"));
assertIsOverlappedBy(false, empty(), VersionNumber.between("10", "20"));
assertIsOverlappedBy(false, empty(), VersionNumber.atLeast("10"));
assertIsOverlappedBy(false, empty(), VersionNumber.atMost("10"));
assertIsOverlappedBy(true, between("10", "20"), between("10", "20"));
assertIsOverlappedBy(true, between("10", "20"), between("5", "20"));
assertIsOverlappedBy(true, between("10", "20"), between("5", "15"));
assertIsOverlappedBy(true, between("10", "20"), between("5", "10"));
assertIsOverlappedBy(false, between("10", "20"), between("5", "5"));
assertIsOverlappedBy(true, between("10", "20"), between("10", "30"));
assertIsOverlappedBy(true, between("10", "20"), between("15", "30"));
assertIsOverlappedBy(true, between("10", "20"), between("20", "30"));
assertIsOverlappedBy(false, between("10", "20"), between("21", "30"));
assertIsOverlappedBy(true, between("10", "20"), atLeast("5"));
assertIsOverlappedBy(true, between("10", "20"), atLeast("10"));
assertIsOverlappedBy(true, between("10", "20"), atLeast("15"));
assertIsOverlappedBy(true, between("10", "20"), atLeast("20"));
assertIsOverlappedBy(false, between("10", "20"), atLeast("25"));
assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.between("10", "20"));
assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.between("5", "20"));
assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.between("5", "15"));
assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.between("5", "10"));
assertIsOverlappedBy(false, VersionNumber.between("10", "20"), VersionNumber.between("5", "5"));
assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.between("10", "30"));
assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.between("15", "30"));
assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.between("20", "30"));
assertIsOverlappedBy(false, VersionNumber.between("10", "20"), VersionNumber.between("21", "30"));
assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.atLeast("5"));
assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.atLeast("10"));
assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.atLeast("15"));
assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.atLeast("20"));
assertIsOverlappedBy(false, VersionNumber.between("10", "20"), VersionNumber.atLeast("25"));
assertIsOverlappedBy(true, atLeast("10"), atLeast("10"));
assertIsOverlappedBy(true, atLeast("10"), atLeast("20"));
assertIsOverlappedBy(true, atLeast("10"), atLeast("5"));
assertIsOverlappedBy(true, atLeast("10"), atMost("10"));
assertIsOverlappedBy(true, atLeast("10"), atMost("20"));
assertIsOverlappedBy(false, atLeast("10"), atMost("5"));
assertIsOverlappedBy(true, VersionNumber.atLeast("10"), VersionNumber.atLeast("10"));
assertIsOverlappedBy(true, VersionNumber.atLeast("10"), VersionNumber.atLeast("20"));
assertIsOverlappedBy(true, VersionNumber.atLeast("10"), VersionNumber.atLeast("5"));
assertIsOverlappedBy(true, VersionNumber.atLeast("10"), VersionNumber.atMost("10"));
assertIsOverlappedBy(true, VersionNumber.atLeast("10"), VersionNumber.atMost("20"));
assertIsOverlappedBy(false, VersionNumber.atLeast("10"), VersionNumber.atMost("5"));
}
private static void assertIntersectionWith(VersionRange range1, VersionRange range2, VersionRange result) {
private static void assertIntersectionWith(VersionRange<VersionNumber> range1, VersionRange<VersionNumber> range2, VersionRange<VersionNumber> result) {
assertEquals(result, range1.intersectionWith(range2));
assertEquals(result, range2.intersectionWith(range1));
}
@ -96,35 +99,35 @@ public class VersionRangeTest {
public void testIntersectionWith() {
assertIntersectionWith(all(), all(), all());
assertIntersectionWith(all(), empty(), empty());
assertIntersectionWith(all(), between("10", "20"), between("10", "20"));
assertIntersectionWith(all(), atLeast("10"), atLeast("10"));
assertIntersectionWith(all(), atMost("10"), atMost("10"));
assertIntersectionWith(all(), VersionNumber.between("10", "20"), VersionNumber.between("10", "20"));
assertIntersectionWith(all(), VersionNumber.atLeast("10"), VersionNumber.atLeast("10"));
assertIntersectionWith(all(), VersionNumber.atMost("10"), VersionNumber.atMost("10"));
assertIntersectionWith(empty(), empty(), empty());
assertIntersectionWith(empty(), between("10", "20"), empty());
assertIntersectionWith(empty(), atLeast("10"), empty());
assertIntersectionWith(empty(), atMost("10"), empty());
assertIntersectionWith(empty(), VersionNumber.between("10", "20"), empty());
assertIntersectionWith(empty(), VersionNumber.atLeast("10"), empty());
assertIntersectionWith(empty(), VersionNumber.atMost("10"), empty());
assertIntersectionWith(between("10", "20"), between("10", "20"), between("10", "20"));
assertIntersectionWith(between("10", "20"), between("5", "20"), between("10", "20"));
assertIntersectionWith(between("10", "20"), between("10", "25"), between("10", "20"));
assertIntersectionWith(between("10", "20"), between("5", "25"), between("10", "20"));
assertIntersectionWith(between("10", "20"), between("15", "20"), between("15", "20"));
assertIntersectionWith(between("10", "20"), between("10", "15"), between("10", "15"));
assertIntersectionWith(between("10", "20"), between("14", "16"), between("14", "16"));
assertIntersectionWith(between("10", "20"), atLeast("5"), between("10", "20"));
assertIntersectionWith(between("10", "20"), atLeast("10"), between("10", "20"));
assertIntersectionWith(between("10", "20"), atLeast("15"), between("15", "20"));
assertIntersectionWith(between("10", "20"), atLeast("20"), between("20", "20"));
assertIntersectionWith(between("10", "20"), atLeast("25"), empty());
assertIntersectionWith(between("10", "20"), atMost("25"), between("10", "20"));
assertIntersectionWith(between("10", "20"), atMost("20"), between("10", "20"));
assertIntersectionWith(between("10", "20"), atMost("15"), between("10", "15"));
assertIntersectionWith(between("10", "20"), atMost("10"), between("10", "10"));
assertIntersectionWith(between("10", "20"), atMost("5"), empty());
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.between("10", "20"), VersionNumber.between("10", "20"));
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.between("5", "20"), VersionNumber.between("10", "20"));
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.between("10", "25"), VersionNumber.between("10", "20"));
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.between("5", "25"), VersionNumber.between("10", "20"));
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.between("15", "20"), VersionNumber.between("15", "20"));
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.between("10", "15"), VersionNumber.between("10", "15"));
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.between("14", "16"), VersionNumber.between("14", "16"));
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atLeast("5"), VersionNumber.between("10", "20"));
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atLeast("10"), VersionNumber.between("10", "20"));
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atLeast("15"), VersionNumber.between("15", "20"));
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atLeast("20"), VersionNumber.between("20", "20"));
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atLeast("25"), empty());
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atMost("25"), VersionNumber.between("10", "20"));
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atMost("20"), VersionNumber.between("10", "20"));
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atMost("15"), VersionNumber.between("10", "15"));
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atMost("10"), VersionNumber.between("10", "10"));
assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atMost("5"), empty());
assertIntersectionWith(atLeast("10"), atMost("10"), between("10", "10"));
assertIntersectionWith(atLeast("10"), atMost("20"), between("10", "20"));
assertIntersectionWith(atLeast("10"), atMost("5"), empty());
assertIntersectionWith(VersionNumber.atLeast("10"), VersionNumber.atMost("10"), VersionNumber.between("10", "10"));
assertIntersectionWith(VersionNumber.atLeast("10"), VersionNumber.atMost("20"), VersionNumber.between("10", "20"));
assertIntersectionWith(VersionNumber.atLeast("10"), VersionNumber.atMost("5"), empty());
}
}