feat: auto select java. Closes #1068.
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.Range;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LAUNCH_WRAPPER_MAIN;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public enum JavaVersionConstraint {
|
||||
|
||||
// Minecraft>=1.17 requires Java 16
|
||||
VANILLA_JAVA_16(JavaVersionConstraint.RULE_MANDATORY, versionRange("1.17", JavaVersionConstraint.MAX), versionRange("16", JavaVersionConstraint.MAX)),
|
||||
// Minecraft>=1.13 requires Java 8
|
||||
VANILLA_JAVA_8(JavaVersionConstraint.RULE_MANDATORY, versionRange("1.13", JavaVersionConstraint.MAX), versionRange("1.8", JavaVersionConstraint.MAX)),
|
||||
// Minecraft>=1.7.10+Forge accepts Java 8
|
||||
MODDED_JAVA_8(JavaVersionConstraint.RULE_SUGGESTED, versionRange("1.7.10", JavaVersionConstraint.MAX), versionRange("1.8", JavaVersionConstraint.MAX)),
|
||||
// Minecraft<=1.7.2+Forge requires Java<=7
|
||||
MODDED_JAVA_7(JavaVersionConstraint.RULE_SUGGESTED, versionRange(JavaVersionConstraint.MIN, "1.7.2"), versionRange(JavaVersionConstraint.MIN, "1.7.999")) {
|
||||
@Override
|
||||
public boolean appliesToVersion(@Nullable VersionNumber gameVersion, @Nullable Version version) {
|
||||
if (version == null) return false;
|
||||
return LAUNCH_WRAPPER_MAIN.equals(version.getMainClass());
|
||||
}
|
||||
},
|
||||
// LaunchWrapper<=1.12 will crash because of assuming the system class loader is an instance of URLClassLoader (Java 8)
|
||||
LAUNCH_WRAPPER(JavaVersionConstraint.RULE_MANDATORY, versionRange("0", "1.12"), versionRange("0", "1.8")) {
|
||||
@Override
|
||||
public boolean appliesToVersion(VersionNumber gameVersion, Version version) {
|
||||
if (version == null) return false;
|
||||
return LAUNCH_WRAPPER_MAIN.equals(version.getMainClass()) &&
|
||||
version.getLibraries().stream()
|
||||
.filter(library -> "launchwrapper".equals(library.getArtifactId()))
|
||||
.anyMatch(library -> VersionNumber.asVersion(library.getVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0);
|
||||
}
|
||||
},
|
||||
// Minecraft>=1.13 may crash when generating world on Java [1.8,1.8.0_51)
|
||||
VANILLA_JAVA_8_51(JavaVersionConstraint.RULE_SUGGESTED, versionRange("1.13", JavaVersionConstraint.MAX), versionRange("1.8.0_51", JavaVersionConstraint.MAX)),
|
||||
// Minecraft with suggested java version recorded in game json is restrictedly constrained.
|
||||
GAME_JSON(JavaVersionConstraint.RULE_MANDATORY, versionRange(JavaVersionConstraint.MIN, JavaVersionConstraint.MAX), versionRange(JavaVersionConstraint.MIN, JavaVersionConstraint.MAX)) {
|
||||
@Override
|
||||
public boolean appliesToVersion(VersionNumber gameVersion, Version version) {
|
||||
if (gameVersion == null || 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 gameVersion.compareTo(VersionNumber.asVersion("1.7.10")) >= 0 && version.getJavaVersion() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range<VersionNumber> getJavaVersion(Version version) {
|
||||
String javaVersion;
|
||||
if (Objects.requireNonNull(version.getJavaVersion()).getMajorVersion() >= 9) {
|
||||
javaVersion = "" + version.getJavaVersion().getMajorVersion();
|
||||
} else {
|
||||
javaVersion = "1." + version.getJavaVersion().getMajorVersion();
|
||||
}
|
||||
return JavaVersionConstraint.versionRange(javaVersion, JavaVersionConstraint.MAX);
|
||||
}
|
||||
},
|
||||
;
|
||||
|
||||
private final int type;
|
||||
private final Range<VersionNumber> gameVersion;
|
||||
private final Range<VersionNumber> javaVersion;
|
||||
|
||||
JavaVersionConstraint(int type, Range<VersionNumber> gameVersion, Range<VersionNumber> javaVersion) {
|
||||
this.type = type;
|
||||
this.gameVersion = gameVersion;
|
||||
this.javaVersion = javaVersion;
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Range<VersionNumber> getGameVersion() {
|
||||
return gameVersion;
|
||||
}
|
||||
|
||||
public Range<VersionNumber> getJavaVersion(Version version) {
|
||||
return javaVersion;
|
||||
}
|
||||
|
||||
public boolean appliesToVersion(@Nullable VersionNumber gameVersion, @Nullable Version version) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Pair<Range<VersionNumber>, Range<VersionNumber>> findSuitableJavaVersionRange(VersionNumber gameVersion, Version version) {
|
||||
Range<VersionNumber> mandatoryJavaRange = versionRange(MIN, MAX);
|
||||
Range<VersionNumber> suggestedJavaRange = versionRange(MIN, MAX);
|
||||
for (JavaVersionConstraint java : values()) {
|
||||
if (java.gameVersion.contains(gameVersion) && java.appliesToVersion(gameVersion, version)) {
|
||||
Range<VersionNumber> javaVersionRange = java.getJavaVersion(version);
|
||||
if (java.type == RULE_MANDATORY) {
|
||||
mandatoryJavaRange = mandatoryJavaRange.intersectionWith(javaVersionRange);
|
||||
suggestedJavaRange = suggestedJavaRange.intersectionWith(javaVersionRange);
|
||||
} else if (java.type == RULE_SUGGESTED) {
|
||||
suggestedJavaRange = suggestedJavaRange.intersectionWith(javaVersionRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
return pair(mandatoryJavaRange, suggestedJavaRange);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static JavaVersion findSuitableJavaVersion(VersionNumber gameVersion, Version version) throws InterruptedException {
|
||||
Pair<Range<VersionNumber>, Range<VersionNumber>> range = findSuitableJavaVersionRange(gameVersion, version);
|
||||
Range<VersionNumber> mandatoryJavaRange = range.getKey();
|
||||
Range<VersionNumber> suggestedJavaRange = range.getValue();
|
||||
|
||||
JavaVersion mandatory = null;
|
||||
JavaVersion suggested = null;
|
||||
for (JavaVersion javaVersion : JavaVersion.getJavas()) {
|
||||
// select the latest java version that this version accepts.
|
||||
if (mandatoryJavaRange.contains(javaVersion.getVersionNumber())) {
|
||||
if (mandatory == null) mandatory = javaVersion;
|
||||
else if (javaVersion.getVersionNumber().compareTo(mandatory.getVersionNumber()) > 0) {
|
||||
mandatory = javaVersion;
|
||||
}
|
||||
}
|
||||
if (suggestedJavaRange.contains(javaVersion.getVersionNumber())) {
|
||||
if (suggested == null) suggested = javaVersion;
|
||||
else if (javaVersion.getVersionNumber().compareTo(suggested.getVersionNumber()) > 0) {
|
||||
suggested = javaVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (suggested != null) return suggested;
|
||||
else return mandatory;
|
||||
}
|
||||
|
||||
public static final int RULE_MANDATORY = 1;
|
||||
public static final int RULE_SUGGESTED = 2;
|
||||
|
||||
public static final String MIN = "0";
|
||||
public static final String MAX = "10000";
|
||||
|
||||
private static Range<VersionNumber> versionRange(String fromInclusive, String toExclusive) {
|
||||
return Range.between(VersionNumber.asVersion(fromInclusive), VersionNumber.asVersion(toExclusive));
|
||||
}
|
||||
}
|
||||
@@ -550,7 +550,7 @@ public abstract class Task<T> {
|
||||
public final <U> Task<U> thenComposeAsync(Task<U> other) {
|
||||
return thenComposeAsync(() -> other);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a new Task that, when this task completes
|
||||
* normally, is executed.
|
||||
@@ -560,7 +560,20 @@ public abstract class Task<T> {
|
||||
* @return the Task
|
||||
*/
|
||||
public final <U> Task<U> thenComposeAsync(ExceptionalSupplier<Task<U>, ?> fn) {
|
||||
return new UniCompose<>(fn, true);
|
||||
return thenComposeAsync(Schedulers.defaultScheduler(), fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Task that, when this task completes
|
||||
* normally, is executed.
|
||||
*
|
||||
* @param fn the function returning a new Task
|
||||
* @param executor the executor to use for asynchronous execution
|
||||
* @param <U> the type of the returned Task's result
|
||||
* @return the Task
|
||||
*/
|
||||
public final <U> Task<U> thenComposeAsync(Executor executor, ExceptionalSupplier<Task<U>, ?> fn) {
|
||||
return new UniCompose<>(fn, true).setExecutor(executor);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -573,7 +586,21 @@ public abstract class Task<T> {
|
||||
* @return the Task
|
||||
*/
|
||||
public <U, E extends Exception> Task<U> thenComposeAsync(ExceptionalFunction<T, Task<U>, E> fn) {
|
||||
return new UniCompose<>(fn, true);
|
||||
return thenComposeAsync(Schedulers.defaultScheduler(), fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Task that, when this task completes
|
||||
* normally, is executed with result of this task as the argument
|
||||
* to the supplied function.
|
||||
*
|
||||
* @param fn the function returning a new Task
|
||||
* @param executor the executor to use for asynchronous execution
|
||||
* @param <U> the type of the returned Task's result
|
||||
* @return the Task
|
||||
*/
|
||||
public <U, E extends Exception> Task<U> thenComposeAsync(Executor executor, ExceptionalFunction<T, Task<U>, E> fn) {
|
||||
return new UniCompose<>(fn, true).setExecutor(executor);
|
||||
}
|
||||
|
||||
public final <U> Task<U> withComposeAsync(Task<U> other) {
|
||||
@@ -840,6 +867,10 @@ public abstract class Task<T> {
|
||||
}.setName(name);
|
||||
}
|
||||
|
||||
public static <T> Task<T> composeAsync(Executor executor, ExceptionalSupplier<Task<T>, ?> fn) {
|
||||
return composeAsync(fn).setExecutor(executor);
|
||||
}
|
||||
|
||||
public static <V> Task<V> supplyAsync(Callable<V> callable) {
|
||||
return supplyAsync(getCaller(), callable).setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
@@ -856,6 +887,10 @@ public abstract class Task<T> {
|
||||
return new SimpleTask<>(callable).setExecutor(executor).setName(name);
|
||||
}
|
||||
|
||||
public static <V> Task<V> completed(V value) {
|
||||
return fromCompletableFuture(CompletableFuture.completedFuture(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Task that is completed when all of the given Tasks
|
||||
* complete. If any of the given Tasks complete exceptionally,
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.Range;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class JavaVersionConstraintTest {
|
||||
|
||||
@Test
|
||||
public void vanillaJava16() {
|
||||
Pair<Range<VersionNumber>, Range<VersionNumber>> range = JavaVersionConstraint.findSuitableJavaVersionRange(
|
||||
VersionNumber.asVersion("1.17"),
|
||||
null
|
||||
);
|
||||
|
||||
Assert.assertEquals(
|
||||
Range.between(VersionNumber.asVersion("16"), VersionNumber.asVersion(JavaVersionConstraint.MAX)),
|
||||
range.getKey());
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,10 @@ public class VersionNumberTest {
|
||||
v = VersionNumber.asVersion("1.8.0_11");
|
||||
Assert.assertTrue(u.compareTo(v) < 0);
|
||||
|
||||
u = VersionNumber.asVersion("1.7.0_22");
|
||||
v = VersionNumber.asVersion("1.7.99");
|
||||
Assert.assertTrue(u.compareTo(v) < 0);
|
||||
|
||||
u = VersionNumber.asVersion("1.12.2-14.23.5.2760");
|
||||
v = VersionNumber.asVersion("1.12.2-14.23.4.2739");
|
||||
Assert.assertTrue(u.compareTo(v) > 0);
|
||||
|
||||
Reference in New Issue
Block a user