Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 58 additions & 14 deletions HMCL/src/main/java/org/jackhuang/hmcl/java/JavaManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@
import org.jackhuang.hmcl.util.platform.UnsupportedPlatformException;
import org.jackhuang.hmcl.util.platform.windows.WinReg;
import org.jackhuang.hmcl.util.versioning.GameVersionNumber;
import org.jetbrains.annotations.NotNullByDefault;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;

import java.io.BufferedReader;
import java.io.File;
Expand All @@ -63,15 +65,18 @@

import static org.jackhuang.hmcl.util.logging.Logger.LOG;

/**
* @author Glavo
*/
/// Detects, selects, installs, and removes Java runtimes used by HMCL.
///
/// @author Glavo
@NotNullByDefault
public final class JavaManager {

/// Prevents construction of this utility class.
private JavaManager() {
}

private static final String[] KNOWN_VENDOR_DIRECTORIES = {
/// Vendor directory names searched in conventional Java installation locations.
private static final String @Unmodifiable [] KNOWN_VENDOR_DIRECTORIES = {
"Java",
"BellSoft",
"AdoptOpenJDK",
Expand All @@ -81,9 +86,18 @@ private JavaManager() {
"Semeru"
};

/// Default repository for Java runtimes managed under the HMCL user data directory.
public static final HMCLJavaRepository REPOSITORY = new HMCLJavaRepository(Metadata.HMCL_USER_HOME.resolve("java"));

/// Repository for Java runtimes bundled under the current HMCL workspace.
public static final HMCLJavaRepository LOCAL_REPOSITORY = new HMCLJavaRepository(Metadata.HMCL_LOCAL_HOME.resolve("java"));

/// Returns the repository selected by the current Java auto-download directory setting.
private static HMCLJavaRepository getActiveRepository() {
Path root = Path.of(SettingsManager.settings().getResolvedJavaDirectory()).toAbsolutePath().normalize();
return new HMCLJavaRepository(root);
}

public static String getMojangJavaPlatform(Platform platform) {
if (platform.getOperatingSystem() == OperatingSystem.WINDOWS) {
if (Architecture.SYSTEM_ARCH == Architecture.X86) {
Expand Down Expand Up @@ -231,15 +245,15 @@ public static Task<JavaRuntime> getAddJavaTask(Path binary) {
}

public static Task<JavaRuntime> getDownloadJavaTask(DownloadProvider downloadProvider, Platform platform, GameJavaVersion gameJavaVersion) {
return REPOSITORY.getDownloadJavaTask(downloadProvider, platform, gameJavaVersion)
return getActiveRepository().getDownloadJavaTask(downloadProvider, platform, gameJavaVersion)
.thenApplyAsync(Schedulers.javafx(), java -> {
addJava(java);
return java;
});
}

public static Task<JavaRuntime> getInstallJavaTask(Platform platform, String name, Map<String, Object> update, Path archiveFile) {
return REPOSITORY.getInstallJavaTask(platform, name, update, archiveFile)
return getActiveRepository().getInstallJavaTask(platform, name, update, archiveFile)
.thenApplyAsync(Schedulers.javafx(), java -> {
addJava(java);
return java;
Expand All @@ -249,15 +263,35 @@ public static Task<JavaRuntime> getInstallJavaTask(Platform platform, String nam
public static Task<Void> getUninstallJavaTask(JavaRuntime java) {
assert java.isManaged();

HMCLJavaRepository activeRepository = getActiveRepository();
@Nullable Task<Void> uninstallTask = getUninstallJavaTask(activeRepository, java);
if (uninstallTask != null) {
return uninstallTask;
}

if (!activeRepository.getPlatformRoot(java.getPlatform()).equals(REPOSITORY.getPlatformRoot(java.getPlatform()))) {
uninstallTask = getUninstallJavaTask(REPOSITORY, java);
if (uninstallTask != null) {
return uninstallTask;
}
}

return Task.completed(null);
}

/// Creates an uninstall task when the runtime belongs to the supplied managed repository.
///
/// @return the uninstall task, or `null` when the runtime is outside the repository
private static @Nullable Task<Void> getUninstallJavaTask(HMCLJavaRepository repository, JavaRuntime java) {
Path platformRoot;
try {
platformRoot = REPOSITORY.getPlatformRoot(java.getPlatform()).toRealPath();
platformRoot = repository.getPlatformRoot(java.getPlatform()).toRealPath();
} catch (Throwable ignored) {
return Task.completed(null);
return null;
}

if (!java.getBinary().startsWith(platformRoot))
return Task.completed(null);
return null;

Path relativized = platformRoot.relativize(java.getBinary());
if (relativized.getNameCount() > 1) {
Expand All @@ -270,9 +304,9 @@ public static Task<Void> getUninstallJavaTask(JavaRuntime java) {
});

String name = relativized.getName(0).toString();
return REPOSITORY.getUninstallJavaTask(java.getPlatform(), name);
return repository.getUninstallJavaTask(java.getPlatform(), name);
} else {
return Task.completed(null);
return null;
}
}

Expand Down Expand Up @@ -773,17 +807,27 @@ void tryAddJavaInComponentDir(String platform, Path component, boolean verify) {
}

void searchAllJavaInRepository(Platform platform) {
for (Path java : REPOSITORY.getAllJava(platform)) {
tryAddJavaExecutable(java, true);
HMCLJavaRepository activeRepository = getActiveRepository();
searchAllJavaInRepository(activeRepository, platform);

if (!activeRepository.getPlatformRoot(platform).equals(REPOSITORY.getPlatformRoot(platform))) {
searchAllJavaInRepository(REPOSITORY, platform);
}

for (Path java : LOCAL_REPOSITORY.getAllJava(platform)) {
tryAddJavaExecutable(java, true);
}
}

/// Adds runtimes from one HMCL-managed repository and its legacy macOS platform directory.
void searchAllJavaInRepository(HMCLJavaRepository repository, Platform platform) {
for (Path java : repository.getAllJava(platform)) {
tryAddJavaExecutable(java, true);
}

if (platform.os() == OperatingSystem.MACOS) {
// In the past, we used 'osx' as the checked name for macOS
Path platformRoot = REPOSITORY.getPlatformRoot(platform).resolveSibling("osx-" + platform.getArchitecture().getCheckedName());
Path platformRoot = repository.getPlatformRoot(platform).resolveSibling("osx-" + platform.getArchitecture().getCheckedName());
searchAllJavaInDirectory(platformRoot);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,39 @@ public String getResolvedCommonDirectory() {
: getDefaultCommonDirectory();
}

/// The HMCL-managed Java directory selection mode.
@SerializedName("javaDirectoryType")
private final ObjectProperty<EnumCommonDirectory> javaDirectoryType = new RawPreservingObjectProperty<>(EnumCommonDirectory.DEFAULT);

/// Returns the HMCL-managed Java directory selection mode property.
public ObjectProperty<EnumCommonDirectory> javaDirectoryTypeProperty() {
return javaDirectoryType;
}

/// The custom directory used to install HMCL-managed Java runtimes.
@SerializedName("javaDirectory")
private final StringProperty javaDirectory = new SimpleStringProperty();

/// Returns the custom HMCL-managed Java directory property.
public StringProperty javaDirectoryProperty() {
return javaDirectory;
}

/// Returns the default directory used to install HMCL-managed Java runtimes.
public static String getDefaultJavaDirectory() {
return Metadata.HMCL_USER_HOME.resolve("java").toString();
}

/// Resolves the effective directory used to install HMCL-managed Java runtimes.
public String getResolvedJavaDirectory() {
EnumCommonDirectory type = javaDirectoryType.get();
String customPath = javaDirectory.get();

return type == EnumCommonDirectory.CUSTOM && StringUtils.isNotBlank(customPath)
? customPath
: getDefaultJavaDirectory();
}

/// The maximum number of log lines kept in log views.
@SerializedName("logLines")
private final ObjectProperty<@Nullable Integer> logLines = new SimpleObjectProperty<>();
Expand Down
6 changes: 6 additions & 0 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,12 @@ public static void initialize(Stage stage) {
dialog(i18n("launcher.cache_directory.invalid"));
}

if (settings().javaDirectoryTypeProperty().get() == EnumCommonDirectory.CUSTOM &&
!FileUtils.canCreateDirectory(settings().getResolvedJavaDirectory())) {
settings().javaDirectoryTypeProperty().set(EnumCommonDirectory.DEFAULT);
dialog(i18n("launcher.java_directory.invalid"));
}

Lang.thread(JavaManager::initialize, "Search Java", true);

scene = new Scene(decorator.getDecorator());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
import org.jetbrains.annotations.NotNullByDefault;

import java.nio.file.Path;
import java.util.Arrays;
Expand All @@ -48,10 +49,14 @@
import static org.jackhuang.hmcl.setting.SettingsManager.settings;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;

/// Displays launcher download, cache, Java directory, thread, and proxy settings.
@NotNullByDefault
public class DownloadSettingsPage extends StackPane {

/// Keeps weak listeners alive for the lifetime of this page.
private final WeakListenerHolder holder = new WeakListenerHolder();

/// Creates the download settings page and binds its controls to launcher settings.
public DownloadSettingsPage() {
VBox content = new VBox(10);
content.setPadding(new Insets(10));
Expand Down Expand Up @@ -131,6 +136,24 @@ public DownloadSettingsPage() {
cleanButton.setOnAction(e -> clearCacheDirectory());
fileCommonLocationSublist.setHeaderRight(cleanButton);

ComponentSublist javaDirectorySublist = new ComponentSublist(() -> {
MultiFileItem<EnumCommonDirectory> javaDirectory = new MultiFileItem<>();
javaDirectory.loadChildren(Arrays.asList(
new MultiFileItem.Option<>(i18n("launcher.java_directory.default"), EnumCommonDirectory.DEFAULT),
new MultiFileItem.FileOption<>(i18n("settings.custom"), EnumCommonDirectory.CUSTOM)
.setChooserTitle(i18n("launcher.java_directory.choose"))
.setSelectionMode(FileSelector.SelectionMode.DIRECTORY)
.bindBidirectional(settings().javaDirectoryProperty())
));
javaDirectory.selectedDataProperty().bindBidirectional(settings().javaDirectoryTypeProperty());
return List.of(javaDirectory);
});
javaDirectorySublist.setTitle(i18n("launcher.java_directory"));
javaDirectorySublist.setHasSubtitle(true);
javaDirectorySublist.descriptionProperty().bind(
Bindings.createStringBinding(settings()::getResolvedJavaDirectory,
settings().javaDirectoryProperty(), settings().javaDirectoryTypeProperty()));

ComponentSublist downloadThreadsSublist = new ComponentSublist(() -> {
var downloadThreadsList = new RadioChoiceList<Boolean>();
downloadThreadsList.setChoices(
Expand Down Expand Up @@ -184,7 +207,7 @@ protected Node createRightNode() {
}
}, settings().autoDownloadThreadsProperty(), settings().downloadThreadsProperty()));

downloadList.getContent().addAll(fileCommonLocationSublist, downloadThreadsSublist);
downloadList.getContent().addAll(fileCommonLocationSublist, javaDirectorySublist, downloadThreadsSublist);
content.getChildren().addAll(ComponentList.createComponentListTitle(i18n("download")), downloadList);
}

Expand Down Expand Up @@ -348,6 +371,7 @@ protected Node createRightNode() {

}

/// Deletes cached download files from the resolved common directory.
private void clearCacheDirectory() {
String commonDirectory = settings().getResolvedCommonDirectory();
if (commonDirectory != null) {
Expand Down
4 changes: 4 additions & 0 deletions HMCL/src/main/resources/assets/lang/I18N.properties
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,10 @@ launcher.cache_directory.choose=Choose cache directory
launcher.cache_directory.default=Default ("%APPDATA%/.minecraft" or "~/.minecraft")
launcher.cache_directory.disabled=Disabled
launcher.cache_directory.invalid=Failed to create a cache directory, falling back to default.
launcher.java_directory=Java Auto-download Directory
launcher.java_directory.choose=Choose Java auto-download directory
launcher.java_directory.default=Default (HMCL data directory/java)
launcher.java_directory.invalid=Failed to create the custom Java auto-download directory, falling back to default.
launcher.contact=Contact Us
launcher.crash=Hello Minecraft! Launcher has encountered a fatal error! Please copy the following log and ask for help on our Discord, QQ group, GitHub, or other Minecraft forum.
launcher.crash.java_internal_error=Hello Minecraft! Launcher has encountered a fatal error because your Java is corrupted. Please uninstall your Java and download a suitable Java <a href="https://bell-sw.com/pages/downloads/#downloads">here</a>.
Expand Down
4 changes: 4 additions & 0 deletions HMCL/src/main/resources/assets/lang/I18N_zh.properties
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,10 @@ launcher.cache_directory.choose=選取檔案下載快取目錄
launcher.cache_directory.default=預設 ("%APPDATA%/.minecraft" 或 "~/.minecraft")
launcher.cache_directory.disabled=停用
launcher.cache_directory.invalid=無法建立自訂的快取目錄。已還原至預設設定。
launcher.java_directory=Java 自動下載目錄
launcher.java_directory.choose=選取 Java 自動下載目錄
launcher.java_directory.default=預設 (HMCL 資料目錄/java)
launcher.java_directory.invalid=無法建立自訂的 Java 自動下載目錄。已還原至預設設定。
launcher.contact=聯絡我們
launcher.crash=Hello Minecraft! Launcher 遇到了無法處理的錯誤。請複製下列內容並透過 GitHub、Discord 或 HMCL QQ 群回報問題。
launcher.crash.java_internal_error=Hello Minecraft! Launcher 由於目前 Java 損壞而無法繼續執行。請移除目前 Java,點擊 <a href="https://bell-sw.com/pages/downloads/#downloads">此處</a> 安裝合適的 Java 版本。
Expand Down
4 changes: 4 additions & 0 deletions HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,10 @@ launcher.cache_directory.choose=选择文件下载缓存文件夹
launcher.cache_directory.default=默认 ("%APPDATA%/.minecraft" 或 "~/.minecraft")
launcher.cache_directory.disabled=禁用 (总是使用游戏文件夹路径)
launcher.cache_directory.invalid=无法创建自定义的缓存文件夹。已经恢复到默认设置。
launcher.java_directory=Java 自动下载文件夹
launcher.java_directory.choose=选择 Java 自动下载文件夹
launcher.java_directory.default=默认 (HMCL 数据文件夹/java)
launcher.java_directory.invalid=无法创建自定义的 Java 自动下载文件夹。已经恢复到默认设置。
launcher.contact=联系我们
launcher.crash=Hello Minecraft! Launcher 遇到了无法处理的错误。请复制下列内容并点击右下角的按钮反馈问题。
launcher.crash.java_internal_error=Hello Minecraft! Launcher 由于当前 Java 损坏而无法继续运行。请卸载当前 Java,点击 <a href="https://bell-sw.com/pages/downloads/#downloads">此处</a> 安装合适的 Java 版本。
Expand Down