blob: 488e9106479d279150a6b5779fdd8a2bc5120450 [file]
/*
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jpackage.internal;
import jdk.jpackage.internal.model.ConfigException;
import java.io.File;
import java.io.IOException;
import java.lang.module.ModuleReference;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE;
/**
* Extracts data needed to run application from parameters.
*/
final class LauncherData {
boolean isModular() {
return moduleInfo != null;
}
String qualifiedClassName() {
return qualifiedClassName;
}
boolean isClassNameFromMainJar() {
return jarMainClass != null;
}
String packageName() {
int sepIdx = qualifiedClassName.lastIndexOf('.');
if (sepIdx < 0) {
return "";
}
return qualifiedClassName.substring(sepIdx + 1);
}
String moduleName() {
verifyIsModular(true);
return moduleInfo.name();
}
List<Path> modulePath() {
verifyIsModular(true);
return modulePath;
}
Path mainJarName() {
verifyIsModular(false);
return mainJarName;
}
List<Path> classPath() {
return classPath;
}
String getAppVersion() {
if (isModular()) {
return moduleInfo.version().orElse(null);
}
return null;
}
private LauncherData() {
}
private void verifyIsModular(boolean isModular) {
if ((moduleInfo == null) == isModular) {
throw new IllegalStateException();
}
}
static LauncherData create(Map<String, ? super Object> params) throws
ConfigException, IOException {
final String mainModule = getMainModule(params);
final LauncherData result;
if (mainModule == null) {
result = createNonModular(params);
} else {
result = createModular(mainModule, params);
}
result.initClasspath(params);
return result;
}
private static LauncherData createModular(String mainModule,
Map<String, ? super Object> params) throws ConfigException,
IOException {
LauncherData launcherData = new LauncherData();
final int sepIdx = mainModule.indexOf("/");
final String moduleName;
if (sepIdx > 0) {
launcherData.qualifiedClassName = mainModule.substring(sepIdx + 1);
moduleName = mainModule.substring(0, sepIdx);
} else {
moduleName = mainModule;
}
launcherData.modulePath = getModulePath(params);
// Try to find module in the specified module path list.
ModuleReference moduleRef = JLinkRuntimeBuilder.createModuleFinder(
launcherData.modulePath).find(moduleName).orElse(null);
if (moduleRef != null) {
launcherData.moduleInfo = ModuleInfo.fromModuleReference(moduleRef);
} else if (params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID())) {
// Failed to find module in the specified module path list and
// there is external runtime given to jpackage.
// Lookup module in this runtime.
Path cookedRuntime = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
launcherData.moduleInfo = ModuleInfo.fromCookedRuntime(moduleName,
cookedRuntime).orElse(null);
}
if (launcherData.moduleInfo == null) {
throw new ConfigException(MessageFormat.format(I18N.getString(
"error.no-module-in-path"), moduleName), null);
}
if (launcherData.qualifiedClassName == null) {
launcherData.qualifiedClassName = launcherData.moduleInfo.mainClass().orElse(null);
if (launcherData.qualifiedClassName == null) {
throw new ConfigException(I18N.getString("ERR_NoMainClass"), null);
}
}
return launcherData;
}
private static LauncherData createNonModular(
Map<String, ? super Object> params) throws ConfigException, IOException {
LauncherData launcherData = new LauncherData();
launcherData.qualifiedClassName = getMainClass(params);
launcherData.mainJarName = getMainJarName(params);
Path mainJarDir = StandardBundlerParam.SOURCE_DIR.fetchFrom(params);
final Path mainJarPath;
if (launcherData.mainJarName != null && mainJarDir != null) {
mainJarPath = mainJarDir.resolve(launcherData.mainJarName);
if (!Files.exists(mainJarPath)) {
throw new ConfigException(MessageFormat.format(I18N.getString(
"error.main-jar-does-not-exist"),
launcherData.mainJarName), I18N.getString(
"error.main-jar-does-not-exist.advice"));
}
} else {
mainJarPath = null;
}
if (launcherData.qualifiedClassName == null) {
if (mainJarPath == null) {
throw new ConfigException(I18N.getString("error.no-main-class"),
I18N.getString("error.no-main-class.advice"));
}
try (JarFile jf = new JarFile(mainJarPath.toFile())) {
Manifest m = jf.getManifest();
Attributes attrs = (m != null) ? m.getMainAttributes() : null;
if (attrs != null) {
launcherData.qualifiedClassName = attrs.getValue(
Attributes.Name.MAIN_CLASS);
launcherData.jarMainClass = launcherData.qualifiedClassName;
}
}
}
if (launcherData.qualifiedClassName == null) {
throw new ConfigException(MessageFormat.format(I18N.getString(
"error.no-main-class-with-main-jar"),
launcherData.mainJarName), MessageFormat.format(
I18N.getString(
"error.no-main-class-with-main-jar.advice"),
launcherData.mainJarName));
}
return launcherData;
}
private void initClasspath(Map<String, ? super Object> params)
throws IOException {
Path inputDir = StandardBundlerParam.SOURCE_DIR.fetchFrom(params);
if (inputDir == null) {
classPath = Collections.emptyList();
} else {
try (Stream<Path> walk = Files.walk(inputDir, Integer.MAX_VALUE)) {
Set<Path> jars = walk.filter(Files::isRegularFile)
.filter(file -> file.toString().endsWith(".jar"))
.map(p -> inputDir.toAbsolutePath()
.relativize(p.toAbsolutePath()))
.collect(Collectors.toSet());
jars.remove(mainJarName);
classPath = jars.stream().sorted().toList();
}
}
}
private static String getMainClass(Map<String, ? super Object> params) {
return getStringParam(params, Arguments.CLIOptions.APPCLASS.getId());
}
private static Path getMainJarName(Map<String, ? super Object> params)
throws ConfigException {
return getPathParam(params, Arguments.CLIOptions.MAIN_JAR.getId());
}
private static String getMainModule(Map<String, ? super Object> params) {
return getStringParam(params, Arguments.CLIOptions.MODULE.getId());
}
private static String getStringParam(Map<String, ? super Object> params,
String paramName) {
Optional<Object> value = Optional.ofNullable(params.get(paramName));
return value.map(Object::toString).orElse(null);
}
private static <T> T getPathParam(String paramName, Supplier<T> func) throws ConfigException {
try {
return func.get();
} catch (InvalidPathException ex) {
throw new ConfigException(MessageFormat.format(I18N.getString(
"error.not-path-parameter"), paramName,
ex.getLocalizedMessage()), null, ex);
}
}
private static Path getPathParam(Map<String, ? super Object> params,
String paramName) throws ConfigException {
return getPathParam(paramName, () -> {
String value = getStringParam(params, paramName);
Path result = null;
if (value != null) {
result = Path.of(value);
}
return result;
});
}
private static List<Path> getModulePath(Map<String, ? super Object> params)
throws ConfigException {
List<Path> modulePath = getPathListParameter(Arguments.CLIOptions.MODULE_PATH.getId(), params);
if (params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID())) {
Path runtimePath = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
runtimePath = runtimePath.resolve("lib");
modulePath = Stream.of(modulePath, List.of(runtimePath))
.flatMap(List::stream)
.toList();
}
return modulePath;
}
private static List<Path> getPathListParameter(String paramName,
Map<String, ? super Object> params) throws ConfigException {
return getPathParam(paramName, () ->
params.get(paramName) instanceof String value ?
Stream.of(value.split(File.pathSeparator)).map(Path::of).toList() : List.of());
}
private String qualifiedClassName;
private String jarMainClass;
private Path mainJarName;
private List<Path> classPath;
private List<Path> modulePath;
private ModuleInfo moduleInfo;
}