8231862: Decouple DesktopIntegration and LinuxPackageBundler classes
Submitted-by: asemenyuk
Reviewed-by: aherrick, almatvee
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/DesktopIntegration.java Wed Oct 16 09:57:23 2019 -0400
@@ -0,0 +1,478 @@
+/*
+ * Copyright (c) 2019, 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 java.awt.image.BufferedImage;
+import java.io.*;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.imageio.ImageIO;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import static jdk.jpackage.internal.LinuxAppBundler.ICON_PNG;
+import static jdk.jpackage.internal.LinuxAppImageBuilder.DEFAULT_ICON;
+import static jdk.jpackage.internal.OverridableResource.createResource;
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+
+/**
+ * Helper to create files for desktop integration.
+ */
+final class DesktopIntegration {
+
+ static final String DESKTOP_COMMANDS_INSTALL = "DESKTOP_COMMANDS_INSTALL";
+ static final String DESKTOP_COMMANDS_UNINSTALL = "DESKTOP_COMMANDS_UNINSTALL";
+ static final String UTILITY_SCRIPTS = "UTILITY_SCRIPTS";
+
+ DesktopIntegration(PlatformPackage thePackage,
+ Map<String, ? super Object> params) {
+
+ associations = FILE_ASSOCIATIONS.fetchFrom(params).stream().filter(
+ a -> {
+ if (a == null) {
+ return false;
+ }
+ List<String> mimes = FA_CONTENT_TYPE.fetchFrom(a);
+ return (mimes != null && !mimes.isEmpty());
+ }).collect(Collectors.toUnmodifiableList());
+
+ launchers = ADD_LAUNCHERS.fetchFrom(params);
+
+ this.thePackage = thePackage;
+
+ final File customIconFile = ICON_PNG.fetchFrom(params);
+
+ iconResource = createResource(DEFAULT_ICON, params)
+ .setCategory(I18N.getString("resource.menu-icon"))
+ .setExternal(customIconFile);
+ desktopFileResource = createResource("template.desktop", params)
+ .setCategory(I18N.getString("resource.menu-shortcut-descriptor"));
+
+ // XDG recommends to use vendor prefix in desktop file names as xdg
+ // commands copy files to system directories.
+ // Package name should be a good prefix.
+ final String desktopFileName = String.format("%s-%s.desktop",
+ thePackage.name(), APP_NAME.fetchFrom(params));
+ final String mimeInfoFileName = String.format("%s-%s-MimeInfo.xml",
+ thePackage.name(), APP_NAME.fetchFrom(params));
+
+ mimeInfoFile = new DesktopFile(mimeInfoFileName);
+
+ if (!associations.isEmpty() || SHORTCUT_HINT.fetchFrom(params) || customIconFile != null) {
+ //
+ // Create primary .desktop file if one of conditions is met:
+ // - there are file associations configured
+ // - user explicitely requested to create a shortcut
+ // - custom icon specified
+ //
+ desktopFile = new DesktopFile(desktopFileName);
+ iconFile = new DesktopFile(String.format("%s.png",
+ APP_NAME.fetchFrom(params)));
+ } else {
+ desktopFile = null;
+ iconFile = null;
+ }
+
+ desktopFileData = Collections.unmodifiableMap(
+ createDataForDesktopFile(params));
+
+ nestedIntegrations = launchers.stream().map(
+ launcherParams -> new DesktopIntegration(thePackage,
+ launcherParams)).collect(Collectors.toList());
+ }
+
+ List<String> requiredPackages() {
+ return Stream.of(List.of(this), nestedIntegrations).flatMap(
+ List::stream).map(DesktopIntegration::requiredPackagesSelf).flatMap(
+ List::stream).distinct().collect(Collectors.toList());
+ }
+
+ Map<String, String> create() throws IOException {
+ if (iconFile != null) {
+ // Create application icon file.
+ iconResource.saveToFile(iconFile.srcPath());
+ }
+
+ Map<String, String> data = new HashMap<>(desktopFileData);
+
+ final ShellCommands shellCommands;
+ if (desktopFile != null) {
+ // Create application desktop description file.
+ createDesktopFile(data);
+
+ // Shell commands will be created only if desktop file
+ // should be installed.
+ shellCommands = new ShellCommands();
+ } else {
+ shellCommands = null;
+ }
+
+ if (!associations.isEmpty()) {
+ // Create XML file with mime types corresponding to file associations.
+ createFileAssociationsMimeInfoFile();
+
+ shellCommands.setFileAssociations();
+
+ // Create icon files corresponding to file associations
+ Map<String, Path> mimeTypeWithIconFile = createFileAssociationIconFiles();
+ mimeTypeWithIconFile.forEach((k, v) -> {
+ shellCommands.addIcon(k, v);
+ });
+ }
+
+ // Create shell commands to install/uninstall integration with desktop of the app.
+ if (shellCommands != null) {
+ shellCommands.applyTo(data);
+ }
+
+ boolean needCleanupScripts = !associations.isEmpty();
+
+ // Take care of additional launchers if there are any.
+ // Process every additional launcher as the main application launcher.
+ // Collect shell commands to install/uninstall integration with desktop
+ // of the additional launchers and append them to the corresponding
+ // commands of the main launcher.
+ List<String> installShellCmds = new ArrayList<>(Arrays.asList(
+ data.get(DESKTOP_COMMANDS_INSTALL)));
+ List<String> uninstallShellCmds = new ArrayList<>(Arrays.asList(
+ data.get(DESKTOP_COMMANDS_UNINSTALL)));
+ for (var integration: nestedIntegrations) {
+ if (!integration.associations.isEmpty()) {
+ needCleanupScripts = true;
+ }
+
+ Map<String, String> launcherData = integration.create();
+
+ installShellCmds.add(launcherData.get(DESKTOP_COMMANDS_INSTALL));
+ uninstallShellCmds.add(launcherData.get(
+ DESKTOP_COMMANDS_UNINSTALL));
+ }
+
+ data.put(DESKTOP_COMMANDS_INSTALL, stringifyShellCommands(
+ installShellCmds));
+ data.put(DESKTOP_COMMANDS_UNINSTALL, stringifyShellCommands(
+ uninstallShellCmds));
+
+ if (needCleanupScripts) {
+ // Pull in utils.sh scrips library.
+ try (InputStream is = OverridableResource.readDefault("utils.sh");
+ InputStreamReader isr = new InputStreamReader(is);
+ BufferedReader reader = new BufferedReader(isr)) {
+ data.put(UTILITY_SCRIPTS, reader.lines().collect(
+ Collectors.joining(System.lineSeparator())));
+ }
+ } else {
+ data.put(UTILITY_SCRIPTS, "");
+ }
+
+ return data;
+ }
+
+ private List<String> requiredPackagesSelf() {
+ if (desktopFile != null) {
+ return List.of("xdg-utils");
+ }
+ return Collections.emptyList();
+ }
+
+ private Map<String, String> createDataForDesktopFile(
+ Map<String, ? super Object> params) {
+ Map<String, String> data = new HashMap<>();
+ data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params));
+ data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params));
+ data.put("APPLICATION_ICON",
+ iconFile != null ? iconFile.installPath().toString() : null);
+ data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params));
+ data.put("APPLICATION_LAUNCHER",
+ thePackage.installedApplicationLayout().launchersDirectory().resolve(
+ LinuxAppImageBuilder.getLauncherName(params)).toString());
+
+ return data;
+ }
+
+ /**
+ * Shell commands to integrate something with desktop.
+ */
+ private class ShellCommands {
+
+ ShellCommands() {
+ registerIconCmds = new ArrayList<>();
+ unregisterIconCmds = new ArrayList<>();
+
+ registerDesktopFileCmd = String.join(" ", "xdg-desktop-menu",
+ "install", desktopFile.installPath().toString());
+ unregisterDesktopFileCmd = String.join(" ", "xdg-desktop-menu",
+ "uninstall", desktopFile.installPath().toString());
+ }
+
+ void setFileAssociations() {
+ registerFileAssociationsCmd = String.join(" ", "xdg-mime",
+ "install",
+ mimeInfoFile.installPath().toString());
+ unregisterFileAssociationsCmd = String.join(" ", "xdg-mime",
+ "uninstall", mimeInfoFile.installPath().toString());
+
+ //
+ // Add manual cleanup of system files to get rid of
+ // the default mime type handlers.
+ //
+ // Even after mime type is unregisterd with `xdg-mime uninstall`
+ // command and desktop file deleted with `xdg-desktop-menu uninstall`
+ // command, records in
+ // `/usr/share/applications/defaults.list` (Ubuntu 16) or
+ // `/usr/local/share/applications/defaults.list` (OracleLinux 7)
+ // files remain referencing deleted mime time and deleted
+ // desktop file which makes `xdg-mime query default` output name
+ // of non-existing desktop file.
+ //
+ String cleanUpCommand = String.join(" ",
+ "uninstall_default_mime_handler",
+ desktopFile.installPath().getFileName().toString(),
+ String.join(" ", getMimeTypeNamesFromFileAssociations()));
+
+ unregisterFileAssociationsCmd = stringifyShellCommands(
+ unregisterFileAssociationsCmd, cleanUpCommand);
+ }
+
+ void addIcon(String mimeType, Path iconFile) {
+ final int imgSize = getSquareSizeOfImage(iconFile.toFile());
+ final String dashMime = mimeType.replace('/', '-');
+ registerIconCmds.add(String.join(" ", "xdg-icon-resource",
+ "install", "--context", "mimetypes", "--size ",
+ Integer.toString(imgSize), iconFile.toString(), dashMime));
+ unregisterIconCmds.add(String.join(" ", "xdg-icon-resource",
+ "uninstall", dashMime));
+ }
+
+ void applyTo(Map<String, String> data) {
+ List<String> cmds = new ArrayList<>();
+
+ cmds.add(registerDesktopFileCmd);
+ cmds.add(registerFileAssociationsCmd);
+ cmds.addAll(registerIconCmds);
+ data.put(DESKTOP_COMMANDS_INSTALL, stringifyShellCommands(cmds));
+
+ cmds.clear();
+ cmds.add(unregisterDesktopFileCmd);
+ cmds.add(unregisterFileAssociationsCmd);
+ cmds.addAll(unregisterIconCmds);
+ data.put(DESKTOP_COMMANDS_UNINSTALL, stringifyShellCommands(cmds));
+ }
+
+ private String registerDesktopFileCmd;
+ private String unregisterDesktopFileCmd;
+
+ private String registerFileAssociationsCmd;
+ private String unregisterFileAssociationsCmd;
+
+ private List<String> registerIconCmds;
+ private List<String> unregisterIconCmds;
+ }
+
+ /**
+ * Desktop integration file. xml, icon, etc.
+ * Resides somewhere in application installation tree.
+ * Has two paths:
+ * - path where it should be placed at package build time;
+ * - path where it should be installed by package manager;
+ */
+ private class DesktopFile {
+
+ DesktopFile(String fileName) {
+ installPath = thePackage
+ .installedApplicationLayout()
+ .destktopIntegrationDirectory().resolve(fileName);
+ srcPath = thePackage
+ .sourceApplicationLayout()
+ .destktopIntegrationDirectory().resolve(fileName);
+ }
+
+ private final Path installPath;
+ private final Path srcPath;
+
+ Path installPath() {
+ return installPath;
+ }
+
+ Path srcPath() {
+ return srcPath;
+ }
+ }
+
+ private void appendFileAssociation(XMLStreamWriter xml,
+ Map<String, ? super Object> assoc) throws XMLStreamException {
+
+ xml.writeStartElement("mime-type");
+ final String thisMime = FA_CONTENT_TYPE.fetchFrom(assoc).get(0);
+ xml.writeAttribute("type", thisMime);
+
+ final String description = FA_DESCRIPTION.fetchFrom(assoc);
+ if (description != null && !description.isEmpty()) {
+ xml.writeStartElement("comment");
+ xml.writeCharacters(description);
+ xml.writeEndElement();
+ }
+
+ final List<String> extensions = FA_EXTENSIONS.fetchFrom(assoc);
+ if (extensions == null) {
+ Log.error(I18N.getString(
+ "message.creating-association-with-null-extension"));
+ } else {
+ for (String ext : extensions) {
+ xml.writeStartElement("glob");
+ xml.writeAttribute("pattern", "*." + ext);
+ xml.writeEndElement();
+ }
+ }
+
+ xml.writeEndElement();
+ }
+
+ private void createFileAssociationsMimeInfoFile() throws IOException {
+ XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
+
+ try (Writer w = new BufferedWriter(new FileWriter(
+ mimeInfoFile.srcPath().toFile()))) {
+ XMLStreamWriter xml = xmlFactory.createXMLStreamWriter(w);
+
+ xml.writeStartDocument();
+ xml.writeStartElement("mime-info");
+ xml.writeNamespace("xmlns",
+ "http://www.freedesktop.org/standards/shared-mime-info");
+
+ for (var assoc : associations) {
+ appendFileAssociation(xml, assoc);
+ }
+
+ xml.writeEndElement();
+ xml.writeEndDocument();
+ xml.flush();
+ xml.close();
+
+ } catch (XMLStreamException ex) {
+ Log.verbose(ex);
+ throw new IOException(ex);
+ }
+ }
+
+ private Map<String, Path> createFileAssociationIconFiles() throws
+ IOException {
+ Map<String, Path> mimeTypeWithIconFile = new HashMap<>();
+ for (var assoc : associations) {
+ File customFaIcon = FA_ICON.fetchFrom(assoc);
+ if (customFaIcon == null || !customFaIcon.exists() || getSquareSizeOfImage(
+ customFaIcon) == 0) {
+ continue;
+ }
+
+ String fname = iconFile.srcPath().getFileName().toString();
+ if (fname.indexOf(".") > 0) {
+ fname = fname.substring(0, fname.lastIndexOf("."));
+ }
+
+ DesktopFile faIconFile = new DesktopFile(
+ fname + "_fa_" + customFaIcon.getName());
+
+ IOUtils.copyFile(customFaIcon, faIconFile.srcPath().toFile());
+
+ mimeTypeWithIconFile.put(FA_CONTENT_TYPE.fetchFrom(assoc).get(0),
+ faIconFile.installPath());
+ }
+ return mimeTypeWithIconFile;
+ }
+
+ private void createDesktopFile(Map<String, String> data) throws IOException {
+ List<String> mimeTypes = getMimeTypeNamesFromFileAssociations();
+ data.put("DESKTOP_MIMES", "MimeType=" + String.join(";", mimeTypes));
+
+ // prepare desktop shortcut
+ desktopFileResource
+ .setSubstitutionData(data)
+ .saveToFile(desktopFile.srcPath());
+ }
+
+ private List<String> getMimeTypeNamesFromFileAssociations() {
+ return associations.stream().map(
+ a -> FA_CONTENT_TYPE.fetchFrom(a).get(0)).collect(
+ Collectors.toUnmodifiableList());
+ }
+
+ private static int getSquareSizeOfImage(File f) {
+ try {
+ BufferedImage bi = ImageIO.read(f);
+ if (bi.getWidth() == bi.getHeight()) {
+ return bi.getWidth();
+ }
+ } catch (IOException e) {
+ Log.verbose(e);
+ }
+ return 0;
+ }
+
+ private static String stringifyShellCommands(String... commands) {
+ return stringifyShellCommands(Arrays.asList(commands));
+ }
+
+ private static String stringifyShellCommands(List<String> commands) {
+ return String.join(System.lineSeparator(), commands.stream().filter(
+ s -> s != null && !s.isEmpty()).collect(Collectors.toList()));
+ }
+
+ private final PlatformPackage thePackage;
+
+ private final List<Map<String, ? super Object>> associations;
+
+ private final List<Map<String, ? super Object>> launchers;
+
+ private final OverridableResource iconResource;
+ private final OverridableResource desktopFileResource;
+
+ private final DesktopFile mimeInfoFile;
+ private final DesktopFile desktopFile;
+ private final DesktopFile iconFile;
+
+ private final List<DesktopIntegration> nestedIntegrations;
+
+ private final Map<String, String> desktopFileData;
+
+ private static final BundlerParamInfo<String> MENU_GROUP =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.LINUX_MENU_GROUP.getId(),
+ String.class,
+ params -> I18N.getString("param.menu-group.default"),
+ (s, p) -> s
+ );
+
+ private static final StandardBundlerParam<Boolean> SHORTCUT_HINT =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId(),
+ Boolean.class,
+ params -> false,
+ (s, p) -> (s == null || "null".equalsIgnoreCase(s))
+ ? false : Boolean.valueOf(s)
+ );
+}
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppImageBuilder.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppImageBuilder.java Wed Oct 16 09:57:23 2019 -0400
@@ -30,13 +30,11 @@
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
-import java.util.ResourceBundle;
+import static jdk.jpackage.internal.OverridableResource.createResource;
import static jdk.jpackage.internal.StandardBundlerParam.*;
@@ -170,19 +168,13 @@
private void copyIcon(Map<String, ? super Object> params)
throws IOException {
- File icon = ICON_PNG.fetchFrom(params);
- File iconTarget = appLayout.destktopIntegrationDirectory().resolve(
- APP_NAME.fetchFrom(params) + ".png").toFile();
+ Path iconTarget = appLayout.destktopIntegrationDirectory().resolve(
+ APP_NAME.fetchFrom(params) + ".png");
- InputStream in = locateResource(
- iconTarget.getName(),
- "icon",
- DEFAULT_ICON,
- icon,
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
-
- Files.copy(in, iconTarget.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ createResource(DEFAULT_ICON, params)
+ .setCategory("icon")
+ .setExternal(ICON_PNG.fetchFrom(params))
+ .saveToFile(iconTarget);
}
private void copyApplication(Map<String, ? super Object> params)
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java Wed Oct 16 09:57:23 2019 -0400
@@ -42,6 +42,7 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
+import static jdk.jpackage.internal.OverridableResource.createResource;
import static jdk.jpackage.internal.StandardBundlerParam.*;
@@ -332,17 +333,11 @@
void create(Map<String, String> data, Map<String, ? super Object> params)
throws IOException {
- Files.createDirectories(dstFilePath.getParent());
- try (Writer w = Files.newBufferedWriter(dstFilePath)) {
- String content = preprocessTextResource(
- dstFilePath.getFileName().toString(),
- I18N.getString(comment),
- "template." + dstFilePath.getFileName().toString(),
- data,
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
- w.write(content);
- }
+ createResource("template." + dstFilePath.getFileName().toString(),
+ params)
+ .setCategory(I18N.getString(comment))
+ .setSubstitutionData(data)
+ .saveToFile(dstFilePath);
if (permissions != null) {
setPermissions(dstFilePath.toFile(), permissions);
}
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java Wed Oct 16 09:57:23 2019 -0400
@@ -24,65 +24,26 @@
*/
package jdk.jpackage.internal;
-import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.InvalidPathException;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import javax.imageio.ImageIO;
-import javax.xml.stream.XMLOutputFactory;
-import javax.xml.stream.XMLStreamException;
-import javax.xml.stream.XMLStreamWriter;
+import static jdk.jpackage.internal.DesktopIntegration.*;
import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
import static jdk.jpackage.internal.LinuxAppBundler.LINUX_PACKAGE_DEPENDENCIES;
-import static jdk.jpackage.internal.LinuxAppImageBuilder.DEFAULT_ICON;
-import static jdk.jpackage.internal.LinuxAppImageBuilder.ICON_PNG;
import static jdk.jpackage.internal.StandardBundlerParam.*;
abstract class LinuxPackageBundler extends AbstractBundler {
- private static final String DESKTOP_COMMANDS_INSTALL = "DESKTOP_COMMANDS_INSTALL";
- private static final String DESKTOP_COMMANDS_UNINSTALL = "DESKTOP_COMMANDS_UNINSTALL";
- private static final String UTILITY_SCRIPTS = "UTILITY_SCRIPTS";
-
- private static final BundlerParamInfo<LinuxAppBundler> APP_BUNDLER =
- new StandardBundlerParam<>(
- "linux.app.bundler",
- LinuxAppBundler.class,
- (params) -> new LinuxAppBundler(),
- null
- );
-
- private static final BundlerParamInfo<String> MENU_GROUP =
- new StandardBundlerParam<>(
- Arguments.CLIOptions.LINUX_MENU_GROUP.getId(),
- String.class,
- params -> I18N.getString("param.menu-group.default"),
- (s, p) -> s
- );
-
- private static final StandardBundlerParam<Boolean> SHORTCUT_HINT =
- new StandardBundlerParam<>(
- Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId(),
- Boolean.class,
- params -> false,
- (s, p) -> (s == null || "null".equalsIgnoreCase(s))
- ? false : Boolean.valueOf(s)
- );
-
LinuxPackageBundler(BundlerParamInfo<String> packageName) {
this.packageName = packageName;
}
- private final BundlerParamInfo<String> packageName;
- private boolean withFindNeededPackages;
-
@Override
final public boolean validate(Map<String, ? super Object> params)
throws ConfigException {
@@ -361,449 +322,16 @@
}
}
- /**
- * Helper to create files for desktop integration.
- */
- private class DesktopIntegration {
-
- DesktopIntegration(PlatformPackage thePackage,
- Map<String, ? super Object> params) {
-
- associations = FILE_ASSOCIATIONS.fetchFrom(params).stream().filter(
- a -> {
- if (a == null) {
- return false;
- }
- List<String> mimes = FA_CONTENT_TYPE.fetchFrom(a);
- return (mimes != null && !mimes.isEmpty());
- }).collect(Collectors.toUnmodifiableList());
-
- launchers = ADD_LAUNCHERS.fetchFrom(params);
-
- this.thePackage = thePackage;
-
- customIconFile = ICON_PNG.fetchFrom(params);
-
- verbose = VERBOSE.fetchFrom(params);
- resourceDir = RESOURCE_DIR.fetchFrom(params);
-
- // XDG recommends to use vendor prefix in desktop file names as xdg
- // commands copy files to system directories.
- // Package name should be a good prefix.
- final String desktopFileName = String.format("%s-%s.desktop",
- thePackage.name(), APP_NAME.fetchFrom(params));
- final String mimeInfoFileName = String.format("%s-%s-MimeInfo.xml",
- thePackage.name(), APP_NAME.fetchFrom(params));
-
- mimeInfoFile = new DesktopFile(mimeInfoFileName);
-
- if (!associations.isEmpty() || SHORTCUT_HINT.fetchFrom(params) || customIconFile != null) {
- //
- // Create primary .desktop file if one of conditions is met:
- // - there are file associations configured
- // - user explicitely requested to create a shortcut
- // - custom icon specified
- //
- desktopFile = new DesktopFile(desktopFileName);
- iconFile = new DesktopFile(String.format("%s.png",
- APP_NAME.fetchFrom(params)));
- } else {
- desktopFile = null;
- iconFile = null;
- }
-
- desktopFileData = Collections.unmodifiableMap(
- createDataForDesktopFile(params));
-
- nestedIntegrations = launchers.stream().map(
- launcherParams -> new DesktopIntegration(thePackage,
- launcherParams)).collect(Collectors.toList());
- }
-
- List<String> requiredPackages() {
- return Stream.of(List.of(this), nestedIntegrations).flatMap(
- List::stream).map(DesktopIntegration::requiredPackagesSelf).flatMap(
- List::stream).distinct().collect(Collectors.toList());
- }
-
- Map<String, String> create() throws IOException {
- if (iconFile != null) {
- // Create application icon file.
- prepareSrcIconFile();
- }
-
- Map<String, String> data = new HashMap<>(desktopFileData);
-
- final ShellCommands shellCommands;
- if (desktopFile != null) {
- // Create application desktop description file.
- createDesktopFile(data);
-
- // Shell commands will be created only if desktop file
- // should be installed.
- shellCommands = new ShellCommands();
- } else {
- shellCommands = null;
- }
-
- if (!associations.isEmpty()) {
- // Create XML file with mime types corresponding to file associations.
- createFileAssociationsMimeInfoFile();
-
- shellCommands.setFileAssociations();
-
- // Create icon files corresponding to file associations
- Map<String, Path> mimeTypeWithIconFile = createFileAssociationIconFiles();
- mimeTypeWithIconFile.forEach((k, v) -> {
- shellCommands.addIcon(k, v);
- });
- }
-
- // Create shell commands to install/uninstall integration with desktop of the app.
- if (shellCommands != null) {
- shellCommands.applyTo(data);
- }
-
- boolean needCleanupScripts = !associations.isEmpty();
-
- // Take care of additional launchers if there are any.
- // Process every additional launcher as the main application launcher.
- // Collect shell commands to install/uninstall integration with desktop
- // of the additional launchers and append them to the corresponding
- // commands of the main launcher.
- List<String> installShellCmds = new ArrayList<>(Arrays.asList(
- data.get(DESKTOP_COMMANDS_INSTALL)));
- List<String> uninstallShellCmds = new ArrayList<>(Arrays.asList(
- data.get(DESKTOP_COMMANDS_UNINSTALL)));
- for (var integration: nestedIntegrations) {
- if (!integration.associations.isEmpty()) {
- needCleanupScripts = true;
- }
-
- Map<String, String> launcherData = integration.create();
-
- installShellCmds.add(launcherData.get(DESKTOP_COMMANDS_INSTALL));
- uninstallShellCmds.add(launcherData.get(
- DESKTOP_COMMANDS_UNINSTALL));
- }
-
- data.put(DESKTOP_COMMANDS_INSTALL, stringifyShellCommands(
- installShellCmds));
- data.put(DESKTOP_COMMANDS_UNINSTALL, stringifyShellCommands(
- uninstallShellCmds));
-
- if (needCleanupScripts) {
- // Pull in utils.sh scrips library.
- try (InputStream is = getResourceAsStream("utils.sh");
- InputStreamReader isr = new InputStreamReader(is);
- BufferedReader reader = new BufferedReader(isr)) {
- data.put(UTILITY_SCRIPTS, reader.lines().collect(
- Collectors.joining(System.lineSeparator())));
- }
- } else {
- data.put(UTILITY_SCRIPTS, "");
- }
-
- return data;
- }
-
- private List<String> requiredPackagesSelf() {
- if (desktopFile != null) {
- return List.of("xdg-utils");
- }
- return Collections.emptyList();
- }
-
- private Map<String, String> createDataForDesktopFile(
- Map<String, ? super Object> params) {
- Map<String, String> data = new HashMap<>();
- data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params));
- data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params));
- data.put("APPLICATION_ICON",
- iconFile != null ? iconFile.installPath().toString() : null);
- data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params));
- data.put("APPLICATION_LAUNCHER",
- thePackage.installedApplicationLayout().launchersDirectory().resolve(
- LinuxAppImageBuilder.getLauncherName(params)).toString());
-
- return data;
- }
-
- /**
- * Shell commands to integrate something with desktop.
- */
- private class ShellCommands {
-
- ShellCommands() {
- registerIconCmds = new ArrayList<>();
- unregisterIconCmds = new ArrayList<>();
-
- registerDesktopFileCmd = String.join(" ", "xdg-desktop-menu",
- "install", desktopFile.installPath().toString());
- unregisterDesktopFileCmd = String.join(" ", "xdg-desktop-menu",
- "uninstall", desktopFile.installPath().toString());
- }
-
- void setFileAssociations() {
- registerFileAssociationsCmd = String.join(" ", "xdg-mime",
- "install",
- mimeInfoFile.installPath().toString());
- unregisterFileAssociationsCmd = String.join(" ", "xdg-mime",
- "uninstall", mimeInfoFile.installPath().toString());
-
- //
- // Add manual cleanup of system files to get rid of
- // the default mime type handlers.
- //
- // Even after mime type is unregisterd with `xdg-mime uninstall`
- // command and desktop file deleted with `xdg-desktop-menu uninstall`
- // command, records in
- // `/usr/share/applications/defaults.list` (Ubuntu 16) or
- // `/usr/local/share/applications/defaults.list` (OracleLinux 7)
- // files remain referencing deleted mime time and deleted
- // desktop file which makes `xdg-mime query default` output name
- // of non-existing desktop file.
- //
- String cleanUpCommand = String.join(" ",
- "uninstall_default_mime_handler",
- desktopFile.installPath().getFileName().toString(),
- String.join(" ", getMimeTypeNamesFromFileAssociations()));
-
- unregisterFileAssociationsCmd = stringifyShellCommands(
- unregisterFileAssociationsCmd, cleanUpCommand);
- }
-
- void addIcon(String mimeType, Path iconFile) {
- final int imgSize = getSquareSizeOfImage(iconFile.toFile());
- final String dashMime = mimeType.replace('/', '-');
- registerIconCmds.add(String.join(" ", "xdg-icon-resource",
- "install", "--context", "mimetypes", "--size ",
- Integer.toString(imgSize), iconFile.toString(), dashMime));
- unregisterIconCmds.add(String.join(" ", "xdg-icon-resource",
- "uninstall", dashMime));
- }
+ private final BundlerParamInfo<String> packageName;
+ private boolean withFindNeededPackages;
+ private DesktopIntegration desktopIntegration;
- void applyTo(Map<String, String> data) {
- List<String> cmds = new ArrayList<>();
-
- cmds.add(registerDesktopFileCmd);
- cmds.add(registerFileAssociationsCmd);
- cmds.addAll(registerIconCmds);
- data.put(DESKTOP_COMMANDS_INSTALL, stringifyShellCommands(cmds));
-
- cmds.clear();
- cmds.add(unregisterDesktopFileCmd);
- cmds.add(unregisterFileAssociationsCmd);
- cmds.addAll(unregisterIconCmds);
- data.put(DESKTOP_COMMANDS_UNINSTALL, stringifyShellCommands(cmds));
- }
-
- private String registerDesktopFileCmd;
- private String unregisterDesktopFileCmd;
-
- private String registerFileAssociationsCmd;
- private String unregisterFileAssociationsCmd;
-
- private List<String> registerIconCmds;
- private List<String> unregisterIconCmds;
- }
-
- private final PlatformPackage thePackage;
-
- private final List<Map<String, ? super Object>> associations;
-
- private final List<Map<String, ? super Object>> launchers;
-
- /**
- * Desktop integration file. xml, icon, etc.
- * Resides somewhere in application installation tree.
- * Has two paths:
- * - path where it should be placed at package build time;
- * - path where it should be installed by package manager;
- */
- private class DesktopFile {
-
- DesktopFile(String fileName) {
- installPath = thePackage
- .installedApplicationLayout()
- .destktopIntegrationDirectory().resolve(fileName);
- srcPath = thePackage
- .sourceApplicationLayout()
- .destktopIntegrationDirectory().resolve(fileName);
- }
-
- private final Path installPath;
- private final Path srcPath;
-
- Path installPath() {
- return installPath;
- }
-
- Path srcPath() {
- return srcPath;
- }
- }
-
- private final boolean verbose;
- private final File resourceDir;
-
- private final DesktopFile mimeInfoFile;
- private final DesktopFile desktopFile;
- private final DesktopFile iconFile;
-
- final private List<DesktopIntegration> nestedIntegrations;
-
- private final Map<String, String> desktopFileData;
-
- /**
- * Path to icon file provided by user or null.
- */
- private final File customIconFile;
-
- private void appendFileAssociation(XMLStreamWriter xml,
- Map<String, ? super Object> assoc) throws XMLStreamException {
-
- xml.writeStartElement("mime-type");
- final String thisMime = FA_CONTENT_TYPE.fetchFrom(assoc).get(0);
- xml.writeAttribute("type", thisMime);
-
- final String description = FA_DESCRIPTION.fetchFrom(assoc);
- if (description != null && !description.isEmpty()) {
- xml.writeStartElement("comment");
- xml.writeCharacters(description);
- xml.writeEndElement();
- }
-
- final List<String> extensions = FA_EXTENSIONS.fetchFrom(assoc);
- if (extensions == null) {
- Log.error(I18N.getString(
- "message.creating-association-with-null-extension"));
- } else {
- for (String ext : extensions) {
- xml.writeStartElement("glob");
- xml.writeAttribute("pattern", "*." + ext);
- xml.writeEndElement();
- }
- }
-
- xml.writeEndElement();
- }
-
- private void createFileAssociationsMimeInfoFile() throws IOException {
- XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
+ private static final BundlerParamInfo<LinuxAppBundler> APP_BUNDLER =
+ new StandardBundlerParam<>(
+ "linux.app.bundler",
+ LinuxAppBundler.class,
+ (params) -> new LinuxAppBundler(),
+ null
+ );
- try (Writer w = new BufferedWriter(new FileWriter(
- mimeInfoFile.srcPath().toFile()))) {
- XMLStreamWriter xml = xmlFactory.createXMLStreamWriter(w);
-
- xml.writeStartDocument();
- xml.writeStartElement("mime-info");
- xml.writeNamespace("xmlns",
- "https://www.freedesktop.org/standards/shared-mime-info");
-
- for (var assoc : associations) {
- appendFileAssociation(xml, assoc);
- }
-
- xml.writeEndElement();
- xml.writeEndDocument();
- xml.flush();
- xml.close();
-
- } catch (XMLStreamException ex) {
- Log.verbose(ex);
- throw new IOException(ex);
- }
- }
-
- private Map<String, Path> createFileAssociationIconFiles() throws
- IOException {
- Map<String, Path> mimeTypeWithIconFile = new HashMap<>();
- for (var assoc : associations) {
- File customFaIcon = FA_ICON.fetchFrom(assoc);
- if (customFaIcon == null || !customFaIcon.exists() || getSquareSizeOfImage(
- customFaIcon) == 0) {
- continue;
- }
-
- String fname = iconFile.srcPath().getFileName().toString();
- if (fname.indexOf(".") > 0) {
- fname = fname.substring(0, fname.lastIndexOf("."));
- }
-
- DesktopFile faIconFile = new DesktopFile(
- fname + "_fa_" + customFaIcon.getName());
-
- IOUtils.copyFile(customFaIcon, faIconFile.srcPath().toFile());
-
- mimeTypeWithIconFile.put(FA_CONTENT_TYPE.fetchFrom(assoc).get(0),
- faIconFile.installPath());
- }
- return mimeTypeWithIconFile;
- }
-
- private void createDesktopFile(Map<String, String> data) throws IOException {
- List<String> mimeTypes = getMimeTypeNamesFromFileAssociations();
- data.put("DESKTOP_MIMES", "MimeType=" + String.join(";", mimeTypes));
-
- // prepare desktop shortcut
- try (Writer w = Files.newBufferedWriter(desktopFile.srcPath())) {
- String content = preprocessTextResource(
- desktopFile.srcPath().getFileName().toString(),
- I18N.getString("resource.menu-shortcut-descriptor"),
- "template.desktop",
- data,
- verbose,
- resourceDir);
- w.write(content);
- }
- }
-
- private void prepareSrcIconFile() throws IOException {
- if (customIconFile == null || !customIconFile.exists()) {
- fetchResource(iconFile.srcPath().getFileName().toString(),
- I18N.getString("resource.menu-icon"),
- DEFAULT_ICON,
- iconFile.srcPath().toFile(),
- verbose,
- resourceDir);
- } else {
- fetchResource(iconFile.srcPath().getFileName().toString(),
- I18N.getString("resource.menu-icon"),
- customIconFile,
- iconFile.srcPath().toFile(),
- verbose,
- resourceDir);
- }
- }
-
- private List<String> getMimeTypeNamesFromFileAssociations() {
- return associations.stream().map(
- a -> FA_CONTENT_TYPE.fetchFrom(a).get(0)).collect(
- Collectors.toUnmodifiableList());
- }
- }
-
- private static int getSquareSizeOfImage(File f) {
- try {
- BufferedImage bi = ImageIO.read(f);
- if (bi.getWidth() == bi.getHeight()) {
- return bi.getWidth();
- }
- } catch (IOException e) {
- Log.verbose(e);
- }
- return 0;
- }
-
- private static String stringifyShellCommands(String ... commands) {
- return stringifyShellCommands(Arrays.asList(commands));
- }
-
- private static String stringifyShellCommands(List<String> commands) {
- return String.join(System.lineSeparator(), commands.stream().filter(
- s -> s != null && !s.isEmpty()).collect(Collectors.toList()));
- }
-
- private DesktopIntegration desktopIntegration;
}
--- a/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java Wed Oct 16 09:57:23 2019 -0400
@@ -26,7 +26,6 @@
package jdk.jpackage.internal;
import java.io.*;
-import java.nio.file.Files;
import java.nio.file.Path;
import java.text.MessageFormat;
import java.util.*;
@@ -36,6 +35,7 @@
import static jdk.jpackage.internal.StandardBundlerParam.*;
import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
+import static jdk.jpackage.internal.OverridableResource.createResource;
/**
* There are two command line options to configure license information for RPM
@@ -146,16 +146,10 @@
Path specFile = specFile(params);
// prepare spec file
- Files.createDirectories(specFile.getParent());
- try (Writer w = Files.newBufferedWriter(specFile)) {
- String content = preprocessTextResource(
- specFile.getFileName().toString(),
- I18N.getString("resource.rpm-spec-file"),
- DEFAULT_SPEC_TEMPLATE, replacementData,
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
- w.write(content);
- }
+ createResource(DEFAULT_SPEC_TEMPLATE, params)
+ .setCategory(I18N.getString("resource.rpm-spec-file"))
+ .setSubstitutionData(replacementData)
+ .saveToFile(specFile);
return buildRPM(params, outputParentDir);
}
@@ -171,14 +165,11 @@
data.put("APPLICATION_LICENSE_TYPE", LICENSE_TYPE.fetchFrom(params));
String licenseFile = LICENSE_FILE.fetchFrom(params);
- if (licenseFile == null) {
- licenseFile = "";
- } else {
+ if (licenseFile != null) {
licenseFile = Path.of(licenseFile).toAbsolutePath().normalize().toString();
}
data.put("APPLICATION_LICENSE_FILE", licenseFile);
- data.put("APPLICATION_GROUP", Optional.ofNullable(
- GROUP.fetchFrom(params)).orElse(""));
+ data.put("APPLICATION_GROUP", GROUP.fetchFrom(params));
return data;
}
--- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java Wed Oct 16 09:57:23 2019 -0400
@@ -141,8 +141,7 @@
SIGNING_KEYCHAIN.fetchFrom(params),
VERBOSE.fetchFrom(params));
if (result != null) {
- MacCertificate certificate = new MacCertificate(result,
- VERBOSE.fetchFrom(params));
+ MacCertificate certificate = new MacCertificate(result);
if (!certificate.isValid()) {
Log.error(MessageFormat.format(I18N.getString(
--- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppImageBuilder.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppImageBuilder.java Wed Oct 16 09:57:23 2019 -0400
@@ -25,16 +25,10 @@
package jdk.jpackage.internal;
-import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.UncheckedIOException;
import java.io.Writer;
import java.math.BigInteger;
import java.nio.file.Files;
@@ -63,6 +57,7 @@
import static jdk.jpackage.internal.StandardBundlerParam.*;
import static jdk.jpackage.internal.MacBaseInstallerBundler.*;
import static jdk.jpackage.internal.MacAppBundler.*;
+import static jdk.jpackage.internal.OverridableResource.createResource;
public class MacAppImageBuilder extends AbstractAppImageBuilder {
@@ -143,13 +138,6 @@
},
(s, p) -> s);
- public static final BundlerParamInfo<String> DEFAULT_ICNS_ICON =
- new StandardBundlerParam<>(
- ".mac.default.icns",
- String.class,
- params -> TEMPLATE_BUNDLE_ICON,
- (s, p) -> s);
-
public static final BundlerParamInfo<File> ICON_ICNS =
new StandardBundlerParam<>(
"icon.icns",
@@ -312,18 +300,12 @@
copyClassPathEntries(appDir, params);
/*********** Take care of "config" files *******/
- File icon = ICON_ICNS.fetchFrom(params);
- InputStream in = locateResource(
- APP_NAME.fetchFrom(params) + ".icns",
- "icon",
- DEFAULT_ICNS_ICON.fetchFrom(params),
- icon,
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
- Files.copy(in,
- resourcesDir.resolve(APP_NAME.fetchFrom(params) + ".icns"),
- StandardCopyOption.REPLACE_EXISTING);
+ createResource(TEMPLATE_BUNDLE_ICON, params)
+ .setCategory("icon")
+ .setExternal(ICON_ICNS.fetchFrom(params))
+ .saveToFile(resourcesDir.resolve(APP_NAME.fetchFrom(params)
+ + ".icns"));
// copy file association icons
for (Map<String, ?
@@ -457,14 +439,11 @@
data.put("CF_BUNDLE_VERSION", VERSION.fetchFrom(params));
data.put("CF_BUNDLE_SHORT_VERSION_STRING", VERSION.fetchFrom(params));
- try (Writer w = Files.newBufferedWriter(file.toPath())) {
- w.write(preprocessTextResource("Runtime-Info.plist",
- I18N.getString("resource.runtime-info-plist"),
- TEMPLATE_RUNTIME_INFO_PLIST,
- data,
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params)));
- }
+ createResource(TEMPLATE_RUNTIME_INFO_PLIST, params)
+ .setPublicName("Runtime-Info.plist")
+ .setCategory(I18N.getString("resource.runtime-info-plist"))
+ .setSubstitutionData(data)
+ .saveToFile(file);
}
private void writeInfoPlist(File file, Map<String, ? super Object> params)
@@ -664,16 +643,11 @@
}
data.put("DEPLOY_FILE_ASSOCIATIONS", associationData);
-
- try (Writer w = Files.newBufferedWriter(file.toPath())) {
- w.write(preprocessTextResource(
- // getConfig_InfoPlist(params).getName(),
- "Info.plist",
- I18N.getString("resource.app-info-plist"),
- TEMPLATE_INFO_PLIST_LITE,
- data, VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params)));
- }
+ createResource(TEMPLATE_INFO_PLIST_LITE, params)
+ .setCategory(I18N.getString("resource.app-info-plist"))
+ .setSubstitutionData(data)
+ .setPublicName("Info.plist")
+ .saveToFile(file);
}
private void writePkgInfo(File file) throws IOException {
--- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppStoreBundler.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppStoreBundler.java Wed Oct 16 09:57:23 2019 -0400
@@ -29,9 +29,6 @@
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -39,6 +36,7 @@
import static jdk.jpackage.internal.StandardBundlerParam.*;
import static jdk.jpackage.internal.MacAppBundler.*;
+import static jdk.jpackage.internal.OverridableResource.createResource;
public class MacAppStoreBundler extends MacBaseInstallerBundler {
@@ -62,8 +60,7 @@
SIGNING_KEYCHAIN.fetchFrom(params),
VERBOSE.fetchFrom(params));
if (result != null) {
- MacCertificate certificate = new MacCertificate(result,
- VERBOSE.fetchFrom(params));
+ MacCertificate certificate = new MacCertificate(result);
if (!certificate.isValid()) {
Log.error(MessageFormat.format(
@@ -88,8 +85,7 @@
VERBOSE.fetchFrom(params));
if (result != null) {
- MacCertificate certificate = new MacCertificate(
- result, VERBOSE.fetchFrom(params));
+ MacCertificate certificate = new MacCertificate(result);
if (!certificate.isValid()) {
Log.error(MessageFormat.format(
@@ -209,40 +205,18 @@
private void prepareEntitlements(Map<String, ? super Object> params)
throws IOException {
- File entitlements = MAC_APP_STORE_ENTITLEMENTS.fetchFrom(params);
- if (entitlements == null || !entitlements.exists()) {
- fetchResource(getEntitlementsFileName(params),
- I18N.getString("resource.mac-app-store-entitlements"),
- DEFAULT_ENTITLEMENTS,
- getConfig_Entitlements(params),
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
- } else {
- fetchResource(getEntitlementsFileName(params),
- I18N.getString("resource.mac-app-store-entitlements"),
- entitlements,
- getConfig_Entitlements(params),
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
- }
- fetchResource(getInheritEntitlementsFileName(params),
- I18N.getString("resource.mac-app-store-inherit-entitlements"),
- DEFAULT_INHERIT_ENTITLEMENTS,
- getConfig_Inherit_Entitlements(params),
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
+ createResource(DEFAULT_ENTITLEMENTS, params)
+ .setCategory(
+ I18N.getString("resource.mac-app-store-entitlements"))
+ .setExternal(MAC_APP_STORE_ENTITLEMENTS.fetchFrom(params))
+ .saveToFile(getConfig_Entitlements(params));
+
+ createResource(DEFAULT_INHERIT_ENTITLEMENTS, params)
+ .setCategory(I18N.getString(
+ "resource.mac-app-store-inherit-entitlements"))
+ .saveToFile(getConfig_Entitlements(params));
}
- private String getEntitlementsFileName(Map<String, ? super Object> params) {
- return APP_NAME.fetchFrom(params) + ".entitlements";
- }
-
- private String getInheritEntitlementsFileName(
- Map<String, ? super Object> params) {
- return APP_NAME.fetchFrom(params) + "_Inherit.entitlements";
- }
-
-
///////////////////////////////////////////////////////////////////////
// Implement Bundler
///////////////////////////////////////////////////////////////////////
--- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacCertificate.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacCertificate.java Wed Oct 16 09:57:23 2019 -0400
@@ -43,23 +43,16 @@
public final class MacCertificate {
private final String certificate;
- private final boolean verbose;
public MacCertificate(String certificate) {
this.certificate = certificate;
- this.verbose = false;
- }
-
- public MacCertificate(String certificate, boolean verbose) {
- this.certificate = certificate;
- this.verbose = verbose;
}
public boolean isValid() {
- return verifyCertificate(this.certificate, verbose);
+ return verifyCertificate(this.certificate);
}
- private static File findCertificate(String certificate, boolean verbose) {
+ private static File findCertificate(String certificate) {
File result = null;
List<String> args = new ArrayList<>();
@@ -87,7 +80,7 @@
return result;
}
- private static Date findCertificateDate(String filename, boolean verbose) {
+ private static Date findCertificateDate(String filename) {
Date result = null;
List<String> args = new ArrayList<>();
@@ -114,8 +107,7 @@
return result;
}
- private static boolean verifyCertificate(
- String certificate, boolean verbose) {
+ private static boolean verifyCertificate(String certificate) {
boolean result = false;
try {
@@ -123,11 +115,11 @@
Date certificateDate = null;
try {
- file = findCertificate(certificate, verbose);
+ file = findCertificate(certificate);
if (file != null) {
certificateDate = findCertificateDate(
- file.getCanonicalPath(), verbose);
+ file.getCanonicalPath());
}
}
finally {
--- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java Wed Oct 16 09:57:23 2019 -0400
@@ -29,6 +29,7 @@
import java.nio.file.Files;
import java.text.MessageFormat;
import java.util.*;
+import static jdk.jpackage.internal.OverridableResource.createResource;
import static jdk.jpackage.internal.StandardBundlerParam.*;
@@ -97,12 +98,10 @@
data.put("DEPLOY_INSTALL_LOCATION", "(path to applications folder)");
data.put("DEPLOY_INSTALL_NAME", "Applications");
- try (Writer w = Files.newBufferedWriter(dmgSetup.toPath())) {
- w.write(preprocessTextResource(dmgSetup.getName(),
- I18N.getString("resource.dmg-setup-script"),
- DEFAULT_DMG_SETUP_SCRIPT, data, VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params)));
- }
+ createResource(DEFAULT_DMG_SETUP_SCRIPT, params)
+ .setCategory(I18N.getString("resource.dmg-setup-script"))
+ .setSubstitutionData(data)
+ .saveToFile(dmgSetup);
}
private File getConfig_VolumeScript(Map<String, ? super Object> params) {
@@ -142,14 +141,10 @@
Map<String, String> data = new HashMap<>();
data.put("APPLICATION_LICENSE_TEXT", licenseInBase64);
- try (Writer w = Files.newBufferedWriter(
- getConfig_LicenseFile(params).toPath())) {
- w.write(preprocessTextResource(
- getConfig_LicenseFile(params).getName(),
- I18N.getString("resource.license-setup"),
- DEFAULT_LICENSE_PLIST, data, VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params)));
- }
+ createResource(DEFAULT_LICENSE_PLIST, params)
+ .setCategory(I18N.getString("resource.license-setup"))
+ .setSubstitutionData(data)
+ .saveToFile(getConfig_LicenseFile(params));
} catch (IOException ex) {
Log.verbose(ex);
@@ -158,39 +153,19 @@
private boolean prepareConfigFiles(Map<String, ? super Object> params)
throws IOException {
- File bgTarget = getConfig_VolumeBackground(params);
- fetchResource(bgTarget.getName(),
- I18N.getString("resource.dmg-background"),
- DEFAULT_BACKGROUND_IMAGE,
- bgTarget,
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
+
+ createResource(DEFAULT_BACKGROUND_IMAGE, params)
+ .setCategory(I18N.getString("resource.dmg-background"))
+ .saveToFile(getConfig_VolumeBackground(params));
- File iconTarget = getConfig_VolumeIcon(params);
- if (MacAppBundler.ICON_ICNS.fetchFrom(params) == null ||
- !MacAppBundler.ICON_ICNS.fetchFrom(params).exists()) {
- fetchResource(iconTarget.getName(),
- I18N.getString("resource.volume-icon"),
- TEMPLATE_BUNDLE_ICON,
- iconTarget,
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
- } else {
- fetchResource(iconTarget.getName(),
- I18N.getString("resource.volume-icon"),
- MacAppBundler.ICON_ICNS.fetchFrom(params),
- iconTarget,
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
- }
+ createResource(TEMPLATE_BUNDLE_ICON, params)
+ .setCategory(I18N.getString("resource.volume-icon"))
+ .setExternal(MacAppBundler.ICON_ICNS.fetchFrom(params))
+ .saveToFile(getConfig_VolumeIcon(params));
-
- fetchResource(getConfig_Script(params).getName(),
- I18N.getString("resource.post-install-script"),
- (String) null,
- getConfig_Script(params),
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
+ createResource(null, params)
+ .setCategory(I18N.getString("resource.post-install-script"))
+ .saveToFile(getConfig_Script(params));
prepareLicense(params);
--- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java Wed Oct 16 09:57:23 2019 -0400
@@ -50,6 +50,7 @@
import static jdk.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEYCHAIN;
import static jdk.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEY_USER;
import static jdk.jpackage.internal.MacAppImageBuilder.MAC_CF_BUNDLE_IDENTIFIER;
+import static jdk.jpackage.internal.OverridableResource.createResource;
public class MacPkgBundler extends MacBaseInstallerBundler {
@@ -100,8 +101,7 @@
SIGNING_KEYCHAIN.fetchFrom(params),
VERBOSE.fetchFrom(params));
if (result != null) {
- MacCertificate certificate = new MacCertificate(
- result, VERBOSE.fetchFrom(params));
+ MacCertificate certificate = new MacCertificate(result);
if (!certificate.isValid()) {
Log.error(MessageFormat.format(
@@ -206,30 +206,16 @@
data.put("INSTALL_LOCATION", MAC_INSTALL_DIR.fetchFrom(params));
data.put("APP_LOCATION", appLocation.toString());
- try (Writer w = Files.newBufferedWriter(
- getScripts_PreinstallFile(params).toPath())) {
- String content = preprocessTextResource(
- getScripts_PreinstallFile(params).getName(),
- I18N.getString("resource.pkg-preinstall-script"),
- TEMPLATE_PREINSTALL_SCRIPT,
- data,
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
- w.write(content);
- }
+ createResource(TEMPLATE_PREINSTALL_SCRIPT, params)
+ .setCategory(I18N.getString("resource.pkg-preinstall-script"))
+ .setSubstitutionData(data)
+ .saveToFile(getScripts_PreinstallFile(params));
getScripts_PreinstallFile(params).setExecutable(true, false);
- try (Writer w = Files.newBufferedWriter(
- getScripts_PostinstallFile(params).toPath())) {
- String content = preprocessTextResource(
- getScripts_PostinstallFile(params).getName(),
- I18N.getString("resource.pkg-postinstall-script"),
- TEMPLATE_POSTINSTALL_SCRIPT,
- data,
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
- w.write(content);
- }
+ createResource(TEMPLATE_POSTINSTALL_SCRIPT, params)
+ .setCategory(I18N.getString("resource.pkg-postinstall-script"))
+ .setSubstitutionData(data)
+ .saveToFile(getScripts_PostinstallFile(params));
getScripts_PostinstallFile(params).setExecutable(true, false);
}
@@ -335,30 +321,20 @@
private boolean prepareConfigFiles(Map<String, ? super Object> params)
throws IOException, URISyntaxException {
- File imageTarget = getConfig_BackgroundImage(params);
- fetchResource(imageTarget.getName(),
- I18N.getString("resource.pkg-background-image"),
- DEFAULT_BACKGROUND_IMAGE,
- imageTarget,
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
- imageTarget = getConfig_BackgroundImageDarkAqua(params);
- fetchResource(imageTarget.getName(),
- I18N.getString("resource.pkg-background-image"),
- DEFAULT_BACKGROUND_IMAGE,
- imageTarget,
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
+ createResource(DEFAULT_BACKGROUND_IMAGE, params)
+ .setCategory(I18N.getString("resource.pkg-background-image"))
+ .saveToFile(getConfig_BackgroundImage(params));
+
+ createResource(DEFAULT_BACKGROUND_IMAGE, params)
+ .setCategory(I18N.getString("resource.pkg-background-image"))
+ .saveToFile(getConfig_BackgroundImageDarkAqua(params));
prepareDistributionXMLFile(params);
- fetchResource(getConfig_Script(params).getName(),
- I18N.getString("resource.post-install-script"),
- (String) null,
- getConfig_Script(params),
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
+ createResource(null, params)
+ .setCategory(I18N.getString("resource.post-install-script"))
+ .saveToFile(getConfig_Script(params));
return true;
}
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractAppImageBuilder.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractAppImageBuilder.java Wed Oct 16 09:57:23 2019 -0400
@@ -87,92 +87,6 @@
}
}
- protected InputStream locateResource(String publicName, String category,
- String defaultName, File customFile,
- boolean verbose, File publicRoot) throws IOException {
- InputStream is = null;
- boolean customFromClasspath = false;
- boolean customFromFile = false;
- if (publicName != null) {
- if (publicRoot != null) {
- File publicResource = new File(publicRoot, publicName);
- if (publicResource.exists() && publicResource.isFile()) {
- is = new FileInputStream(publicResource);
- }
- } else {
- is = getResourceAsStream(publicName);
- }
- customFromClasspath = (is != null);
- }
- if (is == null && customFile != null) {
- is = new FileInputStream(customFile);
- customFromFile = (is != null);
- }
- if (is == null && defaultName != null) {
- is = getResourceAsStream(defaultName);
- }
- if (verbose) {
- String msg = null;
- if (customFromClasspath) {
- msg = MessageFormat.format(I18N.getString(
- "message.using-custom-resource"),
- category == null ? "" : "[" + category + "] ", publicName);
- } else if (customFromFile) {
- msg = MessageFormat.format(I18N.getString(
- "message.using-custom-resource-from-file"),
- category == null ? "" : "[" + category + "] ",
- customFile.getAbsoluteFile());
- } else if (is != null) {
- msg = MessageFormat.format(I18N.getString(
- "message.using-default-resource"),
- defaultName,
- category == null ? "" : "[" + category + "] ",
- publicName);
- } else {
- msg = MessageFormat.format(I18N.getString(
- "message.no-default-resource"),
- defaultName == null ? "" : defaultName,
- category == null ? "" : "[" + category + "] ",
- publicName);
- }
- if (msg != null) {
- Log.verbose(msg);
- }
- }
- return is;
- }
-
-
- protected String preprocessTextResource(String publicName, String category,
- String defaultName, Map<String, String> pairs,
- boolean verbose, File publicRoot) throws IOException {
- InputStream inp = locateResource(publicName, category,
- defaultName, null, verbose, publicRoot);
- if (inp == null) {
- throw new RuntimeException(
- "Module corrupt? No "+defaultName+" resource!");
- }
-
- try (InputStream is = inp) {
- //read fully into memory
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- int length;
- while ((length = is.read(buffer)) != -1) {
- baos.write(buffer, 0, length);
- }
-
- //substitute
- String result = new String(baos.toByteArray());
- for (Map.Entry<String, String> e : pairs.entrySet()) {
- if (e.getValue() != null) {
- result = result.replace(e.getKey(), e.getValue());
- }
- }
- return result;
- }
- }
-
public void writeCfgFile(Map<String, ? super Object> params,
File cfgFileName) throws IOException {
cfgFileName.getParentFile().mkdirs();
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractBundler.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractBundler.java Wed Oct 16 09:57:23 2019 -0400
@@ -25,31 +25,19 @@
package jdk.jpackage.internal;
-import java.io.ByteArrayOutputStream;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.BufferedInputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.StandardCopyOption;
-import java.nio.file.Files;
-import java.text.MessageFormat;
import java.util.Map;
-import java.util.ResourceBundle;
-import jdk.jpackage.internal.resources.ResourceLocator;
/**
* AbstractBundler
*
- * This is the base class all Bundlers extend from.
- * It contains methods and parameters common to all Bundlers.
- * The concrete implementations are in the platform specific Bundlers.
+ * This is the base class all bundlers extend from.
+ * It contains methods and parameters common to all bundlers.
+ * The concrete implementations are in the platform specific bundlers.
*/
-public abstract class AbstractBundler implements Bundler {
-
- private static final ResourceBundle I18N = ResourceBundle.getBundle(
- "jdk.jpackage.internal.resources.MainResources");
+abstract class AbstractBundler implements Bundler {
static final BundlerParamInfo<File> IMAGES_ROOT =
new StandardBundlerParam<>(
@@ -59,118 +47,6 @@
StandardBundlerParam.TEMP_ROOT.fetchFrom(params), "images"),
(s, p) -> null);
- public InputStream getResourceAsStream(String name) {
- return ResourceLocator.class.getResourceAsStream(name);
- }
-
- protected void fetchResource(String publicName, String category,
- String defaultName, File result, boolean verbose, File publicRoot)
- throws IOException {
-
- try (InputStream is = streamResource(publicName, category,
- defaultName, verbose, publicRoot)) {
- if (is != null) {
- Files.copy(is, result.toPath(),
- StandardCopyOption.REPLACE_EXISTING);
- } else {
- if (verbose) {
- Log.verbose(MessageFormat.format(I18N.getString(
- "message.no-default-resource"),
- defaultName == null ? "" : defaultName,
- category == null ? "" : "[" + category + "] ",
- publicName));
- }
- }
- }
- }
-
- protected void fetchResource(String publicName, String category,
- File defaultFile, File result, boolean verbose, File publicRoot)
- throws IOException {
-
- try (InputStream is = streamResource(publicName, category,
- null, verbose, publicRoot)) {
- if (is != null) {
- Files.copy(is, result.toPath());
- } else {
- IOUtils.copyFile(defaultFile, result);
- if (verbose) {
- Log.verbose(MessageFormat.format(I18N.getString(
- "message.using-custom-resource-from-file"),
- category == null ? "" : "[" + category + "] ",
- defaultFile.getAbsoluteFile()));
- }
- }
- }
- }
-
- private InputStream streamResource(String publicName, String category,
- String defaultName, boolean verbose, File publicRoot)
- throws IOException {
- boolean custom = false;
- InputStream is = null;
- if (publicName != null) {
- if (publicRoot != null) {
- File publicResource = new File(publicRoot, publicName);
- if (publicResource.exists() && publicResource.isFile()) {
- is = new BufferedInputStream(
- new FileInputStream(publicResource));
- }
- } else {
- is = getResourceAsStream(publicName);
- }
- custom = (is != null);
- }
- if (is == null && defaultName != null) {
- is = getResourceAsStream(defaultName);
- }
- if (verbose && is != null) {
- String msg = null;
- if (custom) {
- msg = MessageFormat.format(I18N.getString(
- "message.using-custom-resource"),
- category == null ?
- "" : "[" + category + "] ", publicName);
- } else {
- msg = MessageFormat.format(I18N.getString(
- "message.using-default-resource"),
- defaultName == null ? "" : defaultName,
- category == null ? "" : "[" + category + "] ",
- publicName);
- }
- Log.verbose(msg);
- }
- return is;
- }
-
- protected String preprocessTextResource(String publicName, String category,
- String defaultName, Map<String, String> pairs,
- boolean verbose, File publicRoot) throws IOException {
- InputStream inp = streamResource(
- publicName, category, defaultName, verbose, publicRoot);
- if (inp == null) {
- throw new RuntimeException(
- "Jar corrupt? No " + defaultName + " resource!");
- }
-
- // read fully into memory
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- int length;
- while ((length = inp.read(buffer)) != -1) {
- baos.write(buffer, 0, length);
- }
-
- // substitute
- String result = new String(baos.toByteArray());
- for (Map.Entry<String, String> e : pairs.entrySet()) {
- if (e.getValue() != null) {
- result = result.replace(e.getKey(), e.getValue());
- }
- }
- return result;
- }
-
@Override
public String toString() {
return getName();
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayout.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayout.java Wed Oct 16 09:57:23 2019 -0400
@@ -144,7 +144,7 @@
return macAppImage();
}
- throw new IllegalArgumentException("Unknown platform");
+ throw Platform.throwUnknownPlatformError();
}
static ApplicationLayout javaRuntime() {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/OverridableResource.java Wed Oct 16 09:57:23 2019 -0400
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2019, 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 java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import static jdk.jpackage.internal.StandardBundlerParam.RESOURCE_DIR;
+import jdk.jpackage.internal.resources.ResourceLocator;
+
+/**
+ * Resource file that may have the default value supplied by jpackage. It can be
+ * overridden by a file from resource directory set with {@code --resource-dir}
+ * jpackage parameter.
+ *
+ * Resource has default name and public name. Default name is the name of a file
+ * in {@code jdk.jpackage.internal.resources} package that provides the default
+ * value of the resource.
+ *
+ * Public name is a path relative to resource directory to a file with custom
+ * value of the resource.
+ *
+ * Use #setPublicName to set the public name.
+ *
+ * If #setPublicName was not called, name of file passed in #saveToFile function
+ * will be used as a public name.
+ *
+ * Use #setExternal to set arbitrary file as a source of resource. If non-null
+ * value was passed in #setExternal call that value will be used as a path to file
+ * to copy in the destination file passed in #saveToFile function call.
+ */
+final class OverridableResource {
+
+ OverridableResource(String defaultName) {
+ this.defaultName = defaultName;
+ }
+
+ OverridableResource setSubstitutionData(Map<String, String> v) {
+ if (v != null) {
+ // Disconnect `v`
+ substitutionData = new HashMap<>(v);
+ } else {
+ substitutionData = null;
+ }
+ return this;
+ }
+
+ OverridableResource setCategory(String v) {
+ category = v;
+ return this;
+ }
+
+ String getCategory() {
+ return category;
+ }
+
+ OverridableResource setResourceDir(Path v) {
+ resourceDir = v;
+ return this;
+ }
+
+ OverridableResource setResourceDir(File v) {
+ return setResourceDir(toPath(v));
+ }
+
+ /**
+ * Set name of file to look for in resource dir.
+ *
+ * @return this
+ */
+ OverridableResource setPublicName(Path v) {
+ publicName = v;
+ return this;
+ }
+
+ OverridableResource setPublicName(String v) {
+ return setPublicName(Path.of(v));
+ }
+
+ OverridableResource setExternal(Path v) {
+ externalPath = v;
+ return this;
+ }
+
+ OverridableResource setExternal(File v) {
+ return setExternal(toPath(v));
+ }
+
+ void saveToFile(Path dest) throws IOException {
+ final String printableCategory;
+ if (category != null) {
+ printableCategory = String.format("[%s]", category);
+ } else {
+ printableCategory = "";
+ }
+
+ if (externalPath != null && externalPath.toFile().exists()) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.using-custom-resource-from-file"),
+ printableCategory,
+ externalPath.toAbsolutePath().normalize()));
+
+ try (InputStream in = Files.newInputStream(externalPath)) {
+ processResourceStream(in, dest);
+ }
+ return;
+ }
+
+ final Path resourceName = Optional.ofNullable(publicName).orElse(
+ dest.getFileName());
+
+ if (resourceDir != null) {
+ final Path customResource = resourceDir.resolve(resourceName);
+ if (customResource.toFile().exists()) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.using-custom-resource"), printableCategory,
+ resourceDir.normalize().toAbsolutePath().relativize(
+ customResource.normalize().toAbsolutePath())));
+
+ try (InputStream in = Files.newInputStream(customResource)) {
+ processResourceStream(in, dest);
+ }
+ return;
+ }
+ }
+
+ if (defaultName != null) {
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.using-default-resource"),
+ defaultName, printableCategory, resourceName));
+
+ try (InputStream in = readDefault(defaultName)) {
+ processResourceStream(in, dest);
+ }
+ }
+ }
+
+ void saveToFile(File dest) throws IOException {
+ saveToFile(dest.toPath());
+ }
+
+ static InputStream readDefault(String resourceName) {
+ return ResourceLocator.class.getResourceAsStream(resourceName);
+ }
+
+ static OverridableResource createResource(String defaultName,
+ Map<String, ? super Object> params) {
+ return new OverridableResource(defaultName).setResourceDir(
+ RESOURCE_DIR.fetchFrom(params));
+ }
+
+ private static List<String> substitute(Stream<String> lines,
+ Map<String, String> substitutionData) {
+ return lines.map(line -> {
+ String result = line;
+ for (var entry : substitutionData.entrySet()) {
+ result = result.replace(entry.getKey(), Optional.ofNullable(
+ entry.getValue()).orElse(""));
+ }
+ return result;
+ }).collect(Collectors.toList());
+ }
+
+ private static Path toPath(File v) {
+ if (v != null) {
+ return v.toPath();
+ }
+ return null;
+ }
+
+ private void processResourceStream(InputStream rawResource, Path dest)
+ throws IOException {
+ if (substitutionData == null) {
+ Files.createDirectories(dest.getParent());
+ Files.copy(rawResource, dest, StandardCopyOption.REPLACE_EXISTING);
+ } else {
+ // Utf8 in and out
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(rawResource, StandardCharsets.UTF_8))) {
+ Files.createDirectories(dest.getParent());
+ Files.write(dest, substitute(reader.lines(), substitutionData));
+ }
+ }
+ }
+
+ private Map<String, String> substitutionData;
+ private String category;
+ private Path resourceDir;
+ private Path publicName;
+ private Path externalPath;
+ private final String defaultName;
+}
--- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Platform.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Platform.java Wed Oct 16 09:57:23 2019 -0400
@@ -114,4 +114,8 @@
static boolean isLinux() {
return getPlatform() == LINUX;
}
+
+ static RuntimeException throwUnknownPlatformError() {
+ throw new IllegalArgumentException("Unknown platform");
+ }
}
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java Wed Oct 16 09:57:23 2019 -0400
@@ -127,7 +127,7 @@
// Copy template msi wrapper next to msi file
String exePath = msi.getAbsolutePath();
exePath = exePath.substring(0, exePath.lastIndexOf('.')) + ".exe";
- try (InputStream is = getResourceAsStream(EXE_WRAPPER_NAME)) {
+ try (InputStream is = OverridableResource.readDefault(EXE_WRAPPER_NAME)) {
Files.copy(is, Path.of(exePath));
}
// Embed msi in msi wrapper exe.
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java Wed Oct 16 09:57:23 2019 -0400
@@ -28,6 +28,7 @@
import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.MessageFormat;
import java.util.*;
@@ -35,6 +36,7 @@
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
+import static jdk.jpackage.internal.OverridableResource.createResource;
import static jdk.jpackage.internal.WindowsBundlerParam.*;
@@ -527,12 +529,12 @@
private void prepareBasicProjectConfig(
Map<String, ? super Object> params) throws IOException {
- fetchResource(getConfig_Script(params).getName(),
- I18N.getString("resource.post-install-script"),
- (String) null,
- getConfig_Script(params),
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
+
+ Path scriptPath = getConfig_Script(params).toPath();
+
+ createResource(null, params)
+ .setCategory(I18N.getString("resource.post-install-script"))
+ .saveToFile(scriptPath);
}
private static String relativePath(File basedir, File file) {
@@ -663,7 +665,7 @@
if (INSTALLDIR_CHOOSER.fetchFrom(params)) {
data.put("JpInstallDirChooser", "yes");
String fname = "wixhelper.dll";
- try (InputStream is = getResourceAsStream(fname)) {
+ try (InputStream is = OverridableResource.readDefault(fname)) {
Files.copy(is, Paths.get(
CONFIG_ROOT.fetchFrom(params).getAbsolutePath(),
fname));
@@ -673,14 +675,14 @@
// Copy l10n files.
for (String loc : Arrays.asList("en", "ja", "zh_CN")) {
String fname = "MsiInstallerStrings_" + loc + ".wxl";
- try (InputStream is = getResourceAsStream(fname)) {
+ try (InputStream is = OverridableResource.readDefault(fname)) {
Files.copy(is, Paths.get(
CONFIG_ROOT.fetchFrom(params).getAbsolutePath(),
fname));
}
}
- try (InputStream is = getResourceAsStream("main.wxs")) {
+ try (InputStream is = OverridableResource.readDefault("main.wxs")) {
Files.copy(is, Paths.get(
getConfig_ProjectFile(params).getAbsolutePath()));
}
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsAppImageBuilder.java Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsAppImageBuilder.java Wed Oct 16 09:57:23 2019 -0400
@@ -50,6 +50,7 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import java.util.stream.Stream;
+import static jdk.jpackage.internal.OverridableResource.createResource;
import static jdk.jpackage.internal.StandardBundlerParam.*;
@@ -265,35 +266,21 @@
validateValueAndPut(data, "PRODUCT_NAME", APP_NAME, params);
validateValueAndPut(data, "PRODUCT_VERSION", VERSION, params);
- try (Writer w = Files.newBufferedWriter(
- getConfig_ExecutableProperties(params).toPath(),
- StandardCharsets.UTF_8)) {
- String content = preprocessTextResource(
- getConfig_ExecutableProperties(params).getName(),
- I18N.getString("resource.executable-properties-template"),
- EXECUTABLE_PROPERTIES_TEMPLATE, data,
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
- w.write(content);
- }
+ createResource(EXECUTABLE_PROPERTIES_TEMPLATE, params)
+ .setCategory(I18N.getString("resource.executable-properties-template"))
+ .setSubstitutionData(data)
+ .saveToFile(getConfig_ExecutableProperties(params));
}
private void createLauncherForEntryPoint(
Map<String, ? super Object> params) throws IOException {
- File icon = ICON_ICO.fetchFrom(params);
File iconTarget = getConfig_AppIcon(params);
- InputStream in = locateResource(
- iconTarget.getName(),
- "icon",
- TEMPLATE_APP_ICON,
- icon,
- VERBOSE.fetchFrom(params),
- RESOURCE_DIR.fetchFrom(params));
-
- Files.copy(in, iconTarget.toPath(),
- StandardCopyOption.REPLACE_EXISTING);
+ createResource(TEMPLATE_APP_ICON, params)
+ .setCategory("icon")
+ .setExternal(ICON_ICO.fetchFrom(params))
+ .saveToFile(iconTarget);
writeCfgFile(params, root.resolve(
getLauncherCfgName(params)).toFile());
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties Wed Oct 16 09:57:23 2019 -0400
@@ -30,7 +30,7 @@
param.menu-group.default=Unknown
-resource.executable-properties-template=Template for creating executable properties file.
+resource.executable-properties-template=Template for creating executable properties file
resource.setup-icon=setup dialog icon
resource.post-install-script=script to run after application image is populated
resource.wxl-file-name=MsiInstallerStrings_en.wxl
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties Wed Oct 16 09:57:23 2019 -0400
@@ -30,7 +30,7 @@
param.menu-group.default=Unknown
-resource.executable-properties-template=Template for creating executable properties file.
+resource.executable-properties-template=Template for creating executable properties file
resource.setup-icon=setup dialog icon
resource.post-install-script=script to run after application image is populated
resource.wxl-file-name=MsiInstallerStrings_en.wxl
--- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties Tue Oct 15 14:00:04 2019 -0400
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties Wed Oct 16 09:57:23 2019 -0400
@@ -30,7 +30,7 @@
param.menu-group.default=Unknown
-resource.executable-properties-template=Template for creating executable properties file.
+resource.executable-properties-template=Template for creating executable properties file
resource.setup-icon=setup dialog icon
resource.post-install-script=script to run after application image is populated
resource.wxl-file-name=MsiInstallerStrings_en.wxl
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/OverridableResourceTest.java Wed Oct 16 09:57:23 2019 -0400
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2019, 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 java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import jdk.jpackage.internal.resources.ResourceLocator;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class OverridableResourceTest {
+
+ @Rule
+ public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+ @Test
+ public void testDefault() throws IOException {
+ byte[] actualBytes = saveToFile(new OverridableResource(DEFAULT_NAME));
+
+ try (InputStream is = ResourceLocator.class.getResourceAsStream(
+ DEFAULT_NAME)) {
+ assertArrayEquals(is.readAllBytes(), actualBytes);
+ }
+ }
+
+ @Test
+ public void testDefaultWithSubstitution() throws IOException {
+ OverridableResource resource = new OverridableResource(DEFAULT_NAME);
+
+ List<String> linesBeforeSubstitution = convertToStringList(saveToFile(
+ resource));
+
+ if (SUBSTITUTION_DATA.size() != 1) {
+ // Test setup issue
+ throw new IllegalArgumentException(
+ "Substitution map should contain only a single entry");
+ }
+
+ resource.setSubstitutionData(SUBSTITUTION_DATA);
+ List<String> linesAfterSubstitution = convertToStringList(saveToFile(
+ resource));
+
+ assertEquals(linesBeforeSubstitution.size(), linesAfterSubstitution.size());
+
+ Iterator<String> beforeIt = linesBeforeSubstitution.iterator();
+ Iterator<String> afterIt = linesAfterSubstitution.iterator();
+
+ var substitutionEntry = SUBSTITUTION_DATA.entrySet().iterator().next();
+
+ boolean linesMismatch = false;
+ while (beforeIt.hasNext()) {
+ String beforeStr = beforeIt.next();
+ String afterStr = afterIt.next();
+
+ if (beforeStr.equals(afterStr)) {
+ assertFalse(beforeStr.contains(substitutionEntry.getKey()));
+ } else {
+ linesMismatch = true;
+ assertTrue(beforeStr.contains(substitutionEntry.getKey()));
+ assertTrue(afterStr.contains(substitutionEntry.getValue()));
+ assertFalse(afterStr.contains(substitutionEntry.getKey()));
+ }
+ }
+
+ assertTrue(linesMismatch);
+ }
+
+ @Test
+ public void testCustom() throws IOException {
+ testCustom(DEFAULT_NAME);
+ }
+
+ @Test
+ public void testCustomNoDefault() throws IOException {
+ testCustom(null);
+ }
+
+ private void testCustom(String defaultName) throws IOException {
+ List<String> expectedResourceData = List.of("A", "B", "C");
+
+ Path customFile = createCustomFile("foo", expectedResourceData);
+
+ List<String> actualResourceData = convertToStringList(saveToFile(
+ new OverridableResource(defaultName)
+ .setPublicName(customFile.getFileName())
+ .setResourceDir(customFile.getParent())));
+
+ assertArrayEquals(expectedResourceData.toArray(String[]::new),
+ actualResourceData.toArray(String[]::new));
+ }
+
+ @Test
+ public void testCustomtWithSubstitution() throws IOException {
+ testCustomtWithSubstitution(DEFAULT_NAME);
+ }
+
+ @Test
+ public void testCustomtWithSubstitutionNoDefault() throws IOException {
+ testCustomtWithSubstitution(null);
+ }
+
+ private void testCustomtWithSubstitution(String defaultName) throws IOException {
+ final List<String> resourceData = List.of("A", "[BB]", "C", "Foo",
+ "GoodbyeHello");
+ final Path customFile = createCustomFile("foo", resourceData);
+
+ final Map<String, String> substitutionData = new HashMap(Map.of("B",
+ "Bar", "Foo", "B"));
+ substitutionData.put("Hello", null);
+
+ final List<String> expectedResourceData = List.of("A", "[BarBar]", "C",
+ "B", "Goodbye");
+
+ final List<String> actualResourceData = convertToStringList(saveToFile(
+ new OverridableResource(defaultName)
+ .setPublicName(customFile.getFileName())
+ .setSubstitutionData(substitutionData)
+ .setResourceDir(customFile.getParent())));
+ assertArrayEquals(expectedResourceData.toArray(String[]::new),
+ actualResourceData.toArray(String[]::new));
+
+ // Don't call setPublicName()
+ final Path dstFile = tempFolder.newFolder().toPath().resolve(customFile.getFileName());
+ new OverridableResource(defaultName)
+ .setSubstitutionData(substitutionData)
+ .setResourceDir(customFile.getParent())
+ .saveToFile(dstFile);
+ assertArrayEquals(expectedResourceData.toArray(String[]::new),
+ convertToStringList(Files.readAllBytes(dstFile)).toArray(
+ String[]::new));
+
+ // Verify setSubstitutionData() stores a copy of passed in data
+ Map<String, String> substitutionData2 = new HashMap(substitutionData);
+ var resource = new OverridableResource(defaultName)
+ .setResourceDir(customFile.getParent());
+
+ resource.setSubstitutionData(substitutionData2);
+ substitutionData2.clear();
+ Files.delete(dstFile);
+ resource.saveToFile(dstFile);
+ assertArrayEquals(expectedResourceData.toArray(String[]::new),
+ convertToStringList(Files.readAllBytes(dstFile)).toArray(
+ String[]::new));
+ }
+
+ @Test
+ public void testNoDefault() throws IOException {
+ Path dstFolder = tempFolder.newFolder().toPath();
+ Path dstFile = dstFolder.resolve(Path.of("foo", "bar"));
+
+ new OverridableResource(null).saveToFile(dstFile);
+
+ assertFalse(dstFile.toFile().exists());
+ }
+
+ private final static String DEFAULT_NAME;
+ private final static Map<String, String> SUBSTITUTION_DATA;
+ static {
+ if (Platform.isWindows()) {
+ DEFAULT_NAME = "WinLauncher.template";
+ SUBSTITUTION_DATA = Map.of("COMPANY_NAME", "Foo9090345");
+ } else if (Platform.isLinux()) {
+ DEFAULT_NAME = "template.control";
+ SUBSTITUTION_DATA = Map.of("APPLICATION_PACKAGE", "Package1967");
+ } else if (Platform.isMac()) {
+ DEFAULT_NAME = "Info-lite.plist.template";
+ SUBSTITUTION_DATA = Map.of("DEPLOY_BUNDLE_IDENTIFIER", "12345");
+ } else {
+ throw Platform.throwUnknownPlatformError();
+ }
+ }
+
+ private byte[] saveToFile(OverridableResource resource) throws IOException {
+ Path dstFile = tempFolder.newFile().toPath();
+ resource.saveToFile(dstFile);
+ assertThat(0, is(not(dstFile.toFile().length())));
+
+ return Files.readAllBytes(dstFile);
+ }
+
+ private Path createCustomFile(String publicName, List<String> data) throws
+ IOException {
+ Path resourceFolder = tempFolder.newFolder().toPath();
+ Path customFile = resourceFolder.resolve(publicName);
+
+ Files.write(customFile, data);
+
+ return customFile;
+ }
+
+ private static List<String> convertToStringList(byte[] data) {
+ return List.of(new String(data, StandardCharsets.UTF_8).split("\\R"));
+ }
+}
--- a/test/jdk/tools/jpackage/junit/junit.java Tue Oct 15 14:00:04 2019 -0400
+++ b/test/jdk/tools/jpackage/junit/junit.java Wed Oct 16 09:57:23 2019 -0400
@@ -28,9 +28,4 @@
* @summary jpackage unit tests
* @library ${jtreg.home}/lib/junit.jar
* @run shell run_junit.sh
- * jdk.jpackage.internal.PathGroupTest
- * jdk.jpackage.internal.DeployParamsTest
- * jdk.jpackage.internal.ApplicationLayoutTest
- * jdk.jpackage.internal.ToolValidatorTest
- * jdk.jpackage.internal.AppImageFileTest
*/
--- a/test/jdk/tools/jpackage/junit/run_junit.sh Tue Oct 15 14:00:04 2019 -0400
+++ b/test/jdk/tools/jpackage/junit/run_junit.sh Wed Oct 16 09:57:23 2019 -0400
@@ -9,10 +9,11 @@
exit
fi
-classes=( "$@" )
sources=()
-for c in "${classes[@]}"; do
- sources+=( "${TESTSRC}/$(echo $c | sed -e 's|\.|/|g').java" )
+classes=()
+for s in $(find "${TESTSRC}" -name "*.java" | grep -v junit.java); do
+ sources+=( "$s" )
+ classes+=( $(echo "$s" | sed -e "s|${TESTSRC}/||" -e 's|/|.|g' -e 's/.java$//') )
done
common_args=(\
@@ -28,4 +29,4 @@
# Run junit
"${TESTJAVA}/bin/java" ${TESTVMOPTS} ${TESTJAVAOPTS} \
- "${common_args[@]}" org.junit.runner.JUnitCore "$@"
+ "${common_args[@]}" org.junit.runner.JUnitCore "${classes[@]}"
--- a/test/jdk/tools/jpackage/macosx/base/SigningCheck.java Tue Oct 15 14:00:04 2019 -0400
+++ b/test/jdk/tools/jpackage/macosx/base/SigningCheck.java Wed Oct 16 09:57:23 2019 -0400
@@ -72,8 +72,7 @@
private static void validateCertificate(String key) {
if (key != null) {
- MacCertificate certificate = new MacCertificate(
- key, true);
+ MacCertificate certificate = new MacCertificate(key);
if (!certificate.isValid()) {
TKit.throwSkippedException("Certifcate expired: " + key);
} else {