diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 9aabc428b..694d355b9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -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 java8 = JavaVersion.getJREs().stream() + Optional 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 java8 = JavaVersion.getJREs().stream().filter(javaVersion -> javaVersion.getParsedVersion() == JavaVersion.JAVA_8).findAny(); + Optional 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 java8 = JavaVersion.getJREs().stream() + Optional 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 java64 = JavaVersion.getJREs().stream() + Optional 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; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java index 2017ff97a..6e1f79611 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java @@ -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 matchedJava = JavaVersion.getJREs().stream() + List matchedJava = JavaVersion.getJavas().stream() .filter(java -> java.getVersion().equals(getJava())) .collect(Collectors.toList()); if (matchedJava.isEmpty()) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java index 43a66fbc5..625a780b3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java @@ -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.>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.getOptional("java") - .map(JavaVersion::getBinary).map(File::getAbsolutePath).orElse("Invalid Java Directory")))); + .map(JavaVersion::getBinary).map(Path::toString).orElse("Invalid Java Directory")))); } @FXML diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java index c59252427..92ac5e7ee 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java @@ -159,7 +159,7 @@ public final class UpdateHandler { private static void startJava(Path jar, String... appArgs) throws IOException { List 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) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/JavaVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/JavaVersion.java index 236567798..b30a8f42e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/JavaVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/platform/JavaVersion.java @@ -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,181 +142,174 @@ 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"))), - System.getProperty("java.version"), - Platform.PLATFORM - ); + 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); + } private static List JAVAS; private static final CountDownLatch LATCH = new CountDownLatch(1); - public static List getJREs() throws InterruptedException { + public static List 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 javaVersions; - switch (OperatingSystem.CURRENT_OS) { - case WINDOWS: - javaVersions = queryWindows(); - break; - case LINUX: - javaVersions = queryLinux(); - break; - case OSX: - javaVersions = queryMacintosh(); - break; - default: - javaVersions = new ArrayList<>(); - break; + + try { + javaVersions = lookupJavas(searchPotentialJavaHomes()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to search Java homes", e); + javaVersions = new ArrayList<>(); } - 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 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")) - .filter(Files::isExecutable) - .flatMap(executable -> { - try { - return Stream.of(fromExecutable(executable.toFile())); - } catch (IOException e) { - Logging.LOG.log(Level.WARNING, "Couldn't determine java " + executable, e); - return Stream.empty(); - } - }) - .collect(Collectors.toList()); - } else { - return Collections.emptyList(); - } + private static List lookupJavas(Stream javaHomes) { + return javaHomes + .filter(Files::isDirectory) + .map(JavaVersion::getExecutable) + .filter(Files::isExecutable) + .flatMap(executable -> { // resolve symbolic links + try { + return Stream.of(executable.toRealPath()); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to lookup Java executable at " + executable, e); + return Stream.empty(); + } + }) + .distinct() // remove duplicated javas + .flatMap(executable -> { + if (executable.equals(CURRENT_JAVA.getBinary())) { + return Stream.of(CURRENT_JAVA); + } + try { + return Stream.of(fromExecutable(executable)); + } catch (IOException e) { + LOG.log(Level.WARNING, "Failed to determine Java at " + executable, e); + return Stream.empty(); + } + }) + .collect(toList()); } - // ==== - private static JavaVersion fromJavaHomeQuietly(File home) { - try { - return fromJavaHome(home); - } catch (IOException e) { - Logging.LOG.log(Level.WARNING, "Couldn't determine java " + home, e); - return null; + private static Stream searchPotentialJavaHomes() throws IOException { + switch (OperatingSystem.CURRENT_OS) { + + case WINDOWS: + List 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 queryMacintosh() { - List 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 queryWindows() { - List 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 queryJavaInRegistryKey(String location) throws IOException, InterruptedException { - List res = new ArrayList<>(); + // ==== Windows Registry Support ==== + private static List queryJavaHomesInRegistryKey(String location) throws IOException { + List 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)); + } } - - res.removeIf(Objects::isNull); - return res; + return homes; } - // Registry utilities - private static List querySubFolders(String location) throws IOException, InterruptedException { + private static List querySubFolders(String location) throws IOException { List 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; }