From 80ba1ec1a794a0e8d1a477411664296554390f10 Mon Sep 17 00:00:00 2001 From: NoClassDefFoundError Date: Sat, 14 Feb 2026 22:10:04 +0800 Subject: [PATCH] =?UTF-8?q?[Feature]=20=E8=AE=A9Java=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=8F=AF=E4=BB=A5=E5=90=8C=E6=97=B6=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E5=A4=9A=E4=B8=AAJava=20(#5394)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Glavo --- .../hmcl/ui/main/JavaDownloadDialog.java | 96 ++++++++++++------- .../resources/assets/lang/I18N.properties | 1 - .../resources/assets/lang/I18N_ar.properties | 1 - .../resources/assets/lang/I18N_es.properties | 1 - .../resources/assets/lang/I18N_lzh.properties | 1 - .../resources/assets/lang/I18N_ru.properties | 1 - .../resources/assets/lang/I18N_uk.properties | 1 - .../resources/assets/lang/I18N_zh.properties | 1 - .../assets/lang/I18N_zh_CN.properties | 1 - .../java/org/jackhuang/hmcl/task/Task.java | 32 +++++++ .../java/org/jackhuang/hmcl/util/Result.java | 78 +++++++++++++++ 11 files changed, 174 insertions(+), 40 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/Result.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java index c6e88a748..1a9efa1dd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/JavaDownloadDialog.java @@ -18,13 +18,13 @@ package org.jackhuang.hmcl.ui.main; import com.jfoenix.controls.*; +import javafx.beans.InvalidationListener; import javafx.beans.binding.Bindings; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.control.Label; -import javafx.scene.control.ToggleGroup; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; import javafx.scene.layout.StackPane; @@ -48,6 +48,8 @@ import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; import org.jackhuang.hmcl.ui.construct.DialogPane; import org.jackhuang.hmcl.ui.construct.JFXHyperlink; import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.Result; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.gson.JsonUtils; @@ -108,23 +110,39 @@ public final class JavaDownloadDialog extends StackPane { } private final class DownloadMojangJava extends DialogPane { - private final ToggleGroup toggleGroup = new ToggleGroup(); + private final List checkboxes = new ArrayList<>(); DownloadMojangJava() { setTitle(i18n("java.download.title")); - validProperty().bind(toggleGroup.selectedToggleProperty().isNotNull()); VBox vbox = new VBox(16); Label prompt = new Label(i18n("java.download.prompt")); vbox.getChildren().add(prompt); + setValid(false); + InvalidationListener updateValidStatus = e -> { + for (JFXCheckBox box : checkboxes) { + if (!box.isDisabled() && box.isSelected()) { + setValid(true); + return; + } + } + setValid(false); + }; + for (GameJavaVersion version : supportedGameJavaVersions) { - JFXRadioButton button = new JFXRadioButton("Java " + version.majorVersion()); + JFXCheckBox button = new JFXCheckBox("Java " + version.majorVersion()); button.setUserData(version); - vbox.getChildren().add(button); - toggleGroup.getToggles().add(button); - if (JavaManager.REPOSITORY.isInstalled(platform, version)) + + if (JavaManager.REPOSITORY.isInstalled(platform, version)) { button.setDisable(true); + button.setSelected(true); + } else { + button.selectedProperty().addListener(updateValidStatus); + } + + vbox.getChildren().add(button); + checkboxes.add(button); } setBody(vbox); @@ -137,38 +155,52 @@ public final class JavaDownloadDialog extends StackPane { setActions(warningLabel, acceptPane, cancelButton); } - private Task downloadTask(GameJavaVersion javaVersion) { - return JavaManager.getDownloadJavaTask(downloadProvider, platform, javaVersion).whenComplete(Schedulers.javafx(), (result, exception) -> { - if (exception != null) { - Throwable resolvedException = resolveException(exception); - LOG.warning("Failed to download java", exception); - if (!(resolvedException instanceof CancellationException)) { - Controllers.dialog(DownloadProviders.localizeErrorMessage(resolvedException), i18n("install.failed")); - } - } - }); - } - @Override protected void onAccept() { fireEvent(new DialogCloseEvent()); - GameJavaVersion javaVersion = (GameJavaVersion) toggleGroup.getSelectedToggle().getUserData(); - if (javaVersion == null) + List selectedVersions = new ArrayList<>(); + for (JFXCheckBox box : checkboxes) { + if (!box.isDisabled() && box.isSelected()) { + selectedVersions.add((GameJavaVersion) box.getUserData()); + } + } + + if (selectedVersions.isEmpty()) return; - if (JavaManager.REPOSITORY.isInstalled(platform, javaVersion)) - Controllers.confirm(i18n("download.java.override"), null, () -> { - Controllers.taskDialog(Task.supplyAsync(() -> JavaManager.REPOSITORY.getJavaExecutable(platform, javaVersion)) - .thenComposeAsync(Schedulers.javafx(), realPath -> { - if (realPath != null) { - JavaManager.removeJava(realPath); + Task task = Task.allOf(selectedVersions.stream().map(javaVersion -> JavaManager.getDownloadJavaTask(downloadProvider, platform, javaVersion).wrapResult()).toList()) + .whenComplete(Schedulers.javafx(), (results, exception) -> { + Throwable exceptionToDisplay; + if (exception == null) { + List exceptions = results.stream() + .filter(Result::isFailure) + .map(Result::getException) + .map(Lang::resolveException) + .filter(it -> !(it instanceof CancellationException)) + .toList(); + if (!exceptions.isEmpty()) { + if (exceptions.size() == 1) { + exceptionToDisplay = exceptions.get(0); + } else { + exceptionToDisplay = new IOException("Failed to download Java"); + for (Throwable e : exceptions) + exceptionToDisplay.addSuppressed(e); } - return downloadTask(javaVersion); - }), i18n("download.java"), TaskCancellationAction.NORMAL); - }, null); - else - Controllers.taskDialog(downloadTask(javaVersion), i18n("download.java.process"), TaskCancellationAction.NORMAL); + } else { + exceptionToDisplay = null; + } + } else { + exceptionToDisplay = exception; + } + + if (exceptionToDisplay != null) { + LOG.warning("Failed to download java", exceptionToDisplay); + Controllers.dialog(DownloadProviders.localizeErrorMessage(exceptionToDisplay), i18n("install.failed")); + } + }); + + Controllers.taskDialog(task, i18n("download.java.process"), TaskCancellationAction.NORMAL); } } diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index ca9ea6d26..735bc1546 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -367,7 +367,6 @@ download.provider.balanced.desc=Balanced, but may not be the latest download.provider.mirror=From Mirror download.provider.mirror.desc=Fast, but may not be the latest download.java=Downloading Java -download.java.override=This Java version already exists. Do you want to uninstall and reinstall it? download.java.process=Java Download Process download.javafx=Downloading dependencies for the launcher... download.javafx.notes=We are currently downloading dependencies for HMCL from the Internet.\n\ diff --git a/HMCL/src/main/resources/assets/lang/I18N_ar.properties b/HMCL/src/main/resources/assets/lang/I18N_ar.properties index d0fe89098..82115801f 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ar.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ar.properties @@ -340,7 +340,6 @@ download.provider.official=من المصادر الرسمية download.provider.balanced=من الأسرع المتاح download.provider.mirror=من المرآة download.java=تنزيل Java -download.java.override=هذا الإصدار من Java موجود بالفعل. هل تريد إلغاء التثبيت وإعادة التثبيت؟ download.java.process=عملية تنزيل Java download.javafx=تنزيل التبعيات للمشغل... download.javafx.notes=نحن نقوم حالياً بتنزيل تبعيات HMCL من الإنترنت.\n\ diff --git a/HMCL/src/main/resources/assets/lang/I18N_es.properties b/HMCL/src/main/resources/assets/lang/I18N_es.properties index 0538f73b6..303b83ffe 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -345,7 +345,6 @@ download.provider.official=De fuentes oficiales download.provider.balanced=De la fuente más rápida disponible download.provider.mirror=Desde espejo download.java=Descargando Java -download.java.override=Esta versión de Java ya existe. ¿Desea desinstalarla y volver a instalarla? download.java.process=Proceso de descarga Java download.javafx=Descargando dependencias para el launcher... download.javafx.notes=Estamos descargando dependencias para HMCL desde Internet.\n\ diff --git a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties index dc2579160..a2de3bb4c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_lzh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_lzh.properties @@ -344,7 +344,6 @@ download.provider.balanced.desc=均,容有舊 download.provider.mirror=先別源 download.provider.mirror.desc=至快,容有舊 download.java=引爪哇 -download.java.override=爪哇是版既現,除而復裝諸? download.java.process=引爪哇 download.javafx=甫引行需諸案…… download.javafx.notes=方引 HMCL 所需諸案。\n擊「迭引源」以閲詳,并迭之。擊「罷」以止而退。\n誡:引之允滯,宜迭其源。 diff --git a/HMCL/src/main/resources/assets/lang/I18N_ru.properties b/HMCL/src/main/resources/assets/lang/I18N_ru.properties index 2156c65a5..e1a9fc57b 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ru.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ru.properties @@ -342,7 +342,6 @@ download.provider.official=Из официальных источников download.provider.balanced=Из самых быстрых доступных download.provider.mirror=Из зеркала download.java=Скачивание Java -download.java.override=Эта версия Java уже существует. Вы хотите удалить и установить ее заново? download.java.process=Процесс скачивания Java download.javafx=Скачивание зависимостей лаунчера... download.javafx.notes=Скачивание зависимостей для лаунчер из Интернета.\n\ diff --git a/HMCL/src/main/resources/assets/lang/I18N_uk.properties b/HMCL/src/main/resources/assets/lang/I18N_uk.properties index a5024eda7..06628a74d 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_uk.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_uk.properties @@ -341,7 +341,6 @@ download.provider.official=З офіційних джерел download.provider.balanced=З найшвидшого доступного download.provider.mirror=З дзеркала download.java=Завантаження Java -download.java.override=Ця версія Java вже існує. Бажаєте видалити та перевстановити її? download.java.process=Процес завантаження Java download.javafx=Завантаження залежностей для лаунчера... download.javafx.notes=Ми зараз завантажуємо залежності для HMCL з Інтернету. \n\ diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 163ccac5f..e5c472629 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -363,7 +363,6 @@ download.provider.balanced.desc=平衡,但可能不是最新 download.provider.mirror=盡量使用鏡像源 download.provider.mirror.desc=載入快,但可能不是最新 download.java=下載 Java -download.java.override=此 Java 版本已經存在,是否移除並重新安裝? download.java.process=下載 Java download.javafx=正在下載必要的執行時元件 download.javafx.notes=正在透過網路下載 HMCL 必要的執行時元件。\n點擊「切換下載源」按鈕查看詳情以及選取下載源。點擊「取消」按鈕停止並退出。\n注意:如果下載速度過慢,請嘗試切換下載源。 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 9eb2c2a5d..c676e1ce9 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -365,7 +365,6 @@ download.provider.balanced.desc=平衡,但可能不是最新 download.provider.mirror=尽量使用镜像源 download.provider.mirror.desc=加载快,但可能不是最新 download.java=下载 Java -download.java.override=此 Java 版本已经存在,是否卸载并重新安装? download.java.process=下载 Java download.javafx=正在下载必要的运行时组件…… download.javafx.notes=正在通过网络下载 HMCL 必要的运行时组件。\n点击“切换下载源”按钮查看详情以及选择下载源。点击“取消”按钮停止并退出。\n注意:若下载速度过慢,请尝试切换下载源。 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java index 5fb6a8b44..faa60fc40 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/task/Task.java @@ -22,6 +22,7 @@ import javafx.beans.property.DoubleProperty; import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import org.jackhuang.hmcl.event.EventManager; +import org.jackhuang.hmcl.util.Result; import org.jackhuang.hmcl.util.function.ExceptionalConsumer; import org.jackhuang.hmcl.util.function.ExceptionalFunction; import org.jackhuang.hmcl.util.function.ExceptionalRunnable; @@ -784,6 +785,37 @@ public abstract class Task { return whenComplete(executor, (exception -> action.execute(getResult(), exception))); } + public Task> wrapResult() { + return new Task>() { + { + setSignificance(TaskSignificance.MODERATE); + } + + @Override + public void execute() throws Exception { + if (isDependentsSucceeded() != (Task.this.getException() == null)) + throw new AssertionError("When whenComplete succeeded, Task.exception must be null.", Task.this.getException()); + + if (isDependentsSucceeded()) { + setResult(Result.success(Task.this.getResult())); + } else { + setSignificance(TaskSignificance.MINOR); + setResult(Result.failure(Task.this.getException())); + } + } + + @Override + public Collection> getDependents() { + return Collections.singleton(Task.this); + } + + @Override + public boolean isRelyingOnDependents() { + return false; + } + }.setExecutor(executor).setName(getCaller()).setSignificance(TaskSignificance.MODERATE); + } + /** * Returns a new Task with the same exception as this task, that executes * the given actions when this task completes. diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Result.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Result.java new file mode 100644 index 000000000..ee34e2996 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Result.java @@ -0,0 +1,78 @@ +/* + * Hello Minecraft! Launcher + * Copyright (C) 2026 huangyuhui 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 . + */ +package org.jackhuang.hmcl.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +/// @author Glavo +public final class Result { + public static Result success(T value) { + return new Result<>(value, null); + } + + public static Result failure(@NotNull Throwable exception) { + Objects.requireNonNull(exception); + return new Result<>(null, exception); + } + + private final T value; + private final Throwable exception; + + private Result(T value, Throwable exception) { + this.value = value; + this.exception = exception; + } + + public boolean isSuccess() { + return exception == null; + } + + public boolean isFailure() { + return exception != null; + } + + public T get() throws Throwable { + if (exception != null) + throw exception; + else + return value; + } + + public @Nullable T getOrNull() { + return exception == null ? value : null; + } + + public @Nullable Throwable getException() { + return exception; + } + + @Override + public int hashCode() { + return Objects.hash(value, exception); + } + + @Override + public boolean equals(Object obj) { + return this == obj || obj instanceof Result other + && Objects.equals(value, other.value) + && Objects.equals(exception, other.exception); + } +}