package jdk.incubator.jpackage.internal;
import java.io.File;
import java.io.IOException;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Version;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.HashSet;
import java.util.function.BiFunction;
import java.util.function.Function;
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;
class StandardBundlerParam<T> extends BundlerParamInfo<T> {
private static final String JAVABASEJMOD = "java.base.jmod";
private final static String DEFAULT_VERSION = "1.0";
private final static String DEFAULT_RELEASE = "1";
private final static String[] DEFAULT_JLINK_OPTIONS = {
"--strip-native-commands",
"--strip-debug",
"--no-man-pages",
"--no-header-files"};
StandardBundlerParam(String id, Class<T> valueType,
Function<Map<String, ? super Object>, T> defaultValueFunction,
BiFunction<String, Map<String, ? super Object>, T> stringConverter)
{
this.id = id;
this.valueType = valueType;
this.defaultValueFunction = defaultValueFunction;
this.stringConverter = stringConverter;
}
static final StandardBundlerParam<LauncherData> LAUNCHER_DATA =
new StandardBundlerParam<>(
"launcherData",
LauncherData.class,
params -> {
try {
return LauncherData.create(params);
} catch (ConfigException | IOException ex) {
throw new RuntimeException(ex);
}
},
null
);
static final StandardBundlerParam<Path> SOURCE_DIR =
new StandardBundlerParam<>(
Arguments.CLIOptions.INPUT.getId(),
Path.class,
p -> null,
(s, p) -> Path.of(s)
);
static final StandardBundlerParam<Path> MAIN_JAR =
new StandardBundlerParam<>(
Arguments.CLIOptions.MAIN_JAR.getId(),
Path.class,
params -> LAUNCHER_DATA.fetchFrom(params).mainJarName(),
null
);
static final StandardBundlerParam<String> MAIN_CLASS =
new StandardBundlerParam<>(
Arguments.CLIOptions.APPCLASS.getId(),
String.class,
params -> {
if (isRuntimeInstaller(params)) {
return null;
}
return LAUNCHER_DATA.fetchFrom(params).qualifiedClassName();
},
(s, p) -> s
);
static final StandardBundlerParam<File> PREDEFINED_RUNTIME_IMAGE =
new StandardBundlerParam<>(
Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(),
File.class,
params -> null,
(s, p) -> new File(s)
);
static final StandardBundlerParam<String> APP_NAME =
new StandardBundlerParam<>(
Arguments.CLIOptions.NAME.getId(),
String.class,
params -> {
String s = MAIN_CLASS.fetchFrom(params);
if (s != null) {
int idx = s.lastIndexOf(".");
if (idx >= 0) {
return s.substring(idx+1);
}
return s;
} else if (isRuntimeInstaller(params)) {
File f = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
if (f != null) {
return f.getName();
}
}
return null;
},
(s, p) -> s
);
static final StandardBundlerParam<File> ICON =
new StandardBundlerParam<>(
Arguments.CLIOptions.ICON.getId(),
File.class,
params -> null,
(s, p) -> new File(s)
);
static final StandardBundlerParam<String> VENDOR =
new StandardBundlerParam<>(
Arguments.CLIOptions.VENDOR.getId(),
String.class,
params -> I18N.getString("param.vendor.default"),
(s, p) -> s
);
static final StandardBundlerParam<String> DESCRIPTION =
new StandardBundlerParam<>(
Arguments.CLIOptions.DESCRIPTION.getId(),
String.class,
params -> params.containsKey(APP_NAME.getID())
? APP_NAME.fetchFrom(params)
: I18N.getString("param.description.default"),
(s, p) -> s
);
static final StandardBundlerParam<String> COPYRIGHT =
new StandardBundlerParam<>(
Arguments.CLIOptions.COPYRIGHT.getId(),
String.class,
params -> MessageFormat.format(I18N.getString(
"param.copyright.default"), new Date()),
(s, p) -> s
);
@SuppressWarnings("unchecked")
static final StandardBundlerParam<List<String>> ARGUMENTS =
new StandardBundlerParam<>(
Arguments.CLIOptions.ARGUMENTS.getId(),
(Class<List<String>>) (Object) List.class,
params -> Collections.emptyList(),
(s, p) -> null
);
@SuppressWarnings("unchecked")
static final StandardBundlerParam<List<String>> JAVA_OPTIONS =
new StandardBundlerParam<>(
Arguments.CLIOptions.JAVA_OPTIONS.getId(),
(Class<List<String>>) (Object) List.class,
params -> Collections.emptyList(),
(s, p) -> Arrays.asList(s.split("\n\n"))
);
static final StandardBundlerParam<String> VERSION =
new StandardBundlerParam<>(
Arguments.CLIOptions.VERSION.getId(),
String.class,
StandardBundlerParam::getDefaultAppVersion,
(s, p) -> s
);
static final StandardBundlerParam<String> RELEASE =
new StandardBundlerParam<>(
Arguments.CLIOptions.RELEASE.getId(),
String.class,
params -> DEFAULT_RELEASE,
(s, p) -> s
);
@SuppressWarnings("unchecked")
public static final StandardBundlerParam<String> LICENSE_FILE =
new StandardBundlerParam<>(
Arguments.CLIOptions.LICENSE_FILE.getId(),
String.class,
params -> null,
(s, p) -> s
);
static final StandardBundlerParam<File> TEMP_ROOT =
new StandardBundlerParam<>(
Arguments.CLIOptions.TEMP_ROOT.getId(),
File.class,
params -> {
try {
return Files.createTempDirectory(
"jdk.incubator.jpackage").toFile();
} catch (IOException ioe) {
return null;
}
},
(s, p) -> new File(s)
);
public static final StandardBundlerParam<File> CONFIG_ROOT =
new StandardBundlerParam<>(
"configRoot",
File.class,
params -> {
File root =
new File(TEMP_ROOT.fetchFrom(params), "config");
root.mkdirs();
return root;
},
(s, p) -> null
);
static final StandardBundlerParam<Boolean> BIND_SERVICES =
new StandardBundlerParam<>(
Arguments.CLIOptions.BIND_SERVICES.getId(),
Boolean.class,
params -> false,
(s, p) -> (s == null || "null".equalsIgnoreCase(s)) ?
true : Boolean.valueOf(s)
);
static final StandardBundlerParam<Boolean> VERBOSE =
new StandardBundlerParam<>(
Arguments.CLIOptions.VERBOSE.getId(),
Boolean.class,
params -> false,
(s, p) -> (s == null || "null".equalsIgnoreCase(s)) ?
true : Boolean.valueOf(s)
);
static final StandardBundlerParam<File> RESOURCE_DIR =
new StandardBundlerParam<>(
Arguments.CLIOptions.RESOURCE_DIR.getId(),
File.class,
params -> null,
(s, p) -> new File(s)
);
static final BundlerParamInfo<String> INSTALL_DIR =
new StandardBundlerParam<>(
Arguments.CLIOptions.INSTALL_DIR.getId(),
String.class,
params -> null,
(s, p) -> s
);
static final StandardBundlerParam<File> PREDEFINED_APP_IMAGE =
new StandardBundlerParam<>(
Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId(),
File.class,
params -> null,
(s, p) -> new File(s));
@SuppressWarnings("unchecked")
static final StandardBundlerParam<List<Map<String, ? super Object>>> ADD_LAUNCHERS =
new StandardBundlerParam<>(
Arguments.CLIOptions.ADD_LAUNCHER.getId(),
(Class<List<Map<String, ? super Object>>>) (Object)
List.class,
params -> new ArrayList<>(1),
(s, p) -> null
);
@SuppressWarnings("unchecked")
static final StandardBundlerParam
<List<Map<String, ? super Object>>> FILE_ASSOCIATIONS =
new StandardBundlerParam<>(
Arguments.CLIOptions.FILE_ASSOCIATIONS.getId(),
(Class<List<Map<String, ? super Object>>>) (Object)
List.class,
params -> new ArrayList<>(1),
(s, p) -> null
);
@SuppressWarnings("unchecked")
static final StandardBundlerParam<List<String>> FA_EXTENSIONS =
new StandardBundlerParam<>(
"fileAssociation.extension",
(Class<List<String>>) (Object) List.class,
params -> null,
(s, p) -> Arrays.asList(s.split("(,|\\s)+"))
);
@SuppressWarnings("unchecked")
static final StandardBundlerParam<List<String>> FA_CONTENT_TYPE =
new StandardBundlerParam<>(
"fileAssociation.contentType",
(Class<List<String>>) (Object) List.class,
params -> null,
(s, p) -> Arrays.asList(s.split("(,|\\s)+"))
);
static final StandardBundlerParam<String> FA_DESCRIPTION =
new StandardBundlerParam<>(
"fileAssociation.description",
String.class,
params -> APP_NAME.fetchFrom(params) + " File",
null
);
static final StandardBundlerParam<File> FA_ICON =
new StandardBundlerParam<>(
"fileAssociation.icon",
File.class,
ICON::fetchFrom,
(s, p) -> new File(s)
);
@SuppressWarnings("unchecked")
static final BundlerParamInfo<List<Path>> MODULE_PATH =
new StandardBundlerParam<>(
Arguments.CLIOptions.MODULE_PATH.getId(),
(Class<List<Path>>) (Object)List.class,
p -> getDefaultModulePath(),
(s, p) -> {
List<Path> modulePath = Stream.of(s.split(File.pathSeparator))
.map(Path::of)
.collect(Collectors.toList());
Path javaBasePath = findPathOfModule(modulePath, JAVABASEJMOD);
if (javaBasePath == null) {
List<Path> jdkModulePath = getDefaultModulePath();
if (jdkModulePath != null) {
modulePath.addAll(jdkModulePath);
javaBasePath = findPathOfModule(modulePath, JAVABASEJMOD);
}
}
if (javaBasePath == null ||
!Files.exists(javaBasePath)) {
Log.error(String.format(I18N.getString(
"warning.no.jdk.modules.found")));
}
return modulePath;
});
private static Path findPathOfModule( List<Path> modulePath, String moduleName) {
for (Path path : modulePath) {
Path moduleNamePath = path.resolve(moduleName);
if (Files.exists(moduleNamePath)) {
return path;
}
}
return null;
}
static final BundlerParamInfo<String> MODULE =
new StandardBundlerParam<>(
Arguments.CLIOptions.MODULE.getId(),
String.class,
p -> null,
(s, p) -> {
return String.valueOf(s);
});
@SuppressWarnings("unchecked")
static final BundlerParamInfo<Set<String>> ADD_MODULES =
new StandardBundlerParam<>(
Arguments.CLIOptions.ADD_MODULES.getId(),
(Class<Set<String>>) (Object) Set.class,
p -> new LinkedHashSet<String>(),
(s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(",")))
);
@SuppressWarnings("unchecked")
static final StandardBundlerParam<List<String>> JLINK_OPTIONS =
new StandardBundlerParam<>(
Arguments.CLIOptions.JLINK_OPTIONS.getId(),
(Class<List<String>>) (Object) List.class,
p -> Arrays.asList(DEFAULT_JLINK_OPTIONS),
(s, p) -> null);
@SuppressWarnings("unchecked")
static final BundlerParamInfo<Set<String>> LIMIT_MODULES =
new StandardBundlerParam<>(
"limit-modules",
(Class<Set<String>>) (Object) Set.class,
p -> new LinkedHashSet<String>(),
(s, p) -> new LinkedHashSet<>(Arrays.asList(s.split(",")))
);
static boolean isRuntimeInstaller(Map<String, ? super Object> params) {
if (params.containsKey(MODULE.getID()) ||
params.containsKey(MAIN_JAR.getID()) ||
params.containsKey(PREDEFINED_APP_IMAGE.getID())) {
return false;
}
return params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID());
}
static File getPredefinedAppImage(Map<String, ? super Object> params) {
File applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(params);
if (applicationImage != null && !applicationImage.exists()) {
throw new RuntimeException(
MessageFormat.format(I18N.getString(
"message.app-image-dir-does-not-exist"),
PREDEFINED_APP_IMAGE.getID(),
applicationImage.toString()));
}
return applicationImage;
}
static void copyPredefinedRuntimeImage(Map<String, ? super Object> params,
ApplicationLayout appLayout) throws IOException, ConfigException {
File topImage = PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
if (!topImage.exists()) {
throw new ConfigException(
MessageFormat.format(I18N.getString(
"message.runtime-image-dir-does-not-exist"),
PREDEFINED_RUNTIME_IMAGE.getID(),
topImage.toString()),
MessageFormat.format(I18N.getString(
"message.runtime-image-dir-does-not-exist.advice"),
PREDEFINED_RUNTIME_IMAGE.getID()));
}
if (Platform.isMac()) {
Path runtimeHome = topImage.toPath().resolve("Contents/Home");
if (Files.isDirectory(runtimeHome)) {
topImage = runtimeHome.toFile();
}
}
final List<String> excludes = Arrays.asList("jmods", "src.zip");
IOUtils.copyRecursive(topImage.toPath(),
appLayout.runtimeHomeDirectory(), excludes);
List<Path> modulePath =
StandardBundlerParam.MODULE_PATH.fetchFrom(params);
List<Path> defaultModulePath = getDefaultModulePath();
Path dest = appLayout.appModsDirectory();
if (dest != null) {
for (Path mp : modulePath) {
if (!defaultModulePath.contains(mp)) {
Files.createDirectories(dest);
IOUtils.copyRecursive(mp, dest);
}
}
}
}
private static List<Path> getDefaultModulePath() {
return List.of(
Path.of(System.getProperty("java.home"), "jmods").toAbsolutePath());
}
private static String getDefaultAppVersion(Map<String, ? super Object> params) {
String appVersion = DEFAULT_VERSION;
if (isRuntimeInstaller(params)) {
return appVersion;
}
LauncherData launcherData = null;
try {
launcherData = LAUNCHER_DATA.fetchFrom(params);
} catch (RuntimeException ex) {
if (ex.getCause() instanceof ConfigException) {
return appVersion;
}
throw ex;
}
if (launcherData.isModular()) {
String moduleVersion = launcherData.getAppVersion();
if (moduleVersion != null) {
Log.verbose(MessageFormat.format(I18N.getString(
"message.module-version"),
moduleVersion,
launcherData.moduleName()));
appVersion = moduleVersion;
}
}
return appVersion;
}
}