feat: auto select java. Closes #1068.

This commit is contained in:
huanghongxun
2021-09-27 20:53:25 +08:00
parent 04e6897a0d
commit a58de31d4b
13 changed files with 484 additions and 335 deletions

View File

@@ -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));
}
}

View File

@@ -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,

View File

@@ -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());
}
}

View File

@@ -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);