Refactor Java lookup

This commit is contained in:
yushijinhun
2018-09-30 19:18:29 +08:00
parent 76ef47efc4
commit 684813e131
5 changed files with 136 additions and 141 deletions

View File

@@ -238,7 +238,7 @@ public final class LauncherHelper {
// Game later than 1.7.2 accepts Java 8.
if (!flag && java.getParsedVersion() < JavaVersion.JAVA_8 && gameVersion.compareTo(VersionNumber.asVersion("1.7.2")) > 0) {
Optional<JavaVersion> java8 = JavaVersion.getJREs().stream()
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream()
.filter(javaVersion -> javaVersion.getParsedVersion() >= JavaVersion.JAVA_8)
.max(Comparator.comparing(JavaVersion::getVersionNumber));
if (java8.isPresent()) {
@@ -263,7 +263,7 @@ public final class LauncherHelper {
&& version.getLibraries().stream()
.filter(library -> "launchwrapper".equals(library.getArtifactId()))
.anyMatch(library -> VersionNumber.asVersion(library.getVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0)) {
Optional<JavaVersion> java8 = JavaVersion.getJREs().stream().filter(javaVersion -> javaVersion.getParsedVersion() == JavaVersion.JAVA_8).findAny();
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream().filter(javaVersion -> javaVersion.getParsedVersion() == JavaVersion.JAVA_8).findAny();
if (java8.isPresent()) {
java8required = true;
setting.setJavaVersion(java8.get());
@@ -276,7 +276,7 @@ public final class LauncherHelper {
// Minecraft 1.13 may crash when generating world on Java 8 earlier than 1.8.0_51
VersionNumber JAVA_8 = VersionNumber.asVersion("1.8.0.51");
if (!flag && gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0 && java.getParsedVersion() == JavaVersion.JAVA_8 && java.getVersionNumber().compareTo(JAVA_8) < 0) {
Optional<JavaVersion> java8 = JavaVersion.getJREs().stream()
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream()
.filter(javaVersion -> javaVersion.getVersionNumber().compareTo(JAVA_8) >= 0)
.max(Comparator.comparing(JavaVersion::getVersionNumber));
if (java8.isPresent()) {
@@ -293,7 +293,7 @@ public final class LauncherHelper {
final JavaVersion java32 = java;
// First find if same java version but whose platform is 64-bit installed.
Optional<JavaVersion> java64 = JavaVersion.getJREs().stream()
Optional<JavaVersion> java64 = JavaVersion.getJavas().stream()
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.PLATFORM)
.filter(javaVersion -> javaVersion.getParsedVersion() == java32.getParsedVersion())
.max(Comparator.comparing(JavaVersion::getVersionNumber));
@@ -302,7 +302,7 @@ public final class LauncherHelper {
final boolean java8requiredFinal = java8required, newJavaRequiredFinal = newJavaRequired;
// Then find if other java version which satisfies requirements installed.
java64 = JavaVersion.getJREs().stream()
java64 = JavaVersion.getJavas().stream()
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.PLATFORM)
.filter(javaVersion -> {
if (java8requiredFinal) return javaVersion.getParsedVersion() == JavaVersion.JAVA_8;

View File

@@ -32,6 +32,7 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@@ -455,12 +456,12 @@ public final class VersionSetting {
if ("Default".equals(getJava())) return JavaVersion.fromCurrentEnvironment();
else if (isUsesCustomJavaDir()) {
try {
return JavaVersion.fromExecutable(new File(getJavaDir()));
return JavaVersion.fromExecutable(Paths.get(getJavaDir()));
} catch (IOException e) {
return null; // Custom Java Directory not found,
}
} else if (StringUtils.isNotBlank(getJava())) {
List<JavaVersion> matchedJava = JavaVersion.getJREs().stream()
List<JavaVersion> matchedJava = JavaVersion.getJavas().stream()
.filter(java -> java.getVersion().equals(getJava()))
.collect(Collectors.toList());
if (matchedJava.isEmpty()) {

View File

@@ -53,6 +53,7 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
@@ -106,11 +107,11 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
FXUtils.smoothScrolling(scroll);
Task.of(variables -> variables.set("list", JavaVersion.getJREs()))
Task.of(variables -> variables.set("list", JavaVersion.getJavas()))
.subscribe(Schedulers.javafx(), variables -> {
javaItem.loadChildren(
(variables.<List<JavaVersion>>get("list")).stream()
.map(javaVersion -> javaItem.createChildren(javaVersion.getVersion() + i18n("settings.game.java_directory.bit", javaVersion.getPlatform().getBit()), javaVersion.getBinary().getAbsolutePath(), javaVersion))
.map(javaVersion -> javaItem.createChildren(javaVersion.getVersion() + i18n("settings.game.java_directory.bit", javaVersion.getPlatform().getBit()), javaVersion.getBinary().toString(), javaVersion))
.collect(Collectors.toList()));
javaItemsLoaded = true;
initializeSelectedJava();
@@ -259,7 +260,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
Task.of(variables -> variables.set("java", versionSetting.getJavaVersion()))
.subscribe(Task.of(Schedulers.javafx(),
variables -> javaItem.setSubtitle(variables.<JavaVersion>getOptional("java")
.map(JavaVersion::getBinary).map(File::getAbsolutePath).orElse("Invalid Java Directory"))));
.map(JavaVersion::getBinary).map(Path::toString).orElse("Invalid Java Directory"))));
}
@FXML

View File

@@ -159,7 +159,7 @@ public final class UpdateHandler {
private static void startJava(Path jar, String... appArgs) throws IOException {
List<String> commandline = new ArrayList<>();
commandline.add(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath());
commandline.add(JavaVersion.fromCurrentEnvironment().getBinary().toString());
commandline.add("-jar");
commandline.add(jar.toAbsolutePath().toString());
for (String arg : appArgs) {

View File

@@ -17,27 +17,24 @@
*/
package org.jackhuang.hmcl.util.platform;
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toList;
import static org.jackhuang.hmcl.util.Logging.LOG;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
@@ -48,19 +45,19 @@ import org.jackhuang.hmcl.util.versioning.VersionNumber;
*/
public final class JavaVersion {
private final File binary;
private final Path binary;
private final String longVersion;
private final Platform platform;
private final int version;
public JavaVersion(File binary, String longVersion, Platform platform) {
public JavaVersion(Path binary, String longVersion, Platform platform) {
this.binary = binary;
this.longVersion = longVersion;
this.platform = platform;
version = parseVersion(longVersion);
}
public File getBinary() {
public Path getBinary() {
return binary;
}
@@ -114,15 +111,18 @@ public final class JavaVersion {
return UNKNOWN;
}
public static JavaVersion fromExecutable(File executable) throws IOException {
public static JavaVersion fromExecutable(Path executable) throws IOException {
Platform platform = Platform.BIT_32;
String version = null;
// javaw is only used on windows
if ("javaw.exe".equalsIgnoreCase(executable.getName()))
executable = new File(executable.getAbsoluteFile().getParentFile(), "java.exe");
if ("javaw.exe".equalsIgnoreCase(executable.getFileName().toString())) {
executable = executable.resolveSibling("java.exe");
}
Process process = new ProcessBuilder(executable.getAbsolutePath(), "-version").start();
executable = executable.toRealPath();
Process process = new ProcessBuilder(executable.toString(), "-version").start();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
for (String line; (line = reader.readLine()) != null;) {
Matcher m = REGEX.matcher(line);
@@ -134,7 +134,7 @@ public final class JavaVersion {
}
if (version == null)
throw new IOException("No matched Java version.");
throw new IOException("No Java version is matched");
if (parseVersion(version) == UNKNOWN)
throw new IOException("Unrecognized Java version " + version);
@@ -142,182 +142,175 @@ public final class JavaVersion {
return new JavaVersion(executable, version, platform);
}
public static JavaVersion fromJavaHome(File home) throws IOException {
return fromExecutable(getExecutable(home));
}
private static File getExecutable(File javaHome) {
private static Path getExecutable(Path javaHome) {
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
return new File(javaHome, "bin/java.exe");
return javaHome.resolve("bin").resolve("java.exe");
} else {
return new File(javaHome, "bin/java");
return javaHome.resolve("bin").resolve("java");
}
}
public static JavaVersion fromCurrentEnvironment() {
return THIS_JAVA;
return CURRENT_JAVA;
}
public static final JavaVersion THIS_JAVA = new JavaVersion(
getExecutable(new File(System.getProperty("java.home"))),
public static final JavaVersion CURRENT_JAVA;
static {
Path currentExecutable = getExecutable(Paths.get(System.getProperty("java.home")));
try {
currentExecutable = currentExecutable.toRealPath();
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to resolve current Java path: " + currentExecutable, e);
}
CURRENT_JAVA = new JavaVersion(
currentExecutable,
System.getProperty("java.version"),
Platform.PLATFORM
);
Platform.PLATFORM);
}
private static List<JavaVersion> JAVAS;
private static final CountDownLatch LATCH = new CountDownLatch(1);
public static List<JavaVersion> getJREs() throws InterruptedException {
public static List<JavaVersion> getJavas() throws InterruptedException {
if (JAVAS != null)
return JAVAS;
LATCH.await();
return JAVAS;
}
public static synchronized void initialize() throws IOException {
public static synchronized void initialize() {
if (JAVAS != null)
throw new IllegalStateException("JavaVersions have already been initialized.");
List<JavaVersion> javaVersions;
switch (OperatingSystem.CURRENT_OS) {
case WINDOWS:
javaVersions = queryWindows();
break;
case LINUX:
javaVersions = queryLinux();
break;
case OSX:
javaVersions = queryMacintosh();
break;
default:
try {
javaVersions = lookupJavas(searchPotentialJavaHomes());
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to search Java homes", e);
javaVersions = new ArrayList<>();
break;
}
boolean isCurrentJavaIncluded = false;
for (int i = 0; i < javaVersions.size(); i++) {
if (THIS_JAVA.getBinary().equals(javaVersions.get(i).getBinary())) {
javaVersions.set(i, THIS_JAVA);
isCurrentJavaIncluded = true;
break;
}
}
if (!isCurrentJavaIncluded) {
javaVersions.add(THIS_JAVA);
// insert current java to the list
if (!javaVersions.contains(CURRENT_JAVA)) {
javaVersions.add(CURRENT_JAVA);
}
JAVAS = Collections.unmodifiableList(javaVersions);
JAVAS = unmodifiableList(javaVersions);
LATCH.countDown();
}
// ==== Linux ====
private static List<JavaVersion> queryLinux() throws IOException {
Path jvmDir = Paths.get("/usr/lib/jvm");
if (Files.isDirectory(jvmDir)) {
return Files.list(jvmDir)
.filter(dir -> Files.isDirectory(dir, LinkOption.NOFOLLOW_LINKS))
.map(dir -> dir.resolve("bin/java"))
private static List<JavaVersion> lookupJavas(Stream<Path> javaHomes) {
return javaHomes
.filter(Files::isDirectory)
.map(JavaVersion::getExecutable)
.filter(Files::isExecutable)
.flatMap(executable -> {
.flatMap(executable -> { // resolve symbolic links
try {
return Stream.of(fromExecutable(executable.toFile()));
return Stream.of(executable.toRealPath());
} catch (IOException e) {
Logging.LOG.log(Level.WARNING, "Couldn't determine java " + executable, e);
LOG.log(Level.WARNING, "Failed to lookup Java executable at " + executable, e);
return Stream.empty();
}
})
.collect(Collectors.toList());
} else {
return Collections.emptyList();
.distinct() // remove duplicated javas
.flatMap(executable -> {
if (executable.equals(CURRENT_JAVA.getBinary())) {
return Stream.of(CURRENT_JAVA);
}
}
// ====
private static JavaVersion fromJavaHomeQuietly(File home) {
try {
return fromJavaHome(home);
return Stream.of(fromExecutable(executable));
} catch (IOException e) {
Logging.LOG.log(Level.WARNING, "Couldn't determine java " + home, e);
return null;
LOG.log(Level.WARNING, "Failed to determine Java at " + executable, e);
return Stream.empty();
}
})
.collect(toList());
}
private static Stream<Path> searchPotentialJavaHomes() throws IOException {
switch (OperatingSystem.CURRENT_OS) {
case WINDOWS:
List<Path> locations = new ArrayList<>();
locations.addAll(queryJavaHomesInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\"));
locations.addAll(queryJavaHomesInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\"));
locations.addAll(queryJavaHomesInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JRE\\"));
locations.addAll(queryJavaHomesInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JDK\\"));
return locations.stream();
case LINUX:
Path linuxJvmDir = Paths.get("/usr/lib/jvm");
if (Files.isDirectory(linuxJvmDir)) {
return Files.list(linuxJvmDir);
}
return Stream.empty();
case OSX:
Path osxJvmDir = Paths.get("/Library/Java/JavaVirtualMachines");
if (Files.isDirectory(osxJvmDir)) {
return Files.list(osxJvmDir)
.map(dir -> dir.resolve("Contents/Home"));
}
return Stream.empty();
default:
return Stream.empty();
}
}
// ==== OSX ====
private static List<JavaVersion> queryMacintosh() {
List<JavaVersion> res = new ArrayList<>();
File currentJRE = new File("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home");
if (currentJRE.exists())
res.add(fromJavaHomeQuietly(currentJRE));
File[] files = new File("/Library/Java/JavaVirtualMachines/").listFiles();
if (files != null)
for (File file : files)
res.add(fromJavaHomeQuietly(new File(file, "Contents/Home")));
res.removeIf(Objects::isNull);
return res;
}
// ====
// ==== Windows ====
private static List<JavaVersion> queryWindows() {
List<JavaVersion> res = new ArrayList<>();
Lang.ignoringException(() -> res.addAll(queryJavaInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\")));
Lang.ignoringException(() -> res.addAll(queryJavaInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\")));
Lang.ignoringException(() -> res.addAll(queryJavaInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JRE\\")));
Lang.ignoringException(() -> res.addAll(queryJavaInRegistryKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JDK\\")));
return res;
}
private static List<JavaVersion> queryJavaInRegistryKey(String location) throws IOException, InterruptedException {
List<JavaVersion> res = new ArrayList<>();
// ==== Windows Registry Support ====
private static List<Path> queryJavaHomesInRegistryKey(String location) throws IOException {
List<Path> homes = new ArrayList<>();
for (String java : querySubFolders(location)) {
if (!querySubFolders(java).contains(java + "\\MSI")) continue;
if (!querySubFolders(java).contains(java + "\\MSI"))
continue;
String home = queryRegisterValue(java, "JavaHome");
if (home != null)
res.add(fromJavaHomeQuietly(new File(home)));
if (home != null) {
homes.add(Paths.get(home));
}
}
return homes;
}
res.removeIf(Objects::isNull);
return res;
}
// Registry utilities
private static List<String> querySubFolders(String location) throws IOException, InterruptedException {
private static List<String> querySubFolders(String location) throws IOException {
List<String> res = new ArrayList<>();
String[] cmd = new String[] { "cmd", "/c", "reg", "query", location };
Process process = Runtime.getRuntime().exec(cmd);
process.waitFor();
Process process = Runtime.getRuntime().exec(new String[] { "cmd", "/c", "reg", "query", location });
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
for (String line; (line = reader.readLine()) != null; )
if (line.startsWith(location) && !line.equals(location))
for (String line; (line = reader.readLine()) != null;) {
if (line.startsWith(location) && !line.equals(location)) {
res.add(line);
}
}
}
return res;
}
private static String queryRegisterValue(String location, String name) throws IOException, InterruptedException {
String[] cmd = new String[] { "cmd", "/c", "reg", "query", location, "/v", name };
private static String queryRegisterValue(String location, String name) throws IOException {
boolean last = false;
Process process = Runtime.getRuntime().exec(cmd);
process.waitFor();
Process process = Runtime.getRuntime().exec(new String[] { "cmd", "/c", "reg", "query", location, "/v", name });
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
for (String line; (line = reader.readLine()) != null; )
for (String line; (line = reader.readLine()) != null;) {
if (StringUtils.isNotBlank(line)) {
if (last && line.trim().startsWith(name)) {
int begins = line.indexOf(name);
if (begins > 0) {
String s2 = line.substring(begins + name.length());
begins = s2.indexOf("REG_SZ");
if (begins > 0)
if (begins > 0) {
return s2.substring(begins + "REG_SZ".length()).trim();
}
}
if (location.equals(line.trim()))
}
if (location.equals(line.trim())) {
last = true;
}
}
}
}
return null;
}
// ====