Compare commits

...
3 Commits
13 changed files with 64 additions and 315 deletions

No files matched your search

@@ -227,29 +227,50 @@ public final class LauncherHelper {
flag = true;
}
if (!flag && java.getParsedVersion() < JavaVersion.JAVA_8) {
if (gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0) {
// Minecraft 1.13 and later versions only support Java 8 or later.
// Terminate launching operation.
Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
// 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()
.filter(javaVersion -> javaVersion.getParsedVersion() >= JavaVersion.JAVA_8)
.max(Comparator.comparing(JavaVersion::getVersionNumber));
if (java8.isPresent()) {
setting.setJavaVersion(java8.get());
} else {
// Most mods require Java 8 or later version.
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
if (gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0) {
// Minecraft 1.13 and later versions only support Java 8 or later.
// Terminate launching operation.
Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
} else {
// Most mods require Java 8 or later version.
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
}
flag = true;
}
flag = true;
}
// LaunchWrapper will crash because of assuming the system class loader is an instance of URLClassLoader.
// cpw has claimed that he will make MinecraftForge of 1.13 and later versions able to run on Java 9.
if (!flag && java.getParsedVersion() >= JavaVersion.JAVA_9 && gameVersion.compareTo(VersionNumber.asVersion("1.12.5")) < 0 && version.getMainClass().contains("launchwrapper")) {
if (!flag && java.getParsedVersion() >= JavaVersion.JAVA_9 && gameVersion.compareTo(VersionNumber.asVersion("1.13")) < 0 && version.getMainClass().contains("launchwrapper")) {
Optional<JavaVersion> java8 = JavaVersion.getJREs().stream().filter(javaVersion -> javaVersion.getParsedVersion() == JavaVersion.JAVA_8).findAny();
if (java8.isPresent()) {
setting.setJavaVersion(java8.get());
Controllers.dialog(i18n("launch.advice.java9"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
} else {
Controllers.dialog(i18n("launch.advice.java9"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
flag = true;
}
}
// 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()
.filter(javaVersion -> javaVersion.getVersionNumber().compareTo(JAVA_8) >= 0)
.max(Comparator.comparing(JavaVersion::getVersionNumber));
if (java8.isPresent()) {
setting.setJavaVersion(java8.get());
} else {
Controllers.dialog(i18n("launch.advice.java8_51_1_13"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
flag = true;
}
flag = true;
}
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
@@ -257,6 +278,7 @@ public final class LauncherHelper {
Controllers.dialog(i18n("launch.advice.different_platform"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
flag = true;
}
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
setting.getMaxMemory() > 1.5 * 1024) {
// 1.5 * 1024 is an inaccurate number.
@@ -264,6 +286,7 @@ public final class LauncherHelper {
Controllers.dialog(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
flag = true;
}
if (!flag && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
Controllers.dialog(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
flag = true;
@@ -47,6 +47,7 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
private final DownloadProvider downloadProvider;
private final String libraryId;
private final String title;
private final Runnable callback;
@FXML
private JFXListView<VersionsPageItem> list;
@@ -55,6 +56,8 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
@FXML
private StackPane failedPane;
@FXML
private StackPane emptyPane;
@FXML
private JFXCheckBox chkRelease;
@FXML
private JFXCheckBox chkSnapshot;
@@ -74,6 +77,7 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
this.gameVersion = gameVersion;
this.downloadProvider = downloadProvider;
this.libraryId = libraryId;
this.callback = callback;
this.versionList = downloadProvider.getVersionListById(libraryId);
FXUtils.loadFXML(this, "/assets/fxml/download/versions.fxml");
@@ -123,8 +127,12 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
List<VersionsPageItem> items = loadVersions();
Platform.runLater(() -> {
list.getItems().setAll(items);
transitionHandler.setContent(centrePane, ContainerAnimations.FADE.getAnimationProducer());
if (items.isEmpty()) {
transitionHandler.setContent(emptyPane, ContainerAnimations.FADE.getAnimationProducer());
} else {
list.getItems().setAll(items);
transitionHandler.setContent(centrePane, ContainerAnimations.FADE.getAnimationProducer());
}
});
} else {
Platform.runLater(() -> {
@@ -150,4 +158,7 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
private void onRefresh() {
refresh();
}
@FXML
private void onBack() { callback.run(); }
}
@@ -20,4 +20,7 @@
<StackPane fx:id="failedPane">
<Label onMouseClicked="#onRefresh" style="-fx-text-fill: #0079FF; -fx-font-size: 20;" text="%download.failed.refresh" />
</StackPane>
<StackPane fx:id="emptyPane">
<Label onMouseClicked="#onBack" style="-fx-text-fill: #0079FF; -fx-font-size: 20;" text="%download.failed.empty" />
</StackPane>
</fx:root>
@@ -83,6 +83,7 @@ crash.user_fault=Your OS or Java environment may not be properly installed resul
download=Download
download.BMCL=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
download.failed=Failed to download
download.failed.empty=No candidates. Click here to return.
download.failed.refresh=Unable to load version list. Click here to retry.
download.mojang=Mojang
@@ -134,6 +135,7 @@ lang.default=Use system language
launch.advice.different_platform=Your OS is 64-Bit but your Java is 32-Bit. The 64-Bit Java is recommended.
launch.advice.java8_1_13=Minecraft 1.13 and later versions can only run on Java 8 or later.
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 earlier than 1.8.0_51. You are supposed to install the latest version of Java 8.
launch.advice.java9=You cannot launch Minecraft until game version is higher than 1.13 with Java 9 or later Java versions.
launch.advice.newer_java=Java 8 is suggested, which can make game run more fluently. And many mods and Minecraft 1.12 and newer versions requires Java 8.
launch.advice.not_enough_space=You have allocated too much memory, because the physical memory size is %dMB, your game probably crash. The launcher will try to launch it.
@@ -83,6 +83,7 @@ crash.user_fault=您的系統或 Java 環境可能安裝不當導致本軟件崩
download=下載
download.BMCL=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
download.failed=下載失敗
download.failed.empty=沒有可供安裝的版本,點擊此處返回。
download.failed.refresh=加載版本列表失敗,點擊此處重試。
download.mojang=官方
@@ -134,6 +135,7 @@ lang.default=跟隨系統語言
launch.advice.different_platform=您的系統是 64 位的但是 Java 是 32 位的,推薦您安裝 64 位 Java。
launch.advice.java8_1_13=Minecraft 1.13 只支持 Java 8 或更高版本,請使用 Java 8 或最新版本。
launch.advice.java8_51_1_13=舊於 1.8.0_51 的 Java 可能會導致 Minecraft 1.13 崩潰。建議您到 https://java.com 安裝最新版的Java 8。
launch.advice.java9=不高於 1.13 的安裝了 Mod 的 Minecraft 不支持 Java 9 或更高版本,請使用 Java 8。
launch.advice.newer_java=檢測到您未使用 Java 8 及更新版本,Java 8 能使遊戲更流暢而且 Minecraft 1.12 及更新版本和很多 Mod 強制需要需要 Java 8。
launch.advice.not_enough_space=您設置的內存大小過大,由於超過了系統內存大小 %dMB,所以可能影響遊戲體驗或無法啓動遊戲,啓動器仍會嘗試啓動。
@@ -83,6 +83,7 @@ crash.user_fault=您的系统或 Java 环境可能安装不当导致本软件崩
download=下载
download.BMCL=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
download.failed=下载失败
download.failed.empty=没有可供安装的版本,点击此处返回。
download.failed.refresh=加载版本列表失败,点击此处重试。
download.mojang=官方
@@ -134,6 +135,7 @@ lang.default=跟随系统语言
launch.advice.different_platform=您的系统是 64 位的但是 Java 是 32 位的,推荐您安装 64 位 Java。
launch.advice.java8_1_13=Minecraft 1.13 只支持 Java 8 或更高版本,请使用 Java 8 或最新版本。
launch.advice.java8_51_1_13=旧于 1.8.0_51 的 Java 可能会导致 Minecraft 1.13 崩溃。建议您到 https://java.com 安装最新版的 Java 8。
launch.advice.java9=不高于 1.13 的安装了 Mod 的 Minecraft 不支持 Java 9 或更高版本,请使用 Java 8。
launch.advice.newer_java=检测到您未使用 Java 8 及更新版本,Java 8 能使游戏更流畅而且 Minecraft 1.12 及更新版本和很多 Mod 强制需要需要 Java 8。
launch.advice.not_enough_space=您设置的内存大小过大,由于超过了系统内存大小 %dMB,所以可能影响游戏体验或无法启动游戏,启动器仍会尝试启动。
@@ -65,7 +65,6 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
public Task checkGameCompletionAsync(Version version) {
return new ParallelTask(
new GameAssetDownloadTask(this, version),
new GameLoggingDownloadTask(this, version),
new GameLibrariesTask(this, version)
);
}
@@ -55,7 +55,6 @@ public class DefaultGameBuilder extends GameBuilder {
variables.set("version", version);
Task result = new ParallelTask(
new GameAssetDownloadTask(dependencyManager, version),
new GameLoggingDownloadTask(dependencyManager, version),
downloadGameAsync(gameVersion, version),
new GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries.
).with(new VersionJsonSaveTask(dependencyManager.getGameRepository(), version)); // using [with] because download failure here are tolerant.
@@ -73,8 +73,7 @@ public abstract class VersionList<T extends RemoteVersion> {
private Collection<T> getVersionsImpl(String gameVersion) {
lock.readLock().lock();
try {
Collection<T> ans = versions.get(gameVersion);
return ans.isEmpty() ? versions.values() : ans;
return versions.get(gameVersion);
} finally {
lock.readLock().unlock();
}
@@ -1,72 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* 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 {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.game;
import org.jackhuang.hmcl.download.DependencyManager;
import org.jackhuang.hmcl.game.DownloadType;
import org.jackhuang.hmcl.game.LoggingInfo;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.NetworkUtils;
import java.io.File;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* This task is to download log4j configuration file provided in minecraft.json.
*
* @author huangyuhui
*/
public final class GameLoggingDownloadTask extends Task {
private final DependencyManager dependencyManager;
private final Version version;
private final List<Task> dependencies = new LinkedList<>();
/**
* Constructor.
*
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
* @param version the <b>resolved</b> version
*/
public GameLoggingDownloadTask(DependencyManager dependencyManager, Version version) {
this.dependencyManager = dependencyManager;
this.version = version;
setSignificance(TaskSignificance.MODERATE);
}
@Override
public Collection<Task> getDependencies() {
return dependencies;
}
@Override
public void execute() {
if (version.getLogging() == null || !version.getLogging().containsKey(DownloadType.CLIENT))
return;
LoggingInfo logging = version.getLogging().get(DownloadType.CLIENT);
File file = dependencyManager.getGameRepository().getLoggingObject(version.getId(), version.getAssetIndex().getId(), logging);
if (!file.exists())
dependencies.add(new FileDownloadTask(NetworkUtils.toURL(logging.getFile().getUrl()), file));
}
}
@@ -47,7 +47,7 @@ public class DefaultLauncher extends Launcher {
super(repository, versionId, authInfo, options, listener, daemon);
}
private CommandBuilder generateCommandLine(File nativeFolder, boolean enableLoggingInfo) throws IOException {
private CommandBuilder generateCommandLine(File nativeFolder) throws IOException {
CommandBuilder res = new CommandBuilder();
// Executable
@@ -70,16 +70,6 @@ public class DefaultLauncher extends Launcher {
res.add("-Xdock:icon=" + repository.getAssetObject(version.getId(), version.getAssetIndex().getId(), "icons/minecraft.icns").getAbsolutePath());
}
Map<DownloadType, LoggingInfo> logging = version.getLogging();
if (logging != null && enableLoggingInfo) {
LoggingInfo loggingInfo = logging.get(DownloadType.CLIENT);
if (loggingInfo != null) {
File loggingFile = repository.getLoggingObject(version.getId(), version.getAssetIndex().getId(), loggingInfo);
if (loggingFile.exists())
res.add(loggingInfo.getArgument().replace("${path}", loggingFile.getAbsolutePath()));
}
}
if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS)
res.add("-Duser.home=" + options.getGameDir().getParent());
@@ -252,7 +242,7 @@ public class DefaultLauncher extends Launcher {
File nativeFolder = Files.createTempDirectory("minecraft").toFile();
// To guarantee that when failed to generate launch command line, we will not call pre-launch command
List<String> rawCommandLine = generateCommandLine(nativeFolder, isEnablingLoggingInfo()).asList();
List<String> rawCommandLine = generateCommandLine(nativeFolder).asList();
decompressNatives(nativeFolder);
@@ -304,50 +294,17 @@ public class DefaultLauncher extends Launcher {
writer.write(options.getPreLaunchCommand());
writer.newLine();
}
writer.write(generateCommandLine(nativeFolder, false).toString());
writer.write(generateCommandLine(nativeFolder).toString());
}
if (!scriptFile.setExecutable(true))
throw new PermissionException();
}
protected boolean isEnablingLoggingInfo() {
return version.getLogging() != null && version.getLogging().containsKey(DownloadType.CLIENT)
&& !"net.minecraft.launchwrapper.Launch".equals(version.getMainClass());
}
private void startMonitors(ManagedProcess managedProcess, ProcessListener processListener) {
startMonitors(managedProcess, processListener, true);
}
private void startMonitors(ManagedProcess managedProcess, ProcessListener processListener, boolean isDaemon) {
if (isEnablingLoggingInfo())
startMonitorsWithLoggingInfo(managedProcess, processListener, isDaemon);
else
startMonitorsWithoutLoggingInfo(managedProcess, processListener, isDaemon);
}
private void startMonitorsWithLoggingInfo(ManagedProcess managedProcess, ProcessListener processListener, boolean isDaemon) {
processListener.setProcess(managedProcess);
Log4jHandler logHandler = new Log4jHandler((line, level) -> {
processListener.onLog(line, level);
managedProcess.addLine(line);
});
logHandler.start();
managedProcess.addRelatedThread(logHandler);
Thread stdout = Lang.thread(new StreamPump(managedProcess.getProcess().getInputStream(), logHandler::newLine), "stdout-pump", isDaemon);
managedProcess.addRelatedThread(stdout);
Thread stderr = Lang.thread(new StreamPump(managedProcess.getProcess().getErrorStream(), it -> {
processListener.onLog(it + OperatingSystem.LINE_SEPARATOR, Log4jLevel.ERROR);
managedProcess.addLine(it);
}), "stderr-pump", isDaemon);
managedProcess.addRelatedThread(stderr);
managedProcess.addRelatedThread(Lang.thread(new ExitWaiter(managedProcess, Arrays.asList(stdout, stderr), (exitCode, exitType) -> {
logHandler.onStopped();
processListener.onExit(exitCode, exitType);
}), "exit-waiter", isDaemon));
}
private void startMonitorsWithoutLoggingInfo(ManagedProcess managedProcess, ProcessListener processListener, boolean isDaemon) {
processListener.setProcess(managedProcess);
Thread stdout = Lang.thread(new StreamPump(managedProcess.getProcess().getInputStream(), it -> {
processListener.onLog(it + OperatingSystem.LINE_SEPARATOR, Optional.ofNullable(Log4jLevel.guessLevel(it)).orElse(Log4jLevel.INFO));
@@ -1,180 +0,0 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <[email protected]>
*
* 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 {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.launch;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.util.*;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer;
import java.util.logging.Level;
/**
* This class is to parse log4j classic XML layout logging,
* since only vanilla Minecraft will enable this layout.
*
* Also supports plain logs.
*
* @author huangyuhui
*/
final class Log4jHandler extends Thread {
private final XMLReader reader;
private final BiConsumer<String, Log4jLevel> callback;
private final PipedOutputStream outputStream = new PipedOutputStream();
private final PipedInputStream inputStream = Lang.invoke(() -> new PipedInputStream(outputStream));
private final AtomicBoolean interrupted = new AtomicBoolean(false);
private final List<String> logs = new LinkedList<>();
private boolean broken = false;
public Log4jHandler(BiConsumer<String, Log4jLevel> callback) {
this.callback = callback;
newLine("<output>");
reader = Lang.invoke((ExceptionalSupplier<XMLReader, SAXException>) XMLReaderFactory::createXMLReader);
reader.setContentHandler(new Log4jHandlerImpl());
}
@Override
public void run() {
setName("log4j-handler");
try {
InputSource inputSource = new InputSource(inputStream);
reader.parse(inputSource);
} catch (InterruptedIOException e) {
// Game has been interrupted.
interrupted.set(true);
} catch (SAXException | IOException e) {
Logging.LOG.log(Level.WARNING, "An error occurred when reading console lines", e);
}
}
public void onStopped() {
if (interrupted.get())
return;
Lang.invoke(() -> Schedulers.newThread().schedule(() -> {
if (!interrupted.get()) {
newLine("</output>").get();
outputStream.close();
join();
}
}).get());
}
public Future<?> newLine(String log) {
return Schedulers.computation().schedule(() -> {
try {
// Prevent from namespace parsing
String line = log.replace("log4j:", "log4j_");
logs.add(line);
if (broken)
System.out.println(line);
byte[] bytes = (line + OperatingSystem.LINE_SEPARATOR).getBytes(Constants.SYSTEM_CHARSET);
outputStream.write(bytes);
outputStream.flush();
} catch (IOException e) {
// Ignoring IOException, including read end dead.
if (!broken) {
Logging.LOG.log(Level.WARNING, "An error occurred when writing console lines", e);
logs.forEach(System.out::println);
broken = true;
} else {
// Output plain XML to user
callback.accept(log, Log4jLevel.INFO);
}
}
});
}
private class Log4jHandlerImpl extends DefaultHandler {
private final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
private String date = "", thread = "", logger = "";
private StringBuilder message;
private Log4jLevel level;
private boolean readingMessage = false;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) {
switch (localName) {
case "log4j_Event":
message = new StringBuilder();
Date d = new Date(Long.parseLong(attributes.getValue("timestamp")));
date = format.format(d);
try {
level = Log4jLevel.valueOf(attributes.getValue("level"));
} catch (IllegalArgumentException e) {
level = Log4jLevel.INFO;
}
thread = attributes.getValue("thread");
logger = attributes.getValue("logger");
if ("STDERR".equals(logger))
level = Log4jLevel.ERROR;
break;
case "log4j_Message":
readingMessage = true;
break;
case "log4j_Throwable":
break;
}
}
@Override
public void endElement(String uri, String localName, String qName) {
switch (localName) {
case "log4j_Event":
callback.accept("[" + date + "] [" + thread + "/" + level.name() + "] [" + logger + "] " + message.toString(), level);
break;
case "log4j_Message":
readingMessage = false;
break;
}
}
@Override
public void characters(char[] ch, int start, int length) {
String line = new String(ch, start, length);
if (line.trim().isEmpty())
return;
if (readingMessage)
message.append(line).append(OperatingSystem.LINE_SEPARATOR);
else
callback.accept(line, Optional.ofNullable(Log4jLevel.guessLevel(line)).orElse(Log4jLevel.INFO));
}
}
}
@@ -63,6 +63,10 @@ public final class JavaVersion {
return platform;
}
public VersionNumber getVersionNumber() {
return VersionNumber.asVersion(longVersion.replace('_', '.'));
}
/**
* The major version of Java installation.
*