--- a/make/CompileDemos.gmk Tue Oct 01 20:07:30 2019 -0700
+++ b/make/CompileDemos.gmk Tue Oct 01 23:54:39 2019 -0400
@@ -219,6 +219,11 @@
MAIN_CLASS := transparentruler.Ruler, \
))
+$(eval $(call SetupBuildDemo, JNLPConverter, \
+ DEMO_SUBDIR := jpackage, \
+ MAIN_CLASS := jnlp.converter.Main, \
+))
+
################################################################################
# Copy html and README files.
--- a/make/CompileJavaModules.gmk Tue Oct 01 20:07:30 2019 -0700
+++ b/make/CompileJavaModules.gmk Tue Oct 01 23:54:39 2019 -0400
@@ -380,6 +380,13 @@
################################################################################
+jdk.jpackage_COPY += .gif .png .txt .spec .script .prerm .preinst .postrm .postinst .list .sh \
+ .desktop .copyright .control .plist .template .icns .scpt .entitlements .wxs .wxl .ico .bmp
+
+jdk.jpackage_CLEAN += .properties
+
+################################################################################
+
jdk.jconsole_COPY += .gif .png
jdk.jconsole_CLEAN_FILES += $(wildcard \
--- a/make/common/Modules.gmk Tue Oct 01 20:07:30 2019 -0700
+++ b/make/common/Modules.gmk Tue Oct 01 23:54:39 2019 -0400
@@ -129,6 +129,7 @@
JRE_TOOL_MODULES += \
jdk.jdwp.agent \
jdk.pack \
+ jdk.jpackage \
jdk.scripting.nashorn.shell \
#
@@ -169,6 +170,7 @@
jdk.naming.rmi \
jdk.net \
jdk.pack \
+ jdk.jpackage \
jdk.rmic \
jdk.scripting.nashorn \
jdk.sctp \
@@ -228,6 +230,13 @@
endif
################################################################################
+# jpackage is only on windows, macosx, and linux
+
+ifeq ($(call isTargetOs, windows macosx linux), false)
+ MODULES_FILTER += jdk.jpackage
+endif
+
+################################################################################
# Module list macros
# Use append so that the custom extension may add to these variables
--- a/make/common/NativeCompilation.gmk Tue Oct 01 20:07:30 2019 -0700
+++ b/make/common/NativeCompilation.gmk Tue Oct 01 23:54:39 2019 -0400
@@ -397,6 +397,7 @@
# ARFLAGS the archiver flags to be used
# OBJECT_DIR the directory where we store the object files
# OUTPUT_DIR the directory where the resulting binary is put
+# SYMBOLS_DIR the directory where the debug symbols are put, defaults to OUTPUT_DIR
# INCLUDES only pick source from these directories
# EXCLUDES do not pick source from these directories
# INCLUDE_FILES only compile exactly these files!
@@ -511,8 +512,6 @@
$$(call SetIfEmpty, $1_SYSROOT_CFLAGS, $$($$($1_TOOLCHAIN)_SYSROOT_CFLAGS))
$$(call SetIfEmpty, $1_SYSROOT_LDFLAGS, $$($$($1_TOOLCHAIN)_SYSROOT_LDFLAGS))
- # Make sure the dirs exist.
- $$(call MakeDir, $$($1_OBJECT_DIR) $$($1_OUTPUT_DIR))
$$(foreach d, $$($1_SRC), $$(if $$(wildcard $$d), , \
$$(error SRC specified to SetupNativeCompilation $1 contains missing directory $$d)))
@@ -883,30 +882,31 @@
ifeq ($$($1_COPY_DEBUG_SYMBOLS), true)
ifneq ($$($1_DEBUG_SYMBOLS), false)
+ $$(call SetIfEmpty, $1_SYMBOLS_DIR, $$($1_OUTPUT_DIR))
# Only copy debug symbols for dynamic libraries and programs.
ifneq ($$($1_TYPE), STATIC_LIBRARY)
# Generate debuginfo files.
ifeq ($(call isTargetOs, windows), true)
- $1_EXTRA_LDFLAGS += -debug "-pdb:$$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).pdb" \
- "-map:$$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).map"
- $1_DEBUGINFO_FILES := $$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).pdb \
- $$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).map
+ $1_EXTRA_LDFLAGS += -debug "-pdb:$$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).pdb" \
+ "-map:$$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).map"
+ $1_DEBUGINFO_FILES := $$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).pdb \
+ $$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).map
else ifeq ($(call isTargetOs, linux solaris), true)
- $1_DEBUGINFO_FILES := $$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).debuginfo
+ $1_DEBUGINFO_FILES := $$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).debuginfo
# Setup the command line creating debuginfo files, to be run after linking.
# It cannot be run separately since it updates the original target file
$1_CREATE_DEBUGINFO_CMDS := \
$$($1_OBJCOPY) --only-keep-debug $$($1_TARGET) $$($1_DEBUGINFO_FILES) $$(NEWLINE) \
- $(CD) $$($1_OUTPUT_DIR) && \
+ $(CD) $$($1_SYMBOLS_DIR) && \
$$($1_OBJCOPY) --add-gnu-debuglink=$$($1_DEBUGINFO_FILES) $$($1_TARGET)
else ifeq ($(call isTargetOs, macosx), true)
$1_DEBUGINFO_FILES := \
- $$($1_OUTPUT_DIR)/$$($1_BASENAME).dSYM/Contents/Info.plist \
- $$($1_OUTPUT_DIR)/$$($1_BASENAME).dSYM/Contents/Resources/DWARF/$$($1_BASENAME)
+ $$($1_SYMBOLS_DIR)/$$($1_BASENAME).dSYM/Contents/Info.plist \
+ $$($1_SYMBOLS_DIR)/$$($1_BASENAME).dSYM/Contents/Resources/DWARF/$$($1_BASENAME)
$1_CREATE_DEBUGINFO_CMDS := \
- $(DSYMUTIL) --out $$($1_OUTPUT_DIR)/$$($1_BASENAME).dSYM $$($1_TARGET)
+ $(DSYMUTIL) --out $$($1_SYMBOLS_DIR)/$$($1_BASENAME).dSYM $$($1_TARGET)
endif
# Since the link rule creates more than one file that we want to track,
@@ -928,14 +928,14 @@
$1 += $$($1_DEBUGINFO_FILES)
ifeq ($$($1_ZIP_EXTERNAL_DEBUG_SYMBOLS), true)
- $1_DEBUGINFO_ZIP := $$($1_OUTPUT_DIR)/$$($1_NOSUFFIX).diz
+ $1_DEBUGINFO_ZIP := $$($1_SYMBOLS_DIR)/$$($1_NOSUFFIX).diz
$1 += $$($1_DEBUGINFO_ZIP)
# The dependency on TARGET is needed for debuginfo files
# to be rebuilt properly.
$$($1_DEBUGINFO_ZIP): $$($1_DEBUGINFO_FILES) $$($1_TARGET)
- $(CD) $$($1_OUTPUT_DIR) && \
- $(ZIPEXE) -q -r $$@ $$(subst $$($1_OUTPUT_DIR)/,, $$($1_DEBUGINFO_FILES))
+ $(CD) $$($1_SYMBOLS_DIR) && \
+ $(ZIPEXE) -q -r $$@ $$(subst $$($1_SYMBOLS_DIR)/,, $$($1_DEBUGINFO_FILES))
endif
endif # !STATIC_LIBRARY
@@ -971,6 +971,7 @@
$$($1_TARGET): $$($1_TARGET_DEPS)
$$(call LogInfo, Building static library $$($1_BASENAME))
+ $$(call MakeDir, $$($1_OUTPUT_DIR) $$($1_SYMBOLS_DIR))
$$(call ExecuteWithLog, $$($1_OBJECT_DIR)/$$($1_SAFE_NAME)_link, \
$$($1_AR) $$($1_ARFLAGS) $(AR_OUT_OPTION)$$($1_TARGET) $$($1_ALL_OBJS) \
$$($1_RES))
@@ -1072,7 +1073,9 @@
# Keep as much as possible on one execution line for best performance
# on Windows
$$(call LogInfo, Linking $$($1_BASENAME))
+ $$(call MakeDir, $$($1_OUTPUT_DIR) $$($1_SYMBOLS_DIR))
ifeq ($(call isTargetOs, windows), true)
+
$$(call ExecuteWithLog, $$($1_OBJECT_DIR)/$$($1_SAFE_NAME)_link, \
$$($1_LD) $$($1_LDFLAGS) $$($1_EXTRA_LDFLAGS) $$($1_SYSROOT_LDFLAGS) \
$(LD_OUT_OPTION)$$($1_TARGET) $$($1_LD_OBJ_ARG) $$($1_RES) $$(GLOBAL_LIBS) \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/make/launcher/Launcher-jdk.jpackage.gmk Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,78 @@
+#
+# Copyright (c) 2018, 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.
+#
+
+include LauncherCommon.gmk
+
+
+################################################################################
+
+$(eval $(call SetupBuildLauncher, jpackage, \
+ MAIN_CLASS := jdk.jpackage.main.Main, \
+))
+
+################################################################################
+
+JPACKAGE_APPLAUNCHEREXE_SRC := \
+ $(TOPDIR)/src/jdk.jpackage/$(OPENJDK_TARGET_OS)/native/jpackageapplauncher
+
+# Output app launcher executable in resources dir, and symbols in the object dir
+$(eval $(call SetupJdkExecutable, BUILD_JPACKAGE_APPLAUNCHEREXE, \
+ NAME := jpackageapplauncher, \
+ OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/jpackage/internal/resources, \
+ SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/jpackageapplauncher, \
+ SRC := $(JPACKAGE_APPLAUNCHEREXE_SRC), \
+ TOOLCHAIN := TOOLCHAIN_LINK_CXX, \
+ OPTIMIZATION := LOW, \
+ CFLAGS := $(CXXFLAGS_JDKEXE), \
+ CFLAGS_windows := -EHsc -DLAUNCHERC -DUNICODE -D_UNICODE, \
+ LDFLAGS := $(LDFLAGS_JDKEXE), \
+ LIBS_macosx := -framework Cocoa, \
+ LIBS := $(LIBCXX), \
+ LIBS_linux := -ldl, \
+ LIBS_windows := user32.lib shell32.lib advapi32.lib, \
+))
+
+TARGETS += $(BUILD_JPACKAGE_APPLAUNCHEREXE)
+
+# Build non-console version of launcher
+ifeq ($(call isTargetOs, windows), true)
+
+ $(eval $(call SetupJdkExecutable, BUILD_JPACKAGE_APPLAUNCHERWEXE, \
+ NAME := jpackageapplauncherw, \
+ OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/jpackage/internal/resources, \
+ SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/jpackageapplauncherw, \
+ SRC := $(JPACKAGE_APPLAUNCHEREXE_SRC), \
+ TOOLCHAIN := TOOLCHAIN_LINK_CXX, \
+ OPTIMIZATION := LOW, \
+ CFLAGS := $(CXXFLAGS_JDKEXE), \
+ CFLAGS_windows := -EHsc -DUNICODE -D_UNICODE, \
+ LDFLAGS := $(LDFLAGS_JDKEXE), \
+ LIBS := $(LIBCXX), \
+ LIBS_windows := user32.lib shell32.lib advapi32.lib, \
+ ))
+
+ TARGETS += $(BUILD_JPACKAGE_APPLAUNCHERWEXE)
+endif
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/make/lib/Lib-jdk.jpackage.gmk Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,99 @@
+#
+# Copyright (c) 2018, 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.
+#
+
+include LibCommon.gmk
+
+################################################################################
+
+# Output app launcher library in resources dir, and symbols in the object dir
+$(eval $(call SetupJdkLibrary, BUILD_LIB_APPLAUNCHER, \
+ NAME := applauncher, \
+ OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/jpackage/internal/resources, \
+ SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/libapplauncher, \
+ TOOLCHAIN := TOOLCHAIN_LINK_CXX, \
+ OPTIMIZATION := LOW, \
+ CFLAGS := $(CXXFLAGS_JDKLIB), \
+ CFLAGS_windows := -EHsc -DUNICODE -D_UNICODE, \
+ LDFLAGS := $(LDFLAGS_JDKLIB) $(LDFLAGS_CXX_JDK) \
+ $(call SET_SHARED_LIBRARY_ORIGIN), \
+ LIBS := $(LIBCXX), \
+ LIBS_windows := user32.lib shell32.lib advapi32.lib ole32.lib, \
+ LIBS_linux := -ldl -lpthread, \
+ LIBS_macosx := -ldl -framework Cocoa, \
+))
+
+$(BUILD_LIB_APPLAUNCHER): $(call FindLib, java.base, java)
+
+TARGETS += $(BUILD_LIB_APPLAUNCHER)
+
+################################################################################
+
+ifeq ($(call isTargetOs, windows), true)
+
+ $(eval $(call SetupJdkLibrary, BUILD_LIB_JPACKAGE, \
+ NAME := jpackage, \
+ OPTIMIZATION := LOW, \
+ CFLAGS := $(CXXFLAGS_JDKLIB), \
+ CFLAGS_windows := -EHsc -DUNICODE -D_UNICODE, \
+ LDFLAGS := $(LDFLAGS_JDKLIB) $(LDFLAGS_CXX_JDK) \
+ $(call SET_SHARED_LIBRARY_ORIGIN), \
+ LIBS := $(LIBCXX), \
+ LIBS_windows := user32.lib shell32.lib advapi32.lib ole32.lib, \
+ ))
+
+ TARGETS += $(BUILD_LIB_JPACKAGE)
+
+ # Build Wix custom action helper
+ # Output library in resources dir, and symbols in the object dir
+ $(eval $(call SetupJdkLibrary, BUILD_LIB_WIXHELPER, \
+ NAME := wixhelper, \
+ OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/jpackage/internal/resources, \
+ SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/libwixhelper, \
+ OPTIMIZATION := LOW, \
+ CFLAGS := $(CXXFLAGS_JDKLIB), \
+ CFLAGS_windows := -EHsc -DUNICODE -D_UNICODE -MT, \
+ LDFLAGS := $(LDFLAGS_JDKLIB) $(LDFLAGS_CXX_JDK), \
+ LIBS := $(LIBCXX), \
+ LIBS_windows := msi.lib Shlwapi.lib User32.lib, \
+ ))
+
+ TARGETS += $(BUILD_LIB_WIXHELPER)
+
+ $(eval $(call SetupJdkExecutable, BUILD_JPACKAGE_MSIWRAPPER, \
+ NAME := msiwrapper, \
+ OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/jpackage/internal/resources, \
+ SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/msiwrapper, \
+ SRC := $(TOPDIR)/src/jdk.jpackage/$(OPENJDK_TARGET_OS)/native/msiwrapper, \
+ EXTRA_FILES := $(addprefix $(TOPDIR)/src/jdk.jpackage/$(OPENJDK_TARGET_OS)/native/libjpackage/, \
+ FileUtils.cpp Log.cpp WinSysInfo.cpp tstrings.cpp WinErrorHandling.cpp ErrorHandling.cpp), \
+ CFLAGS := $(CXXFLAGS_JDKEXE) -MT \
+ $(addprefix -I$(TOPDIR)/src/jdk.jpackage/$(OPENJDK_TARGET_OS)/native/, msiwrapper libjpackage), \
+ CFLAGS_windows := -EHsc -DUNICODE -D_UNICODE, \
+ LDFLAGS := $(LDFLAGS_JDKEXE), \
+ LIBS := $(LIBCXX), \
+ ))
+
+ TARGETS += $(BUILD_JPACKAGE_MSIWRAPPER)
+endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/README.txt Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,23 @@
+About JNLPConverter
+===================
+
+JNLPConverter is a standalone tool which uses jpackage to create bundles from
+Java Web Start(TM) Applications and helps to migrate from JNLP to jpackage.
+JNLPConverter will locate and use the jpackage tool from the same JDK as used
+to run JNLPConverter. JNLPConverter supports HTTP/HTTPS and FILE protocol.
+
+Running JNLPConverter
+=====================
+
+To run the JNLPConverter:
+
+ java -jar JNLPConverter.jar <mode> <options>
+
+To get help on JNLPConverter options:
+
+ java -jar JNLPConverter.jar --help
+
+These instructions assume that this installation's version of the java command
+is in your path. If it isn't, then you should either specify the complete path
+to the java command or update your PATH environment variable as described
+in the installation instructions for the Java(TM) SE Development Kit.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/HTTPHelper.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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 jnlp.converter;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import jnlp.converter.parser.GeneralUtil;
+
+public class HTTPHelper {
+
+ public static final int BUFFER_SIZE = 4096;
+
+ public static String downloadFile(String url, String destFolder, String destFileName) throws MalformedURLException, IOException {
+ HttpURLConnection connection = null;
+ String destFile = null;
+
+ try {
+ if (url.contains(" ")) {
+ url = url.replace(" ", "%20");
+ }
+ if (url.contains("\\")) {
+ url = url.replace("\\", "/");
+ }
+
+ URL resource = new URL(url);
+ connection = (HttpURLConnection) resource.openConnection();
+
+ int responseCode = connection.getResponseCode();
+ if (responseCode == HttpURLConnection.HTTP_OK) {
+ destFile = destFolder + File.separator + destFileName;
+ Log.verbose("Downloading " + url + " to " + destFile);
+
+ try (InputStream inputStream = connection.getInputStream();
+ OutputStream outputStream = new FileOutputStream(destFile)) {
+ byte[] buffer = new byte[BUFFER_SIZE];
+
+ int length;
+ do {
+ length = inputStream.read(buffer);
+ if (length > 0) {
+ outputStream.write(buffer, 0, length);
+ }
+ } while (length > 0);
+ }
+ } else {
+ HTTPHelperException e = new HTTPHelperException("Error: Cannot download " + url + ". Server response code: " + responseCode);
+ e.setResponseCode(responseCode);
+ throw e;
+ }
+ } catch (IOException e) {
+ if (e instanceof HTTPHelperException) {
+ throw e;
+ } else {
+ throw new HTTPHelperException("Error: Cannot download " + url + ". " + e.getClass().getSimpleName() + ": " + e.getMessage());
+ }
+ } finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+
+ return destFile;
+ }
+
+ public static String copyFile(String url, String destFolder, String destFileName) throws Exception {
+ if (url.contains(" ")) {
+ url = url.replace(" ", "%20");
+ }
+
+ URI sourceURI = new URI(url);
+
+ String sourceFile = sourceURI.getPath();
+ File file = new File(sourceFile);
+ if (!file.exists()) {
+ throw new FileNotFoundException("Error: " + sourceFile + " does not exist.");
+ }
+
+ String destFile = destFolder + File.separator + destFileName;
+ file = new File(destFile);
+ if (file.exists()) {
+ file.delete();
+ }
+
+ Path sourcePath = Paths.get(sourceURI);
+ Path destPath = Paths.get(destFile);
+ Log.verbose("Copying " + url + " to " + destFile);
+ Files.copy(sourcePath, destPath);
+
+ return destFile;
+ }
+
+ public static boolean isHTTPUrl(String url) {
+ return (url.startsWith("http://") || url.startsWith("https://"));
+ }
+
+ public static byte[] getJNLPBits(String versionedJNLP, String jnlp) throws Exception {
+ String jnlpFilePath = null;
+ byte[] bits = null;
+
+ if (isHTTPUrl(jnlp)) {
+ try {
+ jnlpFilePath = downloadFile(versionedJNLP, JNLPConverter.getJnlpDownloadFolderStatic(), getFileNameFromURL(jnlp));
+ } catch (HTTPHelperException ex) {
+ if (ex.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND &&
+ !versionedJNLP.equals(jnlp)) {
+ Log.warning("Downloading versioned JNLP from " + versionedJNLP + " failed.");
+ Log.warning(ex.getMessage());
+ Log.warning("Downloading " + jnlp + " instead.");
+ jnlpFilePath = downloadFile(jnlp, JNLPConverter.getJnlpDownloadFolderStatic(), getFileNameFromURL(jnlp));
+ } else {
+ throw ex;
+ }
+ }
+ JNLPConverter.markFileToDelete(jnlpFilePath);
+ } else {
+ try {
+ jnlpFilePath = copyFile(versionedJNLP, JNLPConverter.getJnlpDownloadFolderStatic(), getFileNameFromURL(jnlp));
+ } catch (FileNotFoundException ex) {
+ System.out.println("Error copying versioned JNLP from " + versionedJNLP);
+ System.out.println(ex.getMessage());
+ System.out.println("Copying " + jnlp + " instead.");
+ jnlpFilePath = HTTPHelper.copyFile(jnlp, JNLPConverter.getJnlpDownloadFolderStatic(), getFileNameFromURL(jnlp));
+ }
+ JNLPConverter.markFileToDelete(jnlpFilePath);
+ }
+
+ File jnlpFile = new File(jnlpFilePath);
+ if (jnlpFile.exists()) {
+ bits = GeneralUtil.readBytes(new FileInputStream(jnlpFile), jnlpFile.length());
+ }
+
+ return bits;
+ }
+
+ public static String getFileNameFromURL(String url) throws IOException {
+ int index;
+ int index1 = url.lastIndexOf('/');
+ int index2 = url.lastIndexOf('\\');
+
+ if (index1 >= index2) {
+ index = index1;
+ } else {
+ index = index2;
+ }
+
+ if (index != -1) {
+ String name = url.substring(index + 1, url.length());
+ name = name.replace("%20", " ");
+ if (name.endsWith(".jnlp") || name.endsWith(".jar")) { // JNLP or JAR
+ return name;
+ } else if (name.endsWith(".ico")) { // Icons
+ return name;
+ } else {
+ throw new IOException("Error: Unsupported file extension for " + url);
+ }
+ } else {
+ throw new IOException("Error: URL (" + url + ") should end with file name.");
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/HTTPHelperException.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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 jnlp.converter;
+
+import java.io.IOException;
+
+public class HTTPHelperException extends IOException {
+ private int responseCode = -1;
+
+ public HTTPHelperException(String msg) {
+ super(msg);
+ }
+
+ public void setResponseCode(int code) {
+ responseCode = code;
+ }
+
+ public int getResponseCode() {
+ return responseCode;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/JNLPConverter.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,841 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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 jnlp.converter;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import jnlp.converter.parser.JNLPDesc;
+import jnlp.converter.parser.JNLPDesc.AssociationDesc;
+import jnlp.converter.parser.JNLPDesc.IconDesc;
+import jnlp.converter.parser.ResourcesDesc.JARDesc;
+import jnlp.converter.parser.XMLFormat;
+
+public class JNLPConverter {
+
+ private final Options options;
+ private JNLPDesc jnlpd = null;
+ private final List<String> launchArgs = new ArrayList<>();
+
+ private String downloadFolder = null;
+ private String jnlpDownloadFolder = null;
+ private static String jnlpDownloadFolderStatic;
+ private String jarDownloadFolder = null;
+ private String iconDownloadFolder = null;
+ private String propDownloadFolder = null;
+
+ private static String jpackagePath = null;
+
+ private static boolean markFileToDelete = false;
+
+ private static final String FA_EXTENSIONS = "extension";
+ private static final String FA_CONTENT_TYPE = "mime-type";
+ private static final String FA_DESCRIPTION = "description";
+ private static final String FA_ICON = "icon";
+
+ public JNLPConverter(Options options) {
+ this.options = options;
+ jnlpDownloadFolderStatic = getJnlpDownloadFolder();
+ markFileToDelete = (options.keep() == null);
+ }
+
+ public String [] getLaunchArgs() {
+ return launchArgs.toArray(new String[0]);
+ }
+
+ public void convert() {
+ try {
+ loadJNLPDesc();
+ downloadResources();
+ validate();
+ buildLaunchArgs();
+ saveLaunchArgs();
+ runJPackage();
+ } catch (Exception ex) {
+ Log.error(ex.getMessage());
+ }
+ }
+
+ private JNLPDesc getJNLPD(String jnlp) throws Exception {
+ URL codebase = getCodeBase(jnlp);
+ byte[] bits = HTTPHelper.getJNLPBits(jnlp, jnlp);
+ return XMLFormat.parse(bits, codebase, jnlp);
+ }
+
+ private void loadJNLPDesc() throws Exception {
+ String jnlp = options.getJNLP();
+ jnlpd = getJNLPD(jnlp);
+
+ // Check for required options in case of FX
+ if (jnlpd.isFXApp()) {
+ if (!options.isRuntimeImageSet()) {
+ throw new Exception("This is a JavaFX Web-Start application which requires a runtime image capable of running JavaFX applications, which can be specified by the jpackage option --runtime-image (using --jpackage-options).");
+ }
+ }
+
+ // Check href. It can be same as URL we provided or new one
+ // if JNLP has different href or codebase. We assume that
+ // XMLFormat.parse() will handle any errors in href and codebase
+ // correctly.
+ String href = jnlpd.getHref();
+ if (href != null && !href.equalsIgnoreCase(jnlp)) {
+ if (href.startsWith("file:")) {
+ URI hrefURI = new URI(href);
+ URI jnlpURI = new URI(jnlp);
+
+ String hrefPath = hrefURI.getPath();
+ String jnlpPath = jnlpURI.getPath();
+
+ if (!hrefPath.equalsIgnoreCase(jnlpPath)) {
+ jnlp = href;
+ jnlpd = getJNLPD(jnlp);
+ }
+ } else {
+ jnlp = href;
+ jnlpd = getJNLPD(jnlp);
+ }
+ }
+
+ if (jnlpd.getName() == null) {
+ jnlpd.setName(getNameFromURL(jnlp));
+ }
+ }
+
+ private static String getNameFromURL(String url) throws IOException {
+ int index;
+ int index1 = url.lastIndexOf('/');
+ int index2 = url.lastIndexOf('\\');
+
+ if (index1 >= index2) {
+ index = index1;
+ } else {
+ index = index2;
+ }
+
+ if (index != -1) {
+ String name = url.substring(index + 1, url.length());
+ if (name.endsWith(".jnlp")) {
+ return name.substring(0, name.length() - 5);
+ }
+ }
+
+ return null;
+ }
+
+ private URL getCodeBase(String jnlp) throws Exception {
+ int index = jnlp.lastIndexOf('/');
+ if (index != -1) {
+ if (HTTPHelper.isHTTPUrl(jnlp)) {
+ return new URL(jnlp.substring(0, index + 1));
+ } else {
+ String codeBasePath = jnlp.substring(0, index);
+ if (!codeBasePath.endsWith("/")) {
+ codeBasePath += "/";
+ }
+ return new URI(codeBasePath).toURL();
+ }
+ }
+
+ return null;
+ }
+
+ public static void markFileToDelete(String file) {
+ if (file == null || file.isEmpty()) {
+ return;
+ }
+
+ if (markFileToDelete) {
+ try {
+ File f = new File(file);
+ f.deleteOnExit();
+ } catch (Exception e) {
+ // Print exception, but do not fail conversion.
+ Log.warning(e.getLocalizedMessage());
+ }
+ }
+ }
+
+ public static void deleteFile(String file) {
+ try {
+ File f = new File(file);
+ f.delete();
+ } catch (Exception e) {
+ Log.warning(e.getLocalizedMessage());
+ }
+ }
+
+ private void downloadResources() throws Exception {
+ List<JARDesc> jars = jnlpd.getResources();
+ for (JARDesc jar : jars) {
+ if (jar.getVersion() != null) {
+ if (!jnlpd.isVersionEnabled()) {
+ throw new Exception("Error: Version based download protocol is not supported without -Djnlp.versionEnabled=true.");
+ }
+ }
+
+ String destFile = null;
+ if (HTTPHelper.isHTTPUrl(jar.getLocation().toString())) {
+ if (jar.getVersion() != null) {
+ try {
+ destFile = HTTPHelper.downloadFile(jar.getVersionLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString()));
+ } catch (HTTPHelperException ex) {
+ if (ex.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
+ System.out.println("Error downloading versioned JAR from " + jar.getVersionLocation());
+ System.out.println(ex.getMessage());
+ System.out.println("Downloading " + jar.getLocation() + " instead.");
+ destFile = HTTPHelper.downloadFile(jar.getLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString()));
+ } else {
+ throw ex;
+ }
+ }
+ } else {
+ destFile = HTTPHelper.downloadFile(jar.getLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString()));
+ }
+ markFileToDelete(destFile);
+ } else {
+ if (jar.getVersion() != null) {
+ try {
+ destFile = HTTPHelper.copyFile(jar.getVersionLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString()));
+ } catch (FileNotFoundException ex) {
+ System.out.println("Error copying versioned JAR from " + jar.getVersionLocation());
+ System.out.println(ex.getMessage());
+ System.out.println("Copying " + jar.getLocation() + " instead.");
+ destFile = HTTPHelper.copyFile(jar.getLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString()));
+ }
+ } else {
+ destFile = HTTPHelper.copyFile(jar.getLocation().toString(), getJarDownloadFolder(), HTTPHelper.getFileNameFromURL(jar.getLocation().toString()));
+ }
+ markFileToDelete(destFile);
+ }
+
+ if (jar.isNativeLib()) {
+ unpackNativeLib(destFile);
+ deleteFile(destFile);
+ } else {
+ jnlpd.addFile(jar.getName());
+ }
+ }
+
+ IconDesc icon = jnlpd.getIcon();
+ if (icon != null) {
+ String destFile;
+
+ if (HTTPHelper.isHTTPUrl(icon.getLocation())) {
+ destFile = HTTPHelper.downloadFile(icon.getLocation(), getIconDownloadFolder(), HTTPHelper.getFileNameFromURL(icon.getLocation()));
+ } else {
+ destFile = HTTPHelper.copyFile(icon.getLocation(), getIconDownloadFolder(), HTTPHelper.getFileNameFromURL(icon.getLocation()));
+ }
+
+ markFileToDelete(destFile);
+ icon.setLocalLocation(destFile);
+ }
+
+ AssociationDesc [] associations = jnlpd.getAssociations();
+ if (associations != null) {
+ for (AssociationDesc association : associations) {
+ if (association.getIconUrl() != null) {
+ String destFile;
+ if (HTTPHelper.isHTTPUrl(association.getIconUrl())) {
+ destFile = HTTPHelper.downloadFile(association.getIconUrl(), getIconDownloadFolder(), HTTPHelper.getFileNameFromURL(association.getIconUrl()));
+ } else {
+ destFile = HTTPHelper.copyFile(association.getIconUrl(), getIconDownloadFolder(), HTTPHelper.getFileNameFromURL(association.getIconUrl()));
+ }
+
+ markFileToDelete(destFile);
+ association.setIconLocalLocation(destFile);
+ }
+ }
+ }
+ }
+
+ public void unpackNativeLib(String file) throws IOException {
+ try (JarFile jarFile = new JarFile(file)) {
+ Enumeration entries = jarFile.entries();
+
+ while (entries.hasMoreElements()) {
+ JarEntry entry = (JarEntry) entries.nextElement();
+
+ // Skip directories
+ if (entry.isDirectory()) {
+ continue;
+ }
+
+ String entryName = entry.getName();
+ // Skip anything in sub-directories
+ if (entryName.contains("\\") || entryName.contains("/")) {
+ continue;
+ }
+
+ // Skip anything not ending with .dll, .dylib or .so
+ if (!entryName.endsWith(".dll") && !entryName.endsWith(".dylib") && !entryName.endsWith(".so")) {
+ continue;
+ }
+
+ File destFile = new File(getJarDownloadFolder(), entryName);
+ if (destFile.exists()) {
+ Log.warning(destFile.getAbsolutePath() + " already exist and will not be overwriten by native library from " + file + ".");
+ continue;
+ }
+
+ InputStream inputStream = jarFile.getInputStream(entry);
+ FileOutputStream outputStream = new FileOutputStream(destFile);
+
+ byte[] buffer = new byte[HTTPHelper.BUFFER_SIZE];
+ int length;
+ do {
+ length = inputStream.read(buffer);
+ if (length > 0) {
+ outputStream.write(buffer, 0, length);
+ }
+ } while (length > 0);
+
+ jnlpd.addFile(entryName);
+ }
+ }
+ }
+
+ private void validate() {
+ if (jnlpd.getMainJar() == null) {
+ Log.error("Cannot find main jar");
+ }
+
+ if (jnlpd.getMainClass() == null) {
+ Log.error("Cannot find main class");
+ }
+ }
+
+ private void addLaunchArg(String arg, List<String> launchArgs) {
+ if (arg != null && !arg.isEmpty()) {
+ if (!options.isOptionPresent(arg)){
+ launchArgs.add(arg);
+ } else {
+ Log.info(arg + " generated by JNLPConverter is dropped, since it is overwriten via --jpackage-options");
+ }
+ }
+ }
+
+ private void addLaunchArg(String arg, String value, List<String> launchArgs) {
+ if (arg != null && !arg.isEmpty() && value != null && !value.isEmpty()) {
+ if (!options.isOptionPresent(arg)){
+ launchArgs.add(arg);
+ launchArgs.add(value);
+ } else {
+ Log.info(arg + "=" + value +" generated by JNLPConverter is dropped, since it is overwriten via --jpackage-options");
+ }
+ }
+ }
+
+ private void displayLaunchArgs() {
+ if (Log.isVerbose()) {
+ System.out.println();
+ System.out.println("jpackage launch arguments (each argument starts on new line):");
+ launchArgs.forEach((arg) -> {
+ System.out.println(arg);
+ });
+ }
+ }
+
+ private static int fileAssociationsCount = 0;
+ private String getFileAssociationsFile() {
+ String file = getPropDownloadFolder();
+ file += File.separator;
+ file += "fileAssociation";
+ file += String.valueOf(fileAssociationsCount);
+ file += ".properties";
+
+ fileAssociationsCount++;
+
+ return file;
+ }
+
+ private void buildLaunchArgs() {
+ if (options.createAppImage()) {
+ addLaunchArg("create-app-image", launchArgs);
+ } else if (options.createInstaller()) {
+ if (options.getInstallerType() == null) {
+ addLaunchArg("create-installer", launchArgs);
+ } else {
+ addLaunchArg("create-installer", launchArgs);
+ if (options.getInstallerType() != null) {
+ addLaunchArg("--installer-type", options.getInstallerType(), launchArgs);
+ }
+ }
+ }
+
+ // Set verbose for jpackage if it is set for us.
+ if (options.verbose()) {
+ addLaunchArg("--verbose", launchArgs);
+ }
+
+ addLaunchArg("--input", getJarDownloadFolder(), launchArgs);
+ addLaunchArg("--output", options.getOutput(), launchArgs);
+ addLaunchArg("--name", jnlpd.getName(), launchArgs);
+ addLaunchArg("--app-version", jnlpd.getVersion(), launchArgs);
+ addLaunchArg("--vendor", jnlpd.getVendor(), launchArgs);
+ addLaunchArg("--description", jnlpd.getDescription(), launchArgs);
+ addLaunchArg("--icon", jnlpd.getIconLocation(), launchArgs);
+ addLaunchArg("--main-jar", jnlpd.getMainJar(), launchArgs);
+ addLaunchArg("--main-class", jnlpd.getMainClass(), launchArgs);
+
+ addArguments(launchArgs);
+ addJVMArgs(launchArgs);
+
+ if (options.createInstaller()) {
+ if (jnlpd.isDesktopHint()) {
+ if (Platform.isWindows()) {
+ addLaunchArg("--win-shortcut", launchArgs);
+ } else {
+ Log.warning("Ignoring shortcut hint, since it is not supported on current platform.");
+ }
+ }
+
+ if (jnlpd.isMenuHint()) {
+ if (Platform.isWindows()) {
+ addLaunchArg("--win-menu", launchArgs);
+ addLaunchArg("--win-menu-group", jnlpd.getSubMenu(), launchArgs);
+ } else {
+ Log.warning("Ignoring menu hint, since it is not supported on current platform.");
+ }
+ }
+
+ AssociationDesc[] associations = jnlpd.getAssociations();
+ if (associations != null) {
+ for (AssociationDesc association : associations) {
+ String file = getFileAssociationsFile();
+ markFileToDelete(file);
+
+ try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)))) {
+ if (association.getExtensions() != null && association.getMimeType() != null) {
+ out.println(FA_EXTENSIONS + "=" + quote(association.getExtensions()));
+ out.println(FA_CONTENT_TYPE + "=" + quote(association.getMimeType()));
+
+ if (association.getMimeDescription() != null) {
+ out.println(FA_DESCRIPTION + "=" + association.getMimeDescription());
+ }
+
+ if (association.getIconLocalLocation() != null) {
+ out.println(FA_ICON + "=" + quote(association.getIconLocalLocation()));
+ }
+
+ addLaunchArg("--file-associations", file, launchArgs);
+ }
+ } catch (Exception ex) {
+ Log.warning(ex.toString());
+ if (association.getExtensions() != null) {
+ Log.warning("File assoication for " + association.getExtensions() + " will be ignored due to exception above.");
+ }
+ }
+ }
+ }
+ }
+
+ // Add options from --jpackage-options
+ List<String> jpackageOptions = options.getJPackageOptions();
+ jpackageOptions.forEach((option) -> {
+ launchArgs.add(option);
+ });
+
+ displayLaunchArgs();
+ }
+
+ private String getCommandFileName() {
+ Platform platform = Platform.getPlatform();
+ switch (platform) {
+ case WINDOWS:
+ return "run_jpackage.bat";
+ case LINUX:
+ return "run_jpackage.sh";
+ case MAC:
+ return "run_jpackage.sh";
+ default:
+ Log.error("Cannot determine platform type.");
+ return "";
+ }
+ }
+
+ private void saveLaunchArgs() {
+ if (options.keep() != null) {
+ File keepFolder = new File(options.keep());
+ String cmdFile = keepFolder.getAbsolutePath() + File.separator + getCommandFileName();
+ try (PrintWriter out = new PrintWriter(cmdFile)) {
+ out.print(getJPackagePath());
+ launchArgs.forEach((arg) -> {
+ out.print(" ");
+
+ if (arg.contains(" ")) {
+ int len = arg.length();
+ if (len >= 1) {
+ if (arg.charAt(0) != '"' && arg.charAt(len - 1) != '"') {
+ out.print("\"" + arg + "\"");
+ } else {
+ if (Platform.isWindows()) {
+ out.print(arg);
+ } else {
+ arg = escapeQuote(arg);
+ out.print("\"" + arg + "\"");
+ }
+ }
+ }
+ } else {
+ out.print(arg);
+ }
+ });
+ } catch (FileNotFoundException ex) {
+ Log.error("Cannot save file with command line: " + ex.getLocalizedMessage());
+ }
+ }
+ }
+
+ private void runJPackage() {
+ List<String> command = new ArrayList<>();
+ command.add(getJPackagePath());
+ command.addAll(launchArgs);
+
+ ProcessBuilder builder = new ProcessBuilder();
+ builder.inheritIO();
+ builder.command(command);
+
+ try {
+ Process process = builder.start();
+ int exitCode = process.waitFor();
+ if (exitCode != 0) {
+ Log.warning("jpackage retrun non zero code: " + exitCode);
+ }
+ } catch (IOException | InterruptedException ex) {
+ Log.error(ex.getMessage());
+ }
+ }
+
+ private void addArguments(List<String> launchArgs) {
+ List<String> arguments = jnlpd.getArguments();
+ if (arguments.isEmpty()) {
+ return;
+ }
+
+ String argsStr = "";
+ for (int i = 0; i < arguments.size(); i++) {
+ String arg = arguments.get(i);
+ argsStr += quote(arg);
+ if ((i + 1) != arguments.size()) {
+ argsStr += " ";
+ }
+ }
+
+ launchArgs.add("--arguments");
+ if (Platform.isWindows()) {
+ if (argsStr.contains(" ")) {
+ if (argsStr.contains("\"")) {
+ argsStr = escapeQuote(argsStr);
+ }
+ argsStr = "\"" + argsStr + "\"";
+ }
+ }
+ launchArgs.add(argsStr);
+ }
+
+ private void addJVMArgs(List<String> launchArgs) {
+ List<String> jvmArgs = jnlpd.getVMArgs();
+ if (jvmArgs.isEmpty()) {
+ return;
+ }
+
+ String jvmArgsStr = "";
+ for (int i = 0; i < jvmArgs.size(); i++) {
+ String arg = jvmArgs.get(i);
+ jvmArgsStr += quote(arg);
+ if ((i + 1) != jvmArgs.size()) {
+ jvmArgsStr += " ";
+ }
+ }
+
+ launchArgs.add("--java-options");
+ if (Platform.isWindows()) {
+ if (jvmArgsStr.contains(" ")) {
+ if (jvmArgsStr.contains("\"")) {
+ jvmArgsStr = escapeQuote(jvmArgsStr);
+ }
+ jvmArgsStr = "\"" + jvmArgsStr + "\"";
+ }
+ }
+ launchArgs.add(jvmArgsStr);
+ }
+
+ private String quote(String in) {
+ if (in == null) {
+ return null;
+ }
+
+ if (in.isEmpty()) {
+ return "";
+ }
+
+ if (!in.contains("=")) {
+ // Not a property
+ if (in.contains(" ")) {
+ in = escapeQuote(in);
+ return "\"" + in + "\"";
+ }
+ return in;
+ }
+
+ if (!in.contains(" ")) {
+ return in; // No need to quote
+ }
+
+ int paramIndex = in.indexOf("=");
+ if (paramIndex <= 0) {
+ return in; // Something wrong, just skip quoting
+ }
+
+ String param = in.substring(0, paramIndex);
+ String value = in.substring(paramIndex + 1);
+
+ if (value.length() == 0) {
+ return in; // No need to quote
+ }
+
+ value = escapeQuote(value);
+
+ return param + "=" + "\"" + value + "\"";
+ }
+
+ private String escapeQuote(String in) {
+ if (in == null) {
+ return null;
+ }
+
+ if (in.isEmpty()) {
+ return "";
+ }
+
+ if (in.contains("\"")) {
+ // Use code points to preserve non-ASCII chars
+ StringBuilder sb = new StringBuilder();
+ int codeLen = in.codePointCount(0, in.length());
+ for (int i = 0; i < codeLen; i++) {
+ int code = in.codePointAt(i);
+ // Note: No need to escape '\' on Linux or OS X.
+ // jpackage expects us to pass arguments and properties with quotes and spaces as a map
+ // with quotes being escaped with additional \ for internal quotes.
+ // So if we want two properties below:
+ // -Djnlp.Prop1=Some "Value" 1
+ // -Djnlp.Prop2=Some Value 2
+ // jpackage will need:
+ // "-Djnlp.Prop1=\"Some \\"Value\\" 1\" -Djnlp.Prop2=\"Some Value 2\""
+ // but since we using ProcessBuilder to run jpackage we will need to escape
+ // our escape symbols as well, so we will need to pass string below to ProcessBuilder:
+ // "-Djnlp.Prop1=\\\"Some \\\\\\\"Value\\\\\\\" 1\\\" -Djnlp.Prop2=\\\"Some Value 2\\\""
+ switch (code) {
+ case '"':
+ // " -> \" -> \\\"
+ if (i == 0 || in.codePointAt(i - 1) != '\\') {
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint(code);
+ }
+ break;
+ case '\\':
+ // We need to escape already escaped symbols as well
+ if ((i + 1) < codeLen) {
+ int nextCode = in.codePointAt(i + 1);
+ if (nextCode == '"') {
+ // \" -> \\\"
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint(nextCode);
+ } else {
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint(code);
+ }
+ } else {
+ sb.appendCodePoint(code);
+ }
+ break;
+ default:
+ sb.appendCodePoint(code);
+ break;
+ }
+ }
+ return sb.toString();
+ }
+
+ return in;
+ }
+
+ public synchronized String getDownloadFolder() {
+ if (downloadFolder == null) {
+ try {
+ File file;
+ if (options.keep() == null) {
+ Path path = Files.createTempDirectory("JNLPConverter");
+ file = path.toFile();
+ file.deleteOnExit();
+ } else {
+ file = new File(options.keep());
+ if (!file.exists()) {
+ file.mkdir();
+ }
+ }
+
+ downloadFolder = file.getAbsolutePath();
+ } catch (IOException e) {
+ Log.error(e.getMessage());
+ }
+ }
+
+ return downloadFolder;
+ }
+
+ public final synchronized String getJnlpDownloadFolder() {
+ if (jnlpDownloadFolder == null) {
+ File file = new File(getDownloadFolder() + File.separator + "jnlp");
+ file.mkdir();
+ markFileToDelete(getDownloadFolder() + File.separator + "jnlp");
+ jnlpDownloadFolder = file.getAbsolutePath();
+ }
+
+ return jnlpDownloadFolder;
+ }
+
+ public static String getJnlpDownloadFolderStatic() {
+ return jnlpDownloadFolderStatic;
+ }
+
+ public synchronized String getJarDownloadFolder() {
+ if (jarDownloadFolder == null) {
+ File file = new File(getDownloadFolder() + File.separator + "jar");
+ file.mkdir();
+ markFileToDelete(getDownloadFolder() + File.separator + "jar");
+ jarDownloadFolder = file.getAbsolutePath();
+ }
+
+ return jarDownloadFolder;
+ }
+
+ public synchronized String getIconDownloadFolder() {
+ if (iconDownloadFolder == null) {
+ File file = new File(getDownloadFolder() + File.separator + "icon");
+ file.mkdir();
+ markFileToDelete(getDownloadFolder() + File.separator + "icon");
+ iconDownloadFolder = file.getAbsolutePath();
+ }
+
+ return iconDownloadFolder;
+ }
+
+ public synchronized String getPropDownloadFolder() {
+ if (propDownloadFolder == null) {
+ File file = new File(getDownloadFolder() + File.separator + "prop");
+ file.mkdir();
+ markFileToDelete(getDownloadFolder() + File.separator + "prop");
+ propDownloadFolder = file.getAbsolutePath();
+ }
+
+ return propDownloadFolder;
+ }
+
+ public synchronized static String getJPackagePath() {
+ if (jpackagePath == null) {
+ jpackagePath = System.getProperty("java.home");
+ jpackagePath += File.separator;
+ jpackagePath += "bin";
+ jpackagePath += File.separator;
+
+ Platform platform = Platform.getPlatform();
+ switch (platform) {
+ case WINDOWS:
+ jpackagePath += "jpackage.exe";
+ break;
+ case LINUX:
+ jpackagePath += "jpackage";
+ break;
+ case MAC:
+ jpackagePath += "jpackage";
+ break;
+ default:
+ Log.error("Cannot determine platform type.");
+ break;
+ }
+
+ Log.verbose("jpackage: " + jpackagePath);
+ }
+
+ return jpackagePath;
+ }
+
+ public static String getIconFormat(String icon) {
+ // GIF, JPEG, ICO, or PNG
+ if (icon.toLowerCase().endsWith(".gif")) {
+ return "GIF";
+ } else if (icon.toLowerCase().endsWith(".jpg")) {
+ return "JPEG";
+ } else if (icon.toLowerCase().endsWith(".ico")) {
+ return "ICO";
+ } else if (icon.toLowerCase().endsWith(".png")) {
+ return "PNG";
+ }
+
+ return "UNKNOWN";
+ }
+
+ public static boolean isIconSupported(String icon) {
+ Platform platform = Platform.getPlatform();
+ switch (platform) {
+ case WINDOWS:
+ if (icon.endsWith(".ico")) {
+ return true;
+ } else {
+ Log.warning("Icon file format (" + getIconFormat(icon) + ") is not supported on Windows for file " + icon + ".");
+ return false;
+ }
+ case LINUX:
+ if (icon.endsWith(".png")) {
+ return true;
+ } else {
+ Log.warning("Icon file format (" + getIconFormat(icon) + ") is not supported on Linux for file " + icon + ".");
+ return false;
+ }
+ case MAC:
+ Log.warning("Icon file format (" + getIconFormat(icon) + ") is not supported on OS X for file " + icon + ".");
+ return false;
+ }
+
+ return false;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/Log.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
+ * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ */
+package jnlp.converter;
+
+public class Log {
+ private static boolean verbose = false;
+
+ public static void setVerbose(boolean verbose) {
+ Log.verbose = verbose;
+ }
+
+ public static boolean isVerbose() {
+ return verbose;
+ }
+
+ public static void verbose(String msg) {
+ if (verbose) {
+ System.out.println(msg);
+ }
+ }
+
+ public static void info(String msg) {
+ System.out.println("Info: " + msg);
+ }
+
+ public static void warning(String msg) {
+ System.err.println("Warning: " + msg);
+ }
+
+ public static void error(String msg) {
+ System.err.println("Error: " + msg);
+ System.exit(1);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/Main.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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 jnlp.converter;
+
+import java.io.File;
+
+public class Main {
+
+ private static void showHelp() {
+ Options.showHelp();
+ }
+
+ private static void showVersion() {
+ System.out.println("Version: 1.0");
+ }
+
+ private static void createBundle(Options options) {
+ Log.verbose("Creating bundle for JNLP: " + options.getJNLP());
+ Log.verbose("Output folder: " + options.getOutput());
+
+ JNLPConverter converter = new JNLPConverter(options);
+ converter.convert();
+ }
+
+ private static void validateJDK() {
+ String jpackagePath = JNLPConverter.getJPackagePath();
+ File file = new File(jpackagePath);
+ if (!file.exists()) {
+ Log.error("Cannot find " + jpackagePath + ". Make sure you running JNLPConverter with supported JDK version.");
+ }
+ }
+
+ public static void main(String[] args) {
+ Options options = Options.parseArgs(args); // Only valid options will be returned
+
+ Log.setVerbose(options.verbose());
+
+ validateJDK();
+
+ if (options.help()) {
+ showHelp();
+ } else if (options.version()) {
+ showVersion();
+ } else {
+ createBundle(options);
+ }
+
+ System.exit(0);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/Options.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,373 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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 jnlp.converter;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Options {
+
+ private boolean createAppImage = false;
+ private boolean createInstaller = false;
+ private String installerType = null;
+ private String jnlp = null;
+ private String output = null;
+ private String keep = null;
+ private boolean help = false;
+ private boolean verbose = false;
+ private boolean version = false;
+ private final List<String> jpackageOptions = new ArrayList<>();
+ private boolean isRuntimeImageSet = false;
+
+ private static final String JNLP_OPTION_PREFIX = "--jnlp=";
+ private static final String OUTPUT_OPTION_PREFIX = "--output=";
+ private static final String KEEP_OPTION_PREFIX = "--keep=";
+ private static final String JNLP_OPTION_SHORT_PREFIX = "-j";
+ private static final String OUTPUT_OPTION_SHORT_PREFIX = "-o";
+ private static final String KEEP_OPTION_SHORT_PREFIX = "-k";
+
+ private static final String [] INSTALLER_TYPES = {"msi", "exe", "dmg", "pkg",
+ "rpm", "deb"};
+
+ // --output, -o, --input, -i, --main-jar, --main-class
+ private static final String [] BLOCKED_JPACKAGE_OPTIONS = {"--output", "-o", "--input", "-i",
+ "--main-jar", "--main-class"};
+
+ private static final String RUNTIME_IMAGE_OPTION = "--runtime-image";
+
+ private static final String ERR_UNKNOWN_OPTION = "Unknown option: ";
+ private static final String ERR_MISSING_VALUE = "Value is required for option ";
+ private static final String ERR_MISSING_MODE = "Error: create-app-image or create-installer mode is required";
+ private static final String ERR_MISSING_JNLP = "Error: --jnlp is required";
+ private static final String ERR_MISSING_OUTPUT = "Error: --output is required";
+ private static final String ERR_OUTPUT_EXISTS = "Error: output folder already exists";
+ private static final String ERR_KEEP_EXISTS = "Error: folder for --keep argument already exists";
+ private static final String ERR_INVALID_PROTOCOL_JNLP = "Error: Invalid protocol for JNLP file. Only HTTP, HTTPS and FILE protocols are supported.";
+
+ public boolean createAppImage() {
+ return createAppImage;
+ }
+
+ public boolean createInstaller() {
+ return createInstaller;
+ }
+
+ public String getInstallerType() {
+ return installerType;
+ }
+
+ public String getJNLP() {
+ return jnlp;
+ }
+
+ public String getOutput() {
+ return output;
+ }
+
+ public String keep() {
+ return keep;
+ }
+
+ public boolean help() {
+ return help;
+ }
+
+ public boolean verbose() {
+ return verbose;
+ }
+
+ public boolean version() {
+ return version;
+ }
+
+ public List<String> getJPackageOptions() {
+ return jpackageOptions;
+ }
+
+ public boolean isRuntimeImageSet() {
+ return isRuntimeImageSet;
+ }
+
+ // Helper method to dump all options
+ private void display() {
+ System.out.println("Options:");
+ System.out.println("createAppImage: " + createAppImage);
+ System.out.println("createInstaller: " + createInstaller);
+ System.out.println("installerType: " + installerType);
+ System.out.println("jnlp: " + jnlp);
+ System.out.println("output: " + output);
+ System.out.println("keep: " + keep);
+ System.out.println("help: " + help);
+ System.out.println("verbose: " + verbose);
+ System.out.println("version: " + version);
+ for (int i = 0; i < jpackageOptions.size(); i++) {
+ System.out.println("jpackageOptions[" + i + "]: " + jpackageOptions.get(i));
+ }
+ }
+
+ private void validate() {
+ if (help || version) {
+ return;
+ }
+
+ if (!createAppImage && !createInstaller) {
+ optionError(ERR_MISSING_MODE);
+ }
+
+ if (jnlp == null) {
+ optionError(ERR_MISSING_JNLP);
+ } else {
+ int index = jnlp.indexOf(":");
+ if (index == -1 || index == 0) {
+ optionError(ERR_INVALID_PROTOCOL_JNLP);
+ } else {
+ String protocol = jnlp.substring(0, index);
+ if (!protocol.equalsIgnoreCase("http") &&
+ !protocol.equalsIgnoreCase("https") &&
+ !protocol.equalsIgnoreCase("file")) {
+ optionError(ERR_INVALID_PROTOCOL_JNLP);
+ }
+ }
+ }
+
+ if (output == null) {
+ optionError(ERR_MISSING_OUTPUT);
+ } else {
+ File file = new File(output);
+ if (file.exists()) {
+ optionErrorNoHelp(ERR_OUTPUT_EXISTS);
+ }
+ }
+
+ if (keep != null) {
+ File file = new File(keep);
+ if (file.exists()) {
+ optionErrorNoHelp(ERR_KEEP_EXISTS);
+ }
+ }
+
+ jpackageOptions.forEach((option) -> {
+ if (isBlockedOption(option)) {
+ Log.error(option + " is not allowed via --jpackage-options, since it will conflict with "
+ + "same option generated by JNLPConverter.");
+ }
+ });
+ }
+
+ public boolean isOptionPresent(String option) {
+ for (String jpackageOption : jpackageOptions) {
+ if (jpackageOption.equalsIgnoreCase(option)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isBlockedOption(String option) {
+ for (String blockedOption : BLOCKED_JPACKAGE_OPTIONS) {
+ if (blockedOption.equalsIgnoreCase(option)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static void showHelp() {
+// System.out.println("********* Help should not be longer then 80 characters as per JEP-293 *********");
+ System.out.println("Usage: java -jar JNLPConverter.jar <mode> <options>");
+ System.out.println("");
+ System.out.println("where mode is one of:");
+ System.out.println(" create-app-image");
+ System.out.println(" Generates a platform-specific application image.");
+ System.out.println(" create-installer");
+ System.out.println(" Generates a platform-specific installer for the application.");
+ System.out.println("");
+ System.out.println("Possible options include:");
+ System.out.println(" -j, --jnlp <path>");
+ System.out.println(" Full path to JNLP file. Supported protocols are HTTP/HTTPS/FILE.");
+ System.out.println(" -o, --output <path>");
+ System.out.println(" Name of the directory where generated output files are placed.");
+ System.out.println(" -k, --keep <path>");
+ System.out.println(" Keep JNLP, JARs and command line arguments for jpackage");
+ System.out.println(" in directory provided.");
+ System.out.println(" --jpackage-options <options>");
+ System.out.println(" Specify additional jpackage options or overwrite provided by JNLPConverter.");
+ System.out.println(" All jpackage options can be specified except: --output -o, --input -i,");
+ System.out.println(" --main-jar -j and --class -c.");
+ System.out.println(" --installer-type <type>");
+ System.out.println(" The type of the installer to create");
+ System.out.println(" Valid values are: {\"exe\", \"msi\", \"rpm\", \"deb\", \"pkg\", \"dmg\"}");
+ System.out.println(" If this option is not specified (in create-installer mode) all");
+ System.out.println(" supported types of installable packages for the current");
+ System.out.println(" platform will be created.");
+ System.out.println(" -h, --help, -?");
+ System.out.println(" Print this help message");
+ System.out.println(" -v, --verbose");
+ System.out.println(" Enable verbose output.");
+ System.out.println(" --version");
+ System.out.println(" Version information.");
+ System.out.println("To specify an argument for a long option, you can use --<name>=<value> or");
+ System.out.println("--<name> <value>.");
+ System.out.println("To specify proxy server use standard Java properties http.proxyHost and http.proxyPort.");
+ }
+
+ private static boolean isInstallerType(String type) {
+ for (String installerType : INSTALLER_TYPES) {
+ if (installerType.equals(type)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static Options parseArgs(String[] args) {
+ Options options = new Options();
+
+ int index = 0;
+ if (args.length >= 1) {
+ switch (args[0]) {
+ case "create-app-image":
+ options.createAppImage = true;
+ index = 1;
+ break;
+ case "create-installer":
+ options.createInstaller = true;
+ index = 1;
+ break;
+ case "-h":
+ case "--help":
+ case "-?":
+ case "--version":
+ break;
+ default:
+ optionError(Options.ERR_MISSING_MODE);
+ break;
+ }
+ }
+
+ for (int i = index; i < args.length; i++) {
+ String arg = args[i];
+
+ if (arg.equals("--jnlp")) {
+ if (++i >= args.length) {
+ optionError(Options.ERR_MISSING_VALUE, "--jnlp");
+ }
+ options.jnlp = args[i];
+ } else if (arg.startsWith(JNLP_OPTION_PREFIX)) {
+ options.jnlp = arg.substring(JNLP_OPTION_PREFIX.length());
+ } else if (arg.equals("--output")) {
+ if (++i >= args.length) {
+ optionError(Options.ERR_MISSING_VALUE, "--output");
+ }
+ options.output = args[i];
+ } else if (arg.startsWith(OUTPUT_OPTION_PREFIX)) {
+ options.output = arg.substring(OUTPUT_OPTION_PREFIX.length());
+ } else if (arg.equals("--keep")) {
+ if (++i >= args.length) {
+ optionError(Options.ERR_MISSING_VALUE, "--keep");
+ }
+ options.keep = args[i];
+ } else if (arg.startsWith(KEEP_OPTION_PREFIX)) {
+ options.keep = arg.substring(KEEP_OPTION_PREFIX.length());
+ } else if (arg.equals("--help")) {
+ options.help = true;
+ } else if (arg.equals("--verbose")) {
+ options.verbose = true;
+ } else if (arg.equals("--version")) {
+ options.version = true;
+ } else if (arg.equals("-j")) { // short options
+ if (++i >= args.length) {
+ optionError(Options.ERR_MISSING_VALUE, "-j");
+ }
+ options.jnlp = args[i];
+ } else if (arg.startsWith(JNLP_OPTION_SHORT_PREFIX)) {
+ options.jnlp = arg.substring(JNLP_OPTION_SHORT_PREFIX.length());
+ } else if (arg.equals("-o")) {
+ if (++i >= args.length) {
+ optionError(Options.ERR_MISSING_VALUE, "-o");
+ }
+ options.output = args[i];
+ } else if (arg.startsWith(OUTPUT_OPTION_SHORT_PREFIX)) {
+ options.output = arg.substring(OUTPUT_OPTION_SHORT_PREFIX.length());
+ } else if (arg.equals("-k")) {
+ if (++i >= args.length) {
+ optionError(Options.ERR_MISSING_VALUE, "-k");
+ }
+ options.keep = args[i];
+ } else if (arg.startsWith(KEEP_OPTION_SHORT_PREFIX)) {
+ options.keep = arg.substring(KEEP_OPTION_SHORT_PREFIX.length());
+ } else if (arg.equals("-h") || arg.equals("-?")) {
+ options.help = true;
+ } else if (arg.equals("-v")) {
+ options.verbose = true;
+ } else if (arg.equals("--jpackage-options")) {
+ for (i = (i + 1); i < args.length; i++) {
+ if (!options.isRuntimeImageSet) {
+ if (args[i].equals(RUNTIME_IMAGE_OPTION)) {
+ options.isRuntimeImageSet = true;
+ }
+ }
+ options.jpackageOptions.add(args[i]);
+ }
+ } else if (arg.equals("--installer-type")) {
+ if ((i + 1) < args.length) {
+ if (isInstallerType(args[i + 1])) {
+ options.installerType = args[i + 1];
+ i++;
+ }
+ }
+ } else {
+ optionError(ERR_UNKNOWN_OPTION, arg);
+ }
+ }
+
+ //options.display(); // For testing only
+ options.validate();
+
+ return options;
+ }
+
+ private static void optionErrorNoHelp(String msg) {
+ System.out.println(msg);
+ System.exit(1);
+ }
+
+ private static void optionError(String msg) {
+ System.out.println(msg);
+ System.out.println();
+ showHelp();
+ System.exit(1);
+ }
+
+ private static void optionError(String msg, String option) {
+ System.out.println(msg + option);
+ System.out.println();
+ showHelp();
+ System.exit(1);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/Platform.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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 jnlp.converter;
+
+import java.util.regex.Pattern;
+
+public enum Platform {
+ UNKNOWN, WINDOWS, LINUX, MAC;
+ private static final Platform platform;
+ private static final int majorVersion;
+ private static final int minorVersion;
+
+ static {
+ String os = System.getProperty("os.name").toLowerCase();
+
+ if (os.contains("win")) {
+ platform = Platform.WINDOWS;
+ } else if (os.contains("nix") || os.contains("nux")) {
+ platform = Platform.LINUX;
+ } else if (os.contains("mac")) {
+ platform = Platform.MAC;
+ } else {
+ platform = Platform.UNKNOWN;
+ }
+
+ String version = System.getProperty("os.version");
+ String[] parts = version.split(Pattern.quote("."));
+
+ if (parts.length > 0) {
+ majorVersion = Integer.parseInt(parts[0]);
+
+ if (parts.length > 1) {
+ minorVersion = Integer.parseInt(parts[0]);
+ } else {
+ minorVersion = -1;
+ }
+ } else {
+ majorVersion = -1;
+ minorVersion = -1;
+ }
+ }
+
+ private Platform() {
+ }
+
+ public static Platform getPlatform() {
+ return platform;
+ }
+
+ public static boolean isWindows() {
+ return (platform == Platform.WINDOWS);
+ }
+
+ public static int getMajorVersion() {
+ return majorVersion;
+ }
+
+ public static int getMinorVersion() {
+ return minorVersion;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/GeneralUtil.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,415 @@
+/*
+ * Copyright (c) 2006, 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.
+ *
+ * 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 jnlp.converter.parser;
+
+import java.util.Locale;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Handy class to add some utility methods for dealing with property matching
+ * etc.
+ */
+public class GeneralUtil {
+
+ public static boolean prefixMatchStringList(String[] prefixList, String target) {
+ // No prefixes matches everything
+ if (prefixList == null) {
+ return true;
+ }
+ // No target, but a prefix list does not match anything
+ if (target == null) {
+ return false;
+ }
+ for (String prefix : prefixList) {
+ if (target.startsWith(prefix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static String getOSArch() {
+ return System.getProperty("os.arch");
+ }
+
+ public static boolean prefixMatchArch(String[] prefixList) {
+ // No prefixes matches everything
+ if (prefixList == null) {
+ return true;
+ }
+
+ // check for the current arch
+ String arch = getOSArch();
+ for (String prefix : prefixList) {
+ if (arch.startsWith(prefix)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Converts a space delimited string to a list of strings
+ */
+ public static String[] getStringList(String str) {
+ if (str == null) {
+ return null;
+ }
+ ArrayList<String> list = new ArrayList<>();
+ int i = 0;
+ int length = str.length();
+ StringBuffer sb = null;
+ while (i < length) {
+ char ch = str.charAt(i);
+ switch (ch) {
+ case ' ':
+ // A space was hit. Add string to list
+ if (sb != null) {
+ list.add(sb.toString());
+ sb = null;
+ }
+ break;
+ case '\\':
+ // It is a delimiter. Add next character
+ if (i + 1 < length) {
+ ch = str.charAt(++i);
+ if (sb == null) {
+ sb = new StringBuffer();
+ }
+ sb.append(ch);
+ }
+ break;
+ default:
+ if (sb == null) {
+ sb = new StringBuffer();
+ } sb.append(ch);
+ break;
+ }
+ i++; // Next character
+ }
+ // Make sure to add the last part to the list too
+ if (sb != null) {
+ list.add(sb.toString());
+ }
+ if (list.isEmpty()) {
+ return null;
+ }
+ String[] results = new String[list.size()];
+ return list.toArray(results);
+ }
+
+ /**
+ * Checks if string list matches default locale
+ */
+ public static boolean matchLocale(String[] localeList, Locale locale) {
+ // No locale specified matches everything
+ if (localeList == null) {
+ return true;
+ }
+ for (String localeList1 : localeList) {
+ if (matchLocale(localeList1, locale)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if string matches default locale
+ */
+ public static boolean matchLocale(String localeStr, Locale locale) {
+ if (localeStr == null || localeStr.length() == 0) {
+ return true;
+ }
+
+ // Compare against default locale
+ String language;
+ String country;
+ String variant;
+
+ // The locale string is of the form language_country_variant
+ StringTokenizer st = new StringTokenizer(localeStr, "_", false);
+ if (st.hasMoreElements() && locale.getLanguage().length() > 0) {
+ language = st.nextToken();
+ if (!language.equalsIgnoreCase(locale.getLanguage())) {
+ return false;
+ }
+ }
+ if (st.hasMoreElements() && locale.getCountry().length() > 0) {
+ country = st.nextToken();
+ if (!country.equalsIgnoreCase(locale.getCountry())) {
+ return false;
+ }
+ }
+ if (st.hasMoreElements() && locale.getVariant().length() > 0) {
+ variant = st.nextToken();
+ if (!variant.equalsIgnoreCase(locale.getVariant())) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public static long heapValToLong(String heapValue) {
+ if (heapValue == null) {
+ return -1;
+ }
+ long multiplier = 1;
+ if (heapValue.toLowerCase().lastIndexOf('m') != -1) {
+ // units are megabytes, 1 megabyte = 1024 * 1024 bytes
+ multiplier = 1024 * 1024;
+ heapValue = heapValue.substring(0, heapValue.length() - 1);
+ } else if (heapValue.toLowerCase().lastIndexOf('k') != -1) {
+ // units are kilobytes, 1 kilobyte = 1024 bytes
+ multiplier = 1024;
+ heapValue = heapValue.substring(0, heapValue.length() - 1);
+ }
+ long theValue;
+ try {
+ theValue = Long.parseLong(heapValue);
+ theValue = theValue * multiplier;
+ } catch (NumberFormatException e) {
+ theValue = -1;
+ }
+ return theValue;
+ }
+
+ public static byte[] readBytes(InputStream is, long size) throws IOException {
+ // Sanity on file size (restrict to 1M)
+ if (size > 1024 * 1024) {
+ throw new IOException("File too large");
+ }
+
+ BufferedInputStream bis;
+ if (is instanceof BufferedInputStream) {
+ bis = (BufferedInputStream) is;
+ } else {
+ bis = new BufferedInputStream(is);
+ }
+
+ if (size <= 0) {
+ size = 10 * 1024; // Default to 10K
+ }
+ byte[] b = new byte[(int) size];
+ int n;
+ int bytesRead = 0;
+ n = bis.read(b, bytesRead, b.length - bytesRead);
+ while (n != -1) {
+ bytesRead += n;
+ // Still room in array
+ if (b.length == bytesRead) {
+ byte[] bb = new byte[b.length * 2];
+ System.arraycopy(b, 0, bb, 0, b.length);
+ b = bb;
+ }
+ // Read next line
+ n = bis.read(b, bytesRead, b.length - bytesRead);
+ }
+ bis.close();
+ is.close();
+
+ if (bytesRead != b.length) {
+ byte[] bb = new byte[bytesRead];
+ System.arraycopy(b, 0, bb, 0, bytesRead);
+ b = bb;
+ }
+ return b;
+ }
+
+ public static String getOSFullName() {
+ return System.getProperty("os.name");
+ }
+
+ /**
+ * Makes sure a URL is a path URL, i.e., ends with '/'
+ */
+ public static URL asPathURL(URL url) {
+ if (url == null) {
+ return null;
+ }
+
+ String path = url.getFile();
+ if (path != null && !path.endsWith("/")) {
+ try {
+ return new URL(url.getProtocol(),
+ url.getHost(),
+ url.getPort(),
+ url.getFile() + "/");
+ } catch (MalformedURLException mue) {
+ // Should not happen
+ }
+ }
+ // Just return same URl
+ return url;
+ }
+
+ public static Locale getDefaultLocale() {
+ return Locale.getDefault();
+ }
+
+ public static String toNormalizedString(URL u) {
+ if (u == null) {
+ return "";
+ }
+
+ try {
+ if (u.getPort() == u.getDefaultPort()) {
+ u = new URL(u.getProtocol().toLowerCase(),
+ u.getHost().toLowerCase(), -1, u.getFile());
+ } else {
+ u = new URL(u.getProtocol().toLowerCase(),
+ u.getHost().toLowerCase(), u.getPort(), u.getFile());
+ }
+ } catch (MalformedURLException ex) {
+ }
+ return u.toExternalForm();
+ }
+
+ public static boolean sameURLs(URL u1, URL u2) {
+ if (u1 == null || u2 == null || (u1 == u2)) {
+ return (u1 == u2);
+ }
+ //NB: do not use URL.sameFile() as it will do DNS lookup
+ // Also, do quick check before slow string comparisons
+ String f1 = u1.getFile();
+ String f2 = u2.getFile();
+ return (f1.length() == f2.length()) && sameBase(u1, u2)
+ && f1.equalsIgnoreCase(f2);
+ }
+
+ public static boolean sameBase(URL u1, URL u2) {
+ return u1 != null && u2 != null &&
+ sameHost(u1, u2) && samePort(u1, u2) && sameProtocol(u1, u2);
+ }
+
+ private static boolean sameProtocol(URL u1, URL u2) {
+ //protocols are known to be lowercase
+ return u1.getProtocol().equals(u2.getProtocol());
+ }
+
+ private static boolean sameHost(URL u1, URL u2) {
+ String host = u1.getHost();
+ String otherHost = u2.getHost();
+ if (host == null || otherHost == null) {
+ return (host == null && otherHost == null);
+ } else {
+ //avoid slow comparison for strings of different length
+ return ((host.length() == otherHost.length())
+ && host.equalsIgnoreCase(otherHost));
+ }
+ }
+
+ private static boolean samePort(URL u1, URL u2) {
+ return getPort(u1) == getPort(u2);
+ }
+
+ public static int getPort(URL u) {
+ if (u.getPort() != -1) {
+ return u.getPort();
+ } else {
+ return u.getDefaultPort();
+ }
+ }
+
+ public static URL getBase(URL url) {
+ if (url == null) return null;
+ String file = url.getFile();
+ if (file != null) {
+ int idx = file.lastIndexOf('/');
+ if (idx != -1 ) {
+ file = file.substring(0, idx + 1);
+ }
+ try {
+ return new URL(
+ url.getProtocol(),
+ url.getHost(),
+ url.getPort(),
+ file);
+ } catch(MalformedURLException mue) {
+ System.err.println(mue.getMessage());
+ }
+ }
+ // Just return same URL
+ return url;
+ }
+
+ private static String getEmbeddedVersionPath(String path, String version) {
+ int index = path.lastIndexOf("/");
+ String filename = path.substring(index + 1);
+ path = path.substring(0, index + 1);
+
+ String ext = null;
+ index = filename.lastIndexOf(".");
+ if (index != -1) {
+ ext = filename.substring(index + 1);
+ filename = filename.substring(0, index);
+ }
+
+ StringBuilder filenameSB = new StringBuilder(filename);
+ if (version != null) {
+ filenameSB.append("__V");
+ filenameSB.append(version);
+ }
+ if (ext != null) {
+ filenameSB.append(".");
+ filenameSB.append(ext);
+ }
+
+ path += filenameSB.toString();
+ return path;
+ }
+
+ public static URL getEmbeddedVersionURL(URL u, String version) throws Exception {
+ if (u == null) {
+ return null;
+ }
+
+ if (version == null || version.indexOf("*") != -1
+ || version.indexOf("+") != -1) {
+ // Do not support * or + in version string
+ return u;
+ }
+
+ URL versionURL = null;
+
+ String protocol = u.getProtocol();
+ String host = u.getHost();
+ int port = u.getPort();
+ String path = u.getPath();
+
+ path = getEmbeddedVersionPath(path, version);
+
+ versionURL = new URL(protocol, host, port, path);
+
+ return versionURL;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/JNLPDesc.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,617 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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 jnlp.converter.parser;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Properties;
+import jnlp.converter.Log;
+
+import jnlp.converter.parser.ResourcesDesc.JARDesc;
+import jnlp.converter.parser.ResourcesDesc.JREDesc;
+
+public class JNLPDesc {
+ private String specVersion = null;
+ private String codebase = null;
+ private String version = null;
+ private String href = null;
+ private String name = null;
+ private String title = null;
+ private String vendor = null;
+ private String mainJar = null;
+ private String [] descriptions = null;
+ private IconDesc [] icons = null;
+ private ShortcutDesc shortcuts = null;
+ private AssociationDesc [] associations = null;
+ private String mainClass = null;
+ private final List<String> arguments = new ArrayList<>();
+ private final List<String> files = new ArrayList<>();
+ private final List<JARDesc> resources = new ArrayList<>();
+ private final List<String> vmArgs = new ArrayList<>();
+ private boolean isLibrary = false;
+ private boolean isInstaller = false;
+ private boolean isJRESet = false;
+ private ResourcesDesc resourcesDesc;
+ private boolean isVersionEnabled = false;
+ private boolean isSandbox = true;
+ private boolean isFXApp = false;
+
+ public void setSpecVersion(String specVersion) {
+ this.specVersion = specVersion;
+
+ // Valid values are 1.0, 1.5, 6.0, 6.0.10, 6.0.18, 7.0, 8.20, 9 or a wildcard such as 1.0+.
+ if (!specVersion.startsWith("1.0") &&
+ !specVersion.startsWith("1.5") &&
+ !specVersion.startsWith("6.0") &&
+ !specVersion.startsWith("6.0.10") &&
+ !specVersion.startsWith("6.0.18") &&
+ !specVersion.startsWith("7.0") &&
+ !specVersion.startsWith("8.20") &&
+ !specVersion.startsWith("9")) {
+ System.out.println("Warning: Invalid version of the JNLP specification found: "
+ + specVersion + ". Valid values are 1.0, 1.5, 6.0, 6.0.10, 6.0.18, 7.0,"
+ + " 8.20, 9 or a wildcard such as 1.0+.");
+ }
+ }
+
+ public String getSpecVersion() {
+ return specVersion;
+ }
+
+ public void setCodebase(String codebase) {
+ this.codebase = codebase;
+ }
+
+ public String getCodebase() {
+ return codebase;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setHref(String href) {
+ this.href = href;
+ }
+
+ public String getHref() {
+ return href;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setVendor(String vendor) {
+ this.vendor = vendor;
+ }
+
+ public String getVendor() {
+ return vendor;
+ }
+
+ public void setMainJar(String mainJar) {
+ if (this.mainJar == null) {
+ this.mainJar = mainJar;
+ } else {
+ Log.warning("Main jar already set to '" + this.mainJar + "'. "
+ + "Attempt to set main jar to '" + mainJar + "' will be ignored.");
+ }
+ }
+
+ public String getMainJar() {
+ return mainJar;
+ }
+
+ public void setDescriptions(String [] descriptions) {
+ this.descriptions = descriptions;
+ }
+
+ public String getDescription() {
+ String description = null;
+ if (descriptions != null) {
+ if (descriptions[InformationDesc.DESC_DEFAULT] != null) {
+ description = descriptions[InformationDesc.DESC_DEFAULT];
+ } else if (descriptions[InformationDesc.DESC_SHORT] != null) {
+ description = descriptions[InformationDesc.DESC_SHORT];
+ } else if (descriptions[InformationDesc.DESC_ONELINE] != null) {
+ description = descriptions[InformationDesc.DESC_ONELINE];
+ } else if (descriptions[InformationDesc.DESC_TOOLTIP] != null) {
+ description = descriptions[InformationDesc.DESC_TOOLTIP];
+ }
+
+ if (description != null) {
+ if (description.contains("\r") || description.contains("\n")) {
+ Log.warning("Multiple lines of text in description is not supported and description will be converted to single line by replacing new lines with spaces.");
+ Log.warning("Original description:");
+ Log.warning(description);
+ String descs[] = description.split("\n");
+ description = "";
+ for (String desc : descs) {
+ desc = desc.trim();
+ if (desc.endsWith("\r")) { // In case new line was \r\n
+ if (desc.length() != 1) {
+ desc = desc.substring(0, desc.length() - 1);
+ } else {
+ continue;
+ }
+ }
+
+ if (desc.isEmpty()) {
+ continue;
+ }
+
+ if (!description.isEmpty()) {
+ description += " ";
+ }
+
+ description += desc;
+ }
+ Log.warning("Converted description:");
+ Log.warning(description);
+ }
+ }
+ }
+
+ return description;
+ }
+
+ public void setIcons(IconDesc [] icons) {
+ this.icons = icons;
+ }
+
+ public IconDesc getIcon() {
+ for (IconDesc icon : icons) {
+ if (icon.getKind() == IconDesc.ICON_KIND_DEFAULT) {
+ return icon;
+ }
+ }
+
+ for (IconDesc icon : icons) {
+ if (icon.getKind() == IconDesc.ICON_KIND_SHORTCUT) {
+ return icon;
+ }
+ }
+
+ return null;
+ }
+
+ public String getIconLocation() {
+ IconDesc icon = getIcon();
+ if (icon != null) {
+ return icon.getLocalLocation();
+ }
+
+ return null;
+ }
+
+ public void setShortcuts(ShortcutDesc shortcuts) {
+ this.shortcuts = shortcuts;
+ }
+
+ public boolean isDesktopHint() {
+ if (shortcuts != null) {
+ return shortcuts.getDesktop();
+ }
+
+ return false;
+ }
+
+ public boolean isMenuHint() {
+ if (shortcuts != null) {
+ return shortcuts.getMenu();
+ }
+
+ return false;
+ }
+
+ public String getSubMenu() {
+ if (shortcuts != null) {
+ return shortcuts.getSubmenu();
+ }
+
+ return null;
+ }
+
+ public void setAssociations(AssociationDesc [] associations) {
+ this.associations = associations;
+ }
+
+ public AssociationDesc [] getAssociations() {
+ return associations;
+ }
+
+ public void setMainClass(String mainClass, boolean isJavafxDesc) {
+ if (isJavafxDesc) {
+ this.mainClass = mainClass;
+ } else if (this.mainClass == null) {
+ this.mainClass = mainClass;
+ }
+ }
+
+ public String getMainClass() {
+ return mainClass;
+ }
+
+ public void addArguments(String argument) {
+ if (argument != null && !argument.isEmpty()) {
+ arguments.add(argument);
+ }
+ }
+
+ public List<String> getArguments() {
+ return arguments;
+ }
+
+ public void setProperty(String name, String value) {
+ if (name.equalsIgnoreCase("jnlp.versionEnabled") && value.equalsIgnoreCase("true")) {
+ isVersionEnabled = true;
+ return;
+ }
+
+ addVMArg("-D" + name + "=" + value);
+ }
+
+ public boolean isVersionEnabled() {
+ return isVersionEnabled;
+ }
+
+ public boolean isSandbox() {
+ return isSandbox;
+ }
+
+ public void setIsSandbox(boolean value) {
+ isSandbox = value;
+ }
+
+ public boolean isFXApp() {
+ return isFXApp;
+ }
+
+ public void setIsFXApp(boolean value) {
+ isFXApp = value;
+ }
+
+ public void addFile(String file) {
+ if (file != null) {
+ files.add(file);
+ }
+ }
+
+ public List<String> getFiles() {
+ return files;
+ }
+
+ private boolean isResourceExists(JARDesc resource) {
+ for (JARDesc r : resources) {
+ if (r.getLocation().equals(resource.getLocation())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void addResource(JARDesc resource) {
+ if (resource != null) {
+ if (isResourceExists(resource)) {
+ Log.warning("Ignoring repeated resource " + resource.getLocation());
+ return;
+ }
+ resources.add(resource);
+ }
+ }
+
+ public List<JARDesc> getResources() {
+ return resources;
+ }
+
+ public void addVMArg(String arg) {
+ if (arg != null) {
+ vmArgs.add(arg);
+ }
+ }
+
+ public List<String> getVMArgs() {
+ return vmArgs;
+ }
+
+ public void setIsLibrary(boolean isLibrary) {
+ this.isLibrary = isLibrary;
+ }
+
+ public boolean isLibrary() {
+ return isLibrary;
+ }
+
+ public void setIsInstaller(boolean isInstaller) {
+ this.isInstaller = isInstaller;
+ }
+
+ public boolean isInstaller() {
+ return isInstaller;
+ }
+
+ public void setIsJRESet(boolean isJRESet) {
+ this.isJRESet = isJRESet;
+ }
+
+ public boolean isJRESet() {
+ return isJRESet;
+ }
+
+ public void setResourcesDesc(ResourcesDesc resourcesDesc) {
+ this.resourcesDesc = resourcesDesc;
+ }
+
+ public ResourcesDesc getResourcesDesc() {
+ return resourcesDesc;
+ }
+
+ public void parseResourceDesc() throws Exception {
+ if (resourcesDesc != null && !resourcesDesc.isEmpty()) {
+ setMainJar(resourcesDesc.getMainJar().getName());
+
+ JARDesc[] jars = resourcesDesc.getAllJarDescs();
+ for (JARDesc jar : jars) {
+ addResource(jar);
+ }
+
+ JREDesc jreDesc = resourcesDesc.getJreDesc();
+ if (jreDesc != null) {
+ String [] args = jreDesc.getVmArgsList();
+ if (args != null) {
+ for (String arg : args) {
+ addVMArg(arg);
+ }
+ }
+
+ if (jreDesc.getMinHeap() != -1) {
+ addVMArg("-Xms" + jreDesc.getMinHeap());
+ }
+
+ if (jreDesc.getMaxHeap() != -1) {
+ addVMArg("-Xmx" + jreDesc.getMaxHeap());
+ }
+ }
+
+ Properties props = resourcesDesc.getResourceProperties();
+ Enumeration e = props.propertyNames();
+ while (e.hasMoreElements()) {
+ String key = (String) e.nextElement();
+ String value = props.getProperty(key);
+ setProperty(key, value);
+ }
+ }
+ }
+
+ public static class InformationDesc {
+
+ private final String _title;
+ private final String _vendor;
+ private final String[] _descriptions;
+ private final IconDesc[] _icons;
+ private ShortcutDesc _shortcutHints;
+ private AssociationDesc[] _associations;
+
+ public InformationDesc(String title, String vendor,
+ String[] descriptions,
+ IconDesc[] icons,
+ ShortcutDesc shortcutHints,
+ AssociationDesc[] associations) {
+ _title = (title == null) ? "" : title;
+ _vendor = (vendor == null) ? "" : vendor;
+ if (descriptions == null) {
+ descriptions = new String[NOF_DESC];
+ }
+ _descriptions = descriptions;
+ _icons = icons;
+ _shortcutHints = shortcutHints;
+ _associations = associations;
+ }
+
+ /**
+ * Constants for the getInfoDescription
+ */
+ final public static int DESC_DEFAULT = 0;
+ final public static int DESC_SHORT = 1;
+ final public static int DESC_ONELINE = 2;
+ final public static int DESC_TOOLTIP = 3;
+ final public static int NOF_DESC = 4;
+
+ /**
+ * Information
+ */
+ public String getTitle() {
+ return _title;
+ }
+
+ public String getVendor() {
+ return _vendor;
+ }
+
+ public IconDesc[] getIcons() {
+ return _icons;
+ }
+
+ public ShortcutDesc getShortcut() {
+ if (_shortcutHints == null) {
+ return null;
+ }
+ return new ShortcutDesc(_shortcutHints.getDesktop(), _shortcutHints.getMenu(), _shortcutHints.getSubmenu());
+ }
+
+ public AssociationDesc[] getAssociations() {
+ return _associations;
+ }
+
+ /**
+ * Sets new shortcut hints.
+ *
+ * @param shortcutDesc the new shortcut hints to set
+ */
+ public void setShortcut(ShortcutDesc shortcut) {
+ _shortcutHints = shortcut;
+ }
+
+ /**
+ * Sets new associations.
+ *
+ * @param assoc the association to set
+ */
+ public void setAssociation(AssociationDesc assoc) {
+ if (assoc == null) {
+ _associations = null;
+ } else {
+ _associations = new AssociationDesc[]{assoc};
+ }
+ }
+
+ /**
+ * Returns the description of the given kind. will return null if none
+ * there
+ */
+ public String getDescription(int kind) {
+ return _descriptions[kind];
+ }
+
+ public String[] getDescription() {
+ return _descriptions;
+ }
+ }
+
+ public static class IconDesc {
+
+ private final String _location;
+ private String _localLocation;
+ private final int _kind;
+
+ final public static int ICON_KIND_DEFAULT = 0;
+ final public static int ICON_KIND_SHORTCUT = 5;
+
+ public IconDesc(URL location, int kind) {
+ _location = location.toExternalForm();
+ _kind = kind;
+ }
+
+ public String getLocation() {
+ return _location;
+ }
+
+ public void setLocalLocation(String localLocation) {
+ _localLocation = localLocation;
+ }
+
+ public String getLocalLocation() {
+ return _localLocation;
+ }
+
+ public int getKind() {
+ return _kind;
+ }
+ }
+
+ public static class ShortcutDesc {
+
+ private final boolean _desktop;
+ private final boolean _menu;
+ private final String _submenu;
+
+ public ShortcutDesc(boolean desktop, boolean menu, String submenu) {
+ _desktop = desktop;
+ _menu = menu;
+ _submenu = submenu;
+ }
+
+ public boolean getDesktop() {
+ return _desktop;
+ }
+
+ public boolean getMenu() {
+ return _menu;
+ }
+
+ public String getSubmenu() {
+ return _submenu;
+ }
+ }
+
+ public static class AssociationDesc {
+
+ private final String _extensions;
+ private final String _mimeType;
+ private final String _description;
+ private final String _icon;
+ private String _iconLocalLocation;
+
+ public AssociationDesc(String extensions, String mimeType, String description, URL icon) {
+ _extensions = extensions;
+ _mimeType = mimeType;
+ _description = description;
+ _icon = (icon != null) ? icon.toExternalForm() : null;
+ }
+
+ public void setIconLocalLocation(String localLocation) {
+ _iconLocalLocation = localLocation;
+ }
+
+ public String getIconLocalLocation() {
+ return _iconLocalLocation;
+ }
+
+ public String getExtensions() {
+ return _extensions;
+ }
+
+ public String getMimeType() {
+ return _mimeType;
+ }
+
+ public String getMimeDescription() {
+ return _description;
+ }
+
+ public String getIconUrl() {
+ return _icon;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/ResourceType.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2006, 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.
+ *
+ * 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 jnlp.converter.parser;
+
+/*
+ * Public super class for all resource entries
+ */
+interface ResourceType {
+ /** Visit this specific type */
+ void visit(ResourceVisitor visitor) throws Exception;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/ResourceVisitor.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2006, 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.
+ *
+ * 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 jnlp.converter.parser;
+
+import jnlp.converter.parser.ResourcesDesc.JARDesc;
+import jnlp.converter.parser.ResourcesDesc.PropertyDesc;
+import jnlp.converter.parser.ResourcesDesc.JREDesc;
+import jnlp.converter.parser.ResourcesDesc.ExtensionDesc;
+
+/**
+ * A visitor class for the various ResourceType objects
+ * with dummy visit methods for all types.
+ */
+public class ResourceVisitor {
+
+ public void visitJARDesc(JARDesc jad) {
+ }
+
+ public void visitPropertyDesc(PropertyDesc prd) {
+ }
+
+ public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
+ }
+
+ public void visitJREDesc(JREDesc jrd) {
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/ResourcesDesc.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,665 @@
+/*
+ * Copyright (c) 2006, 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.
+ *
+ * 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 jnlp.converter.parser;
+
+import java.net.URL;
+import java.util.Properties;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import jnlp.converter.HTTPHelper;
+
+/**
+ * This class contains information about the codebase and properties, i.e., how
+ * to locate the classes and optional-packages
+ */
+public class ResourcesDesc implements ResourceType {
+
+ private final List<ResourceType> _list;
+ private volatile JNLPDesc _parent = null;
+
+ /**
+ * Create empty resource list
+ */
+ public ResourcesDesc() {
+ _list = new CopyOnWriteArrayList<>();
+ }
+
+ public JNLPDesc getParent() {
+ return _parent;
+ }
+
+ void setParent(JNLPDesc parent) {
+ _parent = parent;
+ for (int i = 0; i < _list.size(); i++) {
+ Object o = _list.get(i);
+ if (o instanceof JREDesc) {
+ JREDesc jredesc = (JREDesc) o;
+ if (jredesc.getNestedResources() != null) {
+ jredesc.getNestedResources().setParent(parent);
+ }
+ }
+ }
+ }
+
+ public void addResource(ResourceType rd) {
+ if (rd != null) {
+ _list.add(rd);
+ }
+ }
+
+ boolean isEmpty() {
+ return _list.isEmpty();
+ }
+
+ public JARDesc[] getLocalJarDescs() {
+ ArrayList<JARDesc> jds = new ArrayList<>(_list.size());
+ for (ResourceType rt : _list) {
+ if (rt instanceof JARDesc) {
+ jds.add((JARDesc) rt);
+ }
+ }
+ return jds.toArray(new JARDesc[jds.size()]);
+ }
+
+ public JREDesc getJreDesc() {
+ for (ResourceType rt : _list) {
+ if (rt instanceof JREDesc) {
+ return (JREDesc)rt;
+ }
+ }
+
+ return null;
+ }
+
+ public ExtensionDesc[] getExtensionDescs() throws Exception {
+ final ArrayList<ExtensionDesc> extList = new ArrayList<>();
+ visit(new ResourceVisitor() {
+ @Override
+ public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
+ // add all extensiondesc recursively
+ addExtToList(extList);
+ }
+ });
+ return extList.toArray(new ExtensionDesc[extList.size()]);
+ }
+
+ public JARDesc[] getAllJarDescs() throws Exception {
+ List<JARDesc> jarList = new ArrayList<>();
+ addJarsToList(jarList);
+ return jarList.toArray(new JARDesc[jarList.size()]);
+ }
+
+ /**
+ * Add to a list of all the ExtensionDesc. This method goes recusivly through
+ * all ExtensionDesc
+ */
+ private void addExtToList(final List<ExtensionDesc> list) throws Exception {
+ // Iterate through list an add ext jnlp to the list.
+ visit(new ResourceVisitor() {
+ @Override
+ public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
+ if (ed.getExtensionDesc() != null) {
+ ed.getExtensionDesc().getMainJar();
+ ResourcesDesc rd = ed.getExtensionDesc().getResourcesDesc();
+ if (rd != null) {
+ rd.addExtToList(list);
+ }
+ }
+ list.add(ed);
+ }
+ });
+ }
+
+ private void addJarsToList(final List<JARDesc> list) throws Exception {
+
+ // Iterate through list an add resources to the list.
+ // The ordering of resources are preserved
+ visit(new ResourceVisitor() {
+ @Override
+ public void visitJARDesc(JARDesc jd) {
+ list.add(jd);
+ }
+
+ @Override
+ public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
+ if (ed.getExtensionDesc() != null) {
+ ResourcesDesc rd = ed.getExtensionDesc().getResourcesDesc();
+ if (rd != null) {
+ rd.addJarsToList(list);
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Get all the resources needed when a specific resource is requested.
+ * Returns null if no resource was found
+ */
+ public JARDesc[] getResource(final URL location) throws Exception {
+ final JARDesc[] resources = new JARDesc[1];
+ // Find the given resource
+ visit(new ResourceVisitor() {
+ @Override
+ public void visitJARDesc(JARDesc jd) {
+ if (GeneralUtil.sameURLs(jd.getLocation(), location)) {
+ resources[0] = jd;
+ }
+ }
+ });
+
+ // Found no resource?
+ if (resources[0] == null) {
+ return null;
+ }
+
+ // No part, so just one resource
+ return resources;
+ }
+
+ /* Returns the Expected Main Jar
+ * first jar with attribute main="true"
+ * else first jar if none has that attribute
+ * will look in extensions, and nested resource blocks if matching
+ */
+ protected JARDesc getMainJar() throws Exception {
+ // Normal trick to get around final arguments to inner classes
+ final JARDesc[] results = new JARDesc[2];
+
+ visit(new ResourceVisitor() {
+ @Override
+ public void visitJARDesc(JARDesc jd) {
+ if (jd.isJavaFile()) {
+ // Keep track of first Java File
+ if (results[0] == null || results[0].isNativeLib()) {
+ results[0] = jd;
+ }
+ // Keep tack of Java File marked main
+ if (jd.isMainJarFile()) {
+ results[1] = jd;
+ }
+ } else if (jd.isNativeLib()) {
+ // if jnlp extension has only native lib
+ if (results[0] == null) {
+ results[0] = jd;
+ }
+ }
+ }
+
+ @Override
+ public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
+ // only check if no main yet and it is not an installer
+ if (results[1] == null && !ed.isInstaller()) {
+ JNLPDesc extLd = ed.getExtensionDesc();
+ if (extLd != null && extLd.isLibrary()) {
+ ResourcesDesc rd = extLd.getResourcesDesc();
+ if (rd != null) {
+ // look for main jar in extension resource
+ rd.visit(this);
+ }
+ }
+ }
+ }
+ });
+
+ // Default is the first, if none is specified as main. This might
+ // return NULL if there is no JAR resources.
+ JARDesc first = results[0];
+ JARDesc main = results[1];
+
+ // if main is null then return first;
+ // libraries have no such thing as a main jar, so return first;
+ // otherwise return main
+ // only returns null if there are no jars.
+ return (main == null) ? first : main;
+ }
+
+ /*
+ * Get the properties defined for this object
+ */
+ public Properties getResourceProperties() throws Exception {
+ final Properties props = new Properties();
+ visit(new ResourceVisitor() {
+ @Override
+ public void visitPropertyDesc(PropertyDesc pd) {
+ props.setProperty(pd.getKey(), pd.getValue());
+ }
+
+ @Override
+ public void visitExtensionDesc(ExtensionDesc ed) throws Exception {
+ JNLPDesc jnlpd = ed.getExtensionDesc();
+ ResourcesDesc rd = jnlpd.getResourcesDesc();
+ if (rd != null) {
+ Properties extProps = rd.getResourceProperties();
+ Enumeration e = extProps.propertyNames();
+ while (e.hasMoreElements()) {
+ String key = (String) e.nextElement();
+ String value = extProps.getProperty(key);
+ props.setProperty(key, value);
+ }
+ }
+ }
+ });
+ return props;
+ }
+
+ /*
+ * Get the properties defined for this object, in the right order.
+ */
+ public List<Property> getResourcePropertyList() throws Exception {
+ final LinkedList<Property> propList = new LinkedList<>();
+ visit(new ResourceVisitor() {
+ @Override
+ public void visitPropertyDesc(PropertyDesc pd) {
+ propList.add(new Property(pd.getKey(), pd.getValue()));
+ }
+ });
+ return propList;
+ }
+
+ /**
+ * visitor dispatch
+ */
+ @Override
+ public void visit(ResourceVisitor rv) throws Exception {
+ for (int i = 0; i < _list.size(); i++) {
+ ResourceType rt = _list.get(i);
+ rt.visit(rv);
+ }
+ }
+
+ public void addNested(ResourcesDesc nested) throws Exception {
+ if (nested != null) {
+ nested.visit(new ResourceVisitor() {
+ @Override
+ public void visitJARDesc(JARDesc jd) {
+ _list.add(jd);
+ }
+
+ @Override
+ public void visitPropertyDesc(PropertyDesc pd) {
+ _list.add(pd);
+ }
+
+ @Override
+ public void visitExtensionDesc(ExtensionDesc ed) {
+ _list.add(ed);
+ }
+ });
+ }
+
+ }
+
+ public static class JARDesc implements ResourceType {
+
+ private URL _location;
+ private String _locationString;
+ private String _version;
+ private boolean _isNativeLib;
+ private boolean _isMainFile; // Only used for Java JAR files (a main JAR file is implicitly eager)
+ private ResourcesDesc _parent; // Back-pointer to the Resources that contains this JAR
+
+ public JARDesc(URL location, String version, boolean isMainFile, boolean isNativeLib, ResourcesDesc parent) {
+ _location = location;
+ _locationString = GeneralUtil.toNormalizedString(location);
+ _version = version;
+ _isMainFile = isMainFile;
+ _isNativeLib = isNativeLib;
+ _parent = parent;
+ }
+
+ /**
+ * Type of JAR resource
+ */
+ public boolean isNativeLib() {
+ return _isNativeLib;
+ }
+
+ public boolean isJavaFile() {
+ return !_isNativeLib;
+ }
+
+ /**
+ * Returns URL/version for JAR file
+ */
+ public URL getVersionLocation() throws Exception {
+ if (getVersion() == null) {
+ return _location;
+ } else {
+ return GeneralUtil.getEmbeddedVersionURL(getLocation(), getVersion());
+ }
+ }
+
+ public URL getLocation() {
+ return _location;
+ }
+
+ public String getVersion() {
+ return _version;
+ }
+
+ public String getName() {
+ // File can be separated by '/' or '\\'
+ int index;
+ int index1 = _locationString.lastIndexOf('/');
+ int index2 = _locationString.lastIndexOf('\\');
+
+ if (index1 >= index2) {
+ index = index1;
+ } else {
+ index = index2;
+ }
+
+ if (index != -1) {
+ return _locationString.substring(index + 1, _locationString.length());
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns if this is the main JAR file
+ */
+ public boolean isMainJarFile() {
+ return _isMainFile;
+ }
+
+ /**
+ * Get parent LaunchDesc
+ */
+ public ResourcesDesc getParent() {
+ return _parent;
+ }
+
+ /**
+ * Visitor dispatch
+ */
+ public void visit(ResourceVisitor rv) {
+ rv.visitJARDesc(this);
+ }
+ }
+
+ public static class PropertyDesc implements ResourceType {
+
+ private String _key;
+ private String _value;
+
+ public PropertyDesc(String key, String value) {
+ _key = key;
+ _value = value;
+ }
+
+ // Accessors
+ public String getKey() {
+ return _key;
+ }
+
+ public String getValue() {
+ return _value;
+ }
+
+ /**
+ * Visitor dispatch
+ */
+ public void visit(ResourceVisitor rv) {
+ rv.visitPropertyDesc(this);
+ }
+
+ }
+
+ public static class JREDesc implements ResourceType {
+
+ private String _version;
+ private long _maxHeap;
+ private long _minHeap;
+ private String _vmargs;
+ private ResourcesDesc _resourceDesc;
+ private JNLPDesc _extensioDesc;
+ private String _archList;
+
+ /*
+ * Constructor to create new instance based on the requirements from JNLP file.
+ */
+ public JREDesc(String version, long minHeap, long maxHeap, String vmargs,
+ ResourcesDesc resourcesDesc, String archList) {
+
+ _version = version;
+ _maxHeap = maxHeap;
+ _minHeap = minHeap;
+ _vmargs = vmargs;
+ _resourceDesc = resourcesDesc;
+ _extensioDesc = null;
+ _archList = archList;
+ }
+
+ public String[] getArchList() {
+ return GeneralUtil.getStringList(_archList);
+ }
+
+ public String getVersion() {
+ return _version;
+ }
+
+ public long getMinHeap() {
+ return _minHeap;
+ }
+
+ public long getMaxHeap() {
+ return _maxHeap;
+ }
+
+ public String getVmArgs() {
+ return _vmargs;
+ }
+
+ public String[] getVmArgsList() {
+ return GeneralUtil.getStringList(_vmargs);
+ }
+
+ public ResourcesDesc getNestedResources() {
+ return _resourceDesc;
+ }
+
+ public JNLPDesc getExtensionDesc() {
+ return _extensioDesc;
+ }
+
+ public void setExtensionDesc(JNLPDesc ld) {
+ _extensioDesc = ld;
+ }
+
+ /* visitor dispatch */
+ public void visit(ResourceVisitor rv) {
+ rv.visitJREDesc(this);
+ }
+ }
+
+ public static class Property implements Cloneable {
+
+ public static final String JNLP_VERSION_ENABLED = "jnlp.versionEnabled";
+
+ String key;
+ String value;
+
+ public Property(String spec) {
+ spec = spec.trim();
+ if (!spec.startsWith("-D") || spec.length() < 3) {
+ throw new IllegalArgumentException("Property invalid");
+ }
+
+ int endKey = spec.indexOf("=");
+ if (endKey < 0) {
+ // it's legal to have no assignment
+ this.key = spec.substring(2); // skip "-D"
+ this.value = "";
+ } else {
+ this.key = spec.substring(2, endKey);
+ this.value = spec.substring(endKey + 1);
+ }
+ }
+
+ public static Property createProperty(String spec) {
+ Property prop = null;
+ try {
+ prop = new Property(spec);
+ } catch (IllegalArgumentException iae) {
+ }
+ return prop;
+ }
+
+ public Property(String key, String value) {
+ this.key = key;
+ if (value != null) {
+ this.value = value;
+ } else {
+ this.value = "";
+ }
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ // @return String representation, unquoted, unified presentation
+ public String toString() {
+ if (value.length() == 0) {
+ return "-D" + key;
+ }
+ return "-D" + key + "=" + value;
+ }
+
+ public void addTo(Properties props) {
+ props.setProperty(key, value);
+ }
+
+ // Hash Object
+ public boolean equals(Object o) {
+ if (!(o instanceof Property)) {
+ return false;
+ }
+ Property op = (Property) o;
+ int hashTheirs = op.hashCode();
+ int hashThis = hashCode();
+ return hashTheirs == hashThis;
+ }
+
+ public int hashCode() {
+ return key.hashCode();
+ }
+
+ private static List<Object> jnlpProps = Arrays.asList(new Object[]{
+ JNLP_VERSION_ENABLED
+ });
+
+ public static boolean isJnlpProperty(String spec) {
+ try {
+ Property p = new Property(spec);
+ return isJnlpPropertyKey(p.getKey());
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public static boolean isJnlpPropertyKey(String key) {
+ return key != null && jnlpProps.contains(key);
+ }
+ }
+
+ public static class ExtensionDesc implements ResourceType {
+ // Tag elements
+
+ private final URL _location;
+ private final String _locationString;
+ private final String _version;
+ private final URL _codebase;
+
+ // Link to launchDesc
+ private JNLPDesc _extensionLd; // Link to launchDesc for extension
+
+ public ExtensionDesc(URL location, String version) {
+ _location = location;
+ _locationString = GeneralUtil.toNormalizedString(location);
+ _version = version;
+ _codebase = GeneralUtil.asPathURL(GeneralUtil.getBase(location));
+ _extensionLd = null;
+ }
+
+ public boolean isInstaller() throws Exception {
+ if (getExtensionDesc() != null) {
+ return _extensionLd.isInstaller();
+ }
+ return false;
+ }
+
+ public URL getLocation() {
+ return _location;
+ }
+
+ public String getVersionLocation() throws Exception {
+ if (getVersion() == null) {
+ return _locationString;
+ } else {
+ return GeneralUtil.toNormalizedString(GeneralUtil.getEmbeddedVersionURL(getLocation(), getVersion()));
+ }
+ }
+
+ public String getVersion() {
+ return _version;
+ }
+
+ public URL getCodebase() {
+ return _codebase;
+ }
+
+ /*
+ * Information about the resources
+ */
+ public JNLPDesc getExtensionDesc() throws Exception {
+ if (_extensionLd == null) {
+ byte[] bits = HTTPHelper.getJNLPBits(getVersionLocation(), _locationString);
+ _extensionLd = XMLFormat.parse(bits, getCodebase(), getVersionLocation());
+ }
+ return _extensionLd;
+ }
+
+ public void setExtensionDesc(JNLPDesc desc) {
+ _extensionLd = desc;
+ }
+
+ /**
+ * Visitor dispatch
+ */
+ public void visit(ResourceVisitor rv) throws Exception {
+ rv.visitExtensionDesc(this);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/VersionID.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2006, 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.
+ *
+ * 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 jnlp.converter.parser;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * VersionID contains a JNLP version ID.
+ *
+ * The VersionID also contains a prefix indicator that can
+ * be used when stored with a VersionString
+ *
+ */
+public class VersionID implements Comparable<VersionID> {
+ private final String[] _tuple; // Array of Integer or String objects
+ private final boolean _usePrefixMatch; // star (*) prefix
+ private final boolean _useGreaterThan; // plus (+) greather-than
+ private final boolean _isCompound; // and (&) operator
+ private final VersionID _rest; // remaining part after the &
+
+ /**
+ * Creates a VersionID object from a given <code>String</code>.
+ * @param str version string to parse
+ */
+ public VersionID(String str) {
+ if (str == null || str.length() == 0) {
+ _tuple = new String[0];
+ _useGreaterThan = false;
+ _usePrefixMatch = false;
+ _isCompound = false;
+ _rest = null;
+ return;
+ }
+
+ // Check for compound
+ int amp = str.indexOf("&");
+ if (amp >= 0) {
+ _isCompound = true;
+ VersionID firstPart = new VersionID(str.substring(0, amp));
+ _rest = new VersionID(str.substring(amp+1));
+ _tuple = firstPart._tuple;
+ _usePrefixMatch = firstPart._usePrefixMatch;
+ _useGreaterThan = firstPart._useGreaterThan;
+ } else {
+ _isCompound = false;
+ _rest = null;
+ // Check for postfix
+ if (str.endsWith("+")) {
+ _useGreaterThan = true;
+ _usePrefixMatch = false;
+ str = str.substring(0, str.length() - 1);
+ } else if (str.endsWith("*")) {
+ _useGreaterThan = false;
+ _usePrefixMatch = true;
+ str = str.substring(0, str.length() - 1);
+ } else {
+ _useGreaterThan = false;
+ _usePrefixMatch = false;
+ }
+
+ ArrayList<String> list = new ArrayList<>();
+ int start = 0;
+ for (int i = 0; i < str.length(); i++) {
+ // Split at each separator character
+ if (".-_".indexOf(str.charAt(i)) != -1) {
+ if (start < i) {
+ String value = str.substring(start, i);
+ list.add(value);
+ }
+ start = i + 1;
+ }
+ }
+ if (start < str.length()) {
+ list.add(str.substring(start, str.length()));
+ }
+ _tuple = list.toArray(new String[0]);
+ }
+ }
+
+ /** @return true if no flags are set */
+ public boolean isSimpleVersion() {
+ return !_useGreaterThan && !_usePrefixMatch && !_isCompound;
+ }
+
+ /** Match 'this' versionID against vid.
+ * The _usePrefixMatch/_useGreaterThan flag is used to determine if a
+ * prefix match of an exact match should be performed
+ * if _isCompound, must match _rest also.
+ */
+ public boolean match(VersionID vid) {
+ if (_isCompound) {
+ if (!_rest.match(vid)) {
+ return false;
+ }
+ }
+ return (_usePrefixMatch) ? this.isPrefixMatchTuple(vid) :
+ (_useGreaterThan) ? vid.isGreaterThanOrEqualTuple(this) :
+ matchTuple(vid);
+ }
+
+ /** Compares if two version IDs are equal */
+ @Override
+ public boolean equals(Object o) {
+ if (matchTuple(o)) {
+ VersionID ov = (VersionID) o;
+ if (_rest == null || _rest.equals(ov._rest)) {
+ if ((_useGreaterThan == ov._useGreaterThan) &&
+ (_usePrefixMatch == ov._usePrefixMatch)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /** Computes a hash code for a VersionID */
+ @Override
+ public int hashCode() {
+ boolean first = true;
+ int hashCode = 0;
+ for (String tuple : _tuple) {
+ if (first) {
+ first = false;
+ hashCode = tuple.hashCode();
+ } else {
+ hashCode = hashCode ^ tuple.hashCode();
+ }
+ }
+ return hashCode;
+ }
+
+ /** Compares if two version IDs are equal */
+ private boolean matchTuple(Object o) {
+ // Check for null and type
+ if (o == null || !(o instanceof VersionID)) {
+ return false;
+ }
+ VersionID vid = (VersionID) o;
+
+ // Normalize arrays
+ String[] t1 = normalize(_tuple, vid._tuple.length);
+ String[] t2 = normalize(vid._tuple, _tuple.length);
+
+ // Check contents
+ for (int i = 0; i < t1.length; i++) {
+ Object o1 = getValueAsObject(t1[i]);
+ Object o2 = getValueAsObject(t2[i]);
+ if (!o1.equals(o2)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private Object getValueAsObject(String value) {
+ if (value.length() > 0 && value.charAt(0) != '-') {
+ try {
+ return Integer.valueOf(value);
+ } catch (NumberFormatException nfe) {
+ /* fall through */
+ }
+ }
+ return value;
+ }
+
+ public boolean isGreaterThan(VersionID vid) {
+ if (vid == null) {
+ return false;
+ }
+ return isGreaterThanOrEqualHelper(vid, false, true);
+ }
+
+ public boolean isGreaterThanOrEqual(VersionID vid) {
+ if (vid == null) {
+ return false;
+ }
+ return isGreaterThanOrEqualHelper(vid, true, true);
+ }
+
+ boolean isGreaterThanOrEqualTuple(VersionID vid) {
+ return isGreaterThanOrEqualHelper(vid, true, false);
+ }
+
+ /** Compares if 'this' is greater than vid */
+ private boolean isGreaterThanOrEqualHelper(VersionID vid,
+ boolean allowEqual, boolean useRest) {
+
+ if (useRest && _isCompound) {
+ if (!_rest.isGreaterThanOrEqualHelper(vid, allowEqual, true)) {
+ return false;
+ }
+ }
+ // Normalize the two strings
+ String[] t1 = normalize(_tuple, vid._tuple.length);
+ String[] t2 = normalize(vid._tuple, _tuple.length);
+
+ for (int i = 0; i < t1.length; i++) {
+ // Compare current element
+ Object e1 = getValueAsObject(t1[i]);
+ Object e2 = getValueAsObject(t2[i]);
+ if (e1.equals(e2)) {
+ // So far so good
+ } else {
+ if (e1 instanceof Integer && e2 instanceof Integer) {
+ // if both can be parsed as ints, compare ints
+ return ((Integer)e1).intValue() > ((Integer)e2).intValue();
+ } else {
+ if (e1 instanceof Integer) {
+ return false; // e1 (numeric) < e2 (non-numeric)
+ } else if (e2 instanceof Integer) {
+ return true; // e1 (non-numeric) > e2 (numeric)
+ }
+
+ String s1 = t1[i];
+ String s2 = t2[i];
+
+ return s1.compareTo(s2) > 0;
+ }
+
+ }
+ }
+ // If we get here, they are equal
+ return allowEqual;
+ }
+
+ /** Checks if 'this' is a prefix of vid */
+ private boolean isPrefixMatchTuple(VersionID vid) {
+
+ // Make sure that vid is at least as long as the prefix
+ String[] t2 = normalize(vid._tuple, _tuple.length);
+
+ for (int i = 0; i < _tuple.length; i++) {
+ Object e1 = _tuple[i];
+ Object e2 = t2[i];
+ if (e1.equals(e2)) {
+ // So far so good
+ } else {
+ // Not a prefix
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /** Normalize an array to a certain lengh */
+ private String[] normalize(String[] list, int minlength) {
+ if (list.length < minlength) {
+ // Need to do padding
+ String[] newlist = new String[minlength];
+ System.arraycopy(list, 0, newlist, 0, list.length);
+ Arrays.fill(newlist, list.length, newlist.length, "0");
+ return newlist;
+ } else {
+ return list;
+ }
+ }
+
+ @Override
+ public int compareTo(VersionID o) {
+ if (o == null || !(o instanceof VersionID)) {
+ return -1;
+ }
+ VersionID vid = o;
+ return equals(vid) ? 0 : (isGreaterThanOrEqual(vid) ? 1 : -1);
+ }
+
+ /** Show it as a string */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < _tuple.length - 1; i++) {
+ sb.append(_tuple[i]);
+ sb.append('.');
+ }
+ if (_tuple.length > 0) {
+ sb.append(_tuple[_tuple.length - 1]);
+ }
+ if (_useGreaterThan) {
+ sb.append('+');
+ }
+ if (_usePrefixMatch) {
+ sb.append('*');
+ }
+ if (_isCompound) {
+ sb.append("&");
+ sb.append(_rest);
+ }
+ return sb.toString();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/VersionString.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2006, 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.
+ *
+ * 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 jnlp.converter.parser;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+/*
+ * Utility class that knows to handle version strings
+ * A version string is of the form:
+ *
+ * (version-id ('+'?) ' ') *
+ *
+ */
+public class VersionString {
+ private final ArrayList<VersionID> _versionIds;
+
+ /** Constructs a VersionString object from string */
+ public VersionString(String vs) {
+ _versionIds = new ArrayList<>();
+ if (vs != null) {
+ StringTokenizer st = new StringTokenizer(vs, " ", false);
+ while (st.hasMoreElements()) {
+ // Note: The VersionID class takes care of a postfixed '+'
+ _versionIds.add(new VersionID(st.nextToken()));
+ }
+ }
+ }
+
+ public VersionString(VersionID id) {
+ _versionIds = new ArrayList<>();
+ if (id != null) {
+ _versionIds.add(id);
+ }
+ }
+
+ public boolean isSimpleVersion() {
+ if (_versionIds.size() == 1) {
+ return _versionIds.get(0).isSimpleVersion();
+ }
+ return false;
+ }
+
+ /** Check if this VersionString object contains the VersionID m */
+ public boolean contains(VersionID m) {
+ for (int i = 0; i < _versionIds.size(); i++) {
+ VersionID vi = _versionIds.get(i);
+ boolean check = vi.match(m);
+ if (check) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Check if this VersionString object contains the VersionID m, given as a string */
+ public boolean contains(String versionid) {
+ return contains(new VersionID(versionid));
+ }
+
+ /** Check if this VersionString object contains anything greater than m */
+ public boolean containsGreaterThan(VersionID m) {
+ for (int i = 0; i < _versionIds.size(); i++) {
+ VersionID vi = _versionIds.get(i);
+ boolean check = vi.isGreaterThan(m);
+ if (check) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Check if this VersionString object contains anything greater than the VersionID m, given as a string */
+ public boolean containsGreaterThan(String versionid) {
+ return containsGreaterThan(new VersionID(versionid));
+ }
+
+ /** Check if the versionString 'vs' contains the VersionID 'vi' */
+ public static boolean contains(String vs, String vi) {
+ return (new VersionString(vs)).contains(vi);
+ }
+
+ /** Pretty-print object */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < _versionIds.size(); i++) {
+ sb.append(_versionIds.get(i).toString());
+ sb.append(' ');
+ }
+ return sb.toString();
+ }
+
+ public List<VersionID> getAllVersionIDs() {
+ return _versionIds;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/XMLFormat.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,659 @@
+/*
+ * Copyright (c) 2006, 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.
+ *
+ * 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 jnlp.converter.parser;
+
+import java.net.URL;
+import java.util.Arrays;
+import java.util.ArrayList;
+import jnlp.converter.JNLPConverter;
+import jnlp.converter.parser.exception.MissingFieldException;
+import jnlp.converter.parser.exception.BadFieldException;
+import jnlp.converter.parser.exception.JNLParseException;
+import jnlp.converter.parser.xml.XMLEncoding;
+import jnlp.converter.parser.xml.XMLParser;
+import jnlp.converter.parser.xml.XMLNode;
+import jnlp.converter.parser.JNLPDesc.AssociationDesc;
+import jnlp.converter.parser.JNLPDesc.IconDesc;
+import jnlp.converter.parser.JNLPDesc.InformationDesc;
+import jnlp.converter.parser.JNLPDesc.ShortcutDesc;
+import jnlp.converter.parser.ResourcesDesc.JARDesc;
+import jnlp.converter.parser.ResourcesDesc.JREDesc;
+import jnlp.converter.Log;
+import jnlp.converter.parser.ResourcesDesc.ExtensionDesc;
+import jnlp.converter.parser.ResourcesDesc.PropertyDesc;
+import org.xml.sax.SAXParseException;
+
+public class XMLFormat {
+
+ public static XMLNode parseBits(byte[] bits) throws JNLParseException {
+ return parse(decode(bits));
+ }
+
+ private static String decode(byte[] bits) throws JNLParseException {
+ try {
+ return XMLEncoding.decodeXML(bits);
+ } catch (Exception e) {
+ throw new JNLParseException(e,
+ "exception determining encoding of jnlp file", 0);
+ }
+ }
+
+ private static XMLNode parse(String source) throws JNLParseException {
+ try {
+ return (new XMLParser(source).parse());
+ } catch (SAXParseException spe) {
+ throw new JNLParseException(spe,
+ "exception parsing jnlp file", spe.getLineNumber());
+ } catch (Exception e) {
+ throw new JNLParseException(e,
+ "exception parsing jnlp file", 0);
+ }
+ }
+
+ /**
+ * thisCodebase, if set, is used to determine the codebase,
+ * if JNLP codebase is not absolute.
+ *
+ * @param thisCodebase base URL of this JNLPDesc location
+ */
+ public static JNLPDesc parse(byte[] bits, URL thisCodebase, String jnlp)
+ throws Exception {
+
+ JNLPDesc jnlpd = new JNLPDesc();
+ String source = decode(bits).trim();
+ XMLNode root = parse(source);
+
+ if (root == null || root.getName() == null) {
+ throw new JNLParseException(null, null, 0);
+ }
+
+ // Check that root element is a <jnlp> tag
+ if (!root.getName().equals("jnlp")) {
+ throw (new MissingFieldException(source, "<jnlp>"));
+ }
+
+ // Read <jnlp> attributes (path is empty, i.e., "")
+ // (spec, version, codebase, href)
+ String specVersion = XMLUtils.getAttribute(root, "", "spec", "1.0+");
+ jnlpd.setSpecVersion(specVersion);
+ String version = XMLUtils.getAttribute(root, "", "version");
+ jnlpd.setVersion(version);
+
+ // Make sure the codebase URL ends with a '/'.
+ //
+ // Regarding the JNLP spec,
+ // the thisCodebase is used to determine the codebase.
+ // codebase = new URL(thisCodebase, codebase)
+ URL codebase = GeneralUtil.asPathURL(XMLUtils.getAttributeURL(source,
+ thisCodebase, root, "", "codebase"));
+ if (codebase == null && thisCodebase != null) {
+ codebase = thisCodebase;
+ }
+ jnlpd.setCodebase(codebase.toExternalForm());
+
+ // Get href for JNLP file
+ URL href = XMLUtils.getAttributeURL(source, codebase, root, "", "href");
+ jnlpd.setHref(href.toExternalForm());
+
+ // Read <security> attributes
+ if (XMLUtils.isElementPath(root, "<security><all-permissions>")) {
+ jnlpd.setIsSandbox(false);
+ } else if (XMLUtils.isElementPath(root,
+ "<security><j2ee-application-client-permissions>")) {
+ jnlpd.setIsSandbox(false);
+ }
+
+ // We can be fxapp, and also be applet, or application, or neither
+ boolean isFXApp = false;
+ boolean isApplet = false;
+ if (XMLUtils.isElementPath(root, "<javafx-desc>")) {
+ // no new type for javafx-desc - needs one of the others
+ buildFXAppDesc(source, root, "<javafx-desc>", jnlpd);
+ jnlpd.setIsFXApp(true);
+ isFXApp = true;
+ }
+
+ /*
+ * Note - the jnlp specification says there must be exactly one of
+ * the descriptor types. This code has always violated (or at least
+ * not checked for) that condition.
+ * Instead it uses precedent order app, component, installer, applet
+ * and ignores any other descriptors given.
+ */
+ if (XMLUtils.isElementPath(root, "<application-desc>")) {
+ buildApplicationDesc(source, root, jnlpd);
+ } else if (XMLUtils.isElementPath(root, "<component-desc>")) {
+ jnlpd.setIsLibrary(true);
+ } else if (XMLUtils.isElementPath(root, "<installer-desc>")) {
+ Log.warning("<installer-desc> is not supported and will be ignored in " + jnlp);
+ jnlpd.setIsInstaller(true);
+ } else if (XMLUtils.isElementPath(root, "<applet-desc>")) {
+ isApplet = true;
+ } else {
+ if (!isFXApp) {
+ throw (new MissingFieldException(source,
+ "<jnlp>(<application-desc>|<applet-desc>|" +
+ "<installer-desc>|<component-desc>)"));
+ }
+ }
+
+ if (isApplet && !isFXApp) {
+ Log.error("Applet based applications deployed with <applet-desc> element are not supported.");
+ }
+
+ if (!jnlpd.isLibrary() && !jnlpd.isInstaller()) {
+ buildInformationDesc(source, codebase, root, jnlpd);
+ }
+
+ if (!jnlpd.isInstaller()) {
+ buildResourcesDesc(source, codebase, root, false, jnlpd);
+ }
+
+ if (!jnlpd.isLibrary() && !jnlpd.isInstaller()) {
+ jnlpd.parseResourceDesc();
+ }
+
+ if (!jnlpd.isInstaller()) {
+ if (jnlpd.isSandbox()) {
+ if (jnlpd.isLibrary()) {
+ Log.warning(jnlp + " is sandbox extension. JNLPConverter does not support sandbox environment and converted application will run without security manager.");
+ } else {
+ Log.warning("This is sandbox Web-Start application. JNLPConverter does not support sandbox environment and converted application will run without security manager.");
+ }
+ }
+ }
+
+ return jnlpd;
+ }
+
+ /**
+ * Create a combine informationDesc in the two informationDesc.
+ * The information present in id1 overwrite the information present in id2
+ */
+ private static InformationDesc combineInformationDesc(
+ InformationDesc id1, InformationDesc id2) {
+ if (id1 == null) {
+ return id2;
+ }
+ if (id2 == null) {
+ return id1;
+ }
+
+ String t1 = id1.getTitle();
+ String title = (t1 != null && t1.length() > 0) ?
+ t1 : id2.getTitle();
+ String v1 = id1.getVendor();
+ String vendor = (v1 != null && v1.length() > 0) ?
+ v1 : id2.getVendor();
+
+ /** Copy descriptions */
+ String[] descriptions = new String[InformationDesc.NOF_DESC];
+ for (int i = 0; i < descriptions.length; i++) {
+ descriptions[i] = (id1.getDescription(i) != null)
+ ? id1.getDescription(i) : id2.getDescription(i);
+ }
+
+ /** Icons */
+ ArrayList<IconDesc> iconList = new ArrayList<>();
+ if (id2.getIcons() != null) {
+ iconList.addAll(Arrays.asList(id2.getIcons()));
+ }
+ if (id1.getIcons() != null) {
+ iconList.addAll(Arrays.asList(id1.getIcons()));
+ }
+ IconDesc[] icons = new IconDesc[iconList.size()];
+ icons = iconList.toArray(icons);
+
+ ShortcutDesc hints = (id1.getShortcut() != null) ?
+ id1.getShortcut() : id2.getShortcut();
+
+ AssociationDesc[] asd = ( AssociationDesc[] ) addArrays(
+ (Object[])id1.getAssociations(), (Object[])id2.getAssociations());
+
+ return new InformationDesc(title,
+ vendor,
+ descriptions,
+ icons,
+ hints,
+ asd);
+ }
+
+ /** Extract data from <information> tag */
+ private static void buildInformationDesc(final String source, final URL codebase, XMLNode root, JNLPDesc jnlpd)
+ throws MissingFieldException, BadFieldException {
+ final ArrayList<InformationDesc> list = new ArrayList<>();
+
+ // Iterates over all <information> nodes ignoring the type
+ XMLUtils.visitElements(root,
+ "<information>", new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode e) throws
+ BadFieldException, MissingFieldException {
+
+ // Check for right os, arch, and locale
+ String[] os = GeneralUtil.getStringList(
+ XMLUtils.getAttribute(e, "", "os", null));
+ String[] arch = GeneralUtil.getStringList(
+ XMLUtils.getAttribute(e, "", "arch", null));
+ String[] locale = GeneralUtil.getStringList(
+ XMLUtils.getAttribute(e, "", "locale", null));
+ if (GeneralUtil.prefixMatchStringList(
+ os, GeneralUtil.getOSFullName()) &&
+ GeneralUtil.prefixMatchArch(arch) &&
+ matchDefaultLocale(locale))
+ {
+ // Title, vendor
+ String title = XMLUtils.getElementContents(e, "<title>");
+ String vendor = XMLUtils.getElementContents(e, "<vendor>");
+
+ // Descriptions
+ String[] descriptions =
+ new String[InformationDesc.NOF_DESC];
+ descriptions[InformationDesc.DESC_DEFAULT] =
+ XMLUtils.getElementContentsWithAttribute(
+ e, "<description>", "kind", "", null);
+ descriptions[InformationDesc.DESC_ONELINE] =
+ XMLUtils.getElementContentsWithAttribute(
+ e, "<description>", "kind", "one-line", null);
+ descriptions[InformationDesc.DESC_SHORT] =
+ XMLUtils.getElementContentsWithAttribute(
+ e, "<description>", "kind", "short", null);
+ descriptions[InformationDesc.DESC_TOOLTIP] =
+ XMLUtils.getElementContentsWithAttribute(
+ e, "<description>", "kind", "tooltip", null);
+
+ // Icons
+ IconDesc[] icons = getIconDescs(source, codebase, e);
+
+ // Shortcut hints
+ ShortcutDesc shortcuts = getShortcutDesc(e);
+
+ // Association hints
+ AssociationDesc[] associations = getAssociationDesc(
+ source, codebase, e);
+
+ list.add(new InformationDesc(
+ title, vendor, descriptions, icons,
+ shortcuts, associations));
+ }
+ }
+ });
+
+ /* Combine all information desc. information in a single one for
+ * the current locale using the following priorities:
+ * 1. locale == language_country_variant
+ * 2. locale == lauguage_country
+ * 3. locale == lauguage
+ * 4. no or empty locale
+ */
+ InformationDesc normId = new InformationDesc(null, null, null, null, null, null);
+ for (InformationDesc id : list) {
+ normId = combineInformationDesc(id, normId);
+ }
+
+ jnlpd.setTitle(normId.getTitle());
+ jnlpd.setVendor(normId.getVendor());
+ jnlpd.setDescriptions(normId.getDescription());
+ jnlpd.setIcons(normId.getIcons());
+ jnlpd.setShortcuts(normId.getShortcut());
+ jnlpd.setAssociations(normId.getAssociations());
+ }
+
+ private static Object[] addArrays (Object[] a1, Object[] a2) {
+ if (a1 == null) {
+ return a2;
+ }
+ if (a2 == null) {
+ return a1;
+ }
+ ArrayList<Object> list = new ArrayList<>();
+ int i;
+ for (i=0; i<a1.length; list.add(a1[i++]));
+ for (i=0; i<a2.length; list.add(a2[i++]));
+ return list.toArray(a1);
+ }
+
+ public static boolean matchDefaultLocale(String[] localeStr) {
+ return GeneralUtil.matchLocale(localeStr, GeneralUtil.getDefaultLocale());
+ }
+
+ /** Extract data from <resources> tag. There is only one. */
+ static void buildResourcesDesc(final String source,
+ final URL codebase, XMLNode root, final boolean ignoreJres, JNLPDesc jnlpd)
+ throws MissingFieldException, BadFieldException {
+ // Extract classpath directives
+ final ResourcesDesc rdesc = new ResourcesDesc();
+
+ // Iterate over all entries
+ XMLUtils.visitElements(root, "<resources>",
+ new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode e)
+ throws MissingFieldException, BadFieldException {
+ // Check for right os, archictecture, and locale
+ String[] os = GeneralUtil.getStringList(
+ XMLUtils.getAttribute(e, "", "os", null));
+ final String arch = XMLUtils.getAttribute(e, "", "arch", null);
+ String[] locale = GeneralUtil.getStringList(
+ XMLUtils.getAttribute(e, "", "locale", null));
+ if (GeneralUtil.prefixMatchStringList(
+ os, GeneralUtil.getOSFullName())
+ && matchDefaultLocale(locale)) {
+ // Now visit all children in this node
+ XMLUtils.visitChildrenElements(e,
+ new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode e2)
+ throws MissingFieldException, BadFieldException {
+ handleResourceElement(source, codebase,
+ e2, rdesc, ignoreJres, arch, jnlpd);
+ }
+ });
+ }
+ }
+ });
+
+ if (!rdesc.isEmpty()) {
+ jnlpd.setResourcesDesc(rdesc);
+ }
+ }
+
+ private static IconDesc[] getIconDescs(final String source,
+ final URL codebase, XMLNode e)
+ throws MissingFieldException, BadFieldException {
+ final ArrayList<IconDesc> answer = new ArrayList<>();
+ XMLUtils.visitElements(e, "<icon>", new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode icon) throws
+ MissingFieldException, BadFieldException {
+ String kindStr = XMLUtils.getAttribute(icon, "", "kind", "");
+ URL href = XMLUtils.getRequiredURL(source, codebase, icon, "", "href");
+
+ if (href != null) {
+ if (!JNLPConverter.isIconSupported(href.toExternalForm())) {
+ return;
+ }
+ }
+
+ int kind;
+ if (kindStr == null || kindStr.isEmpty() || kindStr.equals("default")) {
+ kind = IconDesc.ICON_KIND_DEFAULT;
+ } else if (kindStr.equals("shortcut")) {
+ kind = IconDesc.ICON_KIND_SHORTCUT;
+ } else {
+ Log.warning("Ignoring unsupported icon \"" + href + "\" with kind \"" + kindStr + "\".");
+ return;
+ }
+
+ answer.add(new IconDesc(href, kind));
+ }
+ });
+ return answer.toArray(new IconDesc[answer.size()]);
+ }
+
+ private static ShortcutDesc getShortcutDesc(XMLNode e)
+ throws MissingFieldException, BadFieldException {
+ final ArrayList<ShortcutDesc> shortcuts = new ArrayList<>();
+
+ XMLUtils.visitElements(e, "<shortcut>", new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode shortcutNode)
+ throws MissingFieldException, BadFieldException {
+ boolean desktopHinted =
+ XMLUtils.isElementPath(shortcutNode, "<desktop>");
+ boolean menuHinted =
+ XMLUtils.isElementPath(shortcutNode, "<menu>");
+ String submenuHinted =
+ XMLUtils.getAttribute(shortcutNode, "<menu>", "submenu");
+ shortcuts.add(new ShortcutDesc(desktopHinted, menuHinted, submenuHinted));
+ }
+ });
+
+ if (shortcuts.size() > 0) {
+ return shortcuts.get(0);
+ }
+ return null;
+ }
+
+ private static AssociationDesc[] getAssociationDesc(final String source,
+ final URL codebase, XMLNode e)
+ throws MissingFieldException, BadFieldException {
+ final ArrayList<AssociationDesc> answer = new ArrayList<>();
+ XMLUtils.visitElements(e, "<association>",
+ new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode node)
+ throws MissingFieldException, BadFieldException {
+
+ String extensions = XMLUtils.getAttribute(
+ node, "", "extensions");
+
+ String mimeType = XMLUtils.getAttribute(
+ node, "", "mime-type");
+ String description = XMLUtils.getElementContents(
+ node, "<description>");
+
+ URL icon = XMLUtils.getAttributeURL(
+ source, codebase, node, "<icon>", "href");
+
+ if (!JNLPConverter.isIconSupported(icon.toExternalForm())) {
+ icon = null;
+ }
+
+ if (extensions == null && mimeType == null) {
+ throw new MissingFieldException(source,
+ "<association>(<extensions><mime-type>)");
+ } else if (extensions == null) {
+ throw new MissingFieldException(source,
+ "<association><extensions>");
+ } else if (mimeType == null) {
+ throw new MissingFieldException(source,
+ "<association><mime-type>");
+ }
+
+ // don't support uppercase extension and mime-type on gnome.
+ if ("gnome".equals(System.getProperty("sun.desktop"))) {
+ extensions = extensions.toLowerCase();
+ mimeType = mimeType.toLowerCase();
+ }
+
+ answer.add(new AssociationDesc(extensions, mimeType,
+ description, icon));
+ }
+ });
+ return answer.toArray(
+ new AssociationDesc[answer.size()]);
+ }
+
+ /** Handle the individual entries in a resource desc */
+ private static void handleResourceElement(String source, URL codebase,
+ XMLNode e, ResourcesDesc rdesc, boolean ignoreJres, String arch, JNLPDesc jnlpd)
+ throws MissingFieldException, BadFieldException {
+
+ String tag = e.getName();
+
+ boolean matchArch = GeneralUtil.prefixMatchArch(
+ GeneralUtil.getStringList(arch));
+
+
+ if (matchArch && (tag.equals("jar") || tag.equals("nativelib"))) {
+ /*
+ * jar/nativelib elements
+ */
+ URL href = XMLUtils.getRequiredURL(source, codebase, e, "", "href");
+ String version = XMLUtils.getAttribute(e, "", "version", null);
+
+ String mainStr = XMLUtils.getAttribute(e, "", "main");
+ boolean isNativeLib = tag.equals("nativelib");
+
+ boolean isMain = "true".equalsIgnoreCase(mainStr);
+
+ JARDesc jd = new JARDesc(href, version, isMain, isNativeLib, rdesc);
+ rdesc.addResource(jd);
+ } else if (matchArch && tag.equals("property")) {
+ /*
+ * property tag
+ */
+ String name = XMLUtils.getRequiredAttribute(source, e, "", "name");
+ String value = XMLUtils.getRequiredAttributeEmptyOK(
+ source, e, "", "value");
+
+ rdesc.addResource(new PropertyDesc(name, value));
+ } else if (matchArch && tag.equals("extension")) {
+ URL href = XMLUtils.getRequiredURL(source, codebase, e, "", "href");
+ String version = XMLUtils.getAttribute(e, "", "version", null);
+ rdesc.addResource(new ExtensionDesc(href, version));
+ } else if ((tag.equals("java") || tag.equals("j2se")) && !ignoreJres) {
+ /*
+ * j2se element
+ */
+ String version =
+ XMLUtils.getRequiredAttribute(source, e, "", "version");
+ String minheapstr =
+ XMLUtils.getAttribute(e, "", "initial-heap-size");
+ String maxheapstr =
+ XMLUtils.getAttribute(e, "", "max-heap-size");
+
+ String vmargs =
+ XMLUtils.getAttribute(e, "", "java-vm-args");
+
+ if (jnlpd.isJRESet()) {
+ if (vmargs == null) {
+ vmargs = "none";
+ }
+ Log.warning("Ignoring repeated element <" + tag + "> with version " + version +
+ " and java-vm-args: " + vmargs);
+ return;
+ }
+
+ long minheap = GeneralUtil.heapValToLong(minheapstr);
+ long maxheap = GeneralUtil.heapValToLong(maxheapstr);
+
+ ResourcesDesc cbs = null;
+ buildResourcesDesc(source, codebase, e, true, null);
+
+ // JRE
+ JREDesc jreDesc = new JREDesc(
+ version,
+ minheap,
+ maxheap,
+ vmargs,
+ cbs,
+ arch);
+
+ rdesc.addResource(jreDesc);
+
+ jnlpd.setIsJRESet(true);
+ }
+ }
+
+ /** Extract data from the application-desc tag */
+ private static void buildApplicationDesc(final String source,
+ XMLNode root, JNLPDesc jnlpd) throws MissingFieldException, BadFieldException {
+
+ String mainclass = XMLUtils.getClassName(source, root,
+ "<application-desc>", "main-class", false);
+ String appType = XMLUtils.getAttribute(root, "<application-desc>",
+ "type", "Java");
+ String progressclass = XMLUtils.getClassName(source, root,
+ "<application-desc>", "progress-class", false);
+ if (progressclass != null && !progressclass.isEmpty()) {
+ Log.warning("JNLPConverter does not support progress indication. \"" + progressclass + "\" will not be loaded and will be ignored.");
+ }
+
+ if (!("Java".equalsIgnoreCase(appType) ||
+ "JavaFx".equalsIgnoreCase(appType))) {
+ throw new BadFieldException(source, XMLUtils.getPathString(root) +
+ "<application-desc>type", appType);
+ }
+
+ if ("JavaFx".equalsIgnoreCase(appType)) {
+ jnlpd.setIsFXApp(true);
+ }
+
+ XMLUtils.visitElements(root, "<application-desc><argument>", new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode e) throws MissingFieldException, BadFieldException {
+ String arg = XMLUtils.getElementContents(e, "", null);
+ if (arg == null) {
+ throw new BadFieldException(source, XMLUtils.getPathString(e), "");
+ }
+ jnlpd.addArguments(arg);
+ }
+ });
+
+ XMLUtils.visitElements(root, "<application-desc><param>",
+ new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode e) throws MissingFieldException,
+ BadFieldException {
+ String pn = XMLUtils.getRequiredAttribute(
+ source, e, "", "name");
+ String pv = XMLUtils.getRequiredAttributeEmptyOK(
+ source, e, "", "value");
+ jnlpd.setProperty(pn, pv);
+ }
+ });
+ jnlpd.setMainClass(mainclass, false);
+ }
+
+ /** Extract data from the javafx-desc tag */
+ private static void buildFXAppDesc(final String source,
+ XMLNode root, String element, JNLPDesc jnlpd)
+ throws MissingFieldException, BadFieldException {
+ String mainclass = XMLUtils.getClassName(source, root, element,
+ "main-class", true);
+ String name = XMLUtils.getRequiredAttribute(source, root,
+ "<javafx-desc>", "name");
+
+ /* extract arguments */
+ XMLUtils.visitElements(root, "<javafx-desc><argument>", new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode e) throws MissingFieldException, BadFieldException {
+ String arg = XMLUtils.getElementContents(e, "", null);
+ if (arg == null) {
+ throw new BadFieldException(source, XMLUtils.getPathString(e), "");
+ }
+ jnlpd.addArguments(arg);
+ }
+ });
+
+ /* extract parameters */
+ XMLUtils.visitElements(root, "<javafx-desc><param>",
+ new XMLUtils.ElementVisitor() {
+ @Override
+ public void visitElement(XMLNode e) throws MissingFieldException,
+ BadFieldException {
+ String pn = XMLUtils.getRequiredAttribute(
+ source, e, "", "name");
+ String pv = XMLUtils.getRequiredAttributeEmptyOK(
+ source, e, "", "value");
+ jnlpd.setProperty(pn, pv);
+ }
+ });
+
+ jnlpd.setMainClass(mainclass, true);
+ jnlpd.setName(name);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/XMLUtils.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,328 @@
+/*
+ * Copyright (c) 2006, 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.
+ *
+ * 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 jnlp.converter.parser;
+
+import java.net.URL;
+import java.net.MalformedURLException;
+
+import jnlp.converter.parser.exception.BadFieldException;
+import jnlp.converter.parser.exception.MissingFieldException;
+import jnlp.converter.parser.xml.XMLNode;
+
+/** Contains handy methods for looking up information
+ * stored in XMLNodes.
+ */
+public class XMLUtils {
+
+ /** Returns the value of an integer attribute */
+ public static int getIntAttribute(String source, XMLNode root, String path, String name, int defaultvalue)
+ throws BadFieldException {
+ String value = getAttribute(root, path, name);
+ if (value == null) {
+ return defaultvalue;
+ }
+
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException nfe) {
+ throw new BadFieldException(source, getPathString(root) + path + name, value);
+ }
+ }
+
+ /** Returns the value of a given attribute, or null if not set */
+ public static String getAttribute(XMLNode root, String path, String name)
+ throws BadFieldException {
+ return getAttribute(root, path, name, null);
+ }
+
+ /** Returns the value of a given attribute */
+ public static String getRequiredAttributeEmptyOK(String source,
+ XMLNode root, String path, String name) throws MissingFieldException {
+ String value = null;
+ XMLNode elem = findElementPath(root, path);
+ if (elem != null) {
+ value = elem.getAttribute(name);
+ }
+ if (value == null) {
+ throw new MissingFieldException(source,
+ getPathString(root)+ path + name);
+ }
+ return value;
+ }
+
+ /*** Returns the value of an attribute, which must be a valid class name */
+ public static String getClassName(String source, XMLNode root,
+ String path, String name, boolean required)
+ throws BadFieldException, MissingFieldException {
+
+ String className;
+ if (required) {
+ className = getRequiredAttribute(source, root, path, name);
+ } else {
+ className = getAttribute(root, path, name);
+ }
+ if (className != null && className.endsWith(".class")) {
+ int i = className.lastIndexOf(".class");
+ String cname = className.substring(0, i);
+ return cname;
+ }
+ return className;
+ }
+
+ /** Returns the value of a given attribute, or null if not set */
+ public static String getRequiredAttribute(String source, XMLNode root,
+ String path, String name) throws MissingFieldException, BadFieldException {
+ String s = getAttribute(root, path, name, null);
+ if (s == null) {
+ throw new MissingFieldException(source, getPathString(root) + path + name);
+ }
+ s = s.trim();
+ return (s.length() == 0) ? null : s;
+ }
+
+ /** Returns the value of a given attribute, or the default value 'def' if not set */
+ public static String getAttribute(XMLNode root, String path, String name,
+ String def) throws BadFieldException {
+ XMLNode elem = findElementPath(root, path);
+ if (elem == null) {
+ return def;
+ }
+ String value = elem.getAttribute(name);
+ return (value == null || value.length() == 0) ? def : value;
+ }
+
+ /** Expands a URL into an absolute URL from a relative URL */
+ public static URL getAttributeURL(String source, URL base, XMLNode root, String path, String name) throws BadFieldException {
+ String value = getAttribute(root, path, name);
+ if (value == null) return null;
+ try {
+ if (value.startsWith("jar:")) {
+ int bang = value.indexOf("!/");
+ if (bang > 0) {
+ String entry = value.substring(bang);
+ String urlString = value.substring(4, bang);
+ URL url = (base == null) ?
+ new URL(urlString) : new URL(base, urlString);
+ return new URL("jar:" + url.toString() + entry);
+ }
+ }
+ return (base == null) ? new URL(value) : new URL(base, value);
+ } catch(MalformedURLException mue) {
+ if (mue.getMessage().contains("https")) {
+ throw new BadFieldException(source, "<jnlp>", "https");
+ }
+ throw new BadFieldException(source, getPathString(root) + path + name, value);
+ }
+ }
+
+ /** Returns the value of an attribute as a URL or null if not set */
+ public static URL getAttributeURL(String source, XMLNode root, String path, String name) throws BadFieldException {
+ return getAttributeURL(source, null, root, path, name);
+ }
+
+ public static URL getRequiredURL(String source, URL base, XMLNode root, String path, String name) throws BadFieldException, MissingFieldException {
+ URL url = getAttributeURL(source, base, root, path, name);
+ if (url == null) {
+ throw new MissingFieldException(source, getPathString(root) + path + name);
+ }
+ return url;
+ }
+
+ /** Returns the value of an attribute as a URL. Throws a MissingFieldException if the
+ * attribute is not defined
+ */
+ public static URL getRequiredURL(String source, XMLNode root, String path, String name) throws BadFieldException, MissingFieldException {
+ return getRequiredURL(source, null, root, path, name);
+ }
+
+ /** Returns true if the path exists in the document, otherwise false */
+ public static boolean isElementPath(XMLNode root, String path) {
+ return findElementPath(root, path) != null;
+ }
+
+ public static URL getElementURL(String source, XMLNode root, String path) throws BadFieldException {
+ String value = getElementContents(root, path);
+ try {
+ return new URL(value);
+ } catch(MalformedURLException mue) {
+ throw new BadFieldException(source, getPathString(root) + path, value);
+ }
+ }
+
+ /** Returns a string describing the current location in the DOM */
+ public static String getPathString(XMLNode e) {
+ return (e == null || !(e.isElement())) ? "" : getPathString(e.getParent()) + "<" + e.getName() + ">";
+ }
+
+ /** Returns the contents of an element with the given path and an attribute matching a specific value. Returns
+ * NULL if not found
+ */
+ public static String getElementContentsWithAttribute(XMLNode root, String path, String attr, String val, String defaultvalue)
+ throws BadFieldException, MissingFieldException {
+ XMLNode e = getElementWithAttribute(root, path, attr, val);
+ if (e == null) {
+ return defaultvalue;
+ }
+ return getElementContents(e, "", defaultvalue);
+ }
+
+ public static URL getAttributeURLWithAttribute(String source, XMLNode root, String path, String attrcond, String val,
+ String name, URL defaultvalue)
+ throws BadFieldException, MissingFieldException {
+ XMLNode e = getElementWithAttribute(root, path, attrcond, val);
+ if (e == null) {
+ return defaultvalue;
+ }
+ URL url = getAttributeURL(source, e, "", name);
+ if (url == null) {
+ return defaultvalue;
+ }
+ return url;
+ }
+
+ /** Returns an element with the given path and an attribute matching a specific value. Returns
+ * NULL if not found
+ */
+ public static XMLNode getElementWithAttribute(XMLNode root, String path, final String attr, final String val)
+ throws BadFieldException, MissingFieldException {
+ final XMLNode[] result = {null};
+ visitElements(root, path, new ElementVisitor() {
+ public void visitElement(XMLNode e) throws BadFieldException, MissingFieldException {
+ if (result[0] == null && e.getAttribute(attr).equals(val)) {
+ result[0] = e;
+ }
+ }
+ });
+ return result[0];
+ }
+
+ /** Like getElementContents(...) but with a defaultValue of null */
+ public static String getElementContents(XMLNode root, String path) {
+ return getElementContents(root, path, null);
+ }
+
+ /** Returns the value of the last element tag in the path, e.g., <..><tag>value</tag>. The DOM is assumes
+ * to be normalized. If no value is found, the defaultvalue is returned
+ */
+ public static String getElementContents(XMLNode root, String path, String defaultvalue) {
+ XMLNode e = findElementPath(root, path);
+ if (e == null) {
+ return defaultvalue;
+ }
+ XMLNode n = e.getNested();
+ if (n != null && !n.isElement()) {
+ return n.getName();
+ }
+ return defaultvalue;
+ }
+
+ /** Parses a path string of the form <tag1><tag2><tag3> and returns the specific Element
+ * node for that tag, or null if it does not exist. If multiple elements exists with same
+ * path the first is returned
+ */
+ public static XMLNode findElementPath(XMLNode elem, String path) {
+ // End condition. Root null -> path does not exist
+ if (elem == null) {
+ return null;
+ }
+
+ // End condition. String empty, return current root
+ if (path == null || path.length() == 0) {
+ return elem;
+ }
+
+ // Strip of first tag
+ int idx = path.indexOf('>');
+ if (!(path.charAt(0) == '<')) {
+ throw new IllegalArgumentException("bad path. Missing begin tag");
+ }
+ if (idx == -1) {
+ throw new IllegalArgumentException("bad path. Missing end tag");
+ }
+ String head = path.substring(1, idx);
+ String tail = path.substring(idx + 1);
+ return findElementPath(findChildElement(elem, head), tail);
+ }
+
+ /** Returns an child element with the current tag name or null. */
+ public static XMLNode findChildElement(XMLNode elem, String tag) {
+ XMLNode n = elem.getNested();
+ while (n != null) {
+ if (n.isElement() && n.getName().equals(tag)) {
+ return n;
+ }
+ n = n.getNext();
+ }
+ return null;
+ }
+
+ /** Iterator class */
+ public abstract static class ElementVisitor {
+ abstract public void visitElement(XMLNode e) throws BadFieldException, MissingFieldException;
+ }
+
+ /** Visits all elements which matches the <path>. The iteration is only
+ * done on the last element in the path.
+ */
+ public static void visitElements(XMLNode root, String path, ElementVisitor ev)
+ throws BadFieldException, MissingFieldException {
+ // Get last element in path
+ int idx = path.lastIndexOf('<');
+ if (idx == -1) {
+ throw new IllegalArgumentException(
+ "bad path. Must contain atleast one tag");
+ }
+ if (path.length() == 0 || path.charAt(path.length() - 1) != '>') {
+ throw new IllegalArgumentException("bad path. Must end with a >");
+ }
+ String head = path.substring(0, idx);
+ String tag = path.substring(idx + 1, path.length() - 1);
+
+ XMLNode elem = findElementPath(root, head);
+ if (elem == null) {
+ return;
+ }
+
+ // Iterate through all child nodes
+ XMLNode n = elem.getNested();
+ while (n != null) {
+ if (n.isElement() && n.getName().equals(tag)) {
+ ev.visitElement(n);
+ }
+ n = n.getNext();
+ }
+ }
+
+ public static void visitChildrenElements(XMLNode elem, ElementVisitor ev)
+ throws BadFieldException, MissingFieldException {
+ // Iterate through all child nodes
+ XMLNode n = elem.getNested();
+ while (n != null) {
+ if (n.isElement()) {
+ ev.visitElement(n);
+ }
+ n = n.getNext();
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/exception/BadFieldException.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2006, 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.
+ *
+ * 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 jnlp.converter.parser.exception;
+
+public class BadFieldException extends Exception {
+
+ private final String _field;
+ private final String _value;
+
+ public BadFieldException(String source, String field, String value) {
+ super();
+ _value = value;
+ _field = field;
+ }
+
+ /**
+ * Returns the name of the offending field
+ */
+ public String getField() {
+ return _field;
+ }
+
+ /**
+ * Returns the value of the offending field
+ */
+ public String getValue() {
+ return _value;
+ }
+
+ /**
+ * toString implementation
+ */
+ @Override
+ public String toString() {
+ return "BadFieldException[ " + getField() + "," + getValue() + "]";
+ }
+
+ @Override
+ public String getMessage(){
+ return toString();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/exception/JNLParseException.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2006, 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.
+ *
+ * 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 jnlp.converter.parser.exception;
+
+/**
+ * Exception thrown if a parse error occurred when interpreting
+ * the launch descriptor
+ */
+
+public class JNLParseException extends Exception {
+
+ private final String _msg;
+ private final int _line;
+
+ public JNLParseException(Exception exception, String msg, int line) {
+ super(exception);
+ _msg = msg;
+ _line = line;
+ }
+
+ @Override
+ public String getMessage() {
+ return _msg;
+ }
+
+ private int getLine() {
+ return _line;
+ }
+
+ @Override
+ public String toString() {
+ return "JNLParseException[ " + getMessage() + "] at " + getLine();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/exception/MissingFieldException.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2006, 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.
+ *
+ * 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 jnlp.converter.parser.exception;
+
+public class MissingFieldException extends Exception {
+
+ private final String _field;
+
+ public MissingFieldException(String source, String field) {
+ super();
+ _field = field;
+ }
+
+ /**
+ * Returns the name of the offending field
+ */
+ private String getField() {
+ return _field;
+ }
+
+ /**
+ * toString implementation
+ */
+ @Override
+ public String toString() {
+ return "MissingFieldException[ " + getField() + "]";
+ }
+
+ @Override
+ public String getMessage(){
+ return toString();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/xml/XMLAttribute.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2006, 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.
+ *
+ * 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 jnlp.converter.parser.xml;
+
+/**
+ * Class that contains information about a specific attribute
+ */
+public class XMLAttribute {
+
+ private final String _name;
+ private final String _value;
+ private XMLAttribute _next;
+
+ public XMLAttribute(String name, String value) {
+ _name = XMLNode.stripNameSpace(name);
+ _value = value;
+ }
+
+ public String getName() {
+ return _name;
+ }
+
+ public String getValue() {
+ return _value;
+ }
+
+ public XMLAttribute getNext() {
+ return _next;
+ }
+
+ public void setNext(XMLAttribute next) {
+ _next = next;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/xml/XMLEncoding.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,342 @@
+/*
+ * Copyright (c) 2006, 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.
+ *
+ * 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 jnlp.converter.parser.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+
+public class XMLEncoding {
+ /**
+ * Decodes a byte stream into a String by testing for a Byte Order Mark
+ * (BOM) or an XML declaration.
+ * <br />
+ * Detection begins by examining the first four octets of the stream for a
+ * BOM. If a BOM is not found, then an encoding declaration is looked for
+ * at the beginning of the stream. If the encoding still can not be
+ * determined at this point, then UTF-8 is assumed.
+ *
+ * @param data an array of bytes containing an encoded XML document.
+ *
+ * @return A string containing the decoded XML document.
+ */
+ public static String decodeXML(byte [] data) throws IOException {
+ int start = 0;
+ String encoding;
+
+ if (data.length < BOM_LENGTH) {
+ throw (new EOFException("encoding.error.not.xml"));
+ }
+ // no else required; successfully read stream
+ int firstFour = ((0xff000000 & ((int) data[0] << 24)) |
+ (0x00ff0000 & ((int) data[1] << 16)) |
+ (0x0000ff00 & ((int) data[2] << 8)) |
+ (0x000000ff & (int) data[3]));
+
+ // start by examining the first four bytes for a BOM
+ switch (firstFour) {
+ case EBCDIC:
+ // examine the encoding declaration
+ encoding = examineEncodingDeclaration(data, IBM037_ENC);
+ break;
+
+ case XML_DECLARATION:
+ // assume UTF-8, but examine the encoding declaration
+ encoding = examineEncodingDeclaration(data, UTF_8_ENC);
+ break;
+
+ case UTF_16BE:
+ encoding = UTF_16BE_ENC;
+ break;
+
+ case UTF_16LE:
+ encoding = UTF_16LE_ENC;
+ break;
+
+ case UNUSUAL_OCTET_1:
+ case UNUSUAL_OCTET_2:
+ throw (new UnsupportedEncodingException("encoding.error.unusual.octet"));
+
+ case UTF_32_BE_BOM:
+ case UTF_32_LE_BOM:
+ encoding = UTF_32_ENC;
+ break;
+
+ default:
+ int firstThree = firstFour & 0xffffff00;
+
+ switch (firstThree) {
+ case UTF_8_BOM:
+ // the InputStreamReader class doen't properly handle
+ // the Byte Order Mark (BOM) in UTF-8 streams, so don't
+ // putback those 3 bytes.
+ start = 3;
+ encoding = UTF_8_ENC;
+ break;
+
+ default:
+ int firstTwo = firstFour & 0xffff0000;
+
+ switch (firstTwo) {
+ case UTF_16_BE_BOM:
+ case UTF_16_LE_BOM:
+ encoding = UTF_16_ENC;
+ break;
+
+ default:
+ // this is probably UTF-8 without the encoding
+ // declaration
+ encoding = UTF_8_ENC;
+ break;
+ }
+ break;
+ }
+ break;
+ }
+
+ return (new String(data, start, data.length - start, encoding));
+ }
+
+ /**
+ * [3] S ::= ( #x20 | #x09 | #x0d | #x0a )
+ * [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'
+ * [24] VersionInfo ::= S 'version' Eq ( '"' VersionNum '"' |
+ * "'" VersionNum "'" )
+ * [25] Eq ::= S? '=' S?
+ * [26] VersionNum ::= ([a-zA-Z0-9_.:] | '-')+
+ * [80] EncodingDecl ::= S 'encoding' Eq ( '"' EncName '"' |
+ * "'" EncName "'" )
+ * [81] EncName ::= [a-zA-Z] ([a-zA-Z0-9_.] | '-')*
+ */
+ private static String examineEncodingDeclaration(byte [] data,
+ String encoding) throws IOException {
+ boolean loop = false;
+ boolean recognized = false;
+ boolean almost = false;
+ boolean question = false;
+ boolean done = false;
+ boolean found = false;
+ int pos = 0;
+ int ch = -1;
+ Reader reader = null;
+ String result = ((encoding != null) ? encoding : UTF_8_ENC);
+
+ reader = new InputStreamReader(new ByteArrayInputStream(data), result);
+ ch = reader.read();
+
+ // if this is an XML declaration, it will start with the text '<?xml'
+ for (int i = 0; ((i < XML_DECL_START.length()) && (done == false)); i++) {
+ if (ch != XML_DECL_START.charAt(i)) {
+ // This doesn't look like an XML declaration. This method
+ // should only be called if the stream contains an XML
+ // declaration in the encoding that is passed into the method.
+ done = true;
+ break;
+ }
+ // no else required; still matches
+ ch = reader.read();
+ }
+
+ // there must be at least one whitespace character next.
+ loop = true;
+ while ((loop == true) && (done == false)) {
+ switch (ch) {
+ case SPACE:
+ case TAB: // intentional
+ case LINEFEED: // fall
+ case RETURN: // through
+ ch = reader.read();
+ break;
+
+ case -1:
+ // unexpected EOF
+ done = true;
+ break;
+
+ default:
+ // non-whitespace
+ loop = false;
+ break;
+ }
+ }
+
+ // now look for the text 'encoding', but if the end of the XML
+ // declaration (signified by the text '?>') comes first, then
+ // assume the encoding is UTF-8
+ loop = true;
+ while ((loop == true) && (done == false)) {
+ if (ch == -1) {
+ // unexpected EOF
+ done = true;
+ break;
+ } else if (recognized == true) {
+ // this is the encoding declaration as long as the next few
+ // characters are whitespace and/or the equals ('=') sign
+ switch (ch) {
+ case SPACE: // intentional
+ case TAB: // fall
+ case LINEFEED: // through
+ case RETURN:
+ // don't need to do anything
+ break;
+
+ case EQUAL:
+ if (almost == false) {
+ // got the equal, now find a quote
+ almost = true;
+ } else {
+ // this is not valid XML, so punt
+ recognized = false;
+ done = true;
+ }
+ break;
+
+ case DOUBLE_QUOTE: // intentional
+ case SINGLE_QUOTE: // fall through
+ if (almost == true) {
+ // got the quote, so move on to get the value
+ loop = false;
+ } else {
+ // got a quote before the equal; this is not valid
+ // XML, so punt
+ recognized = false;
+ done = true;
+ }
+ break;
+
+ default:
+ // non-whitespace
+ recognized = false;
+ if (almost == true) {
+ // this is not valid XML, so punt
+ done = true;
+ }
+ // no else required; this wasn't the encoding
+ // declaration
+ break;
+ }
+
+ if (recognized == false) {
+ // this isn't the encoding declaration, so go back to the
+ // top without reading the next character
+ pos = 0;
+ continue;
+ }
+ // no else required; still looking good
+ } else if (ch == ENCODING_DECL.charAt(pos++)) {
+ if (ENCODING_DECL.length() == pos) {
+ // this looks like the encoding declaration
+ recognized = true;
+ }
+ // no else required; this might be the encoding declaration
+ } else if (ch == '?') {
+ question = true;
+ pos = 0;
+ } else if ((ch == '>') && (question == true)) {
+ // there is no encoding declaration, so assume that the initial
+ // encoding guess was correct
+ done = true;
+ continue;
+ } else {
+ // still searching for the encoding declaration
+ pos = 0;
+ }
+
+ ch = reader.read();
+ }
+
+ if (done == false) {
+ StringBuilder buffer = new StringBuilder(MAX_ENC_NAME);
+
+ if (((ch >= 'a') && (ch <= 'z')) |
+ ((ch >= 'A') && (ch <= 'Z'))) {
+ // add the character to the result
+ buffer.append((char) ch);
+
+ loop = true;
+ while ((loop == true) && (done == false)) {
+ ch = reader.read();
+
+ if (((ch >= 'a') && (ch <= 'z')) ||
+ ((ch >= 'A') && (ch <= 'Z')) ||
+ ((ch >= '0') && (ch <= '9')) ||
+ (ch == '_') || (ch == '.') || (ch == '-')) {
+ // add the character to the result
+ buffer.append((char) ch);
+ } else if ((ch == DOUBLE_QUOTE) || (ch == SINGLE_QUOTE)) {
+ // finished!
+ found = true;
+ done = true;
+ result = buffer.toString();
+ } else {
+ // this is not a valid encoding name, so punt
+ done = true;
+ }
+ }
+ } else {
+ // this is not a valid encoding name, so punt
+ done = true;
+ }
+ }
+ // no else required; already failed to find the encoding somewhere else
+
+ return (result);
+ }
+
+ private static final int BOM_LENGTH = 4;
+ private static final int MAX_ENC_NAME = 512;
+
+ private static final int SPACE = 0x00000020;
+ private static final int TAB = 0x00000009;
+ private static final int LINEFEED = 0x0000000a;
+ private static final int RETURN = 0x0000000d;
+ private static final int EQUAL = '=';
+ private static final int DOUBLE_QUOTE = '\"';
+ private static final int SINGLE_QUOTE = '\'';
+
+ private static final int UTF_32_BE_BOM = 0x0000feff;
+ private static final int UTF_32_LE_BOM = 0xfffe0000;
+ private static final int UTF_16_BE_BOM = 0xfeff0000;
+ private static final int UTF_16_LE_BOM = 0xfffe0000;
+ private static final int UTF_8_BOM = 0xefbbbf00;
+ private static final int UNUSUAL_OCTET_1 = 0x00003c00;
+ private static final int UNUSUAL_OCTET_2 = 0x003c0000;
+ private static final int UTF_16BE = 0x003c003f;
+ private static final int UTF_16LE = 0x3c003f00;
+ private static final int EBCDIC = 0x4c6fa794;
+ private static final int XML_DECLARATION = 0x3c3f786d;
+
+ private static final String UTF_32_ENC = "UTF-32";
+ private static final String UTF_16_ENC = "UTF-16";
+ private static final String UTF_16BE_ENC = "UTF-16BE";
+ private static final String UTF_16LE_ENC = "UTF-16LE";
+ private static final String UTF_8_ENC = "UTF-8";
+ private static final String IBM037_ENC = "IBM037";
+
+ private static final String XML_DECL_START = "<?xml";
+ private static final String ENCODING_DECL = "encoding";
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/xml/XMLNode.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2006, 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.
+ *
+ * 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 jnlp.converter.parser.xml;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Class that contains information about an XML Node
+ */
+public class XMLNode {
+ private final boolean _isElement; // Element/PCTEXT
+ private final String _name;
+ private final XMLAttribute _attr;
+ private XMLNode _parent; // Parent Node
+ private XMLNode _nested; // Nested XML tags
+ private XMLNode _next; // Following XML tag on the same level
+
+ public final static String WILDCARD = "*";
+
+ /** Creates a PCTEXT node */
+ public XMLNode(String name) {
+ _isElement = false;
+ _name = name;
+ _attr = null;
+ _nested = null;
+ _next = null;
+ _parent = null;
+ }
+
+ /** Creates a ELEMENT node */
+ public XMLNode(String name, XMLAttribute attr) {
+ _isElement = true;
+ _name = stripNameSpace(name);
+ _attr = attr;
+ _nested = null;
+ _next = null;
+ _parent = null;
+ }
+
+ public String getName() {
+ return _name;
+ }
+
+ public XMLAttribute getAttributes() {
+ return _attr;
+ }
+
+ public XMLNode getNested() {
+ return _nested;
+ }
+
+ public XMLNode getNext() {
+ return _next;
+ }
+
+ public boolean isElement() {
+ return _isElement;
+ }
+
+ public void setParent(XMLNode parent) {
+ _parent = parent;
+ }
+
+ public XMLNode getParent() {
+ return _parent;
+ }
+
+ public void setNext(XMLNode next) {
+ _next = next;
+ }
+
+ public void setNested(XMLNode nested) {
+ _nested = nested;
+ }
+
+ public static String stripNameSpace(String name) {
+ if (name != null && !name.startsWith("xmlns:")) {
+ int i = name.lastIndexOf(":");
+ if (i >= 0 && i < name.length()) {
+ return name.substring(i+1);
+ }
+ }
+ return name;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 83 * hash + (this._name != null ? this._name.hashCode() : 0);
+ hash = 83 * hash + (this._attr != null ? this._attr.hashCode() : 0);
+ hash = 83 * hash + (this._nested != null ? this._nested.hashCode() : 0);
+ hash = 83 * hash + (this._next != null ? this._next.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || !(o instanceof XMLNode)) return false;
+ XMLNode other = (XMLNode)o;
+ boolean result =
+ match(_name, other._name) &&
+ match(_attr, other._attr) &&
+ match(_nested, other._nested) &&
+ match(_next, other._next);
+ return result;
+ }
+
+ public String getAttribute(String name) {
+ XMLAttribute cur = _attr;
+ while(cur != null) {
+ if (name.equals(cur.getName())) return cur.getValue();
+ cur = cur.getNext();
+ }
+ return "";
+ }
+
+ private static boolean match(Object o1, Object o2) {
+ if (o1 == null) {
+ return (o2 == null);
+ }
+ return o1.equals(o2);
+ }
+
+ public void printToStream(PrintWriter out) {
+ printToStream(out, false);
+ }
+
+ public void printToStream(PrintWriter out, boolean trim) {
+ printToStream(out, 0, trim);
+ }
+
+ public void printToStream(PrintWriter out, int n, boolean trim) {
+ if (!isElement()) {
+ String value = _name; // value node (where name is data of parent)
+ if (trim && value.length() > 512) {
+ value = "...";
+ }
+ out.print(value);
+ } else {
+ if (_nested == null) {
+ String attrString = (_attr == null) ? "" : (" " + _attr.toString());
+ lineln(out, n, "<" + _name + attrString + "/>");
+ } else {
+ String attrString = (_attr == null) ? "" : (" " + _attr.toString());
+ lineln(out, n, "<" + _name + attrString + ">");
+ _nested.printToStream(out, n + 1, trim);
+ if (_nested.isElement()) {
+ lineln(out, n, "</" + _name + ">");
+ } else {
+ out.print("</" + _name + ">");
+ }
+ }
+ }
+ if (_next != null) {
+ _next.printToStream(out, n, trim);
+ }
+ }
+
+ private static void lineln(PrintWriter out, int indent, String s) {
+ out.println("");
+ for(int i = 0; i < indent; i++) {
+ out.print(" ");
+ }
+ out.print(s);
+ }
+
+ @Override
+ public String toString() {
+ return toString(false);
+ }
+
+ public String toString(boolean hideLongElementValue) {
+ StringWriter sw = new StringWriter(1000);
+ PrintWriter pw = new PrintWriter(sw);
+ printToStream(pw, hideLongElementValue);
+ pw.close();
+ return sw.toString();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/jpackage/JNLPConverter/src/jnlp/converter/parser/xml/XMLParser.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2014, 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.
+ *
+ * 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 jnlp.converter.parser.xml;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.StringReader;
+import java.io.IOException;
+import java.util.Stack;
+
+public class XMLParser extends DefaultHandler {
+
+ private XMLNode _root;
+ private final String _source;
+ private Stack<XMLNode> _inProgress;
+ private String _characters;
+
+ // although defined in com.sun.org.apache.xerces.internal.impl.Constants,
+ // we should not be able to access that, so defined here
+ private final static String DTD_DOWNLOAD =
+ "http://apache.org/xml/features/nonvalidating/load-external-dtd";
+
+ private static final SAXParserFactory SPF = SAXParserFactory.newInstance();
+
+ /*
+ * Construct an <code>XMLParser</code>.
+ *
+ * @param source - the source text to parse.
+ */
+ public XMLParser(String source) {
+ _source = source.trim();
+ }
+
+ public XMLNode parse() throws SAXException {
+ // normally we parse without validating, but leave option to parse
+ // with validation, possibly controlled by config option.
+ return parse(false);
+ }
+
+ public XMLNode parse(boolean validating) throws SAXException {
+ _root = null;
+ _inProgress = new Stack<>();
+
+ try {
+ InputSource is = new InputSource(new StringReader(_source));
+ SPF.setValidating(validating);
+ // only download dtd file from DOCTYPE if we are doing validation
+ try {
+ SPF.setFeature(DTD_DOWNLOAD, validating);
+ } catch (Exception e) {
+ }
+ SAXParser sp = SPF.newSAXParser();
+ sp.parse(is, this);
+ } catch (ParserConfigurationException | IOException pce) {
+ throw new SAXException(pce);
+ }
+
+ return _root;
+ }
+
+ @Override
+ public void startElement(String uri, String localeName, String qName,
+ Attributes attributes) throws SAXException {
+
+ XMLAttribute first = null;
+ XMLAttribute last = null;
+ int len = attributes.getLength();
+
+ for (int i = 0; i < len; i++) {
+ XMLAttribute att = new XMLAttribute(
+ // in old implementation attribute names and values were trimmed
+ attributes.getQName(i).trim(), attributes.getValue(i).trim());
+ if (first == null) {
+ first = att;
+ }
+ if (last != null) {
+ last.setNext(att);
+ }
+ last = att;
+ }
+ _inProgress.push(new XMLNode(qName, first));
+ _characters = null;
+ }
+
+ @Override
+ public void endElement(String uri, String localeName, String elementName)
+ throws SAXException {
+ XMLNode node = _inProgress.pop();
+ // <information>
+ // <title>Title</title>
+ // <vendor>Vendor</vendor> "Some whitespaces"
+ // </information>
+ // In example above when we receive end of <information> we will
+ // have _characters set to whitespace and new line and it will be
+ // added as child node to <information>. This will break our cache code
+ // which will think that JNLP file changed on server even if it is not
+ // and thus we might not load properly.
+ //
+ // <application-desc name="HelloWorld" main-class="HelloWorld">
+ // <argument> test with whitespaces </argument>
+ // </application-desc>
+ // From example above we want to include whitespaces for <argument>.
+ //
+ // <node1>
+ // <node2>abc</node2>
+ // xyz (might be whitespaces)
+ // </node1>
+ // In JNLP spec we do not have cases when node have nested nodes as
+ // well as text which is whitespaces only.
+ //
+ // So to fix it lets check if ending node have nested nodes, then do
+ // not add whitespaces only node.
+ if (node != null && node.getNested() != null && _characters != null) {
+ String trimCharacters = _characters.trim();
+ if ((trimCharacters == null) || (trimCharacters.length() == 0)) {
+ _characters = null; // No need to add whitespaces only
+ }
+ }
+ if ((_characters != null) && (_characters.trim().length() > 0)) {
+ addChild(node, new XMLNode(_characters));
+ }
+
+ if (_inProgress.isEmpty()) {
+ _root = node;
+ } else {
+ addChild(_inProgress.peek(), node);
+ }
+ _characters = null;
+ }
+
+ @Override
+ public void characters(char[] chars, int start, int length)
+ throws SAXException {
+ String s = new String(chars, start, length);
+ _characters = ((_characters == null) ? s : _characters + s);
+ }
+
+ @Override
+ public void ignorableWhitespace(char[] chars, int start, int length)
+ throws SAXException {
+ String s = new String(chars, start, length);
+ _characters = ((_characters == null) ? s : _characters + s);
+ }
+
+ private void addChild(XMLNode parent, XMLNode child) {
+ child.setParent(parent);
+
+ XMLNode sibling = parent.getNested();
+ if (sibling == null) {
+ parent.setNested(child); // set us as only child
+ } else {
+ while (sibling.getNext() != null) {
+ sibling = sibling.getNext();
+ }
+ sibling.setNext(child); // sets us as youngest child
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/nbproject/jpackage/JNLPConverter/build.xml Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- You may freely edit this file. See commented blocks below for -->
+<!-- some examples of how to customize the build. -->
+<!-- (If you delete it and reopen the project it will be recreated.) -->
+<!-- By default, only the Clean and Build commands use this build script. -->
+<!-- Commands such as Run, Debug, and Test only use this build script if -->
+<!-- the Compile on Save feature is turned off for the project. -->
+<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
+<!-- in the project's Project Properties dialog box.-->
+<project name="JNLPConverter" default="default" basedir=".">
+ <description>Builds, tests, and runs the project JNLPConverter.</description>
+ <import file="nbproject/build-impl.xml"/>
+ <!--
+
+ There exist several targets which are by default empty and which can be
+ used for execution of your tasks. These targets are usually executed
+ before and after some main targets. They are:
+
+ -pre-init: called before initialization of project properties
+ -post-init: called after initialization of project properties
+ -pre-compile: called before javac compilation
+ -post-compile: called after javac compilation
+ -pre-compile-single: called before javac compilation of single file
+ -post-compile-single: called after javac compilation of single file
+ -pre-compile-test: called before javac compilation of JUnit tests
+ -post-compile-test: called after javac compilation of JUnit tests
+ -pre-compile-test-single: called before javac compilation of single JUnit test
+ -post-compile-test-single: called after javac compilation of single JUunit test
+ -pre-jar: called before JAR building
+ -post-jar: called after JAR building
+ -post-clean: called after cleaning build products
+
+ (Targets beginning with '-' are not intended to be called on their own.)
+
+ Example of inserting an obfuscator after compilation could look like this:
+
+ <target name="-post-compile">
+ <obfuscate>
+ <fileset dir="${build.classes.dir}"/>
+ </obfuscate>
+ </target>
+
+ For list of available properties check the imported
+ nbproject/build-impl.xml file.
+
+
+ Another way to customize the build is by overriding existing main targets.
+ The targets of interest are:
+
+ -init-macrodef-javac: defines macro for javac compilation
+ -init-macrodef-junit: defines macro for junit execution
+ -init-macrodef-debug: defines macro for class debugging
+ -init-macrodef-java: defines macro for class execution
+ -do-jar: JAR building
+ run: execution of project
+ -javadoc-build: Javadoc generation
+ test-report: JUnit report generation
+
+ An example of overriding the target for project execution could look like this:
+
+ <target name="run" depends="JNLPConverter-impl.jar">
+ <exec dir="bin" executable="launcher.exe">
+ <arg file="${dist.jar}"/>
+ </exec>
+ </target>
+
+ Notice that the overridden target depends on the jar target and not only on
+ the compile target as the regular run target does. Again, for a list of available
+ properties which you can use, check the target you are overriding in the
+ nbproject/build-impl.xml file.
+
+ -->
+ <target name="-post-jar">
+ <copy file="${dist.jar}" todir="../../../jpackage/jnlpconverter"/>
+ </target>
+
+ <target name="-post-clean">
+ <delete file="../../../jpackage/jnlpconverter/JNLPConverter.jar"/>
+ </target>
+</project>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/nbproject/jpackage/JNLPConverter/manifest.mf Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+X-COMMENT: Main-Class will be added automatically by build
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/nbproject/jpackage/JNLPConverter/nbproject/build-impl.xml Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,1403 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+*** GENERATED FROM project.xml - DO NOT EDIT ***
+*** EDIT ../build.xml INSTEAD ***
+
+For the purpose of easier reading the script
+is divided into following sections:
+
+ - initialization
+ - compilation
+ - jar
+ - execution
+ - debugging
+ - javadoc
+ - test compilation
+ - test execution
+ - test debugging
+ - applet
+ - cleanup
+
+ -->
+<project xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3" xmlns:jaxrpc="http://www.netbeans.org/ns/j2se-project/jax-rpc" basedir=".." default="default" name="JNLPConverter-impl">
+ <fail message="Please build using Ant 1.8.0 or higher.">
+ <condition>
+ <not>
+ <antversion atleast="1.8.0"/>
+ </not>
+ </condition>
+ </fail>
+ <target depends="test,jar,javadoc" description="Build and test whole project." name="default"/>
+ <!--
+ ======================
+ INITIALIZATION SECTION
+ ======================
+ -->
+ <target name="-pre-init">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="-pre-init" name="-init-private">
+ <property file="nbproject/private/config.properties"/>
+ <property file="nbproject/private/configs/${config}.properties"/>
+ <property file="nbproject/private/private.properties"/>
+ </target>
+ <target depends="-pre-init,-init-private" name="-init-user">
+ <property file="${user.properties.file}"/>
+ <!-- The two properties below are usually overridden -->
+ <!-- by the active platform. Just a fallback. -->
+ <property name="default.javac.source" value="1.6"/>
+ <property name="default.javac.target" value="1.6"/>
+ </target>
+ <target depends="-pre-init,-init-private,-init-user" name="-init-project">
+ <property file="nbproject/configs/${config}.properties"/>
+ <property file="nbproject/project.properties"/>
+ </target>
+ <target depends="-pre-init,-init-private,-init-user,-init-project,-init-macrodef-property" name="-do-init">
+ <property name="platform.java" value="${java.home}/bin/java"/>
+ <available file="${manifest.file}" property="manifest.available"/>
+ <condition property="splashscreen.available">
+ <and>
+ <not>
+ <equals arg1="${application.splash}" arg2="" trim="true"/>
+ </not>
+ <available file="${application.splash}"/>
+ </and>
+ </condition>
+ <condition property="main.class.available">
+ <and>
+ <isset property="main.class"/>
+ <not>
+ <equals arg1="${main.class}" arg2="" trim="true"/>
+ </not>
+ </and>
+ </condition>
+ <condition property="profile.available">
+ <and>
+ <isset property="javac.profile"/>
+ <length length="0" string="${javac.profile}" when="greater"/>
+ <matches pattern="((1\.[89])|9)(\..*)?" string="${javac.source}"/>
+ </and>
+ </condition>
+ <condition property="do.archive">
+ <or>
+ <not>
+ <istrue value="${jar.archive.disabled}"/>
+ </not>
+ <istrue value="${not.archive.disabled}"/>
+ </or>
+ </condition>
+ <condition property="do.mkdist">
+ <and>
+ <isset property="do.archive"/>
+ <isset property="libs.CopyLibs.classpath"/>
+ <not>
+ <istrue value="${mkdist.disabled}"/>
+ </not>
+ </and>
+ </condition>
+ <condition property="do.archive+manifest.available">
+ <and>
+ <isset property="manifest.available"/>
+ <istrue value="${do.archive}"/>
+ </and>
+ </condition>
+ <condition property="do.archive+main.class.available">
+ <and>
+ <isset property="main.class.available"/>
+ <istrue value="${do.archive}"/>
+ </and>
+ </condition>
+ <condition property="do.archive+splashscreen.available">
+ <and>
+ <isset property="splashscreen.available"/>
+ <istrue value="${do.archive}"/>
+ </and>
+ </condition>
+ <condition property="do.archive+profile.available">
+ <and>
+ <isset property="profile.available"/>
+ <istrue value="${do.archive}"/>
+ </and>
+ </condition>
+ <condition property="have.tests">
+ <or/>
+ </condition>
+ <condition property="have.sources">
+ <or>
+ <available file="${src.src.dir}"/>
+ </or>
+ </condition>
+ <condition property="netbeans.home+have.tests">
+ <and>
+ <isset property="netbeans.home"/>
+ <isset property="have.tests"/>
+ </and>
+ </condition>
+ <condition property="no.javadoc.preview">
+ <and>
+ <isset property="javadoc.preview"/>
+ <isfalse value="${javadoc.preview}"/>
+ </and>
+ </condition>
+ <property name="run.jvmargs" value=""/>
+ <property name="run.jvmargs.ide" value=""/>
+ <property name="javac.compilerargs" value=""/>
+ <property name="work.dir" value="${basedir}"/>
+ <condition property="no.deps">
+ <and>
+ <istrue value="${no.dependencies}"/>
+ </and>
+ </condition>
+ <property name="javac.debug" value="true"/>
+ <property name="javadoc.preview" value="true"/>
+ <property name="application.args" value=""/>
+ <property name="source.encoding" value="${file.encoding}"/>
+ <property name="runtime.encoding" value="${source.encoding}"/>
+ <property name="manifest.encoding" value="${source.encoding}"/>
+ <condition property="javadoc.encoding.used" value="${javadoc.encoding}">
+ <and>
+ <isset property="javadoc.encoding"/>
+ <not>
+ <equals arg1="${javadoc.encoding}" arg2=""/>
+ </not>
+ </and>
+ </condition>
+ <property name="javadoc.encoding.used" value="${source.encoding}"/>
+ <property name="includes" value="**"/>
+ <property name="excludes" value=""/>
+ <property name="do.depend" value="false"/>
+ <condition property="do.depend.true">
+ <istrue value="${do.depend}"/>
+ </condition>
+ <path id="endorsed.classpath.path" path="${endorsed.classpath}"/>
+ <condition else="" property="endorsed.classpath.cmd.line.arg" value="-Xbootclasspath/p:'${toString:endorsed.classpath.path}'">
+ <and>
+ <isset property="endorsed.classpath"/>
+ <not>
+ <equals arg1="${endorsed.classpath}" arg2="" trim="true"/>
+ </not>
+ </and>
+ </condition>
+ <condition else="" property="javac.profile.cmd.line.arg" value="-profile ${javac.profile}">
+ <isset property="profile.available"/>
+ </condition>
+ <condition else="false" property="jdkBug6558476">
+ <and>
+ <matches pattern="1\.[56]" string="${java.specification.version}"/>
+ <not>
+ <os family="unix"/>
+ </not>
+ </and>
+ </condition>
+ <condition else="false" property="javac.fork">
+ <or>
+ <istrue value="${jdkBug6558476}"/>
+ <istrue value="${javac.external.vm}"/>
+ </or>
+ </condition>
+ <property name="jar.index" value="false"/>
+ <property name="jar.index.metainf" value="${jar.index}"/>
+ <property name="copylibs.rebase" value="true"/>
+ <available file="${meta.inf.dir}/persistence.xml" property="has.persistence.xml"/>
+ <condition property="junit.available">
+ <or>
+ <available classname="org.junit.Test" classpath="${run.test.classpath}"/>
+ <available classname="junit.framework.Test" classpath="${run.test.classpath}"/>
+ </or>
+ </condition>
+ <condition property="testng.available">
+ <available classname="org.testng.annotations.Test" classpath="${run.test.classpath}"/>
+ </condition>
+ <condition property="junit+testng.available">
+ <and>
+ <istrue value="${junit.available}"/>
+ <istrue value="${testng.available}"/>
+ </and>
+ </condition>
+ <condition else="testng" property="testng.mode" value="mixed">
+ <istrue value="${junit+testng.available}"/>
+ </condition>
+ <condition else="" property="testng.debug.mode" value="-mixed">
+ <istrue value="${junit+testng.available}"/>
+ </condition>
+ <property name="java.failonerror" value="true"/>
+ </target>
+ <target name="-post-init">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="-pre-init,-init-private,-init-user,-init-project,-do-init" name="-init-check">
+ <fail unless="src.src.dir">Must set src.src.dir</fail>
+ <fail unless="build.dir">Must set build.dir</fail>
+ <fail unless="dist.dir">Must set dist.dir</fail>
+ <fail unless="build.classes.dir">Must set build.classes.dir</fail>
+ <fail unless="dist.javadoc.dir">Must set dist.javadoc.dir</fail>
+ <fail unless="build.test.classes.dir">Must set build.test.classes.dir</fail>
+ <fail unless="build.test.results.dir">Must set build.test.results.dir</fail>
+ <fail unless="build.classes.excludes">Must set build.classes.excludes</fail>
+ <fail unless="dist.jar">Must set dist.jar</fail>
+ </target>
+ <target name="-init-macrodef-property">
+ <macrodef name="property" uri="http://www.netbeans.org/ns/j2se-project/1">
+ <attribute name="name"/>
+ <attribute name="value"/>
+ <sequential>
+ <property name="@{name}" value="${@{value}}"/>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-ap-cmdline-properties" if="ap.supported.internal" name="-init-macrodef-javac-with-processors">
+ <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${src.src.dir}" name="srcdir"/>
+ <attribute default="${build.classes.dir}" name="destdir"/>
+ <attribute default="${javac.classpath}" name="classpath"/>
+ <attribute default="${javac.processorpath}" name="processorpath"/>
+ <attribute default="${build.generated.sources.dir}/ap-source-output" name="apgeneratedsrcdir"/>
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="${javac.debug}" name="debug"/>
+ <attribute default="${empty.dir}" name="sourcepath"/>
+ <attribute default="${empty.dir}" name="gensrcdir"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property location="${build.dir}/empty" name="empty.dir"/>
+ <mkdir dir="${empty.dir}"/>
+ <mkdir dir="@{apgeneratedsrcdir}"/>
+ <javac debug="@{debug}" deprecation="${javac.deprecation}" destdir="@{destdir}" encoding="${source.encoding}" excludes="@{excludes}" fork="${javac.fork}" includeantruntime="false" includes="@{includes}" source="${javac.source}" sourcepath="@{sourcepath}" srcdir="@{srcdir}" target="${javac.target}" tempdir="${java.io.tmpdir}">
+ <src>
+ <dirset dir="@{gensrcdir}" erroronmissingdir="false">
+ <include name="*"/>
+ </dirset>
+ </src>
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ <compilerarg line="${endorsed.classpath.cmd.line.arg}"/>
+ <compilerarg line="${javac.profile.cmd.line.arg}"/>
+ <compilerarg line="${javac.compilerargs}"/>
+ <compilerarg value="-processorpath"/>
+ <compilerarg path="@{processorpath}:${empty.dir}"/>
+ <compilerarg line="${ap.processors.internal}"/>
+ <compilerarg line="${annotation.processing.processor.options}"/>
+ <compilerarg value="-s"/>
+ <compilerarg path="@{apgeneratedsrcdir}"/>
+ <compilerarg line="${ap.proc.none.internal}"/>
+ <customize/>
+ </javac>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-ap-cmdline-properties" name="-init-macrodef-javac-without-processors" unless="ap.supported.internal">
+ <macrodef name="javac" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${src.src.dir}" name="srcdir"/>
+ <attribute default="${build.classes.dir}" name="destdir"/>
+ <attribute default="${javac.classpath}" name="classpath"/>
+ <attribute default="${javac.processorpath}" name="processorpath"/>
+ <attribute default="${build.generated.sources.dir}/ap-source-output" name="apgeneratedsrcdir"/>
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="${javac.debug}" name="debug"/>
+ <attribute default="${empty.dir}" name="sourcepath"/>
+ <attribute default="${empty.dir}" name="gensrcdir"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property location="${build.dir}/empty" name="empty.dir"/>
+ <mkdir dir="${empty.dir}"/>
+ <javac debug="@{debug}" deprecation="${javac.deprecation}" destdir="@{destdir}" encoding="${source.encoding}" excludes="@{excludes}" fork="${javac.fork}" includeantruntime="false" includes="@{includes}" source="${javac.source}" sourcepath="@{sourcepath}" srcdir="@{srcdir}" target="${javac.target}" tempdir="${java.io.tmpdir}">
+ <src>
+ <dirset dir="@{gensrcdir}" erroronmissingdir="false">
+ <include name="*"/>
+ </dirset>
+ </src>
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ <compilerarg line="${endorsed.classpath.cmd.line.arg}"/>
+ <compilerarg line="${javac.profile.cmd.line.arg}"/>
+ <compilerarg line="${javac.compilerargs}"/>
+ <customize/>
+ </javac>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-javac-with-processors,-init-macrodef-javac-without-processors" name="-init-macrodef-javac">
+ <macrodef name="depend" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${src.src.dir}" name="srcdir"/>
+ <attribute default="${build.classes.dir}" name="destdir"/>
+ <attribute default="${javac.classpath}" name="classpath"/>
+ <sequential>
+ <depend cache="${build.dir}/depcache" destdir="@{destdir}" excludes="${excludes}" includes="${includes}" srcdir="@{srcdir}">
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ </depend>
+ </sequential>
+ </macrodef>
+ <macrodef name="force-recompile" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${build.classes.dir}" name="destdir"/>
+ <sequential>
+ <fail unless="javac.includes">Must set javac.includes</fail>
+ <pathconvert pathsep="${line.separator}" property="javac.includes.binary">
+ <path>
+ <filelist dir="@{destdir}" files="${javac.includes}"/>
+ </path>
+ <globmapper from="*.java" to="*.class"/>
+ </pathconvert>
+ <tempfile deleteonexit="true" property="javac.includesfile.binary"/>
+ <echo file="${javac.includesfile.binary}" message="${javac.includes.binary}"/>
+ <delete>
+ <files includesfile="${javac.includesfile.binary}"/>
+ </delete>
+ <delete>
+ <fileset file="${javac.includesfile.binary}"/>
+ </delete>
+ </sequential>
+ </macrodef>
+ </target>
+ <target if="${junit.available}" name="-init-macrodef-junit-init">
+ <condition else="false" property="nb.junit.batch" value="true">
+ <and>
+ <istrue value="${junit.available}"/>
+ <not>
+ <isset property="test.method"/>
+ </not>
+ </and>
+ </condition>
+ <condition else="false" property="nb.junit.single" value="true">
+ <and>
+ <istrue value="${junit.available}"/>
+ <isset property="test.method"/>
+ </and>
+ </condition>
+ </target>
+ <target name="-init-test-properties">
+ <property name="test.binaryincludes" value="<nothing>"/>
+ <property name="test.binarytestincludes" value=""/>
+ <property name="test.binaryexcludes" value=""/>
+ </target>
+ <target if="${nb.junit.single}" name="-init-macrodef-junit-single" unless="${nb.junit.batch}">
+ <macrodef name="junit" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property name="junit.forkmode" value="perTest"/>
+ <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
+ <test methods="@{testmethods}" name="@{testincludes}" todir="${build.test.results.dir}"/>
+ <syspropertyset>
+ <propertyref prefix="test-sys-prop."/>
+ <mapper from="test-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <formatter type="brief" usefile="false"/>
+ <formatter type="xml"/>
+ <jvmarg value="-ea"/>
+ <customize/>
+ </junit>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-test-properties" if="${nb.junit.batch}" name="-init-macrodef-junit-batch" unless="${nb.junit.single}">
+ <macrodef name="junit" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property name="junit.forkmode" value="perTest"/>
+ <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
+ <batchtest todir="${build.test.results.dir}">
+ <fileset dir="${build.test.classes.dir}" excludes="@{excludes},${excludes},${test.binaryexcludes}" includes="${test.binaryincludes}">
+ <filename name="${test.binarytestincludes}"/>
+ </fileset>
+ </batchtest>
+ <syspropertyset>
+ <propertyref prefix="test-sys-prop."/>
+ <mapper from="test-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <formatter type="brief" usefile="false"/>
+ <formatter type="xml"/>
+ <jvmarg value="-ea"/>
+ <customize/>
+ </junit>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-junit-init,-init-macrodef-junit-single, -init-macrodef-junit-batch" if="${junit.available}" name="-init-macrodef-junit"/>
+ <target if="${testng.available}" name="-init-macrodef-testng">
+ <macrodef name="testng" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <condition else="" property="testng.methods.arg" value="@{testincludes}.@{testmethods}">
+ <isset property="test.method"/>
+ </condition>
+ <union id="test.set"/>
+ <taskdef classname="org.testng.TestNGAntTask" classpath="${run.test.classpath}" name="testng"/>
+ <testng classfilesetref="test.set" failureProperty="tests.failed" listeners="org.testng.reporters.VerboseReporter" methods="${testng.methods.arg}" mode="${testng.mode}" outputdir="${build.test.results.dir}" suitename="JNLPConverter" testname="TestNG tests" workingDir="${work.dir}">
+ <xmlfileset dir="${build.test.classes.dir}" includes="@{testincludes}"/>
+ <propertyset>
+ <propertyref prefix="test-sys-prop."/>
+ <mapper from="test-sys-prop.*" to="*" type="glob"/>
+ </propertyset>
+ <customize/>
+ </testng>
+ </sequential>
+ </macrodef>
+ </target>
+ <target name="-init-macrodef-test-impl">
+ <macrodef name="test-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element implicit="true" name="customize" optional="true"/>
+ <sequential>
+ <echo>No tests executed.</echo>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-junit" if="${junit.available}" name="-init-macrodef-junit-impl">
+ <macrodef name="test-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element implicit="true" name="customize" optional="true"/>
+ <sequential>
+ <j2seproject3:junit excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
+ <customize/>
+ </j2seproject3:junit>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-testng" if="${testng.available}" name="-init-macrodef-testng-impl">
+ <macrodef name="test-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element implicit="true" name="customize" optional="true"/>
+ <sequential>
+ <j2seproject3:testng excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
+ <customize/>
+ </j2seproject3:testng>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-test-impl,-init-macrodef-junit-impl,-init-macrodef-testng-impl" name="-init-macrodef-test">
+ <macrodef name="test" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <sequential>
+ <j2seproject3:test-impl excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
+ <customize>
+ <classpath>
+ <path path="${run.test.classpath}"/>
+ </classpath>
+ <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
+ <jvmarg line="${run.jvmargs}"/>
+ <jvmarg line="${run.jvmargs.ide}"/>
+ </customize>
+ </j2seproject3:test-impl>
+ </sequential>
+ </macrodef>
+ </target>
+ <target if="${junit.available}" name="-init-macrodef-junit-debug" unless="${nb.junit.batch}">
+ <macrodef name="junit-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property name="junit.forkmode" value="perTest"/>
+ <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
+ <test methods="@{testmethods}" name="@{testincludes}" todir="${build.test.results.dir}"/>
+ <syspropertyset>
+ <propertyref prefix="test-sys-prop."/>
+ <mapper from="test-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <formatter type="brief" usefile="false"/>
+ <formatter type="xml"/>
+ <jvmarg value="-ea"/>
+ <jvmarg line="${debug-args-line}"/>
+ <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
+ <customize/>
+ </junit>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-test-properties" if="${nb.junit.batch}" name="-init-macrodef-junit-debug-batch">
+ <macrodef name="junit-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property name="junit.forkmode" value="perTest"/>
+ <junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
+ <batchtest todir="${build.test.results.dir}">
+ <fileset dir="${build.test.classes.dir}" excludes="@{excludes},${excludes},${test.binaryexcludes}" includes="${test.binaryincludes}">
+ <filename name="${test.binarytestincludes}"/>
+ </fileset>
+ </batchtest>
+ <syspropertyset>
+ <propertyref prefix="test-sys-prop."/>
+ <mapper from="test-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <formatter type="brief" usefile="false"/>
+ <formatter type="xml"/>
+ <jvmarg value="-ea"/>
+ <jvmarg line="${debug-args-line}"/>
+ <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
+ <customize/>
+ </junit>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-junit-debug,-init-macrodef-junit-debug-batch" if="${junit.available}" name="-init-macrodef-junit-debug-impl">
+ <macrodef name="test-debug-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <element implicit="true" name="customize" optional="true"/>
+ <sequential>
+ <j2seproject3:junit-debug excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
+ <customize/>
+ </j2seproject3:junit-debug>
+ </sequential>
+ </macrodef>
+ </target>
+ <target if="${testng.available}" name="-init-macrodef-testng-debug">
+ <macrodef name="testng-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${main.class}" name="testClass"/>
+ <attribute default="" name="testMethod"/>
+ <element name="customize2" optional="true"/>
+ <sequential>
+ <condition else="-testclass @{testClass}" property="test.class.or.method" value="-methods @{testClass}.@{testMethod}">
+ <isset property="test.method"/>
+ </condition>
+ <condition else="-suitename JNLPConverter -testname @{testClass} ${test.class.or.method}" property="testng.cmd.args" value="@{testClass}">
+ <matches pattern=".*\.xml" string="@{testClass}"/>
+ </condition>
+ <delete dir="${build.test.results.dir}" quiet="true"/>
+ <mkdir dir="${build.test.results.dir}"/>
+ <j2seproject3:debug classname="org.testng.TestNG" classpath="${debug.test.classpath}">
+ <customize>
+ <customize2/>
+ <jvmarg value="-ea"/>
+ <arg line="${testng.debug.mode}"/>
+ <arg line="-d ${build.test.results.dir}"/>
+ <arg line="-listener org.testng.reporters.VerboseReporter"/>
+ <arg line="${testng.cmd.args}"/>
+ </customize>
+ </j2seproject3:debug>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-testng-debug" if="${testng.available}" name="-init-macrodef-testng-debug-impl">
+ <macrodef name="testng-debug-impl" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${main.class}" name="testClass"/>
+ <attribute default="" name="testMethod"/>
+ <element implicit="true" name="customize2" optional="true"/>
+ <sequential>
+ <j2seproject3:testng-debug testClass="@{testClass}" testMethod="@{testMethod}">
+ <customize2/>
+ </j2seproject3:testng-debug>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-junit-debug-impl" if="${junit.available}" name="-init-macrodef-test-debug-junit">
+ <macrodef name="test-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <attribute default="${main.class}" name="testClass"/>
+ <attribute default="" name="testMethod"/>
+ <sequential>
+ <j2seproject3:test-debug-impl excludes="@{excludes}" includes="@{includes}" testincludes="@{testincludes}" testmethods="@{testmethods}">
+ <customize>
+ <classpath>
+ <path path="${run.test.classpath}"/>
+ </classpath>
+ <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
+ <jvmarg line="${run.jvmargs}"/>
+ <jvmarg line="${run.jvmargs.ide}"/>
+ </customize>
+ </j2seproject3:test-debug-impl>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-testng-debug-impl" if="${testng.available}" name="-init-macrodef-test-debug-testng">
+ <macrodef name="test-debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${includes}" name="includes"/>
+ <attribute default="${excludes}" name="excludes"/>
+ <attribute default="**" name="testincludes"/>
+ <attribute default="" name="testmethods"/>
+ <attribute default="${main.class}" name="testClass"/>
+ <attribute default="" name="testMethod"/>
+ <sequential>
+ <j2seproject3:testng-debug-impl testClass="@{testClass}" testMethod="@{testMethod}">
+ <customize2>
+ <syspropertyset>
+ <propertyref prefix="test-sys-prop."/>
+ <mapper from="test-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ </customize2>
+ </j2seproject3:testng-debug-impl>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-init-macrodef-test-debug-junit,-init-macrodef-test-debug-testng" name="-init-macrodef-test-debug"/>
+ <!--
+ pre NB7.2 profiling section; consider it deprecated
+ -->
+ <target depends="-profile-pre-init, init, -profile-post-init, -profile-init-macrodef-profile, -profile-init-check" if="profiler.info.jvmargs.agent" name="profile-init"/>
+ <target if="profiler.info.jvmargs.agent" name="-profile-pre-init">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target if="profiler.info.jvmargs.agent" name="-profile-post-init">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target if="profiler.info.jvmargs.agent" name="-profile-init-macrodef-profile">
+ <macrodef name="resolve">
+ <attribute name="name"/>
+ <attribute name="value"/>
+ <sequential>
+ <property name="@{name}" value="${env.@{value}}"/>
+ </sequential>
+ </macrodef>
+ <macrodef name="profile">
+ <attribute default="${main.class}" name="classname"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property environment="env"/>
+ <resolve name="profiler.current.path" value="${profiler.info.pathvar}"/>
+ <java classname="@{classname}" dir="${profiler.info.dir}" failonerror="${java.failonerror}" fork="true" jvm="${profiler.info.jvm}">
+ <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
+ <jvmarg value="${profiler.info.jvmargs.agent}"/>
+ <jvmarg line="${profiler.info.jvmargs}"/>
+ <env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
+ <arg line="${application.args}"/>
+ <classpath>
+ <path path="${run.classpath}"/>
+ </classpath>
+ <syspropertyset>
+ <propertyref prefix="run-sys-prop."/>
+ <mapper from="run-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <customize/>
+ </java>
+ </sequential>
+ </macrodef>
+ </target>
+ <target depends="-profile-pre-init, init, -profile-post-init, -profile-init-macrodef-profile" if="profiler.info.jvmargs.agent" name="-profile-init-check">
+ <fail unless="profiler.info.jvm">Must set JVM to use for profiling in profiler.info.jvm</fail>
+ <fail unless="profiler.info.jvmargs.agent">Must set profiler agent JVM arguments in profiler.info.jvmargs.agent</fail>
+ </target>
+ <!--
+ end of pre NB7.2 profiling section
+ -->
+ <target depends="-init-debug-args" name="-init-macrodef-nbjpda">
+ <macrodef name="nbjpdastart" uri="http://www.netbeans.org/ns/j2se-project/1">
+ <attribute default="${main.class}" name="name"/>
+ <attribute default="${debug.classpath}" name="classpath"/>
+ <attribute default="" name="stopclassname"/>
+ <sequential>
+ <nbjpdastart addressproperty="jpda.address" name="@{name}" stopclassname="@{stopclassname}" transport="${debug-transport}">
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ </nbjpdastart>
+ </sequential>
+ </macrodef>
+ <macrodef name="nbjpdareload" uri="http://www.netbeans.org/ns/j2se-project/1">
+ <attribute default="${build.classes.dir}" name="dir"/>
+ <sequential>
+ <nbjpdareload>
+ <fileset dir="@{dir}" includes="${fix.classes}">
+ <include name="${fix.includes}*.class"/>
+ </fileset>
+ </nbjpdareload>
+ </sequential>
+ </macrodef>
+ </target>
+ <target name="-init-debug-args">
+ <property name="version-output" value="java version "${ant.java.version}"/>
+ <condition property="have-jdk-older-than-1.4">
+ <or>
+ <contains string="${version-output}" substring="java version "1.0"/>
+ <contains string="${version-output}" substring="java version "1.1"/>
+ <contains string="${version-output}" substring="java version "1.2"/>
+ <contains string="${version-output}" substring="java version "1.3"/>
+ </or>
+ </condition>
+ <condition else="-Xdebug" property="debug-args-line" value="-Xdebug -Xnoagent -Djava.compiler=none">
+ <istrue value="${have-jdk-older-than-1.4}"/>
+ </condition>
+ <condition else="dt_socket" property="debug-transport-by-os" value="dt_shmem">
+ <os family="windows"/>
+ </condition>
+ <condition else="${debug-transport-by-os}" property="debug-transport" value="${debug.transport}">
+ <isset property="debug.transport"/>
+ </condition>
+ </target>
+ <target depends="-init-debug-args" name="-init-macrodef-debug">
+ <macrodef name="debug" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${main.class}" name="classname"/>
+ <attribute default="${debug.classpath}" name="classpath"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <java classname="@{classname}" dir="${work.dir}" failonerror="${java.failonerror}" fork="true">
+ <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
+ <jvmarg line="${debug-args-line}"/>
+ <jvmarg value="-Xrunjdwp:transport=${debug-transport},address=${jpda.address}"/>
+ <jvmarg value="-Dfile.encoding=${runtime.encoding}"/>
+ <redirector errorencoding="${runtime.encoding}" inputencoding="${runtime.encoding}" outputencoding="${runtime.encoding}"/>
+ <jvmarg line="${run.jvmargs}"/>
+ <jvmarg line="${run.jvmargs.ide}"/>
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ <syspropertyset>
+ <propertyref prefix="run-sys-prop."/>
+ <mapper from="run-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <customize/>
+ </java>
+ </sequential>
+ </macrodef>
+ </target>
+ <target name="-init-macrodef-java">
+ <macrodef name="java" uri="http://www.netbeans.org/ns/j2se-project/1">
+ <attribute default="${main.class}" name="classname"/>
+ <attribute default="${run.classpath}" name="classpath"/>
+ <attribute default="jvm" name="jvm"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <java classname="@{classname}" dir="${work.dir}" failonerror="${java.failonerror}" fork="true">
+ <jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
+ <jvmarg value="-Dfile.encoding=${runtime.encoding}"/>
+ <redirector errorencoding="${runtime.encoding}" inputencoding="${runtime.encoding}" outputencoding="${runtime.encoding}"/>
+ <jvmarg line="${run.jvmargs}"/>
+ <jvmarg line="${run.jvmargs.ide}"/>
+ <classpath>
+ <path path="@{classpath}"/>
+ </classpath>
+ <syspropertyset>
+ <propertyref prefix="run-sys-prop."/>
+ <mapper from="run-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <customize/>
+ </java>
+ </sequential>
+ </macrodef>
+ </target>
+ <target name="-init-macrodef-copylibs">
+ <macrodef name="copylibs" uri="http://www.netbeans.org/ns/j2se-project/3">
+ <attribute default="${manifest.file}" name="manifest"/>
+ <element name="customize" optional="true"/>
+ <sequential>
+ <property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
+ <pathconvert property="run.classpath.without.build.classes.dir">
+ <path path="${run.classpath}"/>
+ <map from="${build.classes.dir.resolved}" to=""/>
+ </pathconvert>
+ <pathconvert pathsep=" " property="jar.classpath">
+ <path path="${run.classpath.without.build.classes.dir}"/>
+ <chainedmapper>
+ <flattenmapper/>
+ <filtermapper>
+ <replacestring from=" " to="%20"/>
+ </filtermapper>
+ <globmapper from="*" to="lib/*"/>
+ </chainedmapper>
+ </pathconvert>
+ <taskdef classname="org.netbeans.modules.java.j2seproject.copylibstask.CopyLibs" classpath="${libs.CopyLibs.classpath}" name="copylibs"/>
+ <copylibs compress="${jar.compress}" excludeFromCopy="${copylibs.excludes}" index="${jar.index}" indexMetaInf="${jar.index.metainf}" jarfile="${dist.jar}" manifest="@{manifest}" manifestencoding="UTF-8" rebase="${copylibs.rebase}" runtimeclasspath="${run.classpath.without.build.classes.dir}">
+ <fileset dir="${build.classes.dir}" excludes="${dist.archive.excludes}"/>
+ <manifest>
+ <attribute name="Class-Path" value="${jar.classpath}"/>
+ <customize/>
+ </manifest>
+ </copylibs>
+ </sequential>
+ </macrodef>
+ </target>
+ <target name="-init-presetdef-jar">
+ <presetdef name="jar" uri="http://www.netbeans.org/ns/j2se-project/1">
+ <jar compress="${jar.compress}" index="${jar.index}" jarfile="${dist.jar}" manifestencoding="UTF-8">
+ <j2seproject1:fileset dir="${build.classes.dir}" excludes="${dist.archive.excludes}"/>
+ </jar>
+ </presetdef>
+ </target>
+ <target name="-init-ap-cmdline-properties">
+ <property name="annotation.processing.enabled" value="true"/>
+ <property name="annotation.processing.processors.list" value=""/>
+ <property name="annotation.processing.processor.options" value=""/>
+ <property name="annotation.processing.run.all.processors" value="true"/>
+ <property name="javac.processorpath" value="${javac.classpath}"/>
+ <property name="javac.test.processorpath" value="${javac.test.classpath}"/>
+ <condition property="ap.supported.internal" value="true">
+ <not>
+ <matches pattern="1\.[0-5](\..*)?" string="${javac.source}"/>
+ </not>
+ </condition>
+ </target>
+ <target depends="-init-ap-cmdline-properties" if="ap.supported.internal" name="-init-ap-cmdline-supported">
+ <condition else="" property="ap.processors.internal" value="-processor ${annotation.processing.processors.list}">
+ <isfalse value="${annotation.processing.run.all.processors}"/>
+ </condition>
+ <condition else="" property="ap.proc.none.internal" value="-proc:none">
+ <isfalse value="${annotation.processing.enabled}"/>
+ </condition>
+ </target>
+ <target depends="-init-ap-cmdline-properties,-init-ap-cmdline-supported" name="-init-ap-cmdline">
+ <property name="ap.cmd.line.internal" value=""/>
+ </target>
+ <target depends="-pre-init,-init-private,-init-user,-init-project,-do-init,-post-init,-init-check,-init-macrodef-property,-init-macrodef-javac,-init-macrodef-test,-init-macrodef-test-debug,-init-macrodef-nbjpda,-init-macrodef-debug,-init-macrodef-java,-init-presetdef-jar,-init-ap-cmdline" name="init"/>
+ <!--
+ ===================
+ COMPILATION SECTION
+ ===================
+ -->
+ <target name="-deps-jar-init" unless="built-jar.properties">
+ <property location="${build.dir}/built-jar.properties" name="built-jar.properties"/>
+ <delete file="${built-jar.properties}" quiet="true"/>
+ </target>
+ <target if="already.built.jar.${basedir}" name="-warn-already-built-jar">
+ <echo level="warn" message="Cycle detected: JNLPConverter was already built"/>
+ </target>
+ <target depends="init,-deps-jar-init" name="deps-jar" unless="no.deps">
+ <mkdir dir="${build.dir}"/>
+ <touch file="${built-jar.properties}" verbose="false"/>
+ <property file="${built-jar.properties}" prefix="already.built.jar."/>
+ <antcall target="-warn-already-built-jar"/>
+ <propertyfile file="${built-jar.properties}">
+ <entry key="${basedir}" value=""/>
+ </propertyfile>
+ </target>
+ <target depends="init,-check-automatic-build,-clean-after-automatic-build" name="-verify-automatic-build"/>
+ <target depends="init" name="-check-automatic-build">
+ <available file="${build.classes.dir}/.netbeans_automatic_build" property="netbeans.automatic.build"/>
+ </target>
+ <target depends="init" if="netbeans.automatic.build" name="-clean-after-automatic-build">
+ <antcall target="clean"/>
+ </target>
+ <target depends="init,deps-jar" name="-pre-pre-compile">
+ <mkdir dir="${build.classes.dir}"/>
+ </target>
+ <target name="-pre-compile">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target if="do.depend.true" name="-compile-depend">
+ <pathconvert property="build.generated.subdirs">
+ <dirset dir="${build.generated.sources.dir}" erroronmissingdir="false">
+ <include name="*"/>
+ </dirset>
+ </pathconvert>
+ <j2seproject3:depend srcdir="${src.src.dir}:${build.generated.subdirs}"/>
+ </target>
+ <target depends="init,deps-jar,-pre-pre-compile,-pre-compile, -copy-persistence-xml,-compile-depend" if="have.sources" name="-do-compile">
+ <j2seproject3:javac gensrcdir="${build.generated.sources.dir}"/>
+ <copy todir="${build.classes.dir}">
+ <fileset dir="${src.src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
+ </copy>
+ </target>
+ <target if="has.persistence.xml" name="-copy-persistence-xml">
+ <mkdir dir="${build.classes.dir}/META-INF"/>
+ <copy todir="${build.classes.dir}/META-INF">
+ <fileset dir="${meta.inf.dir}" includes="persistence.xml orm.xml"/>
+ </copy>
+ </target>
+ <target name="-post-compile">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile,-do-compile,-post-compile" description="Compile project." name="compile"/>
+ <target name="-pre-compile-single">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,deps-jar,-pre-pre-compile" name="-do-compile-single">
+ <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
+ <j2seproject3:force-recompile/>
+ <j2seproject3:javac excludes="" gensrcdir="${build.generated.sources.dir}" includes="${javac.includes}" sourcepath="${src.src.dir}"/>
+ </target>
+ <target name="-post-compile-single">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,deps-jar,-verify-automatic-build,-pre-pre-compile,-pre-compile-single,-do-compile-single,-post-compile-single" name="compile-single"/>
+ <!--
+ ====================
+ JAR BUILDING SECTION
+ ====================
+ -->
+ <target depends="init" name="-pre-pre-jar">
+ <dirname file="${dist.jar}" property="dist.jar.dir"/>
+ <mkdir dir="${dist.jar.dir}"/>
+ </target>
+ <target name="-pre-jar">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init" if="do.archive" name="-do-jar-create-manifest" unless="manifest.available">
+ <tempfile deleteonexit="true" destdir="${build.dir}" property="tmp.manifest.file"/>
+ <touch file="${tmp.manifest.file}" verbose="false"/>
+ </target>
+ <target depends="init" if="do.archive+manifest.available" name="-do-jar-copy-manifest">
+ <tempfile deleteonexit="true" destdir="${build.dir}" property="tmp.manifest.file"/>
+ <copy encoding="${manifest.encoding}" file="${manifest.file}" outputencoding="UTF-8" tofile="${tmp.manifest.file}"/>
+ </target>
+ <target depends="init,-do-jar-create-manifest,-do-jar-copy-manifest" if="do.archive+main.class.available" name="-do-jar-set-mainclass">
+ <manifest encoding="UTF-8" file="${tmp.manifest.file}" mode="update">
+ <attribute name="Main-Class" value="${main.class}"/>
+ </manifest>
+ </target>
+ <target depends="init,-do-jar-create-manifest,-do-jar-copy-manifest" if="do.archive+profile.available" name="-do-jar-set-profile">
+ <manifest encoding="UTF-8" file="${tmp.manifest.file}" mode="update">
+ <attribute name="Profile" value="${javac.profile}"/>
+ </manifest>
+ </target>
+ <target depends="init,-do-jar-create-manifest,-do-jar-copy-manifest" if="do.archive+splashscreen.available" name="-do-jar-set-splashscreen">
+ <basename file="${application.splash}" property="splashscreen.basename"/>
+ <mkdir dir="${build.classes.dir}/META-INF"/>
+ <copy failonerror="false" file="${application.splash}" todir="${build.classes.dir}/META-INF"/>
+ <manifest encoding="UTF-8" file="${tmp.manifest.file}" mode="update">
+ <attribute name="SplashScreen-Image" value="META-INF/${splashscreen.basename}"/>
+ </manifest>
+ </target>
+ <target depends="init,-init-macrodef-copylibs,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen" if="do.mkdist" name="-do-jar-copylibs">
+ <j2seproject3:copylibs manifest="${tmp.manifest.file}"/>
+ <echo level="info">To run this application from the command line without Ant, try:</echo>
+ <property location="${dist.jar}" name="dist.jar.resolved"/>
+ <echo level="info">java -jar "${dist.jar.resolved}"</echo>
+ </target>
+ <target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen" if="do.archive" name="-do-jar-jar" unless="do.mkdist">
+ <j2seproject1:jar manifest="${tmp.manifest.file}"/>
+ <property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
+ <property location="${dist.jar}" name="dist.jar.resolved"/>
+ <pathconvert property="run.classpath.with.dist.jar">
+ <path path="${run.classpath}"/>
+ <map from="${build.classes.dir.resolved}" to="${dist.jar.resolved}"/>
+ </pathconvert>
+ <condition else="" property="jar.usage.message" value="To run this application from the command line without Ant, try:${line.separator}${platform.java} -cp ${run.classpath.with.dist.jar} ${main.class}">
+ <isset property="main.class.available"/>
+ </condition>
+ <condition else="debug" property="jar.usage.level" value="info">
+ <isset property="main.class.available"/>
+ </condition>
+ <echo level="${jar.usage.level}" message="${jar.usage.message}"/>
+ </target>
+ <target depends="-do-jar-copylibs" if="do.archive" name="-do-jar-delete-manifest">
+ <delete>
+ <fileset file="${tmp.manifest.file}"/>
+ </delete>
+ </target>
+ <target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen,-do-jar-jar,-do-jar-delete-manifest" name="-do-jar-without-libraries"/>
+ <target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-create-manifest,-do-jar-copy-manifest,-do-jar-set-mainclass,-do-jar-set-profile,-do-jar-set-splashscreen,-do-jar-copylibs,-do-jar-delete-manifest" name="-do-jar-with-libraries"/>
+ <target name="-post-jar">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,compile,-pre-jar,-do-jar-without-libraries,-do-jar-with-libraries,-post-jar" name="-do-jar"/>
+ <target depends="init,compile,-pre-jar,-do-jar,-post-jar" description="Build JAR." name="jar"/>
+ <!--
+ =================
+ EXECUTION SECTION
+ =================
+ -->
+ <target depends="init,compile" description="Run a main class." name="run">
+ <j2seproject1:java>
+ <customize>
+ <arg line="${application.args}"/>
+ </customize>
+ </j2seproject1:java>
+ </target>
+ <target name="-do-not-recompile">
+ <property name="javac.includes.binary" value=""/>
+ </target>
+ <target depends="init,compile-single" name="run-single">
+ <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
+ <j2seproject1:java classname="${run.class}"/>
+ </target>
+ <target depends="init,compile-test-single" name="run-test-with-main">
+ <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
+ <j2seproject1:java classname="${run.class}" classpath="${run.test.classpath}"/>
+ </target>
+ <!--
+ =================
+ DEBUGGING SECTION
+ =================
+ -->
+ <target depends="init" if="netbeans.home" name="-debug-start-debugger">
+ <j2seproject1:nbjpdastart name="${debug.class}"/>
+ </target>
+ <target depends="init" if="netbeans.home" name="-debug-start-debugger-main-test">
+ <j2seproject1:nbjpdastart classpath="${debug.test.classpath}" name="${debug.class}"/>
+ </target>
+ <target depends="init,compile" name="-debug-start-debuggee">
+ <j2seproject3:debug>
+ <customize>
+ <arg line="${application.args}"/>
+ </customize>
+ </j2seproject3:debug>
+ </target>
+ <target depends="init,compile,-debug-start-debugger,-debug-start-debuggee" description="Debug project in IDE." if="netbeans.home" name="debug"/>
+ <target depends="init" if="netbeans.home" name="-debug-start-debugger-stepinto">
+ <j2seproject1:nbjpdastart stopclassname="${main.class}"/>
+ </target>
+ <target depends="init,compile,-debug-start-debugger-stepinto,-debug-start-debuggee" if="netbeans.home" name="debug-stepinto"/>
+ <target depends="init,compile-single" if="netbeans.home" name="-debug-start-debuggee-single">
+ <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail>
+ <j2seproject3:debug classname="${debug.class}"/>
+ </target>
+ <target depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-single" if="netbeans.home" name="debug-single"/>
+ <target depends="init,compile-test-single" if="netbeans.home" name="-debug-start-debuggee-main-test">
+ <fail unless="debug.class">Must select one file in the IDE or set debug.class</fail>
+ <j2seproject3:debug classname="${debug.class}" classpath="${debug.test.classpath}"/>
+ </target>
+ <target depends="init,compile-test-single,-debug-start-debugger-main-test,-debug-start-debuggee-main-test" if="netbeans.home" name="debug-test-with-main"/>
+ <target depends="init" name="-pre-debug-fix">
+ <fail unless="fix.includes">Must set fix.includes</fail>
+ <property name="javac.includes" value="${fix.includes}.java"/>
+ </target>
+ <target depends="init,-pre-debug-fix,compile-single" if="netbeans.home" name="-do-debug-fix">
+ <j2seproject1:nbjpdareload/>
+ </target>
+ <target depends="init,-pre-debug-fix,-do-debug-fix" if="netbeans.home" name="debug-fix"/>
+ <!--
+ =================
+ PROFILING SECTION
+ =================
+ -->
+ <!--
+ pre NB7.2 profiler integration
+ -->
+ <target depends="profile-init,compile" description="Profile a project in the IDE." if="profiler.info.jvmargs.agent" name="-profile-pre72">
+ <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
+ <nbprofiledirect>
+ <classpath>
+ <path path="${run.classpath}"/>
+ </classpath>
+ </nbprofiledirect>
+ <profile/>
+ </target>
+ <target depends="profile-init,compile-single" description="Profile a selected class in the IDE." if="profiler.info.jvmargs.agent" name="-profile-single-pre72">
+ <fail unless="profile.class">Must select one file in the IDE or set profile.class</fail>
+ <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
+ <nbprofiledirect>
+ <classpath>
+ <path path="${run.classpath}"/>
+ </classpath>
+ </nbprofiledirect>
+ <profile classname="${profile.class}"/>
+ </target>
+ <target depends="profile-init,compile-single" if="profiler.info.jvmargs.agent" name="-profile-applet-pre72">
+ <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
+ <nbprofiledirect>
+ <classpath>
+ <path path="${run.classpath}"/>
+ </classpath>
+ </nbprofiledirect>
+ <profile classname="sun.applet.AppletViewer">
+ <customize>
+ <arg value="${applet.url}"/>
+ </customize>
+ </profile>
+ </target>
+ <target depends="profile-init,compile-test-single" if="profiler.info.jvmargs.agent" name="-profile-test-single-pre72">
+ <fail unless="netbeans.home">This target only works when run from inside the NetBeans IDE.</fail>
+ <nbprofiledirect>
+ <classpath>
+ <path path="${run.test.classpath}"/>
+ </classpath>
+ </nbprofiledirect>
+ <junit dir="${profiler.info.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" jvm="${profiler.info.jvm}" showoutput="true">
+ <env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
+ <jvmarg value="${profiler.info.jvmargs.agent}"/>
+ <jvmarg line="${profiler.info.jvmargs}"/>
+ <test name="${profile.class}"/>
+ <classpath>
+ <path path="${run.test.classpath}"/>
+ </classpath>
+ <syspropertyset>
+ <propertyref prefix="test-sys-prop."/>
+ <mapper from="test-sys-prop.*" to="*" type="glob"/>
+ </syspropertyset>
+ <formatter type="brief" usefile="false"/>
+ <formatter type="xml"/>
+ </junit>
+ </target>
+ <!--
+ end of pre NB72 profiling section
+ -->
+ <target if="netbeans.home" name="-profile-check">
+ <condition property="profiler.configured">
+ <or>
+ <contains casesensitive="true" string="${run.jvmargs.ide}" substring="-agentpath:"/>
+ <contains casesensitive="true" string="${run.jvmargs.ide}" substring="-javaagent:"/>
+ </or>
+ </condition>
+ </target>
+ <target depends="-profile-check,-profile-pre72" description="Profile a project in the IDE." if="profiler.configured" name="profile" unless="profiler.info.jvmargs.agent">
+ <startprofiler/>
+ <antcall target="run"/>
+ </target>
+ <target depends="-profile-check,-profile-single-pre72" description="Profile a selected class in the IDE." if="profiler.configured" name="profile-single" unless="profiler.info.jvmargs.agent">
+ <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
+ <startprofiler/>
+ <antcall target="run-single"/>
+ </target>
+ <target depends="-profile-test-single-pre72" description="Profile a selected test in the IDE." name="profile-test-single"/>
+ <target depends="-profile-check" description="Profile a selected test in the IDE." if="profiler.configured" name="profile-test" unless="profiler.info.jvmargs">
+ <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail>
+ <startprofiler/>
+ <antcall target="test-single"/>
+ </target>
+ <target depends="-profile-check" description="Profile a selected class in the IDE." if="profiler.configured" name="profile-test-with-main">
+ <fail unless="run.class">Must select one file in the IDE or set run.class</fail>
+ <startprofiler/>
+ <antcall target="run-test-with-main"/>
+ </target>
+ <target depends="-profile-check,-profile-applet-pre72" if="profiler.configured" name="profile-applet" unless="profiler.info.jvmargs.agent">
+ <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
+ <startprofiler/>
+ <antcall target="run-applet"/>
+ </target>
+ <!--
+ ===============
+ JAVADOC SECTION
+ ===============
+ -->
+ <target depends="init" if="have.sources" name="-javadoc-build">
+ <mkdir dir="${dist.javadoc.dir}"/>
+ <condition else="" property="javadoc.endorsed.classpath.cmd.line.arg" value="-J${endorsed.classpath.cmd.line.arg}">
+ <and>
+ <isset property="endorsed.classpath.cmd.line.arg"/>
+ <not>
+ <equals arg1="${endorsed.classpath.cmd.line.arg}" arg2=""/>
+ </not>
+ </and>
+ </condition>
+ <condition else="" property="bug5101868workaround" value="*.java">
+ <matches pattern="1\.[56](\..*)?" string="${java.version}"/>
+ </condition>
+ <javadoc additionalparam="-J-Dfile.encoding=${file.encoding} ${javadoc.additionalparam}" author="${javadoc.author}" charset="UTF-8" destdir="${dist.javadoc.dir}" docencoding="UTF-8" encoding="${javadoc.encoding.used}" failonerror="true" noindex="${javadoc.noindex}" nonavbar="${javadoc.nonavbar}" notree="${javadoc.notree}" private="${javadoc.private}" source="${javac.source}" splitindex="${javadoc.splitindex}" use="${javadoc.use}" useexternalfile="true" version="${javadoc.version}" windowtitle="${javadoc.windowtitle}">
+ <classpath>
+ <path path="${javac.classpath}"/>
+ </classpath>
+ <fileset dir="${src.src.dir}" excludes="${bug5101868workaround},${excludes}" includes="${includes}">
+ <filename name="**/*.java"/>
+ </fileset>
+ <fileset dir="${build.generated.sources.dir}" erroronmissingdir="false">
+ <include name="**/*.java"/>
+ <exclude name="*.java"/>
+ </fileset>
+ <arg line="${javadoc.endorsed.classpath.cmd.line.arg}"/>
+ </javadoc>
+ <copy todir="${dist.javadoc.dir}">
+ <fileset dir="${src.src.dir}" excludes="${excludes}" includes="${includes}">
+ <filename name="**/doc-files/**"/>
+ </fileset>
+ <fileset dir="${build.generated.sources.dir}" erroronmissingdir="false">
+ <include name="**/doc-files/**"/>
+ </fileset>
+ </copy>
+ </target>
+ <target depends="init,-javadoc-build" if="netbeans.home" name="-javadoc-browse" unless="no.javadoc.preview">
+ <nbbrowse file="${dist.javadoc.dir}/index.html"/>
+ </target>
+ <target depends="init,-javadoc-build,-javadoc-browse" description="Build Javadoc." name="javadoc"/>
+ <!--
+ =========================
+ TEST COMPILATION SECTION
+ =========================
+ -->
+ <target depends="init,compile" if="have.tests" name="-pre-pre-compile-test">
+ <mkdir dir="${build.test.classes.dir}"/>
+ </target>
+ <target name="-pre-compile-test">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target if="do.depend.true" name="-compile-test-depend">
+ <j2seproject3:depend classpath="${javac.test.classpath}" destdir="${build.test.classes.dir}" srcdir=""/>
+ </target>
+ <target depends="init,deps-jar,compile,-pre-pre-compile-test,-pre-compile-test,-compile-test-depend" if="have.tests" name="-do-compile-test">
+ <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" processorpath="${javac.test.processorpath}" srcdir=""/>
+ <copy todir="${build.test.classes.dir}"/>
+ </target>
+ <target name="-post-compile-test">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test,-do-compile-test,-post-compile-test" name="compile-test"/>
+ <target name="-pre-compile-test-single">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,deps-jar,compile,-pre-pre-compile-test,-pre-compile-test-single" if="have.tests" name="-do-compile-test-single">
+ <fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
+ <j2seproject3:force-recompile destdir="${build.test.classes.dir}"/>
+ <j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" excludes="" includes="${javac.includes}" processorpath="${javac.test.processorpath}" sourcepath="" srcdir=""/>
+ <copy todir="${build.test.classes.dir}"/>
+ </target>
+ <target name="-post-compile-test-single">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,compile,-pre-pre-compile-test,-pre-compile-test-single,-do-compile-test-single,-post-compile-test-single" name="compile-test-single"/>
+ <!--
+ =======================
+ TEST EXECUTION SECTION
+ =======================
+ -->
+ <target depends="init" if="have.tests" name="-pre-test-run">
+ <mkdir dir="${build.test.results.dir}"/>
+ </target>
+ <target depends="init,compile-test,-pre-test-run" if="have.tests" name="-do-test-run">
+ <j2seproject3:test includes="${includes}" testincludes="**/*Test.java"/>
+ </target>
+ <target depends="init,compile-test,-pre-test-run,-do-test-run" if="have.tests" name="-post-test-run">
+ <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
+ </target>
+ <target depends="init" if="have.tests" name="test-report"/>
+ <target depends="init" if="netbeans.home+have.tests" name="-test-browse"/>
+ <target depends="init,compile-test,-pre-test-run,-do-test-run,test-report,-post-test-run,-test-browse" description="Run unit tests." name="test"/>
+ <target depends="init" if="have.tests" name="-pre-test-run-single">
+ <mkdir dir="${build.test.results.dir}"/>
+ </target>
+ <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-do-test-run-single">
+ <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail>
+ <j2seproject3:test excludes="" includes="${test.includes}" testincludes="${test.includes}"/>
+ </target>
+ <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single" if="have.tests" name="-post-test-run-single">
+ <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
+ </target>
+ <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single,-post-test-run-single" description="Run single unit test." name="test-single"/>
+ <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-do-test-run-single-method">
+ <fail unless="test.class">Must select some files in the IDE or set test.class</fail>
+ <fail unless="test.method">Must select some method in the IDE or set test.method</fail>
+ <j2seproject3:test excludes="" includes="${javac.includes}" testincludes="${test.class}" testmethods="${test.method}"/>
+ </target>
+ <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single-method" if="have.tests" name="-post-test-run-single-method">
+ <fail if="tests.failed" unless="ignore.failing.tests">Some tests failed; see details above.</fail>
+ </target>
+ <target depends="init,compile-test-single,-pre-test-run-single,-do-test-run-single-method,-post-test-run-single-method" description="Run single unit test." name="test-single-method"/>
+ <!--
+ =======================
+ TEST DEBUGGING SECTION
+ =======================
+ -->
+ <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-debug-start-debuggee-test">
+ <fail unless="test.class">Must select one file in the IDE or set test.class</fail>
+ <j2seproject3:test-debug excludes="" includes="${javac.includes}" testClass="${test.class}" testincludes="${javac.includes}"/>
+ </target>
+ <target depends="init,compile-test-single,-pre-test-run-single" if="have.tests" name="-debug-start-debuggee-test-method">
+ <fail unless="test.class">Must select one file in the IDE or set test.class</fail>
+ <fail unless="test.method">Must select some method in the IDE or set test.method</fail>
+ <j2seproject3:test-debug excludes="" includes="${javac.includes}" testClass="${test.class}" testMethod="${test.method}" testincludes="${test.class}" testmethods="${test.method}"/>
+ </target>
+ <target depends="init,compile-test" if="netbeans.home+have.tests" name="-debug-start-debugger-test">
+ <j2seproject1:nbjpdastart classpath="${debug.test.classpath}" name="${test.class}"/>
+ </target>
+ <target depends="init,compile-test-single,-debug-start-debugger-test,-debug-start-debuggee-test" name="debug-test"/>
+ <target depends="init,compile-test-single,-debug-start-debugger-test,-debug-start-debuggee-test-method" name="debug-test-method"/>
+ <target depends="init,-pre-debug-fix,compile-test-single" if="netbeans.home" name="-do-debug-fix-test">
+ <j2seproject1:nbjpdareload dir="${build.test.classes.dir}"/>
+ </target>
+ <target depends="init,-pre-debug-fix,-do-debug-fix-test" if="netbeans.home" name="debug-fix-test"/>
+ <!--
+ =========================
+ APPLET EXECUTION SECTION
+ =========================
+ -->
+ <target depends="init,compile-single" name="run-applet">
+ <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
+ <j2seproject1:java classname="sun.applet.AppletViewer">
+ <customize>
+ <arg value="${applet.url}"/>
+ </customize>
+ </j2seproject1:java>
+ </target>
+ <!--
+ =========================
+ APPLET DEBUGGING SECTION
+ =========================
+ -->
+ <target depends="init,compile-single" if="netbeans.home" name="-debug-start-debuggee-applet">
+ <fail unless="applet.url">Must select one file in the IDE or set applet.url</fail>
+ <j2seproject3:debug classname="sun.applet.AppletViewer">
+ <customize>
+ <arg value="${applet.url}"/>
+ </customize>
+ </j2seproject3:debug>
+ </target>
+ <target depends="init,compile-single,-debug-start-debugger,-debug-start-debuggee-applet" if="netbeans.home" name="debug-applet"/>
+ <!--
+ ===============
+ CLEANUP SECTION
+ ===============
+ -->
+ <target name="-deps-clean-init" unless="built-clean.properties">
+ <property location="${build.dir}/built-clean.properties" name="built-clean.properties"/>
+ <delete file="${built-clean.properties}" quiet="true"/>
+ </target>
+ <target if="already.built.clean.${basedir}" name="-warn-already-built-clean">
+ <echo level="warn" message="Cycle detected: JNLPConverter was already built"/>
+ </target>
+ <target depends="init,-deps-clean-init" name="deps-clean" unless="no.deps">
+ <mkdir dir="${build.dir}"/>
+ <touch file="${built-clean.properties}" verbose="false"/>
+ <property file="${built-clean.properties}" prefix="already.built.clean."/>
+ <antcall target="-warn-already-built-clean"/>
+ <propertyfile file="${built-clean.properties}">
+ <entry key="${basedir}" value=""/>
+ </propertyfile>
+ </target>
+ <target depends="init" name="-do-clean">
+ <delete dir="${build.dir}"/>
+ <delete dir="${dist.dir}" followsymlinks="false" includeemptydirs="true"/>
+ </target>
+ <target name="-post-clean">
+ <!-- Empty placeholder for easier customization. -->
+ <!-- You can override this target in the ../build.xml file. -->
+ </target>
+ <target depends="init,deps-clean,-do-clean,-post-clean" description="Clean build products." name="clean"/>
+ <target name="-check-call-dep">
+ <property file="${call.built.properties}" prefix="already.built."/>
+ <condition property="should.call.dep">
+ <and>
+ <not>
+ <isset property="already.built.${call.subproject}"/>
+ </not>
+ <available file="${call.script}"/>
+ </and>
+ </condition>
+ </target>
+ <target depends="-check-call-dep" if="should.call.dep" name="-maybe-call-dep">
+ <ant antfile="${call.script}" inheritall="false" target="${call.target}">
+ <propertyset>
+ <propertyref prefix="transfer."/>
+ <mapper from="transfer.*" to="*" type="glob"/>
+ </propertyset>
+ </ant>
+ </target>
+</project>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/nbproject/jpackage/JNLPConverter/nbproject/genfiles.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,8 @@
+build.xml.data.CRC32=9017e41e
+build.xml.script.CRC32=5cf818d6
+build.xml.stylesheet.CRC32=8064a381@1.80.1.48
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=9017e41e
+nbproject/build-impl.xml.script.CRC32=d0290d6d
+nbproject/build-impl.xml.stylesheet.CRC32=830a3534@1.80.1.48
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/nbproject/jpackage/JNLPConverter/nbproject/project.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,96 @@
+annotation.processing.enabled=true
+annotation.processing.enabled.in.editor=false
+annotation.processing.processors.list=
+annotation.processing.run.all.processors=true
+annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
+application.title=JNLPConverter
+application.vendor=
+auxiliary.org-netbeans-spi-editor-hints-projects.perProjectHintSettingsFile=nbproject/cfg_hints.xml
+build.classes.dir=${build.dir}/classes
+build.classes.excludes=**/*.java,**/*.form
+# This directory is removed when the project is cleaned:
+build.dir=build
+build.generated.dir=${build.dir}/generated
+build.generated.sources.dir=${build.dir}/generated-sources
+# Only compile against the classpath explicitly listed here:
+build.sysclasspath=ignore
+build.test.classes.dir=${build.dir}/test/classes
+build.test.results.dir=${build.dir}/test/results
+# Uncomment to specify the preferred debugger connection transport:
+#debug.transport=dt_socket
+debug.classpath=\
+ ${run.classpath}
+debug.test.classpath=\
+ ${run.test.classpath}
+# Files in build.classes.dir which should be excluded from distribution jar
+dist.archive.excludes=
+# This directory is removed when the project is cleaned:
+dist.dir=dist
+dist.jar=${dist.dir}/JNLPConverter.jar
+dist.javadoc.dir=${dist.dir}/javadoc
+endorsed.classpath=
+excludes=
+file.reference.jnlpconverter-src=../../../jpackage/jnlpconverter/src
+includes=**
+jar.archive.disabled=${jnlp.enabled}
+jar.compress=false
+jar.index=${jnlp.enabled}
+javac.classpath=
+# Space-separated list of extra javac options
+javac.compilerargs=
+javac.deprecation=false
+javac.external.vm=true
+javac.processorpath=\
+ ${javac.classpath}
+javac.source=1.8
+javac.target=1.8
+javac.test.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+javac.test.processorpath=\
+ ${javac.test.classpath}
+javadoc.additionalparam=
+javadoc.author=false
+javadoc.encoding=${source.encoding}
+javadoc.noindex=false
+javadoc.nonavbar=false
+javadoc.notree=false
+javadoc.private=false
+javadoc.splitindex=true
+javadoc.use=true
+javadoc.version=false
+javadoc.windowtitle=
+jnlp.codebase.type=no.codebase
+jnlp.descriptor=application
+jnlp.enabled=false
+jnlp.mixed.code=default
+jnlp.offline-allowed=false
+jnlp.signed=false
+jnlp.signing=
+jnlp.signing.alias=
+jnlp.signing.keystore=
+main.class=jnlp.converter.Main
+# Optional override of default Application-Library-Allowable-Codebase attribute identifying the locations where your signed RIA is expected to be found.
+manifest.custom.application.library.allowable.codebase=
+# Optional override of default Caller-Allowable-Codebase attribute identifying the domains from which JavaScript code can make calls to your RIA without security prompts.
+manifest.custom.caller.allowable.codebase=
+# Optional override of default Codebase manifest attribute, use to prevent RIAs from being repurposed
+manifest.custom.codebase=
+# Optional override of default Permissions manifest attribute (supported values: sandbox, all-permissions)
+manifest.custom.permissions=
+manifest.file=manifest.mf
+meta.inf.dir=${src.dir}/META-INF
+mkdist.disabled=false
+platform.active=default_platform
+run.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+# Space-separated list of JVM arguments used when running the project.
+# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
+# To set system properties for unit tests define test-sys-prop.name=value:
+run.jvmargs=
+run.test.classpath=\
+ ${javac.test.classpath}:\
+ ${build.test.classes.dir}
+source.encoding=UTF-8
+src.src.dir=${file.reference.jnlpconverter-src}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/demo/share/nbproject/jpackage/JNLPConverter/nbproject/project.xml Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://www.netbeans.org/ns/project/1">
+ <type>org.netbeans.modules.java.j2seproject</type>
+ <configuration>
+ <data xmlns="http://www.netbeans.org/ns/j2se-project/3">
+ <name>JNLPConverter</name>
+ <source-roots>
+ <root id="src.src.dir" name="Source Packages"/>
+ </source-roots>
+ <test-roots/>
+ </data>
+ </configuration>
+</project>
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/packager/AppRuntimeImageBuilder.java Tue Oct 01 20:07:30 2019 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +0,0 @@
-/*
- * Copyright (c) 2016, 2017, 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.tools.jlink.internal.packager;
-
-
-import jdk.tools.jlink.builder.DefaultImageBuilder;
-import jdk.tools.jlink.internal.Jlink;
-import jdk.tools.jlink.internal.JlinkTask;
-import jdk.tools.jlink.plugin.Plugin;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.module.ModuleFinder;
-import java.nio.ByteOrder;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * AppRuntimeImageBuilder is a private API used only by the Java Packager to generate
- * a Java runtime image using jlink. AppRuntimeImageBuilder encapsulates the
- * arguments that jlink requires to generate this image. To create the image call the
- * build() method.
- */
-public final class AppRuntimeImageBuilder {
- private Path outputDir = null;
- private Map<String, String> launchers = Collections.emptyMap();
- private List<Path> modulePath = null;
- private Set<String> addModules = null;
- private Set<String> limitModules = null;
- private String excludeFileList = null;
- private Map<String, String> userArguments = null;
- private Boolean stripNativeCommands = null;
-
- public AppRuntimeImageBuilder() {}
-
- public void setOutputDir(Path value) {
- outputDir = value;
- }
-
- public void setLaunchers(Map<String, String> value) {
- launchers = value;
- }
-
- public void setModulePath(List<Path> value) {
- modulePath = value;
- }
-
- public void setAddModules(Set<String> value) {
- addModules = value;
- }
-
- public void setLimitModules(Set<String> value) {
- limitModules = value;
- }
-
- public void setExcludeFileList(String value) {
- excludeFileList = value;
- }
-
- public void setStripNativeCommands(boolean value) {
- stripNativeCommands = value;
- }
-
- public void setUserArguments(Map<String, String> value) {
- userArguments = value;
- }
-
- public void build() throws IOException {
- // jlink main arguments
- Jlink.JlinkConfiguration jlinkConfig =
- new Jlink.JlinkConfiguration(new File("").toPath(), // Unused
- addModules,
- ByteOrder.nativeOrder(),
- moduleFinder(modulePath,
- limitModules, addModules));
-
- // plugin configuration
- List<Plugin> plugins = new ArrayList<Plugin>();
-
- if (stripNativeCommands) {
- plugins.add(Jlink.newPlugin(
- "strip-native-commands",
- Collections.singletonMap("strip-native-commands", "on"),
- null));
- }
-
- if (excludeFileList != null && !excludeFileList.isEmpty()) {
- plugins.add(Jlink.newPlugin(
- "exclude-files",
- Collections.singletonMap("exclude-files", excludeFileList),
- null));
- }
-
- // add user supplied jlink arguments
- for (Map.Entry<String, String> entry : userArguments.entrySet()) {
- String key = entry.getKey();
- String value = entry.getValue();
- plugins.add(Jlink.newPlugin(key,
- Collections.singletonMap(key, value),
- null));
- }
-
- // build the image
- Jlink.PluginsConfiguration pluginConfig = new Jlink.PluginsConfiguration(
- plugins, new DefaultImageBuilder(outputDir, launchers), null);
- Jlink jlink = new Jlink();
- jlink.build(jlinkConfig, pluginConfig);
- }
-
- /*
- * Returns a ModuleFinder that limits observability to the given root
- * modules, their transitive dependences, plus a set of other modules.
- */
- public static ModuleFinder moduleFinder(List<Path> modulepaths,
- Set<String> roots,
- Set<String> otherModules) {
- return JlinkTask.newModuleFinder(modulepaths, roots, otherModules);
- }
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LibProvidersLookup.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,163 @@
+/*
+ * 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.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Builds list of packages providing dynamic libraries for the given set of files.
+ */
+final public class LibProvidersLookup {
+ static boolean supported() {
+ return (new ToolValidator(TOOL_LDD).validate() == null);
+ }
+
+ public LibProvidersLookup() {
+ }
+
+ LibProvidersLookup setPackageLookup(PackageLookup v) {
+ packageLookup = v;
+ return this;
+ }
+
+ List<String> execute(Path root) throws IOException {
+ // Get the list of files in the root for which to look up for needed shared libraries
+ List<Path> allPackageFiles = Files.walk(root).filter(
+ Files::isRegularFile).filter(LibProvidersLookup::canDependOnLibs).collect(
+ Collectors.toList());
+
+ Collection<Path> neededLibs = getNeededLibsForFiles(allPackageFiles);
+
+ // Get the list of unique package names.
+ List<String> neededPackages = neededLibs.stream().map(libPath -> {
+ try {
+ List<String> packageNames = packageLookup.apply(libPath).filter(
+ Objects::nonNull).filter(Predicate.not(String::isBlank)).distinct().collect(
+ Collectors.toList());
+ Log.verbose(String.format("%s is provided by %s", libPath, packageNames));
+ return packageNames;
+ } catch (IOException ex) {
+ // Ignore and keep going
+ Log.verbose(ex);
+ List<String> packageNames = Collections.emptyList();
+ return packageNames;
+ }
+ }).flatMap(List::stream).sorted().distinct().collect(Collectors.toList());
+
+ return neededPackages;
+ }
+
+ private static List<Path> getNeededLibsForFile(Path path) throws IOException {
+ List<Path> result = new ArrayList<>();
+ int ret = Executor.of(TOOL_LDD, path.toString()).setOutputConsumer(lines -> {
+ lines.map(line -> {
+ Matcher matcher = LIB_IN_LDD_OUTPUT_REGEX.matcher(line);
+ if (matcher.find()) {
+ return matcher.group(1);
+ }
+ return null;
+ }).filter(Objects::nonNull).map(Path::of).forEach(result::add);
+ }).execute();
+
+ if (ret != 0) {
+ // objdump failed. This is OK if the tool was applied to not a binary file
+ return Collections.emptyList();
+ }
+
+ return result;
+ }
+
+ private static Collection<Path> getNeededLibsForFiles(List<Path> paths) {
+ // Depending on tool used, the set can contain full paths (ldd) or
+ // only file names (objdump).
+ Set<Path> allLibs = paths.stream().map(path -> {
+ List<Path> libs;
+ try {
+ libs = getNeededLibsForFile(path);
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ libs = Collections.emptyList();
+ }
+ return libs;
+ }).flatMap(List::stream).collect(Collectors.toSet());
+
+ // `allLibs` contains names of all .so needed by files from `paths` list.
+ // If there are mutual dependencies between binaries from `paths` list,
+ // then names or full paths to these binaries are in `allLibs` set.
+ // Remove these items from `allLibs`.
+ Set<Path> excludedNames = paths.stream().map(Path::getFileName).collect(
+ Collectors.toSet());
+ Iterator<Path> it = allLibs.iterator();
+ while (it.hasNext()) {
+ Path libName = it.next().getFileName();
+ if (excludedNames.contains(libName)) {
+ it.remove();
+ }
+ }
+
+ return allLibs;
+ }
+
+ private static boolean canDependOnLibs(Path path) {
+ return path.toFile().canExecute() || path.toString().endsWith(".so");
+ }
+
+ @FunctionalInterface
+ public interface PackageLookup {
+ Stream<String> apply(Path path) throws IOException;
+ }
+
+ private PackageLookup packageLookup;
+
+ private static final String TOOL_LDD = "ldd";
+
+ //
+ // Typical ldd output:
+ //
+ // ldd: warning: you do not have execution permission for `/tmp/jdk.jpackage17911687595930080396/images/opt/simplepackagetest/lib/runtime/lib/libawt_headless.so'
+ // linux-vdso.so.1 => (0x00007ffce6bfd000)
+ // libawt.so => /tmp/jdk.jpackage17911687595930080396/images/opt/simplepackagetest/lib/runtime/lib/libawt.so (0x00007f4e00c75000)
+ // libjvm.so => not found
+ // libjava.so => /tmp/jdk.jpackage17911687595930080396/images/opt/simplepackagetest/lib/runtime/lib/libjava.so (0x00007f4e00c41000)
+ // libm.so.6 => /lib64/libm.so.6 (0x00007f4e00834000)
+ // libdl.so.2 => /lib64/libdl.so.2 (0x00007f4e00630000)
+ // libc.so.6 => /lib64/libc.so.6 (0x00007f4e00262000)
+ // libjvm.so => not found
+ // libjvm.so => not found
+ // libverify.so => /tmp/jdk.jpackage17911687595930080396/images/opt/simplepackagetest/lib/runtime/lib/libverify.so (0x00007f4e00c2e000)
+ // /lib64/ld-linux-x86-64.so.2 (0x00007f4e00b36000)
+ // libjvm.so => not found
+ //
+ private static final Pattern LIB_IN_LDD_OUTPUT_REGEX = Pattern.compile(
+ "^\\s*\\S+\\s*=>\\s*(\\S+)\\s+\\(0[xX]\\p{XDigit}+\\)");
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppBundler.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2012, 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.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+
+public class LinuxAppBundler extends AbstractImageBundler {
+
+ static final BundlerParamInfo<File> ICON_PNG =
+ new StandardBundlerParam<>(
+ "icon.png",
+ File.class,
+ params -> {
+ File f = ICON.fetchFrom(params);
+ if (f != null && !f.getName().toLowerCase().endsWith(".png")) {
+ Log.error(MessageFormat.format(
+ I18N.getString("message.icon-not-png"), f));
+ return null;
+ }
+ return f;
+ },
+ (s, p) -> new File(s));
+
+ static final BundlerParamInfo<String> LINUX_INSTALL_DIR =
+ new StandardBundlerParam<>(
+ "linux-install-dir",
+ String.class,
+ params -> {
+ String dir = INSTALL_DIR.fetchFrom(params);
+ if (dir != null) {
+ if (dir.endsWith("/")) {
+ dir = dir.substring(0, dir.length()-1);
+ }
+ return dir;
+ }
+ return "/opt";
+ },
+ (s, p) -> s
+ );
+
+ static final BundlerParamInfo<String> LINUX_PACKAGE_DEPENDENCIES =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.LINUX_PACKAGE_DEPENDENCIES.getId(),
+ String.class,
+ params -> {
+ return "";
+ },
+ (s, p) -> s
+ );
+
+ @Override
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+ try {
+ if (params == null) throw new ConfigException(
+ I18N.getString("error.parameters-null"),
+ I18N.getString("error.parameters-null.advice"));
+
+ return doValidate(params);
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof ConfigException) {
+ throw (ConfigException) re.getCause();
+ } else {
+ throw new ConfigException(re);
+ }
+ }
+ }
+
+ private boolean doValidate(Map<String, ? super Object> params)
+ throws ConfigException {
+
+ imageBundleValidation(params);
+
+ return true;
+ }
+
+ File doBundle(Map<String, ? super Object> params, File outputDirectory,
+ boolean dependentTask) throws PackagerException {
+ if (StandardBundlerParam.isRuntimeInstaller(params)) {
+ return PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
+ } else {
+ return doAppBundle(params, outputDirectory, dependentTask);
+ }
+ }
+
+ private File doAppBundle(Map<String, ? super Object> params,
+ File outputDirectory, boolean dependentTask)
+ throws PackagerException {
+ try {
+ File rootDirectory = createRoot(params, outputDirectory,
+ dependentTask, APP_NAME.fetchFrom(params));
+ AbstractAppImageBuilder appBuilder = new LinuxAppImageBuilder(
+ params, outputDirectory.toPath());
+ if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(params) == null ) {
+ JLinkBundlerHelper.execute(params, appBuilder);
+ } else {
+ StandardBundlerParam.copyPredefinedRuntimeImage(
+ params, appBuilder);
+ }
+ return rootDirectory;
+ } catch (PackagerException pe) {
+ throw pe;
+ } catch (Exception ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ @Override
+ public String getName() {
+ return I18N.getString("app.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "linux.app";
+ }
+
+ @Override
+ public String getBundleType() {
+ return "IMAGE";
+ }
+
+ @Override
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ return doBundle(params, outputParentDir, false);
+ }
+
+ @Override
+ public boolean supported(boolean runtimeInstaller) {
+ return true;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return false;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxAppImageBuilder.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2015, 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.File;
+import java.io.IOException;
+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.StandardBundlerParam.*;
+
+public class LinuxAppImageBuilder extends AbstractAppImageBuilder {
+
+ private static final String LIBRARY_NAME = "libapplauncher.so";
+ final static String DEFAULT_ICON = "java32.png";
+
+ private final ApplicationLayout appLayout;
+
+ public static final BundlerParamInfo<File> ICON_PNG =
+ new StandardBundlerParam<>(
+ "icon.png",
+ File.class,
+ params -> {
+ File f = ICON.fetchFrom(params);
+ if (f != null && !f.getName().toLowerCase().endsWith(".png")) {
+ Log.error(MessageFormat.format(I18N.getString(
+ "message.icon-not-png"), f));
+ return null;
+ }
+ return f;
+ },
+ (s, p) -> new File(s));
+
+ private static ApplicationLayout createAppLayout(Map<String, Object> params,
+ Path imageOutDir) {
+ return ApplicationLayout.linuxAppImage().resolveAt(
+ imageOutDir.resolve(APP_NAME.fetchFrom(params)));
+ }
+
+ public LinuxAppImageBuilder(Map<String, Object> params, Path imageOutDir)
+ throws IOException {
+ super(params, createAppLayout(params, imageOutDir).runtimeDirectory());
+
+ appLayout = createAppLayout(params, imageOutDir);
+ }
+
+ private void writeEntry(InputStream in, Path dstFile) throws IOException {
+ Files.createDirectories(dstFile.getParent());
+ Files.copy(in, dstFile);
+ }
+
+ public static String getLauncherName(Map<String, ? super Object> params) {
+ return APP_NAME.fetchFrom(params);
+ }
+
+ private Path getLauncherCfgPath(Map<String, ? super Object> params) {
+ return appLayout.appDirectory().resolve(
+ APP_NAME.fetchFrom(params) + ".cfg");
+ }
+
+ @Override
+ public Path getAppDir() {
+ return appLayout.appDirectory();
+ }
+
+ @Override
+ public Path getAppModsDir() {
+ return appLayout.appModsDirectory();
+ }
+
+ @Override
+ protected String getCfgAppDir() {
+ return Path.of("$APPDIR").resolve(
+ ApplicationLayout.linuxAppImage().appDirectory()).toString() + File.separator;
+ }
+
+ @Override
+ protected String getCfgRuntimeDir() {
+ return Path.of("$APPDIR").resolve(
+ ApplicationLayout.linuxAppImage().runtimeDirectory()).toString();
+ }
+
+ @Override
+ public void prepareApplicationFiles(Map<String, ? super Object> params)
+ throws IOException {
+ Map<String, ? super Object> originalParams = new HashMap<>(params);
+
+ appLayout.roots().stream().forEach(dir -> {
+ try {
+ IOUtils.writableOutputDir(dir);
+ } catch (PackagerException pe) {
+ throw new RuntimeException(pe);
+ }
+ });
+
+ // create the primary launcher
+ createLauncherForEntryPoint(params);
+
+ // Copy library to the launcher folder
+ try (InputStream is_lib = getResourceAsStream(LIBRARY_NAME)) {
+ writeEntry(is_lib, appLayout.dllDirectory().resolve(LIBRARY_NAME));
+ }
+
+ // create the additional launchers, if any
+ List<Map<String, ? super Object>> entryPoints
+ = StandardBundlerParam.ADD_LAUNCHERS.fetchFrom(params);
+ for (Map<String, ? super Object> entryPoint : entryPoints) {
+ createLauncherForEntryPoint(
+ AddLauncherArguments.merge(originalParams, entryPoint));
+ }
+
+ // Copy class path entries to Java folder
+ copyApplication(params);
+
+ // Copy icon to Resources folder
+ copyIcon(params);
+ }
+
+ @Override
+ public void prepareJreFiles(Map<String, ? super Object> params)
+ throws IOException {}
+
+ private void createLauncherForEntryPoint(
+ Map<String, ? super Object> params) throws IOException {
+ // Copy executable to launchers folder
+ Path executableFile = appLayout.launchersDirectory().resolve(getLauncherName(params));
+ try (InputStream is_launcher =
+ getResourceAsStream("jpackageapplauncher")) {
+ writeEntry(is_launcher, executableFile);
+ }
+
+ executableFile.toFile().setExecutable(true, false);
+ executableFile.toFile().setWritable(true, true);
+
+ writeCfgFile(params, getLauncherCfgPath(params).toFile());
+ }
+
+ 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();
+
+ InputStream in = locateResource(
+ iconTarget.getName(),
+ "icon",
+ DEFAULT_ICON,
+ icon,
+ VERBOSE.fetchFrom(params),
+ RESOURCE_DIR.fetchFrom(params));
+
+ Files.copy(in, iconTarget.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ }
+
+ private void copyApplication(Map<String, ? super Object> params)
+ throws IOException {
+ for (RelativeFileSet appResources :
+ APP_RESOURCES_LIST.fetchFrom(params)) {
+ if (appResources == null) {
+ throw new RuntimeException("Null app resources?");
+ }
+ File srcdir = appResources.getBaseDirectory();
+ for (String fname : appResources.getIncludedFiles()) {
+ copyEntry(appLayout.appDirectory(), srcdir, fname);
+ }
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxDebBundler.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,453 @@
+/*
+ * Copyright (c) 2012, 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.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
+
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+
+
+public class LinuxDebBundler extends LinuxPackageBundler {
+
+ // Debian rules for package naming are used here
+ // https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Source
+ //
+ // Package names must consist only of lower case letters (a-z),
+ // digits (0-9), plus (+) and minus (-) signs, and periods (.).
+ // They must be at least two characters long and
+ // must start with an alphanumeric character.
+ //
+ private static final Pattern DEB_PACKAGE_NAME_PATTERN =
+ Pattern.compile("^[a-z][a-z\\d\\+\\-\\.]+");
+
+ private static final BundlerParamInfo<String> PACKAGE_NAME =
+ new StandardBundlerParam<> (
+ Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId(),
+ String.class,
+ params -> {
+ String nm = APP_NAME.fetchFrom(params);
+
+ if (nm == null) return null;
+
+ // make sure to lower case and spaces/underscores become dashes
+ nm = nm.toLowerCase().replaceAll("[ _]", "-");
+ return nm;
+ },
+ (s, p) -> {
+ if (!DEB_PACKAGE_NAME_PATTERN.matcher(s).matches()) {
+ throw new IllegalArgumentException(new ConfigException(
+ MessageFormat.format(I18N.getString(
+ "error.invalid-value-for-package-name"), s),
+ I18N.getString(
+ "error.invalid-value-for-package-name.advice")));
+ }
+
+ return s;
+ });
+
+ private final static String TOOL_DPKG_DEB = "dpkg-deb";
+ private final static String TOOL_DPKG = "dpkg";
+ private final static String TOOL_FAKEROOT = "fakeroot";
+
+ private final static String DEB_ARCH;
+ static {
+ String debArch;
+ try {
+ debArch = Executor.of(TOOL_DPKG, "--print-architecture").saveOutput(
+ true).executeExpectSuccess().getOutput().get(0);
+ } catch (IOException ex) {
+ debArch = null;
+ }
+ DEB_ARCH = debArch;
+ }
+
+ private static final BundlerParamInfo<String> FULL_PACKAGE_NAME =
+ new StandardBundlerParam<>(
+ "linux.deb.fullPackageName", String.class, params -> {
+ return PACKAGE_NAME.fetchFrom(params)
+ + "_" + VERSION.fetchFrom(params)
+ + "-" + RELEASE.fetchFrom(params)
+ + "_" + DEB_ARCH;
+ }, (s, p) -> s);
+
+ private static final BundlerParamInfo<String> EMAIL =
+ new StandardBundlerParam<> (
+ Arguments.CLIOptions.LINUX_DEB_MAINTAINER.getId(),
+ String.class,
+ params -> "Unknown",
+ (s, p) -> s);
+
+ private static final BundlerParamInfo<String> MAINTAINER =
+ new StandardBundlerParam<> (
+ BundleParams.PARAM_MAINTAINER,
+ String.class,
+ params -> VENDOR.fetchFrom(params) + " <"
+ + EMAIL.fetchFrom(params) + ">",
+ (s, p) -> s);
+
+ private static final BundlerParamInfo<String> SECTION =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.LINUX_CATEGORY.getId(),
+ String.class,
+ params -> "misc",
+ (s, p) -> s);
+
+ private static final BundlerParamInfo<String> LICENSE_TEXT =
+ new StandardBundlerParam<> (
+ "linux.deb.licenseText",
+ String.class,
+ params -> {
+ try {
+ String licenseFile = LICENSE_FILE.fetchFrom(params);
+ if (licenseFile != null) {
+ return Files.lines(Path.of(licenseFile),
+ StandardCharsets.UTF_8).collect(
+ Collectors.joining("\n"));
+ }
+ } catch (IOException e) {
+ Log.verbose(e);
+ }
+ return "Unknown";
+ },
+ (s, p) -> s);
+
+ public LinuxDebBundler() {
+ super(PACKAGE_NAME);
+ }
+
+ @Override
+ public void doValidate(Map<String, ? super Object> params)
+ throws ConfigException {
+
+ // Show warning if license file is missing
+ if (LICENSE_FILE.fetchFrom(params) == null) {
+ Log.verbose(I18N.getString("message.debs-like-licenses"));
+ }
+ }
+
+ @Override
+ protected List<ToolValidator> getToolValidators(
+ Map<String, ? super Object> params) {
+ return Stream.of(TOOL_DPKG_DEB, TOOL_DPKG, TOOL_FAKEROOT).map(
+ ToolValidator::new).collect(Collectors.toList());
+ }
+
+ @Override
+ protected File buildPackageBundle(
+ Map<String, String> replacementData,
+ Map<String, ? super Object> params, File outputParentDir) throws
+ PackagerException, IOException {
+
+ prepareProjectConfig(replacementData, params);
+ adjustPermissionsRecursive(createMetaPackage(params).sourceRoot().toFile());
+ return buildDeb(params, outputParentDir);
+ }
+
+ private static final Pattern PACKAGE_NAME_REGEX = Pattern.compile("^(^\\S+):");
+
+ @Override
+ protected void initLibProvidersLookup(
+ Map<String, ? super Object> params,
+ LibProvidersLookup libProvidersLookup) {
+
+ //
+ // `dpkg -S` command does glob pattern lookup. If not the absolute path
+ // to the file is specified it might return mltiple package names.
+ // Even for full paths multiple package names can be returned as
+ // it is OK for multiple packages to provide the same file. `/opt`
+ // directory is such an example. So we have to deal with multiple
+ // packages per file situation.
+ //
+ // E.g.: `dpkg -S libc.so.6` command reports three packages:
+ // libc6-x32: /libx32/libc.so.6
+ // libc6:amd64: /lib/x86_64-linux-gnu/libc.so.6
+ // libc6-i386: /lib32/libc.so.6
+ // `:amd64` is architecture suffix and can (should) be dropped.
+ // Still need to decide what package to choose from three.
+ // libc6-x32 and libc6-i386 both depend on libc6:
+ // $ dpkg -s libc6-x32
+ // Package: libc6-x32
+ // Status: install ok installed
+ // Priority: optional
+ // Section: libs
+ // Installed-Size: 10840
+ // Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
+ // Architecture: amd64
+ // Source: glibc
+ // Version: 2.23-0ubuntu10
+ // Depends: libc6 (= 2.23-0ubuntu10)
+ //
+ // We can dive into tracking dependencies, but this would be overly
+ // complicated.
+ //
+ // For simplicity lets consider the following rules:
+ // 1. If there is one item in `dpkg -S` output, accept it.
+ // 2. If there are multiple items in `dpkg -S` output and there is at
+ // least one item with the default arch suffix (DEB_ARCH),
+ // accept only these items.
+ // 3. If there are multiple items in `dpkg -S` output and there are
+ // no with the default arch suffix (DEB_ARCH), accept all items.
+ // So lets use this heuristics: don't accept packages for whom
+ // `dpkg -p` command fails.
+ // 4. Arch suffix should be stripped from accepted package names.
+ //
+
+ libProvidersLookup.setPackageLookup(file -> {
+ Set<String> archPackages = new HashSet<>();
+ Set<String> otherPackages = new HashSet<>();
+
+ Executor.of(TOOL_DPKG, "-S", file.toString())
+ .saveOutput(true).executeExpectSuccess()
+ .getOutput().forEach(line -> {
+ Matcher matcher = PACKAGE_NAME_REGEX.matcher(line);
+ if (matcher.find()) {
+ String name = matcher.group(1);
+ if (name.endsWith(":" + DEB_ARCH)) {
+ // Strip arch suffix
+ name = name.substring(0,
+ name.length() - (DEB_ARCH.length() + 1));
+ archPackages.add(name);
+ } else {
+ otherPackages.add(name);
+ }
+ }
+ });
+
+ if (!archPackages.isEmpty()) {
+ return archPackages.stream();
+ }
+ return otherPackages.stream();
+ });
+ }
+
+ /*
+ * set permissions with a string like "rwxr-xr-x"
+ *
+ * This cannot be directly backport to 22u which is built with 1.6
+ */
+ private void setPermissions(File file, String permissions) {
+ Set<PosixFilePermission> filePermissions =
+ PosixFilePermissions.fromString(permissions);
+ try {
+ if (file.exists()) {
+ Files.setPosixFilePermissions(file.toPath(), filePermissions);
+ }
+ } catch (IOException ex) {
+ Log.error(ex.getMessage());
+ Log.verbose(ex);
+ }
+
+ }
+
+ public static boolean isDebian() {
+ // we are just going to run "dpkg -s coreutils" and assume Debian
+ // or deritive if no error is returned.
+ try {
+ Executor.of(TOOL_DPKG, "-s", "coreutils").executeExpectSuccess();
+ return true;
+ } catch (IOException e) {
+ // just fall thru
+ }
+ return false;
+ }
+
+ private void adjustPermissionsRecursive(File dir) throws IOException {
+ Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file,
+ BasicFileAttributes attrs)
+ throws IOException {
+ if (file.endsWith(".so") || !Files.isExecutable(file)) {
+ setPermissions(file.toFile(), "rw-r--r--");
+ } else if (Files.isExecutable(file)) {
+ setPermissions(file.toFile(), "rwxr-xr-x");
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException e)
+ throws IOException {
+ if (e == null) {
+ setPermissions(dir.toFile(), "rwxr-xr-x");
+ return FileVisitResult.CONTINUE;
+ } else {
+ // directory iteration failed
+ throw e;
+ }
+ }
+ });
+ }
+
+ private class DebianFile {
+
+ DebianFile(Path dstFilePath, String comment) {
+ this.dstFilePath = dstFilePath;
+ this.comment = comment;
+ }
+
+ DebianFile setExecutable() {
+ permissions = "rwxr-xr-x";
+ return this;
+ }
+
+ 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);
+ }
+ if (permissions != null) {
+ setPermissions(dstFilePath.toFile(), permissions);
+ }
+ }
+
+ private final Path dstFilePath;
+ private final String comment;
+ private String permissions;
+ }
+
+ private void prepareProjectConfig(Map<String, String> data,
+ Map<String, ? super Object> params) throws IOException {
+
+ Path configDir = createMetaPackage(params).sourceRoot().resolve("DEBIAN");
+ List<DebianFile> debianFiles = new ArrayList<>();
+ debianFiles.add(new DebianFile(
+ configDir.resolve("control"),
+ "resource.deb-control-file"));
+ debianFiles.add(new DebianFile(
+ configDir.resolve("preinst"),
+ "resource.deb-preinstall-script").setExecutable());
+ debianFiles.add(new DebianFile(
+ configDir.resolve("prerm"),
+ "resource.deb-prerm-script").setExecutable());
+ debianFiles.add(new DebianFile(
+ configDir.resolve("postinst"),
+ "resource.deb-postinstall-script").setExecutable());
+ debianFiles.add(new DebianFile(
+ configDir.resolve("postrm"),
+ "resource.deb-postrm-script").setExecutable());
+ debianFiles.add(new DebianFile(
+ getConfig_CopyrightFile(params).toPath(),
+ "resource.copyright-file"));
+
+ for (DebianFile debianFile : debianFiles) {
+ debianFile.create(data, params);
+ }
+ }
+
+ @Override
+ protected Map<String, String> createReplacementData(
+ Map<String, ? super Object> params) throws IOException {
+ Map<String, String> data = new HashMap<>();
+
+ data.put("APPLICATION_MAINTAINER", MAINTAINER.fetchFrom(params));
+ data.put("APPLICATION_SECTION", SECTION.fetchFrom(params));
+ data.put("APPLICATION_COPYRIGHT", COPYRIGHT.fetchFrom(params));
+ data.put("APPLICATION_LICENSE_TEXT", LICENSE_TEXT.fetchFrom(params));
+ data.put("APPLICATION_ARCH", DEB_ARCH);
+ data.put("APPLICATION_INSTALLED_SIZE", Long.toString(
+ createMetaPackage(params).sourceApplicationLayout().sizeInBytes() >> 10));
+
+ return data;
+ }
+
+ private File getConfig_CopyrightFile(Map<String, ? super Object> params) {
+ PlatformPackage thePackage = createMetaPackage(params);
+ return thePackage.sourceRoot().resolve(Path.of(".",
+ LINUX_INSTALL_DIR.fetchFrom(params), PACKAGE_NAME.fetchFrom(
+ params), "share/doc/copyright")).toFile();
+ }
+
+ private File buildDeb(Map<String, ? super Object> params,
+ File outdir) throws IOException {
+ File outFile = new File(outdir,
+ FULL_PACKAGE_NAME.fetchFrom(params)+".deb");
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.outputting-to-location"), outFile.getAbsolutePath()));
+
+ PlatformPackage thePackage = createMetaPackage(params);
+
+ List<String> cmdline = new ArrayList<>();
+ cmdline.addAll(List.of(TOOL_FAKEROOT, TOOL_DPKG_DEB));
+ if (Log.isVerbose()) {
+ cmdline.add("--verbose");
+ }
+ cmdline.addAll(List.of("-b", thePackage.sourceRoot().toString(),
+ outFile.getAbsolutePath()));
+
+ // run dpkg
+ Executor.of(cmdline.toArray(String[]::new)).executeExpectSuccess();
+
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.output-to-location"), outFile.getAbsolutePath()));
+
+ return outFile;
+ }
+
+ @Override
+ public String getName() {
+ return I18N.getString("deb.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "deb";
+ }
+
+ @Override
+ public boolean supported(boolean runtimeInstaller) {
+ return Platform.isLinux() && (new ToolValidator(TOOL_DPKG_DEB).validate() == null);
+ }
+
+ @Override
+ public boolean isDefault() {
+ return isDebian();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxPackageBundler.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,809 @@
+/*
+ * 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.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.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 {
+
+ // run basic validation to ensure requirements are met
+ // we are not interested in return code, only possible exception
+ APP_BUNDLER.fetchFrom(params).validate(params);
+
+ validateInstallDir(LINUX_INSTALL_DIR.fetchFrom(params));
+
+ validateFileAssociations(FILE_ASSOCIATIONS.fetchFrom(params));
+
+ // If package name has some restrictions, the string converter will
+ // throw an exception if invalid
+ packageName.getStringConverter().apply(packageName.fetchFrom(params),
+ params);
+
+ for (var validator: getToolValidators(params)) {
+ ConfigException ex = validator.validate();
+ if (ex != null) {
+ throw ex;
+ }
+ }
+
+ withFindNeededPackages = LibProvidersLookup.supported();
+ if (!withFindNeededPackages) {
+ final String advice;
+ if ("deb".equals(getID())) {
+ advice = "message.deb-ldd-not-available.advice";
+ } else {
+ advice = "message.rpm-ldd-not-available.advice";
+ }
+ // Let user know package dependencies will not be generated.
+ Log.error(String.format("%s\n%s", I18N.getString(
+ "message.ldd-not-available"), I18N.getString(advice)));
+ }
+
+ // Packaging specific validation
+ doValidate(params);
+
+ return true;
+ }
+
+ @Override
+ final public String getBundleType() {
+ return "INSTALLER";
+ }
+
+ @Override
+ final public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ IOUtils.writableOutputDir(outputParentDir.toPath());
+
+ PlatformPackage thePackage = createMetaPackage(params);
+
+ try {
+ File appImage = StandardBundlerParam.getPredefinedAppImage(params);
+
+ // we either have an application image or need to build one
+ if (appImage != null) {
+ appImageLayout(params).resolveAt(appImage.toPath()).copy(
+ thePackage.sourceApplicationLayout());
+ } else {
+ appImage = APP_BUNDLER.fetchFrom(params).doBundle(params,
+ thePackage.sourceRoot().toFile(), true);
+ ApplicationLayout srcAppLayout = appImageLayout(params).resolveAt(
+ appImage.toPath());
+ if (appImage.equals(PREDEFINED_RUNTIME_IMAGE.fetchFrom(params))) {
+ // Application image points to run-time image.
+ // Copy it.
+ srcAppLayout.copy(thePackage.sourceApplicationLayout());
+ } else {
+ // Application image is a newly created directory tree.
+ // Move it.
+ srcAppLayout.move(thePackage.sourceApplicationLayout());
+ if (appImage.exists()) {
+ // Empty app image directory might remain after all application
+ // directories have been moved.
+ appImage.delete();
+ }
+ }
+ }
+
+ if (!StandardBundlerParam.isRuntimeInstaller(params)) {
+ desktopIntegration = new DesktopIntegration(thePackage, params);
+ } else {
+ desktopIntegration = null;
+ }
+
+ Map<String, String> data = createDefaultReplacementData(params);
+ if (desktopIntegration != null) {
+ data.putAll(desktopIntegration.create());
+ } else {
+ Stream.of(DESKTOP_COMMANDS_INSTALL, DESKTOP_COMMANDS_UNINSTALL,
+ UTILITY_SCRIPTS).forEach(v -> data.put(v, ""));
+ }
+
+ data.putAll(createReplacementData(params));
+
+ return buildPackageBundle(Collections.unmodifiableMap(data), params,
+ outputParentDir);
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ private List<String> getListOfNeededPackages(
+ Map<String, ? super Object> params) throws IOException {
+
+ PlatformPackage thePackage = createMetaPackage(params);
+
+ final List<String> xdgUtilsPackage;
+ if (desktopIntegration != null) {
+ xdgUtilsPackage = desktopIntegration.requiredPackages();
+ } else {
+ xdgUtilsPackage = Collections.emptyList();
+ }
+
+ final List<String> neededLibPackages;
+ if (withFindNeededPackages) {
+ LibProvidersLookup lookup = new LibProvidersLookup();
+ initLibProvidersLookup(params, lookup);
+
+ neededLibPackages = lookup.execute(thePackage.sourceRoot());
+ } else {
+ neededLibPackages = Collections.emptyList();
+ }
+
+ // Merge all package lists together.
+ // Filter out empty names, sort and remove duplicates.
+ List<String> result = Stream.of(xdgUtilsPackage, neededLibPackages).flatMap(
+ List::stream).filter(Predicate.not(String::isEmpty)).sorted().distinct().collect(
+ Collectors.toList());
+
+ Log.verbose(String.format("Required packages: %s", result));
+
+ return result;
+ }
+
+ private Map<String, String> createDefaultReplacementData(
+ Map<String, ? super Object> params) throws IOException {
+ Map<String, String> data = new HashMap<>();
+
+ data.put("APPLICATION_PACKAGE", createMetaPackage(params).name());
+ data.put("APPLICATION_VENDOR", VENDOR.fetchFrom(params));
+ data.put("APPLICATION_VERSION", VERSION.fetchFrom(params));
+ data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params));
+ data.put("APPLICATION_RELEASE", RELEASE.fetchFrom(params));
+
+ String defaultDeps = String.join(", ", getListOfNeededPackages(params));
+ String customDeps = LINUX_PACKAGE_DEPENDENCIES.fetchFrom(params).strip();
+ if (!customDeps.isEmpty() && !defaultDeps.isEmpty()) {
+ customDeps = ", " + customDeps;
+ }
+ data.put("PACKAGE_DEFAULT_DEPENDENCIES", defaultDeps);
+ data.put("PACKAGE_CUSTOM_DEPENDENCIES", customDeps);
+
+ return data;
+ }
+
+ abstract protected void initLibProvidersLookup(
+ Map<String, ? super Object> params,
+ LibProvidersLookup libProvidersLookup);
+
+ abstract protected List<ToolValidator> getToolValidators(
+ Map<String, ? super Object> params);
+
+ abstract protected void doValidate(Map<String, ? super Object> params)
+ throws ConfigException;
+
+ abstract protected Map<String, String> createReplacementData(
+ Map<String, ? super Object> params) throws IOException;
+
+ abstract protected File buildPackageBundle(
+ Map<String, String> replacementData,
+ Map<String, ? super Object> params, File outputParentDir) throws
+ PackagerException, IOException;
+
+ final protected PlatformPackage createMetaPackage(
+ Map<String, ? super Object> params) {
+ return new PlatformPackage() {
+ @Override
+ public String name() {
+ return packageName.fetchFrom(params);
+ }
+
+ @Override
+ public Path sourceRoot() {
+ return IMAGES_ROOT.fetchFrom(params).toPath().toAbsolutePath();
+ }
+
+ @Override
+ public ApplicationLayout sourceApplicationLayout() {
+ return appImageLayout(params).resolveAt(
+ applicationInstallDir(sourceRoot()));
+ }
+
+ @Override
+ public ApplicationLayout installedApplicationLayout() {
+ return appImageLayout(params).resolveAt(
+ applicationInstallDir(Path.of("/")));
+ }
+
+ private Path applicationInstallDir(Path root) {
+ Path installDir = Path.of(LINUX_INSTALL_DIR.fetchFrom(params),
+ name());
+ if (installDir.isAbsolute()) {
+ installDir = Path.of("." + installDir.toString()).normalize();
+ }
+ return root.resolve(installDir);
+ }
+ };
+ }
+
+ private ApplicationLayout appImageLayout(
+ Map<String, ? super Object> params) {
+ if (StandardBundlerParam.isRuntimeInstaller(params)) {
+ return ApplicationLayout.javaRuntime();
+ }
+ return ApplicationLayout.linuxAppImage();
+ }
+
+ private static void validateInstallDir(String installDir) throws
+ ConfigException {
+ if (installDir.startsWith("/usr/") || installDir.equals("/usr")) {
+ throw new ConfigException(MessageFormat.format(I18N.getString(
+ "error.unsupported-install-dir"), installDir), null);
+ }
+
+ if (installDir.isEmpty()) {
+ throw new ConfigException(MessageFormat.format(I18N.getString(
+ "error.invalid-install-dir"), "/"), null);
+ }
+
+ boolean valid = false;
+ try {
+ final Path installDirPath = Path.of(installDir);
+ valid = installDirPath.isAbsolute();
+ if (valid && !installDirPath.normalize().toString().equals(
+ installDirPath.toString())) {
+ // Don't allow '/opt/foo/..' or /opt/.
+ valid = false;
+ }
+ } catch (InvalidPathException ex) {
+ }
+
+ if (!valid) {
+ throw new ConfigException(MessageFormat.format(I18N.getString(
+ "error.invalid-install-dir"), installDir), null);
+ }
+ }
+
+ private static void validateFileAssociations(
+ List<Map<String, ? super Object>> associations) throws
+ ConfigException {
+ // only one mime type per association, at least one file extention
+ int assocIdx = 0;
+ for (var assoc : associations) {
+ ++assocIdx;
+ List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc);
+ if (mimes == null || mimes.isEmpty()) {
+ String msgKey = "error.no-content-types-for-file-association";
+ throw new ConfigException(
+ MessageFormat.format(I18N.getString(msgKey), assocIdx),
+ I18N.getString(msgKey + ".advise"));
+
+ }
+
+ if (mimes.size() > 1) {
+ String msgKey = "error.too-many-content-types-for-file-association";
+ throw new ConfigException(
+ MessageFormat.format(I18N.getString(msgKey), assocIdx),
+ I18N.getString(msgKey + ".advise"));
+ }
+ }
+ }
+
+ /**
+ * 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));
+ }
+
+ 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();
+
+ 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
+ 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;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/LinuxRpmBundler.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) 2012, 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.file.Files;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+import static jdk.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
+
+/**
+ * There are two command line options to configure license information for RPM
+ * packaging: --linux-rpm-license-type and --license-file. Value of
+ * --linux-rpm-license-type command line option configures "License:" section
+ * of RPM spec. Value of --license-file command line option specifies a license
+ * file to be added to the package. License file is a sort of documentation file
+ * but it will be installed even if user selects an option to install the
+ * package without documentation. --linux-rpm-license-type is the primary option
+ * to set license information. --license-file makes little sense in case of RPM
+ * packaging.
+ */
+public class LinuxRpmBundler extends LinuxPackageBundler {
+
+ // Fedora rules for package naming are used here
+ // https://fedoraproject.org/wiki/Packaging:NamingGuidelines?rd=Packaging/NamingGuidelines
+ //
+ // all Fedora packages must be named using only the following ASCII
+ // characters. These characters are displayed here:
+ //
+ // abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._+
+ //
+ private static final Pattern RPM_PACKAGE_NAME_PATTERN =
+ Pattern.compile("[a-z\\d\\+\\-\\.\\_]+", Pattern.CASE_INSENSITIVE);
+
+ public static final BundlerParamInfo<String> PACKAGE_NAME =
+ new StandardBundlerParam<> (
+ Arguments.CLIOptions.LINUX_BUNDLE_NAME.getId(),
+ String.class,
+ params -> {
+ String nm = APP_NAME.fetchFrom(params);
+ if (nm == null) return null;
+
+ // make sure to lower case and spaces become dashes
+ nm = nm.toLowerCase().replaceAll("[ ]", "-");
+
+ return nm;
+ },
+ (s, p) -> {
+ if (!RPM_PACKAGE_NAME_PATTERN.matcher(s).matches()) {
+ String msgKey = "error.invalid-value-for-package-name";
+ throw new IllegalArgumentException(
+ new ConfigException(MessageFormat.format(
+ I18N.getString(msgKey), s),
+ I18N.getString(msgKey + ".advice")));
+ }
+
+ return s;
+ }
+ );
+
+ public static final BundlerParamInfo<String> LICENSE_TYPE =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.LINUX_RPM_LICENSE_TYPE.getId(),
+ String.class,
+ params -> I18N.getString("param.license-type.default"),
+ (s, p) -> s
+ );
+
+ public static final BundlerParamInfo<String> GROUP =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.LINUX_CATEGORY.getId(),
+ String.class,
+ params -> null,
+ (s, p) -> s);
+
+ private final static String DEFAULT_SPEC_TEMPLATE = "template.spec";
+
+ public final static String TOOL_RPM = "rpm";
+ public final static String TOOL_RPMBUILD = "rpmbuild";
+ public final static String TOOL_RPMBUILD_MIN_VERSION = "4.0";
+
+ public LinuxRpmBundler() {
+ super(PACKAGE_NAME);
+ }
+
+ @Override
+ public void doValidate(Map<String, ? super Object> params)
+ throws ConfigException {
+ }
+
+ private static ToolValidator createRpmbuildToolValidator() {
+ Pattern pattern = Pattern.compile(" (\\d+\\.\\d+)");
+ return new ToolValidator(TOOL_RPMBUILD).setMinimalVersion(
+ TOOL_RPMBUILD_MIN_VERSION).setVersionParser(lines -> {
+ String versionString = lines.limit(1).collect(
+ Collectors.toList()).get(0);
+ Matcher matcher = pattern.matcher(versionString);
+ if (matcher.find()) {
+ return matcher.group(1);
+ }
+ return null;
+ });
+ }
+
+ @Override
+ protected List<ToolValidator> getToolValidators(
+ Map<String, ? super Object> params) {
+ return List.of(createRpmbuildToolValidator());
+ }
+
+ @Override
+ protected File buildPackageBundle(
+ Map<String, String> replacementData,
+ Map<String, ? super Object> params, File outputParentDir) throws
+ PackagerException, IOException {
+
+ 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);
+ }
+
+ return buildRPM(params, outputParentDir);
+ }
+
+ @Override
+ protected Map<String, String> createReplacementData(
+ Map<String, ? super Object> params) throws IOException {
+ Map<String, String> data = new HashMap<>();
+
+ data.put("APPLICATION_DIRECTORY", Path.of(LINUX_INSTALL_DIR.fetchFrom(
+ params), PACKAGE_NAME.fetchFrom(params)).toString());
+ data.put("APPLICATION_SUMMARY", APP_NAME.fetchFrom(params));
+ data.put("APPLICATION_LICENSE_TYPE", LICENSE_TYPE.fetchFrom(params));
+
+ String licenseFile = LICENSE_FILE.fetchFrom(params);
+ if (licenseFile == null) {
+ licenseFile = "";
+ } else {
+ licenseFile = Path.of(licenseFile).toAbsolutePath().normalize().toString();
+ }
+ data.put("APPLICATION_LICENSE_FILE", licenseFile);
+ data.put("APPLICATION_GROUP", Optional.ofNullable(
+ GROUP.fetchFrom(params)).orElse(""));
+
+ return data;
+ }
+
+ @Override
+ protected void initLibProvidersLookup(
+ Map<String, ? super Object> params,
+ LibProvidersLookup libProvidersLookup) {
+ libProvidersLookup.setPackageLookup(file -> {
+ return Executor.of(TOOL_RPM,
+ "-q", "--queryformat", "%{name}\\n",
+ "-q", "--whatprovides", file.toString())
+ .saveOutput(true).executeExpectSuccess().getOutput().stream();
+ });
+ }
+
+ private Path specFile(Map<String, ? super Object> params) {
+ return TEMP_ROOT.fetchFrom(params).toPath().resolve(Path.of("SPECS",
+ PACKAGE_NAME.fetchFrom(params) + ".spec"));
+ }
+
+ private File buildRPM(Map<String, ? super Object> params,
+ File outdir) throws IOException {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.outputting-bundle-location"),
+ outdir.getAbsolutePath()));
+
+ PlatformPackage thePackage = createMetaPackage(params);
+
+ //run rpmbuild
+ Executor.of(
+ TOOL_RPMBUILD,
+ "-bb", specFile(params).toAbsolutePath().toString(),
+ "--define", String.format("%%_sourcedir %s",
+ thePackage.sourceRoot()),
+ // save result to output dir
+ "--define", String.format("%%_rpmdir %s",
+ outdir.getAbsolutePath()),
+ // do not use other system directories to build as current user
+ "--define", String.format("%%_topdir %s",
+ TEMP_ROOT.fetchFrom(params).toPath().toAbsolutePath())
+ ).executeExpectSuccess();
+
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.output-bundle-location"),
+ outdir.getAbsolutePath()));
+
+ // presume the result is the ".rpm" file with the newest modified time
+ // not the best solution, but it is the most reliable
+ File result = null;
+ long lastModified = 0;
+ File[] list = outdir.listFiles();
+ if (list != null) {
+ for (File f : list) {
+ if (f.getName().endsWith(".rpm") &&
+ f.lastModified() > lastModified) {
+ result = f;
+ lastModified = f.lastModified();
+ }
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public String getName() {
+ return I18N.getString("rpm.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "rpm";
+ }
+
+ @Override
+ public boolean supported(boolean runtimeInstaller) {
+ return Platform.isLinux() && (createRpmbuildToolValidator().validate() == null);
+ }
+
+ @Override
+ public boolean isDefault() {
+ return !LinuxDebBundler.isDebian();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,68 @@
+#
+# Copyright (c) 2017, 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.
+#
+#
+app.bundler.name=Linux Application Image
+deb.bundler.name=DEB Bundle
+rpm.bundler.name=RPM Bundle
+
+param.license-type.default=Unknown
+param.menu-group.default=Unknown
+
+resource.deb-control-file=DEB control file
+resource.deb-preinstall-script=DEB preinstall script
+resource.deb-prerm-script=DEB prerm script
+resource.deb-postinstall-script=DEB postinstall script
+resource.deb-postrm-script=DEB postrm script
+resource.copyright-file=Copyright file
+resource.menu-shortcut-descriptor=Menu shortcut descriptor
+resource.menu-icon=menu icon
+resource.rpm-spec-file=RPM spec file
+
+error.parameters-null=Parameters map is null
+error.parameters-null.advice=Pass in a non-null parameters map
+
+error.tool-not-found.advice=Please install required packages
+error.tool-old-version.advice=Please install required packages
+
+error.invalid-install-dir=Invalid installation directory "{0}"
+error.unsupported-install-dir=Installing to system directory "{0}" is currently unsupported
+
+error.no-content-types-for-file-association=No MIME types were specified for File Association number {0}
+error.too-many-content-types-for-file-association=More than one MIME types was specified for File Association number {0}.
+error.invalid-value-for-package-name=Invalid value "{0}" for the bundle name.
+error.invalid-value-for-package-name.advice=Set the "linux-bundle-name" option to a valid Debian package name. Note that the package names must consist only of lower case letters (a-z), digits (0-9), plus (+) and minus (-) signs, and periods (.). They must be at least two characters long and must start with an alphanumeric character.
+
+message.icon-not-png=The specified icon "{0}" is not a PNG file and will not be used. The default icon will be used in it's place.
+message.test-for-tool=Test for [{0}]. Result: {1}
+message.outputting-to-location=Generating DEB for installer to: {0}.
+message.output-to-location=Package (.deb) saved to: {0}.
+message.debs-like-licenses=Debian packages should specify a license. The absence of a license will cause some linux distributions to complain about the quality of the application.
+message.outputting-bundle-location=Generating RPM for installer to: {0}.
+message.output-bundle-location=Package (.rpm) saved to: {0}.
+message.creating-association-with-null-extension=Creating association with null extension.
+
+message.ldd-not-available=ldd command not found. Package dependencies will not be generated.
+message.deb-ldd-not-available.advice=Install "libc-bin" DEB package to get ldd.
+message.rpm-ldd-not-available.advice=Install "glibc-common" RPM package to get ldd.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_ja.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,68 @@
+#
+# Copyright (c) 2017, 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.
+#
+#
+app.bundler.name=Linux Application Image
+deb.bundler.name=DEB Bundle
+rpm.bundler.name=RPM Bundle
+
+param.license-type.default=Unknown
+param.menu-group.default=Unknown
+
+resource.deb-control-file=DEB control file
+resource.deb-preinstall-script=DEB preinstall script
+resource.deb-prerm-script=DEB prerm script
+resource.deb-postinstall-script=DEB postinstall script
+resource.deb-postrm-script=DEB postrm script
+resource.copyright-file=Copyright file
+resource.menu-shortcut-descriptor=Menu shortcut descriptor
+resource.menu-icon=menu icon
+resource.rpm-spec-file=RPM spec file
+
+error.parameters-null=Parameters map is null
+error.parameters-null.advice=Pass in a non-null parameters map
+
+error.tool-not-found.advice=Please install required packages
+error.tool-old-version.advice=Please install required packages
+
+error.invalid-install-dir=Invalid installation directory "{0}"
+error.unsupported-install-dir=Installing to system directory "{0}" is currently unsupported
+
+error.no-content-types-for-file-association=No MIME types were specified for File Association number {0}
+error.too-many-content-types-for-file-association=More than one MIME types was specified for File Association number {0}.
+error.invalid-value-for-package-name=Invalid value "{0}" for the bundle name.
+error.invalid-value-for-package-name.advice=Set the "linux-bundle-name" option to a valid Debian package name. Note that the package names must consist only of lower case letters (a-z), digits (0-9), plus (+) and minus (-) signs, and periods (.). They must be at least two characters long and must start with an alphanumeric character.
+
+message.icon-not-png=The specified icon "{0}" is not a PNG file and will not be used. The default icon will be used in it's place.
+message.test-for-tool=Test for [{0}]. Result: {1}
+message.outputting-to-location=Generating DEB for installer to: {0}.
+message.output-to-location=Package (.deb) saved to: {0}.
+message.debs-like-licenses=Debian packages should specify a license. The absence of a license will cause some linux distributions to complain about the quality of the application.
+message.outputting-bundle-location=Generating RPM for installer to: {0}.
+message.output-bundle-location=Package (.rpm) saved to: {0}.
+message.creating-association-with-null-extension=Creating association with null extension.
+
+message.ldd-not-available=ldd command not found. Package dependencies will not be generated.
+message.deb-ldd-not-available.advice=Install "libc-bin" DEB package to get ldd.
+message.rpm-ldd-not-available.advice=Install "glibc-common" RPM package to get ldd.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/LinuxResources_zh_CN.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,68 @@
+#
+# Copyright (c) 2017, 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.
+#
+#
+app.bundler.name=Linux Application Image
+deb.bundler.name=DEB Bundle
+rpm.bundler.name=RPM Bundle
+
+param.license-type.default=Unknown
+param.menu-group.default=Unknown
+
+resource.deb-control-file=DEB control file
+resource.deb-preinstall-script=DEB preinstall script
+resource.deb-prerm-script=DEB prerm script
+resource.deb-postinstall-script=DEB postinstall script
+resource.deb-postrm-script=DEB postrm script
+resource.copyright-file=Copyright file
+resource.menu-shortcut-descriptor=Menu shortcut descriptor
+resource.menu-icon=menu icon
+resource.rpm-spec-file=RPM spec file
+
+error.parameters-null=Parameters map is null
+error.parameters-null.advice=Pass in a non-null parameters map
+
+error.tool-not-found.advice=Please install required packages
+error.tool-old-version.advice=Please install required packages
+
+error.invalid-install-dir=Invalid installation directory "{0}"
+error.unsupported-install-dir=Installing to system directory "{0}" is currently unsupported
+
+error.no-content-types-for-file-association=No MIME types were specified for File Association number {0}
+error.too-many-content-types-for-file-association=More than one MIME types was specified for File Association number {0}.
+error.invalid-value-for-package-name=Invalid value "{0}" for the bundle name.
+error.invalid-value-for-package-name.advice=Set the "linux-bundle-name" option to a valid Debian package name. Note that the package names must consist only of lower case letters (a-z), digits (0-9), plus (+) and minus (-) signs, and periods (.). They must be at least two characters long and must start with an alphanumeric character.
+
+message.icon-not-png=The specified icon "{0}" is not a PNG file and will not be used. The default icon will be used in it's place.
+message.test-for-tool=Test for [{0}]. Result: {1}
+message.outputting-to-location=Generating DEB for installer to: {0}.
+message.output-to-location=Package (.deb) saved to: {0}.
+message.debs-like-licenses=Debian packages should specify a license. The absence of a license will cause some linux distributions to complain about the quality of the application.
+message.outputting-bundle-location=Generating RPM for installer to: {0}.
+message.output-bundle-location=Package (.rpm) saved to: {0}.
+message.creating-association-with-null-extension=Creating association with null extension.
+
+message.ldd-not-available=ldd command not found. Package dependencies will not be generated.
+message.deb-ldd-not-available.advice=Install "libc-bin" DEB package to get ldd.
+message.rpm-ldd-not-available.advice=Install "glibc-common" RPM package to get ldd.
Binary file src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/java32.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.control Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,10 @@
+Package: APPLICATION_PACKAGE
+Version: APPLICATION_VERSION-APPLICATION_RELEASE
+Section: APPLICATION_SECTION
+Maintainer: APPLICATION_MAINTAINER
+Priority: optional
+Architecture: APPLICATION_ARCH
+Provides: APPLICATION_PACKAGE
+Description: APPLICATION_DESCRIPTION
+Depends: PACKAGE_DEFAULT_DEPENDENCIES PACKAGE_CUSTOM_DEPENDENCIES
+Installed-Size: APPLICATION_INSTALLED_SIZE
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.copyright Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,5 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+
+Files: *
+Copyright: APPLICATION_COPYRIGHT
+License: APPLICATION_LICENSE_TEXT
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.desktop Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Name=APPLICATION_NAME
+Comment=APPLICATION_DESCRIPTION
+Exec=APPLICATION_LAUNCHER
+Icon=APPLICATION_ICON
+Terminal=false
+Type=Application
+Categories=DEPLOY_BUNDLE_CATEGORY
+DESKTOP_MIMES
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.postinst Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,34 @@
+#!/bin/sh
+# postinst script for APPLICATION_PACKAGE
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postinst> `configure' <most-recently-configured-version>
+# * <old-postinst> `abort-upgrade' <new version>
+# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
+# <new-version>
+# * <postinst> `abort-remove'
+# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
+# <failed-install-package> <version> `removing'
+# <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+case "$1" in
+ configure)
+DESKTOP_COMMANDS_INSTALL
+ ;;
+
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.postrm Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,31 @@
+#!/bin/sh
+# postrm script for APPLICATION_PACKAGE
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <postrm> `remove'
+# * <postrm> `purge'
+# * <old-postrm> `upgrade' <new-version>
+# * <new-postrm> `failed-upgrade' <old-version>
+# * <new-postrm> `abort-install'
+# * <new-postrm> `abort-install' <old-version>
+# * <new-postrm> `abort-upgrade' <old-version>
+# * <disappearer's-postrm> `disappear' <overwriter>
+# <overwriter-version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+case "$1" in
+ purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
+ ;;
+
+ *)
+ echo "postrm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.preinst Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,30 @@
+#!/bin/sh
+# preinst script for APPLICATION_PACKAGE
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <new-preinst> `install'
+# * <new-preinst> `install' <old-version>
+# * <new-preinst> `upgrade' <old-version>
+# * <old-preinst> `abort-upgrade' <new-version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+case "$1" in
+ install|upgrade)
+ ;;
+
+ abort-upgrade)
+ ;;
+
+ *)
+ echo "preinst called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.prerm Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,37 @@
+#!/bin/sh
+# prerm script for APPLICATION_PACKAGE
+#
+# see: dh_installdeb(1)
+
+set -e
+
+# summary of how this script can be called:
+# * <prerm> `remove'
+# * <old-prerm> `upgrade' <new-version>
+# * <new-prerm> `failed-upgrade' <old-version>
+# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
+# * <deconfigured's-prerm> `deconfigure' `in-favour'
+# <package-being-installed> <version> `removing'
+# <conflicting-package> <version>
+# for details, see http://www.debian.org/doc/debian-policy/ or
+# the debian-policy package
+
+
+UTILITY_SCRIPTS
+
+case "$1" in
+ remove|upgrade|deconfigure)
+DESKTOP_COMMANDS_UNINSTALL
+ ;;
+
+ failed-upgrade)
+ ;;
+
+ *)
+ echo "prerm called with unknown argument \`$1'" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/template.spec Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,61 @@
+Summary: APPLICATION_SUMMARY
+Name: APPLICATION_PACKAGE
+Version: APPLICATION_VERSION
+Release: APPLICATION_RELEASE
+License: APPLICATION_LICENSE_TYPE
+Vendor: APPLICATION_VENDOR
+Prefix: %{dirname:APPLICATION_DIRECTORY}
+Provides: APPLICATION_PACKAGE
+%if "xAPPLICATION_GROUP" != x
+Group: APPLICATION_GROUP
+%endif
+
+Autoprov: 0
+Autoreq: 0
+%if "xPACKAGE_DEFAULT_DEPENDENCIES" != x || "xPACKAGE_CUSTOM_DEPENDENCIES" != x
+Requires: PACKAGE_DEFAULT_DEPENDENCIES PACKAGE_CUSTOM_DEPENDENCIES
+%endif
+
+#avoid ARCH subfolder
+%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm
+
+#comment line below to enable effective jar compression
+#it could easily get your package size from 40 to 15Mb but
+#build time will substantially increase and it may require unpack200/system java to install
+%define __jar_repack %{nil}
+
+%description
+APPLICATION_DESCRIPTION
+
+%prep
+
+%build
+
+%install
+rm -rf %{buildroot}
+install -d -m 755 %{buildroot}APPLICATION_DIRECTORY
+cp -r %{_sourcedir}APPLICATION_DIRECTORY/* %{buildroot}APPLICATION_DIRECTORY
+%if "xAPPLICATION_LICENSE_FILE" != x
+ %define license_install_file %{_defaultlicensedir}/%{name}-%{version}/%{basename:APPLICATION_LICENSE_FILE}
+ install -d -m 755 %{buildroot}%{dirname:%{license_install_file}}
+ install -m 644 APPLICATION_LICENSE_FILE %{buildroot}%{license_install_file}
+%endif
+
+%files
+%if "xAPPLICATION_LICENSE_FILE" != x
+ %license %{license_install_file}
+ %{dirname:%{license_install_file}}
+%endif
+# If installation directory for the application is /a/b/c, we want only root
+# component of the path (/a) in the spec file to make sure all subdirectories
+# are owned by the package.
+%(echo APPLICATION_DIRECTORY | sed -e "s|\(^/[^/]\{1,\}\).*$|\1|")
+
+%post
+DESKTOP_COMMANDS_INSTALL
+
+%preun
+UTILITY_SCRIPTS
+DESKTOP_COMMANDS_UNINSTALL
+
+%clean
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/jdk/jpackage/internal/resources/utils.sh Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,104 @@
+#
+# Remove $1 desktop file from the list of default handlers for $2 mime type
+# in $3 file dumping output to stdout.
+#
+_filter_out_default_mime_handler ()
+{
+ local defaults_list="$3"
+
+ local desktop_file="$1"
+ local mime_type="$2"
+
+ awk -f- "$defaults_list" <<EOF
+ BEGIN {
+ mime_type="$mime_type"
+ mime_type_regexp="~" mime_type "="
+ desktop_file="$desktop_file"
+ }
+ \$0 ~ mime_type {
+ \$0 = substr(\$0, length(mime_type) + 2);
+ split(\$0, desktop_files, ";")
+ remaining_desktop_files
+ counter=0
+ for (idx in desktop_files) {
+ if (desktop_files[idx] != desktop_file) {
+ ++counter;
+ }
+ }
+ if (counter) {
+ printf mime_type "="
+ for (idx in desktop_files) {
+ if (desktop_files[idx] != desktop_file) {
+ printf desktop_files[idx]
+ if (--counter) {
+ printf ";"
+ }
+ }
+ }
+ printf "\n"
+ }
+ next
+ }
+
+ { print }
+EOF
+}
+
+
+#
+# Remove $2 desktop file from the list of default handlers for $@ mime types
+# in $1 file.
+# Result is saved in $1 file.
+#
+_uninstall_default_mime_handler ()
+{
+ local defaults_list=$1
+ shift
+ [ -f "$defaults_list" ] || return 0
+
+ local desktop_file="$1"
+ shift
+
+ tmpfile1=$(mktemp)
+ tmpfile2=$(mktemp)
+ cat "$defaults_list" > "$tmpfile1"
+
+ local v
+ local update=
+ for mime in "$@"; do
+ _filter_out_default_mime_handler "$desktop_file" "$mime" "$tmpfile1" > "$tmpfile2"
+ v="$tmpfile2"
+ tmpfile2="$tmpfile1"
+ tmpfile1="$v"
+
+ if ! diff -q "$tmpfile1" "$tmpfile2" > /dev/null; then
+ update=yes
+ trace Remove $desktop_file default handler for $mime mime type from $defaults_list file
+ fi
+ done
+
+ if [ -n "$update" ]; then
+ cat "$tmpfile1" > "$defaults_list"
+ trace "$defaults_list" file updated
+ fi
+
+ rm -f "$tmpfile1" "$tmpfile2"
+}
+
+
+#
+# Remove $1 desktop file from the list of default handlers for $@ mime types
+# in all known system defaults lists.
+#
+uninstall_default_mime_handler ()
+{
+ for f in /usr/share/applications/defaults.list /usr/local/share/applications/defaults.list; do
+ _uninstall_default_mime_handler "$f" "$@"
+ done
+}
+
+
+trace ()
+{
+ echo "$@"
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/classes/module-info.java.extra Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018, 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.
+ */
+
+provides jdk.jpackage.internal.Bundler with
+ jdk.jpackage.internal.LinuxAppBundler,
+ jdk.jpackage.internal.LinuxDebBundler,
+ jdk.jpackage.internal.LinuxRpmBundler;
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/native/jpackageapplauncher/launcher.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include <dlfcn.h>
+#include <locale.h>
+#include <string>
+#include <libgen.h>
+#include <stdio.h>
+#include <unistd.h>
+
+
+typedef bool (*start_launcher)(int argc, char* argv[]);
+typedef void (*stop_launcher)();
+
+#define MAX_PATH 1024
+
+std::string GetProgramPath() {
+ ssize_t len = 0;
+ std::string result;
+ char buffer[MAX_PATH] = {0};
+
+ if ((len = readlink("/proc/self/exe", buffer, MAX_PATH - 1)) != -1) {
+ buffer[len] = '\0';
+ result = buffer;
+ }
+
+ return result;
+}
+
+int main(int argc, char *argv[]) {
+ int result = 1;
+ setlocale(LC_ALL, "en_US.utf8");
+ void* library = NULL;
+
+ {
+ std::string programPath = GetProgramPath();
+ std::string libraryName = dirname((char*)programPath.c_str());
+ libraryName += "/../lib/libapplauncher.so";
+ library = dlopen(libraryName.c_str(), RTLD_LAZY);
+
+ if (library == NULL) {
+ fprintf(stderr, "dlopen failed: %s\n", dlerror());
+ fprintf(stderr, "%s not found.\n", libraryName.c_str());
+ }
+ }
+
+ if (library != NULL) {
+ start_launcher start = (start_launcher)dlsym(library, "start_launcher");
+ stop_launcher stop = (stop_launcher)dlsym(library, "stop_launcher");
+
+ if (start != NULL && stop != NULL) {
+ if (start(argc, argv) == true) {
+ result = 0;
+ stop();
+ }
+ } else {
+ fprintf(stderr, "cannot find start_launcher and stop_launcher in libapplauncher.so");
+ }
+
+ dlclose(library);
+ }
+
+
+ return result;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/native/libapplauncher/LinuxPlatform.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,1080 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "Platform.h"
+
+#include "JavaVirtualMachine.h"
+#include "LinuxPlatform.h"
+#include "PlatformString.h"
+#include "IniFile.h"
+#include "Helpers.h"
+#include "FilePath.h"
+
+#include <stdlib.h>
+#include <pwd.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <limits.h>
+#include <signal.h>
+
+#define LINUX_JPACKAGE_TMP_DIR "/.java/jpackage/tmp"
+
+TString GetEnv(const TString &name) {
+ TString result;
+
+ char *value = ::getenv((TCHAR*) name.c_str());
+
+ if (value != NULL) {
+ result = value;
+ }
+
+ return result;
+}
+
+LinuxPlatform::LinuxPlatform(void) : Platform(),
+PosixPlatform() {
+ FMainThread = pthread_self();
+}
+
+LinuxPlatform::~LinuxPlatform(void) {
+}
+
+TString LinuxPlatform::GetPackageAppDirectory() {
+ return FilePath::IncludeTrailingSeparator(
+ GetPackageRootDirectory()) + _T("lib/app");
+}
+
+TString LinuxPlatform::GetAppName() {
+ TString result = GetModuleFileName();
+ result = FilePath::ExtractFileName(result);
+ return result;
+}
+
+TString LinuxPlatform::GetPackageLauncherDirectory() {
+ return FilePath::IncludeTrailingSeparator(
+ GetPackageRootDirectory()) + _T("bin");
+}
+
+TString LinuxPlatform::GetPackageRuntimeBinDirectory() {
+ return FilePath::IncludeTrailingSeparator(GetPackageRootDirectory())
+ + _T("runtime/bin");
+}
+
+void LinuxPlatform::ShowMessage(TString title, TString description) {
+ printf("%s %s\n", PlatformString(title).toPlatformString(),
+ PlatformString(description).toPlatformString());
+ fflush(stdout);
+}
+
+void LinuxPlatform::ShowMessage(TString description) {
+ TString appname = GetModuleFileName();
+ appname = FilePath::ExtractFileName(appname);
+ ShowMessage(PlatformString(appname).toPlatformString(),
+ PlatformString(description).toPlatformString());
+}
+
+TCHAR* LinuxPlatform::ConvertStringToFileSystemString(TCHAR* Source,
+ bool &release) {
+ // Not Implemented.
+ return NULL;
+}
+
+TCHAR* LinuxPlatform::ConvertFileSystemStringToString(TCHAR* Source,
+ bool &release) {
+ // Not Implemented.
+ return NULL;
+}
+
+TString LinuxPlatform::GetModuleFileName() {
+ ssize_t len = 0;
+ TString result;
+ DynamicBuffer<TCHAR> buffer(MAX_PATH);
+ if (buffer.GetData() == NULL) {
+ return result;
+ }
+
+ if ((len = readlink("/proc/self/exe", buffer.GetData(),
+ MAX_PATH - 1)) != -1) {
+ buffer[len] = '\0';
+ result = buffer.GetData();
+ }
+
+ return result;
+}
+
+TString LinuxPlatform::GetPackageRootDirectory() {
+ TString result;
+ TString filename = GetModuleFileName();
+ TString binPath = FilePath::ExtractFilePath(filename);
+
+ size_t slash = binPath.find_last_of(TRAILING_PATHSEPARATOR);
+ if (slash != TString::npos) {
+ result = binPath.substr(0, slash);
+ }
+
+ return result;
+}
+
+TString LinuxPlatform::GetAppDataDirectory() {
+ TString result;
+ TString home = GetEnv(_T("HOME"));
+
+ if (home.empty() == false) {
+ result += FilePath::IncludeTrailingSeparator(home) + _T(".local");
+ }
+
+ return result;
+}
+
+ISectionalPropertyContainer* LinuxPlatform::GetConfigFile(TString FileName) {
+ IniFile *result = new IniFile();
+ if (result == NULL) {
+ return NULL;
+ }
+
+ result->LoadFromFile(FileName);
+
+ return result;
+}
+
+TString LinuxPlatform::GetBundledJavaLibraryFileName(TString RuntimePath) {
+ TString result = FilePath::IncludeTrailingSeparator(RuntimePath) +
+ "lib/libjli.so";
+
+ if (FilePath::FileExists(result) == false) {
+ result = FilePath::IncludeTrailingSeparator(RuntimePath) +
+ "lib/jli/libjli.so";
+ if (FilePath::FileExists(result) == false) {
+ printf("Cannot find libjli.so!");
+ }
+ }
+
+ return result;
+}
+
+bool LinuxPlatform::IsMainThread() {
+ bool result = (FMainThread == pthread_self());
+ return result;
+}
+
+TString LinuxPlatform::getTmpDirString() {
+ return TString(LINUX_JPACKAGE_TMP_DIR);
+}
+
+TPlatformNumber LinuxPlatform::GetMemorySize() {
+ long pages = sysconf(_SC_PHYS_PAGES);
+ long page_size = sysconf(_SC_PAGE_SIZE);
+ TPlatformNumber result = pages * page_size;
+ result = result / 1048576; // Convert from bytes to megabytes.
+ return result;
+}
+
+void PosixProcess::Cleanup() {
+ if (FOutputHandle != 0) {
+ close(FOutputHandle);
+ FOutputHandle = 0;
+ }
+
+ if (FInputHandle != 0) {
+ close(FInputHandle);
+ FInputHandle = 0;
+ }
+}
+
+#define PIPE_READ 0
+#define PIPE_WRITE 1
+
+bool PosixProcess::Execute(const TString Application,
+ const std::vector<TString> Arguments, bool AWait) {
+ bool result = false;
+
+ if (FRunning == false) {
+ FRunning = true;
+
+ int handles[2];
+
+ if (pipe(handles) == -1) {
+ return false;
+ }
+
+ struct sigaction sa;
+ sa.sa_handler = SIG_IGN;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+
+ FChildPID = fork();
+
+ // PID returned by vfork is 0 for the child process and the
+ // PID of the child process for the parent.
+ if (FChildPID == -1) {
+ // Error
+ TString message = PlatformString::Format(
+ _T("Error: Unable to create process %s"),
+ Application.data());
+ throw Exception(message);
+ } else if (FChildPID == 0) {
+ Cleanup();
+ TString command = Application;
+
+ for (std::vector<TString>::const_iterator iterator =
+ Arguments.begin(); iterator != Arguments.end();
+ iterator++) {
+ command += TString(_T(" ")) + *iterator;
+ }
+#ifdef DEBUG
+ printf("%s\n", command.data());
+#endif // DEBUG
+
+ dup2(handles[PIPE_READ], STDIN_FILENO);
+ dup2(handles[PIPE_WRITE], STDOUT_FILENO);
+
+ close(handles[PIPE_READ]);
+ close(handles[PIPE_WRITE]);
+
+ execl("/bin/sh", "sh", "-c", command.data(), (char *) 0);
+
+ _exit(127);
+ } else {
+ FOutputHandle = handles[PIPE_READ];
+ FInputHandle = handles[PIPE_WRITE];
+
+ if (AWait == true) {
+ ReadOutput();
+ Wait();
+ Cleanup();
+ FRunning = false;
+ result = true;
+ } else {
+ result = true;
+ }
+ }
+ }
+
+ return result;
+}
+
+
+//----------------------------------------------------------------------------
+
+#ifndef __UNIX_JPACKAGE_PLATFORM__
+#define __UNIX_JPACKAGE_PLATFORM__
+
+/** Provide an abstraction for difference in the platform APIs,
+ e.g. string manipulation functions, etc. */
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/stat.h>
+
+#define TCHAR char
+
+#define _T(x) x
+
+#define JPACKAGE_MULTIBYTE_SNPRINTF snprintf
+
+#define JPACKAGE_SNPRINTF(buffer, sizeOfBuffer, count, format, ...) \
+ snprintf((buffer), (count), (format), __VA_ARGS__)
+
+#define JPACKAGE_PRINTF(format, ...) \
+ printf((format), ##__VA_ARGS__)
+
+#define JPACKAGE_FPRINTF(dest, format, ...) \
+ fprintf((dest), (format), __VA_ARGS__)
+
+#define JPACKAGE_SSCANF(buf, format, ...) \
+ sscanf((buf), (format), __VA_ARGS__)
+
+#define JPACKAGE_STRDUP(strSource) \
+ strdup((strSource))
+
+//return "error code" (like on Windows)
+
+static int JPACKAGE_STRNCPY(char *strDest, size_t numberOfElements,
+ const char *strSource, size_t count) {
+ char *s = strncpy(strDest, strSource, count);
+ // Duplicate behavior of the Windows' _tcsncpy_s() by adding a NULL
+ // terminator at the end of the string.
+ if (count < numberOfElements) {
+ s[count] = '\0';
+ } else {
+ s[numberOfElements - 1] = '\0';
+ }
+ return (s == strDest) ? 0 : 1;
+}
+
+#define JPACKAGE_STRICMP(x, y) \
+ strcasecmp((x), (y))
+
+#define JPACKAGE_STRNICMP(x, y, cnt) \
+ strncasecmp((x), (y), (cnt))
+
+#define JPACKAGE_STRNCMP(x, y, cnt) \
+ strncmp((x), (y), (cnt))
+
+#define JPACKAGE_STRLEN(x) \
+ strlen((x))
+
+#define JPACKAGE_STRSTR(x, y) \
+ strstr((x), (y))
+
+#define JPACKAGE_STRCHR(x, y) \
+ strchr((x), (y))
+
+#define JPACKAGE_STRRCHR(x, y) \
+ strrchr((x), (y))
+
+#define JPACKAGE_STRPBRK(x, y) \
+ strpbrk((x), (y))
+
+#define JPACKAGE_GETENV(x) \
+ getenv((x))
+
+#define JPACKAGE_PUTENV(x) \
+ putenv((x))
+
+#define JPACKAGE_STRCMP(x, y) \
+ strcmp((x), (y))
+
+#define JPACKAGE_STRCPY(x, y) \
+ strcpy((x), (y))
+
+#define JPACKAGE_STRCAT(x, y) \
+ strcat((x), (y))
+
+#define JPACKAGE_ATOI(x) \
+ atoi((x))
+
+#define JPACKAGE_FOPEN(x, y) \
+ fopen((x), (y))
+
+#define JPACKAGE_FGETS(x, y, z) \
+ fgets((x), (y), (z))
+
+#define JPACKAGE_REMOVE(x) \
+ remove((x))
+
+#define JPACKAGE_SPAWNV(mode, cmd, args) \
+ spawnv((mode), (cmd), (args))
+
+#define JPACKAGE_ISDIGIT(ch) isdigit(ch)
+
+// for non-unicode, just return the input string for
+// the following 2 conversions
+#define JPACKAGE_NEW_MULTIBYTE(message) message
+
+#define JPACKAGE_NEW_FROM_MULTIBYTE(message) message
+
+// for non-unicode, no-op for the relase operation
+// since there is no memory allocated for the
+// string conversions
+#define JPACKAGE_RELEASE_MULTIBYTE(tmpMBCS)
+
+#define JPACKAGE_RELEASE_FROM_MULTIBYTE(tmpMBCS)
+
+// The size will be used for converting from 1 byte to 1 byte encoding.
+// Ensure have space for zero-terminator.
+#define JPACKAGE_GET_SIZE_FOR_ENCODING(message, theLength) (theLength + 1)
+
+#endif
+#define xmlTagType 0
+#define xmlPCDataType 1
+
+typedef struct _xmlNode XMLNode;
+typedef struct _xmlAttribute XMLAttribute;
+
+struct _xmlNode {
+ int _type; // Type of node: tag, pcdata, cdate
+ TCHAR* _name; // Contents of node
+ XMLNode* _next; // Next node at same level
+ XMLNode* _sub; // First sub-node
+ XMLAttribute* _attributes; // List of attributes
+};
+
+struct _xmlAttribute {
+ TCHAR* _name; // Name of attribute
+ TCHAR* _value; // Value of attribute
+ XMLAttribute* _next; // Next attribute for this tag
+};
+
+// Public interface
+static void RemoveNonAsciiUTF8FromBuffer(char *buf);
+XMLNode* ParseXMLDocument(TCHAR* buf);
+void FreeXMLDocument(XMLNode* root);
+
+// Utility methods for parsing document
+XMLNode* FindXMLChild(XMLNode* root, const TCHAR* name);
+TCHAR* FindXMLAttribute(XMLAttribute* attr, const TCHAR* name);
+
+// Debugging
+void PrintXMLDocument(XMLNode* node, int indt);
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <wctype.h>
+
+#define JWS_assert(s, msg) \
+ if (!(s)) { Abort(msg); }
+
+
+// Internal declarations
+static XMLNode* ParseXMLElement(void);
+static XMLAttribute* ParseXMLAttribute(void);
+static TCHAR* SkipWhiteSpace(TCHAR *p);
+static TCHAR* SkipXMLName(TCHAR *p);
+static TCHAR* SkipXMLComment(TCHAR *p);
+static TCHAR* SkipXMLDocType(TCHAR *p);
+static TCHAR* SkipXMLProlog(TCHAR *p);
+static TCHAR* SkipPCData(TCHAR *p);
+static int IsPCData(TCHAR *p);
+static void ConvertBuiltInEntities(TCHAR* p);
+static void SetToken(int type, TCHAR* start, TCHAR* end);
+static void GetNextToken(void);
+static XMLNode* CreateXMLNode(int type, TCHAR* name);
+static XMLAttribute* CreateXMLAttribute(TCHAR *name, TCHAR* value);
+static XMLNode* ParseXMLElement(void);
+static XMLAttribute* ParseXMLAttribute(void);
+static void FreeXMLAttribute(XMLAttribute* attr);
+static void PrintXMLAttributes(XMLAttribute* attr);
+static void indent(int indt);
+
+static jmp_buf jmpbuf;
+static XMLNode* root_node = NULL;
+
+/** definition of error codes for setjmp/longjmp,
+ * that can be handled in ParseXMLDocument()
+ */
+#define JMP_NO_ERROR 0
+#define JMP_OUT_OF_RANGE 1
+
+#define NEXT_CHAR(p) { \
+ if (*p != 0) { \
+ p++; \
+ } else { \
+ longjmp(jmpbuf, JMP_OUT_OF_RANGE); \
+ } \
+}
+#define NEXT_CHAR_OR_BREAK(p) { \
+ if (*p != 0) { \
+ p++; \
+ } else { \
+ break; \
+ } \
+}
+#define NEXT_CHAR_OR_RETURN(p) { \
+ if (*p != 0) { \
+ p++; \
+ } else { \
+ return; \
+ } \
+}
+#define SKIP_CHARS(p,n) { \
+ int i; \
+ for (i = 0; i < (n); i++) { \
+ if (*p != 0) { \
+ p++; \
+ } else { \
+ longjmp(jmpbuf, JMP_OUT_OF_RANGE); \
+ } \
+ } \
+}
+#define SKIP_CHARS_OR_BREAK(p,n) { \
+ int i; \
+ for (i = 0; i < (n); i++) { \
+ if (*p != 0) { \
+ p++; \
+ } else { \
+ break; \
+ } \
+ } \
+ if (i < (n)) { \
+ break; \
+ } \
+}
+
+/** Iterates through the null-terminated buffer (i.e., C string) and
+ * replaces all UTF-8 encoded character >255 with 255
+ *
+ * UTF-8 encoding:
+ *
+ * Range A: 0x0000 - 0x007F
+ * 0 | bits 0 - 7
+ * Range B : 0x0080 - 0x07FF :
+ * 110 | bits 6 - 10
+ * 10 | bits 0 - 5
+ * Range C : 0x0800 - 0xFFFF :
+ * 1110 | bits 12-15
+ * 10 | bits 6-11
+ * 10 | bits 0-5
+ */
+static void RemoveNonAsciiUTF8FromBuffer(char *buf) {
+ char* p;
+ char* q;
+ char c;
+ p = q = buf;
+ // We are not using NEXT_CHAR() to check if *q is NULL, as q is output
+ // location and offset for q is smaller than for p.
+ while (*p != '\0') {
+ c = *p;
+ if ((c & 0x80) == 0) {
+ /* Range A */
+ *q++ = *p;
+ NEXT_CHAR(p);
+ } else if ((c & 0xE0) == 0xC0) {
+ /* Range B */
+ *q++ = (char) 0xFF;
+ NEXT_CHAR(p);
+ NEXT_CHAR_OR_BREAK(p);
+ } else {
+ /* Range C */
+ *q++ = (char) 0xFF;
+ NEXT_CHAR(p);
+ SKIP_CHARS_OR_BREAK(p, 2);
+ }
+ }
+ /* Null terminate string */
+ *q = '\0';
+}
+
+static TCHAR* SkipWhiteSpace(TCHAR *p) {
+ if (p != NULL) {
+ while (iswspace(*p))
+ NEXT_CHAR_OR_BREAK(p);
+ }
+ return p;
+}
+
+static TCHAR* SkipXMLName(TCHAR *p) {
+ TCHAR c = *p;
+ /* Check if start of token */
+ if (('a' <= c && c <= 'z') ||
+ ('A' <= c && c <= 'Z') ||
+ c == '_' || c == ':') {
+
+ while (('a' <= c && c <= 'z') ||
+ ('A' <= c && c <= 'Z') ||
+ ('0' <= c && c <= '9') ||
+ c == '_' || c == ':' || c == '.' || c == '-') {
+ NEXT_CHAR(p);
+ c = *p;
+ if (c == '\0') break;
+ }
+ }
+ return p;
+}
+
+static TCHAR* SkipXMLComment(TCHAR *p) {
+ if (p != NULL) {
+ if (JPACKAGE_STRNCMP(p, _T("<!--"), 4) == 0) {
+ SKIP_CHARS(p, 4);
+ do {
+ if (JPACKAGE_STRNCMP(p, _T("-->"), 3) == 0) {
+ SKIP_CHARS(p, 3);
+ return p;
+ }
+ NEXT_CHAR(p);
+ } while (*p != '\0');
+ }
+ }
+ return p;
+}
+
+static TCHAR* SkipXMLDocType(TCHAR *p) {
+ if (p != NULL) {
+ if (JPACKAGE_STRNCMP(p, _T("<!"), 2) == 0) {
+ SKIP_CHARS(p, 2);
+ while (*p != '\0') {
+ if (*p == '>') {
+ NEXT_CHAR(p);
+ return p;
+ }
+ NEXT_CHAR(p);
+ }
+ }
+ }
+ return p;
+}
+
+static TCHAR* SkipXMLProlog(TCHAR *p) {
+ if (p != NULL) {
+ if (JPACKAGE_STRNCMP(p, _T("<?"), 2) == 0) {
+ SKIP_CHARS(p, 2);
+ do {
+ if (JPACKAGE_STRNCMP(p, _T("?>"), 2) == 0) {
+ SKIP_CHARS(p, 2);
+ return p;
+ }
+ NEXT_CHAR(p);
+ } while (*p != '\0');
+ }
+ }
+ return p;
+}
+
+/* Search for the built-in XML entities:
+ * & (&), < (<), > (>), ' ('), and "e(")
+ * and convert them to a real TCHARacter
+ */
+static void ConvertBuiltInEntities(TCHAR* p) {
+ TCHAR* q;
+ q = p;
+ // We are not using NEXT_CHAR() to check if *q is NULL,
+ // as q is output location and offset for q is smaller than for p.
+ while (*p) {
+ if (IsPCData(p)) {
+ /* dont convert &xxx values within PData */
+ TCHAR *end;
+ end = SkipPCData(p);
+ while (p < end) {
+ *q++ = *p;
+ NEXT_CHAR(p);
+ }
+ } else {
+ if (JPACKAGE_STRNCMP(p, _T("&"), 5) == 0) {
+ *q++ = '&';
+ SKIP_CHARS(p, 5);
+ } else if (JPACKAGE_STRNCMP(p, _T("<"), 4) == 0) {
+ *q = '<';
+ SKIP_CHARS(p, 4);
+ } else if (JPACKAGE_STRNCMP(p, _T(">"), 4) == 0) {
+ *q = '>';
+ SKIP_CHARS(p, 4);
+ } else if (JPACKAGE_STRNCMP(p, _T("'"), 6) == 0) {
+ *q = '\'';
+ SKIP_CHARS(p, 6);
+ } else if (JPACKAGE_STRNCMP(p, _T(""e;"), 7) == 0) {
+ *q = '\"';
+ SKIP_CHARS(p, 7);
+ } else {
+ *q++ = *p;
+ NEXT_CHAR(p);
+ }
+ }
+ }
+ *q = '\0';
+}
+
+/* ------------------------------------------------------------- */
+/* XML tokenizer */
+
+#define TOKEN_UNKNOWN 0
+#define TOKEN_BEGIN_TAG 1 /* <tag */
+#define TOKEN_END_TAG 2 /* </tag */
+#define TOKEN_CLOSE_BRACKET 3 /* > */
+#define TOKEN_EMPTY_CLOSE_BRACKET 4 /* /> */
+#define TOKEN_PCDATA 5 /* pcdata */
+#define TOKEN_CDATA 6 /* cdata */
+#define TOKEN_EOF 7
+
+static TCHAR* CurPos = NULL;
+static TCHAR* CurTokenName = NULL;
+static int CurTokenType;
+static int MaxTokenSize = -1;
+
+/* Copy token from buffer to Token variable */
+static void SetToken(int type, TCHAR* start, TCHAR* end) {
+ int len = end - start;
+ if (len > MaxTokenSize) {
+ if (CurTokenName != NULL) free(CurTokenName);
+ CurTokenName = (TCHAR *) malloc((len + 1) * sizeof (TCHAR));
+ if (CurTokenName == NULL) {
+ return;
+ }
+ MaxTokenSize = len;
+ }
+
+ CurTokenType = type;
+ JPACKAGE_STRNCPY(CurTokenName, len + 1, start, len);
+ CurTokenName[len] = '\0';
+}
+
+/* Skip XML comments, doctypes, and prolog tags */
+static TCHAR* SkipFilling(void) {
+ TCHAR *q = CurPos;
+
+ /* Skip white space and comment sections */
+ do {
+ q = CurPos;
+ CurPos = SkipWhiteSpace(CurPos);
+ CurPos = SkipXMLComment(CurPos); /* Must be called befor DocTypes */
+ CurPos = SkipXMLDocType(CurPos); /* <! ... > directives */
+ CurPos = SkipXMLProlog(CurPos); /* <? ... ?> directives */
+ } while (CurPos != q);
+
+ return CurPos;
+}
+
+/* Parses next token and initializes the global token variables above
+ The tokennizer automatically skips comments (<!-- comment -->) and
+ <! ... > directives.
+ */
+static void GetNextToken(void) {
+ TCHAR *p, *q;
+
+ /* Skip white space and comment sections */
+ p = SkipFilling();
+
+ if (p == NULL || *p == '\0') {
+ CurTokenType = TOKEN_EOF;
+ return;
+ } else if (p[0] == '<' && p[1] == '/') {
+ /* TOKEN_END_TAG */
+ q = SkipXMLName(p + 2);
+ SetToken(TOKEN_END_TAG, p + 2, q);
+ p = q;
+ } else if (*p == '<') {
+ /* TOKEN_BEGIN_TAG */
+ q = SkipXMLName(p + 1);
+ SetToken(TOKEN_BEGIN_TAG, p + 1, q);
+ p = q;
+ } else if (p[0] == '>') {
+ CurTokenType = TOKEN_CLOSE_BRACKET;
+ NEXT_CHAR(p);
+ } else if (p[0] == '/' && p[1] == '>') {
+ CurTokenType = TOKEN_EMPTY_CLOSE_BRACKET;
+ SKIP_CHARS(p, 2);
+ } else {
+ /* Search for end of data */
+ q = p + 1;
+ while (*q && *q != '<') {
+ if (IsPCData(q)) {
+ q = SkipPCData(q);
+ } else {
+ NEXT_CHAR(q);
+ }
+ }
+ SetToken(TOKEN_PCDATA, p, q);
+ /* Convert all entities inside token */
+ ConvertBuiltInEntities(CurTokenName);
+ p = q;
+ }
+ /* Advance pointer to beginning of next token */
+ CurPos = p;
+}
+
+static XMLNode* CreateXMLNode(int type, TCHAR* name) {
+ XMLNode* node;
+ node = (XMLNode*) malloc(sizeof (XMLNode));
+ if (node == NULL) {
+ return NULL;
+ }
+ node->_type = type;
+ node->_name = name;
+ node->_next = NULL;
+ node->_sub = NULL;
+ node->_attributes = NULL;
+ return node;
+}
+
+static XMLAttribute* CreateXMLAttribute(TCHAR *name, TCHAR* value) {
+ XMLAttribute* attr;
+ attr = (XMLAttribute*) malloc(sizeof (XMLAttribute));
+ if (attr == NULL) {
+ return NULL;
+ }
+ attr->_name = name;
+ attr->_value = value;
+ attr->_next = NULL;
+ return attr;
+}
+
+XMLNode* ParseXMLDocument(TCHAR* buf) {
+ XMLNode* root;
+ int err_code = setjmp(jmpbuf);
+ switch (err_code) {
+ case JMP_NO_ERROR:
+#ifndef _UNICODE
+ /* Remove UTF-8 encoding from buffer */
+ RemoveNonAsciiUTF8FromBuffer(buf);
+#endif
+
+ /* Get first Token */
+ CurPos = buf;
+ GetNextToken();
+
+ /* Parse document*/
+ root = ParseXMLElement();
+ break;
+ case JMP_OUT_OF_RANGE:
+ /* cleanup: */
+ if (root_node != NULL) {
+ FreeXMLDocument(root_node);
+ root_node = NULL;
+ }
+ if (CurTokenName != NULL) free(CurTokenName);
+ fprintf(stderr, "Error during parsing jnlp file...\n");
+ exit(-1);
+ break;
+ default:
+ root = NULL;
+ break;
+ }
+
+ return root;
+}
+
+static XMLNode* ParseXMLElement(void) {
+ XMLNode* node = NULL;
+ XMLNode* subnode = NULL;
+ XMLNode* nextnode = NULL;
+ XMLAttribute* attr = NULL;
+
+ if (CurTokenType == TOKEN_BEGIN_TAG) {
+
+ /* Create node for new element tag */
+ node = CreateXMLNode(xmlTagType, JPACKAGE_STRDUP(CurTokenName));
+ /* We need to save root node pointer to be able to cleanup
+ if an error happens during parsing */
+ if (!root_node) {
+ root_node = node;
+ }
+ /* Parse attributes. This section eats a all input until
+ EOF, a > or a /> */
+ attr = ParseXMLAttribute();
+ while (attr != NULL) {
+ attr->_next = node->_attributes;
+ node->_attributes = attr;
+ attr = ParseXMLAttribute();
+ }
+
+ /* This will eihter be a TOKEN_EOF, TOKEN_CLOSE_BRACKET, or a
+ * TOKEN_EMPTY_CLOSE_BRACKET */
+ GetNextToken();
+
+ if (CurTokenType == TOKEN_EMPTY_CLOSE_BRACKET) {
+ GetNextToken();
+ /* We are done with the sublevel - fall through to continue */
+ /* parsing tags at the same level */
+ } else if (CurTokenType == TOKEN_CLOSE_BRACKET) {
+ GetNextToken();
+
+ /* Parse until end tag if found */
+ node->_sub = ParseXMLElement();
+
+ if (CurTokenType == TOKEN_END_TAG) {
+ /* Find closing bracket '>' for end tag */
+ do {
+ GetNextToken();
+ } while (CurTokenType != TOKEN_EOF &&
+ CurTokenType != TOKEN_CLOSE_BRACKET);
+ GetNextToken();
+ }
+ }
+
+ /* Continue parsing rest on same level */
+ if (CurTokenType != TOKEN_EOF) {
+ /* Parse rest of stream at same level */
+ node->_next = ParseXMLElement();
+ }
+ return node;
+
+ } else if (CurTokenType == TOKEN_PCDATA) {
+ /* Create node for pcdata */
+ node = CreateXMLNode(xmlPCDataType, JPACKAGE_STRDUP(CurTokenName));
+ /* We need to save root node pointer to be able to cleanup
+ if an error happens during parsing */
+ if (!root_node) {
+ root_node = node;
+ }
+ GetNextToken();
+ return node;
+ }
+
+ /* Something went wrong. */
+ return NULL;
+}
+
+/* Parses an XML attribute. */
+static XMLAttribute* ParseXMLAttribute(void) {
+ TCHAR* q = NULL;
+ TCHAR* name = NULL;
+ TCHAR* PrevPos = NULL;
+
+ do {
+ /* We need to check this condition to avoid endless loop
+ in case if an error happend during parsing. */
+ if (PrevPos == CurPos) {
+ if (name != NULL) {
+ free(name);
+ name = NULL;
+ }
+
+ return NULL;
+ }
+
+ PrevPos = CurPos;
+
+ /* Skip whitespace etc. */
+ SkipFilling();
+
+ /* Check if we are done witht this attribute section */
+ if (CurPos[0] == '\0' ||
+ CurPos[0] == '>' ||
+ (CurPos[0] == '/' && CurPos[1] == '>')) {
+
+ if (name != NULL) {
+ free(name);
+ name = NULL;
+ }
+
+ return NULL;
+ }
+
+ /* Find end of name */
+ q = CurPos;
+ while (*q && !iswspace(*q) && *q != '=') NEXT_CHAR(q);
+
+ SetToken(TOKEN_UNKNOWN, CurPos, q);
+ if (name) {
+ free(name);
+ name = NULL;
+ }
+ name = JPACKAGE_STRDUP(CurTokenName);
+
+ /* Skip any whitespace */
+ CurPos = q;
+ CurPos = SkipFilling();
+
+ /* Next TCHARacter must be '=' for a valid attribute.
+ If it is not, this is really an error.
+ We ignore this, and just try to parse an attribute
+ out of the rest of the string.
+ */
+ } while (*CurPos != '=');
+
+ NEXT_CHAR(CurPos);
+ CurPos = SkipWhiteSpace(CurPos);
+ /* Parse CDATA part of attribute */
+ if ((*CurPos == '\"') || (*CurPos == '\'')) {
+ TCHAR quoteChar = *CurPos;
+ q = ++CurPos;
+ while (*q != '\0' && *q != quoteChar) NEXT_CHAR(q);
+ SetToken(TOKEN_CDATA, CurPos, q);
+ CurPos = q + 1;
+ } else {
+ q = CurPos;
+ while (*q != '\0' && !iswspace(*q)) NEXT_CHAR(q);
+ SetToken(TOKEN_CDATA, CurPos, q);
+ CurPos = q;
+ }
+
+ //Note: no need to free name and CurTokenName duplicate; they're assigned
+ // to an XMLAttribute structure in CreateXMLAttribute
+
+ return CreateXMLAttribute(name, JPACKAGE_STRDUP(CurTokenName));
+}
+
+void FreeXMLDocument(XMLNode* root) {
+ if (root == NULL) return;
+ FreeXMLDocument(root->_sub);
+ FreeXMLDocument(root->_next);
+ FreeXMLAttribute(root->_attributes);
+ free(root->_name);
+ free(root);
+}
+
+static void FreeXMLAttribute(XMLAttribute* attr) {
+ if (attr == NULL) return;
+ free(attr->_name);
+ free(attr->_value);
+ FreeXMLAttribute(attr->_next);
+ free(attr);
+}
+
+/* Find element at current level with a given name */
+XMLNode* FindXMLChild(XMLNode* root, const TCHAR* name) {
+ if (root == NULL) return NULL;
+
+ if (root->_type == xmlTagType && JPACKAGE_STRCMP(root->_name, name) == 0) {
+ return root;
+ }
+
+ return FindXMLChild(root->_next, name);
+}
+
+/* Search for an attribute with the given name and returns the contents.
+ * Returns NULL if attribute is not found
+ */
+TCHAR* FindXMLAttribute(XMLAttribute* attr, const TCHAR* name) {
+ if (attr == NULL) return NULL;
+ if (JPACKAGE_STRCMP(attr->_name, name) == 0) return attr->_value;
+ return FindXMLAttribute(attr->_next, name);
+}
+
+void PrintXMLDocument(XMLNode* node, int indt) {
+ if (node == NULL) return;
+
+ if (node->_type == xmlTagType) {
+ JPACKAGE_PRINTF(_T("\n"));
+ indent(indt);
+ JPACKAGE_PRINTF(_T("<%s"), node->_name);
+ PrintXMLAttributes(node->_attributes);
+ if (node->_sub == NULL) {
+ JPACKAGE_PRINTF(_T("/>\n"));
+ } else {
+ JPACKAGE_PRINTF(_T(">"));
+ PrintXMLDocument(node->_sub, indt + 1);
+ indent(indt);
+ JPACKAGE_PRINTF(_T("</%s>"), node->_name);
+ }
+ } else {
+ JPACKAGE_PRINTF(_T("%s"), node->_name);
+ }
+ PrintXMLDocument(node->_next, indt);
+}
+
+static void PrintXMLAttributes(XMLAttribute* attr) {
+ if (attr == NULL) return;
+
+ JPACKAGE_PRINTF(_T(" %s=\"%s\""), attr->_name, attr->_value);
+ PrintXMLAttributes(attr->_next);
+}
+
+static void indent(int indt) {
+ int i;
+ for (i = 0; i < indt; i++) {
+ JPACKAGE_PRINTF(_T(" "));
+ }
+}
+
+const TCHAR *CDStart = _T("<![CDATA[");
+const TCHAR *CDEnd = _T("]]>");
+
+static TCHAR* SkipPCData(TCHAR *p) {
+ TCHAR *end = JPACKAGE_STRSTR(p, CDEnd);
+ if (end != NULL) {
+ return end + sizeof (CDEnd);
+ }
+ return (++p);
+}
+
+static int IsPCData(TCHAR *p) {
+ const int size = sizeof (CDStart);
+ return (JPACKAGE_STRNCMP(CDStart, p, size) == 0);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/native/libapplauncher/LinuxPlatform.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef LINUXPLATFORM_H
+#define LINUXPLATFORM_H
+
+#include "Platform.h"
+#include "PosixPlatform.h"
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <pthread.h>
+#include <list>
+
+class LinuxPlatform : virtual public Platform, PosixPlatform {
+private:
+ pthread_t FMainThread;
+
+protected:
+ virtual TString getTmpDirString();
+
+public:
+ LinuxPlatform(void);
+ virtual ~LinuxPlatform(void);
+
+ TString GetPackageAppDirectory();
+ TString GetPackageLauncherDirectory();
+ TString GetPackageRuntimeBinDirectory();
+
+ virtual void ShowMessage(TString title, TString description);
+ virtual void ShowMessage(TString description);
+
+ virtual TCHAR* ConvertStringToFileSystemString(
+ TCHAR* Source, bool &release);
+ virtual TCHAR* ConvertFileSystemStringToString(
+ TCHAR* Source, bool &release);
+
+ virtual TString GetPackageRootDirectory();
+ virtual TString GetAppDataDirectory();
+ virtual TString GetAppName();
+
+ virtual TString GetModuleFileName();
+
+ virtual TString GetBundledJavaLibraryFileName(TString RuntimePath);
+
+ virtual ISectionalPropertyContainer* GetConfigFile(TString FileName);
+
+ virtual bool IsMainThread();
+ virtual TPlatformNumber GetMemorySize();
+};
+
+#endif //LINUXPLATFORM_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/linux/native/libapplauncher/PlatformDefs.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,67 @@
+/*
+ * 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.
+ */
+
+#ifndef PLATFORM_DEFS_H
+#define PLATFORM_DEFS_H
+
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <dlfcn.h>
+#include <libgen.h>
+#include <string>
+
+using namespace std;
+
+#ifndef LINUX
+#define LINUX
+#endif
+
+#define _T(x) x
+
+typedef char TCHAR;
+typedef std::string TString;
+#define StringLength strlen
+
+typedef unsigned long DWORD;
+
+#define TRAILING_PATHSEPARATOR '/'
+#define BAD_TRAILING_PATHSEPARATOR '\\'
+#define PATH_SEPARATOR ':'
+#define BAD_PATH_SEPARATOR ';'
+#define MAX_PATH 1000
+
+typedef long TPlatformNumber;
+typedef pid_t TProcessID;
+
+#define HMODULE void*
+
+typedef void* Module;
+typedef void* Procedure;
+
+#define StringToFileSystemString PlatformString
+#define FileSystemStringToString PlatformString
+
+#endif // PLATFORM_DEFS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/EnumeratedBundlerParam.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2014, 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.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+/**
+ * EnumeratedBundlerParams<T>
+ *
+ * Contains key-value pairs (elements) where keys are "displayable"
+ * keys which the IDE can display/choose and values are "identifier" values
+ * which can be stored in parameters' map.
+ *
+ * For instance the Mac has a predefined set of categories which can be applied
+ * to LSApplicationCategoryType which is required for the mac app store.
+ *
+ * The following example illustrates a simple usage of
+ * the MAC_CATEGORY parameter:
+ *
+ * <pre>{@code
+ * Set<String> keys = MAC_CATEGORY.getDisplayableKeys();
+ *
+ * String key = getLastValue(keys); // get last value for example
+ *
+ * String value = MAC_CATEGORY.getValueForDisplayableKey(key);
+ * params.put(MAC_CATEGORY.getID(), value);
+ * }</pre>
+ *
+ */
+class EnumeratedBundlerParam<T> extends BundlerParamInfo<T> {
+ // Not sure if this is the correct order, my idea is that from IDE
+ // perspective the string to display to the user is the key and then the
+ // value is some type of object (although probably a String in most cases)
+ private final Map<String, T> elements;
+ private final boolean strict;
+
+ EnumeratedBundlerParam(String id, Class<T> valueType,
+ Function<Map<String, ? super Object>, T> defaultValueFunction,
+ BiFunction<String, Map<String, ? super Object>, T> stringConverter,
+ Map<String, T> elements, boolean strict) {
+ this.id = id;
+ this.valueType = valueType;
+ this.defaultValueFunction = defaultValueFunction;
+ this.stringConverter = stringConverter;
+ this.elements = elements;
+ this.strict = strict;
+ }
+
+ boolean isInPossibleValues(T value) {
+ return elements.values().contains(value);
+ }
+
+ // Having the displayable values as the keys seems a bit wacky
+ Set<String> getDisplayableKeys() {
+ return Collections.unmodifiableSet(elements.keySet());
+ }
+
+ // mapping from a "displayable" key to an "identifier" value.
+ T getValueForDisplayableKey(String displayableKey) {
+ return elements.get(displayableKey);
+ }
+
+ boolean isStrict() {
+ return strict;
+ }
+
+ boolean isLoose() {
+ return !isStrict();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,357 @@
+/*
+ * Copyright (c) 2012, 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.File;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.ResourceBundle;
+
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+import static jdk.jpackage.internal.MacBaseInstallerBundler.*;
+
+public class MacAppBundler extends AbstractImageBundler {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.MacResources");
+
+ private static final String TEMPLATE_BUNDLE_ICON = "java.icns";
+
+ public static Map<String, String> getMacCategories() {
+ Map<String, String> map = new HashMap<>();
+ map.put("Business", "public.app-category.business");
+ map.put("Developer Tools", "public.app-category.developer-tools");
+ map.put("Education", "public.app-category.education");
+ map.put("Entertainment", "public.app-category.entertainment");
+ map.put("Finance", "public.app-category.finance");
+ map.put("Games", "public.app-category.games");
+ map.put("Graphics & Design", "public.app-category.graphics-design");
+ map.put("Healthcare & Fitness",
+ "public.app-category.healthcare-fitness");
+ map.put("Lifestyle", "public.app-category.lifestyle");
+ map.put("Medical", "public.app-category.medical");
+ map.put("Music", "public.app-category.music");
+ map.put("News", "public.app-category.news");
+ map.put("Photography", "public.app-category.photography");
+ map.put("Productivity", "public.app-category.productivity");
+ map.put("Reference", "public.app-category.reference");
+ map.put("Social Networking", "public.app-category.social-networking");
+ map.put("Sports", "public.app-category.sports");
+ map.put("Travel", "public.app-category.travel");
+ map.put("Utilities", "public.app-category.utilities");
+ map.put("Video", "public.app-category.video");
+ map.put("Weather", "public.app-category.weather");
+
+ map.put("Action Games", "public.app-category.action-games");
+ map.put("Adventure Games", "public.app-category.adventure-games");
+ map.put("Arcade Games", "public.app-category.arcade-games");
+ map.put("Board Games", "public.app-category.board-games");
+ map.put("Card Games", "public.app-category.card-games");
+ map.put("Casino Games", "public.app-category.casino-games");
+ map.put("Dice Games", "public.app-category.dice-games");
+ map.put("Educational Games", "public.app-category.educational-games");
+ map.put("Family Games", "public.app-category.family-games");
+ map.put("Kids Games", "public.app-category.kids-games");
+ map.put("Music Games", "public.app-category.music-games");
+ map.put("Puzzle Games", "public.app-category.puzzle-games");
+ map.put("Racing Games", "public.app-category.racing-games");
+ map.put("Role Playing Games", "public.app-category.role-playing-games");
+ map.put("Simulation Games", "public.app-category.simulation-games");
+ map.put("Sports Games", "public.app-category.sports-games");
+ map.put("Strategy Games", "public.app-category.strategy-games");
+ map.put("Trivia Games", "public.app-category.trivia-games");
+ map.put("Word Games", "public.app-category.word-games");
+
+ return map;
+ }
+
+ public static final EnumeratedBundlerParam<String> MAC_CATEGORY =
+ new EnumeratedBundlerParam<>(
+ Arguments.CLIOptions.MAC_APP_STORE_CATEGORY.getId(),
+ String.class,
+ params -> "Unknown",
+ (s, p) -> s,
+ getMacCategories(),
+ false //strict - for MacStoreBundler this should be strict
+ );
+
+ public static final BundlerParamInfo<String> MAC_CF_BUNDLE_NAME =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAC_BUNDLE_NAME.getId(),
+ String.class,
+ params -> null,
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<String> MAC_CF_BUNDLE_VERSION =
+ new StandardBundlerParam<>(
+ "mac.CFBundleVersion",
+ String.class,
+ p -> {
+ String s = VERSION.fetchFrom(p);
+ if (validCFBundleVersion(s)) {
+ return s;
+ } else {
+ return "100";
+ }
+ },
+ (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<String> DEVELOPER_ID_APP_SIGNING_KEY =
+ new StandardBundlerParam<>(
+ "mac.signing-key-developer-id-app",
+ String.class,
+ params -> {
+ String result = MacBaseInstallerBundler.findKey(
+ "Developer ID Application: "
+ + SIGNING_KEY_USER.fetchFrom(params),
+ SIGNING_KEYCHAIN.fetchFrom(params),
+ VERBOSE.fetchFrom(params));
+ if (result != null) {
+ MacCertificate certificate = new MacCertificate(result,
+ VERBOSE.fetchFrom(params));
+
+ if (!certificate.isValid()) {
+ Log.error(MessageFormat.format(I18N.getString(
+ "error.certificate.expired"), result));
+ }
+ }
+
+ return result;
+ },
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<String> BUNDLE_ID_SIGNING_PREFIX =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAC_BUNDLE_SIGNING_PREFIX.getId(),
+ String.class,
+ params -> IDENTIFIER.fetchFrom(params) + ".",
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<File> ICON_ICNS =
+ new StandardBundlerParam<>(
+ "icon.icns",
+ File.class,
+ params -> {
+ File f = ICON.fetchFrom(params);
+ if (f != null && !f.getName().toLowerCase().endsWith(".icns")) {
+ Log.error(MessageFormat.format(
+ I18N.getString("message.icon-not-icns"), f));
+ return null;
+ }
+ return f;
+ },
+ (s, p) -> new File(s));
+
+ public static boolean validCFBundleVersion(String v) {
+ // CFBundleVersion (String - iOS, OS X) specifies the build version
+ // number of the bundle, which identifies an iteration (released or
+ // unreleased) of the bundle. The build version number should be a
+ // string comprised of three non-negative, period-separated integers
+ // with the first integer being greater than zero. The string should
+ // only contain numeric (0-9) and period (.) characters. Leading zeros
+ // are truncated from each integer and will be ignored (that is,
+ // 1.02.3 is equivalent to 1.2.3). This key is not localizable.
+
+ if (v == null) {
+ return false;
+ }
+
+ String p[] = v.split("\\.");
+ if (p.length > 3 || p.length < 1) {
+ Log.verbose(I18N.getString(
+ "message.version-string-too-many-components"));
+ return false;
+ }
+
+ try {
+ BigInteger n = new BigInteger(p[0]);
+ if (BigInteger.ONE.compareTo(n) > 0) {
+ Log.verbose(I18N.getString(
+ "message.version-string-first-number-not-zero"));
+ return false;
+ }
+ if (p.length > 1) {
+ n = new BigInteger(p[1]);
+ if (BigInteger.ZERO.compareTo(n) > 0) {
+ Log.verbose(I18N.getString(
+ "message.version-string-no-negative-numbers"));
+ return false;
+ }
+ }
+ if (p.length > 2) {
+ n = new BigInteger(p[2]);
+ if (BigInteger.ZERO.compareTo(n) > 0) {
+ Log.verbose(I18N.getString(
+ "message.version-string-no-negative-numbers"));
+ return false;
+ }
+ }
+ } catch (NumberFormatException ne) {
+ Log.verbose(I18N.getString("message.version-string-numbers-only"));
+ Log.verbose(ne);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+ try {
+ return doValidate(params);
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof ConfigException) {
+ throw (ConfigException) re.getCause();
+ } else {
+ throw new ConfigException(re);
+ }
+ }
+ }
+
+ private boolean doValidate(Map<String, ? super Object> params)
+ throws ConfigException {
+
+ imageBundleValidation(params);
+
+ if (StandardBundlerParam.getPredefinedAppImage(params) != null) {
+ return true;
+ }
+
+ // validate short version
+ if (!validCFBundleVersion(MAC_CF_BUNDLE_VERSION.fetchFrom(params))) {
+ throw new ConfigException(
+ I18N.getString("error.invalid-cfbundle-version"),
+ I18N.getString("error.invalid-cfbundle-version.advice"));
+ }
+
+ // reject explicitly set sign to true and no valid signature key
+ if (Optional.ofNullable(MacAppImageBuilder.
+ SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) {
+ String signingIdentity =
+ DEVELOPER_ID_APP_SIGNING_KEY.fetchFrom(params);
+ if (signingIdentity == null) {
+ throw new ConfigException(
+ I18N.getString("error.explicit-sign-no-cert"),
+ I18N.getString("error.explicit-sign-no-cert.advice"));
+ }
+
+ // Signing will not work without Xcode with command line developer tools
+ try {
+ ProcessBuilder pb = new ProcessBuilder("xcrun", "--help");
+ Process p = pb.start();
+ int code = p.waitFor();
+ if (code != 0) {
+ throw new ConfigException(
+ I18N.getString("error.no.xcode.signing"),
+ I18N.getString("error.no.xcode.signing.advice"));
+ }
+ } catch (IOException | InterruptedException ex) {
+ throw new ConfigException(ex);
+ }
+ }
+
+ return true;
+ }
+
+ File doBundle(Map<String, ? super Object> params, File outputDirectory,
+ boolean dependentTask) throws PackagerException {
+ if (StandardBundlerParam.isRuntimeInstaller(params)) {
+ return PREDEFINED_RUNTIME_IMAGE.fetchFrom(params);
+ } else {
+ return doAppBundle(params, outputDirectory, dependentTask);
+ }
+ }
+
+ File doAppBundle(Map<String, ? super Object> params, File outputDirectory,
+ boolean dependentTask) throws PackagerException {
+ try {
+ File rootDirectory = createRoot(params, outputDirectory,
+ dependentTask, APP_NAME.fetchFrom(params) + ".app");
+ AbstractAppImageBuilder appBuilder =
+ new MacAppImageBuilder(params, outputDirectory.toPath());
+ if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(params) == null ) {
+ JLinkBundlerHelper.execute(params, appBuilder);
+ } else {
+ StandardBundlerParam.copyPredefinedRuntimeImage(
+ params, appBuilder);
+ }
+ return rootDirectory;
+ } catch (PackagerException pe) {
+ throw pe;
+ } catch (Exception ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ /////////////////////////////////////////////////////////////////////////
+ // Implement Bundler
+ /////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public String getName() {
+ return I18N.getString("app.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "mac.app";
+ }
+
+ @Override
+ public String getBundleType() {
+ return "IMAGE";
+ }
+
+ @Override
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ return doBundle(params, outputParentDir, false);
+ }
+
+ @Override
+ public boolean supported(boolean runtimeInstaller) {
+ return true;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return false;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppImageBuilder.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,981 @@
+/*
+ * Copyright (c) 2015, 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.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;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.PosixFilePermission;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+import static jdk.jpackage.internal.MacBaseInstallerBundler.*;
+import static jdk.jpackage.internal.MacAppBundler.*;
+
+public class MacAppImageBuilder extends AbstractAppImageBuilder {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.MacResources");
+
+ private static final String LIBRARY_NAME = "libapplauncher.dylib";
+ private static final String TEMPLATE_BUNDLE_ICON = "java.icns";
+ private static final String OS_TYPE_CODE = "APPL";
+ private static final String TEMPLATE_INFO_PLIST_LITE =
+ "Info-lite.plist.template";
+ private static final String TEMPLATE_RUNTIME_INFO_PLIST =
+ "Runtime-Info.plist.template";
+
+ private final Path root;
+ private final Path contentsDir;
+ private final Path javaDir;
+ private final Path javaModsDir;
+ private final Path resourcesDir;
+ private final Path macOSDir;
+ private final Path runtimeDir;
+ private final Path runtimeRoot;
+ private final Path mdir;
+
+ private static List<String> keyChains;
+
+ public static final BundlerParamInfo<Boolean>
+ MAC_CONFIGURE_LAUNCHER_IN_PLIST = new StandardBundlerParam<>(
+ "mac.configure-launcher-in-plist",
+ Boolean.class,
+ params -> Boolean.FALSE,
+ (s, p) -> Boolean.valueOf(s));
+
+ public static final EnumeratedBundlerParam<String> MAC_CATEGORY =
+ new EnumeratedBundlerParam<>(
+ Arguments.CLIOptions.MAC_APP_STORE_CATEGORY.getId(),
+ String.class,
+ params -> "Unknown",
+ (s, p) -> s,
+ MacAppBundler.getMacCategories(),
+ false //strict - for MacStoreBundler this should be strict
+ );
+
+ public static final BundlerParamInfo<String> MAC_CF_BUNDLE_NAME =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAC_BUNDLE_NAME.getId(),
+ String.class,
+ params -> null,
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<String> MAC_CF_BUNDLE_IDENTIFIER =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAC_BUNDLE_IDENTIFIER.getId(),
+ String.class,
+ params -> {
+ // Get identifier from app image if user provided
+ // app image and did not provide the identifier via CLI.
+ String identifier = extractBundleIdentifier(params);
+ if (identifier != null) {
+ return identifier;
+ }
+
+ return IDENTIFIER.fetchFrom(params);
+ },
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<String> MAC_CF_BUNDLE_VERSION =
+ new StandardBundlerParam<>(
+ "mac.CFBundleVersion",
+ String.class,
+ p -> {
+ String s = VERSION.fetchFrom(p);
+ if (validCFBundleVersion(s)) {
+ return s;
+ } else {
+ return "100";
+ }
+ },
+ (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",
+ File.class,
+ params -> {
+ File f = ICON.fetchFrom(params);
+ if (f != null && !f.getName().toLowerCase().endsWith(".icns")) {
+ Log.error(MessageFormat.format(
+ I18N.getString("message.icon-not-icns"), f));
+ return null;
+ }
+ return f;
+ },
+ (s, p) -> new File(s));
+
+ public static final StandardBundlerParam<Boolean> SIGN_BUNDLE =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAC_SIGN.getId(),
+ Boolean.class,
+ params -> false,
+ // valueOf(null) is false, we actually do want null in some cases
+ (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ?
+ null : Boolean.valueOf(s)
+ );
+
+ public MacAppImageBuilder(Map<String, Object> params, Path imageOutDir)
+ throws IOException {
+ super(params, imageOutDir.resolve(APP_NAME.fetchFrom(params)
+ + ".app/Contents/runtime/Contents/Home"));
+
+ Objects.requireNonNull(imageOutDir);
+
+ this.root = imageOutDir.resolve(APP_NAME.fetchFrom(params) + ".app");
+ this.contentsDir = root.resolve("Contents");
+ this.javaDir = contentsDir.resolve("Java");
+ this.javaModsDir = javaDir.resolve("mods");
+ this.resourcesDir = contentsDir.resolve("Resources");
+ this.macOSDir = contentsDir.resolve("MacOS");
+ this.runtimeDir = contentsDir.resolve("runtime");
+ this.runtimeRoot = runtimeDir.resolve("Contents/Home");
+ this.mdir = runtimeRoot.resolve("lib");
+ Files.createDirectories(javaDir);
+ Files.createDirectories(resourcesDir);
+ Files.createDirectories(macOSDir);
+ Files.createDirectories(runtimeDir);
+ }
+
+ private void writeEntry(InputStream in, Path dstFile) throws IOException {
+ Files.createDirectories(dstFile.getParent());
+ Files.copy(in, dstFile);
+ }
+
+ public static boolean validCFBundleVersion(String v) {
+ // CFBundleVersion (String - iOS, OS X) specifies the build version
+ // number of the bundle, which identifies an iteration (released or
+ // unreleased) of the bundle. The build version number should be a
+ // string comprised of three non-negative, period-separated integers
+ // with the first integer being greater than zero. The string should
+ // only contain numeric (0-9) and period (.) characters. Leading zeros
+ // are truncated from each integer and will be ignored (that is,
+ // 1.02.3 is equivalent to 1.2.3). This key is not localizable.
+
+ if (v == null) {
+ return false;
+ }
+
+ String p[] = v.split("\\.");
+ if (p.length > 3 || p.length < 1) {
+ Log.verbose(I18N.getString(
+ "message.version-string-too-many-components"));
+ return false;
+ }
+
+ try {
+ BigInteger n = new BigInteger(p[0]);
+ if (BigInteger.ONE.compareTo(n) > 0) {
+ Log.verbose(I18N.getString(
+ "message.version-string-first-number-not-zero"));
+ return false;
+ }
+ if (p.length > 1) {
+ n = new BigInteger(p[1]);
+ if (BigInteger.ZERO.compareTo(n) > 0) {
+ Log.verbose(I18N.getString(
+ "message.version-string-no-negative-numbers"));
+ return false;
+ }
+ }
+ if (p.length > 2) {
+ n = new BigInteger(p[2]);
+ if (BigInteger.ZERO.compareTo(n) > 0) {
+ Log.verbose(I18N.getString(
+ "message.version-string-no-negative-numbers"));
+ return false;
+ }
+ }
+ } catch (NumberFormatException ne) {
+ Log.verbose(I18N.getString("message.version-string-numbers-only"));
+ Log.verbose(ne);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public Path getAppDir() {
+ return javaDir;
+ }
+
+ @Override
+ public Path getAppModsDir() {
+ return javaModsDir;
+ }
+
+ @Override
+ public void prepareApplicationFiles(Map<String, ? super Object> params)
+ throws IOException {
+ Map<String, ? super Object> originalParams = new HashMap<>(params);
+ // Generate PkgInfo
+ File pkgInfoFile = new File(contentsDir.toFile(), "PkgInfo");
+ pkgInfoFile.createNewFile();
+ writePkgInfo(pkgInfoFile);
+
+ Path executable = macOSDir.resolve(getLauncherName(params));
+
+ // create the main app launcher
+ try (InputStream is_launcher =
+ getResourceAsStream("jpackageapplauncher");
+ InputStream is_lib = getResourceAsStream(LIBRARY_NAME)) {
+ // Copy executable and library to MacOS folder
+ writeEntry(is_launcher, executable);
+ writeEntry(is_lib, macOSDir.resolve(LIBRARY_NAME));
+ }
+ executable.toFile().setExecutable(true, false);
+ // generate main app launcher config file
+ File cfg = new File(root.toFile(), getLauncherCfgName(params));
+ writeCfgFile(params, cfg);
+
+ // create additional app launcher(s) and config file(s)
+ List<Map<String, ? super Object>> entryPoints =
+ StandardBundlerParam.ADD_LAUNCHERS.fetchFrom(params);
+ for (Map<String, ? super Object> entryPoint : entryPoints) {
+ Map<String, ? super Object> tmp =
+ AddLauncherArguments.merge(originalParams, entryPoint);
+
+ // add executable for add launcher
+ Path addExecutable = macOSDir.resolve(getLauncherName(tmp));
+ try (InputStream is = getResourceAsStream("jpackageapplauncher");) {
+ writeEntry(is, addExecutable);
+ }
+ addExecutable.toFile().setExecutable(true, false);
+
+ // add config file for add launcher
+ cfg = new File(root.toFile(), getLauncherCfgName(tmp));
+ writeCfgFile(tmp, cfg);
+ }
+
+ // Copy class path entries to Java folder
+ copyClassPathEntries(javaDir, 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);
+
+ // copy file association icons
+ for (Map<String, ?
+ super Object> fa : FILE_ASSOCIATIONS.fetchFrom(params)) {
+ File f = FA_ICON.fetchFrom(fa);
+ if (f != null && f.exists()) {
+ try (InputStream in2 = new FileInputStream(f)) {
+ Files.copy(in2, resourcesDir.resolve(f.getName()));
+ }
+
+ }
+ }
+
+ copyRuntimeFiles(params);
+ sign(params);
+ }
+
+ @Override
+ public void prepareJreFiles(Map<String, ? super Object> params)
+ throws IOException {
+ copyRuntimeFiles(params);
+ sign(params);
+ }
+
+ @Override
+ File getRuntimeImageDir(File runtimeImageTop) {
+ File home = new File(runtimeImageTop, "Contents/Home");
+ return (home.exists() ? home : runtimeImageTop);
+ }
+
+ private void copyRuntimeFiles(Map<String, ? super Object> params)
+ throws IOException {
+ // Generate Info.plist
+ writeInfoPlist(contentsDir.resolve("Info.plist").toFile(), params);
+
+ // generate java runtime info.plist
+ writeRuntimeInfoPlist(
+ runtimeDir.resolve("Contents/Info.plist").toFile(), params);
+
+ // copy library
+ Path runtimeMacOSDir = Files.createDirectories(
+ runtimeDir.resolve("Contents/MacOS"));
+
+ // JDK 9, 10, and 11 have extra '/jli/' subdir
+ Path jli = runtimeRoot.resolve("lib/libjli.dylib");
+ if (!Files.exists(jli)) {
+ jli = runtimeRoot.resolve("lib/jli/libjli.dylib");
+ }
+
+ Files.copy(jli, runtimeMacOSDir.resolve("libjli.dylib"));
+ }
+
+ private void sign(Map<String, ? super Object> params) throws IOException {
+ if (Optional.ofNullable(
+ SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.TRUE)) {
+ try {
+ addNewKeychain(params);
+ } catch (InterruptedException e) {
+ Log.error(e.getMessage());
+ }
+ String signingIdentity =
+ DEVELOPER_ID_APP_SIGNING_KEY.fetchFrom(params);
+ if (signingIdentity != null) {
+ signAppBundle(params, root, signingIdentity,
+ BUNDLE_ID_SIGNING_PREFIX.fetchFrom(params), null, null);
+ }
+ restoreKeychainList(params);
+ }
+ }
+
+ private String getLauncherName(Map<String, ? super Object> params) {
+ if (APP_NAME.fetchFrom(params) != null) {
+ return APP_NAME.fetchFrom(params);
+ } else {
+ return MAIN_CLASS.fetchFrom(params);
+ }
+ }
+
+ public static String getLauncherCfgName(
+ Map<String, ? super Object> params) {
+ return "Contents/Java/" + APP_NAME.fetchFrom(params) + ".cfg";
+ }
+
+ private void copyClassPathEntries(Path javaDirectory,
+ Map<String, ? super Object> params) throws IOException {
+ List<RelativeFileSet> resourcesList =
+ APP_RESOURCES_LIST.fetchFrom(params);
+ if (resourcesList == null) {
+ throw new RuntimeException(
+ I18N.getString("message.null-classpath"));
+ }
+
+ for (RelativeFileSet classPath : resourcesList) {
+ File srcdir = classPath.getBaseDirectory();
+ for (String fname : classPath.getIncludedFiles()) {
+ copyEntry(javaDirectory, srcdir, fname);
+ }
+ }
+ }
+
+ private String getBundleName(Map<String, ? super Object> params) {
+ if (MAC_CF_BUNDLE_NAME.fetchFrom(params) != null) {
+ String bn = MAC_CF_BUNDLE_NAME.fetchFrom(params);
+ if (bn.length() > 16) {
+ Log.error(MessageFormat.format(I18N.getString(
+ "message.bundle-name-too-long-warning"),
+ MAC_CF_BUNDLE_NAME.getID(), bn));
+ }
+ return MAC_CF_BUNDLE_NAME.fetchFrom(params);
+ } else if (APP_NAME.fetchFrom(params) != null) {
+ return APP_NAME.fetchFrom(params);
+ } else {
+ String nm = MAIN_CLASS.fetchFrom(params);
+ if (nm.length() > 16) {
+ nm = nm.substring(0, 16);
+ }
+ return nm;
+ }
+ }
+
+ private void writeRuntimeInfoPlist(File file,
+ Map<String, ? super Object> params) throws IOException {
+ Map<String, String> data = new HashMap<>();
+ String identifier = StandardBundlerParam.isRuntimeInstaller(params) ?
+ MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params) :
+ "com.oracle.java." + MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params);
+ data.put("CF_BUNDLE_IDENTIFIER", identifier);
+ String name = StandardBundlerParam.isRuntimeInstaller(params) ?
+ getBundleName(params): "Java Runtime Image";
+ data.put("CF_BUNDLE_NAME", name);
+ 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)));
+ }
+ }
+
+ private void writeInfoPlist(File file, Map<String, ? super Object> params)
+ throws IOException {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.preparing-info-plist"), file.getAbsolutePath()));
+
+ //prepare config for exe
+ //Note: do not need CFBundleDisplayName if we don't support localization
+ Map<String, String> data = new HashMap<>();
+ data.put("DEPLOY_ICON_FILE", APP_NAME.fetchFrom(params) + ".icns");
+ data.put("DEPLOY_BUNDLE_IDENTIFIER",
+ MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params));
+ data.put("DEPLOY_BUNDLE_NAME",
+ getBundleName(params));
+ data.put("DEPLOY_BUNDLE_COPYRIGHT",
+ COPYRIGHT.fetchFrom(params) != null ?
+ COPYRIGHT.fetchFrom(params) : "Unknown");
+ data.put("DEPLOY_LAUNCHER_NAME", getLauncherName(params));
+ data.put("DEPLOY_JAVA_RUNTIME_NAME", getCfgRuntimeDir());
+ data.put("DEPLOY_BUNDLE_SHORT_VERSION",
+ VERSION.fetchFrom(params) != null ?
+ VERSION.fetchFrom(params) : "1.0.0");
+ data.put("DEPLOY_BUNDLE_CFBUNDLE_VERSION",
+ MAC_CF_BUNDLE_VERSION.fetchFrom(params) != null ?
+ MAC_CF_BUNDLE_VERSION.fetchFrom(params) : "100");
+ data.put("DEPLOY_BUNDLE_CATEGORY", MAC_CATEGORY.fetchFrom(params));
+
+ boolean hasMainJar = MAIN_JAR.fetchFrom(params) != null;
+ boolean hasMainModule =
+ StandardBundlerParam.MODULE.fetchFrom(params) != null;
+
+ if (hasMainJar) {
+ data.put("DEPLOY_MAIN_JAR_NAME", MAIN_JAR.fetchFrom(params).
+ getIncludedFiles().iterator().next());
+ }
+ else if (hasMainModule) {
+ data.put("DEPLOY_MODULE_NAME",
+ StandardBundlerParam.MODULE.fetchFrom(params));
+ }
+
+ StringBuilder sb = new StringBuilder();
+ List<String> jvmOptions = JAVA_OPTIONS.fetchFrom(params);
+
+ String newline = ""; //So we don't add extra line after last append
+ for (String o : jvmOptions) {
+ sb.append(newline).append(
+ " <string>").append(o).append("</string>");
+ newline = "\n";
+ }
+
+ data.put("DEPLOY_JAVA_OPTIONS", sb.toString());
+
+ sb = new StringBuilder();
+ List<String> args = ARGUMENTS.fetchFrom(params);
+ newline = "";
+ // So we don't add unneccessary extra line after last append
+
+ for (String o : args) {
+ sb.append(newline).append(" <string>").append(o).append(
+ "</string>");
+ newline = "\n";
+ }
+ data.put("DEPLOY_ARGUMENTS", sb.toString());
+
+ newline = "";
+
+ data.put("DEPLOY_LAUNCHER_CLASS", MAIN_CLASS.fetchFrom(params));
+
+ data.put("DEPLOY_APP_CLASSPATH",
+ getCfgClassPath(CLASSPATH.fetchFrom(params)));
+
+ StringBuilder bundleDocumentTypes = new StringBuilder();
+ StringBuilder exportedTypes = new StringBuilder();
+ for (Map<String, ? super Object>
+ fileAssociation : FILE_ASSOCIATIONS.fetchFrom(params)) {
+
+ List<String> extensions = FA_EXTENSIONS.fetchFrom(fileAssociation);
+
+ if (extensions == null) {
+ Log.verbose(I18N.getString(
+ "message.creating-association-with-null-extension"));
+ }
+
+ List<String> mimeTypes = FA_CONTENT_TYPE.fetchFrom(fileAssociation);
+ String itemContentType = MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params)
+ + "." + ((extensions == null || extensions.isEmpty())
+ ? "mime" : extensions.get(0));
+ String description = FA_DESCRIPTION.fetchFrom(fileAssociation);
+ File icon = FA_ICON.fetchFrom(fileAssociation);
+
+ bundleDocumentTypes.append(" <dict>\n")
+ .append(" <key>LSItemContentTypes</key>\n")
+ .append(" <array>\n")
+ .append(" <string>")
+ .append(itemContentType)
+ .append("</string>\n")
+ .append(" </array>\n")
+ .append("\n")
+ .append(" <key>CFBundleTypeName</key>\n")
+ .append(" <string>")
+ .append(description)
+ .append("</string>\n")
+ .append("\n")
+ .append(" <key>LSHandlerRank</key>\n")
+ .append(" <string>Owner</string>\n")
+ // TODO make a bundler arg
+ .append("\n")
+ .append(" <key>CFBundleTypeRole</key>\n")
+ .append(" <string>Editor</string>\n")
+ // TODO make a bundler arg
+ .append("\n")
+ .append(" <key>LSIsAppleDefaultForType</key>\n")
+ .append(" <true/>\n")
+ // TODO make a bundler arg
+ .append("\n");
+
+ if (icon != null && icon.exists()) {
+ bundleDocumentTypes
+ .append(" <key>CFBundleTypeIconFile</key>\n")
+ .append(" <string>")
+ .append(icon.getName())
+ .append("</string>\n");
+ }
+ bundleDocumentTypes.append(" </dict>\n");
+
+ exportedTypes.append(" <dict>\n")
+ .append(" <key>UTTypeIdentifier</key>\n")
+ .append(" <string>")
+ .append(itemContentType)
+ .append("</string>\n")
+ .append("\n")
+ .append(" <key>UTTypeDescription</key>\n")
+ .append(" <string>")
+ .append(description)
+ .append("</string>\n")
+ .append(" <key>UTTypeConformsTo</key>\n")
+ .append(" <array>\n")
+ .append(" <string>public.data</string>\n")
+ //TODO expose this?
+ .append(" </array>\n")
+ .append("\n");
+
+ if (icon != null && icon.exists()) {
+ exportedTypes.append(" <key>UTTypeIconFile</key>\n")
+ .append(" <string>")
+ .append(icon.getName())
+ .append("</string>\n")
+ .append("\n");
+ }
+
+ exportedTypes.append("\n")
+ .append(" <key>UTTypeTagSpecification</key>\n")
+ .append(" <dict>\n")
+ // TODO expose via param? .append(
+ // " <key>com.apple.ostype</key>\n");
+ // TODO expose via param? .append(
+ // " <string>ABCD</string>\n")
+ .append("\n");
+
+ if (extensions != null && !extensions.isEmpty()) {
+ exportedTypes.append(
+ " <key>public.filename-extension</key>\n")
+ .append(" <array>\n");
+
+ for (String ext : extensions) {
+ exportedTypes.append(" <string>")
+ .append(ext)
+ .append("</string>\n");
+ }
+ exportedTypes.append(" </array>\n");
+ }
+ if (mimeTypes != null && !mimeTypes.isEmpty()) {
+ exportedTypes.append(" <key>public.mime-type</key>\n")
+ .append(" <array>\n");
+
+ for (String mime : mimeTypes) {
+ exportedTypes.append(" <string>")
+ .append(mime)
+ .append("</string>\n");
+ }
+ exportedTypes.append(" </array>\n");
+ }
+ exportedTypes.append(" </dict>\n")
+ .append(" </dict>\n");
+ }
+ String associationData;
+ if (bundleDocumentTypes.length() > 0) {
+ associationData =
+ "\n <key>CFBundleDocumentTypes</key>\n <array>\n"
+ + bundleDocumentTypes.toString()
+ + " </array>\n\n"
+ + " <key>UTExportedTypeDeclarations</key>\n <array>\n"
+ + exportedTypes.toString()
+ + " </array>\n";
+ } else {
+ associationData = "";
+ }
+ 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)));
+ }
+ }
+
+ private void writePkgInfo(File file) throws IOException {
+ //hardcoded as it does not seem we need to change it ever
+ String signature = "????";
+
+ try (Writer out = Files.newBufferedWriter(file.toPath())) {
+ out.write(OS_TYPE_CODE + signature);
+ out.flush();
+ }
+ }
+
+ public static void addNewKeychain(Map<String, ? super Object> params)
+ throws IOException, InterruptedException {
+ if (Platform.getMajorVersion() < 10 ||
+ (Platform.getMajorVersion() == 10 &&
+ Platform.getMinorVersion() < 12)) {
+ // we need this for OS X 10.12+
+ return;
+ }
+
+ String keyChain = SIGNING_KEYCHAIN.fetchFrom(params);
+ if (keyChain == null || keyChain.isEmpty()) {
+ return;
+ }
+
+ // get current keychain list
+ String keyChainPath = new File (keyChain).getAbsolutePath().toString();
+ List<String> keychainList = new ArrayList<>();
+ int ret = IOUtils.getProcessOutput(
+ keychainList, "security", "list-keychains");
+ if (ret != 0) {
+ Log.error(I18N.getString("message.keychain.error"));
+ return;
+ }
+
+ boolean contains = keychainList.stream().anyMatch(
+ str -> str.trim().equals("\""+keyChainPath.trim()+"\""));
+ if (contains) {
+ // keychain is already added in the search list
+ return;
+ }
+
+ keyChains = new ArrayList<>();
+ // remove "
+ keychainList.forEach((String s) -> {
+ String path = s.trim();
+ if (path.startsWith("\"") && path.endsWith("\"")) {
+ path = path.substring(1, path.length()-1);
+ }
+ keyChains.add(path);
+ });
+
+ List<String> args = new ArrayList<>();
+ args.add("security");
+ args.add("list-keychains");
+ args.add("-s");
+
+ args.addAll(keyChains);
+ args.add(keyChain);
+
+ ProcessBuilder pb = new ProcessBuilder(args);
+ IOUtils.exec(pb);
+ }
+
+ public static void restoreKeychainList(Map<String, ? super Object> params)
+ throws IOException{
+ if (Platform.getMajorVersion() < 10 ||
+ (Platform.getMajorVersion() == 10 &&
+ Platform.getMinorVersion() < 12)) {
+ // we need this for OS X 10.12+
+ return;
+ }
+
+ if (keyChains == null || keyChains.isEmpty()) {
+ return;
+ }
+
+ List<String> args = new ArrayList<>();
+ args.add("security");
+ args.add("list-keychains");
+ args.add("-s");
+
+ args.addAll(keyChains);
+
+ ProcessBuilder pb = new ProcessBuilder(args);
+ IOUtils.exec(pb);
+ }
+
+ public static void signAppBundle(
+ Map<String, ? super Object> params, Path appLocation,
+ String signingIdentity, String identifierPrefix,
+ String entitlementsFile, String inheritedEntitlements)
+ throws IOException {
+ AtomicReference<IOException> toThrow = new AtomicReference<>();
+ String appExecutable = "/Contents/MacOS/" + APP_NAME.fetchFrom(params);
+ String keyChain = SIGNING_KEYCHAIN.fetchFrom(params);
+
+ // sign all dylibs and jars
+ Files.walk(appLocation).peek(path -> { // fix permissions
+ try {
+ Set<PosixFilePermission> pfp =
+ Files.getPosixFilePermissions(path);
+ if (!pfp.contains(PosixFilePermission.OWNER_WRITE)) {
+ pfp = EnumSet.copyOf(pfp);
+ pfp.add(PosixFilePermission.OWNER_WRITE);
+ Files.setPosixFilePermissions(path, pfp);
+ }
+ } catch (IOException e) {
+ Log.verbose(e);
+ }
+ }).filter(p -> Files.isRegularFile(p)
+ && !(p.toString().contains("/Contents/MacOS/libjli.dylib")
+ || p.toString().endsWith(appExecutable)
+ || p.toString().contains("/Contents/runtime")
+ || p.toString().contains("/Contents/Frameworks"))).forEach(p -> {
+ //noinspection ThrowableResultOfMethodCallIgnored
+ if (toThrow.get() != null) return;
+
+ // If p is a symlink then skip the signing process.
+ if (Files.isSymbolicLink(p)) {
+ if (VERBOSE.fetchFrom(params)) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.ignoring.symlink"), p.toString()));
+ }
+ } else {
+ if (p.toString().endsWith(LIBRARY_NAME)) {
+ if (isFileSigned(p)) {
+ return;
+ }
+ }
+
+ List<String> args = new ArrayList<>();
+ args.addAll(Arrays.asList("codesign",
+ "-s", signingIdentity, // sign with this key
+ "--prefix", identifierPrefix,
+ // use the identifier as a prefix
+ "-vvvv"));
+ if (entitlementsFile != null &&
+ (p.toString().endsWith(".jar")
+ || p.toString().endsWith(".dylib"))) {
+ args.add("--entitlements");
+ args.add(entitlementsFile); // entitlements
+ } else if (inheritedEntitlements != null &&
+ Files.isExecutable(p)) {
+ args.add("--entitlements");
+ args.add(inheritedEntitlements);
+ // inherited entitlements for executable processes
+ }
+ if (keyChain != null && !keyChain.isEmpty()) {
+ args.add("--keychain");
+ args.add(keyChain);
+ }
+ args.add(p.toString());
+
+ try {
+ Set<PosixFilePermission> oldPermissions =
+ Files.getPosixFilePermissions(p);
+ File f = p.toFile();
+ f.setWritable(true, true);
+
+ ProcessBuilder pb = new ProcessBuilder(args);
+ IOUtils.exec(pb);
+
+ Files.setPosixFilePermissions(p, oldPermissions);
+ } catch (IOException ioe) {
+ toThrow.set(ioe);
+ }
+ }
+ });
+
+ IOException ioe = toThrow.get();
+ if (ioe != null) {
+ throw ioe;
+ }
+
+ // sign all runtime and frameworks
+ Consumer<? super Path> signIdentifiedByPList = path -> {
+ //noinspection ThrowableResultOfMethodCallIgnored
+ if (toThrow.get() != null) return;
+
+ try {
+ List<String> args = new ArrayList<>();
+ args.addAll(Arrays.asList("codesign",
+ "-s", signingIdentity, // sign with this key
+ "--prefix", identifierPrefix,
+ // use the identifier as a prefix
+ "-vvvv"));
+ if (keyChain != null && !keyChain.isEmpty()) {
+ args.add("--keychain");
+ args.add(keyChain);
+ }
+ args.add(path.toString());
+ ProcessBuilder pb = new ProcessBuilder(args);
+ IOUtils.exec(pb);
+
+ args = new ArrayList<>();
+ args.addAll(Arrays.asList("codesign",
+ "-s", signingIdentity, // sign with this key
+ "--prefix", identifierPrefix,
+ // use the identifier as a prefix
+ "-vvvv"));
+ if (keyChain != null && !keyChain.isEmpty()) {
+ args.add("--keychain");
+ args.add(keyChain);
+ }
+ args.add(path.toString()
+ + "/Contents/_CodeSignature/CodeResources");
+ pb = new ProcessBuilder(args);
+ IOUtils.exec(pb);
+ } catch (IOException e) {
+ toThrow.set(e);
+ }
+ };
+
+ Path javaPath = appLocation.resolve("Contents/runtime");
+ if (Files.isDirectory(javaPath)) {
+ signIdentifiedByPList.accept(javaPath);
+
+ ioe = toThrow.get();
+ if (ioe != null) {
+ throw ioe;
+ }
+ }
+ Path frameworkPath = appLocation.resolve("Contents/Frameworks");
+ if (Files.isDirectory(frameworkPath)) {
+ Files.list(frameworkPath)
+ .forEach(signIdentifiedByPList);
+
+ ioe = toThrow.get();
+ if (ioe != null) {
+ throw ioe;
+ }
+ }
+
+ // sign the app itself
+ List<String> args = new ArrayList<>();
+ args.addAll(Arrays.asList("codesign",
+ "-s", signingIdentity, // sign with this key
+ "-vvvv")); // super verbose output
+ if (entitlementsFile != null) {
+ args.add("--entitlements");
+ args.add(entitlementsFile); // entitlements
+ }
+ if (keyChain != null && !keyChain.isEmpty()) {
+ args.add("--keychain");
+ args.add(keyChain);
+ }
+ args.add(appLocation.toString());
+
+ ProcessBuilder pb =
+ new ProcessBuilder(args.toArray(new String[args.size()]));
+ IOUtils.exec(pb);
+ }
+
+ private static boolean isFileSigned(Path file) {
+ ProcessBuilder pb =
+ new ProcessBuilder("codesign", "--verify", file.toString());
+
+ try {
+ IOUtils.exec(pb);
+ } catch (IOException ex) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private static String extractBundleIdentifier(Map<String, Object> params) {
+ if (PREDEFINED_APP_IMAGE.fetchFrom(params) == null) {
+ return null;
+ }
+
+ try {
+ File infoPList = new File(PREDEFINED_APP_IMAGE.fetchFrom(params) +
+ File.separator + "Contents" +
+ File.separator + "Info.plist");
+
+ DocumentBuilderFactory dbf
+ = DocumentBuilderFactory.newDefaultInstance();
+ dbf.setFeature("http://apache.org/xml/features/" +
+ "nonvalidating/load-external-dtd", false);
+ DocumentBuilder b = dbf.newDocumentBuilder();
+ org.w3c.dom.Document doc = b.parse(new FileInputStream(
+ infoPList.getAbsolutePath()));
+
+ XPath xPath = XPathFactory.newInstance().newXPath();
+ // Query for the value of <string> element preceding <key>
+ // element with value equal to CFBundleIdentifier
+ String v = (String) xPath.evaluate(
+ "//string[preceding-sibling::key = \"CFBundleIdentifier\"][1]",
+ doc, XPathConstants.STRING);
+
+ if (v != null && !v.isEmpty()) {
+ return v;
+ }
+ } catch (Exception ex) {
+ Log.verbose(ex);
+ }
+
+ return null;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppStoreBundler.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,332 @@
+/*
+ * Copyright (c) 2014, 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.File;
+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;
+import java.util.ResourceBundle;
+
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+import static jdk.jpackage.internal.MacAppBundler.*;
+
+public class MacAppStoreBundler extends MacBaseInstallerBundler {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.MacResources");
+
+ private static final String TEMPLATE_BUNDLE_ICON_HIDPI = "java.icns";
+ private final static String DEFAULT_ENTITLEMENTS =
+ "MacAppStore.entitlements";
+ private final static String DEFAULT_INHERIT_ENTITLEMENTS =
+ "MacAppStore_Inherit.entitlements";
+
+ public static final BundlerParamInfo<String> MAC_APP_STORE_APP_SIGNING_KEY =
+ new StandardBundlerParam<>(
+ "mac.signing-key-app",
+ String.class,
+ params -> {
+ String result = MacBaseInstallerBundler.findKey(
+ "3rd Party Mac Developer Application: " +
+ SIGNING_KEY_USER.fetchFrom(params),
+ SIGNING_KEYCHAIN.fetchFrom(params),
+ VERBOSE.fetchFrom(params));
+ if (result != null) {
+ MacCertificate certificate = new MacCertificate(result,
+ VERBOSE.fetchFrom(params));
+
+ if (!certificate.isValid()) {
+ Log.error(MessageFormat.format(
+ I18N.getString("error.certificate.expired"),
+ result));
+ }
+ }
+
+ return result;
+ },
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<String> MAC_APP_STORE_PKG_SIGNING_KEY =
+ new StandardBundlerParam<>(
+ "mac.signing-key-pkg",
+ String.class,
+ params -> {
+ String result = MacBaseInstallerBundler.findKey(
+ "3rd Party Mac Developer Installer: " +
+ SIGNING_KEY_USER.fetchFrom(params),
+ SIGNING_KEYCHAIN.fetchFrom(params),
+ VERBOSE.fetchFrom(params));
+
+ if (result != null) {
+ MacCertificate certificate = new MacCertificate(
+ result, VERBOSE.fetchFrom(params));
+
+ if (!certificate.isValid()) {
+ Log.error(MessageFormat.format(
+ I18N.getString("error.certificate.expired"),
+ result));
+ }
+ }
+
+ return result;
+ },
+ (s, p) -> s);
+
+ public static final StandardBundlerParam<File> MAC_APP_STORE_ENTITLEMENTS =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAC_APP_STORE_ENTITLEMENTS.getId(),
+ File.class,
+ params -> null,
+ (s, p) -> new File(s));
+
+ public static final BundlerParamInfo<String> INSTALLER_SUFFIX =
+ new StandardBundlerParam<> (
+ "mac.app-store.installerName.suffix",
+ String.class,
+ params -> "-MacAppStore",
+ (s, p) -> s);
+
+ public File bundle(Map<String, ? super Object> params,
+ File outdir) throws PackagerException {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.building-bundle"), APP_NAME.fetchFrom(params)));
+
+ IOUtils.writableOutputDir(outdir.toPath());
+
+ // first, load in some overrides
+ // icns needs @2 versions, so load in the @2 default
+ params.put(DEFAULT_ICNS_ICON.getID(), TEMPLATE_BUNDLE_ICON_HIDPI);
+
+ // now we create the app
+ File appImageDir = APP_IMAGE_TEMP_ROOT.fetchFrom(params);
+ try {
+ appImageDir.mkdirs();
+
+ try {
+ MacAppImageBuilder.addNewKeychain(params);
+ } catch (InterruptedException e) {
+ Log.error(e.getMessage());
+ }
+ // first, make sure we don't use the local signing key
+ params.put(DEVELOPER_ID_APP_SIGNING_KEY.getID(), null);
+ File appLocation = prepareAppBundle(params);
+
+ prepareEntitlements(params);
+
+ String signingIdentity =
+ MAC_APP_STORE_APP_SIGNING_KEY.fetchFrom(params);
+ String identifierPrefix =
+ BUNDLE_ID_SIGNING_PREFIX.fetchFrom(params);
+ String entitlementsFile =
+ getConfig_Entitlements(params).toString();
+ String inheritEntitlements =
+ getConfig_Inherit_Entitlements(params).toString();
+
+ MacAppImageBuilder.signAppBundle(params, appLocation.toPath(),
+ signingIdentity, identifierPrefix,
+ entitlementsFile, inheritEntitlements);
+ MacAppImageBuilder.restoreKeychainList(params);
+
+ ProcessBuilder pb;
+
+ // create the final pkg file
+ File finalPKG = new File(outdir, INSTALLER_NAME.fetchFrom(params)
+ + INSTALLER_SUFFIX.fetchFrom(params)
+ + ".pkg");
+ outdir.mkdirs();
+
+ String installIdentify =
+ MAC_APP_STORE_PKG_SIGNING_KEY.fetchFrom(params);
+
+ List<String> buildOptions = new ArrayList<>();
+ buildOptions.add("productbuild");
+ buildOptions.add("--component");
+ buildOptions.add(appLocation.toString());
+ buildOptions.add("/Applications");
+ buildOptions.add("--sign");
+ buildOptions.add(installIdentify);
+ buildOptions.add("--product");
+ buildOptions.add(appLocation + "/Contents/Info.plist");
+ String keychainName = SIGNING_KEYCHAIN.fetchFrom(params);
+ if (keychainName != null && !keychainName.isEmpty()) {
+ buildOptions.add("--keychain");
+ buildOptions.add(keychainName);
+ }
+ buildOptions.add(finalPKG.getAbsolutePath());
+
+ pb = new ProcessBuilder(buildOptions);
+
+ IOUtils.exec(pb);
+ return finalPKG;
+ } catch (PackagerException pe) {
+ throw pe;
+ } catch (Exception ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ private File getConfig_Entitlements(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + ".entitlements");
+ }
+
+ private File getConfig_Inherit_Entitlements(
+ Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "_Inherit.entitlements");
+ }
+
+ 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));
+ }
+
+ 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
+ ///////////////////////////////////////////////////////////////////////
+
+ @Override
+ public String getName() {
+ return I18N.getString("store.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "mac.appStore";
+ }
+
+ @Override
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+ try {
+ if (params == null) {
+ throw new ConfigException(
+ I18N.getString("error.parameters-null"),
+ I18N.getString("error.parameters-null.advice"));
+ }
+
+ // hdiutil is always available so there's no need to test for
+ // availability.
+ // run basic validation to ensure requirements are met
+
+ // we are not interested in return code, only possible exception
+ validateAppImageAndBundeler(params);
+
+ // reject explicitly set to not sign
+ if (!Optional.ofNullable(MacAppImageBuilder.
+ SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.TRUE)) {
+ throw new ConfigException(
+ I18N.getString("error.must-sign-app-store"),
+ I18N.getString("error.must-sign-app-store.advice"));
+ }
+
+ // make sure we have settings for signatures
+ if (MAC_APP_STORE_APP_SIGNING_KEY.fetchFrom(params) == null) {
+ throw new ConfigException(
+ I18N.getString("error.no-app-signing-key"),
+ I18N.getString("error.no-app-signing-key.advice"));
+ }
+ if (MAC_APP_STORE_PKG_SIGNING_KEY.fetchFrom(params) == null) {
+ throw new ConfigException(
+ I18N.getString("error.no-pkg-signing-key"),
+ I18N.getString("error.no-pkg-signing-key.advice"));
+ }
+
+ // things we could check...
+ // check the icons, make sure it has hidpi icons
+ // check the category,
+ // make sure it fits in the list apple has provided
+ // validate bundle identifier is reverse dns
+ // check for \a+\.\a+\..
+
+ return true;
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof ConfigException) {
+ throw (ConfigException) re.getCause();
+ } else {
+ throw new ConfigException(re);
+ }
+ }
+ }
+
+ @Override
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ return bundle(params, outputParentDir);
+ }
+
+ @Override
+ public boolean supported(boolean runtimeInstaller) {
+ // return (!runtimeInstaller &&
+ // Platform.getPlatform() == Platform.MAC);
+ return false; // mac-app-store not yet supported
+ }
+
+ @Override
+ public boolean isDefault() {
+ return false;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBaseInstallerBundler.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2014, 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.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+
+public abstract class MacBaseInstallerBundler extends AbstractBundler {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.MacResources");
+
+ // This could be generalized more to be for any type of Image Bundler
+ public static final BundlerParamInfo<MacAppBundler> APP_BUNDLER =
+ new StandardBundlerParam<>(
+ "mac.app.bundler",
+ MacAppBundler.class,
+ params -> new MacAppBundler(),
+ (s, p) -> null);
+
+ public final BundlerParamInfo<File> APP_IMAGE_TEMP_ROOT =
+ new StandardBundlerParam<>(
+ "mac.app.imageRoot",
+ File.class,
+ params -> {
+ File imageDir = IMAGES_ROOT.fetchFrom(params);
+ if (!imageDir.exists()) imageDir.mkdirs();
+ try {
+ return Files.createTempDirectory(
+ imageDir.toPath(), "image-").toFile();
+ } catch (IOException e) {
+ return new File(imageDir, getID()+ ".image");
+ }
+ },
+ (s, p) -> new File(s));
+
+ public static final BundlerParamInfo<String> SIGNING_KEY_USER =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAC_SIGNING_KEY_NAME.getId(),
+ String.class,
+ params -> "",
+ null);
+
+ public static final BundlerParamInfo<String> SIGNING_KEYCHAIN =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAC_SIGNING_KEYCHAIN.getId(),
+ String.class,
+ params -> "",
+ null);
+
+ public static final BundlerParamInfo<String> INSTALLER_NAME =
+ new StandardBundlerParam<> (
+ "mac.installerName",
+ String.class,
+ params -> {
+ String nm = APP_NAME.fetchFrom(params);
+ if (nm == null) return null;
+
+ String version = VERSION.fetchFrom(params);
+ if (version == null) {
+ return nm;
+ } else {
+ return nm + "-" + version;
+ }
+ },
+ (s, p) -> s);
+
+ protected void validateAppImageAndBundeler(
+ Map<String, ? super Object> params) throws ConfigException {
+ if (PREDEFINED_APP_IMAGE.fetchFrom(params) != null) {
+ File applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(params);
+ if (!applicationImage.exists()) {
+ throw new ConfigException(
+ MessageFormat.format(I18N.getString(
+ "message.app-image-dir-does-not-exist"),
+ PREDEFINED_APP_IMAGE.getID(),
+ applicationImage.toString()),
+ MessageFormat.format(I18N.getString(
+ "message.app-image-dir-does-not-exist.advice"),
+ PREDEFINED_APP_IMAGE.getID()));
+ }
+ if (APP_NAME.fetchFrom(params) == null) {
+ throw new ConfigException(
+ I18N.getString("message.app-image-requires-app-name"),
+ I18N.getString(
+ "message.app-image-requires-app-name.advice"));
+ }
+ } else {
+ APP_BUNDLER.fetchFrom(params).validate(params);
+ }
+ }
+
+ protected File prepareAppBundle(Map<String, ? super Object> params)
+ throws PackagerException {
+ File predefinedImage =
+ StandardBundlerParam.getPredefinedAppImage(params);
+ if (predefinedImage != null) {
+ return predefinedImage;
+ }
+ File appImageRoot = APP_IMAGE_TEMP_ROOT.fetchFrom(params);
+
+ return APP_BUNDLER.fetchFrom(params).doBundle(
+ params, appImageRoot, true);
+ }
+
+ @Override
+ public String getBundleType() {
+ return "INSTALLER";
+ }
+
+ public static String findKey(String key, String keychainName,
+ boolean verbose) {
+ if (Platform.getPlatform() != Platform.MAC) {
+ return null;
+ }
+
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos)) {
+ List<String> searchOptions = new ArrayList<>();
+ searchOptions.add("security");
+ searchOptions.add("find-certificate");
+ searchOptions.add("-c");
+ searchOptions.add(key);
+ searchOptions.add("-a");
+ if (keychainName != null && !keychainName.isEmpty()) {
+ searchOptions.add(keychainName);
+ }
+
+ ProcessBuilder pb = new ProcessBuilder(searchOptions);
+
+ IOUtils.exec(pb, false, ps);
+ Pattern p = Pattern.compile("\"alis\"<blob>=\"([^\"]+)\"");
+ Matcher m = p.matcher(baos.toString());
+ if (!m.find()) {
+ Log.error("Did not find a key matching '" + key + "'");
+ return null;
+ }
+ String matchedKey = m.group(1);
+ if (m.find()) {
+ Log.error("Found more than one key matching '" + key + "'");
+ return null;
+ }
+ Log.verbose("Using key '" + matchedKey + "'");
+ return matchedKey;
+ } catch (IOException ioe) {
+ Log.verbose(ioe);
+ return null;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacCertificate.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2016, 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.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.Files;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+final class MacCertificate {
+ private final String certificate;
+ private final boolean verbose;
+
+ MacCertificate(String certificate) {
+ this.certificate = certificate;
+ this.verbose = false;
+ }
+
+ MacCertificate(String certificate, boolean verbose) {
+ this.certificate = certificate;
+ this.verbose = verbose;
+ }
+
+ boolean isValid() {
+ return verifyCertificate(this.certificate, verbose);
+ }
+
+ private static File findCertificate(String certificate, boolean verbose) {
+ File result = null;
+
+ List<String> args = new ArrayList<>();
+ args.add("security");
+ args.add("find-certificate");
+ args.add("-c");
+ args.add(certificate);
+ args.add("-a");
+ args.add("-p");
+
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos)) {
+ ProcessBuilder security = new ProcessBuilder(args);
+ IOUtils.exec(security, false, ps);
+
+ File output = File.createTempFile("tempfile", ".tmp");
+
+ Files.copy(new ByteArrayInputStream(baos.toByteArray()),
+ output.toPath(), StandardCopyOption.REPLACE_EXISTING);
+
+ result = output;
+ }
+ catch (IOException ignored) {}
+
+ return result;
+ }
+
+ private static Date findCertificateDate(String filename, boolean verbose) {
+ Date result = null;
+
+ List<String> args = new ArrayList<>();
+ args.add("/usr/bin/openssl");
+ args.add("x509");
+ args.add("-noout");
+ args.add("-enddate");
+ args.add("-in");
+ args.add(filename);
+
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(baos)) {
+ ProcessBuilder security = new ProcessBuilder(args);
+ IOUtils.exec(security, false, ps);
+ String output = baos.toString();
+ output = output.substring(output.indexOf("=") + 1);
+ DateFormat df = new SimpleDateFormat(
+ "MMM dd kk:mm:ss yyyy z", Locale.ENGLISH);
+ result = df.parse(output);
+ } catch (IOException | ParseException ex) {
+ Log.verbose(ex);
+ }
+
+ return result;
+ }
+
+ private static boolean verifyCertificate(
+ String certificate, boolean verbose) {
+ boolean result = false;
+
+ try {
+ File file = null;
+ Date certificateDate = null;
+
+ try {
+ file = findCertificate(certificate, verbose);
+
+ if (file != null) {
+ certificateDate = findCertificateDate(
+ file.getCanonicalPath(), verbose);
+ }
+ }
+ finally {
+ if (file != null) {
+ file.delete();
+ }
+ }
+
+ if (certificateDate != null) {
+ Calendar c = Calendar.getInstance();
+ Date today = c.getTime();
+
+ if (certificateDate.after(today)) {
+ result = true;
+ }
+ }
+ }
+ catch (IOException ignored) {}
+
+ return result;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacDmgBundler.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,508 @@
+/*
+ * Copyright (c) 2012, 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.file.Files;
+import java.text.MessageFormat;
+import java.util.*;
+
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+
+public class MacDmgBundler extends MacBaseInstallerBundler {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.MacResources");
+
+ static final String DEFAULT_BACKGROUND_IMAGE="background_dmg.png";
+ static final String DEFAULT_DMG_SETUP_SCRIPT="DMGsetup.scpt";
+ static final String TEMPLATE_BUNDLE_ICON = "java.icns";
+
+ static final String DEFAULT_LICENSE_PLIST="lic_template.plist";
+
+ public static final BundlerParamInfo<String> INSTALLER_SUFFIX =
+ new StandardBundlerParam<> (
+ "mac.dmg.installerName.suffix",
+ String.class,
+ params -> "",
+ (s, p) -> s);
+
+ public File bundle(Map<String, ? super Object> params,
+ File outdir) throws PackagerException {
+ Log.verbose(MessageFormat.format(I18N.getString("message.building-dmg"),
+ APP_NAME.fetchFrom(params)));
+
+ IOUtils.writableOutputDir(outdir.toPath());
+
+ File appImageDir = APP_IMAGE_TEMP_ROOT.fetchFrom(params);
+ try {
+ appImageDir.mkdirs();
+
+ if (prepareAppBundle(params) != null &&
+ prepareConfigFiles(params)) {
+ File configScript = getConfig_Script(params);
+ if (configScript.exists()) {
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.running-script"),
+ configScript.getAbsolutePath()));
+ IOUtils.run("bash", configScript);
+ }
+
+ return buildDMG(params, outdir);
+ }
+ return null;
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ private static final String hdiutil = "/usr/bin/hdiutil";
+
+ private void prepareDMGSetupScript(String volumeName,
+ Map<String, ? super Object> params) throws IOException {
+ File dmgSetup = getConfig_VolumeScript(params);
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.preparing-dmg-setup"),
+ dmgSetup.getAbsolutePath()));
+
+ //prepare config for exe
+ Map<String, String> data = new HashMap<>();
+ data.put("DEPLOY_ACTUAL_VOLUME_NAME", volumeName);
+ data.put("DEPLOY_APPLICATION_NAME", APP_NAME.fetchFrom(params));
+
+ 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)));
+ }
+ }
+
+ private File getConfig_VolumeScript(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-dmg-setup.scpt");
+ }
+
+ private File getConfig_VolumeBackground(
+ Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-background.png");
+ }
+
+ private File getConfig_VolumeIcon(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-volume.icns");
+ }
+
+ private File getConfig_LicenseFile(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-license.plist");
+ }
+
+ private void prepareLicense(Map<String, ? super Object> params) {
+ try {
+ String licFileStr = LICENSE_FILE.fetchFrom(params);
+ if (licFileStr == null) {
+ return;
+ }
+
+ File licFile = new File(licFileStr);
+ byte[] licenseContentOriginal =
+ Files.readAllBytes(licFile.toPath());
+ String licenseInBase64 =
+ Base64.getEncoder().encodeToString(licenseContentOriginal);
+
+ 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)));
+ }
+
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ }
+ }
+
+ 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));
+
+ 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));
+ }
+
+
+ fetchResource(getConfig_Script(params).getName(),
+ I18N.getString("resource.post-install-script"),
+ (String) null,
+ getConfig_Script(params),
+ VERBOSE.fetchFrom(params),
+ RESOURCE_DIR.fetchFrom(params));
+
+ prepareLicense(params);
+
+ // In theory we need to extract name from results of attach command
+ // However, this will be a problem for customization as name will
+ // possibly change every time and developer will not be able to fix it
+ // As we are using tmp dir chance we get "different" name are low =>
+ // Use fixed name we used for bundle
+ prepareDMGSetupScript(APP_NAME.fetchFrom(params), params);
+
+ return true;
+ }
+
+ // name of post-image script
+ private File getConfig_Script(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-post-image.sh");
+ }
+
+ // Location of SetFile utility may be different depending on MacOS version
+ // We look for several known places and if none of them work will
+ // try ot find it
+ private String findSetFileUtility() {
+ String typicalPaths[] = {"/Developer/Tools/SetFile",
+ "/usr/bin/SetFile", "/Developer/usr/bin/SetFile"};
+
+ String setFilePath = null;
+ for (String path: typicalPaths) {
+ File f = new File(path);
+ if (f.exists() && f.canExecute()) {
+ setFilePath = path;
+ break;
+ }
+ }
+
+ // Validate SetFile, if Xcode is not installed it will run, but exit with error
+ // code
+ if (setFilePath != null) {
+ try {
+ ProcessBuilder pb = new ProcessBuilder(setFilePath, "-h");
+ Process p = pb.start();
+ int code = p.waitFor();
+ if (code == 0) {
+ return setFilePath;
+ }
+ } catch (Exception ignored) {}
+
+ // No need for generic find attempt. We found it, but it does not work.
+ // Probably due to missing xcode.
+ return null;
+ }
+
+ // generic find attempt
+ try {
+ ProcessBuilder pb = new ProcessBuilder("xcrun", "-find", "SetFile");
+ Process p = pb.start();
+ InputStreamReader isr = new InputStreamReader(p.getInputStream());
+ BufferedReader br = new BufferedReader(isr);
+ String lineRead = br.readLine();
+ if (lineRead != null) {
+ File f = new File(lineRead);
+ if (f.exists() && f.canExecute()) {
+ return f.getAbsolutePath();
+ }
+ }
+ } catch (IOException ignored) {}
+
+ return null;
+ }
+
+ private File buildDMG(
+ Map<String, ? super Object> params, File outdir)
+ throws IOException {
+ File imagesRoot = IMAGES_ROOT.fetchFrom(params);
+ if (!imagesRoot.exists()) imagesRoot.mkdirs();
+
+ File protoDMG = new File(imagesRoot,
+ APP_NAME.fetchFrom(params) +"-tmp.dmg");
+ File finalDMG = new File(outdir, INSTALLER_NAME.fetchFrom(params)
+ + INSTALLER_SUFFIX.fetchFrom(params) + ".dmg");
+
+ File srcFolder = APP_IMAGE_TEMP_ROOT.fetchFrom(params);
+ File predefinedImage =
+ StandardBundlerParam.getPredefinedAppImage(params);
+ if (predefinedImage != null) {
+ srcFolder = predefinedImage;
+ }
+
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.creating-dmg-file"), finalDMG.getAbsolutePath()));
+
+ protoDMG.delete();
+ if (finalDMG.exists() && !finalDMG.delete()) {
+ throw new IOException(MessageFormat.format(I18N.getString(
+ "message.dmg-cannot-be-overwritten"),
+ finalDMG.getAbsolutePath()));
+ }
+
+ protoDMG.getParentFile().mkdirs();
+ finalDMG.getParentFile().mkdirs();
+
+ String hdiUtilVerbosityFlag = VERBOSE.fetchFrom(params) ?
+ "-verbose" : "-quiet";
+
+ // create temp image
+ ProcessBuilder pb = new ProcessBuilder(
+ hdiutil,
+ "create",
+ hdiUtilVerbosityFlag,
+ "-srcfolder", srcFolder.getAbsolutePath(),
+ "-volname", APP_NAME.fetchFrom(params),
+ "-ov", protoDMG.getAbsolutePath(),
+ "-fs", "HFS+",
+ "-format", "UDRW");
+ IOUtils.exec(pb);
+
+ // mount temp image
+ pb = new ProcessBuilder(
+ hdiutil,
+ "attach",
+ protoDMG.getAbsolutePath(),
+ hdiUtilVerbosityFlag,
+ "-mountroot", imagesRoot.getAbsolutePath());
+ IOUtils.exec(pb);
+
+ File mountedRoot = new File(imagesRoot.getAbsolutePath(),
+ APP_NAME.fetchFrom(params));
+
+ try {
+ // volume icon
+ File volumeIconFile = new File(mountedRoot, ".VolumeIcon.icns");
+ IOUtils.copyFile(getConfig_VolumeIcon(params),
+ volumeIconFile);
+
+ // background image
+ File bgdir = new File(mountedRoot, ".background");
+ bgdir.mkdirs();
+ IOUtils.copyFile(getConfig_VolumeBackground(params),
+ new File(bgdir, "background.png"));
+
+ // Indicate that we want a custom icon
+ // NB: attributes of the root directory are ignored
+ // when creating the volume
+ // Therefore we have to do this after we mount image
+ String setFileUtility = findSetFileUtility();
+ if (setFileUtility != null) {
+ //can not find utility => keep going without icon
+ try {
+ volumeIconFile.setWritable(true);
+ // The "creator" attribute on a file is a legacy attribute
+ // but it seems Finder excepts these bytes to be
+ // "icnC" for the volume icon
+ // http://endrift.com/blog/2010/06/14/dmg-files-volume-icons-cli
+ // (might not work on Mac 10.13 with old XCode)
+ pb = new ProcessBuilder(
+ setFileUtility,
+ "-c", "icnC",
+ volumeIconFile.getAbsolutePath());
+ IOUtils.exec(pb);
+ volumeIconFile.setReadOnly();
+
+ pb = new ProcessBuilder(
+ setFileUtility,
+ "-a", "C",
+ mountedRoot.getAbsolutePath());
+ IOUtils.exec(pb);
+ } catch (IOException ex) {
+ Log.error(ex.getMessage());
+ Log.verbose("Cannot enable custom icon using SetFile utility");
+ }
+ } else {
+ Log.verbose(I18N.getString("message.setfile.dmg"));
+ }
+
+ // We will not consider setting background image and creating link to
+ // /Application folder in DMG as critical error, since it can fail in
+ // headless enviroment.
+ try {
+ pb = new ProcessBuilder("osascript",
+ getConfig_VolumeScript(params).getAbsolutePath());
+ IOUtils.exec(pb);
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ }
+ } finally {
+ // Detach the temporary image
+ pb = new ProcessBuilder(
+ hdiutil,
+ "detach",
+ "-force",
+ hdiUtilVerbosityFlag,
+ mountedRoot.getAbsolutePath());
+ IOUtils.exec(pb);
+ }
+
+ // Compress it to a new image
+ pb = new ProcessBuilder(
+ hdiutil,
+ "convert",
+ protoDMG.getAbsolutePath(),
+ hdiUtilVerbosityFlag,
+ "-format", "UDZO",
+ "-o", finalDMG.getAbsolutePath());
+ IOUtils.exec(pb);
+
+ //add license if needed
+ if (getConfig_LicenseFile(params).exists()) {
+ //hdiutil unflatten your_image_file.dmg
+ pb = new ProcessBuilder(
+ hdiutil,
+ "unflatten",
+ finalDMG.getAbsolutePath()
+ );
+ IOUtils.exec(pb);
+
+ //add license
+ pb = new ProcessBuilder(
+ hdiutil,
+ "udifrez",
+ finalDMG.getAbsolutePath(),
+ "-xml",
+ getConfig_LicenseFile(params).getAbsolutePath()
+ );
+ IOUtils.exec(pb);
+
+ //hdiutil flatten your_image_file.dmg
+ pb = new ProcessBuilder(
+ hdiutil,
+ "flatten",
+ finalDMG.getAbsolutePath()
+ );
+ IOUtils.exec(pb);
+
+ }
+
+ //Delete the temporary image
+ protoDMG.delete();
+
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.output-to-location"),
+ APP_NAME.fetchFrom(params), finalDMG.getAbsolutePath()));
+
+ return finalDMG;
+ }
+
+
+ //////////////////////////////////////////////////////////////////////////
+ // Implement Bundler
+ //////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public String getName() {
+ return I18N.getString("dmg.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "dmg";
+ }
+
+ @Override
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+ try {
+ if (params == null) throw new ConfigException(
+ I18N.getString("error.parameters-null"),
+ I18N.getString("error.parameters-null.advice"));
+
+ //run basic validation to ensure requirements are met
+ //we are not interested in return code, only possible exception
+ validateAppImageAndBundeler(params);
+
+ return true;
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof ConfigException) {
+ throw (ConfigException) re.getCause();
+ } else {
+ throw new ConfigException(re);
+ }
+ }
+ }
+
+ @Override
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ return bundle(params, outputParentDir);
+ }
+
+ @Override
+ public boolean supported(boolean runtimeInstaller) {
+ return isSupported();
+ }
+
+ public final static String[] required =
+ {"/usr/bin/hdiutil", "/usr/bin/osascript"};
+ public static boolean isSupported() {
+ try {
+ for (String s : required) {
+ File f = new File(s);
+ if (!f.exists() || !f.canExecute()) {
+ return false;
+ }
+ }
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isDefault() {
+ return true;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPkgBundler.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,543 @@
+/*
+ * Copyright (c) 2014, 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.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.net.URLEncoder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.ResourceBundle;
+
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+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;
+
+public class MacPkgBundler extends MacBaseInstallerBundler {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.MacResources");
+
+ private static final String DEFAULT_BACKGROUND_IMAGE = "background_dmg.png";
+
+ private static final String TEMPLATE_PREINSTALL_SCRIPT =
+ "preinstall.template";
+ private static final String TEMPLATE_POSTINSTALL_SCRIPT =
+ "postinstall.template";
+
+ private static final BundlerParamInfo<File> PACKAGES_ROOT =
+ new StandardBundlerParam<>(
+ "mac.pkg.packagesRoot",
+ File.class,
+ params -> {
+ File packagesRoot =
+ new File(TEMP_ROOT.fetchFrom(params), "packages");
+ packagesRoot.mkdirs();
+ return packagesRoot;
+ },
+ (s, p) -> new File(s));
+
+
+ protected final BundlerParamInfo<File> SCRIPTS_DIR =
+ new StandardBundlerParam<>(
+ "mac.pkg.scriptsDir",
+ File.class,
+ params -> {
+ File scriptsDir =
+ new File(CONFIG_ROOT.fetchFrom(params), "scripts");
+ scriptsDir.mkdirs();
+ return scriptsDir;
+ },
+ (s, p) -> new File(s));
+
+ public static final
+ BundlerParamInfo<String> DEVELOPER_ID_INSTALLER_SIGNING_KEY =
+ new StandardBundlerParam<>(
+ "mac.signing-key-developer-id-installer",
+ String.class,
+ params -> {
+ String result = MacBaseInstallerBundler.findKey(
+ "Developer ID Installer: "
+ + SIGNING_KEY_USER.fetchFrom(params),
+ SIGNING_KEYCHAIN.fetchFrom(params),
+ VERBOSE.fetchFrom(params));
+ if (result != null) {
+ MacCertificate certificate = new MacCertificate(
+ result, VERBOSE.fetchFrom(params));
+
+ if (!certificate.isValid()) {
+ Log.error(MessageFormat.format(
+ I18N.getString("error.certificate.expired"),
+ result));
+ }
+ }
+
+ return result;
+ },
+ (s, p) -> s);
+
+ public static final BundlerParamInfo<String> MAC_INSTALL_DIR =
+ new StandardBundlerParam<>(
+ "mac-install-dir",
+ String.class,
+ params -> {
+ String dir = INSTALL_DIR.fetchFrom(params);
+ return (dir != null) ? dir : "/Applications";
+ },
+ (s, p) -> s
+ );
+
+ public static final BundlerParamInfo<String> INSTALLER_SUFFIX =
+ new StandardBundlerParam<> (
+ "mac.pkg.installerName.suffix",
+ String.class,
+ params -> "",
+ (s, p) -> s);
+
+ public File bundle(Map<String, ? super Object> params,
+ File outdir) throws PackagerException {
+ Log.verbose(MessageFormat.format(I18N.getString("message.building-pkg"),
+ APP_NAME.fetchFrom(params)));
+
+ IOUtils.writableOutputDir(outdir.toPath());
+
+ try {
+ File appImageDir = prepareAppBundle(params);
+
+ if (appImageDir != null && prepareConfigFiles(params)) {
+
+ File configScript = getConfig_Script(params);
+ if (configScript.exists()) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.running-script"),
+ configScript.getAbsolutePath()));
+ IOUtils.run("bash", configScript);
+ }
+
+ return createPKG(params, outdir, appImageDir);
+ }
+ return null;
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ private File getPackages_AppPackage(Map<String, ? super Object> params) {
+ return new File(PACKAGES_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-app.pkg");
+ }
+
+ private File getConfig_DistributionXMLFile(
+ Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params), "distribution.dist");
+ }
+
+ private File getConfig_BackgroundImage(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-background.png");
+ }
+
+ private File getScripts_PreinstallFile(Map<String, ? super Object> params) {
+ return new File(SCRIPTS_DIR.fetchFrom(params), "preinstall");
+ }
+
+ private File getScripts_PostinstallFile(
+ Map<String, ? super Object> params) {
+ return new File(SCRIPTS_DIR.fetchFrom(params), "postinstall");
+ }
+
+ private String getAppIdentifier(Map<String, ? super Object> params) {
+ return IDENTIFIER.fetchFrom(params);
+ }
+
+ private void preparePackageScripts(Map<String, ? super Object> params)
+ throws IOException {
+ Log.verbose(I18N.getString("message.preparing-scripts"));
+
+ Map<String, String> data = new HashMap<>();
+
+ data.put("INSTALL_LOCATION", MAC_INSTALL_DIR.fetchFrom(params));
+
+ 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);
+ }
+ 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);
+ }
+ getScripts_PostinstallFile(params).setExecutable(true, false);
+ }
+
+ private void prepareDistributionXMLFile(Map<String, ? super Object> params)
+ throws IOException {
+ File f = getConfig_DistributionXMLFile(params);
+
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.preparing-distribution-dist"), f.getAbsolutePath()));
+
+ try (PrintStream out = new PrintStream(f)) {
+
+ out.println(
+ "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>");
+ out.println("<installer-gui-script minSpecVersion=\"1\">");
+
+ out.println("<title>" + APP_NAME.fetchFrom(params) + "</title>");
+ out.println("<background" + " file=\""
+ + getConfig_BackgroundImage(params).getName()
+ + "\""
+ + " mime-type=\"image/png\""
+ + " alignment=\"bottomleft\" "
+ + " scaling=\"none\""
+ + "/>");
+
+ String licFileStr = LICENSE_FILE.fetchFrom(params);
+ if (licFileStr != null) {
+ File licFile = new File(licFileStr);
+ out.println("<license"
+ + " file=\"" + licFile.getAbsolutePath() + "\""
+ + " mime-type=\"text/rtf\""
+ + "/>");
+ }
+
+ /*
+ * Note that the content of the distribution file
+ * below is generated by productbuild --synthesize
+ */
+
+ String appId = getAppIdentifier(params);
+
+ out.println("<pkg-ref id=\"" + appId + "\"/>");
+ out.println(
+ "<options customize=\"never\" require-scripts=\"false\"/>");
+ out.println("<choices-outline>");
+ out.println(" <line choice=\"default\">");
+ out.println(" <line choice=\"" + appId + "\"/>");
+ out.println(" </line>");
+ out.println("</choices-outline>");
+ out.println("<choice id=\"default\"/>");
+ out.println("<choice id=\"" + appId + "\" visible=\"false\">");
+ out.println(" <pkg-ref id=\"" + appId + "\"/>");
+ out.println("</choice>");
+ out.println("<pkg-ref id=\"" + appId + "\" version=\""
+ + VERSION.fetchFrom(params) + "\" onConclusion=\"none\">"
+ + URLEncoder.encode(
+ getPackages_AppPackage(params).getName(),
+ "UTF-8") + "</pkg-ref>");
+
+ out.println("</installer-gui-script>");
+
+ }
+ }
+
+ private boolean prepareConfigFiles(Map<String, ? super Object> params)
+ throws IOException {
+ 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));
+
+ 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));
+
+ return true;
+ }
+
+ // name of post-image script
+ private File getConfig_Script(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-post-image.sh");
+ }
+
+ private void patchCPLFile(File cpl) throws IOException {
+ String cplData = Files.readString(cpl.toPath());
+ String[] lines = cplData.split("\n");
+ try (PrintWriter out = new PrintWriter(Files.newBufferedWriter(
+ cpl.toPath()))) {
+ int skip = 0;
+ // Used to skip Java.runtime bundle, since
+ // pkgbuild with --root will find two bundles app and Java runtime.
+ // We cannot generate component proprty list when using
+ // --component argument.
+ for (int i = 0; i < lines.length; i++) {
+ if (lines[i].trim().equals("<key>BundleIsRelocatable</key>")) {
+ out.println(lines[i]);
+ out.println("<false/>");
+ i++;
+ } else if (lines[i].trim().equals("<key>ChildBundles</key>")) {
+ ++skip;
+ } else if ((skip > 0) && lines[i].trim().equals("</array>")) {
+ --skip;
+ } else {
+ if (skip == 0) {
+ out.println(lines[i]);
+ }
+ }
+ }
+ }
+ }
+
+ // pkgbuild includes all components from "--root" and subfolders,
+ // so if we have app image in folder which contains other images, then they
+ // will be included as well. It does have "--filter" option which use regex
+ // to exclude files/folder, but it will overwrite default one which excludes
+ // based on doc "any .svn or CVS directories, and any .DS_Store files".
+ // So easy aproach will be to copy user provided app-image into temp folder
+ // if root path contains other files.
+ private String getRoot(Map<String, ? super Object> params,
+ File appLocation) throws IOException {
+ String root = appLocation.getParent() == null ?
+ "." : appLocation.getParent();
+ File rootDir = new File(root);
+ File[] list = rootDir.listFiles();
+ if (list != null) { // Should not happend
+ // We should only have app image and/or .DS_Store
+ if (list.length == 1) {
+ return root;
+ } else if (list.length == 2) {
+ // Check case with app image and .DS_Store
+ if (list[0].toString().toLowerCase().endsWith(".ds_store") ||
+ list[1].toString().toLowerCase().endsWith(".ds_store")) {
+ return root; // Only app image and .DS_Store
+ }
+ }
+ }
+
+ // Copy to new root
+ Path newRoot = Files.createTempDirectory(
+ TEMP_ROOT.fetchFrom(params).toPath(),
+ "root-");
+
+ IOUtils.copyRecursive(appLocation.toPath(),
+ newRoot.resolve(appLocation.getName()));
+
+ return newRoot.toString();
+ }
+
+ private File createPKG(Map<String, ? super Object> params,
+ File outdir, File appLocation) {
+ // generic find attempt
+ try {
+ File appPKG = getPackages_AppPackage(params);
+
+ String root = getRoot(params, appLocation);
+
+ // Generate default CPL file
+ File cpl = new File(CONFIG_ROOT.fetchFrom(params).getAbsolutePath()
+ + File.separator + "cpl.plist");
+ ProcessBuilder pb = new ProcessBuilder("pkgbuild",
+ "--root",
+ root,
+ "--install-location",
+ MAC_INSTALL_DIR.fetchFrom(params),
+ "--analyze",
+ cpl.getAbsolutePath());
+
+ IOUtils.exec(pb);
+
+ patchCPLFile(cpl);
+
+ preparePackageScripts(params);
+
+ // build application package
+ pb = new ProcessBuilder("pkgbuild",
+ "--root",
+ root,
+ "--install-location",
+ MAC_INSTALL_DIR.fetchFrom(params),
+ "--component-plist",
+ cpl.getAbsolutePath(),
+ "--scripts",
+ SCRIPTS_DIR.fetchFrom(params).getAbsolutePath(),
+ appPKG.getAbsolutePath());
+ IOUtils.exec(pb);
+
+ // build final package
+ File finalPKG = new File(outdir, INSTALLER_NAME.fetchFrom(params)
+ + INSTALLER_SUFFIX.fetchFrom(params)
+ + ".pkg");
+ outdir.mkdirs();
+
+ List<String> commandLine = new ArrayList<>();
+ commandLine.add("productbuild");
+
+ commandLine.add("--resources");
+ commandLine.add(CONFIG_ROOT.fetchFrom(params).getAbsolutePath());
+
+ // maybe sign
+ if (Optional.ofNullable(MacAppImageBuilder.
+ SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.TRUE)) {
+ if (Platform.getMajorVersion() > 10 ||
+ (Platform.getMajorVersion() == 10 &&
+ Platform.getMinorVersion() >= 12)) {
+ // we need this for OS X 10.12+
+ Log.verbose(I18N.getString("message.signing.pkg"));
+ }
+
+ String signingIdentity =
+ DEVELOPER_ID_INSTALLER_SIGNING_KEY.fetchFrom(params);
+ if (signingIdentity != null) {
+ commandLine.add("--sign");
+ commandLine.add(signingIdentity);
+ }
+
+ String keychainName = SIGNING_KEYCHAIN.fetchFrom(params);
+ if (keychainName != null && !keychainName.isEmpty()) {
+ commandLine.add("--keychain");
+ commandLine.add(keychainName);
+ }
+ }
+
+ commandLine.add("--distribution");
+ commandLine.add(
+ getConfig_DistributionXMLFile(params).getAbsolutePath());
+ commandLine.add("--package-path");
+ commandLine.add(PACKAGES_ROOT.fetchFrom(params).getAbsolutePath());
+
+ commandLine.add(finalPKG.getAbsolutePath());
+
+ pb = new ProcessBuilder(commandLine);
+ IOUtils.exec(pb);
+
+ return finalPKG;
+ } catch (Exception ignored) {
+ Log.verbose(ignored);
+ return null;
+ }
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // Implement Bundler
+ //////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public String getName() {
+ return I18N.getString("pkg.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "pkg";
+ }
+
+ @Override
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+ try {
+ if (params == null) throw new ConfigException(
+ I18N.getString("error.parameters-null"),
+ I18N.getString("error.parameters-null.advice"));
+
+ // run basic validation to ensure requirements are met
+ // we are not interested in return code, only possible exception
+ validateAppImageAndBundeler(params);
+
+ if (MAC_CF_BUNDLE_IDENTIFIER.fetchFrom(params) == null) {
+ throw new ConfigException(
+ I18N.getString("message.app-image-requires-identifier"),
+ I18N.getString(
+ "message.app-image-requires-identifier.advice"));
+ }
+
+ // reject explicitly set sign to true and no valid signature key
+ if (Optional.ofNullable(MacAppImageBuilder.
+ SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) {
+ String signingIdentity =
+ DEVELOPER_ID_INSTALLER_SIGNING_KEY.fetchFrom(params);
+ if (signingIdentity == null) {
+ throw new ConfigException(
+ I18N.getString("error.explicit-sign-no-cert"),
+ I18N.getString(
+ "error.explicit-sign-no-cert.advice"));
+ }
+ }
+
+ // hdiutil is always available so there's no need
+ // to test for availability.
+
+ return true;
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof ConfigException) {
+ throw (ConfigException) re.getCause();
+ } else {
+ throw new ConfigException(re);
+ }
+ }
+ }
+
+ @Override
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ return bundle(params, outputParentDir);
+ }
+
+ @Override
+ public boolean supported(boolean runtimeInstaller) {
+ return true;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return false;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/DMGsetup.scpt Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,38 @@
+tell application "Finder"
+ tell disk "DEPLOY_ACTUAL_VOLUME_NAME"
+ open
+ set current view of container window to icon view
+ set toolbar visible of container window to false
+ set statusbar visible of container window to false
+
+ -- size of window should match size of background
+ set the bounds of container window to {400, 100, 917, 380}
+
+ set theViewOptions to the icon view options of container window
+ set arrangement of theViewOptions to not arranged
+ set icon size of theViewOptions to 128
+ set background picture of theViewOptions to file ".background:background.png"
+
+ -- Create alias for install location
+ make new alias file at container window to DEPLOY_INSTALL_LOCATION with properties {name:"DEPLOY_INSTALL_NAME"}
+
+ set allTheFiles to the name of every item of container window
+ repeat with theFile in allTheFiles
+ set theFilePath to POSIX Path of theFile
+ if theFilePath is "/DEPLOY_APPLICATION_NAME.app"
+ -- Position application location
+ set position of item theFile of container window to {120, 60}
+ else if theFilePath is "/DEPLOY_INSTALL_NAME"
+ -- Position install location
+ set position of item theFile of container window to {390, 60}
+ else
+ -- Move all other files far enough to be not visible if user has "show hidden files" option set
+ set position of item theFile of container window to {1000, 0}
+ end
+ end repeat
+
+ update without registering applications
+ delay 5
+ close
+ end tell
+end tell
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/Info-lite.plist.template Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,38 @@
+<?xml version="1.0" ?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.9</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleAllowMixedLocalizations</key>
+ <true/>
+ <key>CFBundleExecutable</key>
+ <string>DEPLOY_LAUNCHER_NAME</string>
+ <key>CFBundleIconFile</key>
+ <string>DEPLOY_ICON_FILE</string>
+ <key>CFBundleIdentifier</key>
+ <string>DEPLOY_BUNDLE_IDENTIFIER</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>DEPLOY_BUNDLE_NAME</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>DEPLOY_BUNDLE_SHORT_VERSION</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <!-- See http://developer.apple.com/library/mac/#releasenotes/General/SubmittingToMacAppStore/_index.html
+ for list of AppStore categories -->
+ <key>LSApplicationCategoryType</key>
+ <string>DEPLOY_BUNDLE_CATEGORY</string>
+ <key>CFBundleVersion</key>
+ <string>DEPLOY_BUNDLE_CFBUNDLE_VERSION</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>DEPLOY_BUNDLE_COPYRIGHT</string>DEPLOY_FILE_ASSOCIATIONS
+ <key>NSHighResolutionCapable</key>
+ <string>true</string>
+ </dict>
+</plist>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/Info.plist.template Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,54 @@
+<?xml version="1.0" ?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.7.4</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleAllowMixedLocalizations</key>
+ <true/>
+ <key>CFBundleExecutable</key>
+ <string>DEPLOY_LAUNCHER_NAME</string>
+ <key>CFBundleIconFile</key>
+ <string>DEPLOY_ICON_FILE</string>
+ <key>CFBundleIdentifier</key>
+ <string>DEPLOY_BUNDLE_IDENTIFIER</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>DEPLOY_BUNDLE_NAME</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>DEPLOY_BUNDLE_SHORT_VERSION</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <!-- See http://developer.apple.com/library/mac/#releasenotes/General/SubmittingToMacAppStore/_index.html
+ for list of AppStore categories -->
+ <key>LSApplicationCategoryType</key>
+ <string>DEPLOY_BUNDLE_CATEGORY</string>
+ <key>CFBundleVersion</key>
+ <string>DEPLOY_BUNDLE_CFBUNDLE_VERSION</string>
+ <key>NSHumanReadableCopyright</key>
+ <string>DEPLOY_BUNDLE_COPYRIGHT</string>
+ <key>JavaRuntime</key>
+ <string>DEPLOY_JAVA_RUNTIME_NAME</string>
+ <key>JavaMainClassName</key>
+ <string>DEPLOY_LAUNCHER_CLASS</string>
+ <key>JavaAppClasspath</key>
+ <string>DEPLOY_APP_CLASSPATH</string>
+ <key>JavaMainJarName</key>
+ <string>DEPLOY_MAIN_JAR_NAME</string>
+ <key>JavaOptions</key>
+ <array>
+DEPLOY_JAVA_OPTIONS
+ </array>
+ <key>ArgOptions</key>
+ <array>
+DEPLOY_ARGUMENTS
+ </array>DEPLOY_FILE_ASSOCIATIONS
+ <key>NSHighResolutionCapable</key>
+ <string>true</string>
+ </dict>
+</plist>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacAppStore.entitlements Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>com.apple.security.app-sandbox</key>
+ <true/>
+ </dict>
+</plist>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacAppStore_Inherit.entitlements Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>com.apple.security.app-sandbox</key>
+ <true/>
+ <key>com.apple.security.inherit</key>
+ <true/>
+ </dict>
+</plist>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,91 @@
+#
+# Copyright (c) 2017, 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.
+#
+#
+
+app.bundler.name=Mac Application Image
+store.bundler.name=Mac App Store Ready Bundler
+dmg.bundler.name=Mac DMG Package
+pkg.bundler.name=Mac PKG Package
+
+error.invalid-cfbundle-version=Invalid CFBundleVersion: [{0}]
+error.invalid-cfbundle-version.advice=Set a compatible 'appVersion' or set a 'mac.CFBundleVersion'. Valid versions are one to three integers separated by dots.
+error.explicit-sign-no-cert=Signature explicitly requested but no signing certificate specified
+error.explicit-sign-no-cert.advice=Either specify a valid cert in 'mac.signing-key-developer-id-app' or unset 'signBundle' or set 'signBundle' to false.
+error.parameters-null=Parameters map is null
+error.parameters-null.advice=Pass in a non-null parameters map.
+error.must-sign-app-store=Mac App Store apps must be signed, and signing has been disabled by bundler configuration
+error.must-sign-app-store.advice=Either unset 'signBundle' or set 'signBundle' to true
+error.no-app-signing-key=No Mac App Store App Signing Key
+error.no-app-signing-key.advice=Install your app signing keys into your Mac Keychain using XCode.
+error.no-pkg-signing-key=No Mac App Store Installer Signing Key
+error.no-pkg-signing-key.advice=Install your app signing keys into your Mac Keychain using XCode.
+error.certificate.expired=Error: Certificate expired {0}
+error.no.xcode.signing=Xcode with command line developer tools is required for signing
+error.no.xcode.signing.advice=Install Xcode with command line developer tools.
+
+resource.bundle-config-file=Bundle config file
+resource.app-info-plist=Application Info.plist
+resource.runtime-info-plist=Java Runtime Info.plist
+resource.mac-app-store-entitlements=Mac App Store Entitlements
+resource.mac-app-store-inherit-entitlements=Mac App Store Inherit Entitlements
+resource.dmg-setup-script=DMG setup script
+resource.license-setup=License setup
+resource.dmg-background=dmg background
+resource.volume-icon=volume icon
+resource.post-install-script=script to run after application image is populated
+resource.pkg-preinstall-script=PKG preinstall script
+resource.pkg-postinstall-script=PKG postinstall script
+resource.pkg-background-image=pkg background image
+
+
+message.bundle-name-too-long-warning={0} is set to ''{1}'', which is longer than 16 characters. For a better Mac experience consider shortening it.
+message.null-classpath=Null app resources?
+message.preparing-info-plist=Preparing Info.plist: {0}.
+message.icon-not-icns= The specified icon "{0}" is not an ICNS file and will not be used. The default icon will be used in it's place.
+message.version-string-too-many-components=Version sting may have between 1 and 3 numbers: 1, 1.2, 1.2.3.
+message.version-string-first-number-not-zero=The first number in a CFBundleVersion cannot be zero or negative.
+message.version-string-no-negative-numbers=Negative numbers are not allowed in version strings.
+message.version-string-numbers-only=Version strings can consist of only numbers and up to two dots.
+message.creating-association-with-null-extension=Creating association with null extension.
+message.ignoring.symlink=Warning: codesign is skipping the symlink {0}.
+message.keychain.error=Error: unable to get keychain list.
+message.building-bundle=Building Mac App Store Package for {0}.
+message.app-image-dir-does-not-exist=Specified application image directory {0}: {1} does not exists.
+message.app-image-dir-does-not-exist.advice=Confirm that the value for {0} exists.
+message.app-image-requires-app-name=When using an external app image you must specify the app name.
+message.app-image-requires-app-name.advice=Set the app name via the -name CLI flag, the fx:application/@name ANT attribute, or via the 'appName' bundler argument.
+message.app-image-requires-identifier=Unable to extract identifier from app image.
+message.app-image-requires-identifier.advice=Use "--verbose" for extended error message or specify it via "--mac-package-identifier".
+message.building-dmg=Building DMG package for {0}.
+message.running-script=Running shell script on application image [{0}].
+message.preparing-dmg-setup=Preparing dmg setup: {0}.
+message.creating-dmg-file=Creating DMG file: {0}.
+message.dmg-cannot-be-overwritten=Dmg file exists ({0} and can not be removed.
+message.output-to-location=Result DMG installer for {0}: {1}.
+message.building-pkg=Building PKG package for {0}.
+message.preparing-scripts=Preparing package scripts.
+message.preparing-distribution-dist=Preparing distribution.dist: {0}.
+message.signing.pkg=Warning: For signing PKG, you might need to set "Always Trust" for your certificate using "Keychain Access" tool.
+message.setfile.dmg=Setting custom icon on DMG file skipped because 'SetFile' utility was not found. Installing Xcode with Command Line Tools should resolve this issue.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_ja.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,91 @@
+#
+# Copyright (c) 2017, 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.
+#
+#
+
+app.bundler.name=Mac Application Image
+store.bundler.name=Mac App Store Ready Bundler
+dmg.bundler.name=Mac DMG Package
+pkg.bundler.name=Mac PKG Package
+
+error.invalid-cfbundle-version=Invalid CFBundleVersion: [{0}]
+error.invalid-cfbundle-version.advice=Set a compatible 'appVersion' or set a 'mac.CFBundleVersion'. Valid versions are one to three integers separated by dots.
+error.explicit-sign-no-cert=Signature explicitly requested but no signing certificate specified
+error.explicit-sign-no-cert.advice=Either specify a valid cert in 'mac.signing-key-developer-id-app' or unset 'signBundle' or set 'signBundle' to false.
+error.parameters-null=Parameters map is null
+error.parameters-null.advice=Pass in a non-null parameters map.
+error.must-sign-app-store=Mac App Store apps must be signed, and signing has been disabled by bundler configuration
+error.must-sign-app-store.advice=Either unset 'signBundle' or set 'signBundle' to true
+error.no-app-signing-key=No Mac App Store App Signing Key
+error.no-app-signing-key.advice=Install your app signing keys into your Mac Keychain using XCode.
+error.no-pkg-signing-key=No Mac App Store Installer Signing Key
+error.no-pkg-signing-key.advice=Install your app signing keys into your Mac Keychain using XCode.
+error.certificate.expired=Error: Certificate expired {0}
+error.no.xcode.signing=Xcode with command line developer tools is required for signing
+error.no.xcode.signing.advice=Install Xcode with command line developer tools.
+
+resource.bundle-config-file=Bundle config file
+resource.app-info-plist=Application Info.plist
+resource.runtime-info-plist=Java Runtime Info.plist
+resource.mac-app-store-entitlements=Mac App Store Entitlements
+resource.mac-app-store-inherit-entitlements=Mac App Store Inherit Entitlements
+resource.dmg-setup-script=DMG setup script
+resource.license-setup=License setup
+resource.dmg-background=dmg background
+resource.volume-icon=volume icon
+resource.post-install-script=script to run after application image is populated
+resource.pkg-preinstall-script=PKG preinstall script
+resource.pkg-postinstall-script=PKG postinstall script
+resource.pkg-background-image=pkg background image
+
+
+message.bundle-name-too-long-warning={0} is set to ''{1}'', which is longer than 16 characters. For a better Mac experience consider shortening it.
+message.null-classpath=Null app resources?
+message.preparing-info-plist=Preparing Info.plist: {0}.
+message.icon-not-icns= The specified icon "{0}" is not an ICNS file and will not be used. The default icon will be used in it's place.
+message.version-string-too-many-components=Version sting may have between 1 and 3 numbers: 1, 1.2, 1.2.3.
+message.version-string-first-number-not-zero=The first number in a CFBundleVersion cannot be zero or negative.
+message.version-string-no-negative-numbers=Negative numbers are not allowed in version strings.
+message.version-string-numbers-only=Version strings can consist of only numbers and up to two dots.
+message.creating-association-with-null-extension=Creating association with null extension.
+message.ignoring.symlink=Warning: codesign is skipping the symlink {0}.
+message.keychain.error=Error: unable to get keychain list.
+message.building-bundle=Building Mac App Store Package for {0}.
+message.app-image-dir-does-not-exist=Specified application image directory {0}: {1} does not exists.
+message.app-image-dir-does-not-exist.advice=Confirm that the value for {0} exists.
+message.app-image-requires-app-name=When using an external app image you must specify the app name.
+message.app-image-requires-app-name.advice=Set the app name via the -name CLI flag, the fx:application/@name ANT attribute, or via the 'appName' bundler argument.
+message.app-image-requires-identifier=Unable to extract identifier from app image.
+message.app-image-requires-identifier.advice=Use "--verbose" for extended error message or specify it via "--mac-package-identifier".
+message.building-dmg=Building DMG package for {0}.
+message.running-script=Running shell script on application image [{0}].
+message.preparing-dmg-setup=Preparing dmg setup: {0}.
+message.creating-dmg-file=Creating DMG file: {0}.
+message.dmg-cannot-be-overwritten=Dmg file exists ({0} and can not be removed.
+message.output-to-location=Result DMG installer for {0}: {1}.
+message.building-pkg=Building PKG package for {0}.
+message.preparing-scripts=Preparing package scripts.
+message.preparing-distribution-dist=Preparing distribution.dist: {0}.
+message.signing.pkg=Warning: For signing PKG, you might need to set "Always Trust" for your certificate using "Keychain Access" tool.
+message.setfile.dmg=Setting custom icon on DMG file skipped because 'SetFile' utility was not found. Installing Xcode with Command Line Tools should resolve this issue.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources_zh_CN.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,91 @@
+#
+# Copyright (c) 2017, 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.
+#
+#
+
+app.bundler.name=Mac Application Image
+store.bundler.name=Mac App Store Ready Bundler
+dmg.bundler.name=Mac DMG Package
+pkg.bundler.name=Mac PKG Package
+
+error.invalid-cfbundle-version=Invalid CFBundleVersion: [{0}]
+error.invalid-cfbundle-version.advice=Set a compatible 'appVersion' or set a 'mac.CFBundleVersion'. Valid versions are one to three integers separated by dots.
+error.explicit-sign-no-cert=Signature explicitly requested but no signing certificate specified
+error.explicit-sign-no-cert.advice=Either specify a valid cert in 'mac.signing-key-developer-id-app' or unset 'signBundle' or set 'signBundle' to false.
+error.parameters-null=Parameters map is null
+error.parameters-null.advice=Pass in a non-null parameters map.
+error.must-sign-app-store=Mac App Store apps must be signed, and signing has been disabled by bundler configuration
+error.must-sign-app-store.advice=Either unset 'signBundle' or set 'signBundle' to true
+error.no-app-signing-key=No Mac App Store App Signing Key
+error.no-app-signing-key.advice=Install your app signing keys into your Mac Keychain using XCode.
+error.no-pkg-signing-key=No Mac App Store Installer Signing Key
+error.no-pkg-signing-key.advice=Install your app signing keys into your Mac Keychain using XCode.
+error.certificate.expired=Error: Certificate expired {0}
+error.no.xcode.signing=Xcode with command line developer tools is required for signing
+error.no.xcode.signing.advice=Install Xcode with command line developer tools.
+
+resource.bundle-config-file=Bundle config file
+resource.app-info-plist=Application Info.plist
+resource.runtime-info-plist=Java Runtime Info.plist
+resource.mac-app-store-entitlements=Mac App Store Entitlements
+resource.mac-app-store-inherit-entitlements=Mac App Store Inherit Entitlements
+resource.dmg-setup-script=DMG setup script
+resource.license-setup=License setup
+resource.dmg-background=dmg background
+resource.volume-icon=volume icon
+resource.post-install-script=script to run after application image is populated
+resource.pkg-preinstall-script=PKG preinstall script
+resource.pkg-postinstall-script=PKG postinstall script
+resource.pkg-background-image=pkg background image
+
+
+message.bundle-name-too-long-warning={0} is set to ''{1}'', which is longer than 16 characters. For a better Mac experience consider shortening it.
+message.null-classpath=Null app resources?
+message.preparing-info-plist=Preparing Info.plist: {0}.
+message.icon-not-icns= The specified icon "{0}" is not an ICNS file and will not be used. The default icon will be used in it's place.
+message.version-string-too-many-components=Version sting may have between 1 and 3 numbers: 1, 1.2, 1.2.3.
+message.version-string-first-number-not-zero=The first number in a CFBundleVersion cannot be zero or negative.
+message.version-string-no-negative-numbers=Negative numbers are not allowed in version strings.
+message.version-string-numbers-only=Version strings can consist of only numbers and up to two dots.
+message.creating-association-with-null-extension=Creating association with null extension.
+message.ignoring.symlink=Warning: codesign is skipping the symlink {0}.
+message.keychain.error=Error: unable to get keychain list.
+message.building-bundle=Building Mac App Store Package for {0}.
+message.app-image-dir-does-not-exist=Specified application image directory {0}: {1} does not exists.
+message.app-image-dir-does-not-exist.advice=Confirm that the value for {0} exists.
+message.app-image-requires-app-name=When using an external app image you must specify the app name.
+message.app-image-requires-app-name.advice=Set the app name via the -name CLI flag, the fx:application/@name ANT attribute, or via the 'appName' bundler argument.
+message.app-image-requires-identifier=Unable to extract identifier from app image.
+message.app-image-requires-identifier.advice=Use "--verbose" for extended error message or specify it via "--mac-package-identifier".
+message.building-dmg=Building DMG package for {0}.
+message.running-script=Running shell script on application image [{0}].
+message.preparing-dmg-setup=Preparing dmg setup: {0}.
+message.creating-dmg-file=Creating DMG file: {0}.
+message.dmg-cannot-be-overwritten=Dmg file exists ({0} and can not be removed.
+message.output-to-location=Result DMG installer for {0}: {1}.
+message.building-pkg=Building PKG package for {0}.
+message.preparing-scripts=Preparing package scripts.
+message.preparing-distribution-dist=Preparing distribution.dist: {0}.
+message.signing.pkg=Warning: For signing PKG, you might need to set "Always Trust" for your certificate using "Keychain Access" tool.
+message.setfile.dmg=Setting custom icon on DMG file skipped because 'SetFile' utility was not found. Installing Xcode with Command Line Tools should resolve this issue.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/Runtime-Info.plist.template Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>libjli.dylib</string>
+ <key>CFBundleIdentifier</key>
+ <string>CF_BUNDLE_IDENTIFIER</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>7.0</string>
+ <key>CFBundleName</key>
+ <string>CF_BUNDLE_NAME</string>
+ <key>CFBundlePackageType</key>
+ <string>BNDL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>CF_BUNDLE_SHORT_VERSION_STRING</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>CF_BUNDLE_VERSION</string>
+</dict>
+</plist>
Binary file src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/background_dmg.png has changed
Binary file src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/background_pkg.png has changed
Binary file src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/java.icns has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/lic_template.plist Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>LPic</key>
+ <array>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAAAAgAAAAAAAAAAAAQAAA==</data>
+ <key>ID</key>
+ <string>5000</string>
+ <key>Name</key>
+ <string></string>
+ </dict>
+ </array>
+ <key>STR#</key>
+ <array>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYPRW5nbGlzaCBkZWZhdWx0BUFncmVlCERpc2FncmVlBVByaW50B1NhdmUuLi56SWYgeW91IGFncmVlIHdpdGggdGhlIHRlcm1zIG9mIHRoaXMgbGljZW5zZSwgY2xpY2sgIkFncmVlIiB0byBhY2Nlc3MgdGhlIHNvZnR3YXJlLiAgSWYgeW91IGRvIG5vdCBhZ3JlZSwgcHJlc3MgIkRpc2FncmVlLiI=</data>
+ <key>ID</key>
+ <string>5000</string>
+ <key>Name</key>
+ <string>English buttons</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYHRGV1dHNjaAtBa3plcHRpZXJlbghBYmxlaG5lbgdEcnVja2VuClNpY2hlcm4uLi7nS2xpY2tlbiBTaWUgaW4g0kFremVwdGllcmVu0ywgd2VubiBTaWUgbWl0IGRlbiBCZXN0aW1tdW5nZW4gZGVzIFNvZnR3YXJlLUxpemVuenZlcnRyYWdzIGVpbnZlcnN0YW5kZW4gc2luZC4gRmFsbHMgbmljaHQsIGJpdHRlINJBYmxlaG5lbtMgYW5rbGlja2VuLiBTaWUga5pubmVuIGRpZSBTb2Z0d2FyZSBudXIgaW5zdGFsbGllcmVuLCB3ZW5uIFNpZSDSQWt6ZXB0aWVyZW7TIGFuZ2VrbGlja3QgaGFiZW4u</data>
+ <key>ID</key>
+ <string>5001</string>
+ <key>Name</key>
+ <string>German</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYHRW5nbGlzaAVBZ3JlZQhEaXNhZ3JlZQVQcmludAdTYXZlLi4ue0lmIHlvdSBhZ3JlZSB3aXRoIHRoZSB0ZXJtcyBvZiB0aGlzIGxpY2Vuc2UsIHByZXNzICJBZ3JlZSIgdG8gaW5zdGFsbCB0aGUgc29mdHdhcmUuICBJZiB5b3UgZG8gbm90IGFncmVlLCBwcmVzcyAiRGlzYWdyZWUiLg==</data>
+ <key>ID</key>
+ <string>5002</string>
+ <key>Name</key>
+ <string>English</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYHRXNwYZZvbAdBY2VwdGFyCk5vIGFjZXB0YXIISW1wcmltaXIKR3VhcmRhci4uLsBTaSBlc3SHIGRlIGFjdWVyZG8gY29uIGxvcyB0jnJtaW5vcyBkZSBlc3RhIGxpY2VuY2lhLCBwdWxzZSAiQWNlcHRhciIgcGFyYSBpbnN0YWxhciBlbCBzb2Z0d2FyZS4gRW4gZWwgc3VwdWVzdG8gZGUgcXVlIG5vIGVzdI4gZGUgYWN1ZXJkbyBjb24gbG9zIHSOcm1pbm9zIGRlIGVzdGEgbGljZW5jaWEsIHB1bHNlICJObyBhY2VwdGFyLiI=</data>
+ <key>ID</key>
+ <string>5003</string>
+ <key>Name</key>
+ <string>Spanish</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYIRnJhbo1haXMIQWNjZXB0ZXIHUmVmdXNlcghJbXByaW1lcg5FbnJlZ2lzdHJlci4uLrpTaSB2b3VzIGFjY2VwdGV6IGxlcyB0ZXJtZXMgZGUgbGEgcHKOc2VudGUgbGljZW5jZSwgY2xpcXVleiBzdXIgIkFjY2VwdGVyIiBhZmluIGQnaW5zdGFsbGVyIGxlIGxvZ2ljaWVsLiBTaSB2b3VzIG4nkHRlcyBwYXMgZCdhY2NvcmQgYXZlYyBsZXMgdGVybWVzIGRlIGxhIGxpY2VuY2UsIGNsaXF1ZXogc3VyICJSZWZ1c2VyIi4=</data>
+ <key>ID</key>
+ <string>5004</string>
+ <key>Name</key>
+ <string>French</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYISXRhbGlhbm8HQWNjZXR0bwdSaWZpdXRvBlN0YW1wYQtSZWdpc3RyYS4uLn9TZSBhY2NldHRpIGxlIGNvbmRpemlvbmkgZGkgcXVlc3RhIGxpY2VuemEsIGZhaSBjbGljIHN1ICJBY2NldHRvIiBwZXIgaW5zdGFsbGFyZSBpbCBzb2Z0d2FyZS4gQWx0cmltZW50aSBmYWkgY2xpYyBzdSAiUmlmaXV0byIu</data>
+ <key>ID</key>
+ <string>5005</string>
+ <key>Name</key>
+ <string>Italian</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYISmFwYW5lc2UKk6+I04K1gtyCtwyTr4jTgrWC3IK5gvEIiPON/IK3gukHlduRti4uLrSWe4Ncg3SDZ4NFg0eDQY5nl3CLlpH4jF+W8YLMj/CMj4LJk6+I04KzguqC6Y/qjYeCyYLNgUGDXIN0g2eDRYNHg0GC8INDg5ODWINngVuDi4K3gumCvYLfgsmBdZOviNOCtYLcgreBdoLwiZ+CtYLEgq2CvoKzgqKBQoFAk6+I04KzguqCyIKij+qNh4LJgs2BQYF1k6+I04K1gtyCuYLxgXaC8ImfgrWCxIKtgr6Cs4KigUI=</data>
+ <key>ID</key>
+ <string>5006</string>
+ <key>Name</key>
+ <string>Japanese</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYKTmVkZXJsYW5kcwJKYQNOZWUFUHJpbnQJQmV3YWFyLi4upEluZGllbiB1IGFra29vcmQgZ2FhdCBtZXQgZGUgdm9vcndhYXJkZW4gdmFuIGRlemUgbGljZW50aWUsIGt1bnQgdSBvcCAnSmEnIGtsaWtrZW4gb20gZGUgcHJvZ3JhbW1hdHV1ciB0ZSBpbnN0YWxsZXJlbi4gSW5kaWVuIHUgbmlldCBha2tvb3JkIGdhYXQsIGtsaWt0IHUgb3AgJ05lZScu</data>
+ <key>ID</key>
+ <string>5007</string>
+ <key>Name</key>
+ <string>Dutch</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYGU3ZlbnNrCEdvZGuKbm5zBkF2YppqcwhTa3JpdiB1dAhTcGFyYS4uLpNPbSBEdSBnb2Rrim5uZXIgbGljZW5zdmlsbGtvcmVuIGtsaWNrYSBwjCAiR29ka4pubnMiIGaaciBhdHQgaW5zdGFsbGVyYSBwcm9ncmFtcHJvZHVrdGVuLiBPbSBEdSBpbnRlIGdvZGuKbm5lciBsaWNlbnN2aWxsa29yZW4sIGtsaWNrYSBwjCAiQXZimmpzIi4=</data>
+ <key>ID</key>
+ <string>5008</string>
+ <key>Name</key>
+ <string>Swedish</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYRUG9ydHVndZBzLCBCcmFzaWwJQ29uY29yZGFyCURpc2NvcmRhcghJbXByaW1pcglTYWx2YXIuLi6MU2UgZXN0hyBkZSBhY29yZG8gY29tIG9zIHRlcm1vcyBkZXN0YSBsaWNlbo1hLCBwcmVzc2lvbmUgIkNvbmNvcmRhciIgcGFyYSBpbnN0YWxhciBvIHNvZnR3YXJlLiBTZSBui28gZXN0hyBkZSBhY29yZG8sIHByZXNzaW9uZSAiRGlzY29yZGFyIi4=</data>
+ <key>ID</key>
+ <string>5009</string>
+ <key>Name</key>
+ <string>Brazilian Portuguese</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYSU2ltcGxpZmllZCBDaGluZXNlBM2s0uIGsrvNrNLiBLTy06EGtOa0oqGtVMjnufvE+s2s0uKxvtDtv8nQrdLptcTM9b/uo6zH67C0obDNrNLiobHAtLCy17C0y8jtvP6ho8jnufvE+rK7zazS4qOsx+uwtKGwsrvNrNLiobGhow==</data>
+ <key>ID</key>
+ <string>5010</string>
+ <key>Name</key>
+ <string>Simplified Chinese</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYTVHJhZGl0aW9uYWwgQ2hpbmVzZQSmULdOBqSjplC3TgSmQ6ZMBsB4pnOhS1CmcKpHsXqmULdOpbuzXKVpw9K4zKq6sfi02qFBvdCr9qGnplC3TqGopUimd7jLs27F6aFDpnCqR6SjplC3TqFBvdCr9qGnpKOmULdOoaihQw==</data>
+ <key>ID</key>
+ <string>5011</string>
+ <key>Name</key>
+ <string>Traditional Chinese</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYFRGFuc2sERW5pZwVVZW5pZwdVZHNrcml2CkFya2l2ZXIuLi6YSHZpcyBkdSBhY2NlcHRlcmVyIGJldGluZ2Vsc2VybmUgaSBsaWNlbnNhZnRhbGVuLCBza2FsIGR1IGtsaWtrZSBwjCDSRW5pZ9MgZm9yIGF0IGluc3RhbGxlcmUgc29mdHdhcmVuLiBLbGlrIHCMINJVZW5pZ9MgZm9yIGF0IGFubnVsbGVyZSBpbnN0YWxsZXJpbmdlbi4=</data>
+ <key>ID</key>
+ <string>5012</string>
+ <key>Name</key>
+ <string>Danish</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYFU3VvbWkISHl2imtzeW4KRW4gaHl2imtzeQdUdWxvc3RhCVRhbGxlbm5hyW9IeXaKa3N5IGxpc2Vuc3Npc29waW11a3NlbiBlaGRvdCBvc29pdHRhbWFsbGEg1Uh5doprc3nVLiBKb3MgZXQgaHl2imtzeSBzb3BpbXVrc2VuIGVodG9qYSwgb3NvaXRhINVFbiBoeXaKa3N51S4=</data>
+ <key>ID</key>
+ <string>5013</string>
+ <key>Name</key>
+ <string>Finnish</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYRRnJhbo1haXMgY2FuYWRpZW4IQWNjZXB0ZXIHUmVmdXNlcghJbXByaW1lcg5FbnJlZ2lzdHJlci4uLrpTaSB2b3VzIGFjY2VwdGV6IGxlcyB0ZXJtZXMgZGUgbGEgcHKOc2VudGUgbGljZW5jZSwgY2xpcXVleiBzdXIgIkFjY2VwdGVyIiBhZmluIGQnaW5zdGFsbGVyIGxlIGxvZ2ljaWVsLiBTaSB2b3VzIG4nkHRlcyBwYXMgZCdhY2NvcmQgYXZlYyBsZXMgdGVybWVzIGRlIGxhIGxpY2VuY2UsIGNsaXF1ZXogc3VyICJSZWZ1c2VyIi4=</data>
+ <key>ID</key>
+ <string>5014</string>
+ <key>Name</key>
+ <string>French Canadian</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYGS29yZWFuBLW/wMcJtb/AxyC+yMfUBsfBuLDGrgfA+sDlLi4ufrvnv+sgsOi+4LytwMcgs7u/67+hILW/wMfHz7jpLCAitb/AxyIgtNzD37imILStt68gvNLHwcauv/6+7rimILyzxKHHz73KvcO/wC4gtb/Ax8fPwfYgvsq0wrTZuOksICK1v8DHIL7Ix9QiILTcw9+4piC0qbijvcq9w7/ALg==</data>
+ <key>ID</key>
+ <string>5015</string>
+ <key>Name</key>
+ <string>Korean</string>
+ </dict>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAYFTm9yc2sERW5pZwlJa2tlIGVuaWcIU2tyaXYgdXQKQXJraXZlci4uLqNIdmlzIERlIGVyIGVuaWcgaSBiZXN0ZW1tZWxzZW5lIGkgZGVubmUgbGlzZW5zYXZ0YWxlbiwga2xpa2tlciBEZSBwjCAiRW5pZyIta25hcHBlbiBmb3IgjCBpbnN0YWxsZXJlIHByb2dyYW12YXJlbi4gSHZpcyBEZSBpa2tlIGVyIGVuaWcsIGtsaWtrZXIgRGUgcIwgIklra2UgZW5pZyIu</data>
+ <key>ID</key>
+ <string>5016</string>
+ <key>Name</key>
+ <string>Norwegian</string>
+ </dict>
+ </array>
+ <key>TEXT</key>
+ <array>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>APPLICATION_LICENSE_TEXT</data>
+ <key>ID</key>
+ <string>5000</string>
+ <key>Name</key>
+ <string>English SLA</string>
+ </dict>
+ </array>
+ <key>TMPL</key>
+ <array>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>E0RlZmF1bHQgTGFuZ3VhZ2UgSUREV1JEBUNvdW50T0NOVAQqKioqTFNUQwtzeXMgbGFuZyBJRERXUkQebG9jYWwgcmVzIElEIChvZmZzZXQgZnJvbSA1MDAwRFdSRBAyLWJ5dGUgbGFuZ3VhZ2U/RFdSRAQqKioqTFNURQ==</data>
+ <key>ID</key>
+ <string>128</string>
+ <key>Name</key>
+ <string>LPic</string>
+ </dict>
+ </array>
+ <key>plst</key>
+ <array>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0050</string>
+ <key>Data</key>
+ <data>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</data>
+ <key>ID</key>
+ <string>0</string>
+ <key>Name</key>
+ <string></string>
+ </dict>
+ </array>
+ <key>styl</key>
+ <array>
+ <dict>
+ <key>Attributes</key>
+ <string>0x0000</string>
+ <key>Data</key>
+ <data>AAMAAAAAAAwACQAUAAAAAAAAAAAAAAAAACcADAAJABQBAAAAAAAAAAAAAAAAKgAMAAkAFAAAAAAAAAAAAAA=</data>
+ <key>ID</key>
+ <string>5000</string>
+ <key>Name</key>
+ <string>English SLA</string>
+ </dict>
+ </array>
+</dict>
+</plist>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/postinstall.template Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,6 @@
+#!/usr/bin/env sh
+
+chown root:wheel "INSTALL_LOCATION"
+chmod a+rX "INSTALL_LOCATION"
+
+exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/preinstall.template Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,8 @@
+#!/usr/bin/env sh
+
+if [ ! -d "INSTALL_LOCATION" ]
+then
+ mkdir -p "INSTALL_LOCATION"
+fi
+
+exit 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/classes/module-info.java.extra Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018, 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.
+ */
+
+provides jdk.jpackage.internal.Bundler with
+ jdk.jpackage.internal.MacAppBundler,
+ jdk.jpackage.internal.MacAppStoreBundler,
+ jdk.jpackage.internal.MacDmgBundler,
+ jdk.jpackage.internal.MacPkgBundler;
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/native/jpackageapplauncher/main.m Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2012, 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.
+ */
+
+#import <Cocoa/Cocoa.h>
+#include <dlfcn.h>
+#include <unistd.h>
+
+typedef bool (*start_launcher)(int argc, char* argv[]);
+typedef void (*stop_launcher)();
+
+int main(int argc, char *argv[]) {
+#if !__has_feature(objc_arc)
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+#endif
+
+ int result = 1;
+
+ @try {
+ setlocale(LC_ALL, "en_US.utf8");
+
+ NSBundle *mainBundle = [NSBundle mainBundle];
+ NSString *mainBundlePath = [mainBundle bundlePath];
+ NSString *libraryName = [mainBundlePath stringByAppendingPathComponent:@"Contents/MacOS/libapplauncher.dylib"];
+
+ void* library = dlopen([libraryName UTF8String], RTLD_LAZY);
+
+ if (library == NULL) {
+ NSLog(@"%@ not found.\n", libraryName);
+ }
+
+ if (library != NULL) {
+ start_launcher start =
+ (start_launcher)dlsym(library, "start_launcher");
+ stop_launcher stop =
+ (stop_launcher)dlsym(library, "stop_launcher");
+
+ if (start != NULL && stop != NULL) {
+ if (start(argc, argv) == true) {
+ result = 0;
+ stop();
+ }
+ } else if (start == NULL) {
+ NSLog(@"start_launcher not found in %@.\n", libraryName);
+ } else {
+ NSLog(@"stop_launcher not found in %@.\n", libraryName);
+ }
+ dlclose(library);
+ }
+ } @catch (NSException *exception) {
+ NSLog(@"%@: %@", exception, [exception callStackSymbols]);
+ result = 1;
+ }
+
+#if !__has_feature(objc_arc)
+ [pool drain];
+#endif
+
+ return result;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/native/libapplauncher/MacPlatform.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef MACPLATFORM_H
+#define MACPLATFORM_H
+
+#include "Platform.h"
+#include "PosixPlatform.h"
+
+class MacPlatform : virtual public Platform, PosixPlatform {
+private:
+ bool UsePListForConfigFile();
+
+protected:
+ virtual TString getTmpDirString();
+
+public:
+ MacPlatform(void);
+ virtual ~MacPlatform(void);
+
+public:
+ virtual void ShowMessage(TString title, TString description);
+ virtual void ShowMessage(TString description);
+
+ virtual TCHAR* ConvertStringToFileSystemString(
+ TCHAR* Source, bool &release);
+ virtual TCHAR* ConvertFileSystemStringToString(
+ TCHAR* Source, bool &release);
+
+ virtual TString GetPackageRootDirectory();
+ virtual TString GetAppDataDirectory();
+ virtual TString GetBundledJavaLibraryFileName(TString RuntimePath);
+ virtual TString GetAppName();
+
+ TString GetPackageAppDirectory();
+ TString GetPackageLauncherDirectory();
+ TString GetPackageRuntimeBinDirectory();
+
+ virtual ISectionalPropertyContainer* GetConfigFile(TString FileName);
+ virtual TString GetModuleFileName();
+
+ virtual bool IsMainThread();
+ virtual TPlatformNumber GetMemorySize();
+
+ virtual std::map<TString, TString> GetKeys();
+};
+
+
+#endif // MACPLATFORM_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/native/libapplauncher/MacPlatform.mm Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,505 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "Platform.h"
+
+#include "MacPlatform.h"
+#include "Helpers.h"
+#include "Package.h"
+#include "PropertyFile.h"
+#include "IniFile.h"
+
+#include <sys/sysctl.h>
+#include <pthread.h>
+#include <vector>
+#include <signal.h>
+#include <mach-o/dyld.h>
+
+#import <Foundation/Foundation.h>
+#import <AppKit/NSRunningApplication.h>
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreFoundation/CFString.h>
+
+#ifdef __OBJC__
+#import <Cocoa/Cocoa.h>
+#endif //__OBJC__
+
+#define MAC_JPACKAGE_TMP_DIR \
+ "/Library/Application Support/Java/JPackage/tmp"
+
+NSString* StringToNSString(TString Value) {
+ NSString* result = [NSString stringWithCString : Value.c_str()
+ encoding : [NSString defaultCStringEncoding]];
+ return result;
+}
+
+FileSystemStringToString::FileSystemStringToString(const TCHAR* value) {
+ bool release = false;
+ PlatformString lvalue = PlatformString(value);
+ Platform& platform = Platform::GetInstance();
+ TCHAR* buffer = platform.ConvertFileSystemStringToString(lvalue, release);
+ FData = buffer;
+
+ if (buffer != NULL && release == true) {
+ delete[] buffer;
+ }
+}
+
+FileSystemStringToString::operator TString() {
+ return FData;
+}
+
+StringToFileSystemString::StringToFileSystemString(const TString &value) {
+ FRelease = false;
+ PlatformString lvalue = PlatformString(value);
+ Platform& platform = Platform::GetInstance();
+ FData = platform.ConvertStringToFileSystemString(lvalue, FRelease);
+}
+
+StringToFileSystemString::~StringToFileSystemString() {
+ if (FRelease == true) {
+ delete[] FData;
+ }
+}
+
+StringToFileSystemString::operator TCHAR* () {
+ return FData;
+}
+
+MacPlatform::MacPlatform(void) : Platform(), PosixPlatform() {
+}
+
+MacPlatform::~MacPlatform(void) {
+}
+
+TString MacPlatform::GetPackageAppDirectory() {
+ return FilePath::IncludeTrailingSeparator(
+ GetPackageRootDirectory()) + _T("Java");
+}
+
+TString MacPlatform::GetPackageLauncherDirectory() {
+ return FilePath::IncludeTrailingSeparator(
+ GetPackageRootDirectory()) + _T("MacOS");
+}
+
+TString MacPlatform::GetPackageRuntimeBinDirectory() {
+ return FilePath::IncludeTrailingSeparator(GetPackageRootDirectory()) +
+ _T("runtime/Contents/Home/bin");
+}
+
+bool MacPlatform::UsePListForConfigFile() {
+ return FilePath::FileExists(GetConfigFileName()) == false;
+}
+
+void MacPlatform::ShowMessage(TString Title, TString Description) {
+ NSString *ltitle = StringToNSString(Title);
+ NSString *ldescription = StringToNSString(Description);
+
+ NSLog(@"%@:%@", ltitle, ldescription);
+}
+
+void MacPlatform::ShowMessage(TString Description) {
+ TString appname = GetModuleFileName();
+ appname = FilePath::ExtractFileName(appname);
+ ShowMessage(appname, Description);
+}
+
+TString MacPlatform::getTmpDirString() {
+ return TString(MAC_JPACKAGE_TMP_DIR);
+}
+
+TCHAR* MacPlatform::ConvertStringToFileSystemString(TCHAR* Source,
+ bool &release) {
+ TCHAR* result = NULL;
+ release = false;
+ CFStringRef StringRef = CFStringCreateWithCString(kCFAllocatorDefault,
+ Source, kCFStringEncodingUTF8);
+
+ if (StringRef != NULL) {
+ @ try {
+ CFIndex length =
+ CFStringGetMaximumSizeOfFileSystemRepresentation(StringRef);
+ result = new char[length + 1];
+ if (result != NULL) {
+ if (CFStringGetFileSystemRepresentation(StringRef,
+ result, length)) {
+ release = true;
+ } else {
+ delete[] result;
+ result = NULL;
+ }
+ }
+ }
+ @finally
+ {
+ CFRelease(StringRef);
+ }
+ }
+
+ return result;
+}
+
+TCHAR* MacPlatform::ConvertFileSystemStringToString(TCHAR* Source,
+ bool &release) {
+ TCHAR* result = NULL;
+ release = false;
+ CFStringRef StringRef = CFStringCreateWithFileSystemRepresentation(
+ kCFAllocatorDefault, Source);
+
+ if (StringRef != NULL) {
+ @ try {
+ CFIndex length = CFStringGetLength(StringRef);
+
+ if (length > 0) {
+ CFIndex maxSize = CFStringGetMaximumSizeForEncoding(
+ length, kCFStringEncodingUTF8);
+
+ result = new char[maxSize + 1];
+ if (result != NULL) {
+ if (CFStringGetCString(StringRef, result, maxSize,
+ kCFStringEncodingUTF8) == true) {
+ release = true;
+ } else {
+ delete[] result;
+ result = NULL;
+ }
+ }
+ }
+ }
+ @finally
+ {
+ CFRelease(StringRef);
+ }
+ }
+
+ return result;
+}
+
+TString MacPlatform::GetPackageRootDirectory() {
+ NSBundle *mainBundle = [NSBundle mainBundle];
+ NSString *mainBundlePath = [mainBundle bundlePath];
+ NSString *contentsPath =
+ [mainBundlePath stringByAppendingString : @"/Contents"];
+ TString result = [contentsPath UTF8String];
+ return result;
+}
+
+TString MacPlatform::GetAppDataDirectory() {
+ TString result;
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(
+ NSApplicationSupportDirectory, NSUserDomainMask, YES);
+ NSString *applicationSupportDirectory = [paths firstObject];
+ result = [applicationSupportDirectory UTF8String];
+ return result;
+}
+
+TString MacPlatform::GetBundledJavaLibraryFileName(TString RuntimePath) {
+ TString result;
+
+ // first try lib/, then lib/jli
+ result = FilePath::IncludeTrailingSeparator(RuntimePath) +
+ _T("Contents/Home/lib/libjli.dylib");
+
+ if (FilePath::FileExists(result) == false) {
+ result = FilePath::IncludeTrailingSeparator(RuntimePath) +
+ _T("Contents/Home/lib/jli/libjli.dylib");
+
+ if (FilePath::FileExists(result) == false) {
+ // cannot find
+ NSLog(@"Cannot find libjli.dysym!");
+ result = _T("");
+ }
+ }
+
+ return result;
+}
+
+TString MacPlatform::GetAppName() {
+ NSString *appName = [[NSProcessInfo processInfo] processName];
+ TString result = [appName UTF8String];
+ return result;
+}
+
+void PosixProcess::Cleanup() {
+ if (FOutputHandle != 0) {
+ close(FOutputHandle);
+ FOutputHandle = 0;
+ }
+
+ if (FInputHandle != 0) {
+ close(FInputHandle);
+ FInputHandle = 0;
+ }
+
+ sigaction(SIGINT, &savintr, (struct sigaction *) 0);
+ sigaction(SIGQUIT, &savequit, (struct sigaction *) 0);
+ sigprocmask(SIG_SETMASK, &saveblock, (sigset_t *) 0);
+}
+
+#define PIPE_READ 0
+#define PIPE_WRITE 1
+
+bool PosixProcess::Execute(const TString Application,
+ const std::vector<TString> Arguments, bool AWait) {
+ bool result = false;
+
+ if (FRunning == false) {
+ FRunning = true;
+
+ int handles[2];
+
+ if (pipe(handles) == -1) {
+ return false;
+ }
+
+ struct sigaction sa;
+ sa.sa_handler = SIG_IGN;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigemptyset(&savintr.sa_mask);
+ sigemptyset(&savequit.sa_mask);
+ sigaction(SIGINT, &sa, &savintr);
+ sigaction(SIGQUIT, &sa, &savequit);
+ sigaddset(&sa.sa_mask, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &sa.sa_mask, &saveblock);
+
+ FChildPID = fork();
+
+ // PID returned by vfork is 0 for the child process and the
+ // PID of the child process for the parent.
+ if (FChildPID == -1) {
+ // Error
+ TString message = PlatformString::Format(
+ _T("Error: Unable to create process %s"),
+ Application.data());
+ throw Exception(message);
+ } else if (FChildPID == 0) {
+ Cleanup();
+ TString command = Application;
+
+ for (std::vector<TString>::const_iterator iterator =
+ Arguments.begin(); iterator != Arguments.end();
+ iterator++) {
+ command += TString(_T(" ")) + *iterator;
+ }
+#ifdef DEBUG
+ printf("%s\n", command.data());
+#endif // DEBUG
+
+ dup2(handles[PIPE_READ], STDIN_FILENO);
+ dup2(handles[PIPE_WRITE], STDOUT_FILENO);
+
+ close(handles[PIPE_READ]);
+ close(handles[PIPE_WRITE]);
+
+ execl("/bin/sh", "sh", "-c", command.data(), (char *) 0);
+
+ _exit(127);
+ } else {
+ FOutputHandle = handles[PIPE_READ];
+ FInputHandle = handles[PIPE_WRITE];
+
+ if (AWait == true) {
+ ReadOutput();
+ Wait();
+ Cleanup();
+ FRunning = false;
+ result = true;
+ } else {
+ result = true;
+ }
+ }
+ }
+
+ return result;
+}
+
+void AppendPListArrayToIniFile(NSDictionary *infoDictionary,
+ IniFile *result, TString Section) {
+ NSString *sectionKey =
+ [NSString stringWithUTF8String : PlatformString(Section).toMultibyte()];
+ NSDictionary *array = [infoDictionary objectForKey : sectionKey];
+
+ for (id option in array) {
+ if ([option isKindOfClass : [NSString class]]) {
+ TString arg = [option UTF8String];
+
+ TString name;
+ TString value;
+
+ if (Helpers::SplitOptionIntoNameValue(arg, name, value) == true) {
+ result->Append(Section, name, value);
+ }
+ }
+ }
+}
+
+void AppendPListDictionaryToIniFile(NSDictionary *infoDictionary,
+ IniFile *result, TString Section, bool FollowSection = true) {
+ NSDictionary *dictionary = NULL;
+
+ if (FollowSection == true) {
+ NSString *sectionKey = [NSString stringWithUTF8String : PlatformString(
+ Section).toMultibyte()];
+ dictionary = [infoDictionary objectForKey : sectionKey];
+ } else {
+ dictionary = infoDictionary;
+ }
+
+ for (id key in dictionary) {
+ id option = [dictionary valueForKey : key];
+
+ if ([key isKindOfClass : [NSString class]] &&
+ [option isKindOfClass : [NSString class]]) {
+ TString name = [key UTF8String];
+ TString value = [option UTF8String];
+ result->Append(Section, name, value);
+ }
+ }
+}
+
+// Convert parts of the info.plist to the INI format the rest of the jpackage
+// uses unless a jpackage config file exists.
+ISectionalPropertyContainer* MacPlatform::GetConfigFile(TString FileName) {
+ IniFile* result = new IniFile();
+ if (result == NULL) {
+ return NULL;
+ }
+
+ if (UsePListForConfigFile() == false) {
+ result->LoadFromFile(FileName);
+ } else {
+ NSBundle *mainBundle = [NSBundle mainBundle];
+ NSDictionary *infoDictionary = [mainBundle infoDictionary];
+ std::map<TString, TString> keys = GetKeys();
+
+ // JPackage options.
+ AppendPListDictionaryToIniFile(infoDictionary, result,
+ keys[CONFIG_SECTION_APPLICATION], false);
+
+ // jvmargs
+ AppendPListArrayToIniFile(infoDictionary, result,
+ keys[CONFIG_SECTION_JAVAOPTIONS]);
+
+ // Generate AppCDS Cache
+ AppendPListDictionaryToIniFile(infoDictionary, result,
+ keys[CONFIG_SECTION_APPCDSJAVAOPTIONS]);
+ AppendPListDictionaryToIniFile(infoDictionary, result,
+ keys[CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS]);
+
+ // args
+ AppendPListArrayToIniFile(infoDictionary, result,
+ keys[CONFIG_SECTION_ARGOPTIONS]);
+ }
+
+ return result;
+}
+
+TString GetModuleFileNameOSX() {
+ Dl_info module_info;
+ if (dladdr(reinterpret_cast<void*> (GetModuleFileNameOSX),
+ &module_info) == 0) {
+ // Failed to find the symbol we asked for.
+ return std::string();
+ }
+ return TString(module_info.dli_fname);
+}
+
+TString MacPlatform::GetModuleFileName() {
+ TString result;
+ DynamicBuffer<TCHAR> buffer(MAX_PATH);
+ uint32_t size = buffer.GetSize();
+
+ if (_NSGetExecutablePath(buffer.GetData(), &size) == 0) {
+ result = FileSystemStringToString(buffer.GetData());
+ }
+
+ return result;
+}
+
+bool MacPlatform::IsMainThread() {
+ bool result = (pthread_main_np() == 1);
+ return result;
+}
+
+TPlatformNumber MacPlatform::GetMemorySize() {
+ unsigned long long memory = [[NSProcessInfo processInfo] physicalMemory];
+
+ // Convert from bytes to megabytes.
+ TPlatformNumber result = memory / 1048576;
+
+ return result;
+}
+
+std::map<TString, TString> MacPlatform::GetKeys() {
+ std::map<TString, TString> keys;
+
+ if (UsePListForConfigFile() == false) {
+ return Platform::GetKeys();
+ } else {
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_VERSION,
+ _T("app.version")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINJAR_KEY,
+ _T("JavaMainJarName")));
+ keys.insert(std::map<TString,
+ TString>::value_type(CONFIG_MAINMODULE_KEY,
+ _T("JavaMainModuleName")));
+ keys.insert(std::map<TString, TString>::value_type(
+ CONFIG_MAINCLASSNAME_KEY, _T("JavaMainClassName")));
+ keys.insert(std::map<TString, TString>::value_type(
+ CONFIG_CLASSPATH_KEY, _T("JavaAppClasspath")));
+ keys.insert(std::map<TString, TString>::value_type(APP_NAME_KEY,
+ _T("CFBundleName")));
+ keys.insert(std::map<TString, TString>::value_type(JAVA_RUNTIME_KEY,
+ _T("JavaRuntime")));
+ keys.insert(std::map<TString,
+ TString>::value_type(JPACKAGE_APP_DATA_DIR,
+ _T("CFBundleIdentifier")));
+
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_SPLASH_KEY,
+ _T("app.splash")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_MEMORY,
+ _T("app.memory")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_DEBUG,
+ _T("app.debug")));
+ keys.insert(std::map<TString, TString>::value_type(
+ CONFIG_APPLICATION_INSTANCE, _T("app.application.instance")));
+
+ keys.insert(std::map<TString, TString>::value_type(
+ CONFIG_SECTION_APPLICATION, _T("Application")));
+ keys.insert(std::map<TString, TString>::value_type(
+ CONFIG_SECTION_JAVAOPTIONS, _T("JavaOptions")));
+ keys.insert(std::map<TString, TString>::value_type(
+ CONFIG_SECTION_APPCDSJAVAOPTIONS, _T("AppCDSJavaOptions")));
+ keys.insert(std::map<TString, TString>::value_type(
+ CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS,
+ _T("AppCDSGenerateCacheJavaOptions")));
+ keys.insert(std::map<TString, TString>::value_type(
+ CONFIG_SECTION_ARGOPTIONS, _T("ArgOptions")));
+ }
+
+ return keys;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/macosx/native/libapplauncher/PlatformDefs.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+#ifndef PLATFORM_DEFS_H
+#define PLATFORM_DEFS_H
+
+#include <errno.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <dlfcn.h>
+#include <libgen.h>
+#include <string>
+
+using namespace std;
+
+#ifndef MAC
+#define MAC
+#endif
+
+#define _T(x) x
+
+typedef char TCHAR;
+typedef std::string TString;
+#define StringLength strlen
+
+typedef unsigned long DWORD;
+
+#define TRAILING_PATHSEPARATOR '/'
+#define BAD_TRAILING_PATHSEPARATOR '\\'
+#define PATH_SEPARATOR ':'
+#define BAD_PATH_SEPARATOR ';'
+#define MAX_PATH 1000
+
+typedef long TPlatformNumber;
+typedef pid_t TProcessID;
+
+#define HMODULE void*
+
+typedef void* Module;
+typedef void* Procedure;
+
+
+// StringToFileSystemString is a stack object. It's usage is
+// simply inline to convert a
+// TString to a file system string. Example:
+//
+// return dlopen(StringToFileSystemString(FileName), RTLD_LAZY);
+//
+class StringToFileSystemString {
+ // Prohibit Heap-Based StringToFileSystemString
+private:
+ static void *operator new(size_t size);
+ static void operator delete(void *ptr);
+
+private:
+ TCHAR* FData;
+ bool FRelease;
+
+public:
+ StringToFileSystemString(const TString &value);
+ ~StringToFileSystemString();
+
+ operator TCHAR* ();
+};
+
+
+// FileSystemStringToString is a stack object. It's usage is
+// simply inline to convert a
+// file system string to a TString. Example:
+//
+// DynamicBuffer<TCHAR> buffer(MAX_PATH);
+// if (readlink("/proc/self/exe", buffer.GetData(), MAX_PATH) != -1)
+// result = FileSystemStringToString(buffer.GetData());
+//
+class FileSystemStringToString {
+ // Prohibit Heap-Based FileSystemStringToString
+private:
+ static void *operator new(size_t size);
+ static void operator delete(void *ptr);
+
+private:
+ TString FData;
+
+public:
+ FileSystemStringToString(const TCHAR* value);
+
+ operator TString ();
+};
+
+#endif // PLATFORM_DEFS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractAppImageBuilder.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 2015, 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.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.ArrayList;
+
+import jdk.jpackage.internal.resources.ResourceLocator;
+
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+
+/*
+ * AbstractAppImageBuilder
+ * This is sub-classed by each of the platform dependent AppImageBuilder
+ * classes, and contains resource processing code common to all platforms.
+ */
+
+public abstract class AbstractAppImageBuilder {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.MainResources");
+
+ private final Path root;
+
+ public AbstractAppImageBuilder(Map<String, Object> unused, Path root) {
+ this.root = root;
+ }
+
+ public InputStream getResourceAsStream(String name) {
+ return ResourceLocator.class.getResourceAsStream(name);
+ }
+
+ public abstract void prepareApplicationFiles(
+ Map<String, ? super Object> params) throws IOException;
+ public abstract void prepareJreFiles(
+ Map<String, ? super Object> params) throws IOException;
+ public abstract Path getAppDir();
+ public abstract Path getAppModsDir();
+
+ public Path getRuntimeRoot() {
+ return this.root;
+ }
+
+ protected void copyEntry(Path appDir, File srcdir, String fname)
+ throws IOException {
+ Path dest = appDir.resolve(fname);
+ Files.createDirectories(dest.getParent());
+ File src = new File(srcdir, fname);
+ if (src.isDirectory()) {
+ IOUtils.copyRecursive(src.toPath(), dest);
+ } else {
+ Files.copy(src.toPath(), dest);
+ }
+ }
+
+ 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();
+ cfgFileName.delete();
+ File mainJar = JLinkBundlerHelper.getMainJar(params);
+ ModFile.ModType mainJarType = ModFile.ModType.Unknown;
+
+ if (mainJar != null) {
+ mainJarType = new ModFile(mainJar).getModType();
+ }
+
+ String mainModule = StandardBundlerParam.MODULE.fetchFrom(params);
+
+ try (PrintStream out = new PrintStream(cfgFileName)) {
+
+ out.println("[Application]");
+ out.println("app.name=" + APP_NAME.fetchFrom(params));
+ out.println("app.version=" + VERSION.fetchFrom(params));
+ out.println("app.runtime=" + getCfgRuntimeDir());
+ out.println("app.identifier=" + IDENTIFIER.fetchFrom(params));
+ out.println("app.classpath="
+ + getCfgClassPath(CLASSPATH.fetchFrom(params)));
+
+ // The main app is required to be a jar, modular or unnamed.
+ if (mainModule != null &&
+ (mainJarType == ModFile.ModType.Unknown ||
+ mainJarType == ModFile.ModType.ModularJar)) {
+ out.println("app.mainmodule=" + mainModule);
+ } else {
+ String mainClass = JLinkBundlerHelper.getMainClass(params);
+ // If the app is contained in an unnamed jar then launch it the
+ // legacy way and the main class string must be
+ // of the format com/foo/Main
+ if (mainJar != null) {
+ out.println("app.mainjar=" + getCfgAppDir()
+ + mainJar.toPath().getFileName().toString());
+ }
+ if (mainClass != null) {
+ out.println("app.mainclass="
+ + mainClass.replace("\\", "/"));
+ }
+ }
+
+ out.println();
+ out.println("[JavaOptions]");
+ List<String> jvmargs = JAVA_OPTIONS.fetchFrom(params);
+ for (String arg : jvmargs) {
+ out.println(arg);
+ }
+ Path modsDir = getAppModsDir();
+
+ if (modsDir != null && modsDir.toFile().exists()) {
+ out.println("--module-path");
+ out.println(getCfgAppDir().replace("\\","/") + "mods");
+ }
+
+ out.println();
+ out.println("[ArgOptions]");
+ List<String> args = ARGUMENTS.fetchFrom(params);
+ for (String arg : args) {
+ if (arg.endsWith("=") &&
+ (arg.indexOf("=") == arg.lastIndexOf("="))) {
+ out.print(arg.substring(0, arg.length() - 1));
+ out.println("\\=");
+ } else {
+ out.println(arg);
+ }
+ }
+ }
+ }
+
+ File getRuntimeImageDir(File runtimeImageTop) {
+ return runtimeImageTop;
+ }
+
+ protected String getCfgAppDir() {
+ return "$APPDIR" + File.separator
+ + getAppDir().getFileName() + File.separator;
+ }
+
+ protected String getCfgRuntimeDir() {
+ return "$APPDIR" + File.separator + "runtime";
+ }
+
+ String getCfgClassPath(String classpath) {
+ String cfgAppDir = getCfgAppDir();
+
+ StringBuilder sb = new StringBuilder();
+ for (String path : classpath.split("[:;]")) {
+ if (path.length() > 0) {
+ sb.append(cfgAppDir);
+ sb.append(path);
+ sb.append(File.pathSeparator);
+ }
+ }
+ if (sb.length() > 0) {
+ sb.deleteCharAt(sb.length() - 1);
+ }
+ return sb.toString();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractBundler.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2014, 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.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.
+ */
+public abstract class AbstractBundler implements Bundler {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.MainResources");
+
+ static final BundlerParamInfo<File> IMAGES_ROOT =
+ new StandardBundlerParam<>(
+ "imagesRoot",
+ File.class,
+ params -> new File(
+ 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();
+ }
+
+ @Override
+ public void cleanup(Map<String, ? super Object> params) {
+ try {
+ IOUtils.deleteRecursive(
+ StandardBundlerParam.TEMP_ROOT.fetchFrom(params));
+ } catch (IOException e) {
+ Log.verbose(e.getMessage());
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AbstractImageBundler.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2015, 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.text.MessageFormat;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.io.File;
+import java.io.IOException;
+
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+
+/**
+ * AbstractImageBundler
+ *
+ * This is the base class for each of the Application Image Bundlers.
+ *
+ * It contains methods and parameters common to all Image Bundlers.
+ *
+ * Application Image Bundlers are created in "create-app-image" mode,
+ * or as an intermediate step in "create-installer" mode.
+ *
+ * The concrete implementations are in the platform specific Bundlers.
+ */
+public abstract class AbstractImageBundler extends AbstractBundler {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.MainResources");
+
+ public void imageBundleValidation(Map<String, ? super Object> params)
+ throws ConfigException {
+ StandardBundlerParam.validateMainClassInfoFromAppResources(params);
+
+ }
+
+ protected File createRoot(Map<String, ? super Object> params,
+ File outputDirectory, boolean dependentTask, String name)
+ throws PackagerException {
+
+ IOUtils.writableOutputDir(outputDirectory.toPath());
+
+ if (!dependentTask) {
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.creating-app-bundle"),
+ name, outputDirectory.getAbsolutePath()));
+ }
+
+ // NAME will default to CLASS, so the real problem is no MAIN_CLASS
+ if (name == null) {
+ throw new PackagerException("ERR_NoMainClass");
+ }
+
+ // Create directory structure
+ File rootDirectory = new File(outputDirectory, name);
+
+ if (rootDirectory.exists()) {
+ throw new PackagerException("error.root-exists",
+ rootDirectory.getAbsolutePath());
+ }
+
+ rootDirectory.mkdirs();
+
+ return rootDirectory;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AddLauncherArguments.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2018, 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.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.io.File;
+import jdk.jpackage.internal.Arguments.CLIOptions;
+
+/*
+ * AddLauncherArguments
+ *
+ * Processes a add-launcher properties file to create the Map of
+ * bundle params applicable to the add-launcher:
+ *
+ * BundlerParams p = (new AddLauncherArguments(file)).getLauncherMap();
+ *
+ * A add-launcher is another executable program generated by either the
+ * create-app-image mode or the create-installer mode.
+ * The add-launcher may be the same program with different configuration,
+ * or a completely different program created from the same files.
+ *
+ * There may be multiple add-launchers, each created by using the
+ * command line arg "--add-launcher <file path>
+ *
+ * The add-launcher properties file may have any of:
+ *
+ * appVersion
+ * module
+ * main-jar
+ * main-class
+ * icon
+ * arguments
+ * java-options
+ * win-console
+ * linux-app-category
+ *
+ */
+class AddLauncherArguments {
+
+ private final String name;
+ private final String filename;
+ private Map<String, String> allArgs;
+ private Map<String, ? super Object> bundleParams;
+
+ AddLauncherArguments(String name, String filename) {
+ this.name = name;
+ this.filename = filename;
+ }
+
+ private void initLauncherMap() {
+ if (bundleParams != null) {
+ return;
+ }
+
+ allArgs = Arguments.getPropertiesFromFile(filename);
+ allArgs.put(CLIOptions.NAME.getId(), name);
+
+ bundleParams = new HashMap<>();
+ String mainJar = getOptionValue(CLIOptions.MAIN_JAR);
+ String mainClass = getOptionValue(CLIOptions.APPCLASS);
+ String module = getOptionValue(CLIOptions.MODULE);
+
+ if (module != null && mainClass != null) {
+ putUnlessNull(bundleParams, CLIOptions.MODULE.getId(),
+ module + "/" + mainClass);
+ } else if (module != null) {
+ putUnlessNull(bundleParams, CLIOptions.MODULE.getId(),
+ module);
+ } else {
+ putUnlessNull(bundleParams, CLIOptions.MAIN_JAR.getId(),
+ mainJar);
+ putUnlessNull(bundleParams, CLIOptions.APPCLASS.getId(),
+ mainClass);
+ }
+
+ putUnlessNull(bundleParams, CLIOptions.NAME.getId(),
+ getOptionValue(CLIOptions.NAME));
+
+ putUnlessNull(bundleParams, CLIOptions.VERSION.getId(),
+ getOptionValue(CLIOptions.VERSION));
+
+ putUnlessNull(bundleParams, CLIOptions.RELEASE.getId(),
+ getOptionValue(CLIOptions.RELEASE));
+
+ putUnlessNull(bundleParams, CLIOptions.LINUX_CATEGORY.getId(),
+ getOptionValue(CLIOptions.LINUX_CATEGORY));
+
+ putUnlessNull(bundleParams,
+ CLIOptions.WIN_CONSOLE_HINT.getId(),
+ getOptionValue(CLIOptions.WIN_CONSOLE_HINT));
+
+ String value = getOptionValue(CLIOptions.ICON);
+ putUnlessNull(bundleParams, CLIOptions.ICON.getId(),
+ (value == null) ? null : new File(value));
+
+ // "arguments" and "java-options" even if value is null:
+ if (allArgs.containsKey(CLIOptions.ARGUMENTS.getId())) {
+ String argumentStr = getOptionValue(CLIOptions.ARGUMENTS);
+ bundleParams.put(CLIOptions.ARGUMENTS.getId(),
+ Arguments.getArgumentList(argumentStr));
+ }
+
+ if (allArgs.containsKey(CLIOptions.JAVA_OPTIONS.getId())) {
+ String jvmargsStr = getOptionValue(CLIOptions.JAVA_OPTIONS);
+ bundleParams.put(CLIOptions.JAVA_OPTIONS.getId(),
+ Arguments.getArgumentList(jvmargsStr));
+ }
+ }
+
+ private String getOptionValue(CLIOptions option) {
+ if (option == null || allArgs == null) {
+ return null;
+ }
+
+ String id = option.getId();
+
+ if (allArgs.containsKey(id)) {
+ return allArgs.get(id);
+ }
+
+ return null;
+ }
+
+ Map<String, ? super Object> getLauncherMap() {
+ initLauncherMap();
+ return bundleParams;
+ }
+
+ private void putUnlessNull(Map<String, ? super Object> params,
+ String param, Object value) {
+ if (value != null) {
+ params.put(param, value);
+ }
+ }
+
+ static Map<String, ? super Object> merge(
+ Map<String, ? super Object> original,
+ Map<String, ? super Object> additional) {
+ Map<String, ? super Object> tmp = new HashMap<>(original);
+ if (additional.containsKey(CLIOptions.MODULE.getId())) {
+ tmp.remove(CLIOptions.MAIN_JAR.getId());
+ tmp.remove(CLIOptions.APPCLASS.getId());
+ } else if (additional.containsKey(CLIOptions.MAIN_JAR.getId())) {
+ tmp.remove(CLIOptions.MODULE.getId());
+ }
+ if (additional.containsKey(CLIOptions.ARGUMENTS.getId())) {
+ // if add launcher properties file contains "arguments", even with
+ // null value, disregard the "arguments" from command line
+ tmp.remove(CLIOptions.ARGUMENTS.getId());
+ }
+ if (additional.containsKey(CLIOptions.JAVA_OPTIONS.getId())) {
+ // same thing for java-options
+ tmp.remove(CLIOptions.JAVA_OPTIONS.getId());
+ }
+ tmp.putAll(additional);
+ return tmp;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/AppImageFile.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,218 @@
+/*
+ * 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.BufferedWriter;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+
+class AppImageFile {
+
+ // These values will be loaded from AppImage xml file.
+ private final String creatorVersion;
+ private final String creatorPlatform;
+ private final String launcherName;
+ private final List<String> addLauncherNames;
+
+ final static String XML_FILENAME = ".jpackage.xml";
+
+ private final static Map<Platform, String> PLATFORM_LABELS = Map.of(
+ Platform.LINUX, "linux", Platform.WINDOWS, "windows", Platform.MAC,
+ "macOS");
+
+
+ private AppImageFile() {
+ this(null, null, null, null);
+ }
+
+ private AppImageFile(String launcherName, List<String> addLauncherNames,
+ String creatorVersion, String creatorPlatform) {
+ this.launcherName = launcherName;
+ this.addLauncherNames = addLauncherNames;
+ this.creatorVersion = creatorVersion;
+ this.creatorPlatform = creatorPlatform;
+ }
+
+ /**
+ * Would return null to indicate stored command line is invalid.
+ */
+ List<String> getAddLauncherNames() {
+ return addLauncherNames;
+ }
+
+ String getLauncherName() {
+ return launcherName;
+ }
+
+ void verifyCompatible() throws ConfigException {
+ // Just do nohing for now.
+ }
+
+ static void save(Path appImage, Map<String, Object> params)
+ throws IOException {
+ Path xmlFile = appImage.resolve(XML_FILENAME);
+ XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
+
+ try (Writer w = new BufferedWriter(new FileWriter(xmlFile.toFile()))) {
+ XMLStreamWriter xml = xmlFactory.createXMLStreamWriter(w);
+
+ xml.writeStartDocument();
+ xml.writeStartElement("jpackage-state");
+ xml.writeAttribute("version", getVersion());
+ xml.writeAttribute("platform", getPlatform());
+
+ xml.writeStartElement("main-launcher");
+ xml.writeCharacters(APP_NAME.fetchFrom(params));
+ xml.writeEndElement();
+
+ List<Map<String, ? super Object>> addLaunchers =
+ ADD_LAUNCHERS.fetchFrom(params);
+
+ for (int i = 0; i < addLaunchers.size(); i++) {
+ Map<String, ? super Object> sl = addLaunchers.get(i);
+ xml.writeStartElement("add-launcher");
+ xml.writeCharacters(APP_NAME.fetchFrom(sl));
+ xml.writeEndElement();
+ }
+
+ xml.writeEndElement();
+ xml.writeEndDocument();
+ xml.flush();
+ xml.close();
+
+ } catch (XMLStreamException ex) {
+ Log.verbose(ex);
+ throw new IOException(ex);
+ }
+ }
+
+ static AppImageFile load(Path appImageDir) throws IOException {
+ try {
+ Path path = appImageDir.resolve(XML_FILENAME);
+ DocumentBuilderFactory dbf =
+ DocumentBuilderFactory.newDefaultInstance();
+ dbf.setFeature(
+ "http://apache.org/xml/features/nonvalidating/load-external-dtd",
+ false);
+ DocumentBuilder b = dbf.newDocumentBuilder();
+ Document doc = b.parse(new FileInputStream(path.toFile()));
+
+ XPath xPath = XPathFactory.newInstance().newXPath();
+
+ String mainLauncher = xpathQueryNullable(xPath,
+ "/jpackage-state/main-launcher/text()", doc);
+ if (mainLauncher == null) {
+ // No main launcher, this is fatal.
+ return new AppImageFile();
+ }
+
+ List<String> addLaunchers = new ArrayList<String>();
+
+ String platform = xpathQueryNullable(xPath,
+ "/jpackage-state/@platform", doc);
+
+ String version = xpathQueryNullable(xPath,
+ "/jpackage-state/@version", doc);
+
+ NodeList launcherNameNodes = (NodeList) xPath.evaluate(
+ "/jpackage-state/add-launcher/text()", doc,
+ XPathConstants.NODESET);
+
+ for (int i = 0; i != launcherNameNodes.getLength(); i++) {
+ addLaunchers.add(launcherNameNodes.item(i).getNodeValue());
+ }
+
+ AppImageFile file = new AppImageFile(
+ mainLauncher, addLaunchers, version, platform);
+ if (!file.isValid()) {
+ file = new AppImageFile();
+ }
+ return file;
+ } catch (ParserConfigurationException | SAXException ex) {
+ // Let caller sort this out
+ throw new IOException(ex);
+ } catch (XPathExpressionException ex) {
+ // This should never happen as XPath expressions should be correct
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static String xpathQueryNullable(XPath xPath, String xpathExpr,
+ Document xml) throws XPathExpressionException {
+ NodeList nodes = (NodeList) xPath.evaluate(xpathExpr, xml,
+ XPathConstants.NODESET);
+ if (nodes != null && nodes.getLength() > 0) {
+ return nodes.item(0).getNodeValue();
+ }
+ return null;
+ }
+
+ private static String getVersion() {
+ return System.getProperty("java.version");
+ }
+
+ private static String getPlatform() {
+ return PLATFORM_LABELS.get(Platform.getPlatform());
+ }
+
+ private boolean isValid() {
+ if (launcherName == null || launcherName.length() == 0 ||
+ addLauncherNames.indexOf("") != -1) {
+ // Some launchers have empty names. This is invalid.
+ return false;
+ }
+
+ // Add more validation.
+
+ return true;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ApplicationLayout.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,155 @@
+/*
+ * 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.nio.file.Path;
+import java.util.Map;
+
+
+/**
+ * Application directory layout.
+ */
+public final class ApplicationLayout implements PathGroup.Facade<ApplicationLayout> {
+ enum PathRole {
+ RUNTIME, APP, LAUNCHERS, DESKTOP, APP_MODS, DLLS
+ }
+
+ ApplicationLayout(Map<Object, Path> paths) {
+ data = new PathGroup(paths);
+ }
+
+ private ApplicationLayout(PathGroup data) {
+ this.data = data;
+ }
+
+ @Override
+ public PathGroup pathGroup() {
+ return data;
+ }
+
+ @Override
+ public ApplicationLayout resolveAt(Path root) {
+ return new ApplicationLayout(pathGroup().resolveAt(root));
+ }
+
+ /**
+ * Path to launchers directory.
+ */
+ public Path launchersDirectory() {
+ return pathGroup().getPath(PathRole.LAUNCHERS);
+ }
+
+ /**
+ * Path to directory with dynamic libraries.
+ */
+ public Path dllDirectory() {
+ return pathGroup().getPath(PathRole.DLLS);
+ }
+
+ /**
+ * Path to application data directory.
+ */
+ public Path appDirectory() {
+ return pathGroup().getPath(PathRole.APP);
+ }
+
+ /**
+ * Path to Java runtime directory.
+ */
+ public Path runtimeDirectory() {
+ return pathGroup().getPath(PathRole.RUNTIME);
+ }
+
+ /**
+ * Path to application mods directory.
+ */
+ public Path appModsDirectory() {
+ return pathGroup().getPath(PathRole.APP_MODS);
+ }
+
+ /**
+ * Path to directory with application's desktop integration files.
+ */
+ public Path destktopIntegrationDirectory() {
+ return pathGroup().getPath(PathRole.DESKTOP);
+ }
+
+ static ApplicationLayout linuxAppImage() {
+ return new ApplicationLayout(Map.of(
+ PathRole.LAUNCHERS, Path.of("bin"),
+ PathRole.APP, Path.of("lib/app"),
+ PathRole.RUNTIME, Path.of("lib/runtime"),
+ PathRole.DESKTOP, Path.of("lib"),
+ PathRole.DLLS, Path.of("lib"),
+ PathRole.APP_MODS, Path.of("lib/app/mods")
+ ));
+ }
+
+ static ApplicationLayout windowsAppImage() {
+ return new ApplicationLayout(Map.of(
+ PathRole.LAUNCHERS, Path.of(""),
+ PathRole.APP, Path.of("app"),
+ PathRole.RUNTIME, Path.of("runtime"),
+ PathRole.DESKTOP, Path.of(""),
+ PathRole.DLLS, Path.of(""),
+ PathRole.APP_MODS, Path.of("app/mods")
+ ));
+ }
+
+ static ApplicationLayout macAppImage() {
+ return new ApplicationLayout(Map.of(
+ PathRole.LAUNCHERS, Path.of("Contents/MacOS"),
+ PathRole.APP, Path.of("Contents/Java"),
+ PathRole.RUNTIME, Path.of("Contents/runtime"),
+ PathRole.DESKTOP, Path.of("Contents/Resources"),
+ PathRole.DLLS, Path.of("Contents/MacOS"),
+ PathRole.APP_MODS, Path.of("Contents/Java/mods")
+ ));
+ }
+
+
+
+ public static ApplicationLayout platformAppImage() {
+ if (Platform.isWindows()) {
+ return windowsAppImage();
+ }
+
+ if (Platform.isLinux()) {
+ return linuxAppImage();
+ }
+
+ if (Platform.isMac()) {
+ return macAppImage();
+ }
+
+ throw new IllegalArgumentException("Unknown platform");
+ }
+
+ static ApplicationLayout javaRuntime() {
+ return new ApplicationLayout(Map.of(PathRole.RUNTIME, Path.of("")));
+ }
+
+ private final PathGroup data;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ArgAction.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2018, 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;
+
+@FunctionalInterface
+interface ArgAction {
+ void execute();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Arguments.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,801 @@
+/*
+ * Copyright (c) 2018, 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.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+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.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.stream.Stream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Arguments
+ *
+ * This class encapsulates and processes the command line arguments,
+ * in effect, implementing all the work of jpackage tool.
+ *
+ * The primary entry point, processArguments():
+ * Processes and validates command line arguments, constructing DeployParams.
+ * Validates the DeployParams, and generate the BundleParams.
+ * Generates List of Bundlers from BundleParams valid for this platform.
+ * Executes each Bundler in the list.
+ */
+public class Arguments {
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.MainResources");
+
+ private static final String FA_EXTENSIONS = "extension";
+ private static final String FA_CONTENT_TYPE = "mime-type";
+ private static final String FA_DESCRIPTION = "description";
+ private static final String FA_ICON = "icon";
+
+ // regexp for parsing args (for example, for additional launchers)
+ private static Pattern pattern = Pattern.compile(
+ "(?:(?:([\"'])(?:\\\\\\1|.)*?(?:\\1|$))|(?:\\\\[\"'\\s]|[^\\s]))++");
+
+ private DeployParams deployParams = null;
+
+ private int pos = 0;
+ private List<String> argList = null;
+
+ private List<CLIOptions> allOptions = null;
+
+ private String input = null;
+ private String output = null;
+
+ private boolean hasMainJar = false;
+ private boolean hasMainClass = false;
+ private boolean hasMainModule = false;
+ public boolean userProvidedBuildRoot = false;
+
+ private String buildRoot = null;
+ private String mainJarPath = null;
+
+ private static boolean runtimeInstaller = false;
+
+ private List<AddLauncherArguments> addLaunchers = null;
+
+ private static Map<String, CLIOptions> argIds = new HashMap<>();
+ private static Map<String, CLIOptions> argShortIds = new HashMap<>();
+
+ static {
+ // init maps for parsing arguments
+ (EnumSet.allOf(CLIOptions.class)).forEach(option -> {
+ argIds.put(option.getIdWithPrefix(), option);
+ if (option.getShortIdWithPrefix() != null) {
+ argShortIds.put(option.getShortIdWithPrefix(), option);
+ }
+ });
+ }
+
+ public Arguments(String[] args) {
+ argList = new ArrayList<String>(args.length);
+ for (String arg : args) {
+ argList.add(arg);
+ }
+ Log.verbose ("\njpackage argument list: \n" + argList + "\n");
+ pos = 0;
+
+ deployParams = new DeployParams();
+
+ allOptions = new ArrayList<>();
+
+ addLaunchers = new ArrayList<>();
+
+ output = Paths.get("").toAbsolutePath().toString();
+ deployParams.setOutput(new File(output));
+ }
+
+ // CLIOptions is public for DeployParamsTest
+ public enum CLIOptions {
+ PACKAGE_TYPE("package-type", OptionCategories.PROPERTY, () -> {
+ context().deployParams.setTargetFormat(popArg());
+ }),
+
+ INPUT ("input", "i", OptionCategories.PROPERTY, () -> {
+ context().input = popArg();
+ setOptionValue("input", context().input);
+ }),
+
+ OUTPUT ("dest", "d", OptionCategories.PROPERTY, () -> {
+ context().output = popArg();
+ context().deployParams.setOutput(new File(context().output));
+ }),
+
+ DESCRIPTION ("description", OptionCategories.PROPERTY),
+
+ VENDOR ("vendor", OptionCategories.PROPERTY),
+
+ APPCLASS ("main-class", OptionCategories.PROPERTY, () -> {
+ context().hasMainClass = true;
+ setOptionValue("main-class", popArg());
+ }),
+
+ NAME ("name", "n", OptionCategories.PROPERTY),
+
+ VERBOSE ("verbose", OptionCategories.PROPERTY, () -> {
+ setOptionValue("verbose", true);
+ Log.setVerbose();
+ }),
+
+ RESOURCE_DIR("resource-dir",
+ OptionCategories.PROPERTY, () -> {
+ String resourceDir = popArg();
+ setOptionValue("resource-dir", resourceDir);
+ }),
+
+ ARGUMENTS ("arguments", OptionCategories.PROPERTY, () -> {
+ List<String> arguments = getArgumentList(popArg());
+ setOptionValue("arguments", arguments);
+ }),
+
+ ICON ("icon", OptionCategories.PROPERTY),
+
+ COPYRIGHT ("copyright", OptionCategories.PROPERTY),
+
+ LICENSE_FILE ("license-file", OptionCategories.PROPERTY),
+
+ VERSION ("app-version", OptionCategories.PROPERTY),
+
+ RELEASE ("linux-app-release", OptionCategories.PROPERTY),
+
+ JAVA_OPTIONS ("java-options", OptionCategories.PROPERTY, () -> {
+ List<String> args = getArgumentList(popArg());
+ args.forEach(a -> setOptionValue("java-options", a));
+ }),
+
+ FILE_ASSOCIATIONS ("file-associations",
+ OptionCategories.PROPERTY, () -> {
+ Map<String, ? super Object> args = new HashMap<>();
+
+ // load .properties file
+ Map<String, String> initialMap = getPropertiesFromFile(popArg());
+
+ String ext = initialMap.get(FA_EXTENSIONS);
+ if (ext != null) {
+ args.put(StandardBundlerParam.FA_EXTENSIONS.getID(), ext);
+ }
+
+ String type = initialMap.get(FA_CONTENT_TYPE);
+ if (type != null) {
+ args.put(StandardBundlerParam.FA_CONTENT_TYPE.getID(), type);
+ }
+
+ String desc = initialMap.get(FA_DESCRIPTION);
+ if (desc != null) {
+ args.put(StandardBundlerParam.FA_DESCRIPTION.getID(), desc);
+ }
+
+ String icon = initialMap.get(FA_ICON);
+ if (icon != null) {
+ args.put(StandardBundlerParam.FA_ICON.getID(), icon);
+ }
+
+ ArrayList<Map<String, ? super Object>> associationList =
+ new ArrayList<Map<String, ? super Object>>();
+
+ associationList.add(args);
+
+ // check that we really add _another_ value to the list
+ setOptionValue("file-associations", associationList);
+
+ }),
+
+ ADD_LAUNCHER ("add-launcher",
+ OptionCategories.PROPERTY, () -> {
+ String spec = popArg();
+ String name = null;
+ String filename = spec;
+ if (spec.contains("=")) {
+ String[] values = spec.split("=", 2);
+ name = values[0];
+ filename = values[1];
+ }
+ context().addLaunchers.add(
+ new AddLauncherArguments(name, filename));
+ }),
+
+ TEMP_ROOT ("temp", OptionCategories.PROPERTY, () -> {
+ context().buildRoot = popArg();
+ context().userProvidedBuildRoot = true;
+ setOptionValue("temp", context().buildRoot);
+ }),
+
+ INSTALL_DIR ("install-dir", OptionCategories.PROPERTY),
+
+ PREDEFINED_APP_IMAGE ("app-image", OptionCategories.PROPERTY),
+
+ PREDEFINED_RUNTIME_IMAGE ("runtime-image", OptionCategories.PROPERTY),
+
+ MAIN_JAR ("main-jar", OptionCategories.PROPERTY, () -> {
+ context().mainJarPath = popArg();
+ context().hasMainJar = true;
+ setOptionValue("main-jar", context().mainJarPath);
+ }),
+
+ MODULE ("module", "m", OptionCategories.MODULAR, () -> {
+ context().hasMainModule = true;
+ setOptionValue("module", popArg());
+ }),
+
+ ADD_MODULES ("add-modules", OptionCategories.MODULAR),
+
+ MODULE_PATH ("module-path", "p", OptionCategories.MODULAR),
+
+ MAC_SIGN ("mac-sign", "s", OptionCategories.PLATFORM_MAC, () -> {
+ setOptionValue("mac-sign", true);
+ }),
+
+ MAC_BUNDLE_NAME ("mac-package-name", OptionCategories.PLATFORM_MAC),
+
+ MAC_BUNDLE_IDENTIFIER("mac-package-identifier",
+ OptionCategories.PLATFORM_MAC),
+
+ MAC_APP_STORE_CATEGORY ("mac-app-store-category",
+ OptionCategories.PLATFORM_MAC),
+
+ MAC_BUNDLE_SIGNING_PREFIX ("mac-package-signing-prefix",
+ OptionCategories.PLATFORM_MAC),
+
+ MAC_SIGNING_KEY_NAME ("mac-signing-key-user-name",
+ OptionCategories.PLATFORM_MAC),
+
+ MAC_SIGNING_KEYCHAIN ("mac-signing-keychain",
+ OptionCategories.PLATFORM_MAC),
+
+ MAC_APP_STORE_ENTITLEMENTS ("mac-app-store-entitlements",
+ OptionCategories.PLATFORM_MAC),
+
+ WIN_MENU_HINT ("win-menu", OptionCategories.PLATFORM_WIN, () -> {
+ setOptionValue("win-menu", true);
+ }),
+
+ WIN_MENU_GROUP ("win-menu-group", OptionCategories.PLATFORM_WIN),
+
+ WIN_SHORTCUT_HINT ("win-shortcut",
+ OptionCategories.PLATFORM_WIN, () -> {
+ setOptionValue("win-shortcut", true);
+ }),
+
+ WIN_PER_USER_INSTALLATION ("win-per-user-install",
+ OptionCategories.PLATFORM_WIN, () -> {
+ setOptionValue("win-per-user-install", false);
+ }),
+
+ WIN_DIR_CHOOSER ("win-dir-chooser",
+ OptionCategories.PLATFORM_WIN, () -> {
+ setOptionValue("win-dir-chooser", true);
+ }),
+
+ WIN_UPGRADE_UUID ("win-upgrade-uuid",
+ OptionCategories.PLATFORM_WIN),
+
+ WIN_CONSOLE_HINT ("win-console", OptionCategories.PLATFORM_WIN, () -> {
+ setOptionValue("win-console", true);
+ }),
+
+ LINUX_BUNDLE_NAME ("linux-package-name",
+ OptionCategories.PLATFORM_LINUX),
+
+ LINUX_DEB_MAINTAINER ("linux-deb-maintainer",
+ OptionCategories.PLATFORM_LINUX),
+
+ LINUX_CATEGORY ("linux-app-category",
+ OptionCategories.PLATFORM_LINUX),
+
+ LINUX_RPM_LICENSE_TYPE ("linux-rpm-license-type",
+ OptionCategories.PLATFORM_LINUX),
+
+ LINUX_PACKAGE_DEPENDENCIES ("linux-package-deps",
+ OptionCategories.PLATFORM_LINUX),
+
+ LINUX_SHORTCUT_HINT ("linux-shortcut",
+ OptionCategories.PLATFORM_LINUX, () -> {
+ setOptionValue("linux-shortcut", true);
+ }),
+
+ LINUX_MENU_GROUP ("linux-menu-group", OptionCategories.PLATFORM_LINUX);
+
+ private final String id;
+ private final String shortId;
+ private final OptionCategories category;
+ private final ArgAction action;
+ private static Arguments argContext;
+
+ private CLIOptions(String id, OptionCategories category) {
+ this(id, null, category, null);
+ }
+
+ private CLIOptions(String id, String shortId,
+ OptionCategories category) {
+ this(id, shortId, category, null);
+ }
+
+ private CLIOptions(String id,
+ OptionCategories category, ArgAction action) {
+ this(id, null, category, action);
+ }
+
+ private CLIOptions(String id, String shortId,
+ OptionCategories category, ArgAction action) {
+ this.id = id;
+ this.shortId = shortId;
+ this.action = action;
+ this.category = category;
+ }
+
+ static void setContext(Arguments context) {
+ argContext = context;
+ }
+
+ public static Arguments context() {
+ if (argContext != null) {
+ return argContext;
+ } else {
+ throw new RuntimeException("Argument context is not set.");
+ }
+ }
+
+ public String getId() {
+ return this.id;
+ }
+
+ String getIdWithPrefix() {
+ return "--" + this.id;
+ }
+
+ String getShortIdWithPrefix() {
+ return this.shortId == null ? null : "-" + this.shortId;
+ }
+
+ void execute() {
+ if (action != null) {
+ action.execute();
+ } else {
+ defaultAction();
+ }
+ }
+
+ private void defaultAction() {
+ context().deployParams.addBundleArgument(id, popArg());
+ }
+
+ private static void setOptionValue(String option, Object value) {
+ context().deployParams.addBundleArgument(option, value);
+ }
+
+ private static String popArg() {
+ nextArg();
+ return (context().pos >= context().argList.size()) ?
+ "" : context().argList.get(context().pos);
+ }
+
+ private static String getArg() {
+ return (context().pos >= context().argList.size()) ?
+ "" : context().argList.get(context().pos);
+ }
+
+ private static void nextArg() {
+ context().pos++;
+ }
+
+ private static boolean hasNextArg() {
+ return context().pos < context().argList.size();
+ }
+ }
+
+ enum OptionCategories {
+ MODULAR,
+ PROPERTY,
+ PLATFORM_MAC,
+ PLATFORM_WIN,
+ PLATFORM_LINUX;
+ }
+
+ public boolean processArguments() {
+ try {
+
+ // init context of arguments
+ CLIOptions.setContext(this);
+
+ // parse cmd line
+ String arg;
+ CLIOptions option;
+ for (; CLIOptions.hasNextArg(); CLIOptions.nextArg()) {
+ arg = CLIOptions.getArg();
+ if ((option = toCLIOption(arg)) != null) {
+ // found a CLI option
+ allOptions.add(option);
+ option.execute();
+ } else {
+ throw new PackagerException("ERR_InvalidOption", arg);
+ }
+ }
+
+ if (hasMainJar && !hasMainClass) {
+ // try to get main-class from manifest
+ String mainClass = getMainClassFromManifest();
+ if (mainClass != null) {
+ CLIOptions.setOptionValue(
+ CLIOptions.APPCLASS.getId(), mainClass);
+ }
+ }
+
+ // display error for arguments that are not supported
+ // for current configuration.
+
+ validateArguments();
+
+ addResources(deployParams, input, mainJarPath);
+
+ List<Map<String, ? super Object>> launchersAsMap =
+ new ArrayList<>();
+
+ for (AddLauncherArguments sl : addLaunchers) {
+ launchersAsMap.add(sl.getLauncherMap());
+ }
+
+ deployParams.addBundleArgument(
+ StandardBundlerParam.ADD_LAUNCHERS.getID(),
+ launchersAsMap);
+
+ // at this point deployParams should be already configured
+
+ deployParams.validate();
+
+ BundleParams bp = deployParams.getBundleParams();
+
+ // validate name(s)
+ ArrayList<String> usedNames = new ArrayList<String>();
+ usedNames.add(bp.getName()); // add main app name
+
+ for (AddLauncherArguments sl : addLaunchers) {
+ Map<String, ? super Object> slMap = sl.getLauncherMap();
+ String slName =
+ (String) slMap.get(Arguments.CLIOptions.NAME.getId());
+ if (slName == null) {
+ throw new PackagerException("ERR_NoAddLauncherName");
+ }
+ // same rules apply to additional launcher names as app name
+ DeployParams.validateName(slName, false);
+ for (String usedName : usedNames) {
+ if (slName.equals(usedName)) {
+ throw new PackagerException("ERR_NoUniqueName");
+ }
+ }
+ usedNames.add(slName);
+ }
+ if (runtimeInstaller && bp.getName() == null) {
+ throw new PackagerException("ERR_NoJreInstallerName");
+ }
+
+ generateBundle(bp.getBundleParamsAsMap());
+ return true;
+ } catch (Exception e) {
+ if (Log.isVerbose()) {
+ Log.verbose(e);
+ } else {
+ String msg1 = e.getMessage();
+ Log.error(msg1);
+ if (e.getCause() != null && e.getCause() != e) {
+ String msg2 = e.getCause().getMessage();
+ if (msg2 != null && !msg1.contains(msg2)) {
+ Log.error(msg2);
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ private void validateArguments() throws PackagerException {
+ String type = deployParams.getTargetFormat();
+ String ptype = (type != null) ? type : "default";
+ boolean imageOnly = deployParams.isTargetAppImage();
+ boolean hasAppImage = allOptions.contains(
+ CLIOptions.PREDEFINED_APP_IMAGE);
+ boolean hasRuntime = allOptions.contains(
+ CLIOptions.PREDEFINED_RUNTIME_IMAGE);
+ boolean installerOnly = !imageOnly && hasAppImage;
+ runtimeInstaller = !imageOnly && hasRuntime && !hasAppImage &&
+ !hasMainModule && !hasMainJar;
+
+ for (CLIOptions option : allOptions) {
+ if (!ValidOptions.checkIfSupported(option)) {
+ // includes option valid only on different platform
+ throw new PackagerException("ERR_UnsupportedOption",
+ option.getIdWithPrefix());
+ }
+ if (imageOnly) {
+ if (!ValidOptions.checkIfImageSupported(option)) {
+ throw new PackagerException("ERR_InvalidTypeOption",
+ option.getIdWithPrefix(), type);
+ }
+ } else if (installerOnly || runtimeInstaller) {
+ if (!ValidOptions.checkIfInstallerSupported(option)) {
+ if (runtimeInstaller) {
+ throw new PackagerException("ERR_NoInstallerEntryPoint",
+ option.getIdWithPrefix());
+ } else {
+ throw new PackagerException("ERR_InvalidTypeOption",
+ option.getIdWithPrefix(), ptype);
+ }
+ }
+ }
+ }
+ if (installerOnly && hasRuntime) {
+ // note --runtime-image is only for image or runtime installer.
+ throw new PackagerException("ERR_InvalidTypeOption",
+ CLIOptions.PREDEFINED_RUNTIME_IMAGE.getIdWithPrefix(),
+ ptype);
+ }
+ if (hasMainJar && hasMainModule) {
+ throw new PackagerException("ERR_BothMainJarAndModule");
+ }
+ if (imageOnly && !hasMainJar && !hasMainModule) {
+ throw new PackagerException("ERR_NoEntryPoint");
+ }
+ }
+
+ private jdk.jpackage.internal.Bundler getPlatformBundler() {
+ boolean appImage = deployParams.isTargetAppImage();
+ String type = deployParams.getTargetFormat();
+ String bundleType = (appImage ? "IMAGE" : "INSTALLER");
+
+ for (jdk.jpackage.internal.Bundler bundler :
+ Bundlers.createBundlersInstance().getBundlers(bundleType)) {
+ if (type == null) {
+ if (bundler.isDefault()
+ && bundler.supported(runtimeInstaller)) {
+ return bundler;
+ }
+ } else {
+ if ((appImage || type.equalsIgnoreCase(bundler.getID()))
+ && bundler.supported(runtimeInstaller)) {
+ return bundler;
+ }
+ }
+ }
+ return null;
+ }
+
+ private void generateBundle(Map<String,? super Object> params)
+ throws PackagerException {
+
+ boolean bundleCreated = false;
+
+ // the temp dir needs to be fetched from the params early,
+ // to prevent each copy of the params (such as may be used for
+ // additional launchers) from generating a separate temp dir when
+ // the default is used (the default is a new temp directory)
+ // The bundler.cleanup() below would not otherwise be able to
+ // clean these extra (and unneeded) temp directories.
+ StandardBundlerParam.TEMP_ROOT.fetchFrom(params);
+
+ // determine what bundler to run
+ jdk.jpackage.internal.Bundler bundler = getPlatformBundler();
+
+ if (bundler == null) {
+ throw new PackagerException("ERR_InvalidInstallerType",
+ deployParams.getTargetFormat());
+ }
+
+ Map<String, ? super Object> localParams = new HashMap<>(params);
+ try {
+ bundler.validate(localParams);
+ File result = bundler.execute(localParams, deployParams.outdir);
+ if (result == null) {
+ throw new PackagerException("MSG_BundlerFailed",
+ bundler.getID(), bundler.getName());
+ }
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.bundle-created"),
+ bundler.getName()));
+ } catch (ConfigException e) {
+ Log.verbose(e);
+ if (e.getAdvice() != null) {
+ throw new PackagerException(e, "MSG_BundlerConfigException",
+ bundler.getName(), e.getMessage(), e.getAdvice());
+ } else {
+ throw new PackagerException(e,
+ "MSG_BundlerConfigExceptionNoAdvice",
+ bundler.getName(), e.getMessage());
+ }
+ } catch (RuntimeException re) {
+ Log.verbose(re);
+ throw new PackagerException(re, "MSG_BundlerRuntimeException",
+ bundler.getName(), re.toString());
+ } finally {
+ if (userProvidedBuildRoot) {
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.debug-working-directory"),
+ (new File(buildRoot)).getAbsolutePath()));
+ } else {
+ // always clean up the temporary directory created
+ // when --temp option not used.
+ bundler.cleanup(localParams);
+ }
+ }
+ }
+
+ private void addResources(DeployParams deployParams,
+ String inputdir, String mainJar) throws PackagerException {
+
+ if (inputdir == null || inputdir.isEmpty()) {
+ return;
+ }
+
+ File baseDir = new File(inputdir);
+
+ if (!baseDir.isDirectory()) {
+ throw new PackagerException("ERR_InputNotDirectory", inputdir);
+ }
+ if (!baseDir.canRead()) {
+ throw new PackagerException("ERR_CannotReadInputDir", inputdir);
+ }
+
+ List<String> fileNames;
+ fileNames = new ArrayList<>();
+ try (Stream<Path> files = Files.list(baseDir.toPath())) {
+ files.forEach(file -> fileNames.add(
+ file.getFileName().toString()));
+ } catch (IOException e) {
+ Log.error("Unable to add resources: " + e.getMessage());
+ }
+ fileNames.forEach(file -> deployParams.addResource(baseDir, file));
+
+ deployParams.setClasspath(mainJar);
+ }
+
+ static CLIOptions toCLIOption(String arg) {
+ CLIOptions option;
+ if ((option = argIds.get(arg)) == null) {
+ option = argShortIds.get(arg);
+ }
+ return option;
+ }
+
+ static Map<String, String> getPropertiesFromFile(String filename) {
+ Map<String, String> map = new HashMap<>();
+ // load properties file
+ File file = new File(filename);
+ Properties properties = new Properties();
+ try (FileInputStream in = new FileInputStream(file)) {
+ properties.load(in);
+ } catch (IOException e) {
+ Log.error("Exception: " + e.getMessage());
+ }
+
+ for (final String name: properties.stringPropertyNames()) {
+ map.put(name, properties.getProperty(name));
+ }
+
+ return map;
+ }
+
+ static List<String> getArgumentList(String inputString) {
+ List<String> list = new ArrayList<>();
+ if (inputString == null || inputString.isEmpty()) {
+ return list;
+ }
+
+ // The "pattern" regexp attempts to abide to the rule that
+ // strings are delimited by whitespace unless surrounded by
+ // quotes, then it is anything (including spaces) in the quotes.
+ Matcher m = pattern.matcher(inputString);
+ while (m.find()) {
+ String s = inputString.substring(m.start(), m.end()).trim();
+ // Ensure we do not have an empty string. trim() will take care of
+ // whitespace only strings. The regex preserves quotes and escaped
+ // chars so we need to clean them before adding to the List
+ if (!s.isEmpty()) {
+ list.add(unquoteIfNeeded(s));
+ }
+ }
+ return list;
+ }
+
+ private static String unquoteIfNeeded(String in) {
+ if (in == null) {
+ return null;
+ }
+
+ if (in.isEmpty()) {
+ return "";
+ }
+
+ // Use code points to preserve non-ASCII chars
+ StringBuilder sb = new StringBuilder();
+ int codeLen = in.codePointCount(0, in.length());
+ int quoteChar = -1;
+ for (int i = 0; i < codeLen; i++) {
+ int code = in.codePointAt(i);
+ if (code == '"' || code == '\'') {
+ // If quote is escaped make sure to copy it
+ if (i > 0 && in.codePointAt(i - 1) == '\\') {
+ sb.deleteCharAt(sb.length() - 1);
+ sb.appendCodePoint(code);
+ continue;
+ }
+ if (quoteChar != -1) {
+ if (code == quoteChar) {
+ // close quote, skip char
+ quoteChar = -1;
+ } else {
+ sb.appendCodePoint(code);
+ }
+ } else {
+ // opening quote, skip char
+ quoteChar = code;
+ }
+ } else {
+ sb.appendCodePoint(code);
+ }
+ }
+ return sb.toString();
+ }
+
+ private String getMainClassFromManifest() {
+ if (mainJarPath == null ||
+ input == null ) {
+ return null;
+ }
+
+ JarFile jf;
+ try {
+ File file = new File(input, mainJarPath);
+ if (!file.exists()) {
+ return null;
+ }
+ jf = new JarFile(file);
+ Manifest m = jf.getManifest();
+ Attributes attrs = (m != null) ? m.getMainAttributes() : null;
+ if (attrs != null) {
+ return attrs.getValue(Attributes.Name.MAIN_CLASS);
+ }
+ } catch (IOException ignore) {}
+ return null;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BasicBundlers.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2014, 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.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.ServiceLoader;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * BasicBundlers
+ *
+ * A basic bundlers collection that loads the default bundlers.
+ * Loads the common bundlers.
+ * <UL>
+ * <LI>Windows file image</LI>
+ * <LI>Mac .app</LI>
+ * <LI>Linux file image</LI>
+ * <LI>Windows MSI</LI>
+ * <LI>Windows EXE</LI>
+ * <LI>Mac DMG</LI>
+ * <LI>Mac PKG</LI>
+ * <LI>Linux DEB</LI>
+ * <LI>Linux RPM</LI>
+ *
+ * </UL>
+ */
+public class BasicBundlers implements Bundlers {
+
+ boolean defaultsLoaded = false;
+
+ private final Collection<Bundler> bundlers = new CopyOnWriteArrayList<>();
+
+ @Override
+ public Collection<Bundler> getBundlers() {
+ return Collections.unmodifiableCollection(bundlers);
+ }
+
+ @Override
+ public Collection<Bundler> getBundlers(String type) {
+ if (type == null) return Collections.emptySet();
+ switch (type) {
+ case "NONE":
+ return Collections.emptySet();
+ case "ALL":
+ return getBundlers();
+ default:
+ return Arrays.asList(getBundlers().stream()
+ .filter(b -> type.equalsIgnoreCase(b.getBundleType()))
+ .toArray(Bundler[]::new));
+ }
+ }
+
+ // Loads bundlers from the META-INF/services direct
+ @Override
+ public void loadBundlersFromServices(ClassLoader cl) {
+ ServiceLoader<Bundler> loader = ServiceLoader.load(Bundler.class, cl);
+ for (Bundler aLoader : loader) {
+ bundlers.add(aLoader);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundleParams.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2012, 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.File;
+import java.io.IOException;
+import java.util.*;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+
+public class BundleParams {
+
+ final protected Map<String, ? super Object> params;
+
+ // RelativeFileSet
+ public static final String PARAM_APP_RESOURCES = "appResources";
+
+ // String - Icon file name
+ public static final String PARAM_ICON = "icon";
+
+ // String - Name of bundle file and native launcher
+ public static final String PARAM_NAME = "name";
+
+ // String - application vendor, used by most of the bundlers
+ public static final String PARAM_VENDOR = "vendor";
+
+ // String - email name and email, only used for debian */
+ public static final String PARAM_EMAIL = "email";
+
+ // String - vendor <email>, only used for debian */
+ public static final String PARAM_MAINTAINER = "maintainer";
+
+ /* String - Copyright. Used on Mac */
+ public static final String PARAM_COPYRIGHT = "copyright";
+
+ // String - GUID on windows for MSI, CFBundleIdentifier on Mac
+ // If not compatible with requirements then bundler either do not bundle
+ // or autogenerate
+ public static final String PARAM_IDENTIFIER = "identifier";
+
+ /* boolean - shortcut preferences */
+ public static final String PARAM_SHORTCUT = "shortcutHint";
+ // boolean - menu shortcut preference
+ public static final String PARAM_MENU = "menuHint";
+
+ // String - Application version. Format may differ for different bundlers
+ public static final String PARAM_VERSION = "appVersion";
+
+ // String - Application release. Used on Linux.
+ public static final String PARAM_RELEASE = "appRelease";
+
+ // String - Optional application description. Used by MSI and on Linux
+ public static final String PARAM_DESCRIPTION = "description";
+
+ // String - License type. Needed on Linux (rpm)
+ public static final String PARAM_LICENSE_TYPE = "licenseType";
+
+ // String - File with license. Format is OS/bundler specific
+ public static final String PARAM_LICENSE_FILE = "licenseFile";
+
+ // String Main application class.
+ // Not used directly but used to derive default values
+ public static final String PARAM_APPLICATION_CLASS = "applicationClass";
+
+ // boolean - Adds a dialog to let the user choose a directory
+ // where the product will be installed.
+ public static final String PARAM_INSTALLDIR_CHOOSER = "installdirChooser";
+
+ /**
+ * create a new bundle with all default values
+ */
+ public BundleParams() {
+ params = new HashMap<>();
+ }
+
+ /**
+ * Create a bundle params with a copy of the params
+ * @param params map of initial parameters to be copied in.
+ */
+ public BundleParams(Map<String, ?> params) {
+ this.params = new HashMap<>(params);
+ }
+
+ public void addAllBundleParams(Map<String, ? super Object> params) {
+ this.params.putAll(params);
+ }
+
+ // NOTE: we do not care about application parameters here
+ // as they will be embeded into jar file manifest and
+ // java launcher will take care of them!
+
+ public Map<String, ? super Object> getBundleParamsAsMap() {
+ return new HashMap<>(params);
+ }
+
+ public String getName() {
+ return APP_NAME.fetchFrom(params);
+ }
+
+ public void setAppResourcesList(
+ List<jdk.jpackage.internal.RelativeFileSet> rfs) {
+ putUnlessNull(APP_RESOURCES_LIST.getID(), rfs);
+ }
+
+ private void putUnlessNull(String param, Object value) {
+ if (value != null) {
+ params.put(param, value);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundler.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2014, 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.File;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Bundler
+ *
+ * The basic interface implemented by all Bundlers.
+ */
+public interface Bundler {
+ /**
+ * @return User Friendly name of this bundler.
+ */
+ String getName();
+
+ /**
+ * @return Command line identifier of the bundler. Should be unique.
+ */
+ String getID();
+
+ /**
+ * @return The bundle type of the bundle that is created by this bundler.
+ */
+ String getBundleType();
+
+ /**
+ * Determines if this bundler will execute with the given parameters.
+ *
+ * @param params The parameters to be validate. Validation may modify
+ * the map, so if you are going to be using the same map
+ * across multiple bundlers you should pass in a deep copy.
+ * @return true if valid
+ * @throws ConfigException If the configuration params are incorrect. The
+ * exception may contain advice on how to modify the params map
+ * to make it valid.
+ */
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException;
+
+ /**
+ * Creates a bundle from existing content.
+ *
+ * If a call to {@link #validate(java.util.Map)} date} returns true with
+ * the parameters map, then you can expect a valid output.
+ * However if an exception was thrown out of validate or it returned
+ * false then you should not expect sensible results from this call.
+ * It may or may not return a value, and it may or may not throw an
+ * exception. But any output should not be considered valid or sane.
+ *
+ * @param params The Bundle parameters,
+ * Keyed by the id from the ParamInfo. Execution may
+ * modify the map, so if you are going to be using the
+ * same map across multiple bundlers you should pass
+ * in a deep copy.
+ * @param outputParentDir
+ * The parent dir that the returned bundle will be placed in.
+ * @return The resulting bundled file
+ *
+ * For a bundler that produces a single artifact file this will be the
+ * location of that artifact (.exe file, .deb file, etc)
+ *
+ * For a bundler that produces a specific directory format output this will
+ * be the location of that specific directory (.app file, etc).
+ *
+ * For a bundler that produce multiple files, this will be a parent
+ * directory of those files (linux and windows images), whose name is not
+ * relevant to the result.
+ *
+ * @throws java.lang.IllegalArgumentException for any of the following
+ * reasons:
+ * <ul>
+ * <li>A required parameter is not found in the params list, for
+ * example missing the main class.</li>
+ * <li>A parameter has the wrong type of an object, for example a
+ * String where a File is required</li>
+ * <li>Bundler specific incompatibilities with the parameters, for
+ * example a bad version number format or an application id with
+ * forward slashes.</li>
+ * </ul>
+ */
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException;
+
+ /**
+ * Removes temporary files that are used for bundling.
+ */
+ public void cleanup(Map<String, ? super Object> params);
+
+ /**
+ * Returns "true" if this bundler is supported on current platform.
+ */
+ public boolean supported(boolean runtimeInstaller);
+
+ /**
+ * Returns "true" if this bundler is he default for the current platform.
+ */
+ public boolean isDefault();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/BundlerParamInfo.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2014, 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.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+/**
+ * BundlerParamInfo<T>
+ *
+ * A BundlerParamInfo encapsulates an individual bundler parameter of type <T>.
+ */
+class BundlerParamInfo<T> {
+
+ /**
+ * The command line and hashmap name of the parameter
+ */
+ String id;
+
+ /**
+ * Type of the parameter
+ */
+ Class<T> valueType;
+
+ /**
+ * Indicates if value was set using default value function
+ */
+ boolean isDefaultValue;
+
+ /**
+ * If the value is not set, and no fallback value is found,
+ * the parameter uses the value returned by the producer.
+ */
+ Function<Map<String, ? super Object>, T> defaultValueFunction;
+
+ /**
+ * An optional string converter for command line arguments.
+ */
+ BiFunction<String, Map<String, ? super Object>, T> stringConverter;
+
+ String getID() {
+ return id;
+ }
+
+ Class<T> getValueType() {
+ return valueType;
+ }
+
+ boolean getIsDefaultValue() {
+ return isDefaultValue;
+ }
+
+ Function<Map<String, ? super Object>, T> getDefaultValueFunction() {
+ return defaultValueFunction;
+ }
+
+ BiFunction<String, Map<String, ? super Object>,T>
+ getStringConverter() {
+ return stringConverter;
+ }
+
+ @SuppressWarnings("unchecked")
+ final T fetchFrom(Map<String, ? super Object> params) {
+ return fetchFrom(params, true);
+ }
+
+ @SuppressWarnings("unchecked")
+ final T fetchFrom(Map<String, ? super Object> params,
+ boolean invokeDefault) {
+ Object o = params.get(getID());
+ if (o instanceof String && getStringConverter() != null) {
+ return getStringConverter().apply((String)o, params);
+ }
+
+ Class<T> klass = getValueType();
+ if (klass.isInstance(o)) {
+ return (T) o;
+ }
+ if (o != null) {
+ throw new IllegalArgumentException("Param " + getID()
+ + " should be of type " + getValueType()
+ + " but is a " + o.getClass());
+ }
+ if (params.containsKey(getID())) {
+ // explicit nulls are allowed
+ return null;
+ }
+
+ if (invokeDefault && (getDefaultValueFunction() != null)) {
+ T result = getDefaultValueFunction().apply(params);
+ if (result != null) {
+ params.put(getID(), result);
+ isDefaultValue = true;
+ }
+ return result;
+ }
+
+ // ultimate fallback
+ return null;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Bundlers.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2014, 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.util.Collection;
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+/**
+ * Bundlers
+ *
+ * The interface implemented by BasicBundlers
+ */
+public interface Bundlers {
+
+ /**
+ * This convenience method will call
+ * {@link #createBundlersInstance(ClassLoader)}
+ * with the classloader that this Bundlers is loaded from.
+ *
+ * @return an instance of Bundlers loaded and configured from
+ * the current ClassLoader.
+ */
+ public static Bundlers createBundlersInstance() {
+ return createBundlersInstance(Bundlers.class.getClassLoader());
+ }
+
+ /**
+ * This convenience method will automatically load a Bundlers instance
+ * from either META-INF/services or the default
+ * {@link BasicBundlers} if none are found in
+ * the services meta-inf.
+ *
+ * After instantiating the bundlers instance it will load the default
+ * bundlers via {@link #loadDefaultBundlers()} as well as requesting
+ * the services loader to load any other bundelrs via
+ * {@link #loadBundlersFromServices(ClassLoader)}.
+
+ *
+ * @param servicesClassLoader the classloader to search for
+ * META-INF/service registered bundlers
+ * @return an instance of Bundlers loaded and configured from
+ * the specified ClassLoader
+ */
+ public static Bundlers createBundlersInstance(
+ ClassLoader servicesClassLoader) {
+ ServiceLoader<Bundlers> bundlersLoader =
+ ServiceLoader.load(Bundlers.class, servicesClassLoader);
+ Bundlers bundlers = null;
+ Iterator<Bundlers> iter = bundlersLoader.iterator();
+ if (iter.hasNext()) {
+ bundlers = iter.next();
+ }
+ if (bundlers == null) {
+ bundlers = new BasicBundlers();
+ }
+
+ bundlers.loadBundlersFromServices(servicesClassLoader);
+ return bundlers;
+ }
+
+ /**
+ * Returns all of the preconfigured, requested, and manually
+ * configured bundlers loaded with this instance.
+ *
+ * @return a read-only collection of the requested bundlers
+ */
+ Collection<Bundler> getBundlers();
+
+ /**
+ * Returns all of the preconfigured, requested, and manually
+ * configured bundlers loaded with this instance that are of
+ * a specific BundleType, such as disk images, installers, or
+ * remote installers.
+ *
+ * @return a read-only collection of the requested bundlers
+ */
+ Collection<Bundler> getBundlers(String type);
+
+ /**
+ * Loads bundlers from the META-INF/services directly.
+ *
+ * This method is called from the
+ * {@link #createBundlersInstance(ClassLoader)}
+ * and {@link #createBundlersInstance()} methods.
+ */
+ void loadBundlersFromServices(ClassLoader cl);
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/CLIHelp.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2018, 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.util.ResourceBundle;
+import java.io.File;
+import java.text.MessageFormat;
+
+
+/**
+ * CLIHelp
+ *
+ * Generate and show the command line interface help message(s).
+ */
+public class CLIHelp {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.HelpResources");
+
+ // generates --help for jpackage's CLI
+ public static void showHelp(boolean noArgs) {
+
+ if (noArgs) {
+ Log.info(I18N.getString("MSG_Help_no_args"));
+ } else {
+ Platform platform = (Log.isVerbose()) ?
+ Platform.UNKNOWN : Platform.getPlatform();
+ String types;
+ String pLaunchOptions;
+ String pInstallOptions;
+ String pInstallDir;
+ switch (platform) {
+ case MAC:
+ types = "{\"app-image\", \"dmg\", \"pkg\"}";
+ pLaunchOptions = I18N.getString("MSG_Help_mac_launcher");
+ pInstallOptions = "";
+ pInstallDir
+ = I18N.getString("MSG_Help_mac_linux_install_dir");
+ break;
+ case LINUX:
+ types = "{\"app-image\", \"rpm\", \"deb\"}";
+ pLaunchOptions = "";
+ pInstallOptions = I18N.getString("MSG_Help_linux_install");
+ pInstallDir
+ = I18N.getString("MSG_Help_mac_linux_install_dir");
+ break;
+ case WINDOWS:
+ types = "{\"app-image\", \"exe\", \"msi\"}";
+ pLaunchOptions = I18N.getString("MSG_Help_win_launcher");
+ pInstallOptions = I18N.getString("MSG_Help_win_install");
+ pInstallDir
+ = I18N.getString("MSG_Help_win_install_dir");
+ break;
+ default:
+ types = "{\"app-image\", \"exe\", \"msi\", \"rpm\", \"deb\", \"pkg\", \"dmg\"}";
+ pLaunchOptions = I18N.getString("MSG_Help_win_launcher")
+ + I18N.getString("MSG_Help_mac_launcher");
+ pInstallOptions = I18N.getString("MSG_Help_win_install")
+ + I18N.getString("MSG_Help_linux_install");
+ pInstallDir
+ = I18N.getString("MSG_Help_default_install_dir");
+ break;
+ }
+ Log.info(MessageFormat.format(I18N.getString("MSG_Help"),
+ File.pathSeparator, types, pLaunchOptions,
+ pInstallOptions, pInstallDir));
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ConfigException.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2012, 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;
+
+public class ConfigException extends Exception {
+ private static final long serialVersionUID = 1L;
+ final String advice;
+
+ public ConfigException(String msg, String advice) {
+ super(msg);
+ this.advice = advice;
+ }
+
+ public ConfigException(String msg, String advice, Exception cause) {
+ super(msg, cause);
+ this.advice = advice;
+ }
+
+ public ConfigException(Exception cause) {
+ super(cause);
+ this.advice = null;
+ }
+
+ public String getAdvice() {
+ return advice;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/DeployParams.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,354 @@
+/*
+ * Copyright (c) 2011, 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.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.InvalidPathException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * DeployParams
+ *
+ * This class is generated and used in Arguments.processArguments() as
+ * intermediate step in generating the BundleParams and ultimately the Bundles
+ */
+public class DeployParams {
+
+ final List<RelativeFileSet> resources = new ArrayList<>();
+
+ String targetFormat = null; // means default type for this platform
+
+ File outdir = null;
+
+ // raw arguments to the bundler
+ Map<String, ? super Object> bundlerArguments = new LinkedHashMap<>();
+
+ public void setOutput(File output) {
+ outdir = output;
+ }
+
+ static class Template {
+ File in;
+ File out;
+
+ Template(File in, File out) {
+ this.in = in;
+ this.out = out;
+ }
+ }
+
+ // we need to expand as in some cases
+ // (most notably jpackage)
+ // we may get "." as filename and assumption is we include
+ // everything in the given folder
+ // (IOUtils.copyfiles() have recursive behavior)
+ List<File> expandFileset(File root) {
+ List<File> files = new LinkedList<>();
+ if (!Files.isSymbolicLink(root.toPath())) {
+ if (root.isDirectory()) {
+ File[] children = root.listFiles();
+ if (children != null) {
+ for (File f : children) {
+ files.addAll(expandFileset(f));
+ }
+ }
+ } else {
+ files.add(root);
+ }
+ }
+ return files;
+ }
+
+ public void addResource(File baseDir, String path) {
+ addResource(baseDir, new File(baseDir, path));
+ }
+
+ public void addResource(File baseDir, File file) {
+ // normalize initial file
+ // to strip things like "." in the path
+ // or it can confuse symlink detection logic
+ file = file.getAbsoluteFile();
+
+ if (baseDir == null) {
+ baseDir = file.getParentFile();
+ }
+ resources.add(new RelativeFileSet(
+ baseDir, new LinkedHashSet<>(expandFileset(file))));
+ }
+
+ void setClasspath(String mainJarPath) {
+ String classpath;
+ // we want main jar first on the classpath
+ if (mainJarPath != null) {
+ classpath = mainJarPath + File.pathSeparator;
+ } else {
+ classpath = "";
+ }
+ for (RelativeFileSet resource : resources) {
+ for (String file : resource.getIncludedFiles()) {
+ if (file.endsWith(".jar")) {
+ if (!file.equals(mainJarPath)) {
+ classpath += file + File.pathSeparator;
+ }
+ }
+ }
+ }
+ addBundleArgument(
+ StandardBundlerParam.CLASSPATH.getID(), classpath);
+ }
+
+ static void validateName(String s, boolean forApp)
+ throws PackagerException {
+
+ String exceptionKey = forApp ?
+ "ERR_InvalidAppName" : "ERR_InvalidSLName";
+
+ if (s == null) {
+ if (forApp) {
+ return;
+ } else {
+ throw new PackagerException(exceptionKey, s);
+ }
+ }
+ if (s.length() == 0 || s.charAt(s.length() - 1) == '\\') {
+ throw new PackagerException(exceptionKey, s);
+ }
+ try {
+ // name must be valid path element for this file system
+ Path p = (new File(s)).toPath();
+ // and it must be a single name element in a path
+ if (p.getNameCount() != 1) {
+ throw new PackagerException(exceptionKey, s);
+ }
+ } catch (InvalidPathException ipe) {
+ throw new PackagerException(ipe, exceptionKey, s);
+ }
+
+ for (int i = 0; i < s.length(); i++) {
+ char a = s.charAt(i);
+ // We check for ASCII codes first which we accept. If check fails,
+ // check if it is acceptable extended ASCII or unicode character.
+ if (a < ' ' || a > '~') {
+ // Accept anything else including special chars like copyright
+ // symbols. Note: space will be included by ASCII check above,
+ // but other whitespace like tabs or new line will be rejected.
+ if (Character.isISOControl(a) ||
+ Character.isWhitespace(a)) {
+ throw new PackagerException(exceptionKey, s);
+ }
+ } else if (a == '"' || a == '%') {
+ throw new PackagerException(exceptionKey, s);
+ }
+ }
+ }
+
+ public void validate() throws PackagerException {
+ boolean hasModule = (bundlerArguments.get(
+ Arguments.CLIOptions.MODULE.getId()) != null);
+ boolean hasAppImage = (bundlerArguments.get(
+ Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId()) != null);
+ boolean hasClass = (bundlerArguments.get(
+ Arguments.CLIOptions.APPCLASS.getId()) != null);
+ boolean hasMain = (bundlerArguments.get(
+ Arguments.CLIOptions.MAIN_JAR.getId()) != null);
+ boolean hasRuntimeImage = (bundlerArguments.get(
+ Arguments.CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId()) != null);
+ boolean hasInput = (bundlerArguments.get(
+ Arguments.CLIOptions.INPUT.getId()) != null);
+ boolean hasModulePath = (bundlerArguments.get(
+ Arguments.CLIOptions.MODULE_PATH.getId()) != null);
+ boolean runtimeInstaller = !isTargetAppImage() &&
+ !hasAppImage && !hasModule && !hasMain && hasRuntimeImage;
+
+ if (isTargetAppImage()) {
+ // Module application requires --runtime-image or --module-path
+ if (hasModule) {
+ if (!hasModulePath && !hasRuntimeImage) {
+ throw new PackagerException("ERR_MissingArgument",
+ "--runtime-image or --module-path");
+ }
+ } else {
+ if (!hasInput) {
+ throw new PackagerException(
+ "ERR_MissingArgument", "--input");
+ }
+ }
+ } else {
+ if (!runtimeInstaller) {
+ if (hasModule) {
+ if (!hasModulePath && !hasRuntimeImage && !hasAppImage) {
+ throw new PackagerException("ERR_MissingArgument",
+ "--runtime-image, --module-path or --app-image");
+ }
+ } else {
+ if (!hasInput && !hasAppImage) {
+ throw new PackagerException("ERR_MissingArgument",
+ "--input or --app-image");
+ }
+ }
+ }
+ }
+
+ // if bundling non-modular image, or installer without app-image
+ // then we need some resources and a main class
+ if (!hasModule && !hasAppImage && !runtimeInstaller) {
+ if (resources.isEmpty()) {
+ throw new PackagerException("ERR_MissingAppResources");
+ }
+ if (!hasMain) {
+ throw new PackagerException("ERR_MissingArgument",
+ "--main-jar");
+ }
+ }
+
+ String name = (String)bundlerArguments.get(
+ Arguments.CLIOptions.NAME.getId());
+ validateName(name, true);
+
+ // Validate app image if set
+ String appImage = (String)bundlerArguments.get(
+ Arguments.CLIOptions.PREDEFINED_APP_IMAGE.getId());
+ if (appImage != null) {
+ File appImageDir = new File(appImage);
+ if (!appImageDir.exists() || appImageDir.list().length == 0) {
+ throw new PackagerException("ERR_AppImageNotExist", appImage);
+ }
+ }
+
+ // Validate temp dir
+ String root = (String)bundlerArguments.get(
+ Arguments.CLIOptions.TEMP_ROOT.getId());
+ if (root != null) {
+ String [] contents = (new File(root)).list();
+
+ if (contents != null && contents.length > 0) {
+ throw new PackagerException("ERR_BuildRootInvalid", root);
+ }
+ }
+
+ // Validate license file if set
+ String license = (String)bundlerArguments.get(
+ Arguments.CLIOptions.LICENSE_FILE.getId());
+ if (license != null) {
+ File licenseFile = new File(license);
+ if (!licenseFile.exists()) {
+ throw new PackagerException("ERR_LicenseFileNotExit");
+ }
+ }
+ }
+
+ void setTargetFormat(String t) {
+ targetFormat = t;
+ }
+
+ String getTargetFormat() {
+ return targetFormat;
+ }
+
+ boolean isTargetAppImage() {
+ return ("app-image".equals(targetFormat));
+ }
+
+ private static final Set<String> multi_args = new TreeSet<>(Arrays.asList(
+ StandardBundlerParam.JAVA_OPTIONS.getID(),
+ StandardBundlerParam.ARGUMENTS.getID(),
+ StandardBundlerParam.MODULE_PATH.getID(),
+ StandardBundlerParam.ADD_MODULES.getID(),
+ StandardBundlerParam.LIMIT_MODULES.getID(),
+ StandardBundlerParam.FILE_ASSOCIATIONS.getID()
+ ));
+
+ @SuppressWarnings("unchecked")
+ public void addBundleArgument(String key, Object value) {
+ // special hack for multi-line arguments
+ if (multi_args.contains(key)) {
+ Object existingValue = bundlerArguments.get(key);
+ if (existingValue instanceof String && value instanceof String) {
+ String delim = "\n\n";
+ if (key.equals(StandardBundlerParam.MODULE_PATH.getID())) {
+ delim = File.pathSeparator;
+ } else if (key.equals(
+ StandardBundlerParam.ADD_MODULES.getID())) {
+ delim = ",";
+ }
+ bundlerArguments.put(key, existingValue + delim + value);
+ } else if (existingValue instanceof List && value instanceof List) {
+ ((List)existingValue).addAll((List)value);
+ } else if (existingValue instanceof Map &&
+ value instanceof String && ((String)value).contains("=")) {
+ String[] mapValues = ((String)value).split("=", 2);
+ ((Map)existingValue).put(mapValues[0], mapValues[1]);
+ } else {
+ bundlerArguments.put(key, value);
+ }
+ } else {
+ bundlerArguments.put(key, value);
+ }
+ }
+
+ BundleParams getBundleParams() {
+ BundleParams bundleParams = new BundleParams();
+
+ // construct app resources relative to destination folder!
+ bundleParams.setAppResourcesList(resources);
+
+ Map<String, String> unescapedHtmlParams = new TreeMap<>();
+ Map<String, String> escapedHtmlParams = new TreeMap<>();
+
+ // check for collisions
+ TreeSet<String> keys = new TreeSet<>(bundlerArguments.keySet());
+ keys.retainAll(bundleParams.getBundleParamsAsMap().keySet());
+
+ if (!keys.isEmpty()) {
+ throw new RuntimeException("Deploy Params and Bundler Arguments "
+ + "overlap in the following values:" + keys.toString());
+ }
+
+ bundleParams.addAllBundleParams(bundlerArguments);
+
+ return bundleParams;
+ }
+
+ @Override
+ public String toString() {
+ return "DeployParams {" + "output: " + outdir
+ + " resources: {" + resources + "}}";
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Executor.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,162 @@
+/*
+ * 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.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+final public class Executor {
+
+ Executor() {
+ }
+
+ Executor setOutputConsumer(Consumer<Stream<String>> v) {
+ outputConsumer = v;
+ return this;
+ }
+
+ Executor saveOutput(boolean v) {
+ saveOutput = v;
+ return this;
+ }
+
+ Executor setProcessBuilder(ProcessBuilder v) {
+ pb = v;
+ return this;
+ }
+
+ Executor setCommandLine(String... cmdline) {
+ return setProcessBuilder(new ProcessBuilder(cmdline));
+ }
+
+ List<String> getOutput() {
+ return output;
+ }
+
+ Executor executeExpectSuccess() throws IOException {
+ int ret = execute();
+ if (0 != ret) {
+ throw new IOException(
+ String.format("Command %s exited with %d code",
+ createLogMessage(pb), ret));
+ }
+ return this;
+ }
+
+ int execute() throws IOException {
+ output = null;
+
+ boolean needProcessOutput = outputConsumer != null || Log.isVerbose() || saveOutput;
+ if (needProcessOutput) {
+ pb.redirectErrorStream(true);
+ } else {
+ // We are not going to read process output, so need to notify
+ // ProcessBuilder about this. Otherwise some processes might just
+ // hang up (`ldconfig -p`).
+ pb.redirectError(ProcessBuilder.Redirect.DISCARD);
+ pb.redirectOutput(ProcessBuilder.Redirect.DISCARD);
+ }
+
+ Log.verbose(String.format("Running %s", createLogMessage(pb)));
+ Process p = pb.start();
+
+ if (needProcessOutput) {
+ try (var br = new BufferedReader(new InputStreamReader(
+ p.getInputStream()))) {
+ final List<String> savedOutput;
+ // Need to save output if explicitely requested (saveOutput=true) or
+ // if will be used used by multiple consumers
+ if ((outputConsumer != null && Log.isVerbose()) || saveOutput) {
+ savedOutput = br.lines().collect(Collectors.toList());
+ if (saveOutput) {
+ output = savedOutput;
+ }
+ } else {
+ savedOutput = null;
+ }
+
+ Supplier<Stream<String>> outputStream = () -> {
+ if (savedOutput != null) {
+ return savedOutput.stream();
+ }
+ return br.lines();
+ };
+
+ if (Log.isVerbose()) {
+ outputStream.get().forEach(Log::verbose);
+ }
+
+ if (outputConsumer != null) {
+ outputConsumer.accept(outputStream.get());
+ }
+
+ if (savedOutput == null) {
+ // For some processes on Linux if the output stream
+ // of the process is opened but not consumed, the process
+ // would exit with code 141.
+ // It turned out that reading just a single line of process
+ // output fixes the problem, but let's process
+ // all of the output, just in case.
+ br.lines().forEach(x -> {});
+ }
+ }
+ }
+
+ try {
+ return p.waitFor();
+ } catch (InterruptedException ex) {
+ Log.verbose(ex);
+ throw new RuntimeException(ex);
+ }
+ }
+
+ static Executor of(String... cmdline) {
+ return new Executor().setCommandLine(cmdline);
+ }
+
+ static Executor of(ProcessBuilder pb) {
+ return new Executor().setProcessBuilder(pb);
+ }
+
+ private static String createLogMessage(ProcessBuilder pb) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(String.format("%s", pb.command()));
+ if (pb.directory() != null) {
+ sb.append(String.format("in %s", pb.directory().getAbsolutePath()));
+ }
+ return sb.toString();
+ }
+
+ private ProcessBuilder pb;
+ private boolean saveOutput;
+ private List<String> output;
+ private Consumer<Stream<String>> outputConsumer;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/I18N.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,57 @@
+/*
+ * 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.util.ResourceBundle;
+
+class I18N {
+
+ static String getString(String key) {
+ if (PLATFORM.containsKey(key)) {
+ return PLATFORM.getString(key);
+ }
+ return SHARED.getString(key);
+ }
+
+ private static final ResourceBundle SHARED = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.MainResources");
+
+ private static final ResourceBundle PLATFORM;
+
+ static {
+ if (Platform.isLinux()) {
+ PLATFORM = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.LinuxResources");
+ } else if (Platform.isWindows()) {
+ PLATFORM = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.WinResources");
+ } else if (Platform.isMac()) {
+ PLATFORM = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.MacResources");
+ } else {
+ throw new IllegalStateException("Unknwon platform");
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/IOUtils.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 2012, 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.channels.FileChannel;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * IOUtils
+ *
+ * A collection of static utility methods.
+ */
+public class IOUtils {
+
+ public static void deleteRecursive(File path) throws IOException {
+ if (!path.exists()) {
+ return;
+ }
+ Path directory = path.toPath();
+ Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file,
+ BasicFileAttributes attr) throws IOException {
+ if (Platform.getPlatform() == Platform.WINDOWS) {
+ Files.setAttribute(file, "dos:readonly", false);
+ }
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult preVisitDirectory(Path dir,
+ BasicFileAttributes attr) throws IOException {
+ if (Platform.getPlatform() == Platform.WINDOWS) {
+ Files.setAttribute(dir, "dos:readonly", false);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException e)
+ throws IOException {
+ Files.delete(dir);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ public static void copyRecursive(Path src, Path dest) throws IOException {
+ copyRecursive(src, dest, List.of());
+ }
+
+ public static void copyRecursive(Path src, Path dest,
+ final List<String> excludes) throws IOException {
+ Files.walkFileTree(src, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult preVisitDirectory(final Path dir,
+ final BasicFileAttributes attrs) throws IOException {
+ if (excludes.contains(dir.toFile().getName())) {
+ return FileVisitResult.SKIP_SUBTREE;
+ } else {
+ Files.createDirectories(dest.resolve(src.relativize(dir)));
+ return FileVisitResult.CONTINUE;
+ }
+ }
+
+ @Override
+ public FileVisitResult visitFile(final Path file,
+ final BasicFileAttributes attrs) throws IOException {
+ if (!excludes.contains(file.toFile().getName())) {
+ Files.copy(file, dest.resolve(src.relativize(file)));
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ public static void copyFile(File sourceFile, File destFile)
+ throws IOException {
+ destFile.getParentFile().mkdirs();
+
+ //recreate the file as existing copy may have weird permissions
+ destFile.delete();
+ destFile.createNewFile();
+
+ try (FileChannel source = new FileInputStream(sourceFile).getChannel();
+ FileChannel destination =
+ new FileOutputStream(destFile).getChannel()) {
+
+ if (destination != null && source != null) {
+ destination.transferFrom(source, 0, source.size());
+ }
+ }
+
+ //preserve executable bit!
+ if (sourceFile.canExecute()) {
+ destFile.setExecutable(true, false);
+ }
+ if (!sourceFile.canWrite()) {
+ destFile.setReadOnly();
+ }
+ destFile.setReadable(true, false);
+ }
+
+ // run "launcher paramfile" in the directory where paramfile is kept
+ public static void run(String launcher, File paramFile)
+ throws IOException {
+ if (paramFile != null && paramFile.exists()) {
+ ProcessBuilder pb =
+ new ProcessBuilder(launcher, paramFile.getName());
+ pb = pb.directory(paramFile.getParentFile());
+ exec(pb);
+ }
+ }
+
+ public static void exec(ProcessBuilder pb)
+ throws IOException {
+ exec(pb, false, null);
+ }
+
+ static void exec(ProcessBuilder pb, boolean testForPresenseOnly,
+ PrintStream consumer) throws IOException {
+ List<String> output = new ArrayList<>();
+ Executor exec = Executor.of(pb).setOutputConsumer(lines -> {
+ lines.forEach(output::add);
+ if (consumer != null) {
+ output.forEach(consumer::println);
+ }
+ });
+
+ if (testForPresenseOnly) {
+ exec.execute();
+ } else {
+ exec.executeExpectSuccess();
+ }
+ }
+
+ public static int getProcessOutput(List<String> result, String... args)
+ throws IOException, InterruptedException {
+
+ ProcessBuilder pb = new ProcessBuilder(args);
+
+ final Process p = pb.start();
+
+ List<String> list = new ArrayList<>();
+
+ final BufferedReader in =
+ new BufferedReader(new InputStreamReader(p.getInputStream()));
+ final BufferedReader err =
+ new BufferedReader(new InputStreamReader(p.getErrorStream()));
+
+ Thread t = new Thread(() -> {
+ try {
+ String line;
+ while ((line = in.readLine()) != null) {
+ list.add(line);
+ }
+ } catch (IOException ioe) {
+ Log.verbose(ioe);
+ }
+
+ try {
+ String line;
+ while ((line = err.readLine()) != null) {
+ Log.error(line);
+ }
+ } catch (IOException ioe) {
+ Log.verbose(ioe);
+ }
+ });
+ t.setDaemon(true);
+ t.start();
+
+ int ret = p.waitFor();
+
+ result.clear();
+ result.addAll(list);
+
+ return ret;
+ }
+
+ static void writableOutputDir(Path outdir) throws PackagerException {
+ File file = outdir.toFile();
+
+ if (!file.isDirectory() && !file.mkdirs()) {
+ throw new PackagerException("error.cannot-create-output-dir",
+ file.getAbsolutePath());
+ }
+ if (!file.canWrite()) {
+ throw new PackagerException("error.cannot-write-to-output-dir",
+ file.getAbsolutePath());
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JLinkBundlerHelper.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,446 @@
+/*
+ * Copyright (c) 2015, 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.File;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.Optional;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.regex.Matcher;
+import java.util.spi.ToolProvider;
+import java.lang.module.Configuration;
+import java.lang.module.ResolvedModule;
+import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleFinder;
+import java.lang.module.ModuleReference;
+
+final class JLinkBundlerHelper {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.MainResources");
+
+ static final ToolProvider JLINK_TOOL =
+ ToolProvider.findFirst("jlink").orElseThrow();
+
+ static File getMainJar(Map<String, ? super Object> params) {
+ File result = null;
+ RelativeFileSet fileset =
+ StandardBundlerParam.MAIN_JAR.fetchFrom(params);
+
+ if (fileset != null) {
+ String filename = fileset.getIncludedFiles().iterator().next();
+ result = fileset.getBaseDirectory().toPath().
+ resolve(filename).toFile();
+
+ if (result == null || !result.exists()) {
+ String srcdir =
+ StandardBundlerParam.SOURCE_DIR.fetchFrom(params);
+
+ if (srcdir != null) {
+ result = new File(srcdir + File.separator + filename);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ static String getMainClass(Map<String, ? super Object> params) {
+ String result = "";
+ String mainModule = StandardBundlerParam.MODULE.fetchFrom(params);
+ if (mainModule != null) {
+ int index = mainModule.indexOf("/");
+ if (index > 0) {
+ result = mainModule.substring(index + 1);
+ } else {
+ ModuleDescriptor descriptor =
+ JLinkBundlerHelper.getMainModuleDescription(params);
+ if (descriptor != null) {
+ Optional<String> mainClass = descriptor.mainClass();
+ if (mainClass.isPresent()) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.module-class"),
+ mainClass.get(),
+ JLinkBundlerHelper.getMainModule(params)));
+ result = mainClass.get();
+ }
+ }
+ }
+ } else {
+ RelativeFileSet fileset =
+ StandardBundlerParam.MAIN_JAR.fetchFrom(params);
+ if (fileset != null) {
+ result = StandardBundlerParam.MAIN_CLASS.fetchFrom(params);
+ } else {
+ // possibly app-image
+ }
+ }
+
+ return result;
+ }
+
+ static String getMainModule(Map<String, ? super Object> params) {
+ String result = null;
+ String mainModule = StandardBundlerParam.MODULE.fetchFrom(params);
+
+ if (mainModule != null) {
+ int index = mainModule.indexOf("/");
+
+ if (index > 0) {
+ result = mainModule.substring(0, index);
+ } else {
+ result = mainModule;
+ }
+ }
+
+ return result;
+ }
+
+ private static Set<String> getValidModules(List<Path> modulePath,
+ Set<String> addModules, Set<String> limitModules) {
+ ModuleHelper moduleHelper = new ModuleHelper(
+ modulePath, addModules, limitModules);
+ return removeInvalidModules(modulePath, moduleHelper.modules());
+ }
+
+ static void execute(Map<String, ? super Object> params,
+ AbstractAppImageBuilder imageBuilder)
+ throws IOException, Exception {
+
+ // we might be able to build it (with no main class) but it won't run
+ if (StandardBundlerParam.MAIN_CLASS.fetchFrom(params) == null) {
+ throw new PackagerException("ERR_NoMainClass");
+ }
+
+ List<Path> modulePath =
+ StandardBundlerParam.MODULE_PATH.fetchFrom(params);
+ Set<String> addModules =
+ StandardBundlerParam.ADD_MODULES.fetchFrom(params);
+ Set<String> limitModules =
+ StandardBundlerParam.LIMIT_MODULES.fetchFrom(params);
+ Path outputDir = imageBuilder.getRuntimeRoot();
+ File mainJar = getMainJar(params);
+ ModFile.ModType mainJarType = ModFile.ModType.Unknown;
+
+ if (mainJar != null) {
+ mainJarType = new ModFile(mainJar).getModType();
+ } else if (StandardBundlerParam.MODULE.fetchFrom(params) == null) {
+ // user specified only main class, all jars will be on the classpath
+ mainJarType = ModFile.ModType.UnnamedJar;
+ }
+
+ boolean bindServices = addModules.isEmpty();
+
+ // Modules
+ String mainModule = getMainModule(params);
+ if (mainModule == null) {
+ if (mainJarType == ModFile.ModType.UnnamedJar) {
+ if (addModules.isEmpty()) {
+ // The default for an unnamed jar is ALL_DEFAULT
+ addModules.add(ModuleHelper.ALL_DEFAULT);
+ }
+ } else if (mainJarType == ModFile.ModType.Unknown ||
+ mainJarType == ModFile.ModType.ModularJar) {
+ addModules.add(ModuleHelper.ALL_DEFAULT);
+ }
+ }
+
+ Set<String> validModules =
+ getValidModules(modulePath, addModules, limitModules);
+
+ if (mainModule != null) {
+ validModules.add(mainModule);
+ }
+
+ runJLink(outputDir, modulePath, validModules, limitModules,
+ new HashMap<String,String>(), bindServices);
+
+ imageBuilder.prepareApplicationFiles(params);
+ }
+
+
+ // Returns the path to the JDK modules in the user defined module path.
+ 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 ModuleDescriptor getMainModuleDescription(Map<String, ? super Object> params) {
+ boolean hasModule = params.containsKey(StandardBundlerParam.MODULE.getID());
+ if (hasModule) {
+ List<Path> modulePath = StandardBundlerParam.MODULE_PATH.fetchFrom(params);
+ if (!modulePath.isEmpty()) {
+ ModuleFinder finder = ModuleFinder.of(modulePath.toArray(new Path[0]));
+ String mainModule = JLinkBundlerHelper.getMainModule(params);
+ Optional<ModuleReference> omref = finder.find(mainModule);
+ if (omref.isPresent()) {
+ return omref.get().descriptor();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /*
+ * Returns the set of modules that would be visible by default for
+ * a non-modular-aware application consisting of the given elements.
+ */
+ private static Set<String> getDefaultModules(
+ Path[] paths, String[] addModules) {
+
+ // the modules in the run-time image that export an API
+ Stream<String> systemRoots = ModuleFinder.ofSystem().findAll().stream()
+ .map(ModuleReference::descriptor)
+ .filter(descriptor -> exportsAPI(descriptor))
+ .map(ModuleDescriptor::name);
+
+ Set<String> roots;
+ if (addModules == null || addModules.length == 0) {
+ roots = systemRoots.collect(Collectors.toSet());
+ } else {
+ var extraRoots = Stream.of(addModules);
+ roots = Stream.concat(systemRoots,
+ extraRoots).collect(Collectors.toSet());
+ }
+
+ ModuleFinder finder = ModuleFinder.ofSystem();
+ if (paths != null && paths.length > 0) {
+ finder = ModuleFinder.compose(finder, ModuleFinder.of(paths));
+ }
+ return Configuration.empty()
+ .resolveAndBind(finder, ModuleFinder.of(), roots)
+ .modules()
+ .stream()
+ .map(ResolvedModule::name)
+ .collect(Collectors.toSet());
+ }
+
+ /*
+ * Returns true if the given module exports an API to all module.
+ */
+ private static boolean exportsAPI(ModuleDescriptor descriptor) {
+ return descriptor.exports()
+ .stream()
+ .filter(e -> !e.isQualified())
+ .findAny()
+ .isPresent();
+ }
+
+ private static Set<String> removeInvalidModules(
+ List<Path> modulePath, Set<String> modules) {
+ Set<String> result = new LinkedHashSet<String>();
+ ModuleManager mm = new ModuleManager(modulePath);
+ List<ModFile> lmodfiles =
+ mm.getModules(EnumSet.of(ModuleManager.SearchType.ModularJar,
+ ModuleManager.SearchType.Jmod,
+ ModuleManager.SearchType.ExplodedModule));
+
+ HashMap<String, ModFile> validModules = new HashMap<>();
+
+ for (ModFile modFile : lmodfiles) {
+ validModules.put(modFile.getModName(), modFile);
+ }
+
+ for (String name : modules) {
+ if (validModules.containsKey(name)) {
+ result.add(name);
+ } else {
+ Log.error(MessageFormat.format(
+ I18N.getString("warning.module.does.not.exist"), name));
+ }
+ }
+
+ return result;
+ }
+
+ private static class ModuleHelper {
+ // The token for "all modules on the module path".
+ private static final String ALL_MODULE_PATH = "ALL-MODULE-PATH";
+
+ // The token for "all valid runtime modules".
+ static final String ALL_DEFAULT = "ALL-DEFAULT";
+
+ private final Set<String> modules = new HashSet<>();
+ ModuleHelper(List<Path> paths, Set<String> addModules,
+ Set<String> limitModules) {
+ boolean addAllModulePath = false;
+ boolean addDefaultMods = false;
+
+ for (Iterator<String> iterator = addModules.iterator();
+ iterator.hasNext();) {
+ String module = iterator.next();
+
+ switch (module) {
+ case ALL_MODULE_PATH:
+ iterator.remove();
+ addAllModulePath = true;
+ break;
+ case ALL_DEFAULT:
+ iterator.remove();
+ addDefaultMods = true;
+ break;
+ default:
+ this.modules.add(module);
+ }
+ }
+
+ if (addAllModulePath) {
+ this.modules.addAll(getModuleNamesFromPath(paths));
+ } else if (addDefaultMods) {
+ this.modules.addAll(getDefaultModules(
+ paths.toArray(new Path[0]),
+ addModules.toArray(new String[0])));
+ }
+ }
+
+ Set<String> modules() {
+ return modules;
+ }
+
+ private static Set<String> getModuleNamesFromPath(List<Path> Value) {
+ Set<String> result = new LinkedHashSet<String>();
+ ModuleManager mm = new ModuleManager(Value);
+ List<ModFile> modFiles = mm.getModules(
+ EnumSet.of(ModuleManager.SearchType.ModularJar,
+ ModuleManager.SearchType.Jmod,
+ ModuleManager.SearchType.ExplodedModule));
+
+ for (ModFile modFile : modFiles) {
+ result.add(modFile.getModName());
+ }
+ return result;
+ }
+ }
+
+ private static void runJLink(Path output, List<Path> modulePath,
+ Set<String> modules, Set<String> limitModules,
+ HashMap<String, String> user, boolean bindServices)
+ throws IOException {
+
+ // This is just to ensure jlink is given a non-existant directory
+ // The passed in output path should be non-existant or empty directory
+ IOUtils.deleteRecursive(output.toFile());
+
+ ArrayList<String> args = new ArrayList<String>();
+ args.add("--output");
+ args.add(output.toString());
+ if (modulePath != null && !modulePath.isEmpty()) {
+ args.add("--module-path");
+ args.add(getPathList(modulePath));
+ }
+ if (modules != null && !modules.isEmpty()) {
+ args.add("--add-modules");
+ args.add(getStringList(modules));
+ }
+ if (limitModules != null && !limitModules.isEmpty()) {
+ args.add("--limit-modules");
+ args.add(getStringList(limitModules));
+ }
+ if (user != null && !user.isEmpty()) {
+ for (Map.Entry<String, String> entry : user.entrySet()) {
+ args.add(entry.getKey());
+ args.add(entry.getValue());
+ }
+ } else {
+ args.add("--strip-native-commands");
+ args.add("--strip-debug");
+ args.add("--no-man-pages");
+ args.add("--no-header-files");
+ if (bindServices) {
+ args.add("--bind-services");
+ }
+ }
+
+ StringWriter writer = new StringWriter();
+ PrintWriter pw = new PrintWriter(writer);
+
+ Log.verbose("jlink arguments: " + args);
+ int retVal = JLINK_TOOL.run(pw, pw, args.toArray(new String[0]));
+ String jlinkOut = writer.toString();
+
+ if (retVal != 0) {
+ throw new IOException("jlink failed with: " + jlinkOut);
+ } else if (jlinkOut.length() > 0) {
+ Log.verbose("jlink output: " + jlinkOut);
+ }
+ }
+
+ private static String getPathList(List<Path> pathList) {
+ String ret = null;
+ for (Path p : pathList) {
+ String s = Matcher.quoteReplacement(p.toString());
+ if (ret == null) {
+ ret = s;
+ } else {
+ ret += File.pathSeparator + s;
+ }
+ }
+ return ret;
+ }
+
+ private static String getStringList(Set<String> strings) {
+ String ret = null;
+ for (String s : strings) {
+ if (ret == null) {
+ ret = s;
+ } else {
+ ret += "," + s;
+ }
+ }
+ return (ret == null) ? null : Matcher.quoteReplacement(ret);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/JPackageToolProvider.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2017, 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.PrintWriter;
+import java.util.spi.ToolProvider;
+
+/**
+ * JPackageToolProvider
+ *
+ * This is the ToolProvider implementation exported
+ * to java.util.spi.ToolProvider and ultimately javax.tools.ToolProvider
+ */
+public class JPackageToolProvider implements ToolProvider {
+
+ public String name() {
+ return "jpackage";
+ }
+
+ public synchronized int run(
+ PrintWriter out, PrintWriter err, String... args) {
+ try {
+ return new jdk.jpackage.main.Main().execute(out, err, args);
+ } catch (RuntimeException re) {
+ Log.error(re.getMessage());
+ Log.verbose(re);
+ return 1;
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Log.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2011, 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
+/**
+ * Log
+ *
+ * General purpose logging mechanism.
+ */
+public class Log {
+ public static class Logger {
+ private boolean verbose = false;
+ private PrintWriter out = null;
+ private PrintWriter err = null;
+
+ // verbose defaults to true unless environment variable JPACKAGE_DEBUG
+ // is set to true.
+ // Then it is only set to true by using --verbose jpackage option
+
+ public Logger() {
+ verbose = ("true".equals(System.getenv("JPACKAGE_DEBUG")));
+ }
+
+ public void setVerbose() {
+ verbose = true;
+ }
+
+ public boolean isVerbose() {
+ return verbose;
+ }
+
+ public void setPrintWriter(PrintWriter out, PrintWriter err) {
+ this.out = out;
+ this.err = err;
+ }
+
+ public void flush() {
+ if (out != null) {
+ out.flush();
+ }
+
+ if (err != null) {
+ err.flush();
+ }
+ }
+
+ public void info(String msg) {
+ if (out != null) {
+ out.println(msg);
+ } else {
+ System.out.println(msg);
+ }
+ }
+
+ public void error(String msg) {
+ if (err != null) {
+ err.println(msg);
+ } else {
+ System.err.println(msg);
+ }
+ }
+
+ public void verbose(Throwable t) {
+ if (out != null && verbose) {
+ t.printStackTrace(out);
+ } else if (verbose) {
+ t.printStackTrace(System.out);
+ }
+ }
+
+ public void verbose(String msg) {
+ if (out != null && verbose) {
+ out.println(msg);
+ } else if (verbose) {
+ System.out.println(msg);
+ }
+ }
+ }
+
+ private static Logger delegate = null;
+
+ public static void setLogger(Logger logger) {
+ delegate = (logger != null) ? logger : new Logger();
+ }
+
+ public static void flush() {
+ if (delegate != null) {
+ delegate.flush();
+ }
+ }
+
+ public static void info(String msg) {
+ if (delegate != null) {
+ delegate.info(msg);
+ }
+ }
+
+ public static void error(String msg) {
+ if (delegate != null) {
+ delegate.error(msg);
+ }
+ }
+
+ public static void setVerbose() {
+ if (delegate != null) {
+ delegate.setVerbose();
+ }
+ }
+
+ public static boolean isVerbose() {
+ return (delegate != null) ? delegate.isVerbose() : false;
+ }
+
+ public static void verbose(String msg) {
+ if (delegate != null) {
+ delegate.verbose(msg);
+ }
+ }
+
+ public static void verbose(Throwable t) {
+ if (delegate != null) {
+ delegate.verbose(t);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ModFile.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2016, 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.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+final class ModFile {
+ private final String filename;
+ private final ModType moduleType;
+
+ enum JarType {All, UnnamedJar, ModularJar}
+ enum ModType {
+ Unknown, UnnamedJar, ModularJar, Jmod, ExplodedModule}
+
+ ModFile(File aFile) {
+ super();
+ filename = aFile.getPath();
+ moduleType = getModType(aFile);
+ }
+
+ String getModName() {
+ File file = new File(getFileName());
+ // do not try to remove extension for directories
+ return moduleType == ModType.ExplodedModule ?
+ file.getName() : getFileWithoutExtension(file.getName());
+ }
+
+ String getFileName() {
+ return filename;
+ }
+
+ ModType getModType() {
+ return moduleType;
+ }
+
+ private static ModType getModType(File aFile) {
+ ModType result = ModType.Unknown;
+ String filename = aFile.getAbsolutePath();
+
+ if (aFile.isFile()) {
+ if (filename.endsWith(".jmod")) {
+ result = ModType.Jmod;
+ }
+ else if (filename.endsWith(".jar")) {
+ JarType status = isModularJar(filename);
+
+ if (status == JarType.ModularJar) {
+ result = ModType.ModularJar;
+ }
+ else if (status == JarType.UnnamedJar) {
+ result = ModType.UnnamedJar;
+ }
+ }
+ }
+ else if (aFile.isDirectory()) {
+ File moduleInfo = new File(
+ filename + File.separator + "module-info.class");
+
+ if (moduleInfo.exists()) {
+ result = ModType.ExplodedModule;
+ }
+ }
+
+ return result;
+ }
+
+ private static JarType isModularJar(String FileName) {
+ JarType result = JarType.All;
+
+ try (ZipInputStream zip =
+ new ZipInputStream(new FileInputStream(FileName))) {
+ result = JarType.UnnamedJar;
+
+ for (ZipEntry entry = zip.getNextEntry(); entry != null;
+ entry = zip.getNextEntry()) {
+ if (entry.getName().matches("module-info.class")) {
+ result = JarType.ModularJar;
+ break;
+ }
+ }
+ } catch (IOException ex) {
+ }
+
+ return result;
+ }
+
+ private static String getFileWithoutExtension(String FileName) {
+ return FileName.replaceFirst("[.][^.]+$", "");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ModuleManager.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2016, 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.File;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+
+final class ModuleManager {
+ private final List<String> folders = new ArrayList<String>();
+
+ enum SearchType {UnnamedJar, ModularJar, Jmod, ExplodedModule}
+
+ ModuleManager(String folders) {
+ super();
+ String lfolders = folders.replaceAll("^\"|\"$", "");
+ List<Path> paths = new ArrayList<Path>();
+
+ for (String folder :
+ Arrays.asList(lfolders.split(File.pathSeparator))) {
+ File file = new File(folder);
+ paths.add(file.toPath());
+ }
+
+ initialize(paths);
+ }
+
+ ModuleManager(List<Path> Paths) {
+ super();
+ initialize(Paths);
+ }
+
+ private void initialize(List<Path> Paths) {
+ for (Path path : Paths) {
+ folders.add(path.toString().replaceAll("^\"|\"$", ""));
+ }
+ }
+
+ List<ModFile> getModules(EnumSet<SearchType> Search) {
+ List<ModFile> result = new ArrayList<ModFile>();
+
+ for (String folder : folders) {
+ result.addAll(getAllModulesInDirectory(folder, Search));
+ }
+
+ return result;
+ }
+
+ private static List<ModFile> getAllModulesInDirectory(String folder,
+ EnumSet<SearchType> Search) {
+ List<ModFile> result = new ArrayList<ModFile>();
+ File lfolder = new File(folder);
+ File[] files = { lfolder };
+ if (lfolder.isDirectory()) {
+ files = lfolder.listFiles();
+ }
+
+ if (files != null) {
+ for (File file : files) {
+ ModFile modFile = new ModFile(file);
+
+ switch (modFile.getModType()) {
+ case Unknown:
+ break;
+ case UnnamedJar:
+ if (Search.contains(SearchType.UnnamedJar)) {
+ result.add(modFile);
+ }
+ break;
+ case ModularJar:
+ if (Search.contains(SearchType.ModularJar)) {
+ result.add(modFile);
+ }
+ break;
+ case Jmod:
+ if (Search.contains(SearchType.Jmod)) {
+ result.add(modFile);
+ }
+ break;
+ case ExplodedModule:
+ if (Search.contains(SearchType.ExplodedModule)) {
+ result.add(modFile);
+ }
+ break;
+ }
+ }
+ }
+ return result;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagerException.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2011, 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.text.MessageFormat;
+import java.util.ResourceBundle;
+
+public class PackagerException extends Exception {
+ private static final long serialVersionUID = 1L;
+ private static final ResourceBundle bundle = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.MainResources");
+
+ public PackagerException(Throwable cause) {
+ super(cause);
+ }
+
+ public PackagerException(String key, Throwable cause) {
+ super(bundle.getString(key), cause);
+ }
+
+ public PackagerException(String key) {
+ super(bundle.getString(key));
+ }
+
+ public PackagerException(String key, String ... arguments) {
+ super(MessageFormat.format(
+ bundle.getString(key), (Object[]) arguments));
+ }
+
+ public PackagerException(
+ Throwable cause, String key, String ... arguments) {
+ super(MessageFormat.format(bundle.getString(key),
+ (Object[]) arguments), cause);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PathGroup.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,203 @@
+/*
+ * 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.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+
+
+/**
+ * Group of paths.
+ * Each path in the group is assigned a unique id.
+ */
+final class PathGroup {
+ PathGroup(Map<Object, Path> paths) {
+ entries = Collections.unmodifiableMap(paths);
+ }
+
+ Path getPath(Object id) {
+ return entries.get(id);
+ }
+
+ /**
+ * All configured entries.
+ */
+ List<Path> paths() {
+ return entries.values().stream().collect(Collectors.toList());
+ }
+
+ /**
+ * Root entries.
+ */
+ List<Path> roots() {
+ // Sort by the number of path components in ascending order.
+ List<Map.Entry<Path, Path>> sorted = normalizedPaths().stream().sorted(
+ (a, b) -> a.getKey().getNameCount() - b.getKey().getNameCount()).collect(
+ Collectors.toList());
+
+ // Returns `true` if `a` is a parent of `b`
+ BiFunction<Map.Entry<Path, Path>, Map.Entry<Path, Path>, Boolean> isParentOrSelf = (a, b) -> {
+ return a == b || b.getKey().startsWith(a.getKey());
+ };
+
+ return sorted.stream().filter(
+ v -> v == sorted.stream().sequential().filter(
+ v2 -> isParentOrSelf.apply(v2, v)).findFirst().get()).map(
+ v -> v.getValue()).collect(Collectors.toList());
+ }
+
+ long sizeInBytes() throws IOException {
+ long reply = 0;
+ for (Path dir : roots().stream().filter(f -> Files.isDirectory(f)).collect(
+ Collectors.toList())) {
+ reply += Files.walk(dir).filter(p -> Files.isRegularFile(p)).mapToLong(
+ f -> f.toFile().length()).sum();
+ }
+ return reply;
+ }
+
+ PathGroup resolveAt(Path root) {
+ return new PathGroup(entries.entrySet().stream().collect(
+ Collectors.toMap(e -> e.getKey(),
+ e -> root.resolve(e.getValue()))));
+ }
+
+ void copy(PathGroup dst) throws IOException {
+ copy(this, dst, false);
+ }
+
+ void move(PathGroup dst) throws IOException {
+ copy(this, dst, true);
+ }
+
+ static interface Facade<T> {
+ PathGroup pathGroup();
+
+ default Collection<Path> paths() {
+ return pathGroup().paths();
+ }
+
+ default List<Path> roots() {
+ return pathGroup().roots();
+ }
+
+ default long sizeInBytes() throws IOException {
+ return pathGroup().sizeInBytes();
+ }
+
+ T resolveAt(Path root);
+
+ default void copy(Facade<T> dst) throws IOException {
+ pathGroup().copy(dst.pathGroup());
+ }
+
+ default void move(Facade<T> dst) throws IOException {
+ pathGroup().move(dst.pathGroup());
+ }
+ }
+
+ private static void copy(PathGroup src, PathGroup dst, boolean move) throws
+ IOException {
+ copy(move, src.entries.keySet().stream().filter(
+ id -> dst.entries.containsKey(id)).map(id -> Map.entry(
+ src.entries.get(id), dst.entries.get(id))).collect(
+ Collectors.toCollection(ArrayList::new)));
+ }
+
+ private static void copy(boolean move, List<Map.Entry<Path, Path>> entries)
+ throws IOException {
+
+ // destination -> source file mapping
+ Map<Path, Path> actions = new HashMap<>();
+ for (var action: entries) {
+ Path src = action.getKey();
+ Path dst = action.getValue();
+ if (src.toFile().isDirectory()) {
+ Files.walk(src).forEach(path -> actions.put(dst.resolve(
+ src.relativize(path)).toAbsolutePath().normalize(), path));
+ } else {
+ actions.put(dst.toAbsolutePath().normalize(), src);
+ }
+ }
+
+ for (var action : actions.entrySet()) {
+ Path dst = action.getKey();
+ Path src = action.getValue();
+
+ if (src.equals(dst) || !src.toFile().exists()) {
+ continue;
+ }
+
+ if (src.toFile().isDirectory()) {
+ Files.createDirectories(dst);
+ } else {
+ Files.createDirectories(dst.getParent());
+ if (move) {
+ Files.move(src, dst);
+ } else {
+ Files.copy(src, dst);
+ }
+ }
+ }
+
+ if (move) {
+ // Delete source dirs.
+ for (var entry: entries) {
+ File srcFile = entry.getKey().toFile();
+ if (srcFile.isDirectory()) {
+ IOUtils.deleteRecursive(srcFile);
+ }
+ }
+ }
+ }
+
+ private static Map.Entry<Path, Path> normalizedPath(Path v) {
+ final Path normalized;
+ if (!v.isAbsolute()) {
+ normalized = Path.of("./").resolve(v.normalize());
+ } else {
+ normalized = v.normalize();
+ }
+
+ return Map.entry(normalized, v);
+ }
+
+ private List<Map.Entry<Path, Path>> normalizedPaths() {
+ return entries.values().stream().map(PathGroup::normalizedPath).collect(
+ Collectors.toList());
+ }
+
+ private final Map<Object, Path> entries;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Platform.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2016, 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.util.regex.Pattern;
+
+/**
+ * Platform
+ *
+ * Use <code>Platform</code> to detect the operating system
+ * that is currently running.
+ *
+ * Example:
+ *
+ * Platform platform = Platform.getPlatform();
+ *
+ * switch(platform) {
+ * case Platform.MAC: {
+ * // Do something
+ * break;
+ * }
+ * case Platform.WINDOWS:
+ * case Platform.LINUX: {
+ * // Do something else
+ * }
+ * }
+ *
+ */
+enum Platform {UNKNOWN, WINDOWS, LINUX, MAC;
+ private static final Platform platform;
+ private static final int majorVersion;
+ private static final int minorVersion;
+
+ static {
+ String os = System.getProperty("os.name").toLowerCase();
+
+ if (os.indexOf("win") >= 0) {
+ platform = Platform.WINDOWS;
+ }
+ else if (os.indexOf("nix") >= 0 || os.indexOf("nux") >= 0) {
+ platform = Platform.LINUX;
+ }
+ else if (os.indexOf("mac") >= 0) {
+ platform = Platform.MAC;
+ }
+ else {
+ platform = Platform.UNKNOWN;
+ }
+
+ String version = System.getProperty("os.version").toString();
+ String[] parts = version.split(Pattern.quote("."));
+
+ if (parts.length > 0) {
+ majorVersion = Integer.parseInt(parts[0]);
+
+ if (parts.length > 1) {
+ minorVersion = Integer.parseInt(parts[1]);
+ }
+ else {
+ minorVersion = -1;
+ }
+ }
+ else {
+ majorVersion = -1;
+ minorVersion = -1;
+ }
+ }
+
+ private Platform() {}
+
+ static Platform getPlatform() {
+ return platform;
+ }
+
+ static int getMajorVersion() {
+ return majorVersion;
+ }
+
+ static int getMinorVersion() {
+ return minorVersion;
+ }
+
+ static boolean isWindows() {
+ return getPlatform() == WINDOWS;
+ }
+
+ static boolean isMac() {
+ return getPlatform() == MAC;
+ }
+
+ static boolean isLinux() {
+ return getPlatform() == LINUX;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PlatformPackage.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,53 @@
+/*
+ * 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.nio.file.Path;
+
+/**
+ *
+ * Platform package of an application.
+ */
+interface PlatformPackage {
+ /**
+ * Platform-specific package name.
+ */
+ String name();
+
+ /**
+ * Root directory where sources for packaging tool should be stored
+ */
+ Path sourceRoot();
+
+ /**
+ * Source application layout from which to build the package.
+ */
+ ApplicationLayout sourceApplicationLayout();
+
+ /**
+ * Application layout of the installed package.
+ */
+ ApplicationLayout installedApplicationLayout();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/RelativeFileSet.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2012, 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.File;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * RelativeFileSet
+ *
+ * A class encapsulating a directory and a set of files within it.
+ */
+class RelativeFileSet {
+
+ private File basedir;
+ private Set<String> files = new LinkedHashSet<>();
+
+ RelativeFileSet(File base, Collection<File> files) {
+ basedir = base;
+ String baseAbsolute = basedir.getAbsolutePath();
+ for (File f: files) {
+ String absolute = f.getAbsolutePath();
+ if (!absolute.startsWith(baseAbsolute)) {
+ throw new RuntimeException("File " + f.getAbsolutePath() +
+ " does not belong to " + baseAbsolute);
+ }
+ if (!absolute.equals(baseAbsolute)) {
+ // possible in jpackage case
+ this.files.add(absolute.substring(baseAbsolute.length()+1));
+ }
+ }
+ }
+
+ RelativeFileSet(File base, Set<File> files) {
+ this(base, (Collection<File>) files);
+ }
+
+ File getBaseDirectory() {
+ return basedir;
+ }
+
+ Set<String> getIncludedFiles() {
+ return files;
+ }
+
+ @Override
+ public String toString() {
+ if (files.size() == 1) {
+ return "" + basedir + File.pathSeparator + files;
+ }
+ return "RelativeFileSet {basedir:" + basedir
+ + ", files: {" + files + "}";
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/StandardBundlerParam.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,774 @@
+/*
+ * Copyright (c) 2014, 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.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;
+
+/**
+ * StandardBundlerParam
+ *
+ * A parameter to a bundler.
+ *
+ * Also contains static definitions of all of the common bundler parameters.
+ * (additional platform specific and mode specific bundler parameters
+ * are defined in each of the specific bundlers)
+ *
+ * Also contains static methods that operate on maps of parameters.
+ */
+class StandardBundlerParam<T> extends BundlerParamInfo<T> {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.MainResources");
+ private static final String JAVABASEJMOD = "java.base.jmod";
+ private final static String DEFAULT_VERSION = "1.0";
+ private final static String DEFAULT_RELEASE = "1";
+
+ 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<RelativeFileSet> APP_RESOURCES =
+ new StandardBundlerParam<>(
+ BundleParams.PARAM_APP_RESOURCES,
+ RelativeFileSet.class,
+ null, // no default. Required parameter
+ null // no string translation,
+ // tool must provide complex type
+ );
+
+ @SuppressWarnings("unchecked")
+ static final
+ StandardBundlerParam<List<RelativeFileSet>> APP_RESOURCES_LIST =
+ new StandardBundlerParam<>(
+ BundleParams.PARAM_APP_RESOURCES + "List",
+ (Class<List<RelativeFileSet>>) (Object) List.class,
+ // Default is appResources, as a single item list
+ p -> new ArrayList<>(Collections.singletonList(
+ APP_RESOURCES.fetchFrom(p))),
+ StandardBundlerParam::createAppResourcesListFromString
+ );
+
+ static final StandardBundlerParam<String> SOURCE_DIR =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.INPUT.getId(),
+ String.class,
+ p -> null,
+ (s, p) -> {
+ String value = String.valueOf(s);
+ if (value.charAt(value.length() - 1) ==
+ File.separatorChar) {
+ return value.substring(0, value.length() - 1);
+ }
+ else {
+ return value;
+ }
+ }
+ );
+
+ // note that each bundler is likely to replace this one with
+ // their own converter
+ static final StandardBundlerParam<RelativeFileSet> MAIN_JAR =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.MAIN_JAR.getId(),
+ RelativeFileSet.class,
+ params -> {
+ extractMainClassInfoFromAppResources(params);
+ return (RelativeFileSet) params.get("mainJar");
+ },
+ (s, p) -> getMainJar(s, p)
+ );
+
+ static final StandardBundlerParam<String> CLASSPATH =
+ new StandardBundlerParam<>(
+ "classpath",
+ String.class,
+ params -> {
+ extractMainClassInfoFromAppResources(params);
+ String cp = (String) params.get("classpath");
+ return cp == null ? "" : cp;
+ },
+ (s, p) -> s
+ );
+
+ static final StandardBundlerParam<String> MAIN_CLASS =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.APPCLASS.getId(),
+ String.class,
+ params -> {
+ if (isRuntimeInstaller(params)) {
+ return null;
+ }
+ extractMainClassInfoFromAppResources(params);
+ String s = (String) params.get(
+ BundleParams.PARAM_APPLICATION_CLASS);
+ if (s == null) {
+ s = JLinkBundlerHelper.getMainClass(params);
+ if (s.length() == 0) {
+ s = null;
+ }
+ }
+ return s;
+ },
+ (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"))
+ );
+
+ // note that each bundler is likely to replace this one with
+ // their own converter
+ static final StandardBundlerParam<String> VERSION =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.VERSION.getId(),
+ String.class,
+ params -> getDefaultAppVersion(params),
+ (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.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<String> IDENTIFIER =
+ new StandardBundlerParam<>(
+ "identifier.default",
+ String.class,
+ params -> {
+ String s = MAIN_CLASS.fetchFrom(params);
+ if (s == null) return null;
+
+ int idx = s.lastIndexOf(".");
+ if (idx >= 1) {
+ return s.substring(0, idx);
+ }
+ return s;
+ },
+ (s, p) -> s
+ );
+
+ static final StandardBundlerParam<Boolean> VERBOSE =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.VERBOSE.getId(),
+ Boolean.class,
+ params -> false,
+ // valueOf(null) is false, and we actually do want null
+ (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),
+ // valueOf(null) is false, and we actually do want null
+ (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),
+ // valueOf(null) is false, and we actually do want null
+ (s, p) -> null
+ );
+
+ @SuppressWarnings("unchecked")
+ static final StandardBundlerParam<List<String>> FA_EXTENSIONS =
+ new StandardBundlerParam<>(
+ "fileAssociation.extension",
+ (Class<List<String>>) (Object) List.class,
+ params -> null, // null means not matched to an extension
+ (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,
+ // null means not matched to a content/mime type
+ (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 -> { return getDefaultModulePath(); },
+ (s, p) -> {
+ List<Path> modulePath = Arrays.asList(s
+ .split(File.pathSeparator)).stream()
+ .map(ss -> new File(ss).toPath())
+ .collect(Collectors.toList());
+ Path javaBasePath = null;
+ if (modulePath != null) {
+ javaBasePath = JLinkBundlerHelper
+ .findPathOfModule(modulePath, JAVABASEJMOD);
+ } else {
+ modulePath = new ArrayList<Path>();
+ }
+
+ // Add the default JDK module path to the module path.
+ if (javaBasePath == null) {
+ List<Path> jdkModulePath = getDefaultModulePath();
+
+ if (jdkModulePath != null) {
+ modulePath.addAll(jdkModulePath);
+ javaBasePath =
+ JLinkBundlerHelper.findPathOfModule(
+ modulePath, JAVABASEJMOD);
+ }
+ }
+
+ if (javaBasePath == null ||
+ !Files.exists(javaBasePath)) {
+ Log.error(String.format(I18N.getString(
+ "warning.no.jdk.modules.found")));
+ }
+
+ return modulePath;
+ });
+
+ 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 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; // we are building or are given an application
+ }
+ // runtime installer requires --runtime-image, if this is false
+ // here then we should have thrown error validating args.
+ return params.containsKey(PREDEFINED_RUNTIME_IMAGE.getID());
+ }
+
+ static File getPredefinedAppImage(Map<String, ? super Object> params) {
+ File applicationImage = null;
+ if (PREDEFINED_APP_IMAGE.fetchFrom(params) != null) {
+ applicationImage = PREDEFINED_APP_IMAGE.fetchFrom(params);
+ if (!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,
+ AbstractAppImageBuilder appBuilder)
+ 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()));
+ }
+ File image = appBuilder.getRuntimeImageDir(topImage);
+ // copy whole runtime, need to skip jmods and src.zip
+ final List<String> excludes = Arrays.asList("jmods", "src.zip");
+ IOUtils.copyRecursive(image.toPath(), appBuilder.getRuntimeRoot(), excludes);
+
+ // if module-path given - copy modules to appDir/mods
+ List<Path> modulePath =
+ StandardBundlerParam.MODULE_PATH.fetchFrom(params);
+ List<Path> defaultModulePath = getDefaultModulePath();
+ Path dest = appBuilder.getAppModsDir();
+
+ if (dest != null) {
+ for (Path mp : modulePath) {
+ if (!defaultModulePath.contains(mp)) {
+ Files.createDirectories(dest);
+ IOUtils.copyRecursive(mp, dest);
+ }
+ }
+ }
+
+ appBuilder.prepareApplicationFiles(params);
+ }
+
+ static void extractMainClassInfoFromAppResources(
+ Map<String, ? super Object> params) {
+ boolean hasMainClass = params.containsKey(MAIN_CLASS.getID());
+ boolean hasMainJar = params.containsKey(MAIN_JAR.getID());
+ boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID());
+ boolean hasModule = params.containsKey(MODULE.getID());
+
+ if (hasMainClass && hasMainJar && hasMainJarClassPath || hasModule ||
+ isRuntimeInstaller(params)) {
+ return;
+ }
+
+ // it's a pair.
+ // The [0] is the srcdir [1] is the file relative to sourcedir
+ List<String[]> filesToCheck = new ArrayList<>();
+
+ if (hasMainJar) {
+ RelativeFileSet rfs = MAIN_JAR.fetchFrom(params);
+ for (String s : rfs.getIncludedFiles()) {
+ filesToCheck.add(
+ new String[] {rfs.getBaseDirectory().toString(), s});
+ }
+ } else if (hasMainJarClassPath) {
+ for (String s : CLASSPATH.fetchFrom(params).split("\\s+")) {
+ if (APP_RESOURCES.fetchFrom(params) != null) {
+ filesToCheck.add(
+ new String[] {APP_RESOURCES.fetchFrom(params)
+ .getBaseDirectory().toString(), s});
+ }
+ }
+ } else {
+ List<RelativeFileSet> rfsl = APP_RESOURCES_LIST.fetchFrom(params);
+ if (rfsl == null || rfsl.isEmpty()) {
+ return;
+ }
+ for (RelativeFileSet rfs : rfsl) {
+ if (rfs == null) continue;
+
+ for (String s : rfs.getIncludedFiles()) {
+ filesToCheck.add(
+ new String[]{rfs.getBaseDirectory().toString(), s});
+ }
+ }
+ }
+
+ // presume the set iterates in-order
+ for (String[] fnames : filesToCheck) {
+ try {
+ // only sniff jars
+ if (!fnames[1].toLowerCase().endsWith(".jar")) continue;
+
+ File file = new File(fnames[0], fnames[1]);
+ // that actually exist
+ if (!file.exists()) continue;
+
+ try (JarFile jf = new JarFile(file)) {
+ Manifest m = jf.getManifest();
+ Attributes attrs = (m != null) ?
+ m.getMainAttributes() : null;
+
+ if (attrs != null) {
+ if (!hasMainJar) {
+ if (fnames[0] == null) {
+ fnames[0] = file.getParentFile().toString();
+ }
+ params.put(MAIN_JAR.getID(), new RelativeFileSet(
+ new File(fnames[0]),
+ new LinkedHashSet<>(Collections
+ .singletonList(file))));
+ }
+ if (!hasMainJarClassPath) {
+ String cp =
+ attrs.getValue(Attributes.Name.CLASS_PATH);
+ params.put(CLASSPATH.getID(),
+ cp == null ? "" : cp);
+ }
+ break;
+ }
+ }
+ } catch (IOException ignore) {
+ ignore.printStackTrace();
+ }
+ }
+ }
+
+ static void validateMainClassInfoFromAppResources(
+ Map<String, ? super Object> params) throws ConfigException {
+ boolean hasMainClass = params.containsKey(MAIN_CLASS.getID());
+ boolean hasMainJar = params.containsKey(MAIN_JAR.getID());
+ boolean hasMainJarClassPath = params.containsKey(CLASSPATH.getID());
+ boolean hasModule = params.containsKey(MODULE.getID());
+ boolean hasAppImage = params.containsKey(PREDEFINED_APP_IMAGE.getID());
+
+ if (hasMainClass && hasMainJar && hasMainJarClassPath ||
+ hasModule || hasAppImage || isRuntimeInstaller(params)) {
+ return;
+ }
+
+ extractMainClassInfoFromAppResources(params);
+
+ if (!params.containsKey(MAIN_CLASS.getID())) {
+ if (hasMainJar) {
+ throw new ConfigException(
+ MessageFormat.format(I18N.getString(
+ "error.no-main-class-with-main-jar"),
+ MAIN_JAR.fetchFrom(params)),
+ MessageFormat.format(I18N.getString(
+ "error.no-main-class-with-main-jar.advice"),
+ MAIN_JAR.fetchFrom(params)));
+ } else {
+ throw new ConfigException(
+ I18N.getString("error.no-main-class"),
+ I18N.getString("error.no-main-class.advice"));
+ }
+ }
+ }
+
+ private static List<RelativeFileSet>
+ createAppResourcesListFromString(String s,
+ Map<String, ? super Object> objectObjectMap) {
+ List<RelativeFileSet> result = new ArrayList<>();
+ for (String path : s.split("[:;]")) {
+ File f = new File(path);
+ if (f.getName().equals("*") || path.endsWith("/") ||
+ path.endsWith("\\")) {
+ if (f.getName().equals("*")) {
+ f = f.getParentFile();
+ }
+ Set<File> theFiles = new HashSet<>();
+ try {
+ Files.walk(f.toPath())
+ .filter(Files::isRegularFile)
+ .forEach(p -> theFiles.add(p.toFile()));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ result.add(new RelativeFileSet(f, theFiles));
+ } else {
+ result.add(new RelativeFileSet(f.getParentFile(),
+ Collections.singleton(f)));
+ }
+ }
+ return result;
+ }
+
+ private static RelativeFileSet getMainJar(
+ String mainJarValue, Map<String, ? super Object> params) {
+ for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(params)) {
+ File appResourcesRoot = rfs.getBaseDirectory();
+ File mainJarFile = new File(appResourcesRoot, mainJarValue);
+
+ if (mainJarFile.exists()) {
+ return new RelativeFileSet(appResourcesRoot,
+ new LinkedHashSet<>(Collections.singletonList(
+ mainJarFile)));
+ }
+ mainJarFile = new File(mainJarValue);
+ if (mainJarFile.exists()) {
+ // absolute path for main-jar may fail is not legal
+ // below contains explicit error message.
+ } else {
+ List<Path> modulePath = MODULE_PATH.fetchFrom(params);
+ modulePath.removeAll(getDefaultModulePath());
+ if (!modulePath.isEmpty()) {
+ Path modularJarPath = JLinkBundlerHelper.findPathOfModule(
+ modulePath, mainJarValue);
+ if (modularJarPath != null &&
+ Files.exists(modularJarPath)) {
+ return new RelativeFileSet(appResourcesRoot,
+ new LinkedHashSet<>(Collections.singletonList(
+ modularJarPath.toFile())));
+ }
+ }
+ }
+ }
+
+ throw new IllegalArgumentException(
+ new ConfigException(MessageFormat.format(I18N.getString(
+ "error.main-jar-does-not-exist"),
+ mainJarValue), I18N.getString(
+ "error.main-jar-does-not-exist.advice")));
+ }
+
+ static List<Path> getDefaultModulePath() {
+ List<Path> result = new ArrayList<Path>();
+ Path jdkModulePath = Paths.get(
+ System.getProperty("java.home"), "jmods").toAbsolutePath();
+
+ if (jdkModulePath != null && Files.exists(jdkModulePath)) {
+ result.add(jdkModulePath);
+ }
+ else {
+ // On a developer build the JDK Home isn't where we expect it
+ // relative to the jmods directory. Do some extra
+ // processing to find it.
+ Map<String, String> env = System.getenv();
+
+ if (env.containsKey("JDK_HOME")) {
+ jdkModulePath = Paths.get(env.get("JDK_HOME"),
+ ".." + File.separator + "images"
+ + File.separator + "jmods").toAbsolutePath();
+
+ if (jdkModulePath != null && Files.exists(jdkModulePath)) {
+ result.add(jdkModulePath);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ static String getDefaultAppVersion(Map<String, ? super Object> params) {
+ String appVersion = DEFAULT_VERSION;
+
+ ModuleDescriptor descriptor = JLinkBundlerHelper.getMainModuleDescription(params);
+ if (descriptor != null) {
+ Optional<Version> oversion = descriptor.version();
+ if (oversion.isPresent()) {
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.module-version"),
+ oversion.get().toString(),
+ JLinkBundlerHelper.getMainModule(params)));
+ appVersion = oversion.get().toString();
+ }
+ }
+
+ return appVersion;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ToolValidator.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,105 @@
+/*
+ * 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.IOException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+
+public final class ToolValidator {
+
+ ToolValidator(String name) {
+ this.name = name;
+ args = new ArrayList<>();
+
+ if (Platform.getPlatform() == Platform.LINUX) {
+ setCommandLine("--version");
+ }
+ }
+
+ ToolValidator setCommandLine(String... args) {
+ this.args = List.of(args);
+ return this;
+ }
+
+ ToolValidator setMinimalVersion(String v) {
+ minimalVersion = v;
+ return this;
+ }
+
+ ToolValidator setVersionParser(Function<Stream<String>, String> v) {
+ versionParser = v;
+ return this;
+ }
+
+ ConfigException validate() {
+ List<String> cmdline = new ArrayList<>();
+ cmdline.add(name);
+ cmdline.addAll(args);
+ try {
+ ProcessBuilder pb = new ProcessBuilder(cmdline);
+ AtomicBoolean canUseTool = new AtomicBoolean();
+ if (minimalVersion == null) {
+ // No version check.
+ canUseTool.setPlain(true);
+ }
+
+ Executor.of(pb).setOutputConsumer(lines -> {
+ if (versionParser != null && minimalVersion != null) {
+ String version = versionParser.apply(lines);
+ if (minimalVersion.compareTo(version) < 0) {
+ canUseTool.setPlain(true);
+ }
+ }
+ }).execute();
+
+ if (!canUseTool.getPlain()) {
+ return new ConfigException(MessageFormat.format(I18N.getString(
+ "error.tool-old-version"), name, minimalVersion),
+ MessageFormat.format(I18N.getString(
+ "error.tool-old-version.advice"), name,
+ minimalVersion));
+ }
+ } catch (IOException e) {
+ return new ConfigException(MessageFormat.format(I18N.getString(
+ "error.tool-not-found"), name, e.getMessage()),
+ MessageFormat.format(I18N.getString(
+ "error.tool-not-found.advice"), name), e);
+ }
+
+ // All good. Tool can be used.
+ return null;
+ }
+
+ private final String name;
+ private List<String> args;
+ private String minimalVersion;
+ private Function<Stream<String>, String> versionParser;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/ValidOptions.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2018, 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.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import jdk.jpackage.internal.Arguments.CLIOptions;
+
+/**
+ * ValidOptions
+ *
+ * Two basic methods for validating command line options.
+ *
+ * initArgs()
+ * Computes the Map of valid options for each mode on this Platform.
+ *
+ * checkIfSupported(CLIOptions arg)
+ * Determine if the given arg is valid on this platform.
+ *
+ * checkIfImageSupported(CLIOptions arg)
+ * Determine if the given arg is valid for creating app image.
+ *
+ * checkIfInstallerSupported(CLIOptions arg)
+ * Determine if the given arg is valid for creating installer.
+ *
+ */
+class ValidOptions {
+
+ enum USE {
+ ALL, // valid in all cases
+ LAUNCHER, // valid when creating a launcher
+ INSTALL // valid when creating an installer
+ }
+
+ private static final HashMap<String, USE> options = new HashMap<>();
+
+
+ // initializing list of mandatory arguments
+ static {
+ options.put(CLIOptions.NAME.getId(), USE.ALL);
+ options.put(CLIOptions.VERSION.getId(), USE.ALL);
+ options.put(CLIOptions.OUTPUT.getId(), USE.ALL);
+ options.put(CLIOptions.TEMP_ROOT.getId(), USE.ALL);
+ options.put(CLIOptions.VERBOSE.getId(), USE.ALL);
+ options.put(CLIOptions.PREDEFINED_RUNTIME_IMAGE.getId(), USE.ALL);
+ options.put(CLIOptions.RESOURCE_DIR.getId(), USE.ALL);
+ options.put(CLIOptions.DESCRIPTION.getId(), USE.ALL);
+ options.put(CLIOptions.VENDOR.getId(), USE.ALL);
+ options.put(CLIOptions.COPYRIGHT.getId(), USE.ALL);
+ options.put(CLIOptions.PACKAGE_TYPE.getId(), USE.ALL);
+
+ options.put(CLIOptions.INPUT.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.MODULE.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.MODULE_PATH.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.ADD_MODULES.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.MAIN_JAR.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.APPCLASS.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.ICON.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.ARGUMENTS.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.JAVA_OPTIONS.getId(), USE.LAUNCHER);
+ options.put(CLIOptions.ADD_LAUNCHER.getId(), USE.LAUNCHER);
+
+ options.put(CLIOptions.LICENSE_FILE.getId(), USE.INSTALL);
+ options.put(CLIOptions.INSTALL_DIR.getId(), USE.INSTALL);
+ options.put(CLIOptions.PREDEFINED_APP_IMAGE.getId(), USE.INSTALL);
+
+ options.put(CLIOptions.FILE_ASSOCIATIONS.getId(),
+ (Platform.getPlatform() == Platform.MAC) ? USE.ALL : USE.INSTALL);
+
+ if (Platform.getPlatform() == Platform.WINDOWS) {
+ options.put(CLIOptions.WIN_CONSOLE_HINT.getId(), USE.LAUNCHER);
+
+ options.put(CLIOptions.WIN_MENU_HINT.getId(), USE.INSTALL);
+ options.put(CLIOptions.WIN_MENU_GROUP.getId(), USE.INSTALL);
+ options.put(CLIOptions.WIN_SHORTCUT_HINT.getId(), USE.INSTALL);
+ options.put(CLIOptions.WIN_DIR_CHOOSER.getId(), USE.INSTALL);
+ options.put(CLIOptions.WIN_UPGRADE_UUID.getId(), USE.INSTALL);
+ options.put(CLIOptions.WIN_PER_USER_INSTALLATION.getId(),
+ USE.INSTALL);
+ }
+
+ if (Platform.getPlatform() == Platform.MAC) {
+ options.put(CLIOptions.MAC_SIGN.getId(), USE.ALL);
+ options.put(CLIOptions.MAC_BUNDLE_NAME.getId(), USE.ALL);
+ options.put(CLIOptions.MAC_BUNDLE_IDENTIFIER.getId(), USE.ALL);
+ options.put(CLIOptions.MAC_BUNDLE_SIGNING_PREFIX.getId(),
+ USE.ALL);
+ options.put(CLIOptions.MAC_SIGNING_KEY_NAME.getId(), USE.ALL);
+ options.put(CLIOptions.MAC_SIGNING_KEYCHAIN.getId(), USE.ALL);
+ options.put(CLIOptions.MAC_APP_STORE_CATEGORY.getId(), USE.ALL);
+ options.put(CLIOptions.MAC_APP_STORE_ENTITLEMENTS.getId(),
+ USE.ALL);
+ }
+
+ if (Platform.getPlatform() == Platform.LINUX) {
+ options.put(CLIOptions.LINUX_BUNDLE_NAME.getId(), USE.INSTALL);
+ options.put(CLIOptions.LINUX_DEB_MAINTAINER.getId(), USE.INSTALL);
+ options.put(CLIOptions.LINUX_CATEGORY.getId(), USE.INSTALL);
+ options.put(CLIOptions.LINUX_RPM_LICENSE_TYPE.getId(), USE.INSTALL);
+ options.put(CLIOptions.LINUX_PACKAGE_DEPENDENCIES.getId(),
+ USE.INSTALL);
+ options.put(CLIOptions.LINUX_MENU_GROUP.getId(), USE.INSTALL);
+ options.put(CLIOptions.RELEASE.getId(), USE.INSTALL);
+ options.put(CLIOptions.LINUX_SHORTCUT_HINT.getId(), USE.INSTALL);
+ }
+ }
+
+ static boolean checkIfSupported(CLIOptions arg) {
+ return options.containsKey(arg.getId());
+ }
+
+ static boolean checkIfImageSupported(CLIOptions arg) {
+ USE use = options.get(arg.getId());
+ return USE.ALL == use || USE.LAUNCHER == use;
+ }
+
+ static boolean checkIfInstallerSupported(CLIOptions arg) {
+ USE use = options.get(arg.getId());
+ return USE.ALL == use || USE.INSTALL == use;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,271 @@
+#
+# Copyright (c) 2017, 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.
+#
+#
+
+MSG_Help=Usage: jpackage <options>\n\
+\n\
+Sample usages:\n\
+--------------\n\
+\ Generate a non-modular application image:\n\
+\ jpackage --package-type app-image -d destdir -i inputdir -n name \\\n\
+\ --main-class className --main-jar MyJar.jar\n\
+\ Generate a modular application image:\n\
+\ jpackage --package-type app-image -n name -p modulePath \\\n\
+\ -m moduleName/className\n\
+\ To provide your own options to jlink, run jlink separately:\n\
+\ jlink --output appRuntimeImage -p ModulePath -m moduleName \\\n\
+\ --no-header-files [<additional jlink options>...]\n\
+\ jpackage --package-type app-image -n name \\\n\
+\ -m moduleName/className --runtime-image appRuntimeImage\n\
+\ Generate an application package:\n\
+\ jpackage --package-type <type> -n name \\\n\
+\ -p modulePath -m moduleName/className\n\
+\ jpackage --package-type <type> -i inputdir -n name \\\n\
+\ --main-class package.ClassName --main-jar MyJar.jar\n\
+\ jpackage --package-type <type> -n name \\\n\
+\ --app-image <app image dir>\n\
+\ Generate a Java runtime package:\n\
+\ jpackage -n name --runtime-image <runtime-image>\n\
+\n\
+Generic Options:\n\
+\ @<filename> \n\
+\ Read options and/or mode from a file \n\
+\ This option can be used multiple times.\n\
+\ --package-type <type> \n\
+\ The type of package to create\n\
+\ Valid values are: {1} \n\
+\ If this option is not specified a platform dependent\n\
+\ default package type will be created.\n\
+\ --app-version <version>\n\
+\ Version of the application and/or package\n\
+\ --copyright <copyright string>\n\
+\ Copyright for the application\n\
+\ --description <description string>\n\
+\ Description of the application\n\
+\ --help -h \n\
+\ Print the usage text with a list and description of each valid\n\
+\ option for the current platform to the output stream, and exit\n\
+\ --name -n <name>\n\
+\ Name of the application and/or package\n\
+\ --dest -d <destination path>\n\
+\ Path where generated output file is placed\n\
+\ Defaults to the current working directory.\n\
+\ (absolute path or relative to the current directory)\n\
+\ --temp <file path>\n\
+\ Path of a new or empty directory used to create temporary files\n\
+\ (absolute path or relative to the current directory)\n\
+\ If specified, the temp dir will not be removed upon the task\n\
+\ completion and must be removed manually\n\
+\ If not specified, a temporary directory will be created and\n\
+\ removed upon the task completion.\n\
+\ --vendor <vendor string>\n\
+\ Vendor of the application\n\
+\ --verbose\n\
+\ Enables verbose output\n\
+\ --version\n\
+\ Print the product version to the output stream and exit\n\
+\n\
+\Options for creating the runtime image:\n\
+\ --add-modules <module name>[,<module name>...]\n\
+\ A comma (",") separated list of modules to add.\n\
+\ This module list, along with the main module (if specified)\n\
+\ will be passed to jlink as the --add-module argument.\n\
+\ if not specified, either just the main module (if --module is\n\
+\ specified), or the default set of modules (if --main-jar is \n\
+\ specified) are used.\n\
+\ This option can be used multiple times.\n\
+\ --module-path -p <module path>...\n\
+\ A {0} separated list of paths\n\
+\ Each path is either a directory of modules or the path to a\n\
+\ modular jar.\n\
+\ (each path is absolute or relative to the current directory)\n\
+\ This option can be used multiple times.\n\
+\ --runtime-image <file path>\n\
+\ Path of the predefined runtime image that will be copied into\n\
+\ the application image\n\
+\ (absolute path or relative to the current directory)\n\
+\ If --runtime-image is not specified, jpackage will run jlink to\n\
+\ create the runtime image using options:\n\
+\ --strip-debug, --no-header-files, --no-man-pages, and\n\
+\ --strip-native-commands. --bind-services will also be added if\n\
+\ --add-modules is not specified.\n\
+\n\
+\Options for creating the application image:\n\
+\ --icon <icon file path>\n\
+\ Path of the icon of the application package\n\
+\ (absolute path or relative to the current directory)\n\
+\ --input -i <input path>\n\
+\ Path of the input directory that contains the files to be packaged\n\
+\ (absolute path or relative to the current directory)\n\
+\ All files in the input directory will be packaged into the\n\
+\ application image.\n\
+\n\
+\Options for creating the application launcher(s):\n\
+\ --add-launcher <launcher name>=<file path>\n\
+\ Name of launcher, and a path to a Properties file that contains\n\
+\ a list of key, value pairs\n\
+\ (absolute path or relative to the current directory)\n\
+\ The keys "module", "main-jar", "main-class",\n\
+\ "arguments", "java-options", "app-version", "icon", and\n\
+\ "win-console" can be used.\n\
+\ These options are added to, or used to overwrite, the original\n\
+\ command line options to build an additional alternative launcher.\n\
+\ The main application launcher will be built from the command line\n\
+\ options. Additional alternative launchers can be built using\n\
+\ this option, and this option can be used multiple times to\n\
+\ build multiple additional launchers. \n\
+\ --arguments <main class arguments>\n\
+\ Command line arguments to pass to the main class if no command\n\
+\ line arguments are given to the launcher\n\
+\ This option can be used multiple times.\n\
+\ --java-options <java options>\n\
+\ Options to pass to the Java runtime\n\
+\ This option can be used multiple times.\n\
+\ --main-class <class name>\n\
+\ Qualified name of the application main class to execute\n\
+\ This option can only be used if --main-jar is specified.\n\
+\ --main-jar <main jar file>\n\
+\ The main JAR of the application; containing the main class\n\
+\ (specified as a path relative to the input path)\n\
+\ Either --module or --main-jar option can be specified but not\n\
+\ both.\n\
+\ --module -m <module name>[/<main class>]\n\
+\ The main module (and optionally main class) of the application\n\
+\ This module must be located on the module path.\n\
+\ When this option is specified, the main module will be linked\n\
+\ in the Java runtime image. Either --module or --main-jar\n\
+\ option can be specified but not both.\n\
+{2}\n\
+\Options for creating the application package:\n\
+\ --app-image <file path>\n\
+\ Location of the predefined application image that is used\n\
+\ to build an installable package\n\
+\ (absolute path or relative to the current directory)\n\
+\ --file-associations <file path>\n\
+\ Path to a Properties file that contains list of key, value pairs\n\
+\ (absolute path or relative to the current directory)\n\
+\ The keys "extension", "mime-type", "icon", and "description"\n\
+\ can be used to describe the association.\n\
+\ This option can be used multiple times.\n\
+\ --install-dir <file path>\n\
+\ {4}\
+\ --license-file <file path>\n\
+\ Path to the license file\n\
+\ (absolute path or relative to the current directory)\n\
+\ --resource-dir <path>\n\
+\ Path to override jpackage resources\n\
+\ Icons, template files, and other resources of jpackage can be\n\
+\ over-ridden by adding replacement resources to this directory.\n\
+\ (absolute path or relative to the current directory)\n\
+\ --runtime-image <file-path>\n\
+\ Path of the predefined runtime image to install\n\
+\ (absolute path or relative to the current directory)\n\
+\ Option is required when creating a runtime package.\n\
+\n\
+\Platform dependent options for creating the application package:\n\
+{3}
+
+MSG_Help_win_launcher=\
+\n\
+\Platform dependent option for creating the application launcher:\n\
+\ --win-console\n\
+\ Creates a console launcher for the application, should be\n\
+\ specified for application which requires console interactions\n\
+
+MSG_Help_win_install=\
+\ --win-dir-chooser\n\
+\ Adds a dialog to enable the user to choose a directory in which\n\
+\ the product is installed\n\
+\ --win-menu\n\
+\ Adds the application to the system menu\n\
+\ --win-menu-group <menu group name>\n\
+\ Start Menu group this application is placed in\n\
+\ --win-per-user-install\n\
+\ Request to perform an install on a per-user basis\n\
+\ --win-shortcut\n\
+\ Creates a desktop shortcut for the application\n\
+\ --win-upgrade-uuid <id string>\n\
+\ UUID associated with upgrades for this package\n\
+
+MSG_Help_win_install_dir=\
+\Relative sub-path under the default installation location\n\
+
+MSG_Help_mac_launcher=\
+\ --mac-package-identifier <ID string>\n\
+\ An identifier that uniquely identifies the application for macOS\n\
+\ Defaults to the main class name.\n\
+\ May only use alphanumeric (A-Z,a-z,0-9), hyphen (-),\n\
+\ and period (.) characters.\n\
+\ --mac-package-name <name string>\n\
+\ Name of the application as it appears in the Menu Bar\n\
+\ This can be different from the application name.\n\
+\ This name must be less than 16 characters long and be suitable for\n\
+\ displaying in the menu bar and the application Info window.\n\
+\ Defaults to the application name.\n\
+\ --mac-package-signing-prefix <prefix string>\n\
+\ When signing the application package, this value is prefixed\n\
+\ to all components that need to be signed that don't have\n\
+\ an existing package identifier.\n\
+\ --mac-sign\n\
+\ Request that the package be signed\n\
+\ --mac-signing-keychain <file path>\n\
+\ Path of the keychain to search for the signing identity\n\
+\ (absolute path or relative to the current directory).\n\
+\ If not specified, the standard keychains are used.\n\
+\ --mac-signing-key-user-name <team name>\n\
+\ Team name portion in Apple signing identities' names.\n\
+\ For example "Developer ID Application: "\n\
+
+MSG_Help_linux_install=\
+\ --linux-package-name <package name>\n\
+\ Name for Linux package, defaults to the application name\n\
+\ --linux-deb-maintainer <email address>\n\
+\ Maintainer for .deb package\n\
+\ --linux-menu-group <menu-group-name>\n\
+\ Menu group this application is placed in\n\
+\ --linux-package-deps\n\
+\ Required packages or capabilities for the application\n\
+\ --linux-rpm-license-type <type string>\n\
+\ Type of the license ("License: <value>" of the RPM .spec)\n\
+\ --linux-app-release <release value>\n\
+\ Release value of the RPM <name>.spec file or \n\
+\ Debian revision value of the DEB control file.\n\
+\ --linux-app-category <category value>\n\
+\ Group value of the RPM <name>.spec file or \n\
+\ Section value of DEB control file.\n\
+\ --linux-shortcut\n\
+\ Creates a shortcut for the application\n\
+
+MSG_Help_mac_linux_install_dir=\
+\Absolute path of the installation directory of the application\n\
+
+MSG_Help_default_install_dir=\
+\Absolute path of the installation directory of the application on OS X\n\
+\ or Linux. Relative sub-path of the installation location of\n\
+\ the application such as "Program Files" or "AppData" on Windows.\n\
+
+MSG_Help_no_args=Usage: jpackage <mode> <options>\n\
+\Use jpackage --help (or -h) for a list of possible options\
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_ja.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,271 @@
+#
+# Copyright (c) 2017, 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.
+#
+#
+
+MSG_Help=Usage: jpackage <options>\n\
+\n\
+Sample usages:\n\
+--------------\n\
+\ Generate a non-modular application image:\n\
+\ jpackage --package-type app-image -d destdir -i inputdir -n name \\\n\
+\ --main-class className --main-jar MyJar.jar\n\
+\ Generate a modular application image:\n\
+\ jpackage --package-type app-image -n name -p modulePath \\\n\
+\ -m moduleName/className\n\
+\ To provide your own options to jlink, run jlink separately:\n\
+\ jlink --output appRuntimeImage -p ModulePath -m moduleName \\\n\
+\ --no-header-files [<additional jlink options>...]\n\
+\ jpackage --package-type app-image -n name \\\n\
+\ -m moduleName/className --runtime-image appRuntimeImage\n\
+\ Generate an application package:\n\
+\ jpackage --package-type <type> -n name \\\n\
+\ -p modulePath -m moduleName/className\n\
+\ jpackage --package-type <type> -i inputdir -n name \\\n\
+\ --main-class package.ClassName --main-jar MyJar.jar\n\
+\ jpackage --package-type <type> -n name \\\n\
+\ --app-image <app image dir>\n\
+\ Generate a Java runtime package:\n\
+\ jpackage -n name --runtime-image <runtime-image>\n\
+\n\
+Generic Options:\n\
+\ @<filename> \n\
+\ Read options and/or mode from a file \n\
+\ This option can be used multiple times.\n\
+\ --package-type <type> \n\
+\ The type of package to create\n\
+\ Valid values are: {1} \n\
+\ If this option is not specified a platform dependent\n\
+\ default package type will be created.\n\
+\ --app-version <version>\n\
+\ Version of the application and/or package\n\
+\ --copyright <copyright string>\n\
+\ Copyright for the application\n\
+\ --description <description string>\n\
+\ Description of the application\n\
+\ --help -h \n\
+\ Print the usage text with a list and description of each valid\n\
+\ option for the current platform to the output stream, and exit\n\
+\ --name -n <name>\n\
+\ Name of the application and/or package\n\
+\ --dest -d <destination path>\n\
+\ Path where generated output file is placed\n\
+\ Defaults to the current working directory.\n\
+\ (absolute path or relative to the current directory)\n\
+\ --temp <file path>\n\
+\ Path of a new or empty directory used to create temporary files\n\
+\ (absolute path or relative to the current directory)\n\
+\ If specified, the temp dir will not be removed upon the task\n\
+\ completion and must be removed manually\n\
+\ If not specified, a temporary directory will be created and\n\
+\ removed upon the task completion.\n\
+\ --vendor <vendor string>\n\
+\ Vendor of the application\n\
+\ --verbose\n\
+\ Enables verbose output\n\
+\ --version\n\
+\ Print the product version to the output stream and exit\n\
+\n\
+\Options for creating the runtime image:\n\
+\ --add-modules <module name>[,<module name>...]\n\
+\ A comma (",") separated list of modules to add.\n\
+\ This module list, along with the main module (if specified)\n\
+\ will be passed to jlink as the --add-module argument.\n\
+\ if not specified, either just the main module (if --module is\n\
+\ specified), or the default set of modules (if --main-jar is \n\
+\ specified) are used.\n\
+\ This option can be used multiple times.\n\
+\ --module-path -p <module path>...\n\
+\ A {0} separated list of paths\n\
+\ Each path is either a directory of modules or the path to a\n\
+\ modular jar.\n\
+\ (each path is absolute or relative to the current directory)\n\
+\ This option can be used multiple times.\n\
+\ --runtime-image <file path>\n\
+\ Path of the predefined runtime image that will be copied into\n\
+\ the application image\n\
+\ (absolute path or relative to the current directory)\n\
+\ If --runtime-image is not specified, jpackage will run jlink to\n\
+\ create the runtime image using options:\n\
+\ --strip-debug, --no-header-files, --no-man-pages, and\n\
+\ --strip-native-commands. --bind-services will also be added if\n\
+\ --add-modules is not specified.\n\
+\n\
+\Options for creating the application image:\n\
+\ --icon <icon file path>\n\
+\ Path of the icon of the application package\n\
+\ (absolute path or relative to the current directory)\n\
+\ --input -i <input path>\n\
+\ Path of the input directory that contains the files to be packaged\n\
+\ (absolute path or relative to the current directory)\n\
+\ All files in the input directory will be packaged into the\n\
+\ application image.\n\
+\n\
+\Options for creating the application launcher(s):\n\
+\ --add-launcher <launcher name>=<file path>\n\
+\ Name of launcher, and a path to a Properties file that contains\n\
+\ a list of key, value pairs\n\
+\ (absolute path or relative to the current directory)\n\
+\ The keys "module", "main-jar", "main-class",\n\
+\ "arguments", "java-options", "app-version", "icon", and\n\
+\ "win-console" can be used.\n\
+\ These options are added to, or used to overwrite, the original\n\
+\ command line options to build an additional alternative launcher.\n\
+\ The main application launcher will be built from the command line\n\
+\ options. Additional alternative launchers can be built using\n\
+\ this option, and this option can be used multiple times to\n\
+\ build multiple additional launchers. \n\
+\ --arguments <main class arguments>\n\
+\ Command line arguments to pass to the main class if no command\n\
+\ line arguments are given to the launcher\n\
+\ This option can be used multiple times.\n\
+\ --java-options <java options>\n\
+\ Options to pass to the Java runtime\n\
+\ This option can be used multiple times.\n\
+\ --main-class <class name>\n\
+\ Qualified name of the application main class to execute\n\
+\ This option can only be used if --main-jar is specified.\n\
+\ --main-jar <main jar file>\n\
+\ The main JAR of the application; containing the main class\n\
+\ (specified as a path relative to the input path)\n\
+\ Either --module or --main-jar option can be specified but not\n\
+\ both.\n\
+\ --module -m <module name>[/<main class>]\n\
+\ The main module (and optionally main class) of the application\n\
+\ This module must be located on the module path.\n\
+\ When this option is specified, the main module will be linked\n\
+\ in the Java runtime image. Either --module or --main-jar\n\
+\ option can be specified but not both.\n\
+{2}\n\
+\Options for creating the application package:\n\
+\ --app-image <file path>\n\
+\ Location of the predefined application image that is used\n\
+\ to build an installable package\n\
+\ (absolute path or relative to the current directory)\n\
+\ --file-associations <file path>\n\
+\ Path to a Properties file that contains list of key, value pairs\n\
+\ (absolute path or relative to the current directory)\n\
+\ The keys "extension", "mime-type", "icon", and "description"\n\
+\ can be used to describe the association.\n\
+\ This option can be used multiple times.\n\
+\ --install-dir <file path>\n\
+\ {4}\
+\ --license-file <file path>\n\
+\ Path to the license file\n\
+\ (absolute path or relative to the current directory)\n\
+\ --resource-dir <path>\n\
+\ Path to override jpackage resources\n\
+\ Icons, template files, and other resources of jpackage can be\n\
+\ over-ridden by adding replacement resources to this directory.\n\
+\ (absolute path or relative to the current directory)\n\
+\ --runtime-image <file-path>\n\
+\ Path of the predefined runtime image to install\n\
+\ (absolute path or relative to the current directory)\n\
+\ Option is required when creating a runtime package.\n\
+\n\
+\Platform dependent options for creating the application package:\n\
+{3}
+
+MSG_Help_win_launcher=\
+\n\
+\Platform dependent option for creating the application launcher:\n\
+\ --win-console\n\
+\ Creates a console launcher for the application, should be\n\
+\ specified for application which requires console interactions\n\
+
+MSG_Help_win_install=\
+\ --win-dir-chooser\n\
+\ Adds a dialog to enable the user to choose a directory in which\n\
+\ the product is installed\n\
+\ --win-menu\n\
+\ Adds the application to the system menu\n\
+\ --win-menu-group <menu group name>\n\
+\ Start Menu group this application is placed in\n\
+\ --win-per-user-install\n\
+\ Request to perform an install on a per-user basis\n\
+\ --win-shortcut\n\
+\ Creates a desktop shortcut for the application\n\
+\ --win-upgrade-uuid <id string>\n\
+\ UUID associated with upgrades for this package\n\
+
+MSG_Help_win_install_dir=\
+\Relative sub-path under the default installation location\n\
+
+MSG_Help_mac_launcher=\
+\ --mac-package-identifier <ID string>\n\
+\ An identifier that uniquely identifies the application for macOS\n\
+\ Defaults to the main class name.\n\
+\ May only use alphanumeric (A-Z,a-z,0-9), hyphen (-),\n\
+\ and period (.) characters.\n\
+\ --mac-package-name <name string>\n\
+\ Name of the application as it appears in the Menu Bar\n\
+\ This can be different from the application name.\n\
+\ This name must be less than 16 characters long and be suitable for\n\
+\ displaying in the menu bar and the application Info window.\n\
+\ Defaults to the application name.\n\
+\ --mac-package-signing-prefix <prefix string>\n\
+\ When signing the application package, this value is prefixed\n\
+\ to all components that need to be signed that don't have\n\
+\ an existing package identifier.\n\
+\ --mac-sign\n\
+\ Request that the package be signed\n\
+\ --mac-signing-keychain <file path>\n\
+\ Path of the keychain to search for the signing identity\n\
+\ (absolute path or relative to the current directory).\n\
+\ If not specified, the standard keychains are used.\n\
+\ --mac-signing-key-user-name <team name>\n\
+\ Team name portion in Apple signing identities' names.\n\
+\ For example "Developer ID Application: "\n\
+
+MSG_Help_linux_install=\
+\ --linux-package-name <package name>\n\
+\ Name for Linux package, defaults to the application name\n\
+\ --linux-deb-maintainer <email address>\n\
+\ Maintainer for .deb package\n\
+\ --linux-menu-group <menu-group-name>\n\
+\ Menu group this application is placed in\n\
+\ --linux-package-deps\n\
+\ Required packages or capabilities for the application\n\
+\ --linux-rpm-license-type <type string>\n\
+\ Type of the license ("License: <value>" of the RPM .spec)\n\
+\ --linux-app-release <release value>\n\
+\ Release value of the RPM <name>.spec file or \n\
+\ Debian revision value of the DEB control file.\n\
+\ --linux-app-category <category value>\n\
+\ Group value of the RPM <name>.spec file or \n\
+\ Section value of DEB control file.\n\
+\ --linux-shortcut\n\
+\ Creates a shortcut for the application\n\
+
+MSG_Help_mac_linux_install_dir=\
+\Absolute path of the installation directory of the application\n\
+
+MSG_Help_default_install_dir=\
+\Absolute path of the installation directory of the application on OS X\n\
+\ or Linux. Relative sub-path of the installation location of\n\
+\ the application such as "Program Files" or "AppData" on Windows.\n\
+
+MSG_Help_no_args=Usage: jpackage <mode> <options>\n\
+\Use jpackage --help (or -h) for a list of possible options\
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/HelpResources_zh_CN.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,271 @@
+#
+# Copyright (c) 2017, 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.
+#
+#
+
+MSG_Help=Usage: jpackage <options>\n\
+\n\
+Sample usages:\n\
+--------------\n\
+\ Generate a non-modular application image:\n\
+\ jpackage --package-type app-image -d destdir -i inputdir -n name \\\n\
+\ --main-class className --main-jar MyJar.jar\n\
+\ Generate a modular application image:\n\
+\ jpackage --package-type app-image -n name -p modulePath \\\n\
+\ -m moduleName/className\n\
+\ To provide your own options to jlink, run jlink separately:\n\
+\ jlink --output appRuntimeImage -p ModulePath -m moduleName \\\n\
+\ --no-header-files [<additional jlink options>...]\n\
+\ jpackage --package-type app-image -n name \\\n\
+\ -m moduleName/className --runtime-image appRuntimeImage\n\
+\ Generate an application package:\n\
+\ jpackage --package-type <type> -n name \\\n\
+\ -p modulePath -m moduleName/className\n\
+\ jpackage --package-type <type> -i inputdir -n name \\\n\
+\ --main-class package.ClassName --main-jar MyJar.jar\n\
+\ jpackage --package-type <type> -n name \\\n\
+\ --app-image <app image dir>\n\
+\ Generate a Java runtime package:\n\
+\ jpackage -n name --runtime-image <runtime-image>\n\
+\n\
+Generic Options:\n\
+\ @<filename> \n\
+\ Read options and/or mode from a file \n\
+\ This option can be used multiple times.\n\
+\ --package-type <type> \n\
+\ The type of package to create\n\
+\ Valid values are: {1} \n\
+\ If this option is not specified a platform dependent\n\
+\ default package type will be created.\n\
+\ --app-version <version>\n\
+\ Version of the application and/or package\n\
+\ --copyright <copyright string>\n\
+\ Copyright for the application\n\
+\ --description <description string>\n\
+\ Description of the application\n\
+\ --help -h \n\
+\ Print the usage text with a list and description of each valid\n\
+\ option for the current platform to the output stream, and exit\n\
+\ --name -n <name>\n\
+\ Name of the application and/or package\n\
+\ --dest -d <destination path>\n\
+\ Path where generated output file is placed\n\
+\ Defaults to the current working directory.\n\
+\ (absolute path or relative to the current directory)\n\
+\ --temp <file path>\n\
+\ Path of a new or empty directory used to create temporary files\n\
+\ (absolute path or relative to the current directory)\n\
+\ If specified, the temp dir will not be removed upon the task\n\
+\ completion and must be removed manually\n\
+\ If not specified, a temporary directory will be created and\n\
+\ removed upon the task completion.\n\
+\ --vendor <vendor string>\n\
+\ Vendor of the application\n\
+\ --verbose\n\
+\ Enables verbose output\n\
+\ --version\n\
+\ Print the product version to the output stream and exit\n\
+\n\
+\Options for creating the runtime image:\n\
+\ --add-modules <module name>[,<module name>...]\n\
+\ A comma (",") separated list of modules to add.\n\
+\ This module list, along with the main module (if specified)\n\
+\ will be passed to jlink as the --add-module argument.\n\
+\ if not specified, either just the main module (if --module is\n\
+\ specified), or the default set of modules (if --main-jar is \n\
+\ specified) are used.\n\
+\ This option can be used multiple times.\n\
+\ --module-path -p <module path>...\n\
+\ A {0} separated list of paths\n\
+\ Each path is either a directory of modules or the path to a\n\
+\ modular jar.\n\
+\ (each path is absolute or relative to the current directory)\n\
+\ This option can be used multiple times.\n\
+\ --runtime-image <file path>\n\
+\ Path of the predefined runtime image that will be copied into\n\
+\ the application image\n\
+\ (absolute path or relative to the current directory)\n\
+\ If --runtime-image is not specified, jpackage will run jlink to\n\
+\ create the runtime image using options:\n\
+\ --strip-debug, --no-header-files, --no-man-pages, and\n\
+\ --strip-native-commands. --bind-services will also be added if\n\
+\ --add-modules is not specified.\n\
+\n\
+\Options for creating the application image:\n\
+\ --icon <icon file path>\n\
+\ Path of the icon of the application package\n\
+\ (absolute path or relative to the current directory)\n\
+\ --input -i <input path>\n\
+\ Path of the input directory that contains the files to be packaged\n\
+\ (absolute path or relative to the current directory)\n\
+\ All files in the input directory will be packaged into the\n\
+\ application image.\n\
+\n\
+\Options for creating the application launcher(s):\n\
+\ --add-launcher <launcher name>=<file path>\n\
+\ Name of launcher, and a path to a Properties file that contains\n\
+\ a list of key, value pairs\n\
+\ (absolute path or relative to the current directory)\n\
+\ The keys "module", "main-jar", "main-class",\n\
+\ "arguments", "java-options", "app-version", "icon", and\n\
+\ "win-console" can be used.\n\
+\ These options are added to, or used to overwrite, the original\n\
+\ command line options to build an additional alternative launcher.\n\
+\ The main application launcher will be built from the command line\n\
+\ options. Additional alternative launchers can be built using\n\
+\ this option, and this option can be used multiple times to\n\
+\ build multiple additional launchers. \n\
+\ --arguments <main class arguments>\n\
+\ Command line arguments to pass to the main class if no command\n\
+\ line arguments are given to the launcher\n\
+\ This option can be used multiple times.\n\
+\ --java-options <java options>\n\
+\ Options to pass to the Java runtime\n\
+\ This option can be used multiple times.\n\
+\ --main-class <class name>\n\
+\ Qualified name of the application main class to execute\n\
+\ This option can only be used if --main-jar is specified.\n\
+\ --main-jar <main jar file>\n\
+\ The main JAR of the application; containing the main class\n\
+\ (specified as a path relative to the input path)\n\
+\ Either --module or --main-jar option can be specified but not\n\
+\ both.\n\
+\ --module -m <module name>[/<main class>]\n\
+\ The main module (and optionally main class) of the application\n\
+\ This module must be located on the module path.\n\
+\ When this option is specified, the main module will be linked\n\
+\ in the Java runtime image. Either --module or --main-jar\n\
+\ option can be specified but not both.\n\
+{2}\n\
+\Options for creating the application package:\n\
+\ --app-image <file path>\n\
+\ Location of the predefined application image that is used\n\
+\ to build an installable package\n\
+\ (absolute path or relative to the current directory)\n\
+\ --file-associations <file path>\n\
+\ Path to a Properties file that contains list of key, value pairs\n\
+\ (absolute path or relative to the current directory)\n\
+\ The keys "extension", "mime-type", "icon", and "description"\n\
+\ can be used to describe the association.\n\
+\ This option can be used multiple times.\n\
+\ --install-dir <file path>\n\
+\ {4}\
+\ --license-file <file path>\n\
+\ Path to the license file\n\
+\ (absolute path or relative to the current directory)\n\
+\ --resource-dir <path>\n\
+\ Path to override jpackage resources\n\
+\ Icons, template files, and other resources of jpackage can be\n\
+\ over-ridden by adding replacement resources to this directory.\n\
+\ (absolute path or relative to the current directory)\n\
+\ --runtime-image <file-path>\n\
+\ Path of the predefined runtime image to install\n\
+\ (absolute path or relative to the current directory)\n\
+\ Option is required when creating a runtime package.\n\
+\n\
+\Platform dependent options for creating the application package:\n\
+{3}
+
+MSG_Help_win_launcher=\
+\n\
+\Platform dependent option for creating the application launcher:\n\
+\ --win-console\n\
+\ Creates a console launcher for the application, should be\n\
+\ specified for application which requires console interactions\n\
+
+MSG_Help_win_install=\
+\ --win-dir-chooser\n\
+\ Adds a dialog to enable the user to choose a directory in which\n\
+\ the product is installed\n\
+\ --win-menu\n\
+\ Adds the application to the system menu\n\
+\ --win-menu-group <menu group name>\n\
+\ Start Menu group this application is placed in\n\
+\ --win-per-user-install\n\
+\ Request to perform an install on a per-user basis\n\
+\ --win-shortcut\n\
+\ Creates a desktop shortcut for the application\n\
+\ --win-upgrade-uuid <id string>\n\
+\ UUID associated with upgrades for this package\n\
+
+MSG_Help_win_install_dir=\
+\Relative sub-path under the default installation location\n\
+
+MSG_Help_mac_launcher=\
+\ --mac-package-identifier <ID string>\n\
+\ An identifier that uniquely identifies the application for macOS\n\
+\ Defaults to the main class name.\n\
+\ May only use alphanumeric (A-Z,a-z,0-9), hyphen (-),\n\
+\ and period (.) characters.\n\
+\ --mac-package-name <name string>\n\
+\ Name of the application as it appears in the Menu Bar\n\
+\ This can be different from the application name.\n\
+\ This name must be less than 16 characters long and be suitable for\n\
+\ displaying in the menu bar and the application Info window.\n\
+\ Defaults to the application name.\n\
+\ --mac-package-signing-prefix <prefix string>\n\
+\ When signing the application package, this value is prefixed\n\
+\ to all components that need to be signed that don't have\n\
+\ an existing package identifier.\n\
+\ --mac-sign\n\
+\ Request that the package be signed\n\
+\ --mac-signing-keychain <file path>\n\
+\ Path of the keychain to search for the signing identity\n\
+\ (absolute path or relative to the current directory).\n\
+\ If not specified, the standard keychains are used.\n\
+\ --mac-signing-key-user-name <team name>\n\
+\ Team name portion in Apple signing identities' names.\n\
+\ For example "Developer ID Application: "\n\
+
+MSG_Help_linux_install=\
+\ --linux-package-name <package name>\n\
+\ Name for Linux package, defaults to the application name\n\
+\ --linux-deb-maintainer <email address>\n\
+\ Maintainer for .deb package\n\
+\ --linux-menu-group <menu-group-name>\n\
+\ Menu group this application is placed in\n\
+\ --linux-package-deps\n\
+\ Required packages or capabilities for the application\n\
+\ --linux-rpm-license-type <type string>\n\
+\ Type of the license ("License: <value>" of the RPM .spec)\n\
+\ --linux-app-release <release value>\n\
+\ Release value of the RPM <name>.spec file or \n\
+\ Debian revision value of the DEB control file.\n\
+\ --linux-app-category <category value>\n\
+\ Group value of the RPM <name>.spec file or \n\
+\ Section value of DEB control file.\n\
+\ --linux-shortcut\n\
+\ Creates a shortcut for the application\n\
+
+MSG_Help_mac_linux_install_dir=\
+\Absolute path of the installation directory of the application\n\
+
+MSG_Help_default_install_dir=\
+\Absolute path of the installation directory of the application on OS X\n\
+\ or Linux. Relative sub-path of the installation location of\n\
+\ the application such as "Program Files" or "AppData" on Windows.\n\
+
+MSG_Help_no_args=Usage: jpackage <mode> <options>\n\
+\Use jpackage --help (or -h) for a list of possible options\
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,92 @@
+#
+# Copyright (c) 2017, 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.
+#
+#
+
+param.copyright.default=Copyright (C) {0,date,YYYY}
+param.description.default=None
+param.vendor.default=Unknown
+
+warning.experimental=WARNING: Using experimental tool jpackage
+message.using-default-resource=Using default package resource {0} {1} (add {2} to the resource-dir to customize).
+message.no-default-resource=no default package resource {0} {1} (add {2} to the resource-dir to customize).
+message.using-custom-resource-from-file=Using custom package resource {0} (loaded from file {1}).
+message.using-custom-resource=Using custom package resource {0} (loaded from {1}).
+message.creating-app-bundle=Creating app package: {0} in {1}
+message.app-image-dir-does-not-exist=Specified application image directory {0}: {1} does not exists
+message.app-image-dir-does-not-exist.advice=Confirm that the value for {0} exists
+message.runtime-image-dir-does-not-exist=Specified runtime image directory {0}: {1} does not exists
+message.runtime-image-dir-does-not-exist.advice=Confirm that the value for {0} exists
+message.debug-working-directory=Kept working directory for debug: {0}
+message.bundle-created=Succeeded in building {0} package
+message.module-version=Using version "{0}" from module "{1}" as application version
+message.module-class=Using class "{0}" from module "{1}" as application main class
+
+error.cannot-create-output-dir=Destination directory {0} cannot be created
+error.cannot-write-to-output-dir=Destination directory {0} is not writable
+error.root-exists=Error: Application destination directory {0} already exists
+error.no-main-class-with-main-jar=A main class was not specified nor was one found in the jar {0}
+error.no-main-class-with-main-jar.advice=Specify a main class or ensure that the jar {0} specifies one in the manifest
+error.no-main-class=A main class was not specified nor was one found in the supplied application resources
+error.no-main-class.advice=Please specify a application class or ensure that the appResources has a jar containing one in the manifest
+error.main-jar-does-not-exist=The configured main jar does not exist {0} in the input directory
+error.main-jar-does-not-exist.advice=The main jar must be specified relative to the input directory (not an absolute path), and must exist within that directory
+
+error.tool-not-found=Can not find {0}. Reason: {1}
+error.tool-not-found.advice=Please install {0}
+error.tool-old-version=Can not find {0} {1} or newer
+error.tool-old-version.advice=Please install {0} {1} or newer
+
+warning.module.does.not.exist=Module [{0}] does not exist
+warning.no.jdk.modules.found=Warning: No JDK Modules found
+
+MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package
+MSG_BundlerConfigException=Bundler {0} skipped because of a configuration problem: {1} \n\
+Advice to fix: {2}
+MSG_BundlerConfigExceptionNoAdvice=Bundler {0} skipped because of a configuration problem: {1}
+MSG_BundlerRuntimeException=Bundler {0} failed because of {1}
+MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package
+
+ERR_NoMainClass=Error: Main application class is missing
+ERR_UnsupportedOption=Error: Option [{0}] is not valid on this platform
+ERR_InvalidTypeOption=Error: Option [{0}] is not valid with package-type [{1}]
+ERR_NoInstallerEntryPoint=Error: Option [{0}] is not valid without --module or --main-jar entry point option
+
+ERR_MissingArgument=Error: Missing argument: {0}
+ERR_MissingAppResources=Error: No application jars found
+ERR_AppImageNotExist=Error: App image directory "{0}" does not exist
+ERR_NoAddLauncherName=Error: --add-launcher option requires a name and a file path (--add-launcher <name>=<file path>)
+ERR_NoUniqueName=Error: --add-launcher <name>=<file path> requires a unique name
+ERR_NoJreInstallerName=Error: Jre Installers require a name parameter
+ERR_InvalidAppName=Error: Invalid Application name: {0}
+ERR_InvalidSLName=Error: Invalid Add Launcher name: {0}
+ERR_LicenseFileNotExit=Error: Specified license file does not exist
+ERR_BuildRootInvalid=Error: temp ({0}) must be non-existant or empty directory
+ERR_InvalidOption=Error: Invalid Option: [{0}]
+ERR_InvalidInstallerType=Error: Invalid or unsupported package type: [{0}]
+ERR_BothMainJarAndModule=Error: Cannot have both --main-jar and --module Options
+ERR_NoEntryPoint=Error: creating application image requires --main-jar or --module Option
+ERR_InputNotDirectory=Error: Input directory specified is not a directory: {0}
+ERR_CannotReadInputDir=Error: No permission to read from input directory: {0}
+ERR_CannotParseOptions=Error: Processing @filename option: {0}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources_ja.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,92 @@
+#
+# Copyright (c) 2017, 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.
+#
+#
+
+param.copyright.default=Copyright (C) {0,date,YYYY}
+param.description.default=None
+param.vendor.default=Unknown
+
+warning.experimental=WARNING: Using experimental tool jpackage
+message.using-default-resource=Using default package resource {0} {1} (add {2} to the resource-dir to customize).
+message.no-default-resource=no default package resource {0} {1} (add {2} to the resource-dir to customize).
+message.using-custom-resource-from-file=Using custom package resource {0} (loaded from file {1}).
+message.using-custom-resource=Using custom package resource {0} (loaded from {1}).
+message.creating-app-bundle=Creating app package: {0} in {1}
+message.app-image-dir-does-not-exist=Specified application image directory {0}: {1} does not exists
+message.app-image-dir-does-not-exist.advice=Confirm that the value for {0} exists
+message.runtime-image-dir-does-not-exist=Specified runtime image directory {0}: {1} does not exists
+message.runtime-image-dir-does-not-exist.advice=Confirm that the value for {0} exists
+message.debug-working-directory=Kept working directory for debug: {0}
+message.bundle-created=Succeeded in building {0} package
+message.module-version=Using version "{0}" from module "{1}" as application version
+message.module-class=Using class "{0}" from module "{1}" as application main class
+
+error.cannot-create-output-dir=Destination directory {0} cannot be created
+error.cannot-write-to-output-dir=Destination directory {0} is not writable
+error.root-exists=Error: Application destination directory {0} already exists
+error.no-main-class-with-main-jar=A main class was not specified nor was one found in the jar {0}
+error.no-main-class-with-main-jar.advice=Specify a main class or ensure that the jar {0} specifies one in the manifest
+error.no-main-class=A main class was not specified nor was one found in the supplied application resources
+error.no-main-class.advice=Please specify a application class or ensure that the appResources has a jar containing one in the manifest
+error.main-jar-does-not-exist=The configured main jar does not exist {0} in the input directory
+error.main-jar-does-not-exist.advice=The main jar must be specified relative to the input directory (not an absolute path), and must exist within that directory
+
+error.tool-not-found=Can not find {0}. Reason: {1}
+error.tool-not-found.advice=Please install {0}
+error.tool-old-version=Can not find {0} {1} or newer
+error.tool-old-version.advice=Please install {0} {1} or newer
+
+warning.module.does.not.exist=Module [{0}] does not exist
+warning.no.jdk.modules.found=Warning: No JDK Modules found
+
+MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package
+MSG_BundlerConfigException=Bundler {0} skipped because of a configuration problem: {1} \n\
+Advice to fix: {2}
+MSG_BundlerConfigExceptionNoAdvice=Bundler {0} skipped because of a configuration problem: {1}
+MSG_BundlerRuntimeException=Bundler {0} failed because of {1}
+MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package
+
+ERR_NoMainClass=Error: Main application class is missing
+ERR_UnsupportedOption=Error: Option [{0}] is not valid on this platform
+ERR_InvalidTypeOption=Error: Option [{0}] is not valid with package-type [{1}]
+ERR_NoInstallerEntryPoint=Error: Option [{0}] is not valid without --module or --main-jar entry point option
+
+ERR_MissingArgument=Error: Missing argument: {0}
+ERR_MissingAppResources=Error: No application jars found
+ERR_AppImageNotExist=Error: App image directory "{0}" does not exist
+ERR_NoAddLauncherName=Error: --add-launcher option requires a name and a file path (--add-launcher <name>=<file path>)
+ERR_NoUniqueName=Error: --add-launcher <name>=<file path> requires a unique name
+ERR_NoJreInstallerName=Error: Jre Installers require a name parameter
+ERR_InvalidAppName=Error: Invalid Application name: {0}
+ERR_InvalidSLName=Error: Invalid Add Launcher name: {0}
+ERR_LicenseFileNotExit=Error: Specified license file does not exist
+ERR_BuildRootInvalid=Error: temp ({0}) must be non-existant or empty directory
+ERR_InvalidOption=Error: Invalid Option: [{0}]
+ERR_InvalidInstallerType=Error: Invalid or unsupported package type: [{0}]
+ERR_BothMainJarAndModule=Error: Cannot have both --main-jar and --module Options
+ERR_NoEntryPoint=Error: creating application image requires --main-jar or --module Option
+ERR_InputNotDirectory=Error: Input directory specified is not a directory: {0}
+ERR_CannotReadInputDir=Error: No permission to read from input directory: {0}
+ERR_CannotParseOptions=Error: Processing @filename option: {0}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/MainResources_zh_CN.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,92 @@
+#
+# Copyright (c) 2017, 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.
+#
+#
+
+param.copyright.default=Copyright (C) {0,date,YYYY}
+param.description.default=None
+param.vendor.default=Unknown
+
+warning.experimental=WARNING: Using experimental tool jpackage
+message.using-default-resource=Using default package resource {0} {1} (add {2} to the resource-dir to customize).
+message.no-default-resource=no default package resource {0} {1} (add {2} to the resource-dir to customize).
+message.using-custom-resource-from-file=Using custom package resource {0} (loaded from file {1}).
+message.using-custom-resource=Using custom package resource {0} (loaded from {1}).
+message.creating-app-bundle=Creating app package: {0} in {1}
+message.app-image-dir-does-not-exist=Specified application image directory {0}: {1} does not exists
+message.app-image-dir-does-not-exist.advice=Confirm that the value for {0} exists
+message.runtime-image-dir-does-not-exist=Specified runtime image directory {0}: {1} does not exists
+message.runtime-image-dir-does-not-exist.advice=Confirm that the value for {0} exists
+message.debug-working-directory=Kept working directory for debug: {0}
+message.bundle-created=Succeeded in building {0} package
+message.module-version=Using version "{0}" from module "{1}" as application version
+message.module-class=Using class "{0}" from module "{1}" as application main class
+
+error.cannot-create-output-dir=Destination directory {0} cannot be created
+error.cannot-write-to-output-dir=Destination directory {0} is not writable
+error.root-exists=Error: Application destination directory {0} already exists
+error.no-main-class-with-main-jar=A main class was not specified nor was one found in the jar {0}
+error.no-main-class-with-main-jar.advice=Specify a main class or ensure that the jar {0} specifies one in the manifest
+error.no-main-class=A main class was not specified nor was one found in the supplied application resources
+error.no-main-class.advice=Please specify a application class or ensure that the appResources has a jar containing one in the manifest
+error.main-jar-does-not-exist=The configured main jar does not exist {0} in the input directory
+error.main-jar-does-not-exist.advice=The main jar must be specified relative to the input directory (not an absolute path), and must exist within that directory
+
+error.tool-not-found=Can not find {0}. Reason: {1}
+error.tool-not-found.advice=Please install {0}
+error.tool-old-version=Can not find {0} {1} or newer
+error.tool-old-version.advice=Please install {0} {1} or newer
+
+warning.module.does.not.exist=Module [{0}] does not exist
+warning.no.jdk.modules.found=Warning: No JDK Modules found
+
+MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package
+MSG_BundlerConfigException=Bundler {0} skipped because of a configuration problem: {1} \n\
+Advice to fix: {2}
+MSG_BundlerConfigExceptionNoAdvice=Bundler {0} skipped because of a configuration problem: {1}
+MSG_BundlerRuntimeException=Bundler {0} failed because of {1}
+MSG_BundlerFailed=Error: Bundler "{1}" ({0}) failed to produce a package
+
+ERR_NoMainClass=Error: Main application class is missing
+ERR_UnsupportedOption=Error: Option [{0}] is not valid on this platform
+ERR_InvalidTypeOption=Error: Option [{0}] is not valid with package-type [{1}]
+ERR_NoInstallerEntryPoint=Error: Option [{0}] is not valid without --module or --main-jar entry point option
+
+ERR_MissingArgument=Error: Missing argument: {0}
+ERR_MissingAppResources=Error: No application jars found
+ERR_AppImageNotExist=Error: App image directory "{0}" does not exist
+ERR_NoAddLauncherName=Error: --add-launcher option requires a name and a file path (--add-launcher <name>=<file path>)
+ERR_NoUniqueName=Error: --add-launcher <name>=<file path> requires a unique name
+ERR_NoJreInstallerName=Error: Jre Installers require a name parameter
+ERR_InvalidAppName=Error: Invalid Application name: {0}
+ERR_InvalidSLName=Error: Invalid Add Launcher name: {0}
+ERR_LicenseFileNotExit=Error: Specified license file does not exist
+ERR_BuildRootInvalid=Error: temp ({0}) must be non-existant or empty directory
+ERR_InvalidOption=Error: Invalid Option: [{0}]
+ERR_InvalidInstallerType=Error: Invalid or unsupported package type: [{0}]
+ERR_BothMainJarAndModule=Error: Cannot have both --main-jar and --module Options
+ERR_NoEntryPoint=Error: creating application image requires --main-jar or --module Option
+ERR_InputNotDirectory=Error: Input directory specified is not a directory: {0}
+ERR_CannotReadInputDir=Error: No permission to read from input directory: {0}
+ERR_CannotParseOptions=Error: Processing @filename option: {0}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/resources/ResourceLocator.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2011, 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.resources;
+
+/*
+ * ResourceLocator
+ * This empty class is the only class in this package. Otherwise the
+ * package consists only of resources. ResourceLocator is needed in order
+ * to call getResourceAsStream() to get those resources.
+ */
+
+public class ResourceLocator {
+ public ResourceLocator() {
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/main/CommandLine.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 1999, 2018, 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.main;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.File;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This file was originally a copy of CommandLine.java in
+ * com.sun.tools.javac.main.
+ * It should track changes made to that file.
+ */
+
+/**
+ * Various utility methods for processing Java tool command line arguments.
+ *
+ * <p><b>This is NOT part of any supported API.
+ * If you write code that depends on this, you do so at your own risk.
+ * This code and its internal interfaces are subject to change or
+ * deletion without notice.</b>
+ */
+class CommandLine {
+ /**
+ * Process Win32-style command files for the specified command line
+ * arguments and return the resulting arguments. A command file argument
+ * is of the form '@file' where 'file' is the name of the file whose
+ * contents are to be parsed for additional arguments. The contents of
+ * the command file are parsed using StreamTokenizer and the original
+ * '@file' argument replaced with the resulting tokens. Recursive command
+ * files are not supported. The '@' character itself can be quoted with
+ * the sequence '@@'.
+ * @param args the arguments that may contain @files
+ * @return the arguments, with @files expanded
+ * @throws IOException if there is a problem reading any of the @files
+ */
+ public static String[] parse(String[] args) throws IOException {
+ List<String> newArgs = new ArrayList<>();
+ appendParsedCommandArgs(newArgs, Arrays.asList(args));
+ return newArgs.toArray(new String[newArgs.size()]);
+ }
+
+ private static void appendParsedCommandArgs(List<String> newArgs,
+ List<String> args) throws IOException {
+ for (String arg : args) {
+ if (arg.length() > 1 && arg.charAt(0) == '@') {
+ arg = arg.substring(1);
+ if (arg.charAt(0) == '@') {
+ newArgs.add(arg);
+ } else {
+ loadCmdFile(arg, newArgs);
+ }
+ } else {
+ newArgs.add(arg);
+ }
+ }
+ }
+
+ private static void loadCmdFile(String name, List<String> args)
+ throws IOException {
+ if (!Files.isReadable(Path.of(name))) {
+ throw new FileNotFoundException(name);
+ }
+ try (Reader r = Files.newBufferedReader(Paths.get(name),
+ Charset.defaultCharset())) {
+ Tokenizer t = new Tokenizer(r);
+ String s;
+ while ((s = t.nextToken()) != null) {
+ args.add(s);
+ }
+ }
+ }
+
+ public static class Tokenizer {
+ private final Reader in;
+ private int ch;
+
+ public Tokenizer(Reader in) throws IOException {
+ this.in = in;
+ ch = in.read();
+ }
+
+ public String nextToken() throws IOException {
+ skipWhite();
+ if (ch == -1) {
+ return null;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ char quoteChar = 0;
+
+ while (ch != -1) {
+ switch (ch) {
+ case ' ':
+ case '\t':
+ case '\f':
+ if (quoteChar == 0) {
+ return sb.toString();
+ }
+ sb.append((char) ch);
+ break;
+
+ case '\n':
+ case '\r':
+ return sb.toString();
+
+ case '\'':
+ case '"':
+ if (quoteChar == 0) {
+ quoteChar = (char) ch;
+ } else if (quoteChar == ch) {
+ quoteChar = 0;
+ } else {
+ sb.append((char) ch);
+ }
+ break;
+
+ case '\\':
+ if (quoteChar != 0) {
+ ch = in.read();
+ switch (ch) {
+ case '\n':
+ case '\r':
+ while (ch == ' ' || ch == '\n'
+ || ch == '\r' || ch == '\t'
+ || ch == '\f') {
+ ch = in.read();
+ }
+ continue;
+
+ case 'n':
+ ch = '\n';
+ break;
+ case 'r':
+ ch = '\r';
+ break;
+ case 't':
+ ch = '\t';
+ break;
+ case 'f':
+ ch = '\f';
+ break;
+ }
+ }
+ sb.append((char) ch);
+ break;
+
+ default:
+ sb.append((char) ch);
+ }
+
+ ch = in.read();
+ }
+
+ return sb.toString();
+ }
+
+ void skipWhite() throws IOException {
+ while (ch != -1) {
+ switch (ch) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ case '\f':
+ break;
+
+ case '#':
+ ch = in.read();
+ while (ch != '\n' && ch != '\r' && ch != -1) {
+ ch = in.read();
+ }
+ break;
+
+ default:
+ return;
+ }
+
+ ch = in.read();
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/jdk/jpackage/main/Main.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2011, 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
+ * 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.main;
+
+import jdk.jpackage.internal.Arguments;
+import jdk.jpackage.internal.Log;
+import jdk.jpackage.internal.CLIHelp;
+import java.io.PrintWriter;
+import java.util.ResourceBundle;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.text.MessageFormat;
+
+public class Main {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.MainResources");
+
+ /**
+ * main(String... args)
+ * This is the entry point for the jpackage tool.
+ *
+ * @param args command line arguments
+ */
+ public static void main(String... args) throws Exception {
+ // Create logger with default system.out and system.err
+ Log.setLogger(null);
+
+ int status = new jdk.jpackage.main.Main().execute(args);
+ System.exit(status);
+ }
+
+ /**
+ * execute() - this is the entry point for the ToolProvider API.
+ *
+ * @param out output stream
+ * @param err error output stream
+ * @param args command line arguments
+ * @return an exit code. 0 means success, non-zero means an error occurred.
+ */
+ public int execute(PrintWriter out, PrintWriter err, String... args) {
+ // Create logger with provided streams
+ Log.Logger logger = new Log.Logger();
+ logger.setPrintWriter(out, err);
+ Log.setLogger(logger);
+
+ return execute(args);
+ }
+
+ private int execute(String... args) {
+ Log.info(I18N.getString("warning.experimental"));
+ try {
+ String[] newArgs;
+ try {
+ newArgs = CommandLine.parse(args);
+ } catch (FileNotFoundException fnfe) {
+ Log.error(MessageFormat.format(I18N.getString(
+ "ERR_CannotParseOptions"), fnfe.getMessage()));
+ return 1;
+ } catch (IOException ioe) {
+ Log.error(ioe.getMessage());
+ return 1;
+ }
+
+ if (newArgs.length == 0) {
+ CLIHelp.showHelp(true);
+ } else if (hasHelp(newArgs)){
+ if (hasVersion(newArgs)) {
+ Log.info(System.getProperty("java.version") + "\n");
+ }
+ CLIHelp.showHelp(false);
+ } else if (hasVersion(newArgs)) {
+ Log.info(System.getProperty("java.version"));
+ } else {
+ Arguments arguments = new Arguments(newArgs);
+ if (!arguments.processArguments()) {
+ // processArguments() will log error message if failed.
+ return 1;
+ }
+ }
+ return 0;
+ } finally {
+ Log.flush();
+ }
+ }
+
+ private boolean hasHelp(String[] args) {
+ for (String a : args) {
+ if ("--help".equals(a) || "-h".equals(a)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean hasVersion(String[] args) {
+ for (String a : args) {
+ if ("--version".equals(a)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/classes/module-info.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018, 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.
+ */
+
+/**
+ * Defines the Java Packaging tool, jpackage.
+ *
+ * <p>jpackage is a tool for generating self-contained application bundles.
+ *
+ * <p> This module provides the equivalent of command-line access to <em>jpackage</em>
+ * via the {@link java.util.spi.ToolProvider ToolProvider} SPI.
+ * Instances of the tool can be obtained by calling
+ * {@link java.util.spi.ToolProvider#findFirst ToolProvider.findFirst}
+ * or the {@link java.util.ServiceLoader service loader} with the name
+ * {@code "jpackage"}.
+ *
+ * @implNote The {@code jpackage} tool is not thread-safe. An application
+ * should not call either of the
+ * {@link java.util.spi.ToolProvider ToolProvider} {@code run} methods
+ * concurrently, even with separate {@code "jpackage"} {@code ToolProvider}
+ * instances, or undefined behavior may result.
+ * <p></p>
+ *
+ * @moduleGraph
+ * @since 14
+ */
+
+module jdk.jpackage {
+ requires jdk.jlink;
+
+ requires java.desktop;
+
+ uses jdk.jpackage.internal.Bundler;
+ uses jdk.jpackage.internal.Bundlers;
+
+ provides jdk.jpackage.internal.Bundlers with
+ jdk.jpackage.internal.BasicBundlers;
+
+ provides java.util.spi.ToolProvider
+ with jdk.jpackage.internal.JPackageToolProvider;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/FileAttributes.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef FILEATTRIBUTES_H
+#define FILEATTRIBUTES_H
+
+#include "Platform.h"
+#include "PlatformString.h"
+#include "FileAttribute.h"
+
+#include <vector>
+
+class FileAttributes {
+private:
+ TString FFileName;
+ bool FFollowLink;
+ std::vector<FileAttribute> FAttributes;
+
+ bool WriteAttributes();
+ bool ReadAttributes();
+ bool Valid(const FileAttribute Value);
+
+public:
+ FileAttributes(const TString FileName, bool FollowLink = true);
+
+ void Append(const FileAttribute Value);
+ bool Contains(const FileAttribute Value);
+ void Remove(const FileAttribute Value);
+};
+
+#endif // FILEATTRIBUTES_H
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/FilePath.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef FILEPATH_H
+#define FILEPATH_H
+
+#include "Platform.h"
+#include "PlatformString.h"
+#include "FileAttribute.h"
+
+#include <vector>
+
+class FileAttributes {
+private:
+ TString FFileName;
+ bool FFollowLink;
+ std::vector<FileAttribute> FAttributes;
+
+ bool WriteAttributes();
+ bool ReadAttributes();
+ bool Valid(const FileAttribute Value);
+
+public:
+ FileAttributes(const TString FileName, bool FollowLink = true);
+
+ void Append(const FileAttribute Value);
+ bool Contains(const FileAttribute Value);
+ void Remove(const FileAttribute Value);
+};
+
+class FilePath {
+private:
+ FilePath(void) {}
+ ~FilePath(void) {}
+
+public:
+ static bool FileExists(const TString FileName);
+ static bool DirectoryExists(const TString DirectoryName);
+
+ static bool DeleteFile(const TString FileName);
+ static bool DeleteDirectory(const TString DirectoryName);
+
+ static TString ExtractFilePath(TString Path);
+ static TString ExtractFileExt(TString Path);
+ static TString ExtractFileName(TString Path);
+ static TString ChangeFileExt(TString Path, TString Extension);
+
+ static TString IncludeTrailingSeparator(const TString value);
+ static TString IncludeTrailingSeparator(const char* value);
+ static TString IncludeTrailingSeparator(const wchar_t* value);
+ static TString FixPathForPlatform(TString Path);
+ static TString FixPathSeparatorForPlatform(TString Path);
+ static TString PathSeparator();
+
+ static bool CreateDirectory(TString Path, bool ownerOnly);
+ static void ChangePermissions(TString FileName, bool ownerOnly);
+};
+
+#endif //FILEPATH_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/Helpers.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "Helpers.h"
+#include "PlatformString.h"
+#include "PropertyFile.h"
+
+
+bool Helpers::SplitOptionIntoNameValue(
+ TString option, TString& Name, TString& Value) {
+ bool hasValue = false;
+ Name = _T("");
+ Value = _T("");
+ unsigned int index = 0;
+
+ for (; index < option.length(); index++) {
+ TCHAR c = option[index];
+
+ switch (c) {
+ case '=': {
+ index++;
+ hasValue = true;
+ break;
+ }
+
+ case '\\': {
+ if (index + 1 < option.length()) {
+ c = option[index + 1];
+
+ switch (c) {
+ case '\\': {
+ index++;
+ Name += '\\';
+ break;
+ }
+
+ case '=': {
+ index++;
+ Name += '=';
+ break;
+ }
+ }
+
+ }
+
+ continue;
+ }
+
+ default: {
+ Name += c;
+ continue;
+ }
+ }
+
+ break;
+ }
+
+ if (hasValue) {
+ Value = option.substr(index, index - option.length());
+ }
+
+ return (option.length() > 0);
+}
+
+
+TString Helpers::ReplaceString(TString subject, const TString& search,
+ const TString& replace) {
+ size_t pos = 0;
+ while((pos = subject.find(search, pos)) != TString::npos) {
+ subject.replace(pos, search.length(), replace);
+ pos += replace.length();
+ }
+ return subject;
+}
+
+TString Helpers::ConvertIdToFilePath(TString Value) {
+ TString search;
+ search = '.';
+ TString replace;
+ replace = '/';
+ TString result = ReplaceString(Value, search, replace);
+ return result;
+}
+
+TString Helpers::ConvertIdToJavaPath(TString Value) {
+ TString search;
+ search = '.';
+ TString replace;
+ replace = '/';
+ TString result = ReplaceString(Value, search, replace);
+ search = '\\';
+ result = ReplaceString(result, search, replace);
+ return result;
+}
+
+TString Helpers::ConvertJavaPathToId(TString Value) {
+ TString search;
+ search = '/';
+ TString replace;
+ replace = '.';
+ TString result = ReplaceString(Value, search, replace);
+ return result;
+}
+
+OrderedMap<TString, TString>
+ Helpers::GetJavaOptionsFromConfig(IPropertyContainer* config) {
+ OrderedMap<TString, TString> result;
+
+ for (unsigned int index = 0; index < config->GetCount(); index++) {
+ TString argname =
+ TString(_T("jvmarg.")) + PlatformString(index + 1).toString();
+ TString argvalue;
+
+ if (config->GetValue(argname, argvalue) == false) {
+ break;
+ }
+ else if (argvalue.empty() == false) {
+ TString name;
+ TString value;
+ if (Helpers::SplitOptionIntoNameValue(argvalue, name, value)) {
+ result.Append(name, value);
+ }
+ }
+ }
+
+ return result;
+}
+
+std::list<TString> Helpers::GetArgsFromConfig(IPropertyContainer* config) {
+ std::list<TString> result;
+
+ for (unsigned int index = 0; index < config->GetCount(); index++) {
+ TString argname = TString(_T("arg."))
+ + PlatformString(index + 1).toString();
+ TString argvalue;
+
+ if (config->GetValue(argname, argvalue) == false) {
+ break;
+ }
+ else if (argvalue.empty() == false) {
+ result.push_back((argvalue));
+ }
+ }
+
+ return result;
+}
+
+std::list<TString>
+ Helpers::MapToNameValueList(OrderedMap<TString, TString> Map) {
+ std::list<TString> result;
+ std::vector<TString> keys = Map.GetKeys();
+
+ for (OrderedMap<TString, TString>::const_iterator iterator = Map.begin();
+ iterator != Map.end(); iterator++) {
+ JPPair<TString, TString> *item = *iterator;
+ TString key = item->first;
+ TString value = item->second;
+
+ if (value.length() == 0) {
+ result.push_back(key);
+ } else {
+ result.push_back(key + _T('=') + value);
+ }
+ }
+
+ return result;
+}
+
+TString Helpers::NameValueToString(TString name, TString value) {
+ TString result;
+
+ if (value.empty() == true) {
+ result = name;
+ }
+ else {
+ result = name + TString(_T("=")) + value;
+ }
+
+ return result;
+}
+
+std::list<TString> Helpers::StringToArray(TString Value) {
+ std::list<TString> result;
+ TString line;
+
+ for (unsigned int index = 0; index < Value.length(); index++) {
+ TCHAR c = Value[index];
+
+ switch (c) {
+ case '\n': {
+ result.push_back(line);
+ line = _T("");
+ break;
+ }
+
+ case '\r': {
+ result.push_back(line);
+ line = _T("");
+
+ if (Value[index + 1] == '\n')
+ index++;
+
+ break;
+ }
+
+ default: {
+ line += c;
+ }
+ }
+ }
+
+ // The buffer may not have ended with a Carriage Return/Line Feed.
+ if (line.length() > 0) {
+ result.push_back(line);
+ }
+
+ return result;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/Helpers.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef HELPERS_H
+#define HELPERS_H
+
+#include "Platform.h"
+#include "OrderedMap.h"
+#include "IniFile.h"
+
+
+class Helpers {
+private:
+ Helpers(void) {}
+ ~Helpers(void) {}
+
+public:
+ // Supports two formats for option:
+ // Example 1:
+ // foo=bar
+ //
+ // Example 2:
+ // <name=foo=, value=goo>
+ static bool SplitOptionIntoNameValue(TString option,
+ TString& Name, TString& Value);
+ static TString ReplaceString(TString subject, const TString& search,
+ const TString& replace);
+ static TString ConvertIdToFilePath(TString Value);
+ static TString ConvertIdToJavaPath(TString Value);
+ static TString ConvertJavaPathToId(TString Value);
+
+ static OrderedMap<TString, TString>
+ GetJavaOptionsFromConfig(IPropertyContainer* config);
+ static std::list<TString> GetArgsFromConfig(IPropertyContainer* config);
+
+ static std::list<TString>
+ MapToNameValueList(OrderedMap<TString, TString> Map);
+
+ static TString NameValueToString(TString name, TString value);
+
+ static std::list<TString> StringToArray(TString Value);
+};
+
+#endif // HELPERS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/IniFile.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+#include "IniFile.h"
+#include "Helpers.h"
+
+#include <string>
+
+
+IniFile::IniFile() : ISectionalPropertyContainer() {
+}
+
+IniFile::~IniFile() {
+ for (OrderedMap<TString, IniSectionData*>::iterator iterator =
+ FMap.begin(); iterator != FMap.end(); iterator++) {
+ JPPair<TString, IniSectionData*> *item = *iterator;
+ delete item->second;
+ }
+}
+
+bool IniFile::LoadFromFile(const TString FileName) {
+ bool result = false;
+ Platform& platform = Platform::GetInstance();
+
+ std::list<TString> contents = platform.LoadFromFile(FileName);
+
+ if (contents.empty() == false) {
+ bool found = false;
+
+ // Determine the if file is an INI file or property file.
+ // Assign FDefaultSection if it is
+ // an INI file. Otherwise FDefaultSection is NULL.
+ for (std::list<TString>::const_iterator iterator = contents.begin();
+ iterator != contents.end(); iterator++) {
+ TString line = *iterator;
+
+ if (line[0] == ';') {
+ // Semicolon is a comment so ignore the line.
+ continue;
+ }
+ else {
+ if (line[0] == '[') {
+ found = true;
+ }
+
+ break;
+ }
+ }
+
+ if (found == true) {
+ TString sectionName;
+
+ for (std::list<TString>::const_iterator iterator = contents.begin();
+ iterator != contents.end(); iterator++) {
+ TString line = *iterator;
+
+ if (line[0] == ';') {
+ // Semicolon is a comment so ignore the line.
+ continue;
+ }
+ else if (line[0] == '[' && line[line.length() - 1] == ']') {
+ sectionName = line.substr(1, line.size() - 2);
+ }
+ else if (sectionName.empty() == false) {
+ TString name;
+ TString value;
+
+ if (Helpers::SplitOptionIntoNameValue(
+ line, name, value) == true) {
+ Append(sectionName, name, value);
+ }
+ }
+ }
+
+ result = true;
+ }
+ }
+
+ return result;
+}
+
+bool IniFile::SaveToFile(const TString FileName, bool ownerOnly) {
+ bool result = false;
+
+ std::list<TString> contents;
+ std::vector<TString> keys = FMap.GetKeys();
+
+ for (unsigned int index = 0; index < keys.size(); index++) {
+ TString name = keys[index];
+ IniSectionData *section;
+
+ if (FMap.GetValue(name, section) == true) {
+ contents.push_back(_T("[") + name + _T("]"));
+ std::list<TString> lines = section->GetLines();
+ contents.insert(contents.end(), lines.begin(), lines.end());
+ contents.push_back(_T(""));
+ }
+ }
+
+ Platform& platform = Platform::GetInstance();
+ platform.SaveToFile(FileName, contents, ownerOnly);
+ result = true;
+ return result;
+}
+
+void IniFile::Append(const TString SectionName,
+ const TString Key, TString Value) {
+ if (FMap.ContainsKey(SectionName) == true) {
+ IniSectionData* section;
+
+ if (FMap.GetValue(SectionName, section) == true && section != NULL) {
+ section->SetValue(Key, Value);
+ }
+ }
+ else {
+ IniSectionData *section = new IniSectionData();
+ section->SetValue(Key, Value);
+ FMap.Append(SectionName, section);
+ }
+}
+
+void IniFile::AppendSection(const TString SectionName,
+ OrderedMap<TString, TString> Values) {
+ if (FMap.ContainsKey(SectionName) == true) {
+ IniSectionData* section;
+
+ if (FMap.GetValue(SectionName, section) == true && section != NULL) {
+ section->Append(Values);
+ }
+ }
+ else {
+ IniSectionData *section = new IniSectionData(Values);
+ FMap.Append(SectionName, section);
+ }
+}
+
+bool IniFile::GetValue(const TString SectionName,
+ const TString Key, TString& Value) {
+ bool result = false;
+ IniSectionData* section;
+
+ if (FMap.GetValue(SectionName, section) == true && section != NULL) {
+ result = section->GetValue(Key, Value);
+ }
+
+ return result;
+}
+
+bool IniFile::SetValue(const TString SectionName,
+ const TString Key, TString Value) {
+ bool result = false;
+ IniSectionData* section;
+
+ if (FMap.GetValue(SectionName, section) && section != NULL) {
+ result = section->SetValue(Key, Value);
+ }
+ else {
+ Append(SectionName, Key, Value);
+ }
+
+
+ return result;
+}
+
+bool IniFile::GetSection(const TString SectionName,
+ OrderedMap<TString, TString> &Data) {
+ bool result = false;
+
+ if (FMap.ContainsKey(SectionName) == true) {
+ IniSectionData* section = NULL;
+
+ if (FMap.GetValue(SectionName, section) == true && section != NULL) {
+ OrderedMap<TString, TString> data = section->GetData();
+ Data.Append(data);
+ result = true;
+ }
+ }
+
+ return result;
+}
+
+bool IniFile::ContainsSection(const TString SectionName) {
+ return FMap.ContainsKey(SectionName);
+}
+
+//----------------------------------------------------------------------------
+
+IniSectionData::IniSectionData() {
+ FMap.SetAllowDuplicates(true);
+}
+
+IniSectionData::IniSectionData(OrderedMap<TString, TString> Values) {
+ FMap = Values;
+}
+
+std::vector<TString> IniSectionData::GetKeys() {
+ return FMap.GetKeys();
+}
+
+std::list<TString> IniSectionData::GetLines() {
+ std::list<TString> result;
+ std::vector<TString> keys = FMap.GetKeys();
+
+ for (unsigned int index = 0; index < keys.size(); index++) {
+ TString name = keys[index];
+ TString value;
+
+ if (FMap.GetValue(name, value) == true) {
+ name = Helpers::ReplaceString(name, _T("="), _T("\\="));
+ value = Helpers::ReplaceString(value, _T("="), _T("\\="));
+
+ TString line = name + _T('=') + value;
+ result.push_back(line);
+ }
+ }
+
+ return result;
+}
+
+OrderedMap<TString, TString> IniSectionData::GetData() {
+ OrderedMap<TString, TString> result = FMap;
+ return result;
+}
+
+bool IniSectionData::GetValue(const TString Key, TString& Value) {
+ return FMap.GetValue(Key, Value);
+}
+
+bool IniSectionData::SetValue(const TString Key, TString Value) {
+ return FMap.SetValue(Key, Value);
+}
+
+void IniSectionData::Append(OrderedMap<TString, TString> Values) {
+ FMap.Append(Values);
+}
+
+size_t IniSectionData::GetCount() {
+ return FMap.Count();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/IniFile.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+#ifndef INIFILE_H
+#define INIFILE_H
+
+#include "Platform.h"
+#include "OrderedMap.h"
+
+#include <map>
+
+
+class IniSectionData : public IPropertyContainer {
+private:
+ OrderedMap<TString, TString> FMap;
+
+public:
+ IniSectionData();
+ IniSectionData(OrderedMap<TString, TString> Values);
+
+ std::vector<TString> GetKeys();
+ std::list<TString> GetLines();
+ OrderedMap<TString, TString> GetData();
+
+ bool SetValue(const TString Key, TString Value);
+ void Append(OrderedMap<TString, TString> Values);
+
+ virtual bool GetValue(const TString Key, TString& Value);
+ virtual size_t GetCount();
+};
+
+
+class IniFile : public ISectionalPropertyContainer {
+private:
+ OrderedMap<TString, IniSectionData*> FMap;
+
+public:
+ IniFile();
+ virtual ~IniFile();
+
+ void internalTest();
+
+ bool LoadFromFile(const TString FileName);
+ bool SaveToFile(const TString FileName, bool ownerOnly = true);
+
+ void Append(const TString SectionName, const TString Key, TString Value);
+ void AppendSection(const TString SectionName,
+ OrderedMap<TString, TString> Values);
+ bool SetValue(const TString SectionName,
+ const TString Key, TString Value);
+
+ // ISectionalPropertyContainer
+ virtual bool GetSection(const TString SectionName,
+ OrderedMap<TString, TString> &Data);
+ virtual bool ContainsSection(const TString SectionName);
+ virtual bool GetValue(const TString SectionName,
+ const TString Key, TString& Value);
+};
+
+#endif // INIFILE_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/JavaVirtualMachine.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "JavaVirtualMachine.h"
+#include "Platform.h"
+#include "PlatformString.h"
+#include "FilePath.h"
+#include "Package.h"
+#include "Helpers.h"
+#include "Messages.h"
+#include "Macros.h"
+
+#include "jni.h"
+
+#include <map>
+#include <list>
+#include <sstream>
+
+
+bool RunVM() {
+ JavaVirtualMachine javavm;
+
+ bool result = javavm.StartJVM();
+
+ if (!result) {
+ Platform& platform = Platform::GetInstance();
+ platform.ShowMessage(_T("Failed to launch JVM\n"));
+ }
+
+ return result;
+}
+
+//----------------------------------------------------------------------------
+
+JavaOptions::JavaOptions(): FOptions(NULL) {
+}
+
+JavaOptions::~JavaOptions() {
+ if (FOptions != NULL) {
+ for (unsigned int index = 0; index < GetCount(); index++) {
+ delete[] FOptions[index].optionString;
+ }
+
+ delete[] FOptions;
+ }
+}
+
+void JavaOptions::AppendValue(const TString Key, TString Value, void* Extra) {
+ JavaOptionItem item;
+ item.name = Key;
+ item.value = Value;
+ item.extraInfo = Extra;
+ FItems.push_back(item);
+}
+
+void JavaOptions::AppendValue(const TString Key, TString Value) {
+ AppendValue(Key, Value, NULL);
+}
+
+void JavaOptions::AppendValue(const TString Key) {
+ AppendValue(Key, _T(""), NULL);
+}
+
+void JavaOptions::AppendValues(OrderedMap<TString, TString> Values) {
+ if (Values.GetAllowDuplicates()) {
+ for (int i = 0; i < (int)Values.Count(); i++) {
+ TString name, value;
+
+ bool bResult = Values.GetKey(i, name);
+ bResult &= Values.GetValue(i, value);
+
+ if (bResult) {
+ AppendValue(name, value);
+ }
+ }
+ } else { // In case we asked to add values from OrderedMap with allow
+ // duplicates set to false. Not used now, but should avoid possible
+ // bugs.
+ std::vector<TString> orderedKeys = Values.GetKeys();
+
+ for (std::vector<TString>::const_iterator iterator = orderedKeys.begin();
+ iterator != orderedKeys.end(); iterator++) {
+ TString name = *iterator;
+ TString value;
+
+ if (Values.GetValue(name, value) == true) {
+ AppendValue(name, value);
+ }
+ }
+ }
+}
+
+void JavaOptions::ReplaceValue(const TString Key, TString Value) {
+ for (std::list<JavaOptionItem>::iterator iterator = FItems.begin();
+ iterator != FItems.end(); iterator++) {
+
+ TString lkey = iterator->name;
+
+ if (lkey == Key) {
+ JavaOptionItem item = *iterator;
+ item.value = Value;
+ iterator = FItems.erase(iterator);
+ FItems.insert(iterator, item);
+ break;
+ }
+ }
+}
+
+std::list<TString> JavaOptions::ToList() {
+ std::list<TString> result;
+ Macros& macros = Macros::GetInstance();
+
+ for (std::list<JavaOptionItem>::const_iterator iterator = FItems.begin();
+ iterator != FItems.end(); iterator++) {
+ TString key = iterator->name;
+ TString value = iterator->value;
+ TString option = Helpers::NameValueToString(key, value);
+ option = macros.ExpandMacros(option);
+ result.push_back(option);
+ }
+
+ return result;
+}
+
+size_t JavaOptions::GetCount() {
+ return FItems.size();
+}
+
+//----------------------------------------------------------------------------
+
+JavaVirtualMachine::JavaVirtualMachine() {
+}
+
+JavaVirtualMachine::~JavaVirtualMachine(void) {
+}
+
+bool JavaVirtualMachine::StartJVM() {
+ Platform& platform = Platform::GetInstance();
+ Package& package = Package::GetInstance();
+
+ TString classpath = package.GetClassPath();
+ TString modulepath = package.GetModulePath();
+ JavaOptions options;
+
+ if (modulepath.empty() == false) {
+ options.AppendValue(_T("-Djava.module.path"), modulepath);
+ }
+
+ options.AppendValue(_T("-Djava.library.path"),
+ package.GetPackageAppDirectory() + FilePath::PathSeparator()
+ + package.GetPackageLauncherDirectory());
+ options.AppendValue(
+ _T("-Djava.launcher.path"), package.GetPackageLauncherDirectory());
+ options.AppendValues(package.GetJavaOptions());
+
+#ifdef DEBUG
+ if (package.Debugging() == dsJava) {
+ options.AppendValue(_T("-Xdebug"), _T(""));
+ options.AppendValue(
+ _T("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=localhost:5005"),
+ _T(""));
+ platform.ShowMessage(_T("localhost:5005"));
+ }
+#endif // DEBUG
+
+ TString maxHeapSizeOption;
+ TString minHeapSizeOption;
+
+
+ if (package.GetMemoryState() == PackageBootFields::msAuto) {
+ TPlatformNumber memorySize = package.GetMemorySize();
+ TString memory =
+ PlatformString((size_t)memorySize).toString() + _T("m");
+ maxHeapSizeOption = TString(_T("-Xmx")) + memory;
+ options.AppendValue(maxHeapSizeOption, _T(""));
+
+ if (memorySize > 256)
+ minHeapSizeOption = _T("-Xms256m");
+ else
+ minHeapSizeOption = _T("-Xms") + memory;
+
+ options.AppendValue(minHeapSizeOption, _T(""));
+ }
+
+ TString mainClassName = package.GetMainClassName();
+ TString mainModule = package.GetMainModule();
+
+ if (mainClassName.empty() == true && mainModule.empty() == true) {
+ Messages& messages = Messages::GetInstance();
+ platform.ShowMessage(messages.GetMessage(NO_MAIN_CLASS_SPECIFIED));
+ return false;
+ }
+
+ configureLibrary();
+
+ // Initialize the arguments to JLI_Launch()
+ //
+ // On Mac OS X JLI_Launch spawns a new thread that actually starts the JVM.
+ // This new thread simply re-runs main(argc, argv). Therefore we do not
+ // want to add new args if we are still in the original main thread so we
+ // will treat them as command line args provided by the user ...
+ // Only propagate original set of args first time.
+
+ options.AppendValue(_T("-classpath"));
+ options.AppendValue(classpath);
+
+ std::list<TString> vmargs;
+ vmargs.push_back(package.GetCommandName());
+
+ if (package.HasSplashScreen() == true) {
+ options.AppendValue(TString(_T("-splash:"))
+ + package.GetSplashScreenFileName(), _T(""));
+ }
+
+ if (mainModule.empty() == true) {
+ options.AppendValue(Helpers::ConvertJavaPathToId(mainClassName),
+ _T(""));
+ } else {
+ options.AppendValue(_T("-m"));
+ options.AppendValue(mainModule);
+ }
+
+ return launchVM(options, vmargs);
+}
+
+void JavaVirtualMachine::configureLibrary() {
+ Platform& platform = Platform::GetInstance();
+ Package& package = Package::GetInstance();
+ TString libName = package.GetJavaLibraryFileName();
+ platform.addPlatformDependencies(&javaLibrary);
+ javaLibrary.Load(libName);
+}
+
+bool JavaVirtualMachine::launchVM(JavaOptions& options,
+ std::list<TString>& vmargs) {
+ Platform& platform = Platform::GetInstance();
+ Package& package = Package::GetInstance();
+
+#ifdef MAC
+ // Mac adds a ProcessSerialNumber to args when launched from .app
+ // filter out the psn since they it's not expected in the app
+ if (platform.IsMainThread() == false) {
+ std::list<TString> loptions = options.ToList();
+ vmargs.splice(vmargs.end(), loptions,
+ loptions.begin(), loptions.end());
+ }
+#else
+ std::list<TString> loptions = options.ToList();
+ vmargs.splice(vmargs.end(), loptions, loptions.begin(), loptions.end());
+#endif
+
+ std::list<TString> largs = package.GetArgs();
+ vmargs.splice(vmargs.end(), largs, largs.begin(), largs.end());
+
+ size_t argc = vmargs.size();
+ DynamicBuffer<char*> argv(argc + 1);
+ if (argv.GetData() == NULL) {
+ return false;
+ }
+
+ unsigned int index = 0;
+ for (std::list<TString>::const_iterator iterator = vmargs.begin();
+ iterator != vmargs.end(); iterator++) {
+ TString item = *iterator;
+ std::string arg = PlatformString(item).toStdString();
+#ifdef DEBUG
+ printf("%i %s\n", index, arg.c_str());
+#endif // DEBUG
+ argv[index] = PlatformString::duplicate(arg.c_str());
+ index++;
+ }
+
+ argv[argc] = NULL;
+
+// On Mac we can only free the boot fields if the calling thread is
+// not the main thread.
+#ifdef MAC
+ if (platform.IsMainThread() == false) {
+ package.FreeBootFields();
+ }
+#else
+ package.FreeBootFields();
+#endif // MAC
+
+ if (javaLibrary.JavaVMCreate(argc, argv.GetData()) == true) {
+ return true;
+ }
+
+ for (index = 0; index < argc; index++) {
+ if (argv[index] != NULL) {
+ delete[] argv[index];
+ }
+ }
+
+ return false;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/JavaVirtualMachine.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef JAVAVIRTUALMACHINE_H
+#define JAVAVIRTUALMACHINE_H
+
+
+#include "jni.h"
+#include "Platform.h"
+#include "Library.h"
+
+struct JavaOptionItem {
+ TString name;
+ TString value;
+ void* extraInfo;
+};
+
+class JavaOptions {
+private:
+ std::list<JavaOptionItem> FItems;
+ JavaVMOption* FOptions;
+
+public:
+ JavaOptions();
+ ~JavaOptions();
+
+ void AppendValue(const TString Key, TString Value, void* Extra);
+ void AppendValue(const TString Key, TString Value);
+ void AppendValue(const TString Key);
+ void AppendValues(OrderedMap<TString, TString> Values);
+ void ReplaceValue(const TString Key, TString Value);
+ std::list<TString> ToList();
+ size_t GetCount();
+};
+
+class JavaVirtualMachine {
+private:
+ JavaLibrary javaLibrary;
+
+ void configureLibrary();
+ bool launchVM(JavaOptions& options, std::list<TString>& vmargs);
+public:
+ JavaVirtualMachine();
+ ~JavaVirtualMachine(void);
+
+ bool StartJVM();
+};
+
+bool RunVM();
+
+#endif // JAVAVIRTUALMACHINE_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/Library.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "Library.h"
+#include "Platform.h"
+#include "Messages.h"
+#include "PlatformString.h"
+
+#include <fstream>
+#include <locale>
+
+Library::Library() {
+ Initialize();
+}
+
+Library::Library(const TString &FileName) {
+ Initialize();
+ Load(FileName);
+}
+
+Library::~Library() {
+ Unload();
+}
+
+void Library::Initialize() {
+ FModule = NULL;
+ FDependentLibraryNames = NULL;
+ FDependenciesLibraries = NULL;
+}
+
+void Library::InitializeDependencies() {
+ if (FDependentLibraryNames == NULL) {
+ FDependentLibraryNames = new std::vector<TString>();
+ }
+
+ if (FDependenciesLibraries == NULL) {
+ FDependenciesLibraries = new std::vector<Library*>();
+ }
+}
+
+void Library::LoadDependencies() {
+ if (FDependentLibraryNames != NULL && FDependenciesLibraries != NULL) {
+ for (std::vector<TString>::const_iterator iterator =
+ FDependentLibraryNames->begin();
+ iterator != FDependentLibraryNames->end(); iterator++) {
+ Library* library = new Library();
+
+ if (library->Load(*iterator) == true) {
+ FDependenciesLibraries->push_back(library);
+ }
+ }
+
+ delete FDependentLibraryNames;
+ FDependentLibraryNames = NULL;
+ }
+}
+
+void Library::UnloadDependencies() {
+ if (FDependenciesLibraries != NULL) {
+ for (std::vector<Library*>::const_iterator iterator =
+ FDependenciesLibraries->begin();
+ iterator != FDependenciesLibraries->end(); iterator++) {
+ Library* library = *iterator;
+
+ if (library != NULL) {
+ library->Unload();
+ delete library;
+ }
+ }
+
+ delete FDependenciesLibraries;
+ FDependenciesLibraries = NULL;
+ }
+}
+
+Procedure Library::GetProcAddress(const std::string& MethodName) const {
+ Platform& platform = Platform::GetInstance();
+ return platform.GetProcAddress(FModule, MethodName);
+}
+
+bool Library::Load(const TString &FileName) {
+ bool result = true;
+
+ if (FModule == NULL) {
+ LoadDependencies();
+ Platform& platform = Platform::GetInstance();
+ FModule = platform.LoadLibrary(FileName);
+
+ if (FModule == NULL) {
+ Messages& messages = Messages::GetInstance();
+ platform.ShowMessage(messages.GetMessage(LIBRARY_NOT_FOUND),
+ FileName);
+ result = false;
+ } else {
+ fname = PlatformString(FileName).toStdString();
+ }
+ }
+
+ return result;
+}
+
+bool Library::Unload() {
+ bool result = false;
+
+ if (FModule != NULL) {
+ Platform& platform = Platform::GetInstance();
+ platform.FreeLibrary(FModule);
+ FModule = NULL;
+ UnloadDependencies();
+ result = true;
+ }
+
+ return result;
+}
+
+void Library::AddDependency(const TString &FileName) {
+ InitializeDependencies();
+
+ if (FDependentLibraryNames != NULL) {
+ FDependentLibraryNames->push_back(FileName);
+ }
+}
+
+void Library::AddDependencies(const std::vector<TString> &Dependencies) {
+ if (Dependencies.size() > 0) {
+ InitializeDependencies();
+
+ if (FDependentLibraryNames != NULL) {
+ for (std::vector<TString>::const_iterator iterator =
+ FDependentLibraryNames->begin();
+ iterator != FDependentLibraryNames->end(); iterator++) {
+ TString fileName = *iterator;
+ AddDependency(fileName);
+ }
+ }
+ }
+}
+
+JavaLibrary::JavaLibrary() : Library(), FCreateProc(NULL) {
+}
+
+bool JavaLibrary::JavaVMCreate(size_t argc, char *argv[]) {
+ if (FCreateProc == NULL) {
+ FCreateProc = (JAVA_CREATE) GetProcAddress(LAUNCH_FUNC);
+ }
+
+ if (FCreateProc == NULL) {
+ Platform& platform = Platform::GetInstance();
+ Messages& messages = Messages::GetInstance();
+ platform.ShowMessage(
+ messages.GetMessage(FAILED_LOCATING_JVM_ENTRY_POINT));
+ return false;
+ }
+
+ return FCreateProc((int) argc, argv,
+ 0, NULL,
+ 0, NULL,
+ "",
+ "",
+ "java",
+ "java",
+ false,
+ false,
+ false,
+ 0) == 0;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/Library.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef LIBRARY_H
+#define LIBRARY_H
+
+#include "PlatformDefs.h"
+//#include "Platform.h"
+#include "OrderedMap.h"
+
+#include "jni.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <memory.h>
+#include <string>
+#include <map>
+#include <list>
+#include <vector>
+#include <fstream>
+
+using namespace std;
+
+// Private typedef for function pointer casting
+
+#if defined(_WIN32) && !defined(_WIN64)
+#define LAUNCH_FUNC "_JLI_Launch@56"
+#else
+#define LAUNCH_FUNC "JLI_Launch"
+#endif
+
+
+typedef int (JNICALL *JAVA_CREATE)(int argc, char ** argv,
+ int jargc, const char** jargv,
+ int appclassc, const char** appclassv,
+ const char* fullversion,
+ const char* dotversion,
+ const char* pname,
+ const char* lname,
+ jboolean javaargs,
+ jboolean cpwildcard,
+ jboolean javaw,
+ jint ergo);
+
+class Library {
+private:
+ std::vector<TString> *FDependentLibraryNames;
+ std::vector<Library*> *FDependenciesLibraries;
+ Module FModule;
+ std::string fname;
+
+ void Initialize();
+ void InitializeDependencies();
+ void LoadDependencies();
+ void UnloadDependencies();
+
+public:
+ void* GetProcAddress(const std::string& MethodName) const;
+
+public:
+ Library();
+ Library(const TString &FileName);
+ ~Library();
+
+ bool Load(const TString &FileName);
+ bool Unload();
+
+ const std::string& GetName() const {
+ return fname;
+ }
+
+ void AddDependency(const TString &FileName);
+ void AddDependencies(const std::vector<TString> &Dependencies);
+};
+
+class JavaLibrary : public Library {
+ JAVA_CREATE FCreateProc;
+ JavaLibrary(const TString &FileName);
+public:
+ JavaLibrary();
+ bool JavaVMCreate(size_t argc, char *argv[]);
+};
+
+#endif // LIBRARY_H
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/Macros.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "Macros.h"
+#include "Package.h"
+#include "Helpers.h"
+
+
+Macros::Macros(void) {
+}
+
+Macros::~Macros(void) {
+}
+
+void Macros::Initialize() {
+ Package& package = Package::GetInstance();
+ Macros& macros = Macros::GetInstance();
+
+ // Public macros.
+ macros.AddMacro(_T("$APPDIR"), package.GetPackageRootDirectory());
+ macros.AddMacro(_T("$PACKAGEDIR"), package.GetPackageAppDirectory());
+ macros.AddMacro(_T("$LAUNCHERDIR"), package.GetPackageLauncherDirectory());
+ macros.AddMacro(_T("$APPDATADIR"), package.GetAppDataDirectory());
+
+ TString javaHome =
+ FilePath::ExtractFilePath(package.GetJavaLibraryFileName());
+ macros.AddMacro(_T("$JREHOME"), javaHome);
+
+ // App CDS Macros
+ macros.AddMacro(_T("$CACHEDIR"), package.GetAppCDSCacheDirectory());
+
+ // Private macros.
+ TString javaVMLibraryName = FilePath::ExtractFileName(javaHome);
+ macros.AddMacro(_T("$JAVAVMLIBRARYNAME"), javaVMLibraryName);
+}
+
+Macros& Macros::GetInstance() {
+ static Macros instance;
+ return instance;
+}
+
+TString Macros::ExpandMacros(TString Value) {
+ TString result = Value;
+
+ for (std::map<TString, TString>::iterator iterator = FData.begin();
+ iterator != FData.end();
+ iterator++) {
+
+ TString name = iterator->first;
+
+ if (Value.find(name) != TString::npos) {
+ TString lvalue = iterator->second;
+ result = Helpers::ReplaceString(Value, name, lvalue);
+ result = ExpandMacros(result);
+ break;
+ }
+ }
+
+ return result;
+}
+
+void Macros::AddMacro(TString Key, TString Value) {
+ FData.insert(std::map<TString, TString>::value_type(Key, Value));
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/Macros.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef MACROS_H
+#define MACROS_H
+
+#include "Platform.h"
+
+#include <map>
+
+
+class Macros {
+private:
+ std::map<TString, TString> FData;
+
+ Macros(void);
+
+public:
+ static Macros& GetInstance();
+ static void Initialize();
+ ~Macros(void);
+
+ TString ExpandMacros(TString Value);
+ void AddMacro(TString Key, TString Value);
+};
+
+#endif // MACROS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/Messages.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "Messages.h"
+#include "Platform.h"
+#include "FilePath.h"
+#include "Helpers.h"
+#include "Macros.h"
+#include "JavaVirtualMachine.h"
+
+Messages::Messages(void) {
+ FMessages.SetReadOnly(false);
+ FMessages.SetValue(LIBRARY_NOT_FOUND, _T("Failed to find library."));
+ FMessages.SetValue(FAILED_CREATING_JVM, _T("Failed to create JVM"));
+ FMessages.SetValue(FAILED_LOCATING_JVM_ENTRY_POINT,
+ _T("Failed to locate JLI_Launch"));
+ FMessages.SetValue(NO_MAIN_CLASS_SPECIFIED, _T("No main class specified"));
+ FMessages.SetValue(METHOD_NOT_FOUND, _T("No method %s in class %s."));
+ FMessages.SetValue(CLASS_NOT_FOUND, _T("Class %s not found."));
+ FMessages.SetValue(ERROR_INVOKING_METHOD, _T("Error invoking method."));
+ FMessages.SetValue(APPCDS_CACHE_FILE_NOT_FOUND,
+ _T("Error: AppCDS cache does not exists:\n%s\n"));
+}
+
+Messages& Messages::GetInstance() {
+ static Messages instance;
+ // Guaranteed to be destroyed. Instantiated on first use.
+ return instance;
+}
+
+Messages::~Messages(void) {
+}
+
+TString Messages::GetMessage(const TString Key) {
+ TString result;
+ FMessages.GetValue(Key, result);
+ Macros& macros = Macros::GetInstance();
+ result = macros.ExpandMacros(result);
+ return result;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/Messages.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef MESSAGES_H
+#define MESSAGES_H
+
+#include "PropertyFile.h"
+
+#define LIBRARY_NOT_FOUND _T("library.not.found")
+#define FAILED_CREATING_JVM _T("failed.creating.jvm")
+#define FAILED_LOCATING_JVM_ENTRY_POINT _T("failed.locating.jvm.entry.point")
+#define NO_MAIN_CLASS_SPECIFIED _T("no.main.class.specified")
+
+#define METHOD_NOT_FOUND _T("method.not.found")
+#define CLASS_NOT_FOUND _T("class.not.found")
+#define ERROR_INVOKING_METHOD _T("error.invoking.method")
+
+#define CONFIG_FILE_NOT_FOUND _T("config.file.not.found")
+
+#define BUNDLED_JVM_NOT_FOUND _T("bundled.jvm.not.found")
+
+#define APPCDS_CACHE_FILE_NOT_FOUND _T("appcds.cache.file.not.found")
+
+class Messages {
+private:
+ PropertyFile FMessages;
+
+ Messages(void);
+public:
+ static Messages& GetInstance();
+ ~Messages(void);
+
+ TString GetMessage(const TString Key);
+};
+
+#endif // MESSAGES_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/OrderedMap.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+#ifndef ORDEREDMAP_H
+#define ORDEREDMAP_H
+
+#include <map>
+#include <vector>
+#include <assert.h>
+#include <stdexcept>
+
+#include <iostream>
+
+template <typename _T1, typename _T2>
+struct JPPair
+{
+ typedef _T1 first_type;
+ typedef _T2 second_type;
+
+ first_type first;
+ second_type second;
+
+ JPPair(first_type Value1, second_type Value2) {
+ first = Value1;
+ second = Value2;
+ }
+};
+
+
+template <typename TKey, typename TValue>
+class OrderedMap {
+public:
+ typedef TKey key_type;
+ typedef TValue mapped_type;
+ typedef JPPair<key_type, mapped_type> container_type;
+ typedef typename std::vector<container_type*>::iterator iterator;
+ typedef typename std::vector<container_type*>::const_iterator const_iterator;
+
+private:
+ typedef std::map<key_type, container_type*> map_type;
+ typedef std::vector<container_type*> list_type;
+
+ map_type FMap;
+ list_type FList;
+ bool FAllowDuplicates;
+
+ typename list_type::iterator FindListItem(const key_type Key) {
+ typename list_type::iterator result = FList.end();
+
+ for (typename list_type::iterator iterator =
+ FList.begin(); iterator != FList.end(); iterator++) {
+ container_type *item = *iterator;
+
+ if (item->first == Key) {
+ result = iterator;
+ break;
+ }
+ }
+
+ return result;
+ }
+
+public:
+ OrderedMap() {
+ FAllowDuplicates = false;
+ }
+
+ OrderedMap(const OrderedMap<key_type, mapped_type> &Value) {
+ Append(Value);
+ FAllowDuplicates = Value.GetAllowDuplicates();
+ }
+
+ ~OrderedMap() {
+ Clear();
+ }
+
+ void SetAllowDuplicates(bool Value) {
+ FAllowDuplicates = Value;
+ }
+
+ bool GetAllowDuplicates() const {
+ return FAllowDuplicates;
+ }
+
+ iterator begin() {
+ return FList.begin();
+ }
+
+ const_iterator begin() const {
+ return FList.begin();
+ }
+
+ iterator end() {
+ return FList.end();
+ }
+
+ const_iterator end() const {
+ return FList.end();
+ }
+
+ void Clear() {
+ for (typename list_type::iterator iterator =
+ FList.begin(); iterator != FList.end(); iterator++) {
+ container_type *item = *iterator;
+
+ if (item != NULL) {
+ delete item;
+ item = NULL;
+ }
+ }
+
+ FMap.clear();
+ FList.clear();
+ }
+
+ bool ContainsKey(key_type Key) {
+ bool result = false;
+
+ if (FMap.find(Key) != FMap.end()) {
+ result = true;
+ }
+
+ return result;
+ }
+
+ std::vector<key_type> GetKeys() {
+ std::vector<key_type> result;
+
+ for (typename list_type::const_iterator iterator = FList.begin();
+ iterator != FList.end(); iterator++) {
+ container_type *item = *iterator;
+ result.push_back(item->first);
+ }
+
+ return result;
+ }
+
+ void Assign(const OrderedMap<key_type, mapped_type> &Value) {
+ Clear();
+ Append(Value);
+ }
+
+ void Append(const OrderedMap<key_type, mapped_type> &Value) {
+ for (size_t index = 0; index < Value.FList.size(); index++) {
+ container_type *item = Value.FList[index];
+ Append(item->first, item->second);
+ }
+ }
+
+ void Append(key_type Key, mapped_type Value) {
+ container_type *item = new container_type(Key, Value);
+ FMap.insert(std::pair<key_type, container_type*>(Key, item));
+ FList.push_back(item);
+ }
+
+ bool RemoveByKey(key_type Key) {
+ bool result = false;
+ typename list_type::iterator iterator = FindListItem(Key);
+
+ if (iterator != FList.end()) {
+ FMap.erase(Key);
+ FList.erase(iterator);
+ result = true;
+ }
+
+ return result;
+ }
+
+ bool GetValue(key_type Key, mapped_type &Value) {
+ bool result = false;
+ container_type* item = FMap[Key];
+
+ if (item != NULL) {
+ Value = item->second;
+ result = true;
+ }
+
+ return result;
+ }
+
+ bool SetValue(key_type Key, mapped_type &Value) {
+ bool result = false;
+
+ if ((FAllowDuplicates == false) && (ContainsKey(Key) == true)) {
+ container_type *item = FMap[Key];
+
+ if (item != NULL) {
+ item->second = Value;
+ result = true;
+ }
+ }
+ else {
+ Append(Key, Value);
+ result = true;
+ }
+
+ return result;
+ }
+
+ bool GetKey(int index, key_type &Value) {
+ if (index < 0 || index >= (int)FList.size()) {
+ return false;
+ }
+ container_type *item = FList.at(index);
+ if (item != NULL) {
+ Value = item->first;
+ return true;
+ }
+
+ return false;
+ }
+
+ bool GetValue(int index, mapped_type &Value) {
+ if (index < 0 || index >= (int)FList.size()) {
+ return false;
+ }
+ container_type *item = FList.at(index);
+ if (item != NULL) {
+ Value = item->second;
+ return true;
+ }
+
+ return false;
+ }
+
+ mapped_type &operator[](key_type Key) {
+ container_type* item = FMap[Key];
+ assert(item != NULL);
+
+ if (item != NULL) {
+ return item->second;
+ }
+
+ throw std::invalid_argument("Key not found");
+ }
+
+ OrderedMap& operator= (OrderedMap &Value) {
+ Clear();
+ FAllowDuplicates = Value.GetAllowDuplicates();
+ Append(Value);
+ return *this;
+ }
+
+ size_t Count() {
+ return FList.size();
+ }
+};
+
+#endif // ORDEREDMAP_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/Package.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,557 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "Package.h"
+#include "Helpers.h"
+#include "Macros.h"
+#include "IniFile.h"
+
+#include <assert.h>
+
+
+Package::Package(void) {
+ FInitialized = false;
+ Initialize();
+}
+
+TPlatformNumber StringToPercentageOfNumber(TString Value,
+ TPlatformNumber Number) {
+ TPlatformNumber result = 0;
+ size_t percentage = atoi(PlatformString(Value.c_str()));
+
+ if (percentage > 0 && Number > 0) {
+ result = Number * percentage / 100;
+ }
+
+ return result;
+}
+
+void Package::Initialize() {
+ if (FInitialized == true) {
+ return;
+ }
+
+ Platform& platform = Platform::GetInstance();
+
+ FBootFields = new PackageBootFields();
+ FDebugging = dsNone;
+
+ // Allow duplicates for Java options, so we can have multiple --add-exports
+ // or similar args.
+ FBootFields->FJavaOptions.SetAllowDuplicates(true);
+ FBootFields->FPackageRootDirectory = platform.GetPackageRootDirectory();
+ FBootFields->FPackageAppDirectory = platform.GetPackageAppDirectory();
+ FBootFields->FPackageLauncherDirectory =
+ platform.GetPackageLauncherDirectory();
+ FBootFields->FAppDataDirectory = platform.GetAppDataDirectory();
+
+ std::map<TString, TString> keys = platform.GetKeys();
+
+ // Read from configure.cfg/Info.plist
+ AutoFreePtr<ISectionalPropertyContainer> config =
+ platform.GetConfigFile(platform.GetConfigFileName());
+
+ config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[JPACKAGE_APP_DATA_DIR], FBootFields->FPackageAppDataDirectory);
+ FBootFields->FPackageAppDataDirectory =
+ FilePath::FixPathForPlatform(FBootFields->FPackageAppDataDirectory);
+
+ // Main JAR.
+ config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[CONFIG_MAINJAR_KEY], FBootFields->FMainJar);
+ FBootFields->FMainJar = FilePath::FixPathForPlatform(FBootFields->FMainJar);
+
+ // Main Module.
+ config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[CONFIG_MAINMODULE_KEY], FBootFields->FMainModule);
+
+ // Classpath.
+ // 1. If the provided class path contains main jar then only use
+ // provided class path.
+ // 2. If class path provided by config file is empty then add main jar.
+ // 3. If main jar is not in provided class path then add it.
+ config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[CONFIG_CLASSPATH_KEY], FBootFields->FClassPath);
+ FBootFields->FClassPath =
+ FilePath::FixPathSeparatorForPlatform(FBootFields->FClassPath);
+
+ if (FBootFields->FClassPath.empty() == true) {
+ FBootFields->FClassPath = GetMainJar();
+ } else if (FBootFields->FClassPath.find(GetMainJar()) == TString::npos) {
+ FBootFields->FClassPath = GetMainJar()
+ + FilePath::PathSeparator() + FBootFields->FClassPath;
+ }
+
+ // Modulepath.
+ config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[CONFIG_MODULEPATH_KEY], FBootFields->FModulePath);
+ FBootFields->FModulePath =
+ FilePath::FixPathSeparatorForPlatform(FBootFields->FModulePath);
+
+ // Main Class.
+ config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[CONFIG_MAINCLASSNAME_KEY], FBootFields->FMainClassName);
+
+ // Splash Screen.
+ if (config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[CONFIG_SPLASH_KEY],
+ FBootFields->FSplashScreenFileName) == true) {
+ FBootFields->FSplashScreenFileName =
+ FilePath::IncludeTrailingSeparator(GetPackageAppDirectory())
+ + FilePath::FixPathForPlatform(FBootFields->FSplashScreenFileName);
+
+ if (FilePath::FileExists(FBootFields->FSplashScreenFileName) == false) {
+ FBootFields->FSplashScreenFileName = _T("");
+ }
+ }
+
+ // Runtime.
+ config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[JAVA_RUNTIME_KEY], FBootFields->FJavaRuntimeDirectory);
+
+ // Read jvmargs.
+ PromoteAppCDSState(config);
+ ReadJavaOptions(config);
+
+ // Read args if none were passed in.
+ if (FBootFields->FArgs.size() == 0) {
+ OrderedMap<TString, TString> args;
+
+ if (config->GetSection(keys[CONFIG_SECTION_ARGOPTIONS], args) == true) {
+ FBootFields->FArgs = Helpers::MapToNameValueList(args);
+ }
+ }
+
+ // Auto Memory.
+ TString autoMemory;
+
+ if (config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[CONFIG_APP_MEMORY], autoMemory) == true) {
+ if (autoMemory == _T("auto") || autoMemory == _T("100%")) {
+ FBootFields->FMemoryState = PackageBootFields::msAuto;
+ FBootFields->FMemorySize = platform.GetMemorySize();
+ } else if (autoMemory.length() == 2 && isdigit(autoMemory[0]) &&
+ autoMemory[1] == '%') {
+ FBootFields->FMemoryState = PackageBootFields::msAuto;
+ FBootFields->FMemorySize =
+ StringToPercentageOfNumber(autoMemory.substr(0, 1),
+ platform.GetMemorySize());
+ } else if (autoMemory.length() == 3 && isdigit(autoMemory[0]) &&
+ isdigit(autoMemory[1]) && autoMemory[2] == '%') {
+ FBootFields->FMemoryState = PackageBootFields::msAuto;
+ FBootFields->FMemorySize =
+ StringToPercentageOfNumber(autoMemory.substr(0, 2),
+ platform.GetMemorySize());
+ } else {
+ FBootFields->FMemoryState = PackageBootFields::msManual;
+ FBootFields->FMemorySize = 0;
+ }
+ }
+
+ // Debug
+ TString debug;
+ if (config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ keys[CONFIG_APP_DEBUG], debug) == true) {
+ FBootFields->FArgs.push_back(debug);
+ }
+}
+
+void Package::Clear() {
+ FreeBootFields();
+ FInitialized = false;
+}
+
+// This is the only location that the AppCDS state should be modified except
+// by command line arguments provided by the user.
+//
+// The state of AppCDS is as follows:
+//
+// -> cdsUninitialized
+// -> cdsGenCache If -Xappcds:generatecache
+// -> cdsDisabled If -Xappcds:off
+// -> cdsEnabled If "AppCDSJavaOptions" section is present
+// -> cdsAuto If "AppCDSJavaOptions" section is present and
+// app.appcds.cache=auto
+// -> cdsDisabled Default
+//
+void Package::PromoteAppCDSState(ISectionalPropertyContainer* Config) {
+ Platform& platform = Platform::GetInstance();
+ std::map<TString, TString> keys = platform.GetKeys();
+
+ // The AppCDS state can change at this point.
+ switch (platform.GetAppCDSState()) {
+ case cdsEnabled:
+ case cdsAuto:
+ case cdsDisabled:
+ case cdsGenCache: {
+ // Do nothing.
+ break;
+ }
+
+ case cdsUninitialized: {
+ if (Config->ContainsSection(
+ keys[CONFIG_SECTION_APPCDSJAVAOPTIONS]) == true) {
+ // If the AppCDS section is present then enable AppCDS.
+ TString appCDSCacheValue;
+
+ // If running with AppCDS enabled, and the configuration has
+ // been setup so "auto" is enabled, then
+ // the launcher will attempt to generate the cache file
+ // automatically and run the application.
+ if (Config->GetValue(keys[CONFIG_SECTION_APPLICATION],
+ _T("app.appcds.cache"), appCDSCacheValue) == true &&
+ appCDSCacheValue == _T("auto")) {
+ platform.SetAppCDSState(cdsAuto);
+ }
+ else {
+ platform.SetAppCDSState(cdsEnabled);
+ }
+ } else {
+
+ platform.SetAppCDSState(cdsDisabled);
+ }
+ }
+ }
+}
+
+void Package::ReadJavaOptions(ISectionalPropertyContainer* Config) {
+ Platform& platform = Platform::GetInstance();
+ std::map<TString, TString> keys = platform.GetKeys();
+
+ // Evaluate based on the current AppCDS state.
+ switch (platform.GetAppCDSState()) {
+ case cdsUninitialized: {
+ throw Exception(_T("Internal Error"));
+ }
+
+ case cdsDisabled: {
+ Config->GetSection(keys[CONFIG_SECTION_JAVAOPTIONS],
+ FBootFields->FJavaOptions);
+ break;
+ }
+
+ case cdsGenCache: {
+ Config->GetSection(keys[
+ CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS],
+ FBootFields->FJavaOptions);
+ break;
+ }
+
+ case cdsAuto:
+ case cdsEnabled: {
+ if (Config->GetValue(keys[CONFIG_SECTION_APPCDSJAVAOPTIONS],
+ _T( "-XX:SharedArchiveFile"),
+ FBootFields->FAppCDSCacheFileName) == true) {
+ // File names may contain the incorrect path separators.
+ // The cache file name must be corrected at this point.
+ if (FBootFields->FAppCDSCacheFileName.empty() == false) {
+ IniFile* iniConfig = dynamic_cast<IniFile*>(Config);
+
+ if (iniConfig != NULL) {
+ FBootFields->FAppCDSCacheFileName =
+ FilePath::FixPathForPlatform(
+ FBootFields->FAppCDSCacheFileName);
+ iniConfig->SetValue(keys[
+ CONFIG_SECTION_APPCDSJAVAOPTIONS],
+ _T( "-XX:SharedArchiveFile"),
+ FBootFields->FAppCDSCacheFileName);
+ }
+ }
+
+ Config->GetSection(keys[CONFIG_SECTION_APPCDSJAVAOPTIONS],
+ FBootFields->FJavaOptions);
+ }
+
+ break;
+ }
+ }
+}
+
+void Package::SetCommandLineArguments(int argc, TCHAR* argv[]) {
+ if (argc > 0) {
+ std::list<TString> args;
+
+ // Prepare app arguments. Skip value at index 0 -
+ // this is path to executable.
+ FBootFields->FCommandName = argv[0];
+
+ // Path to executable is at 0 index so start at index 1.
+ for (int index = 1; index < argc; index++) {
+ TString arg = argv[index];
+
+#ifdef DEBUG
+ if (arg == _T("-debug")) {
+ FDebugging = dsNative;
+ }
+
+ if (arg == _T("-javadebug")) {
+ FDebugging = dsJava;
+ }
+#endif //DEBUG
+#ifdef MAC
+ if (arg.find(_T("-psn_"), 0) != TString::npos) {
+ Platform& platform = Platform::GetInstance();
+
+ if (platform.IsMainThread() == true) {
+#ifdef DEBUG
+ printf("%s\n", arg.c_str());
+#endif //DEBUG
+ continue;
+ }
+ }
+
+ if (arg == _T("-NSDocumentRevisionsDebugMode")) {
+ // Ignore -NSDocumentRevisionsDebugMode and
+ // the following YES/NO
+ index++;
+ continue;
+ }
+#endif //MAC
+
+ args.push_back(arg);
+ }
+
+ if (args.size() > 0) {
+ FBootFields->FArgs = args;
+ }
+ }
+}
+
+Package& Package::GetInstance() {
+ static Package instance;
+ // Guaranteed to be destroyed. Instantiated on first use.
+ return instance;
+}
+
+Package::~Package(void) {
+ FreeBootFields();
+}
+
+void Package::FreeBootFields() {
+ if (FBootFields != NULL) {
+ delete FBootFields;
+ FBootFields = NULL;
+ }
+}
+
+OrderedMap<TString, TString> Package::GetJavaOptions() {
+ return FBootFields->FJavaOptions;
+}
+
+std::vector<TString> GetKeysThatAreNotDuplicates(OrderedMap<TString,
+ TString> &Defaults, OrderedMap<TString, TString> &Overrides) {
+ std::vector<TString> result;
+ std::vector<TString> overrideKeys = Overrides.GetKeys();
+
+ for (size_t index = 0; index < overrideKeys.size(); index++) {
+ TString overridesKey = overrideKeys[index];
+ TString overridesValue;
+ TString defaultValue;
+
+ if ((Defaults.ContainsKey(overridesKey) == false) ||
+ (Defaults.GetValue(overridesKey, defaultValue) == true &&
+ Overrides.GetValue(overridesKey, overridesValue) == true &&
+ defaultValue != overridesValue)) {
+ result.push_back(overridesKey);
+ }
+ }
+
+ return result;
+}
+
+OrderedMap<TString, TString> CreateOrderedMapFromKeyList(OrderedMap<TString,
+ TString> &Map, std::vector<TString> &Keys) {
+ OrderedMap<TString, TString> result;
+
+ for (size_t index = 0; index < Keys.size(); index++) {
+ TString key = Keys[index];
+ TString value;
+
+ if (Map.GetValue(key, value) == true) {
+ result.Append(key, value);
+ }
+ }
+
+ return result;
+}
+
+std::vector<TString> GetKeysThatAreNotOverridesOfDefaultValues(
+ OrderedMap<TString, TString> &Defaults, OrderedMap<TString,
+ TString> &Overrides) {
+ std::vector<TString> result;
+ std::vector<TString> keys = Overrides.GetKeys();
+
+ for (unsigned int index = 0; index< keys.size(); index++) {
+ TString key = keys[index];
+
+ if (Defaults.ContainsKey(key) == true) {
+ try {
+ TString value = Overrides[key];
+ Defaults[key] = value;
+ }
+ catch (std::out_of_range &) {
+ }
+ }
+ else {
+ result.push_back(key);
+ }
+ }
+
+ return result;
+}
+
+std::list<TString> Package::GetArgs() {
+ assert(FBootFields != NULL);
+ return FBootFields->FArgs;
+}
+
+TString Package::GetPackageRootDirectory() {
+ assert(FBootFields != NULL);
+ return FBootFields->FPackageRootDirectory;
+}
+
+TString Package::GetPackageAppDirectory() {
+ assert(FBootFields != NULL);
+ return FBootFields->FPackageAppDirectory;
+}
+
+TString Package::GetPackageLauncherDirectory() {
+ assert(FBootFields != NULL);
+ return FBootFields->FPackageLauncherDirectory;
+}
+
+TString Package::GetAppDataDirectory() {
+ assert(FBootFields != NULL);
+ return FBootFields->FAppDataDirectory;
+}
+
+TString Package::GetAppCDSCacheDirectory() {
+ if (FAppCDSCacheDirectory.empty()) {
+ Platform& platform = Platform::GetInstance();
+ FAppCDSCacheDirectory = FilePath::IncludeTrailingSeparator(
+ platform.GetAppDataDirectory())
+ + FilePath::IncludeTrailingSeparator(
+ GetPackageAppDataDirectory()) + _T("cache");
+
+ Macros& macros = Macros::GetInstance();
+ FAppCDSCacheDirectory = macros.ExpandMacros(FAppCDSCacheDirectory);
+ FAppCDSCacheDirectory =
+ FilePath::FixPathForPlatform(FAppCDSCacheDirectory);
+ }
+
+ return FAppCDSCacheDirectory;
+}
+
+TString Package::GetAppCDSCacheFileName() {
+ assert(FBootFields != NULL);
+
+ if (FBootFields->FAppCDSCacheFileName.empty() == false) {
+ Macros& macros = Macros::GetInstance();
+ FBootFields->FAppCDSCacheFileName =
+ macros.ExpandMacros(FBootFields->FAppCDSCacheFileName);
+ FBootFields->FAppCDSCacheFileName =
+ FilePath::FixPathForPlatform(FBootFields->FAppCDSCacheFileName);
+ }
+
+ return FBootFields->FAppCDSCacheFileName;
+}
+
+TString Package::GetPackageAppDataDirectory() {
+ assert(FBootFields != NULL);
+ return FBootFields->FPackageAppDataDirectory;
+}
+
+TString Package::GetClassPath() {
+ assert(FBootFields != NULL);
+ return FBootFields->FClassPath;
+}
+
+TString Package::GetModulePath() {
+ assert(FBootFields != NULL);
+ return FBootFields->FModulePath;
+}
+
+TString Package::GetMainJar() {
+ assert(FBootFields != NULL);
+ return FBootFields->FMainJar;
+}
+
+TString Package::GetMainModule() {
+ assert(FBootFields != NULL);
+ return FBootFields->FMainModule;
+}
+
+TString Package::GetMainClassName() {
+ assert(FBootFields != NULL);
+ return FBootFields->FMainClassName;
+}
+
+TString Package::GetJavaLibraryFileName() {
+ assert(FBootFields != NULL);
+
+ if (FBootFields->FJavaLibraryFileName.empty() == true) {
+ Platform& platform = Platform::GetInstance();
+ Macros& macros = Macros::GetInstance();
+ TString jvmRuntimePath = macros.ExpandMacros(GetJavaRuntimeDirectory());
+ FBootFields->FJavaLibraryFileName =
+ platform.GetBundledJavaLibraryFileName(jvmRuntimePath);
+ }
+
+ return FBootFields->FJavaLibraryFileName;
+}
+
+TString Package::GetJavaRuntimeDirectory() {
+ assert(FBootFields != NULL);
+ return FBootFields->FJavaRuntimeDirectory;
+}
+
+TString Package::GetSplashScreenFileName() {
+ assert(FBootFields != NULL);
+ return FBootFields->FSplashScreenFileName;
+}
+
+bool Package::HasSplashScreen() {
+ assert(FBootFields != NULL);
+ return FilePath::FileExists(FBootFields->FSplashScreenFileName);
+}
+
+TString Package::GetCommandName() {
+ assert(FBootFields != NULL);
+ return FBootFields->FCommandName;
+}
+
+TPlatformNumber Package::GetMemorySize() {
+ assert(FBootFields != NULL);
+ return FBootFields->FMemorySize;
+}
+
+PackageBootFields::MemoryState Package::GetMemoryState() {
+ assert(FBootFields != NULL);
+ return FBootFields->FMemoryState;
+}
+
+DebugState Package::Debugging() {
+ return FDebugging;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/Package.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef PACKAGE_H
+#define PACKAGE_H
+
+
+#include "Platform.h"
+#include "PlatformString.h"
+#include "FilePath.h"
+#include "PropertyFile.h"
+
+#include <map>
+#include <list>
+
+class PackageBootFields {
+public:
+ enum MemoryState {msManual, msAuto};
+
+public:
+ OrderedMap<TString, TString> FJavaOptions;
+ std::list<TString> FArgs;
+
+ TString FPackageRootDirectory;
+ TString FPackageAppDirectory;
+ TString FPackageLauncherDirectory;
+ TString FAppDataDirectory;
+ TString FPackageAppDataDirectory;
+ TString FClassPath;
+ TString FModulePath;
+ TString FMainJar;
+ TString FMainModule;
+ TString FMainClassName;
+ TString FJavaRuntimeDirectory;
+ TString FJavaLibraryFileName;
+ TString FSplashScreenFileName;
+ bool FUseJavaPreferences;
+ TString FCommandName;
+
+ TString FAppCDSCacheFileName;
+
+ TPlatformNumber FMemorySize;
+ MemoryState FMemoryState;
+};
+
+
+class Package {
+private:
+ Package(Package const&); // Don't Implement.
+ void operator=(Package const&); // Don't implement
+
+private:
+ bool FInitialized;
+ PackageBootFields* FBootFields;
+ TString FAppCDSCacheDirectory;
+
+ DebugState FDebugging;
+
+ Package(void);
+
+ TString GetMainJar();
+ void ReadJavaOptions(ISectionalPropertyContainer* Config);
+ void PromoteAppCDSState(ISectionalPropertyContainer* Config);
+
+public:
+ static Package& GetInstance();
+ ~Package(void);
+
+ void Initialize();
+ void Clear();
+ void FreeBootFields();
+
+ void SetCommandLineArguments(int argc, TCHAR* argv[]);
+
+ OrderedMap<TString, TString> GetJavaOptions();
+ TString GetMainModule();
+
+ std::list<TString> GetArgs();
+
+ TString GetPackageRootDirectory();
+ TString GetPackageAppDirectory();
+ TString GetPackageLauncherDirectory();
+ TString GetAppDataDirectory();
+
+ TString GetAppCDSCacheDirectory();
+ TString GetAppCDSCacheFileName();
+
+ TString GetPackageAppDataDirectory();
+ TString GetClassPath();
+ TString GetModulePath();
+ TString GetMainClassName();
+ TString GetJavaLibraryFileName();
+ TString GetJavaRuntimeDirectory();
+ TString GetSplashScreenFileName();
+ bool HasSplashScreen();
+ TString GetCommandName();
+
+ TPlatformNumber GetMemorySize();
+ PackageBootFields::MemoryState GetMemoryState();
+
+ DebugState Debugging();
+};
+
+#endif // PACKAGE_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/Platform.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "Platform.h"
+#include "Messages.h"
+#include "PlatformString.h"
+#include "FilePath.h"
+
+#include <fstream>
+#include <locale>
+
+#ifdef WINDOWS
+#include "WindowsPlatform.h"
+#endif // WINDOWS
+#ifdef LINUX
+#include "LinuxPlatform.h"
+#endif // LINUX
+#ifdef MAC
+#include "MacPlatform.h"
+#endif // MAC
+
+Platform& Platform::GetInstance() {
+#ifdef WINDOWS
+ static WindowsPlatform instance;
+#endif // WINDOWS
+
+#ifdef LINUX
+ static LinuxPlatform instance;
+#endif // LINUX
+
+#ifdef MAC
+ static MacPlatform instance;
+#endif // MAC
+
+ return instance;
+}
+
+TString Platform::GetConfigFileName() {
+ TString result;
+ TString basedir = GetPackageAppDirectory();
+
+ if (basedir.empty() == false) {
+ basedir = FilePath::IncludeTrailingSeparator(basedir);
+ TString appConfig = basedir + GetAppName() + _T(".cfg");
+
+ if (FilePath::FileExists(appConfig) == true) {
+ result = appConfig;
+ }
+ else {
+ result = basedir + _T("package.cfg");
+
+ if (FilePath::FileExists(result) == false) {
+ result = _T("");
+ }
+ }
+ }
+
+ return result;
+}
+
+std::list<TString> Platform::LoadFromFile(TString FileName) {
+ std::list<TString> result;
+
+ if (FilePath::FileExists(FileName) == true) {
+ std::wifstream stream(FileName.data());
+ InitStreamLocale(&stream);
+
+ if (stream.is_open() == true) {
+ while (stream.eof() == false) {
+ std::wstring line;
+ std::getline(stream, line);
+
+ // # at the first character will comment out the line.
+ if (line.empty() == false && line[0] != '#') {
+ result.push_back(PlatformString(line).toString());
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+void Platform::SaveToFile(TString FileName, std::list<TString> Contents, bool ownerOnly) {
+ TString path = FilePath::ExtractFilePath(FileName);
+
+ if (FilePath::DirectoryExists(path) == false) {
+ FilePath::CreateDirectory(path, ownerOnly);
+ }
+
+ std::wofstream stream(FileName.data());
+ InitStreamLocale(&stream);
+
+ FilePath::ChangePermissions(FileName.data(), ownerOnly);
+
+ if (stream.is_open() == true) {
+ for (std::list<TString>::const_iterator iterator =
+ Contents.begin(); iterator != Contents.end(); iterator++) {
+ TString line = *iterator;
+ stream << PlatformString(line).toUnicodeString() << std::endl;
+ }
+ }
+}
+
+std::map<TString, TString> Platform::GetKeys() {
+ std::map<TString, TString> keys;
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_VERSION,
+ _T("app.version")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINJAR_KEY,
+ _T("app.mainjar")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINMODULE_KEY,
+ _T("app.mainmodule")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINCLASSNAME_KEY,
+ _T("app.mainclass")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_CLASSPATH_KEY,
+ _T("app.classpath")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_MODULEPATH_KEY,
+ _T("app.modulepath")));
+ keys.insert(std::map<TString, TString>::value_type(APP_NAME_KEY,
+ _T("app.name")));
+ keys.insert(std::map<TString, TString>::value_type(JAVA_RUNTIME_KEY,
+ _T("app.runtime")));
+ keys.insert(std::map<TString, TString>::value_type(JPACKAGE_APP_DATA_DIR,
+ _T("app.identifier")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_SPLASH_KEY,
+ _T("app.splash")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_MEMORY,
+ _T("app.memory")));
+ keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_DEBUG,
+ _T("app.debug")));
+ keys.insert(std::map<TString,
+ TString>::value_type(CONFIG_APPLICATION_INSTANCE,
+ _T("app.application.instance")));
+ keys.insert(std::map<TString,
+ TString>::value_type(CONFIG_SECTION_APPLICATION,
+ _T("Application")));
+ keys.insert(std::map<TString,
+ TString>::value_type(CONFIG_SECTION_JAVAOPTIONS,
+ _T("JavaOptions")));
+ keys.insert(std::map<TString,
+ TString>::value_type(CONFIG_SECTION_APPCDSJAVAOPTIONS,
+ _T("AppCDSJavaOptions")));
+ keys.insert(std::map<TString,
+ TString>::value_type(CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS,
+ _T("AppCDSGenerateCacheJavaOptions")));
+ keys.insert(std::map<TString,
+ TString>::value_type(CONFIG_SECTION_ARGOPTIONS,
+ _T("ArgOptions")));
+
+ return keys;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/Platform.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef PLATFORM_H
+#define PLATFORM_H
+
+#include "PlatformDefs.h"
+#include "Properties.h"
+#include "OrderedMap.h"
+#include "Library.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <memory.h>
+#include <string>
+#include <map>
+#include <list>
+#include <vector>
+#include <fstream>
+
+using namespace std;
+
+// Config file sections
+#define CONFIG_SECTION_APPLICATION _T("CONFIG_SECTION_APPLICATION")
+#define CONFIG_SECTION_JAVAOPTIONS _T("CONFIG_SECTION_JAVAOPTIONS")
+#define CONFIG_SECTION_APPCDSJAVAOPTIONS _T("CONFIG_SECTION_APPCDSJAVAOPTIONS")
+#define CONFIG_SECTION_ARGOPTIONS _T("CONFIG_SECTION_ARGOPTIONS")
+#define CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS \
+ _T("CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS")
+
+// Config file keys.
+#define CONFIG_VERSION _T("CONFIG_VERSION")
+#define CONFIG_MAINJAR_KEY _T("CONFIG_MAINJAR_KEY")
+#define CONFIG_MAINMODULE_KEY _T("CONFIG_MAINMODULE_KEY")
+#define CONFIG_MAINCLASSNAME_KEY _T("CONFIG_MAINCLASSNAME_KEY")
+#define CONFIG_CLASSPATH_KEY _T("CONFIG_CLASSPATH_KEY")
+#define CONFIG_MODULEPATH_KEY _T("CONFIG_MODULEPATH_KEY")
+#define APP_NAME_KEY _T("APP_NAME_KEY")
+#define CONFIG_SPLASH_KEY _T("CONFIG_SPLASH_KEY")
+#define CONFIG_APP_MEMORY _T("CONFIG_APP_MEMORY")
+#define CONFIG_APP_DEBUG _T("CONFIG_APP_DEBUG")
+#define CONFIG_APPLICATION_INSTANCE _T("CONFIG_APPLICATION_INSTANCE")
+
+#define JAVA_RUNTIME_KEY _T("JAVA_RUNTIME_KEY")
+#define JPACKAGE_APP_DATA_DIR _T("CONFIG_APP_IDENTIFIER")
+
+struct WideString {
+ size_t length;
+ wchar_t* data;
+
+ WideString() { length = 0; data = NULL; }
+};
+
+struct MultibyteString {
+ size_t length;
+ char* data;
+
+ MultibyteString() { length = 0; data = NULL; }
+};
+
+class Process {
+protected:
+ std::list<TString> FOutput;
+
+public:
+ Process() {
+ Output.SetInstance(this);
+ Input.SetInstance(this);
+ }
+
+ virtual ~Process() {}
+
+ virtual bool IsRunning() = 0;
+ virtual bool Terminate() = 0;
+ virtual bool Execute(const TString Application,
+ const std::vector<TString> Arguments, bool AWait = false) = 0;
+ virtual bool Wait() = 0;
+ virtual TProcessID GetProcessID() = 0;
+
+ virtual std::list<TString> GetOutput() { return FOutput; }
+ virtual void SetInput(TString Value) = 0;
+
+ ReadProperty<Process, std::list<TString>, &Process::GetOutput> Output;
+ WriteProperty<Process, TString, &Process::SetInput> Input;
+};
+
+
+template <typename T>
+class AutoFreePtr {
+private:
+ T* FObject;
+
+public:
+ AutoFreePtr() {
+ FObject = NULL;
+ }
+
+ AutoFreePtr(T* Value) {
+ FObject = Value;
+ }
+
+ ~AutoFreePtr() {
+ if (FObject != NULL) {
+ delete FObject;
+ }
+ }
+
+ operator T* () const {
+ return FObject;
+ }
+
+ T& operator* () const {
+ return *FObject;
+ }
+
+ T* operator->() const {
+ return FObject;
+ }
+
+ T** operator&() {
+ return &FObject;
+ }
+
+ T* operator=(const T * rhs) {
+ FObject = rhs;
+ return FObject;
+ }
+};
+
+enum DebugState {dsNone, dsNative, dsJava};
+enum MessageResponse {mrOK, mrCancel};
+enum AppCDSState {cdsUninitialized, cdsDisabled,
+ cdsEnabled, cdsAuto, cdsGenCache};
+
+class Platform {
+private:
+ AppCDSState FAppCDSState;
+
+protected:
+ Platform(void): FAppCDSState(cdsUninitialized) {
+ }
+
+public:
+ AppCDSState GetAppCDSState() { return FAppCDSState; }
+ void SetAppCDSState(AppCDSState Value) { FAppCDSState = Value; }
+
+ static Platform& GetInstance();
+
+ virtual ~Platform(void) {}
+
+public:
+ virtual void ShowMessage(TString title, TString description) = 0;
+ virtual void ShowMessage(TString description) = 0;
+ virtual MessageResponse ShowResponseMessage(TString title,
+ TString description) = 0;
+
+ // Caller must free result using delete[].
+ virtual TCHAR* ConvertStringToFileSystemString(TCHAR* Source,
+ bool &release) = 0;
+
+ // Caller must free result using delete[].
+ virtual TCHAR* ConvertFileSystemStringToString(TCHAR* Source,
+ bool &release) = 0;
+
+ // Returns:
+ // Windows=C:\Users\<username>\AppData\Local
+ // Linux=~/.local
+ // Mac=~/Library/Application Support
+ virtual TString GetAppDataDirectory() = 0;
+
+ virtual TString GetPackageAppDirectory() = 0;
+ virtual TString GetPackageLauncherDirectory() = 0;
+ virtual TString GetPackageRuntimeBinDirectory() = 0;
+ virtual TString GetAppName() = 0;
+
+ virtual TString GetConfigFileName();
+
+ virtual TString GetBundledJavaLibraryFileName(TString RuntimePath) = 0;
+
+ // Caller must free result.
+ virtual ISectionalPropertyContainer* GetConfigFile(TString FileName) = 0;
+
+ virtual TString GetModuleFileName() = 0;
+ virtual TString GetPackageRootDirectory() = 0;
+
+ virtual Module LoadLibrary(TString FileName) = 0;
+ virtual void FreeLibrary(Module Module) = 0;
+ virtual Procedure GetProcAddress(Module Module, std::string MethodName) = 0;
+
+ // Caller must free result.
+ virtual Process* CreateProcess() = 0;
+
+ virtual bool IsMainThread() = 0;
+
+ // Returns megabytes.
+ virtual TPlatformNumber GetMemorySize() = 0;
+
+ virtual std::map<TString, TString> GetKeys();
+
+ virtual void InitStreamLocale(wios *stream) = 0;
+ virtual std::list<TString> LoadFromFile(TString FileName);
+ virtual void SaveToFile(TString FileName,
+ std::list<TString> Contents, bool ownerOnly);
+
+ virtual TString GetTempDirectory() = 0;
+
+ virtual void addPlatformDependencies(JavaLibrary *pJavaLibrary) = 0;
+
+public:
+ // String helpers
+ // Caller must free result using delete[].
+ static void CopyString(char *Destination,
+ size_t NumberOfElements, const char *Source);
+
+ // Caller must free result using delete[].
+ static void CopyString(wchar_t *Destination,
+ size_t NumberOfElements, const wchar_t *Source);
+
+ static WideString MultibyteStringToWideString(const char* value);
+ static MultibyteString WideStringToMultibyteString(const wchar_t* value);
+};
+
+class Exception: public std::exception {
+private:
+ TString FMessage;
+
+protected:
+ void SetMessage(const TString Message) {
+ FMessage = Message;
+ }
+
+public:
+ explicit Exception() : exception() {}
+ explicit Exception(const TString Message) : exception() {
+ SetMessage(Message);
+ }
+ virtual ~Exception() throw() {}
+
+ TString GetMessage() { return FMessage; }
+};
+
+#endif // PLATFORM_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/PlatformString.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "PlatformString.h"
+
+#include "Helpers.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdlib.h>
+#include <memory.h>
+#include <sstream>
+#include <string.h>
+
+#include "jni.h"
+
+void PlatformString::initialize() {
+ FWideTStringToFree = NULL;
+ FLength = 0;
+ FData = NULL;
+}
+
+PlatformString::PlatformString(void) {
+ initialize();
+}
+
+PlatformString::~PlatformString(void) {
+ if (FData != NULL) {
+ delete[] FData;
+ }
+
+ if (FWideTStringToFree != NULL) {
+ delete[] FWideTStringToFree;
+ }
+}
+
+PlatformString::PlatformString(const PlatformString &value) {
+ initialize();
+ FLength = value.FLength;
+ FData = new char[FLength + 1];
+ Platform::CopyString(FData, FLength + 1, value.FData);
+}
+
+PlatformString::PlatformString(const char* value) {
+ initialize();
+ FLength = strlen(value);
+ FData = new char[FLength + 1];
+ Platform::CopyString(FData, FLength + 1, value);
+}
+
+PlatformString::PlatformString(size_t Value) {
+ initialize();
+
+ std::stringstream ss;
+ std::string s;
+ ss << Value;
+ s = ss.str();
+
+ FLength = strlen(s.c_str());
+ FData = new char[FLength + 1];
+ Platform::CopyString(FData, FLength + 1, s.c_str());
+}
+
+PlatformString::PlatformString(const wchar_t* value) {
+ initialize();
+ MultibyteString temp = Platform::WideStringToMultibyteString(value);
+ FLength = temp.length;
+ FData = temp.data;
+}
+
+PlatformString::PlatformString(const std::string &value) {
+ initialize();
+ const char* lvalue = value.data();
+ FLength = value.size();
+ FData = new char[FLength + 1];
+ Platform::CopyString(FData, FLength + 1, lvalue);
+}
+
+PlatformString::PlatformString(const std::wstring &value) {
+ initialize();
+ const wchar_t* lvalue = value.data();
+ MultibyteString temp = Platform::WideStringToMultibyteString(lvalue);
+ FLength = temp.length;
+ FData = temp.data;
+}
+
+TString PlatformString::Format(const TString value, ...) {
+ TString result = value;
+
+ va_list arglist;
+ va_start(arglist, value);
+
+ while (1) {
+ size_t pos = result.find(_T("%s"), 0);
+
+ if (pos == TString::npos) {
+ break;
+ }
+ else {
+ TCHAR* arg = va_arg(arglist, TCHAR*);
+
+ if (arg == NULL) {
+ break;
+ }
+ else {
+ result.replace(pos, StringLength(_T("%s")), arg);
+ }
+ }
+ }
+
+ va_end(arglist);
+
+ return result;
+}
+
+size_t PlatformString::length() {
+ return FLength;
+}
+
+char* PlatformString::c_str() {
+ return FData;
+}
+
+char* PlatformString::toMultibyte() {
+ return FData;
+}
+
+wchar_t* PlatformString::toWideString() {
+ WideString result = Platform::MultibyteStringToWideString(FData);
+
+ if (result.data != NULL) {
+ if (FWideTStringToFree != NULL) {
+ delete [] FWideTStringToFree;
+ }
+
+ FWideTStringToFree = result.data;
+ }
+
+ return result.data;
+}
+
+std::wstring PlatformString::toUnicodeString() {
+ std::wstring result;
+ wchar_t* data = toWideString();
+
+ if (FLength != 0 && data != NULL) {
+ // NOTE: Cleanup of result is handled by PlatformString destructor.
+ result = data;
+ }
+
+ return result;
+}
+
+std::string PlatformString::toStdString() {
+ std::string result;
+ char* data = toMultibyte();
+
+ if (FLength > 0 && data != NULL) {
+ result = data;
+ }
+
+ return result;
+}
+
+TCHAR* PlatformString::toPlatformString() {
+#ifdef _UNICODE
+ return toWideString();
+#else
+ return c_str();
+#endif //_UNICODE
+}
+
+TString PlatformString::toString() {
+#ifdef _UNICODE
+ return toUnicodeString();
+#else
+ return toStdString();
+#endif //_UNICODE
+}
+
+PlatformString::operator char* () {
+ return c_str();
+}
+
+PlatformString::operator wchar_t* () {
+ return toWideString();
+}
+
+PlatformString::operator std::wstring () {
+ return toUnicodeString();
+}
+
+char* PlatformString::duplicate(const char* Value) {
+ size_t length = strlen(Value);
+ char* result = new char[length + 1];
+ Platform::CopyString(result, length + 1, Value);
+ return result;
+}
+
+wchar_t* PlatformString::duplicate(const wchar_t* Value) {
+ size_t length = wcslen(Value);
+ wchar_t* result = new wchar_t[length + 1];
+ Platform::CopyString(result, length + 1, Value);
+ return result;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/PlatformString.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef PLATFORMSTRING_H
+#define PLATFORMSTRING_H
+
+
+#include <string>
+#include <list>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "jni.h"
+#include "Platform.h"
+
+
+template <typename T>
+class DynamicBuffer {
+private:
+ T* FData;
+ size_t FSize;
+
+public:
+ DynamicBuffer(size_t Size) {
+ FSize = 0;
+ FData = NULL;
+ Resize(Size);
+ }
+
+ ~DynamicBuffer() {
+ delete[] FData;
+ }
+
+ T* GetData() { return FData; }
+ size_t GetSize() { return FSize; }
+
+ bool Resize(size_t Size) {
+ FSize = Size;
+
+ if (FData != NULL) {
+ delete[] FData;
+ FData = NULL;
+ }
+
+ if (FSize != 0) {
+ FData = new T[FSize];
+ if (FData != NULL) {
+ Zero();
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ void Zero() {
+ memset(FData, 0, FSize * sizeof(T));
+ }
+
+ T& operator[](size_t index) {
+ return FData[index];
+ }
+};
+
+class PlatformString {
+private:
+ char* FData; // Stored as UTF-8
+ size_t FLength;
+ wchar_t* FWideTStringToFree;
+
+ void initialize();
+
+// Prohibit Heap-Based PlatformStrings
+private:
+ static void *operator new(size_t size);
+ static void operator delete(void *ptr);
+
+public:
+ PlatformString(void);
+ PlatformString(const PlatformString &value);
+ PlatformString(const char* value);
+ PlatformString(const wchar_t* value);
+ PlatformString(const std::string &value);
+ PlatformString(const std::wstring &value);
+ PlatformString(size_t Value);
+
+ static TString Format(const TString value, ...);
+
+ ~PlatformString(void);
+
+ size_t length();
+
+ char* c_str();
+ char* toMultibyte();
+ wchar_t* toWideString();
+ std::wstring toUnicodeString();
+ std::string toStdString();
+ TCHAR* toPlatformString();
+ TString toString();
+
+ operator char* ();
+ operator wchar_t* ();
+ operator std::wstring ();
+
+ // Caller must free result using delete[].
+ static char* duplicate(const char* Value);
+
+ // Caller must free result using delete[].
+ static wchar_t* duplicate(const wchar_t* Value);
+};
+
+
+#endif // PLATFORMSTRING_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/Properties.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef PROPERTIES_H
+#define PROPERTIES_H
+
+#include "PlatformDefs.h"
+#include "OrderedMap.h"
+
+//#include <stdio.h>
+//#include <stdlib.h>
+//#include <memory.h>
+//#include <string>
+//#include <map>
+//#include <list>
+//#include <vector>
+//#include <fstream>
+
+//using namespace std;
+
+template <typename ObjectType, typename ValueType,
+ ValueType (ObjectType::*getter)(void),
+ void (ObjectType::*setter)(ValueType)>
+class Property {
+private:
+ ObjectType* FObject;
+
+public:
+ Property() {
+ FObject = NULL;
+ }
+
+ void SetInstance(ObjectType* Value) {
+ FObject = Value;
+ }
+
+ // To set the value using the set method.
+ ValueType operator =(const ValueType& Value) {
+ assert(FObject != NULL);
+ (FObject->*setter)(Value);
+ return Value;
+ }
+
+ // The Property class is treated as the internal type.
+ operator ValueType() {
+ assert(FObject != NULL);
+ return (FObject->*getter)();
+ }
+};
+
+template <typename ObjectType, typename ValueType,
+ ValueType (ObjectType::*getter)(void)>
+class ReadProperty {
+private:
+ ObjectType* FObject;
+
+public:
+ ReadProperty() {
+ FObject = NULL;
+ }
+
+ void SetInstance(ObjectType* Value) {
+ FObject = Value;
+ }
+
+ // The Property class is treated as the internal type.
+ operator ValueType() {
+ assert(FObject != NULL);
+ return (FObject->*getter)();
+ }
+};
+
+template <typename ObjectType, typename ValueType,
+ void (ObjectType::*setter)(ValueType)>
+class WriteProperty {
+private:
+ ObjectType* FObject;
+
+public:
+ WriteProperty() {
+ FObject = NULL;
+ }
+
+ void SetInstance(ObjectType* Value) {
+ FObject = Value;
+ }
+
+ // To set the value using the set method.
+ ValueType operator =(const ValueType& Value) {
+ assert(FObject != NULL);
+ (FObject->*setter)(Value);
+ return Value;
+ }
+};
+
+template <typename ValueType,
+ ValueType (*getter)(void), void (*setter)(ValueType)>
+class StaticProperty {
+public:
+ StaticProperty() {
+ }
+
+ // To set the value using the set method.
+ ValueType operator =(const ValueType& Value) {
+ (*getter)(Value);
+ return Value;
+ }
+
+ // The Property class is treated as the internal type which is the getter.
+ operator ValueType() {
+ return (*setter)();
+ }
+};
+
+template <typename ValueType, ValueType (*getter)(void)>
+class StaticReadProperty {
+public:
+ StaticReadProperty() {
+ }
+
+ // The Property class is treated as the internal type which is the getter.
+ operator ValueType() {
+ return (*getter)();
+ }
+};
+
+template <typename ValueType, void (*setter)(ValueType)>
+class StaticWriteProperty {
+public:
+ StaticWriteProperty() {
+ }
+
+ // To set the value using the set method.
+ ValueType operator =(const ValueType& Value) {
+ (*setter)(Value);
+ return Value;
+ }
+};
+
+class IPropertyContainer {
+public:
+ IPropertyContainer(void) {}
+ virtual ~IPropertyContainer(void) {}
+
+ virtual bool GetValue(const TString Key, TString& Value) = 0;
+ virtual size_t GetCount() = 0;
+};
+
+class ISectionalPropertyContainer {
+public:
+ ISectionalPropertyContainer(void) {}
+ virtual ~ISectionalPropertyContainer(void) {}
+
+ virtual bool GetValue(const TString SectionName,
+ const TString Key, TString& Value) = 0;
+ virtual bool ContainsSection(const TString SectionName) = 0;
+ virtual bool GetSection(const TString SectionName,
+ OrderedMap<TString, TString> &Data) = 0;
+};
+
+#endif // PROPERTIES_H
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/PropertyFile.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "PropertyFile.h"
+
+#include "Helpers.h"
+#include "FilePath.h"
+
+#include <string>
+
+
+PropertyFile::PropertyFile(void) : IPropertyContainer() {
+ FReadOnly = false;
+ FModified = false;
+}
+
+PropertyFile::PropertyFile(const TString FileName) : IPropertyContainer() {
+ FReadOnly = true;
+ FModified = false;
+ LoadFromFile(FileName);
+}
+
+PropertyFile::PropertyFile(OrderedMap<TString, TString> Value) {
+ FData.Append(Value);
+}
+
+PropertyFile::PropertyFile(PropertyFile &Value) {
+ FData = Value.FData;
+ FReadOnly = Value.FReadOnly;
+ FModified = Value.FModified;
+}
+
+PropertyFile::~PropertyFile(void) {
+ FData.Clear();
+}
+
+void PropertyFile::SetModified(bool Value) {
+ FModified = Value;
+}
+
+bool PropertyFile::IsModified() {
+ return FModified;
+}
+
+bool PropertyFile::GetReadOnly() {
+ return FReadOnly;
+}
+
+void PropertyFile::SetReadOnly(bool Value) {
+ FReadOnly = Value;
+}
+
+bool PropertyFile::LoadFromFile(const TString FileName) {
+ bool result = false;
+ Platform& platform = Platform::GetInstance();
+
+ std::list<TString> contents = platform.LoadFromFile(FileName);
+
+ if (contents.empty() == false) {
+ for (std::list<TString>::const_iterator iterator = contents.begin();
+ iterator != contents.end(); iterator++) {
+ TString line = *iterator;
+ TString name;
+ TString value;
+
+ if (Helpers::SplitOptionIntoNameValue(line, name, value) == true) {
+ FData.Append(name, value);
+ }
+ }
+
+ SetModified(false);
+ result = true;
+ }
+
+ return result;
+}
+
+bool PropertyFile::SaveToFile(const TString FileName, bool ownerOnly) {
+ bool result = false;
+
+ if (GetReadOnly() == false && IsModified()) {
+ std::list<TString> contents;
+ std::vector<TString> keys = FData.GetKeys();
+
+ for (size_t index = 0; index < keys.size(); index++) {
+ TString name = keys[index];
+
+ try {
+ TString value;// = FData[index];
+
+ if (FData.GetValue(name, value) == true) {
+ TString line = name + _T('=') + value;
+ contents.push_back(line);
+ }
+ }
+ catch (std::out_of_range &) {
+ }
+ }
+
+ Platform& platform = Platform::GetInstance();
+ platform.SaveToFile(FileName, contents, ownerOnly);
+
+ SetModified(false);
+ result = true;
+ }
+
+ return result;
+}
+
+bool PropertyFile::GetValue(const TString Key, TString& Value) {
+ return FData.GetValue(Key, Value);
+}
+
+bool PropertyFile::SetValue(const TString Key, TString Value) {
+ bool result = false;
+
+ if (GetReadOnly() == false) {
+ FData.SetValue(Key, Value);
+ SetModified(true);
+ result = true;
+ }
+
+ return result;
+}
+
+bool PropertyFile::RemoveKey(const TString Key) {
+ bool result = false;
+
+ if (GetReadOnly() == false) {
+ result = FData.RemoveByKey(Key);
+
+ if (result == true) {
+ SetModified(true);
+ }
+ }
+
+ return result;
+}
+
+size_t PropertyFile::GetCount() {
+ return FData.Count();
+}
+
+OrderedMap<TString, TString> PropertyFile::GetData() {
+ return FData;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/PropertyFile.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef PROPERTYFILE_H
+#define PROPERTYFILE_H
+
+#include "Platform.h"
+#include "Helpers.h"
+
+
+class PropertyFile : public IPropertyContainer {
+private:
+ bool FReadOnly;
+ bool FModified;
+ OrderedMap<TString, TString> FData;
+
+ void SetModified(bool Value);
+
+public:
+ PropertyFile(void);
+ PropertyFile(const TString FileName);
+ PropertyFile(OrderedMap<TString, TString> Value);
+ PropertyFile(PropertyFile &Value);
+ virtual ~PropertyFile(void);
+
+ bool IsModified();
+ bool GetReadOnly();
+ void SetReadOnly(bool Value);
+
+ bool LoadFromFile(const TString FileName);
+ bool SaveToFile(const TString FileName, bool ownerOnly = true);
+
+ bool SetValue(const TString Key, TString Value);
+ bool RemoveKey(const TString Key);
+
+ OrderedMap<TString, TString> GetData();
+
+ // IPropertyContainer
+ virtual bool GetValue(const TString Key, TString& Value);
+ virtual size_t GetCount();
+};
+
+#endif // PROPERTYFILE_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/share/native/libapplauncher/main.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "Platform.h"
+#include "PlatformString.h"
+#include "FilePath.h"
+#include "PropertyFile.h"
+#include "JavaVirtualMachine.h"
+#include "Package.h"
+#include "Macros.h"
+#include "Messages.h"
+
+#include <stdio.h>
+#include <signal.h>
+#include <stdlib.h>
+
+/*
+This is the app launcher program for application packaging on Windows, Mac,
+ and Linux.
+
+Basic approach:
+ - Launcher (jpackageapplauncher) is executable that loads
+ applauncher.dll/libapplauncher.dylib/libapplauncher.so
+ and calls start_launcher below.
+ - Reads app/package.cfg or Info.plist or app/<appname>.cfg for application
+ launch configuration (package.cfg is property file).
+ - Load Java with requested Java settings (bundled client Java if availble,
+ server or installed Java otherwise).
+ - Wait for Java to exit and then exit from Main
+ - To debug application by passing command line argument.
+ - Application folder is added to the library path (so LoadLibrary()) works.
+
+Limitations and future work:
+ - Running Java code in primordial thread may cause problems
+ (example: can not use custom stack size).
+ Solution used by java launcher is to create a new thread to invoke Java.
+ See CR 6316197 for more information.
+*/
+
+extern "C" {
+
+ JNIEXPORT bool start_launcher(int argc, TCHAR* argv[]) {
+ bool result = false;
+ bool parentProcess = true;
+
+ // Platform must be initialize first.
+ Platform& platform = Platform::GetInstance();
+
+ try {
+ for (int index = 0; index < argc; index++) {
+ TString argument = argv[index];
+
+ if (argument == _T("-Xappcds:generatecache")) {
+ platform.SetAppCDSState(cdsGenCache);
+ }
+ else if (argument == _T("-Xappcds:off")) {
+ platform.SetAppCDSState(cdsDisabled);
+ }
+ else if (argument == _T("-Xapp:child")) {
+ parentProcess = false;
+ }
+ }
+
+ // Package must be initialized after Platform is fully initialized.
+ Package& package = Package::GetInstance();
+ Macros::Initialize();
+ package.SetCommandLineArguments(argc, argv);
+
+ switch (platform.GetAppCDSState()) {
+ case cdsDisabled:
+ case cdsUninitialized:
+ case cdsEnabled: {
+ break;
+ }
+
+ case cdsGenCache: {
+ TString cacheDirectory = package.GetAppCDSCacheDirectory();
+
+ if (FilePath::DirectoryExists(cacheDirectory) == false) {
+ FilePath::CreateDirectory(cacheDirectory, true);
+ } else {
+ TString cacheFileName =
+ package.GetAppCDSCacheFileName();
+ if (FilePath::FileExists(cacheFileName) == true) {
+ FilePath::DeleteFile(cacheFileName);
+ }
+ }
+
+ break;
+ }
+
+ case cdsAuto: {
+ TString cacheFileName = package.GetAppCDSCacheFileName();
+
+ if (parentProcess == true &&
+ FilePath::FileExists(cacheFileName) == false) {
+ AutoFreePtr<Process> process = platform.CreateProcess();
+ std::vector<TString> args;
+ args.push_back(_T("-Xappcds:generatecache"));
+ args.push_back(_T("-Xapp:child"));
+ process->Execute(
+ platform.GetModuleFileName(), args, true);
+
+ if (FilePath::FileExists(cacheFileName) == false) {
+ // Cache does not exist after trying to generate it,
+ // so run without cache.
+ platform.SetAppCDSState(cdsDisabled);
+ package.Clear();
+ package.Initialize();
+ }
+ }
+
+ break;
+ }
+ }
+
+ // Validation
+ switch (platform.GetAppCDSState()) {
+ case cdsDisabled:
+ case cdsGenCache: {
+ // Do nothing.
+ break;
+ }
+
+ case cdsEnabled:
+ case cdsAuto: {
+ TString cacheFileName =
+ package.GetAppCDSCacheFileName();
+
+ if (FilePath::FileExists(cacheFileName) == false) {
+ Messages& messages = Messages::GetInstance();
+ TString message = PlatformString::Format(
+ messages.GetMessage(
+ APPCDS_CACHE_FILE_NOT_FOUND),
+ cacheFileName.data());
+ throw Exception(message);
+ }
+ break;
+ }
+
+ case cdsUninitialized: {
+ platform.ShowMessage(_T("Internal Error"));
+ break;
+ }
+ }
+
+ // Run App
+ result = RunVM();
+ } catch (Exception &e) {
+ platform.ShowMessage(e.GetMessage());
+ }
+
+ return result;
+ }
+
+ JNIEXPORT void stop_launcher() {
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/unix/native/libapplauncher/FileAttribute.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+#ifndef FILEATTRIBUTE_H
+#define FILEATTRIBUTE_H
+
+enum FileAttribute {
+ faBlockSpecial,
+ faCharacterSpecial,
+ faFIFOSpecial,
+ faNormal,
+ faDirectory,
+ faSymbolicLink,
+ faSocket,
+
+ // Owner
+ faReadOnly,
+ faWriteOnly,
+ faReadWrite,
+ faExecute,
+
+ // Group
+ faGroupReadOnly,
+ faGroupWriteOnly,
+ faGroupReadWrite,
+ faGroupExecute,
+
+ // Others
+ faOthersReadOnly,
+ faOthersWriteOnly,
+ faOthersReadWrite,
+ faOthersExecute,
+
+ faHidden
+};
+
+#endif // FILEATTRIBUTE_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/unix/native/libapplauncher/FileAttributes.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "FileAttributes.h"
+
+#include <algorithm>
+#include <list>
+#include <sys/stat.h>
+
+FileAttributes::FileAttributes(const TString FileName, bool FollowLink) {
+ FFileName = FileName;
+ FFollowLink = FollowLink;
+ ReadAttributes();
+}
+
+bool FileAttributes::WriteAttributes() {
+ bool result = false;
+
+ mode_t attributes = 0;
+
+ for (std::vector<FileAttribute>::const_iterator iterator =
+ FAttributes.begin();
+ iterator != FAttributes.end(); iterator++) {
+ switch (*iterator) {
+ case faBlockSpecial:
+ {
+ attributes |= S_IFBLK;
+ break;
+ }
+ case faCharacterSpecial:
+ {
+ attributes |= S_IFCHR;
+ break;
+ }
+ case faFIFOSpecial:
+ {
+ attributes |= S_IFIFO;
+ break;
+ }
+ case faNormal:
+ {
+ attributes |= S_IFREG;
+ break;
+ }
+ case faDirectory:
+ {
+ attributes |= S_IFDIR;
+ break;
+ }
+ case faSymbolicLink:
+ {
+ attributes |= S_IFLNK;
+ break;
+ }
+ case faSocket:
+ {
+ attributes |= S_IFSOCK;
+ break;
+ }
+
+ // Owner
+ case faReadOnly:
+ {
+ attributes |= S_IRUSR;
+ break;
+ }
+ case faWriteOnly:
+ {
+ attributes |= S_IWUSR;
+ break;
+ }
+ case faReadWrite:
+ {
+ attributes |= S_IRUSR;
+ attributes |= S_IWUSR;
+ break;
+ }
+ case faExecute:
+ {
+ attributes |= S_IXUSR;
+ break;
+ }
+
+ // Group
+ case faGroupReadOnly:
+ {
+ attributes |= S_IRGRP;
+ break;
+ }
+ case faGroupWriteOnly:
+ {
+ attributes |= S_IWGRP;
+ break;
+ }
+ case faGroupReadWrite:
+ {
+ attributes |= S_IRGRP;
+ attributes |= S_IWGRP;
+ break;
+ }
+ case faGroupExecute:
+ {
+ attributes |= S_IXGRP;
+ break;
+ }
+
+ // Others
+ case faOthersReadOnly:
+ {
+ attributes |= S_IROTH;
+ break;
+ }
+ case faOthersWriteOnly:
+ {
+ attributes |= S_IWOTH;
+ break;
+ }
+ case faOthersReadWrite:
+ {
+ attributes |= S_IROTH;
+ attributes |= S_IWOTH;
+ break;
+ }
+ case faOthersExecute:
+ {
+ attributes |= S_IXOTH;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (chmod(FFileName.data(), attributes) == 0) {
+ result = true;
+ }
+
+ return result;
+}
+
+#define S_ISRUSR(m) (((m) & S_IRWXU) == S_IRUSR)
+#define S_ISWUSR(m) (((m) & S_IRWXU) == S_IWUSR)
+#define S_ISXUSR(m) (((m) & S_IRWXU) == S_IXUSR)
+
+#define S_ISRGRP(m) (((m) & S_IRWXG) == S_IRGRP)
+#define S_ISWGRP(m) (((m) & S_IRWXG) == S_IWGRP)
+#define S_ISXGRP(m) (((m) & S_IRWXG) == S_IXGRP)
+
+#define S_ISROTH(m) (((m) & S_IRWXO) == S_IROTH)
+#define S_ISWOTH(m) (((m) & S_IRWXO) == S_IWOTH)
+#define S_ISXOTH(m) (((m) & S_IRWXO) == S_IXOTH)
+
+bool FileAttributes::ReadAttributes() {
+ bool result = false;
+
+ struct stat status;
+
+ if (stat(StringToFileSystemString(FFileName), &status) == 0) {
+ result = true;
+
+ if (S_ISBLK(status.st_mode) != 0) {
+ FAttributes.push_back(faBlockSpecial);
+ }
+ if (S_ISCHR(status.st_mode) != 0) {
+ FAttributes.push_back(faCharacterSpecial);
+ }
+ if (S_ISFIFO(status.st_mode) != 0) {
+ FAttributes.push_back(faFIFOSpecial);
+ }
+ if (S_ISREG(status.st_mode) != 0) {
+ FAttributes.push_back(faNormal);
+ }
+ if (S_ISDIR(status.st_mode) != 0) {
+ FAttributes.push_back(faDirectory);
+ }
+ if (S_ISLNK(status.st_mode) != 0) {
+ FAttributes.push_back(faSymbolicLink);
+ }
+ if (S_ISSOCK(status.st_mode) != 0) {
+ FAttributes.push_back(faSocket);
+ }
+
+ // Owner
+ if (S_ISRUSR(status.st_mode) != 0) {
+ if (S_ISWUSR(status.st_mode) != 0) {
+ FAttributes.push_back(faReadWrite);
+ } else {
+ FAttributes.push_back(faReadOnly);
+ }
+ } else if (S_ISWUSR(status.st_mode) != 0) {
+ FAttributes.push_back(faWriteOnly);
+ }
+
+ if (S_ISXUSR(status.st_mode) != 0) {
+ FAttributes.push_back(faExecute);
+ }
+
+ // Group
+ if (S_ISRGRP(status.st_mode) != 0) {
+ if (S_ISWGRP(status.st_mode) != 0) {
+ FAttributes.push_back(faGroupReadWrite);
+ } else {
+ FAttributes.push_back(faGroupReadOnly);
+ }
+ } else if (S_ISWGRP(status.st_mode) != 0) {
+ FAttributes.push_back(faGroupWriteOnly);
+ }
+
+ if (S_ISXGRP(status.st_mode) != 0) {
+ FAttributes.push_back(faGroupExecute);
+ }
+
+
+ // Others
+ if (S_ISROTH(status.st_mode) != 0) {
+ if (S_ISWOTH(status.st_mode) != 0) {
+ FAttributes.push_back(faOthersReadWrite);
+ } else {
+ FAttributes.push_back(faOthersReadOnly);
+ }
+ } else if (S_ISWOTH(status.st_mode) != 0) {
+ FAttributes.push_back(faOthersWriteOnly);
+ }
+
+ if (S_ISXOTH(status.st_mode) != 0) {
+ FAttributes.push_back(faOthersExecute);
+ }
+
+ if (FFileName.size() > 0 && FFileName[0] == '.') {
+ FAttributes.push_back(faHidden);
+ }
+ }
+
+ return result;
+}
+
+bool FileAttributes::Valid(const FileAttribute Value) {
+ bool result = false;
+
+ switch (Value) {
+ case faReadWrite:
+ case faWriteOnly:
+ case faExecute:
+
+ case faGroupReadWrite:
+ case faGroupWriteOnly:
+ case faGroupReadOnly:
+ case faGroupExecute:
+
+ case faOthersReadWrite:
+ case faOthersWriteOnly:
+ case faOthersReadOnly:
+ case faOthersExecute:
+
+ case faReadOnly:
+ result = true;
+ break;
+
+ default:
+ break;
+ }
+
+ return result;
+}
+
+void FileAttributes::Append(FileAttribute Value) {
+ if (Valid(Value) == true) {
+ if ((Value == faReadOnly && Contains(faWriteOnly) == true) ||
+ (Value == faWriteOnly && Contains(faReadOnly) == true)) {
+ Value = faReadWrite;
+ }
+
+ FAttributes.push_back(Value);
+ WriteAttributes();
+ }
+}
+
+bool FileAttributes::Contains(FileAttribute Value) {
+ bool result = false;
+
+ std::vector<FileAttribute>::const_iterator iterator =
+ std::find(FAttributes.begin(), FAttributes.end(), Value);
+
+ if (iterator != FAttributes.end()) {
+ result = true;
+ }
+
+ return result;
+}
+
+void FileAttributes::Remove(FileAttribute Value) {
+ if (Valid(Value) == true) {
+ if (Value == faReadOnly && Contains(faReadWrite) == true) {
+ Append(faWriteOnly);
+ Remove(faReadWrite);
+ } else if (Value == faWriteOnly && Contains(faReadWrite) == true) {
+ Append(faReadOnly);
+ Remove(faReadWrite);
+ }
+
+ std::vector<FileAttribute>::iterator iterator =
+ std::find(FAttributes.begin(), FAttributes.end(), Value);
+
+ if (iterator != FAttributes.end()) {
+ FAttributes.erase(iterator);
+ WriteAttributes();
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/unix/native/libapplauncher/FilePath.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "PlatformDefs.h"
+#include "FilePath.h"
+
+#include <algorithm>
+#include <list>
+#include <sys/stat.h>
+
+bool FilePath::FileExists(const TString FileName) {
+ bool result = false;
+ struct stat buf;
+
+ if ((stat(StringToFileSystemString(FileName), &buf) == 0) &&
+ (S_ISREG(buf.st_mode) != 0)) {
+ result = true;
+ }
+
+ return result;
+}
+
+bool FilePath::DirectoryExists(const TString DirectoryName) {
+ bool result = false;
+
+ struct stat buf;
+
+ if ((stat(StringToFileSystemString(DirectoryName), &buf) == 0) &&
+ (S_ISDIR(buf.st_mode) != 0)) {
+ result = true;
+ }
+
+ return result;
+}
+
+bool FilePath::DeleteFile(const TString FileName) {
+ bool result = false;
+
+ if (FileExists(FileName) == true) {
+ if (unlink(StringToFileSystemString(FileName)) == 0) {
+ result = true;
+ }
+ }
+
+ return result;
+}
+
+bool FilePath::DeleteDirectory(const TString DirectoryName) {
+ bool result = false;
+
+ if (DirectoryExists(DirectoryName) == true) {
+ if (unlink(StringToFileSystemString(DirectoryName)) == 0) {
+ result = true;
+ }
+ }
+
+ return result;
+}
+
+TString FilePath::IncludeTrailingSeparator(const TString value) {
+ TString result = value;
+
+ if (value.size() > 0) {
+ TString::iterator i = result.end();
+ i--;
+
+ if (*i != TRAILING_PATHSEPARATOR) {
+ result += TRAILING_PATHSEPARATOR;
+ }
+ }
+
+ return result;
+}
+
+TString FilePath::IncludeTrailingSeparator(const char* value) {
+ TString lvalue = PlatformString(value).toString();
+ return IncludeTrailingSeparator(lvalue);
+}
+
+TString FilePath::IncludeTrailingSeparator(const wchar_t* value) {
+ TString lvalue = PlatformString(value).toString();
+ return IncludeTrailingSeparator(lvalue);
+}
+
+TString FilePath::ExtractFilePath(TString Path) {
+ return dirname(StringToFileSystemString(Path));
+}
+
+TString FilePath::ExtractFileExt(TString Path) {
+ TString result;
+ size_t dot = Path.find_last_of('.');
+
+ if (dot != TString::npos) {
+ result = Path.substr(dot, Path.size() - dot);
+ }
+
+ return result;
+}
+
+TString FilePath::ExtractFileName(TString Path) {
+ return basename(StringToFileSystemString(Path));
+}
+
+TString FilePath::ChangeFileExt(TString Path, TString Extension) {
+ TString result;
+ size_t dot = Path.find_last_of('.');
+
+ if (dot != TString::npos) {
+ result = Path.substr(0, dot) + Extension;
+ }
+
+ if (result.empty() == true) {
+ result = Path;
+ }
+
+ return result;
+}
+
+TString FilePath::FixPathForPlatform(TString Path) {
+ TString result = Path;
+ std::replace(result.begin(), result.end(),
+ BAD_TRAILING_PATHSEPARATOR, TRAILING_PATHSEPARATOR);
+ return result;
+}
+
+TString FilePath::FixPathSeparatorForPlatform(TString Path) {
+ TString result = Path;
+ std::replace(result.begin(), result.end(),
+ BAD_PATH_SEPARATOR, PATH_SEPARATOR);
+ return result;
+}
+
+TString FilePath::PathSeparator() {
+ TString result;
+ result = PATH_SEPARATOR;
+ return result;
+}
+
+bool FilePath::CreateDirectory(TString Path, bool ownerOnly) {
+ bool result = false;
+
+ std::list<TString> paths;
+ TString lpath = Path;
+
+ while (lpath.empty() == false && DirectoryExists(lpath) == false) {
+ paths.push_front(lpath);
+ lpath = ExtractFilePath(lpath);
+ }
+
+ for (std::list<TString>::iterator iterator = paths.begin();
+ iterator != paths.end(); iterator++) {
+ lpath = *iterator;
+
+ mode_t mode = S_IRWXU;
+ if (!ownerOnly) {
+ mode |= S_IRWXG | S_IROTH | S_IXOTH;
+ }
+ if (mkdir(StringToFileSystemString(lpath), mode) == 0) {
+ result = true;
+ } else {
+ result = false;
+ break;
+ }
+ }
+
+ return result;
+}
+
+void FilePath::ChangePermissions(TString FileName, bool ownerOnly) {
+ mode_t mode = S_IRWXU;
+ if (!ownerOnly) {
+ mode |= S_IRWXG | S_IROTH | S_IXOTH;
+ }
+ chmod(FileName.data(), mode);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/unix/native/libapplauncher/PosixPlatform.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "PosixPlatform.h"
+
+#include "PlatformString.h"
+#include "FilePath.h"
+#include "Helpers.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/sysctl.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <limits.h>
+#include <pwd.h>
+#include <iostream>
+#include <algorithm>
+#include <dlfcn.h>
+#include <signal.h>
+
+using namespace std;
+
+PosixPlatform::PosixPlatform(void) {
+}
+
+PosixPlatform::~PosixPlatform(void) {
+}
+
+TString PosixPlatform::GetTempDirectory() {
+ struct passwd* pw = getpwuid(getuid());
+ TString homedir(pw->pw_dir);
+ homedir += getTmpDirString();
+ if (!FilePath::DirectoryExists(homedir)) {
+ if (!FilePath::CreateDirectory(homedir, false)) {
+ homedir.clear();
+ }
+ }
+
+ return homedir;
+}
+
+TString PosixPlatform::fixName(const TString& name) {
+ TString fixedName(name);
+ const TString chars("?:*<>/\\");
+ for (TString::const_iterator it = chars.begin(); it != chars.end(); it++) {
+ fixedName.erase(std::remove(fixedName.begin(),
+ fixedName.end(), *it), fixedName.end());
+ }
+ return fixedName;
+}
+
+MessageResponse PosixPlatform::ShowResponseMessage(TString title,
+ TString description) {
+ MessageResponse result = mrCancel;
+
+ printf("%s %s (Y/N)\n", PlatformString(title).toPlatformString(),
+ PlatformString(description).toPlatformString());
+ fflush(stdout);
+
+ std::string input;
+ std::cin >> input;
+
+ if (input == "Y") {
+ result = mrOK;
+ }
+
+ return result;
+}
+
+Module PosixPlatform::LoadLibrary(TString FileName) {
+ return dlopen(StringToFileSystemString(FileName), RTLD_LAZY);
+}
+
+void PosixPlatform::FreeLibrary(Module AModule) {
+ dlclose(AModule);
+}
+
+Procedure PosixPlatform::GetProcAddress(Module AModule,
+ std::string MethodName) {
+ return dlsym(AModule, PlatformString(MethodName));
+}
+
+Process* PosixPlatform::CreateProcess() {
+ return new PosixProcess();
+}
+
+void PosixPlatform::addPlatformDependencies(JavaLibrary *pJavaLibrary) {
+}
+
+void Platform::CopyString(char *Destination,
+ size_t NumberOfElements, const char *Source) {
+ strncpy(Destination, Source, NumberOfElements);
+
+ if (NumberOfElements > 0) {
+ Destination[NumberOfElements - 1] = '\0';
+ }
+}
+
+void Platform::CopyString(wchar_t *Destination,
+ size_t NumberOfElements, const wchar_t *Source) {
+ wcsncpy(Destination, Source, NumberOfElements);
+
+ if (NumberOfElements > 0) {
+ Destination[NumberOfElements - 1] = '\0';
+ }
+}
+
+// Owner must free the return value.
+
+MultibyteString Platform::WideStringToMultibyteString(
+ const wchar_t* value) {
+ MultibyteString result;
+ size_t count = 0;
+
+ if (value == NULL) {
+ return result;
+ }
+
+ count = wcstombs(NULL, value, 0);
+ if (count > 0) {
+ result.data = new char[count + 1];
+ result.data[count] = '\0';
+ result.length = count;
+ wcstombs(result.data, value, count);
+ }
+
+ return result;
+}
+
+// Owner must free the return value.
+
+WideString Platform::MultibyteStringToWideString(const char* value) {
+ WideString result;
+ size_t count = 0;
+
+ if (value == NULL) {
+ return result;
+ }
+
+ count = mbstowcs(NULL, value, 0);
+ if (count > 0) {
+ result.data = new wchar_t[count + 1];
+ result.data[count] = '\0';
+ result.length = count;
+ mbstowcs(result.data, value, count);
+ }
+
+ return result;
+}
+
+void PosixPlatform::InitStreamLocale(wios *stream) {
+ // Nothing to do for POSIX platforms.
+}
+
+PosixProcess::PosixProcess() : Process() {
+ FChildPID = 0;
+ FRunning = false;
+ FOutputHandle = 0;
+ FInputHandle = 0;
+}
+
+PosixProcess::~PosixProcess() {
+ Terminate();
+}
+
+bool PosixProcess::ReadOutput() {
+ bool result = false;
+
+ if (FOutputHandle != 0 && IsRunning() == true) {
+ char buffer[4096] = {0};
+
+ ssize_t count = read(FOutputHandle, buffer, sizeof (buffer));
+
+ if (count == -1) {
+ if (errno == EINTR) {
+ // continue;
+ } else {
+ perror("read");
+ exit(1);
+ }
+ } else if (count == 0) {
+ // break;
+ } else {
+ std::list<TString> output = Helpers::StringToArray(buffer);
+ FOutput.splice(FOutput.end(), output, output.begin(), output.end());
+ result = true;
+ }
+ }
+
+ return false;
+}
+
+bool PosixProcess::IsRunning() {
+ bool result = false;
+
+ if (kill(FChildPID, 0) == 0) {
+ result = true;
+ }
+
+ return result;
+}
+
+bool PosixProcess::Terminate() {
+ bool result = false;
+
+ if (IsRunning() == true && FRunning == true) {
+ FRunning = false;
+ Cleanup();
+ int status = kill(FChildPID, SIGTERM);
+
+ if (status == 0) {
+ result = true;
+ } else {
+#ifdef DEBUG
+ if (errno == EINVAL) {
+ printf("Kill error: The value of the sig argument is an invalid or unsupported signal number.");
+ } else if (errno == EPERM) {
+ printf("Kill error: The process does not have permission to send the signal to any receiving process.");
+ } else if (errno == ESRCH) {
+ printf("Kill error: No process or process group can be found corresponding to that specified by pid.");
+ }
+#endif // DEBUG
+ if (IsRunning() == true) {
+ status = kill(FChildPID, SIGKILL);
+
+ if (status == 0) {
+ result = true;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+bool PosixProcess::Wait() {
+ bool result = false;
+
+ int status = 0;
+ pid_t wpid = 0;
+
+ wpid = wait(&status);
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ if (errno != EINTR) {
+ status = -1;
+ }
+ }
+
+#ifdef DEBUG
+ if (WIFEXITED(status)) {
+ printf("child exited, status=%d\n", WEXITSTATUS(status));
+ } else if (WIFSIGNALED(status)) {
+ printf("child killed (signal %d)\n", WTERMSIG(status));
+ } else if (WIFSTOPPED(status)) {
+ printf("child stopped (signal %d)\n", WSTOPSIG(status));
+#ifdef WIFCONTINUED // Not all implementations support this
+ } else if (WIFCONTINUED(status)) {
+ printf("child continued\n");
+#endif // WIFCONTINUED
+ } else { // Non-standard case -- may never happen
+ printf("Unexpected status (0x%x)\n", status);
+ }
+#endif // DEBUG
+
+ if (wpid != -1) {
+ result = true;
+ }
+
+ return result;
+}
+
+TProcessID PosixProcess::GetProcessID() {
+ return FChildPID;
+}
+
+void PosixProcess::SetInput(TString Value) {
+ if (FInputHandle != 0) {
+ if (write(FInputHandle, Value.data(), Value.size()) < 0) {
+ throw Exception(_T("Internal Error - write failed"));
+ }
+ }
+}
+
+std::list<TString> PosixProcess::GetOutput() {
+ ReadOutput();
+ return Process::GetOutput();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/unix/native/libapplauncher/PosixPlatform.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef POSIXPLATFORM_H
+#define POSIXPLATFORM_H
+
+#include "Platform.h"
+#include <signal.h>
+
+class PosixPlatform : virtual public Platform {
+protected:
+
+ TString fixName(const TString& name);
+
+ virtual TString getTmpDirString() = 0;
+
+public:
+ PosixPlatform(void);
+ virtual ~PosixPlatform(void);
+
+public:
+ virtual MessageResponse ShowResponseMessage(TString title,
+ TString description);
+
+ virtual Module LoadLibrary(TString FileName);
+ virtual void FreeLibrary(Module AModule);
+ virtual Procedure GetProcAddress(Module AModule, std::string MethodName);
+
+ virtual Process* CreateProcess();
+ virtual TString GetTempDirectory();
+ void InitStreamLocale(wios *stream);
+ void addPlatformDependencies(JavaLibrary *pJavaLibrary);
+};
+
+class PosixProcess : public Process {
+private:
+ pid_t FChildPID;
+ sigset_t saveblock;
+ int FOutputHandle;
+ int FInputHandle;
+ struct sigaction savintr, savequit;
+ bool FRunning;
+
+ void Cleanup();
+ bool ReadOutput();
+
+public:
+ PosixProcess();
+ virtual ~PosixProcess();
+
+ virtual bool IsRunning();
+ virtual bool Terminate();
+ virtual bool Execute(const TString Application,
+ const std::vector<TString> Arguments, bool AWait = false);
+ virtual bool Wait();
+ virtual TProcessID GetProcessID();
+ virtual void SetInput(TString Value);
+ virtual std::list<TString> GetOutput();
+};
+
+#endif // POSIXPLATFORM_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/VersionExtractor.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,91 @@
+/*
+ * 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.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.text.MessageFormat;
+import java.util.ResourceBundle;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class VersionExtractor extends PrintStream {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.WinResources");
+
+ private final String pattern;
+ private String version = null;
+
+ public VersionExtractor(String pattern) {
+ super(new ByteArrayOutputStream());
+
+ this.pattern = pattern;
+ }
+
+ public String getVersion() {
+ if (version == null) {
+ String content
+ = new String(((ByteArrayOutputStream) out).toByteArray());
+ Pattern p = Pattern.compile(pattern);
+ Matcher matcher = p.matcher(content);
+ if (matcher.find()) {
+ version = matcher.group(1);
+ }
+ }
+ return version;
+ }
+
+ public static boolean isLessThan(String version, String compareTo)
+ throws RuntimeException {
+ if (version == null || version.isEmpty()) {
+ throw new RuntimeException(MessageFormat.format(
+ I18N.getString("error.version-compare"),
+ version, compareTo));
+ }
+
+ if (compareTo == null || compareTo.isEmpty()) {
+ throw new RuntimeException(MessageFormat.format(
+ I18N.getString("error.version-compare"),
+ version, compareTo));
+ }
+
+ String [] versionArray = version.trim().split(Pattern.quote("."));
+ String [] compareToArray = compareTo.trim().split(Pattern.quote("."));
+
+ for (int i = 0; i < versionArray.length; i++) {
+ int v1 = Integer.parseInt(versionArray[i]);
+ int v2 = Integer.parseInt(compareToArray[i]);
+ if (v1 < v2) {
+ return true;
+ } else if (v1 > v2) {
+ return false;
+ }
+ }
+
+ return false;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinAppBundler.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2012, 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.File;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import static jdk.jpackage.internal.WindowsBundlerParam.*;
+import static jdk.jpackage.internal.WinMsiBundler.WIN_APP_IMAGE;
+
+public class WinAppBundler extends AbstractImageBundler {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.WinResources");
+
+ static final BundlerParamInfo<File> ICON_ICO =
+ new StandardBundlerParam<>(
+ "icon.ico",
+ File.class,
+ params -> {
+ File f = ICON.fetchFrom(params);
+ if (f != null && !f.getName().toLowerCase().endsWith(".ico")) {
+ Log.error(MessageFormat.format(
+ I18N.getString("message.icon-not-ico"), f));
+ return null;
+ }
+ return f;
+ },
+ (s, p) -> new File(s));
+
+ @Override
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+ try {
+ if (params == null) throw new ConfigException(
+ I18N.getString("error.parameters-null"),
+ I18N.getString("error.parameters-null.advice"));
+
+ return doValidate(params);
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof ConfigException) {
+ throw (ConfigException) re.getCause();
+ } else {
+ throw new ConfigException(re);
+ }
+ }
+ }
+
+ // to be used by chained bundlers, e.g. by EXE bundler to avoid
+ // skipping validation if p.type does not include "image"
+ private boolean doValidate(Map<String, ? super Object> p)
+ throws ConfigException {
+
+ imageBundleValidation(p);
+
+ if (StandardBundlerParam.getPredefinedAppImage(p) != null) {
+ return true;
+ }
+
+ // Make sure that jpackage.exe exists.
+ File tool = new File(
+ System.getProperty("java.home") + "\\bin\\jpackage.exe");
+
+ if (!tool.exists()) {
+ throw new ConfigException(
+ I18N.getString("error.no-windows-resources"),
+ I18N.getString("error.no-windows-resources.advice"));
+ }
+
+ return true;
+ }
+
+ public boolean bundle(Map<String, ? super Object> p, File outputDirectory)
+ throws PackagerException {
+ return doBundle(p, outputDirectory, false) != null;
+ }
+
+ File doBundle(Map<String, ? super Object> p, File outputDirectory,
+ boolean dependentTask) throws PackagerException {
+ if (StandardBundlerParam.isRuntimeInstaller(p)) {
+ return PREDEFINED_RUNTIME_IMAGE.fetchFrom(p);
+ } else {
+ return doAppBundle(p, outputDirectory, dependentTask);
+ }
+ }
+
+ File doAppBundle(Map<String, ? super Object> p, File outputDirectory,
+ boolean dependentTask) throws PackagerException {
+ try {
+ File rootDirectory = createRoot(p, outputDirectory, dependentTask,
+ APP_NAME.fetchFrom(p));
+ AbstractAppImageBuilder appBuilder =
+ new WindowsAppImageBuilder(p, outputDirectory.toPath());
+ if (PREDEFINED_RUNTIME_IMAGE.fetchFrom(p) == null ) {
+ JLinkBundlerHelper.execute(p, appBuilder);
+ } else {
+ StandardBundlerParam.copyPredefinedRuntimeImage(p, appBuilder);
+ }
+ if (!dependentTask) {
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.result-dir"),
+ outputDirectory.getAbsolutePath()));
+ }
+ return rootDirectory;
+ } catch (PackagerException pe) {
+ throw pe;
+ } catch (Exception e) {
+ Log.verbose(e);
+ throw new PackagerException(e);
+ }
+ }
+
+ @Override
+ public String getName() {
+ return I18N.getString("app.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "windows.app";
+ }
+
+ @Override
+ public String getBundleType() {
+ return "IMAGE";
+ }
+
+ @Override
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ return doBundle(params, outputParentDir, false);
+ }
+
+ @Override
+ public boolean supported(boolean platformInstaller) {
+ return true;
+ }
+
+ @Override
+ public boolean isDefault() {
+ return false;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinExeBundler.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2017, 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.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.MessageFormat;
+import java.util.*;
+
+public class WinExeBundler extends AbstractBundler {
+
+ static {
+ System.loadLibrary("jpackage");
+ }
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.WinResources");
+
+ public static final BundlerParamInfo<WinAppBundler> APP_BUNDLER
+ = new WindowsBundlerParam<>(
+ "win.app.bundler",
+ WinAppBundler.class,
+ params -> new WinAppBundler(),
+ null);
+
+ public static final BundlerParamInfo<File> EXE_IMAGE_DIR
+ = new WindowsBundlerParam<>(
+ "win.exe.imageDir",
+ File.class,
+ params -> {
+ File imagesRoot = IMAGES_ROOT.fetchFrom(params);
+ if (!imagesRoot.exists()) {
+ imagesRoot.mkdirs();
+ }
+ return new File(imagesRoot, "win-exe.image");
+ },
+ (s, p) -> null);
+
+ private final static String EXE_WRAPPER_NAME = "msiwrapper.exe";
+
+ @Override
+ public String getName() {
+ return getString("exe.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "exe";
+ }
+
+ @Override
+ public String getBundleType() {
+ return "INSTALLER";
+ }
+
+ @Override
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ return bundle(params, outputParentDir);
+ }
+
+ @Override
+ public boolean supported(boolean platformInstaller) {
+ return WinMsiBundler.isSupported();
+ }
+
+ @Override
+ public boolean isDefault() {
+ return true;
+ }
+
+ @Override
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+ return new WinMsiBundler().validate(params);
+ }
+
+ public File bundle(Map<String, ? super Object> params, File outdir)
+ throws PackagerException {
+
+ IOUtils.writableOutputDir(outdir.toPath());
+
+ File exeImageDir = EXE_IMAGE_DIR.fetchFrom(params);
+
+ // Write msi to temporary directory.
+ File msi = new WinMsiBundler().bundle(params, exeImageDir);
+
+ try {
+ return buildEXE(msi, outdir);
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ private File buildEXE(File msi, File outdir)
+ throws IOException {
+
+ Log.verbose(MessageFormat.format(
+ getString("message.outputting-to-location"),
+ outdir.getAbsolutePath()));
+
+ // 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)) {
+ Files.copy(is, Path.of(exePath));
+ }
+ // Embed msi in msi wrapper exe.
+ embedMSI(exePath, msi.getAbsolutePath());
+
+ Path dstExePath = Paths.get(outdir.getAbsolutePath(),
+ Path.of(exePath).getFileName().toString());
+ Files.deleteIfExists(dstExePath);
+
+ Files.copy(Path.of(exePath), dstExePath);
+
+ Log.verbose(MessageFormat.format(
+ getString("message.output-location"),
+ outdir.getAbsolutePath()));
+
+ return dstExePath.toFile();
+ }
+
+ private static String getString(String key)
+ throws MissingResourceException {
+ return I18N.getString(key);
+ }
+
+ private static native int embedMSI(String exePath, String msiPath);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WinMsiBundler.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,1173 @@
+/*
+ * Copyright (c) 2012, 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.Charset;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.regex.Pattern;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+import static jdk.jpackage.internal.WindowsBundlerParam.*;
+
+/**
+ * WinMsiBundler
+ *
+ * Produces .msi installer from application image. Uses WiX Toolkit to build
+ * .msi installer.
+ * <p>
+ * {@link #execute} method creates a number of source files with the description
+ * of installer to be processed by WiX tools. Generated source files are stored
+ * in "config" subdirectory next to "app" subdirectory in the root work
+ * directory. The following WiX source files are generated:
+ * <ul>
+ * <li>main.wxs. Main source file with the installer description
+ * <li>bundle.wxi. Source file with application and Java run-time directory tree
+ * description. This source file is included from main.wxs
+ * <li>icons.wxi. Source file with the list of icons used by the application.
+ * This source file is included from main.wxs
+ * </ul>
+ * <p>
+ * main.wxs file is a copy of main.wxs resource from
+ * jdk.jpackage.internal.resources package. It is parametrized with the
+ * following WiX variables:
+ * <ul>
+ * <li>JpAppName. Name of the application. Set to the value of --name command
+ * line option
+ * <li>JpAppVersion. Version of the application. Set to the value of
+ * --app-version command line option
+ * <li>JpAppVendor. Vendor of the application. Set to the value of --vendor
+ * command line option
+ * <li>JpAppDescription. Description of the application. Set to the value of
+ * --description command line option
+ * <li>JpProductCode. Set to product code UUID of the application. Random value
+ * generated by jpackage every time {@link #execute} method is called
+ * <li>JpProductUpgradeCode. Set to upgrade code UUID of the application. Random
+ * value generated by jpackage every time {@link #execute} method is called if
+ * --win-upgrade-uuid command line option is not specified. Otherwise this
+ * variable is set to the value of --win-upgrade-uuid command line option
+ * <li>JpAllowDowngrades. Set to "yes" if --win-upgrade-uuid command line option
+ * was specified. Undefined otherwise
+ * <li>JpLicenseRtf. Set to the value of --license-file command line option.
+ * Undefined is --license-file command line option was not specified
+ * <li>JpInstallDirChooser. Set to "yes" if --win-dir-chooser command line
+ * option was specified. Undefined otherwise
+ * <li>JpConfigDir. Absolute path to the directory with generated WiX source
+ * files.
+ * <li>JpIsSystemWide. Set to "yes" if --win-per-user-install command line
+ * option was not specified. Undefined otherwise
+ * <li>JpWixVersion36OrNewer. Set to "yes" if WiX Toolkit v3.6 or newer is used.
+ * Undefined otherwise
+ * </ul>
+ */
+public class WinMsiBundler extends AbstractBundler {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.WinResources");
+
+ public static final BundlerParamInfo<WinAppBundler> APP_BUNDLER =
+ new WindowsBundlerParam<>(
+ "win.app.bundler",
+ WinAppBundler.class,
+ params -> new WinAppBundler(),
+ null);
+
+ public static final BundlerParamInfo<Boolean> CAN_USE_WIX36 =
+ new WindowsBundlerParam<>(
+ "win.msi.canUseWix36",
+ Boolean.class,
+ params -> false,
+ (s, p) -> Boolean.valueOf(s));
+
+ public static final BundlerParamInfo<File> MSI_IMAGE_DIR =
+ new WindowsBundlerParam<>(
+ "win.msi.imageDir",
+ File.class,
+ params -> {
+ File imagesRoot = IMAGES_ROOT.fetchFrom(params);
+ if (!imagesRoot.exists()) imagesRoot.mkdirs();
+ return new File(imagesRoot, "win-msi.image");
+ },
+ (s, p) -> null);
+
+ public static final BundlerParamInfo<File> WIN_APP_IMAGE =
+ new WindowsBundlerParam<>(
+ "win.app.image",
+ File.class,
+ null,
+ (s, p) -> null);
+
+ public static final StandardBundlerParam<Boolean> MSI_SYSTEM_WIDE =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.WIN_PER_USER_INSTALLATION.getId(),
+ Boolean.class,
+ params -> true, // MSIs default to system wide
+ // valueOf(null) is false,
+ // and we actually do want null
+ (s, p) -> (s == null || "null".equalsIgnoreCase(s))? null
+ : Boolean.valueOf(s)
+ );
+
+
+ public static final StandardBundlerParam<String> PRODUCT_VERSION =
+ new StandardBundlerParam<>(
+ "win.msi.productVersion",
+ String.class,
+ VERSION::fetchFrom,
+ (s, p) -> s
+ );
+
+ public static final BundlerParamInfo<UUID> UPGRADE_UUID =
+ new WindowsBundlerParam<>(
+ Arguments.CLIOptions.WIN_UPGRADE_UUID.getId(),
+ UUID.class,
+ params -> UUID.randomUUID(),
+ (s, p) -> UUID.fromString(s));
+
+ private static final String TOOL_CANDLE = "candle.exe";
+ private static final String TOOL_LIGHT = "light.exe";
+ // autodetect just v3.7, v3.8, 3.9, 3.10 and 3.11
+ private static final String AUTODETECT_DIRS =
+ ";C:\\Program Files (x86)\\WiX Toolset v3.11\\bin;"
+ + "C:\\Program Files\\WiX Toolset v3.11\\bin;"
+ + "C:\\Program Files (x86)\\WiX Toolset v3.10\\bin;"
+ + "C:\\Program Files\\WiX Toolset v3.10\\bin;"
+ + "C:\\Program Files (x86)\\WiX Toolset v3.9\\bin;"
+ + "C:\\Program Files\\WiX Toolset v3.9\\bin;"
+ + "C:\\Program Files (x86)\\WiX Toolset v3.8\\bin;"
+ + "C:\\Program Files\\WiX Toolset v3.8\\bin;"
+ + "C:\\Program Files (x86)\\WiX Toolset v3.7\\bin;"
+ + "C:\\Program Files\\WiX Toolset v3.7\\bin";
+
+ private static String getCandlePath() {
+ for (String dirString : (System.getenv("PATH")
+ + AUTODETECT_DIRS).split(";")) {
+ File f = new File(dirString.replace("\"", ""), TOOL_CANDLE);
+ if (f.isFile()) {
+ return f.toString();
+ }
+ }
+ return null;
+ }
+
+ private static String getLightPath() {
+ for (String dirString : (System.getenv("PATH")
+ + AUTODETECT_DIRS).split(";")) {
+ File f = new File(dirString.replace("\"", ""), TOOL_LIGHT);
+ if (f.isFile()) {
+ return f.toString();
+ }
+ }
+ return null;
+ }
+
+
+ public static final StandardBundlerParam<Boolean> MENU_HINT =
+ new WindowsBundlerParam<>(
+ Arguments.CLIOptions.WIN_MENU_HINT.getId(),
+ Boolean.class,
+ params -> false,
+ // valueOf(null) is false,
+ // and we actually do want null in some cases
+ (s, p) -> (s == null ||
+ "null".equalsIgnoreCase(s))? true : Boolean.valueOf(s)
+ );
+
+ public static final StandardBundlerParam<Boolean> SHORTCUT_HINT =
+ new WindowsBundlerParam<>(
+ Arguments.CLIOptions.WIN_SHORTCUT_HINT.getId(),
+ Boolean.class,
+ params -> false,
+ // valueOf(null) is false,
+ // and we actually do want null in some cases
+ (s, p) -> (s == null ||
+ "null".equalsIgnoreCase(s))? false : Boolean.valueOf(s)
+ );
+
+ @Override
+ public String getName() {
+ return I18N.getString("msi.bundler.name");
+ }
+
+ @Override
+ public String getID() {
+ return "msi";
+ }
+
+ @Override
+ public String getBundleType() {
+ return "INSTALLER";
+ }
+
+ @Override
+ public File execute(Map<String, ? super Object> params,
+ File outputParentDir) throws PackagerException {
+ return bundle(params, outputParentDir);
+ }
+
+ @Override
+ public boolean supported(boolean platformInstaller) {
+ return isSupported();
+ }
+
+ @Override
+ public boolean isDefault() {
+ return false;
+ }
+
+ public static boolean isSupported() {
+ try {
+ validateWixTools();
+ return true;
+ } catch (ConfigException ce) {
+ Log.error(ce.getMessage());
+ if (ce.getAdvice() != null) {
+ Log.error(ce.getAdvice());
+ }
+ } catch (Exception e) {
+ Log.error(e.getMessage());
+ }
+ return false;
+ }
+
+ private static String findToolVersion(String toolName) {
+ try {
+ if (toolName == null || "".equals(toolName)) return null;
+
+ ProcessBuilder pb = new ProcessBuilder(
+ toolName,
+ "/?");
+ VersionExtractor ve = new VersionExtractor("version (\\d+.\\d+)");
+ // not interested in the output
+ IOUtils.exec(pb, true, ve);
+ String version = ve.getVersion();
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.tool-version"),
+ toolName, version));
+ return version;
+ } catch (Exception e) {
+ Log.verbose(e);
+ return null;
+ }
+ }
+
+ public static void validateWixTools() throws ConfigException{
+ String candleVersion = findToolVersion(getCandlePath());
+ String lightVersion = findToolVersion(getLightPath());
+
+ // WiX 3.0+ is required
+ String minVersion = "3.0";
+ if (candleVersion == null || lightVersion == null) {
+ throw new ConfigException(
+ I18N.getString("error.no-wix-tools"),
+ I18N.getString("error.no-wix-tools.advice"));
+ }
+
+ if (VersionExtractor.isLessThan(candleVersion, minVersion)) {
+ throw new ConfigException(
+ MessageFormat.format(
+ I18N.getString("message.wrong-tool-version"),
+ TOOL_CANDLE, candleVersion, minVersion),
+ I18N.getString("error.no-wix-tools.advice"));
+ }
+ if (VersionExtractor.isLessThan(lightVersion, minVersion)) {
+ throw new ConfigException(
+ MessageFormat.format(
+ I18N.getString("message.wrong-tool-version"),
+ TOOL_LIGHT, lightVersion, minVersion),
+ I18N.getString("error.no-wix-tools.advice"));
+ }
+ }
+
+ @Override
+ public boolean validate(Map<String, ? super Object> params)
+ throws ConfigException {
+ try {
+ if (params == null) throw new ConfigException(
+ I18N.getString("error.parameters-null"),
+ I18N.getString("error.parameters-null.advice"));
+
+ // run basic validation to ensure requirements are met
+ // we are not interested in return code, only possible exception
+ validateWixTools();
+
+ String lightVersion = findToolVersion(getLightPath());
+ if (!VersionExtractor.isLessThan(lightVersion, "3.6")) {
+ Log.verbose(I18N.getString("message.use-wix36-features"));
+ params.put(CAN_USE_WIX36.getID(), Boolean.TRUE);
+ }
+
+ /********* validate bundle parameters *************/
+
+ String version = PRODUCT_VERSION.fetchFrom(params);
+ if (!isVersionStringValid(version)) {
+ throw new ConfigException(
+ MessageFormat.format(I18N.getString(
+ "error.version-string-wrong-format"), version),
+ MessageFormat.format(I18N.getString(
+ "error.version-string-wrong-format.advice"),
+ PRODUCT_VERSION.getID()));
+ }
+
+ // only one mime type per association, at least one file extension
+ List<Map<String, ? super Object>> associations =
+ FILE_ASSOCIATIONS.fetchFrom(params);
+ if (associations != null) {
+ for (int i = 0; i < associations.size(); i++) {
+ Map<String, ? super Object> assoc = associations.get(i);
+ List<String> mimes = FA_CONTENT_TYPE.fetchFrom(assoc);
+ if (mimes.size() > 1) {
+ throw new ConfigException(MessageFormat.format(
+ I18N.getString("error.too-many-content-"
+ + "types-for-file-association"), i),
+ I18N.getString("error.too-many-content-"
+ + "types-for-file-association.advice"));
+ }
+ }
+ }
+
+ return true;
+ } catch (RuntimeException re) {
+ if (re.getCause() instanceof ConfigException) {
+ throw (ConfigException) re.getCause();
+ } else {
+ throw new ConfigException(re);
+ }
+ }
+ }
+
+ // http://msdn.microsoft.com/en-us/library/aa370859%28v=VS.85%29.aspx
+ // The format of the string is as follows:
+ // major.minor.build
+ // The first field is the major version and has a maximum value of 255.
+ // The second field is the minor version and has a maximum value of 255.
+ // The third field is called the build version or the update version and
+ // has a maximum value of 65,535.
+ static boolean isVersionStringValid(String v) {
+ if (v == null) {
+ return true;
+ }
+
+ String p[] = v.split("\\.");
+ if (p.length > 3) {
+ Log.verbose(I18N.getString(
+ "message.version-string-too-many-components"));
+ return false;
+ }
+
+ try {
+ int val = Integer.parseInt(p[0]);
+ if (val < 0 || val > 255) {
+ Log.verbose(I18N.getString(
+ "error.version-string-major-out-of-range"));
+ return false;
+ }
+ if (p.length > 1) {
+ val = Integer.parseInt(p[1]);
+ if (val < 0 || val > 255) {
+ Log.verbose(I18N.getString(
+ "error.version-string-minor-out-of-range"));
+ return false;
+ }
+ }
+ if (p.length > 2) {
+ val = Integer.parseInt(p[2]);
+ if (val < 0 || val > 65535) {
+ Log.verbose(I18N.getString(
+ "error.version-string-build-out-of-range"));
+ return false;
+ }
+ }
+ } catch (NumberFormatException ne) {
+ Log.verbose(I18N.getString("error.version-string-part-not-number"));
+ Log.verbose(ne);
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean prepareProto(Map<String, ? super Object> params)
+ throws PackagerException, IOException {
+ File appImage = StandardBundlerParam.getPredefinedAppImage(params);
+ File appDir = null;
+
+ // we either have an application image or need to build one
+ if (appImage != null) {
+ appDir = new File(MSI_IMAGE_DIR.fetchFrom(params),
+ APP_NAME.fetchFrom(params));
+ // copy everything from appImage dir into appDir/name
+ IOUtils.copyRecursive(appImage.toPath(), appDir.toPath());
+ } else {
+ appDir = APP_BUNDLER.fetchFrom(params).doBundle(params,
+ MSI_IMAGE_DIR.fetchFrom(params), true);
+ }
+
+ params.put(WIN_APP_IMAGE.getID(), appDir);
+
+ String licenseFile = LICENSE_FILE.fetchFrom(params);
+ if (licenseFile != null) {
+ // need to copy license file to the working directory
+ // and convert to rtf if needed
+ File lfile = new File(licenseFile);
+ File destFile = new File(CONFIG_ROOT.fetchFrom(params),
+ lfile.getName());
+
+ IOUtils.copyFile(lfile, destFile);
+ destFile.setWritable(true);
+ ensureByMutationFileIsRTF(destFile);
+ }
+
+ // copy file association icons
+ List<Map<String, ? super Object>> fileAssociations =
+ FILE_ASSOCIATIONS.fetchFrom(params);
+ for (Map<String, ? super Object> fa : fileAssociations) {
+ File icon = FA_ICON.fetchFrom(fa);
+ if (icon == null) {
+ continue;
+ }
+
+ File faIconFile = new File(appDir, icon.getName());
+
+ if (icon.exists()) {
+ try {
+ IOUtils.copyFile(icon, faIconFile);
+ } catch (IOException e) {
+ Log.verbose(e);
+ }
+ }
+ }
+
+ return appDir != null;
+ }
+
+ public File bundle(Map<String, ? super Object> params, File outdir)
+ throws PackagerException {
+
+ IOUtils.writableOutputDir(outdir.toPath());
+
+ // validate we have valid tools before continuing
+ String light = getLightPath();
+ String candle = getCandlePath();
+ if (light == null || !new File(light).isFile() ||
+ candle == null || !new File(candle).isFile()) {
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.light-file-string"), light));
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.candle-file-string"), candle));
+ throw new PackagerException("error.no-wix-tools");
+ }
+
+ Map<String, String> wixVars = null;
+
+ File imageDir = MSI_IMAGE_DIR.fetchFrom(params);
+ try {
+ imageDir.mkdirs();
+
+ prepareBasicProjectConfig(params);
+ if (prepareProto(params)) {
+ wixVars = prepareWiXConfig(params);
+
+ File configScriptSrc = getConfig_Script(params);
+ if (configScriptSrc.exists()) {
+ // we need to be running post script in the image folder
+
+ // NOTE: Would it be better to generate it to the image
+ // folder and save only if "verbose" is requested?
+
+ // for now we replicate it
+ File configScript =
+ new File(imageDir, configScriptSrc.getName());
+ IOUtils.copyFile(configScriptSrc, configScript);
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.running-wsh-script"),
+ configScript.getAbsolutePath()));
+ IOUtils.run("wscript", configScript);
+ }
+ return buildMSI(params, wixVars, outdir);
+ }
+ return null;
+ } catch (IOException ex) {
+ Log.verbose(ex);
+ throw new PackagerException(ex);
+ }
+ }
+
+ // name of post-image script
+ private File getConfig_Script(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + "-post-image.wsf");
+ }
+
+ 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));
+ }
+
+ private static String relativePath(File basedir, File file) {
+ return file.getAbsolutePath().substring(
+ basedir.getAbsolutePath().length() + 1);
+ }
+
+ private AppImageFile appImageFile = null;
+ private String[] getLaunchers( Map<String, ? super Object> params) {
+ try {
+ ArrayList<String> launchers = new ArrayList<String>();
+ if (appImageFile == null) {
+ appImageFile = AppImageFile.load(
+ WIN_APP_IMAGE.fetchFrom(params).toPath());
+ }
+ launchers.add(appImageFile.getLauncherName());
+ launchers.addAll(appImageFile.getAddLauncherNames());
+ return launchers.toArray(new String[0]);
+ } catch (IOException ioe) {
+ Log.verbose(ioe.getMessage());
+ }
+ String [] launcherNames = new String [1];
+ launcherNames[0] = APP_NAME.fetchFrom(params);
+ return launcherNames;
+ }
+
+ private void prepareIconsFile(
+ Map<String, ? super Object> params) throws IOException {
+
+ File imageRootDir = WIN_APP_IMAGE.fetchFrom(params);
+
+ List<Map<String, ? super Object>> addLaunchers =
+ ADD_LAUNCHERS.fetchFrom(params);
+
+ XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
+ try (Writer w = new BufferedWriter(new FileWriter(new File(
+ CONFIG_ROOT.fetchFrom(params), "icons.wxi")))) {
+ XMLStreamWriter xml = xmlFactory.createXMLStreamWriter(w);
+
+ xml.writeStartDocument();
+ xml.writeStartElement("Include");
+
+ String[] launcherNames = getLaunchers(params);
+
+ File[] icons = new File[launcherNames.length];
+ for (int i=0; i<launcherNames.length; i++) {
+ icons[i] = new File(imageRootDir, launcherNames[i] + ".ico");
+ }
+
+ for (int i = 0; i < icons.length; i++) {
+ if (icons[i].exists()) {
+ String iconPath = icons[i].getAbsolutePath();
+
+ if (MENU_HINT.fetchFrom(params)) {
+ xml.writeStartElement("Icon");
+ xml.writeAttribute("Id", "StartMenuIcon.exe" + i);
+ xml.writeAttribute("SourceFile", iconPath);
+ xml.writeEndElement();
+ }
+ if (SHORTCUT_HINT.fetchFrom(params)) {
+ xml.writeStartElement("Icon");
+ xml.writeAttribute("Id", "DesktopIcon.exe" + i);
+ xml.writeAttribute("SourceFile", iconPath);
+ xml.writeEndElement();
+ }
+ }
+ }
+
+ xml.writeEndElement();
+ xml.writeEndDocument();
+ xml.flush();
+ xml.close();
+ } catch (XMLStreamException ex) {
+ Log.verbose(ex);
+ throw new IOException(ex);
+ }
+ }
+
+ Map<String, String> prepareMainProjectFile(
+ Map<String, ? super Object> params) throws IOException {
+ Map<String, String> data = new HashMap<>();
+
+ UUID productGUID = UUID.randomUUID();
+
+ Log.verbose(MessageFormat.format(
+ I18N.getString("message.generated-product-guid"),
+ productGUID.toString()));
+
+ // we use random GUID for product itself but
+ // user provided for upgrade guid
+ // Upgrade guid is important to decide whether it is an upgrade of
+ // installed app. I.e. we need it to be the same for
+ // 2 different versions of app if possible
+ data.put("JpProductCode", productGUID.toString());
+ data.put("JpProductUpgradeCode",
+ UPGRADE_UUID.fetchFrom(params).toString());
+
+ if (!UPGRADE_UUID.getIsDefaultValue()) {
+ data.put("JpAllowDowngrades", "yes");
+ }
+
+ if (CAN_USE_WIX36.fetchFrom(params)) {
+ data.put("JpWixVersion36OrNewer", "yes");
+ }
+
+ data.put("JpAppName", APP_NAME.fetchFrom(params));
+ data.put("JpAppDescription", DESCRIPTION.fetchFrom(params));
+ data.put("JpAppVendor", VENDOR.fetchFrom(params));
+ data.put("JpAppVersion", PRODUCT_VERSION.fetchFrom(params));
+
+ data.put("JpConfigDir",
+ CONFIG_ROOT.fetchFrom(params).getAbsolutePath());
+
+ File imageRootDir = WIN_APP_IMAGE.fetchFrom(params);
+
+ if (MSI_SYSTEM_WIDE.fetchFrom(params)) {
+ data.put("JpIsSystemWide", "yes");
+ }
+
+ String licenseFile = LICENSE_FILE.fetchFrom(params);
+ if (licenseFile != null) {
+ String lname = new File(licenseFile).getName();
+ File destFile = new File(CONFIG_ROOT.fetchFrom(params), lname);
+ data.put("JpLicenseRtf", destFile.getAbsolutePath());
+ }
+
+ // Copy CA dll to include with installer
+ if (INSTALLDIR_CHOOSER.fetchFrom(params)) {
+ data.put("JpInstallDirChooser", "yes");
+ String fname = "wixhelper.dll";
+ try (InputStream is = getResourceAsStream(fname)) {
+ Files.copy(is, Paths.get(
+ CONFIG_ROOT.fetchFrom(params).getAbsolutePath(),
+ fname));
+ }
+ }
+
+ // Copy l10n files.
+ for (String loc : Arrays.asList("en", "ja", "zh_CN")) {
+ String fname = "MsiInstallerStrings_" + loc + ".wxl";
+ try (InputStream is = getResourceAsStream(fname)) {
+ Files.copy(is, Paths.get(
+ CONFIG_ROOT.fetchFrom(params).getAbsolutePath(),
+ fname));
+ }
+ }
+
+ try (InputStream is = getResourceAsStream("main.wxs")) {
+ Files.copy(is, Paths.get(
+ getConfig_ProjectFile(params).getAbsolutePath()));
+ }
+
+ return data;
+ }
+ private int id;
+ private int compId;
+ private final static String LAUNCHER_ID = "LauncherId";
+
+ private void walkFileTree(Map<String, ? super Object> params,
+ File root, PrintStream out, String prefix) {
+ List<File> dirs = new ArrayList<>();
+ List<File> files = new ArrayList<>();
+
+ if (!root.isDirectory()) {
+ throw new RuntimeException(
+ MessageFormat.format(
+ I18N.getString("error.cannot-walk-directory"),
+ root.getAbsolutePath()));
+ }
+
+ // sort to files and dirs
+ File[] children = root.listFiles();
+ if (children != null) {
+ for (File f : children) {
+ if (f.isDirectory()) {
+ dirs.add(f);
+ } else {
+ files.add(f);
+ }
+ }
+ }
+
+ // have files => need to output component
+ out.println(prefix + " <Component Id=\"comp" + (compId++)
+ + "\" DiskId=\"1\""
+ + " Guid=\"" + UUID.randomUUID().toString() + "\""
+ + " Win64=\"yes\""
+ + ">");
+ out.println(prefix + " <CreateFolder/>");
+ out.println(prefix + " <RemoveFolder Id=\"RemoveDir"
+ + (id++) + "\" On=\"uninstall\" />");
+
+
+ File imageRootDir = WIN_APP_IMAGE.fetchFrom(params);
+
+ // Find out if we need to use registry. We need it if
+ // - we doing user level install as file can not serve as KeyPath
+ // - if we adding shortcut in this component
+ boolean menuShortcut = MENU_HINT.fetchFrom(params);
+ boolean desktopShortcut = SHORTCUT_HINT.fetchFrom(params);
+ boolean needRegistryKey = !MSI_SYSTEM_WIDE.fetchFrom(params) ||
+ menuShortcut || desktopShortcut;
+
+ if (needRegistryKey) {
+ // has to be under HKCU to make WiX happy
+ out.println(prefix + " <RegistryKey Root=\"HKCU\" "
+ + " Key=\"Software\\" + VENDOR.fetchFrom(params) + "\\"
+ + APP_NAME.fetchFrom(params) + "\""
+ + (CAN_USE_WIX36.fetchFrom(params) ?
+ ">" : " Action=\"createAndRemoveOnUninstall\">"));
+ out.println(prefix
+ + " <RegistryValue Name=\"Version\" Value=\""
+ + VERSION.fetchFrom(params)
+ + "\" Type=\"string\" KeyPath=\"yes\"/>");
+ out.println(prefix + " </RegistryKey>");
+ }
+
+ String[] launcherNames = getLaunchers(params);
+
+ File[] launcherFiles = new File[launcherNames.length];
+ for (int i=0; i<launcherNames.length; i++) {
+ launcherFiles[i] =
+ new File(imageRootDir, launcherNames[i] + ".exe");
+ }
+ Map<String, String> idToFileMap = new TreeMap<>();
+ boolean launcherSet = false;
+
+ for (File f : files) {
+ boolean isMainLauncher =
+ launcherFiles.length > 0 && f.equals(launcherFiles[0]);
+
+ launcherSet = launcherSet || isMainLauncher;
+
+ String thisFileId = isMainLauncher ? LAUNCHER_ID : ("FileId" + (id++));
+ idToFileMap.put(f.getName(), thisFileId);
+
+ out.println(prefix + " <File Id=\"" +
+ thisFileId + "\""
+ + " Name=\"" + f.getName() + "\" "
+ + " Source=\"" + relativePath(imageRootDir, f) + "\""
+ + " ProcessorArchitecture=\"x64\"" + ">");
+ if (isMainLauncher && desktopShortcut) {
+ out.println(prefix
+ + " <Shortcut Id=\"desktopShortcut\" Directory="
+ + "\"DesktopFolder\""
+ + " Name=\"" + launcherNames[0]
+ + "\" WorkingDirectory=\"INSTALLDIR\""
+ + " Advertise=\"no\" Icon=\"DesktopIcon.exe0\""
+ + " IconIndex=\"0\" />");
+ }
+ if (isMainLauncher && menuShortcut) {
+ out.println(prefix
+ + " <Shortcut Id=\"ExeShortcut\" Directory="
+ + "\"ProgramMenuDir\""
+ + " Name=\"" + launcherNames[0]
+ + "\" Advertise=\"no\" Icon=\"StartMenuIcon.exe0\""
+ + " IconIndex=\"0\" />");
+ }
+
+ // any additional launchers
+ for (int index = 1; index < launcherNames.length; index++ ) {
+
+ if (f.equals(launcherFiles[index])) {
+ if (desktopShortcut) {
+ out.println(prefix
+ + " <Shortcut Id=\"desktopShortcut"
+ + index + "\" Directory=\"DesktopFolder\""
+ + " Name=\"" + launcherNames[index]
+ + "\" WorkingDirectory=\"INSTALLDIR\""
+ + " Advertise=\"no\" Icon=\"DesktopIcon.exe"
+ + index + "\""
+ + " IconIndex=\"0\" />");
+ }
+ if (menuShortcut) {
+ out.println(prefix
+ + " <Shortcut Id=\"ExeShortcut"
+ + index + "\" Directory=\"ProgramMenuDir\""
+ + " Name=\"" + launcherNames[index]
+ + "\" Advertise=\"no\" Icon=\"StartMenuIcon.exe"
+ + index + "\""
+ + " IconIndex=\"0\" />");
+ }
+ }
+ }
+ out.println(prefix + " </File>");
+ }
+
+ if (launcherSet) {
+ List<Map<String, ? super Object>> fileAssociations =
+ FILE_ASSOCIATIONS.fetchFrom(params);
+ Set<String> defaultedMimes = new TreeSet<>();
+ for (Map<String, ? super Object> fa : fileAssociations) {
+ String description = FA_DESCRIPTION.fetchFrom(fa);
+ List<String> extensions = FA_EXTENSIONS.fetchFrom(fa);
+ List<String> mimeTypes = FA_CONTENT_TYPE.fetchFrom(fa);
+ File icon = FA_ICON.fetchFrom(fa);
+
+ String mime = (mimeTypes == null ||
+ mimeTypes.isEmpty()) ? null : mimeTypes.get(0);
+
+ String entryName = APP_REGISTRY_NAME.fetchFrom(params) + "File";
+
+ if (extensions == null) {
+ Log.verbose(I18N.getString(
+ "message.creating-association-with-null-extension"));
+
+ out.print(prefix + " <ProgId Id='" + entryName
+ + "' Description='" + description + "'");
+ if (icon != null && icon.exists()) {
+ out.print(" Icon='" + idToFileMap.get(icon.getName())
+ + "' IconIndex='0'");
+ }
+ out.println(" />");
+ } else {
+ for (String ext : extensions) {
+
+ entryName = ext.toUpperCase() + "File";
+
+ out.print(prefix + " <ProgId Id='" + entryName
+ + "' Description='" + description + "'");
+ if (icon != null && icon.exists()) {
+ out.print(" Icon='"
+ + idToFileMap.get(icon.getName())
+ + "' IconIndex='0'");
+ }
+ out.println(">");
+
+ out.print(prefix + " <Extension Id='"
+ + ext + "' Advertise='no'");
+ if (mime == null) {
+ out.println(">");
+ } else {
+ out.println(" ContentType='" + mime + "'>");
+ if (!defaultedMimes.contains(mime)) {
+ out.println(prefix
+ + " <MIME ContentType='"
+ + mime + "' Default='yes' />");
+ defaultedMimes.add(mime);
+ }
+ }
+ out.println(prefix
+ + " <Verb Id='open' Command='Open' "
+ + "TargetFile='" + LAUNCHER_ID
+ + "' Argument='\"%1\"' />");
+ out.println(prefix + " </Extension>");
+ out.println(prefix + " </ProgId>");
+ }
+ }
+ }
+ }
+
+ out.println(prefix + " </Component>");
+
+ for (File d : dirs) {
+ out.println(prefix + " <Directory Id=\"dirid" + (id++)
+ + "\" Name=\"" + d.getName() + "\">");
+ walkFileTree(params, d, out, prefix + " ");
+ out.println(prefix + " </Directory>");
+ }
+ }
+
+ void prepareContentList(Map<String, ? super Object> params)
+ throws FileNotFoundException {
+ File f = new File(
+ CONFIG_ROOT.fetchFrom(params), MSI_PROJECT_CONTENT_FILE);
+
+ try (PrintStream out = new PrintStream(f)) {
+
+ // opening
+ out.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
+ out.println("<Include>");
+
+ out.println(" <Directory Id=\"TARGETDIR\" Name=\"SourceDir\">");
+ if (MSI_SYSTEM_WIDE.fetchFrom(params)) {
+ // install to programfiles
+ out.println(" <Directory Id=\"ProgramFiles64Folder\" "
+ + "Name=\"PFiles\">");
+ } else {
+ // install to user folder
+ out.println(
+ " <Directory Name=\"AppData\" Id=\"LocalAppDataFolder\">");
+ }
+
+ // reset counters
+ compId = 0;
+ id = 0;
+
+ // We should get valid folder or subfolders
+ String installDir = WINDOWS_INSTALL_DIR.fetchFrom(params);
+ String [] installDirs = installDir.split(Pattern.quote("\\"));
+ for (int i = 0; i < (installDirs.length - 1); i++) {
+ out.println(" <Directory Id=\"SUBDIR" + i + "\" Name=\""
+ + installDirs[i] + "\">");
+ if (!MSI_SYSTEM_WIDE.fetchFrom(params)) {
+ out.println(" <Component Id=\"comp" + (compId++)
+ + "\" DiskId=\"1\""
+ + " Guid=\"" + UUID.randomUUID().toString() + "\""
+ + " Win64=\"yes\""
+ + ">");
+ out.println("<CreateFolder/>");
+ // has to be under HKCU to make WiX happy
+ out.println(" <RegistryKey Root=\"HKCU\" "
+ + " Key=\"Software\\" + VENDOR.fetchFrom(params) + "\\"
+ + APP_NAME.fetchFrom(params) + "\""
+ + (CAN_USE_WIX36.fetchFrom(params) ?
+ ">" : " Action=\"createAndRemoveOnUninstall\">"));
+ out.println(" <RegistryValue Name=\"Version\" Value=\""
+ + VERSION.fetchFrom(params)
+ + "\" Type=\"string\" KeyPath=\"yes\"/>");
+ out.println(" </RegistryKey>");
+ out.println(" <RemoveFolder Id=\"RemoveDir"
+ + (id++) + "\" Directory=\"SUBDIR" + i
+ + "\" On=\"uninstall\" />");
+ out.println("</Component>");
+ }
+ }
+
+ out.println(" <Directory Id=\"APPLICATIONFOLDER\" Name=\""
+ + installDirs[installDirs.length - 1] + "\">");
+
+ // dynamic part
+ walkFileTree(params, WIN_APP_IMAGE.fetchFrom(params), out, " ");
+
+ // closing
+ for (int i = 0; i < installDirs.length; i++) {
+ out.println(" </Directory>");
+ }
+ out.println(" </Directory>");
+
+ // for shortcuts
+ if (SHORTCUT_HINT.fetchFrom(params)) {
+ out.println(" <Directory Id=\"DesktopFolder\" />");
+ }
+ if (MENU_HINT.fetchFrom(params)) {
+ out.println(" <Directory Id=\"ProgramMenuFolder\">");
+ out.println(" <Directory Id=\"ProgramMenuDir\" Name=\""
+ + MENU_GROUP.fetchFrom(params) + "\">");
+ out.println(" <Component Id=\"comp" + (compId++) + "\""
+ + " Guid=\"" + UUID.randomUUID().toString() + "\""
+ + " Win64=\"yes\""
+ + ">");
+ out.println(" <RemoveFolder Id=\"ProgramMenuDir\" "
+ + "On=\"uninstall\" />");
+ // This has to be under HKCU to make WiX happy.
+ // There are numberous discussions on this amoung WiX users
+ // (if user A installs and user B uninstalls key is left behind)
+ // there are suggested workarounds but none are appealing.
+ // Leave it for now
+ out.println(
+ " <RegistryValue Root=\"HKCU\" Key=\"Software\\"
+ + VENDOR.fetchFrom(params) + "\\"
+ + APP_NAME.fetchFrom(params)
+ + "\" Type=\"string\" Value=\"\" />");
+ out.println(" </Component>");
+ out.println(" </Directory>");
+ out.println(" </Directory>");
+ }
+
+ out.println(" </Directory>");
+
+ out.println(" <Feature Id=\"DefaultFeature\" "
+ + "Title=\"Main Feature\" Level=\"1\">");
+ for (int j = 0; j < compId; j++) {
+ out.println(" <ComponentRef Id=\"comp" + j + "\" />");
+ }
+ // component is defined in the main.wsx
+ out.println(
+ " <ComponentRef Id=\"CleanupMainApplicationFolder\" />");
+ out.println(" </Feature>");
+ out.println("</Include>");
+
+ }
+ }
+
+ private File getConfig_ProjectFile(Map<String, ? super Object> params) {
+ return new File(CONFIG_ROOT.fetchFrom(params),
+ APP_NAME.fetchFrom(params) + ".wxs");
+ }
+
+ private Map<String, String> prepareWiXConfig(
+ Map<String, ? super Object> params) throws IOException {
+ prepareContentList(params);
+ prepareIconsFile(params);
+ return prepareMainProjectFile(params);
+ }
+
+ private final static String MSI_PROJECT_CONTENT_FILE = "bundle.wxi";
+
+ private File buildMSI(Map<String, ? super Object> params,
+ Map<String, String> wixVars, File outdir)
+ throws IOException {
+ File tmpDir = new File(TEMP_ROOT.fetchFrom(params), "tmp");
+ File candleOut = new File(
+ tmpDir, APP_NAME.fetchFrom(params) + ".wixobj");
+ File msiOut = new File(
+ outdir, INSTALLER_FILE_NAME.fetchFrom(params) + ".msi");
+
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.preparing-msi-config"), msiOut.getAbsolutePath()));
+
+ msiOut.getParentFile().mkdirs();
+
+ List<String> commandLine = new ArrayList<>(Arrays.asList(
+ getCandlePath(),
+ "-nologo",
+ getConfig_ProjectFile(params).getAbsolutePath(),
+ "-ext", "WixUtilExtension",
+ "-out", candleOut.getAbsolutePath()));
+ for(Map.Entry<String, String> wixVar: wixVars.entrySet()) {
+ String v = "-d" + wixVar.getKey() + "=" + wixVar.getValue();
+ commandLine.add(v);
+ }
+ ProcessBuilder pb = new ProcessBuilder(commandLine);
+ pb = pb.directory(WIN_APP_IMAGE.fetchFrom(params));
+ IOUtils.exec(pb);
+
+ Log.verbose(MessageFormat.format(I18N.getString(
+ "message.generating-msi"), msiOut.getAbsolutePath()));
+
+ boolean enableLicenseUI = (LICENSE_FILE.fetchFrom(params) != null);
+ boolean enableInstalldirUI = INSTALLDIR_CHOOSER.fetchFrom(params);
+
+ commandLine = new ArrayList<>();
+
+ commandLine.add(getLightPath());
+
+ commandLine.add("-nologo");
+ commandLine.add("-spdb");
+ if (!MSI_SYSTEM_WIDE.fetchFrom(params)) {
+ commandLine.add("-sice:ICE91");
+ }
+ commandLine.add(candleOut.getAbsolutePath());
+ commandLine.add("-ext");
+ commandLine.add("WixUtilExtension");
+ if (enableLicenseUI || enableInstalldirUI) {
+ commandLine.add("-ext");
+ commandLine.add("WixUIExtension");
+ }
+
+ commandLine.add("-loc");
+ commandLine.add(new File(CONFIG_ROOT.fetchFrom(params), I18N.getString(
+ "resource.wxl-file-name")).getAbsolutePath());
+
+ // Only needed if we using CA dll, so Wix can find it
+ if (enableInstalldirUI) {
+ commandLine.add("-b");
+ commandLine.add(CONFIG_ROOT.fetchFrom(params).getAbsolutePath());
+ }
+
+ commandLine.add("-out");
+ commandLine.add(msiOut.getAbsolutePath());
+
+ // create .msi
+ pb = new ProcessBuilder(commandLine);
+
+ pb = pb.directory(WIN_APP_IMAGE.fetchFrom(params));
+ IOUtils.exec(pb);
+
+ candleOut.delete();
+ IOUtils.deleteRecursive(tmpDir);
+
+ return msiOut;
+ }
+
+ public static void ensureByMutationFileIsRTF(File f) {
+ if (f == null || !f.isFile()) return;
+
+ try {
+ boolean existingLicenseIsRTF = false;
+
+ try (FileInputStream fin = new FileInputStream(f)) {
+ byte[] firstBits = new byte[7];
+
+ if (fin.read(firstBits) == firstBits.length) {
+ String header = new String(firstBits);
+ existingLicenseIsRTF = "{\\rtf1\\".equals(header);
+ }
+ }
+
+ if (!existingLicenseIsRTF) {
+ List<String> oldLicense = Files.readAllLines(f.toPath());
+ try (Writer w = Files.newBufferedWriter(
+ f.toPath(), Charset.forName("Windows-1252"))) {
+ w.write("{\\rtf1\\ansi\\ansicpg1252\\deff0\\deflang1033"
+ + "{\\fonttbl{\\f0\\fnil\\fcharset0 Arial;}}\n"
+ + "\\viewkind4\\uc1\\pard\\sa200\\sl276"
+ + "\\slmult1\\lang9\\fs20 ");
+ oldLicense.forEach(l -> {
+ try {
+ for (char c : l.toCharArray()) {
+ // 0x00 <= ch < 0x20 Escaped (\'hh)
+ // 0x20 <= ch < 0x80 Raw(non - escaped) char
+ // 0x80 <= ch <= 0xFF Escaped(\ 'hh)
+ // 0x5C, 0x7B, 0x7D (special RTF characters
+ // \,{,})Escaped(\'hh)
+ // ch > 0xff Escaped (\\ud###?)
+ if (c < 0x10) {
+ w.write("\\'0");
+ w.write(Integer.toHexString(c));
+ } else if (c > 0xff) {
+ w.write("\\ud");
+ w.write(Integer.toString(c));
+ // \\uc1 is in the header and in effect
+ // so we trail with a replacement char if
+ // the font lacks that character - '?'
+ w.write("?");
+ } else if ((c < 0x20) || (c >= 0x80) ||
+ (c == 0x5C) || (c == 0x7B) ||
+ (c == 0x7D)) {
+ w.write("\\'");
+ w.write(Integer.toHexString(c));
+ } else {
+ w.write(c);
+ }
+ }
+ // blank lines are interpreted as paragraph breaks
+ if (l.length() < 1) {
+ w.write("\\par");
+ } else {
+ w.write(" ");
+ }
+ w.write("\r\n");
+ } catch (IOException e) {
+ Log.verbose(e);
+ }
+ });
+ w.write("}\r\n");
+ }
+ }
+ } catch (IOException e) {
+ Log.verbose(e);
+ }
+
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsAppImageBuilder.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,380 @@
+/*
+ * Copyright (c) 2015, 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.File;
+import java.io.FileOutputStream;
+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.io.BufferedWriter;
+import java.io.FileWriter;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.PosixFilePermission;
+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 java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+import static jdk.jpackage.internal.StandardBundlerParam.*;
+
+public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
+
+ static {
+ System.loadLibrary("jpackage");
+ }
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.WinResources");
+
+ private final static String LIBRARY_NAME = "applauncher.dll";
+ private final static String REDIST_MSVCR = "vcruntimeVS_VER.dll";
+ private final static String REDIST_MSVCP = "msvcpVS_VER.dll";
+
+ private final static String TEMPLATE_APP_ICON ="java48.ico";
+
+ private static final String EXECUTABLE_PROPERTIES_TEMPLATE =
+ "WinLauncher.template";
+
+ private final Path root;
+ private final Path appDir;
+ private final Path appModsDir;
+ private final Path runtimeDir;
+ private final Path mdir;
+ private final Path binDir;
+
+ public static final BundlerParamInfo<Boolean> REBRAND_EXECUTABLE =
+ new WindowsBundlerParam<>(
+ "win.launcher.rebrand",
+ Boolean.class,
+ params -> Boolean.TRUE,
+ (s, p) -> Boolean.valueOf(s));
+
+ public static final BundlerParamInfo<File> ICON_ICO =
+ new StandardBundlerParam<>(
+ "icon.ico",
+ File.class,
+ params -> {
+ File f = ICON.fetchFrom(params);
+ if (f != null && !f.getName().toLowerCase().endsWith(".ico")) {
+ Log.error(MessageFormat.format(
+ I18N.getString("message.icon-not-ico"), f));
+ return null;
+ }
+ return f;
+ },
+ (s, p) -> new File(s));
+
+ public static final StandardBundlerParam<Boolean> CONSOLE_HINT =
+ new WindowsBundlerParam<>(
+ Arguments.CLIOptions.WIN_CONSOLE_HINT.getId(),
+ Boolean.class,
+ params -> false,
+ // valueOf(null) is false,
+ // and we actually do want null in some cases
+ (s, p) -> (s == null
+ || "null".equalsIgnoreCase(s)) ? true : Boolean.valueOf(s));
+
+ public WindowsAppImageBuilder(Map<String, Object> params, Path imageOutDir)
+ throws IOException {
+ super(params,
+ imageOutDir.resolve(APP_NAME.fetchFrom(params) + "/runtime"));
+
+ Objects.requireNonNull(imageOutDir);
+
+ this.root = imageOutDir.resolve(APP_NAME.fetchFrom(params));
+ this.appDir = root.resolve("app");
+ this.appModsDir = appDir.resolve("mods");
+ this.runtimeDir = root.resolve("runtime");
+ this.mdir = runtimeDir.resolve("lib");
+ this.binDir = root;
+ Files.createDirectories(appDir);
+ Files.createDirectories(runtimeDir);
+ }
+
+ private void writeEntry(InputStream in, Path dstFile) throws IOException {
+ Files.createDirectories(dstFile.getParent());
+ Files.copy(in, dstFile);
+ }
+
+ private static String getLauncherName(Map<String, ? super Object> params) {
+ return APP_NAME.fetchFrom(params) + ".exe";
+ }
+
+ // Returns launcher resource name for launcher we need to use.
+ public static String getLauncherResourceName(
+ Map<String, ? super Object> params) {
+ if (CONSOLE_HINT.fetchFrom(params)) {
+ return "jpackageapplauncher.exe";
+ } else {
+ return "jpackageapplauncherw.exe";
+ }
+ }
+
+ public static String getLauncherCfgName(
+ Map<String, ? super Object> params) {
+ return "app/" + APP_NAME.fetchFrom(params) +".cfg";
+ }
+
+ private File getConfig_AppIcon(Map<String, ? super Object> params) {
+ return new File(getConfigRoot(params),
+ APP_NAME.fetchFrom(params) + ".ico");
+ }
+
+ private File getConfig_ExecutableProperties(
+ Map<String, ? super Object> params) {
+ return new File(getConfigRoot(params),
+ APP_NAME.fetchFrom(params) + ".properties");
+ }
+
+ File getConfigRoot(Map<String, ? super Object> params) {
+ return CONFIG_ROOT.fetchFrom(params);
+ }
+
+ @Override
+ public Path getAppDir() {
+ return appDir;
+ }
+
+ @Override
+ public Path getAppModsDir() {
+ return appModsDir;
+ }
+
+ @Override
+ public void prepareApplicationFiles(Map<String, ? super Object> params)
+ throws IOException {
+ Map<String, ? super Object> originalParams = new HashMap<>(params);
+
+ try {
+ IOUtils.writableOutputDir(root);
+ IOUtils.writableOutputDir(binDir);
+ } catch (PackagerException pe) {
+ throw new RuntimeException(pe);
+ }
+ AppImageFile.save(root, params);
+
+ // create the .exe launchers
+ createLauncherForEntryPoint(params);
+
+ // copy the jars
+ copyApplication(params);
+
+ // copy in the needed libraries
+ try (InputStream is_lib = getResourceAsStream(LIBRARY_NAME)) {
+ Files.copy(is_lib, binDir.resolve(LIBRARY_NAME));
+ }
+
+ copyMSVCDLLs();
+
+ // create the additional launcher(s), if any
+ List<Map<String, ? super Object>> entryPoints =
+ StandardBundlerParam.ADD_LAUNCHERS.fetchFrom(params);
+ for (Map<String, ? super Object> entryPoint : entryPoints) {
+ createLauncherForEntryPoint(
+ AddLauncherArguments.merge(originalParams, entryPoint));
+ }
+ }
+
+ @Override
+ public void prepareJreFiles(Map<String, ? super Object> params)
+ throws IOException {}
+
+ private void copyMSVCDLLs() throws IOException {
+ AtomicReference<IOException> ioe = new AtomicReference<>();
+ try (Stream<Path> files = Files.list(runtimeDir.resolve("bin"))) {
+ files.filter(p -> Pattern.matches(
+ "^(vcruntime|msvcp|msvcr|ucrtbase|api-ms-win-).*\\.dll$",
+ p.toFile().getName().toLowerCase()))
+ .forEach(p -> {
+ try {
+ Files.copy(p, binDir.resolve((p.toFile().getName())));
+ } catch (IOException e) {
+ ioe.set(e);
+ }
+ });
+ }
+
+ IOException e = ioe.get();
+ if (e != null) {
+ throw e;
+ }
+ }
+
+ private void validateValueAndPut(
+ Map<String, String> data, String key,
+ BundlerParamInfo<String> param,
+ Map<String, ? super Object> params) {
+ String value = param.fetchFrom(params);
+ if (value.contains("\r") || value.contains("\n")) {
+ Log.error("Configuration Parameter " + param.getID()
+ + " contains multiple lines of text, ignore it");
+ data.put(key, "");
+ return;
+ }
+ data.put(key, value);
+ }
+
+ protected void prepareExecutableProperties(
+ Map<String, ? super Object> params) throws IOException {
+
+ Map<String, String> data = new HashMap<>();
+
+ // mapping Java parameters in strings for version resource
+ validateValueAndPut(data, "COMPANY_NAME", VENDOR, params);
+ validateValueAndPut(data, "FILE_DESCRIPTION", DESCRIPTION, params);
+ validateValueAndPut(data, "FILE_VERSION", VERSION, params);
+ data.put("INTERNAL_NAME", getLauncherName(params));
+ validateValueAndPut(data, "LEGAL_COPYRIGHT", COPYRIGHT, params);
+ data.put("ORIGINAL_FILENAME", getLauncherName(params));
+ 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);
+ }
+ }
+
+ 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);
+
+ writeCfgFile(params, root.resolve(
+ getLauncherCfgName(params)).toFile());
+
+ prepareExecutableProperties(params);
+
+ // Copy executable to bin folder
+ Path executableFile = binDir.resolve(getLauncherName(params));
+
+ try (InputStream is_launcher =
+ getResourceAsStream(getLauncherResourceName(params))) {
+ writeEntry(is_launcher, executableFile);
+ }
+
+ File launcher = executableFile.toFile();
+ launcher.setWritable(true, true);
+
+ // Update branding of EXE file
+ if (REBRAND_EXECUTABLE.fetchFrom(params)) {
+ try {
+ String tempDirectory = WindowsDefender.getUserTempDirectory();
+ if (Arguments.CLIOptions.context().userProvidedBuildRoot) {
+ tempDirectory =
+ TEMP_ROOT.fetchFrom(params).getAbsolutePath();
+ }
+ if (WindowsDefender.isThereAPotentialWindowsDefenderIssue(
+ tempDirectory)) {
+ Log.error(MessageFormat.format(I18N.getString(
+ "message.potential.windows.defender.issue"),
+ tempDirectory));
+ }
+
+ launcher.setWritable(true);
+
+ if (iconTarget.exists()) {
+ iconSwap(iconTarget.getAbsolutePath(),
+ launcher.getAbsolutePath());
+ }
+
+ File executableProperties =
+ getConfig_ExecutableProperties(params);
+
+ if (executableProperties.exists()) {
+ if (versionSwap(executableProperties.getAbsolutePath(),
+ launcher.getAbsolutePath()) != 0) {
+ throw new RuntimeException(MessageFormat.format(
+ I18N.getString("error.version-swap"),
+ executableProperties.getAbsolutePath()));
+ }
+ }
+ } finally {
+ executableFile.toFile().setExecutable(true);
+ executableFile.toFile().setReadOnly();
+ }
+ }
+
+ Files.copy(iconTarget.toPath(),
+ binDir.resolve(APP_NAME.fetchFrom(params) + ".ico"));
+ }
+
+ private void copyApplication(Map<String, ? super Object> params)
+ throws IOException {
+ List<RelativeFileSet> appResourcesList =
+ APP_RESOURCES_LIST.fetchFrom(params);
+ if (appResourcesList == null) {
+ throw new RuntimeException("Null app resources?");
+ }
+ for (RelativeFileSet appResources : appResourcesList) {
+ if (appResources == null) {
+ throw new RuntimeException("Null app resources?");
+ }
+ File srcdir = appResources.getBaseDirectory();
+ for (String fname : appResources.getIncludedFiles()) {
+ copyEntry(appDir, srcdir, fname);
+ }
+ }
+ }
+
+ private static native int iconSwap(String iconTarget, String launcher);
+
+ private static native int versionSwap(String executableProperties,
+ String launcher);
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsBundlerParam.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2014, 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.text.MessageFormat;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+class WindowsBundlerParam<T> extends StandardBundlerParam<T> {
+
+ private static final ResourceBundle I18N = ResourceBundle.getBundle(
+ "jdk.jpackage.internal.resources.WinResources");
+
+ WindowsBundlerParam(String id, Class<T> valueType,
+ Function<Map<String, ? super Object>, T> defaultValueFunction,
+ BiFunction<String,
+ Map<String, ? super Object>, T> stringConverter) {
+ super(id, valueType, defaultValueFunction, stringConverter);
+ }
+
+ static final BundlerParamInfo<String> INSTALLER_FILE_NAME =
+ new StandardBundlerParam<> (
+ "win.installerName",
+ String.class,
+ params -> {
+ String nm = APP_NAME.fetchFrom(params);
+ if (nm == null) return null;
+
+ String version = VERSION.fetchFrom(params);
+ if (version == null) {
+ return nm;
+ } else {
+ return nm + "-" + version;
+ }
+ },
+ (s, p) -> s);
+
+ static final BundlerParamInfo<String> APP_REGISTRY_NAME =
+ new StandardBundlerParam<> (
+ "win.registryName",
+ String.class,
+ params -> {
+ String nm = APP_NAME.fetchFrom(params);
+ if (nm == null) return null;
+
+ return nm.replaceAll("[^-a-zA-Z\\.0-9]", "");
+ },
+ (s, p) -> s);
+
+ static final StandardBundlerParam<String> MENU_GROUP =
+ new StandardBundlerParam<>(
+ Arguments.CLIOptions.WIN_MENU_GROUP.getId(),
+ String.class,
+ params -> I18N.getString("param.menu-group.default"),
+ (s, p) -> s
+ );
+
+ static final BundlerParamInfo<Boolean> INSTALLDIR_CHOOSER =
+ new StandardBundlerParam<> (
+ Arguments.CLIOptions.WIN_DIR_CHOOSER.getId(),
+ Boolean.class,
+ params -> Boolean.FALSE,
+ (s, p) -> Boolean.valueOf(s)
+ );
+
+ static final BundlerParamInfo<String> WINDOWS_INSTALL_DIR =
+ new StandardBundlerParam<>(
+ "windows-install-dir",
+ String.class,
+ params -> {
+ String dir = INSTALL_DIR.fetchFrom(params);
+ if (dir != null) {
+ if (dir.contains(":") || dir.contains("..")) {
+ Log.error(MessageFormat.format(I18N.getString(
+ "message.invalid.install.dir"), dir,
+ APP_NAME.fetchFrom(params)));
+ } else {
+ if (dir.startsWith("\\")) {
+ dir = dir.substring(1);
+ }
+ if (dir.endsWith("\\")) {
+ dir = dir.substring(0, dir.length() - 1);
+ }
+ return dir;
+ }
+ }
+ return APP_NAME.fetchFrom(params); // Default to app name
+ },
+ (s, p) -> s
+ );
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsDefender.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2012, 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.util.List;
+
+final class WindowsDefender {
+
+ private WindowsDefender() {}
+
+ static final boolean isThereAPotentialWindowsDefenderIssue(String dir) {
+ boolean result = false;
+
+ if (Platform.getPlatform() == Platform.WINDOWS &&
+ Platform.getMajorVersion() == 10) {
+
+ // If DisableRealtimeMonitoring is not enabled then there
+ // may be a problem.
+ if (!WindowsRegistry.readDisableRealtimeMonitoring() &&
+ !isDirectoryInExclusionPath(dir)) {
+ result = true;
+ }
+ }
+
+ return result;
+ }
+
+ private static boolean isDirectoryInExclusionPath(String dir) {
+ boolean result = false;
+ // If the user temp directory is not found in the exclusion
+ // list then there may be a problem.
+ List<String> paths = WindowsRegistry.readExclusionsPaths();
+ for (String s : paths) {
+ if (WindowsRegistry.comparePaths(s, dir)) {
+ result = true;
+ break;
+ }
+ }
+
+ return result;
+ }
+
+ static final String getUserTempDirectory() {
+ String tempDirectory = System.getProperty("java.io.tmpdir");
+ return tempDirectory;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WindowsRegistry.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2012, 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.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+final class WindowsRegistry {
+
+ // Currently we only support HKEY_LOCAL_MACHINE. Native implementation will
+ // require support for additinal HKEY if needed.
+ private static final int HKEY_LOCAL_MACHINE = 1;
+
+ static {
+ System.loadLibrary("jpackage");
+ }
+
+ private WindowsRegistry() {}
+
+ /**
+ * Reads the registry value for DisableRealtimeMonitoring.
+ * @return true if DisableRealtimeMonitoring is set to 0x1,
+ * false otherwise.
+ */
+ static final boolean readDisableRealtimeMonitoring() {
+ final String subKey = "Software\\Microsoft\\"
+ + "Windows Defender\\Real-Time Protection";
+ final String value = "DisableRealtimeMonitoring";
+ int result = readDwordValue(HKEY_LOCAL_MACHINE, subKey, value, 0);
+ return (result == 1);
+ }
+
+ static final List<String> readExclusionsPaths() {
+ List<String> result = new ArrayList<>();
+ final String subKey = "Software\\Microsoft\\"
+ + "Windows Defender\\Exclusions\\Paths";
+ long lKey = openRegistryKey(HKEY_LOCAL_MACHINE, subKey);
+ if (lKey == 0) {
+ return result;
+ }
+
+ String valueName;
+ int index = 0;
+ do {
+ valueName = enumRegistryValue(lKey, index);
+ if (valueName != null) {
+ result.add(valueName);
+ index++;
+ }
+ } while (valueName != null);
+
+ closeRegistryKey(lKey);
+
+ return result;
+ }
+
+ /**
+ * Reads DWORD registry value.
+ *
+ * @param key one of HKEY predefine value
+ * @param subKey registry sub key
+ * @param value value to read
+ * @param defaultValue default value in case if subKey or value not found
+ * or any other errors occurred
+ * @return value's data only if it was read successfully, otherwise
+ * defaultValue
+ */
+ private static native int readDwordValue(int key, String subKey,
+ String value, int defaultValue);
+
+ /**
+ * Open registry key.
+ *
+ * @param key one of HKEY predefine value
+ * @param subKey registry sub key
+ * @return native handle to open key
+ */
+ private static native long openRegistryKey(int key, String subKey);
+
+ /**
+ * Enumerates the values for registry key.
+ *
+ * @param lKey native handle to open key returned by openRegistryKey
+ * @param index index of value starting from 0. Increment until this
+ * function returns NULL which means no more values.
+ * @return returns value or NULL if error or no more data
+ */
+ private static native String enumRegistryValue(long lKey, int index);
+
+ /**
+ * Close registry key.
+ *
+ * @param lKey native handle to open key returned by openRegistryKey
+ */
+ private static native void closeRegistryKey(long lKey);
+
+ /**
+ * Compares two Windows paths regardless case and if paths
+ * are short or long.
+ *
+ * @param path1 path to compare
+ * @param path2 path to compare
+ * @return true if paths point to same location
+ */
+ public static native boolean comparePaths(String path1, String path2);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/MsiInstallerStrings_en.wxl Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization" Codepage="1252">
+ <String Id="message.install.dir.exist">The folder [APPLICATIONFOLDER] already exist. Would you like to install to that folder anyway?</String>
+</WixLocalization>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/MsiInstallerStrings_ja.wxl Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization" Codepage="1252">
+ <String Id="message.install.dir.exist">The folder [APPLICATIONFOLDER] already exist. Would you like to install to that folder anyway?</String>
+</WixLocalization>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/MsiInstallerStrings_zh_CN.wxl Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization" Codepage="1252">
+ <String Id="message.install.dir.exist">The folder [APPLICATIONFOLDER] already exist. Would you like to install to that folder anyway?</String>
+</WixLocalization>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinLauncher.template Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,34 @@
+#
+# Copyright (c) 2017, 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.
+#
+#
+
+CompanyName=COMPANY_NAME
+FileDescription=FILE_DESCRIPTION
+FileVersion=FILE_VERSION
+InternalName=INTERNAL_NAME
+LegalCopyright=LEGAL_COPYRIGHT
+OriginalFilename=ORIGINAL_FILENAME
+ProductName=PRODUCT_NAME
+ProductVersion=PRODUCT_VERSION
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,77 @@
+#
+# Copyright (c) 2017, 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.
+#
+#
+
+app.bundler.name=Windows Application Image
+exe.bundler.name=EXE Installer Package
+msi.bundler.name=MSI Installer Package
+
+param.menu-group.default=Unknown
+
+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
+
+error.parameters-null=Parameters map is null
+error.parameters-null.advice=Pass in a non-null parameters map
+error.no-windows-resources=This copy of the JDK does not support Windows
+error.no-windows-resources.advice=Please use the Oracle JDK for Windows
+error.cannot-find-launcher=Cannot find cfg file in predefined app image directory {0}
+error.copyright-is-too-long=The copyright string is too long for InnoSetup
+error.copyright-is-too-long.advice=Provide a copyright string shorter than 100 characters.
+error.no-wix-tools=Can not find WiX tools (light.exe, candle.exe)
+error.no-wix-tools.advice=Download WiX 3.0 or later from http://wix.sf.net and add it to the PATH.
+error.version-string-wrong-format=Version string is not compatible with MSI rules [{0}]
+error.version-string-wrong-format.advice=Set the bundler argument "{0}" according to these rules: http://msdn.microsoft.com/en-us/library/aa370859%28v\=VS.85%29.aspx .
+error.version-string-major-out-of-range=Major version must be in the range [0, 255]
+error.version-string-build-out-of-range=Build part of version must be in the range [0, 65535]
+error.version-string-minor-out-of-range=Minor version must be in the range [0, 255]
+error.version-string-part-not-number=Failed to convert version component to int
+error.cannot-walk-directory=Can not walk [{0}] - it is not a valid directory
+error.version-swap=Failed to update version information for {0}
+error.version-compare=Error: Failed to compare version {0} with {1.
+
+message.result-dir=Result application bundle: {0}.
+message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place.
+message.multiple-launchers=Multiple launchers found in predefined app-image. {0} will be used as primary launcher.
+message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}".
+message.tool-wrong-version=Detected [{0}] version {1} but version {2} is required.
+message.outputting-to-location=Generating EXE for installer to: {0}.
+message.output-location=Installer (.exe) saved to: {0}
+message.tool-version=Detected [{0}] version [{1}].
+message.running-wsh-script=Running WSH script on application image [{0}].
+message.creating-association-with-null-extension=Creating association with null extension.
+message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required.
+message.version-string-too-many-components=Version sting may have up to 3 components - major.minor.build .
+message.truncating.id=Truncating Application ID to 126 chars for Inno Setup.
+message.use-wix36-features=WiX 3.6 detected. Enabling advanced cleanup action.
+message.generated-product-guid=Generated product GUID: {0}.
+message.preparing-msi-config=Preparing MSI config: {0}.
+message.generating-msi=Generating MSI: {0}.
+message.light-file-string=WiX light tool set to {0}.
+message.candle-file-string=WiX candle tool set to {0}.
+message.invalid.install.dir=Warning: Invalid install directory {0}. Install directory should be a relative sub-path under the default installation location such as "Program Files". Defaulting to application name "{1}".
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_ja.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,77 @@
+#
+# Copyright (c) 2017, 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.
+#
+#
+
+app.bundler.name=Windows Application Image
+exe.bundler.name=EXE Installer Package
+msi.bundler.name=MSI Installer Package
+
+param.menu-group.default=Unknown
+
+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
+
+error.parameters-null=Parameters map is null
+error.parameters-null.advice=Pass in a non-null parameters map
+error.no-windows-resources=This copy of the JDK does not support Windows
+error.no-windows-resources.advice=Please use the Oracle JDK for Windows
+error.cannot-find-launcher=Cannot find cfg file in predefined app image directory {0}
+error.copyright-is-too-long=The copyright string is too long for InnoSetup
+error.copyright-is-too-long.advice=Provide a copyright string shorter than 100 characters.
+error.no-wix-tools=Can not find WiX tools (light.exe, candle.exe)
+error.no-wix-tools.advice=Download WiX 3.0 or later from http://wix.sf.net and add it to the PATH.
+error.version-string-wrong-format=Version string is not compatible with MSI rules [{0}]
+error.version-string-wrong-format.advice=Set the bundler argument "{0}" according to these rules: http://msdn.microsoft.com/en-us/library/aa370859%28v\=VS.85%29.aspx .
+error.version-string-major-out-of-range=Major version must be in the range [0, 255]
+error.version-string-build-out-of-range=Build part of version must be in the range [0, 65535]
+error.version-string-minor-out-of-range=Minor version must be in the range [0, 255]
+error.version-string-part-not-number=Failed to convert version component to int
+error.cannot-walk-directory=Can not walk [{0}] - it is not a valid directory
+error.version-swap=Failed to update version information for {0}
+error.version-compare=Error: Failed to compare version {0} with {1.
+
+message.result-dir=Result application bundle: {0}.
+message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place.
+message.multiple-launchers=Multiple launchers found in predefined app-image. {0} will be used as primary launcher.
+message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}".
+message.tool-wrong-version=Detected [{0}] version {1} but version {2} is required.
+message.outputting-to-location=Generating EXE for installer to: {0}.
+message.output-location=Installer (.exe) saved to: {0}
+message.tool-version=Detected [{0}] version [{1}].
+message.running-wsh-script=Running WSH script on application image [{0}].
+message.creating-association-with-null-extension=Creating association with null extension.
+message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required.
+message.version-string-too-many-components=Version sting may have up to 3 components - major.minor.build .
+message.truncating.id=Truncating Application ID to 126 chars for Inno Setup.
+message.use-wix36-features=WiX 3.6 detected. Enabling advanced cleanup action.
+message.generated-product-guid=Generated product GUID: {0}.
+message.preparing-msi-config=Preparing MSI config: {0}.
+message.generating-msi=Generating MSI: {0}.
+message.light-file-string=WiX light tool set to {0}.
+message.candle-file-string=WiX candle tool set to {0}.
+message.invalid.install.dir=Warning: Invalid install directory {0}. Install directory should be a relative sub-path under the default installation location such as "Program Files". Defaulting to application name "{1}".
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources_zh_CN.properties Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,77 @@
+#
+# Copyright (c) 2017, 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.
+#
+#
+
+app.bundler.name=Windows Application Image
+exe.bundler.name=EXE Installer Package
+msi.bundler.name=MSI Installer Package
+
+param.menu-group.default=Unknown
+
+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
+
+error.parameters-null=Parameters map is null
+error.parameters-null.advice=Pass in a non-null parameters map
+error.no-windows-resources=This copy of the JDK does not support Windows
+error.no-windows-resources.advice=Please use the Oracle JDK for Windows
+error.cannot-find-launcher=Cannot find cfg file in predefined app image directory {0}
+error.copyright-is-too-long=The copyright string is too long for InnoSetup
+error.copyright-is-too-long.advice=Provide a copyright string shorter than 100 characters.
+error.no-wix-tools=Can not find WiX tools (light.exe, candle.exe)
+error.no-wix-tools.advice=Download WiX 3.0 or later from http://wix.sf.net and add it to the PATH.
+error.version-string-wrong-format=Version string is not compatible with MSI rules [{0}]
+error.version-string-wrong-format.advice=Set the bundler argument "{0}" according to these rules: http://msdn.microsoft.com/en-us/library/aa370859%28v\=VS.85%29.aspx .
+error.version-string-major-out-of-range=Major version must be in the range [0, 255]
+error.version-string-build-out-of-range=Build part of version must be in the range [0, 65535]
+error.version-string-minor-out-of-range=Minor version must be in the range [0, 255]
+error.version-string-part-not-number=Failed to convert version component to int
+error.cannot-walk-directory=Can not walk [{0}] - it is not a valid directory
+error.version-swap=Failed to update version information for {0}
+error.version-compare=Error: Failed to compare version {0} with {1.
+
+message.result-dir=Result application bundle: {0}.
+message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place.
+message.multiple-launchers=Multiple launchers found in predefined app-image. {0} will be used as primary launcher.
+message.potential.windows.defender.issue=Warning: Windows Defender may prevent jpackage from functioning. If there is an issue, it can be addressed by either disabling realtime monitoring, or adding an exclusion for the directory "{0}".
+message.tool-wrong-version=Detected [{0}] version {1} but version {2} is required.
+message.outputting-to-location=Generating EXE for installer to: {0}.
+message.output-location=Installer (.exe) saved to: {0}
+message.tool-version=Detected [{0}] version [{1}].
+message.running-wsh-script=Running WSH script on application image [{0}].
+message.creating-association-with-null-extension=Creating association with null extension.
+message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required.
+message.version-string-too-many-components=Version sting may have up to 3 components - major.minor.build .
+message.truncating.id=Truncating Application ID to 126 chars for Inno Setup.
+message.use-wix36-features=WiX 3.6 detected. Enabling advanced cleanup action.
+message.generated-product-guid=Generated product GUID: {0}.
+message.preparing-msi-config=Preparing MSI config: {0}.
+message.generating-msi=Generating MSI: {0}.
+message.light-file-string=WiX light tool set to {0}.
+message.candle-file-string=WiX candle tool set to {0}.
+message.invalid.install.dir=Warning: Invalid install directory {0}. Install directory should be a relative sub-path under the default installation location such as "Program Files". Defaulting to application name "{1}".
+
Binary file src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/java48.ico has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/main.wxs Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
+ xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
+
+ <?ifdef JpIsSystemWide ?>
+ <?define JpInstallScope="perMachine"?>
+ <?define JpRegistryRoot="HKLM"?>
+ <?else?>
+ <?define JpInstallScope="perUser"?>
+ <?define JpRegistryRoot="HKCU"?>
+ <?endif?>
+
+ <Product Id="$(var.JpProductCode)" Name="$(var.JpAppName)"
+ Language="1033" Version="$(var.JpAppVersion)"
+ Manufacturer="$(var.JpAppVendor)"
+ UpgradeCode="$(var.JpProductUpgradeCode)">
+ <Package Description="$(var.JpAppDescription)"
+ Manufacturer="$(var.JpAppVendor)"
+ InstallerVersion="200" Compressed="yes"
+ InstallScope="$(var.JpInstallScope)" Platform="x64"/>
+ <Media Id="1" Cabinet="simple.cab" EmbedCab="yes" />
+
+ <?ifdef JpAllowDowngrades ?>
+ <MajorUpgrade AllowDowngrades="yes"/>
+ <?endif?>
+
+ <!-- We use RemoveFolderEx to ensure application folder is fully
+ removed on uninstall. Including files created outside of MSI
+ after application had been installed (e.g. on AU or user state).
+
+ However, RemoveFolderEx is only available in WiX 3.6,
+ we will comment it out if we running older WiX.
+
+ RemoveFolderEx requires that we "remember" the path for uninstall.
+ Read the path value and set the APPLICATIONFOLDER property with the value.
+ -->
+ <Property Id="APPLICATIONFOLDER">
+ <RegistrySearch Key="SOFTWARE\$(var.JpAppVendor)\$(var.JpAppName)"
+ Root="$(var.JpRegistryRoot)" Type="raw" Win64="yes"
+ Id="APPLICATIONFOLDER_REGSEARCH" Name="Path" />
+ </Property>
+ <DirectoryRef Id="APPLICATIONFOLDER">
+ <Component Id="CleanupMainApplicationFolder" Guid="*" Win64="yes">
+ <RegistryValue Root="$(var.JpRegistryRoot)"
+ Key="SOFTWARE\$(var.JpAppVendor)\$(var.JpAppName)"
+ Name="Path" Type="string" Value="[APPLICATIONFOLDER]"
+ KeyPath="yes" />
+ <!-- We need to use APPLICATIONFOLDER variable here or RemoveFolderEx
+ will not remove on "install". But only if WiX 3.6 is used. -->
+ <?ifdef JpWixVersion36OrNewer ?>
+ <util:RemoveFolderEx On="uninstall" Property="APPLICATIONFOLDER" />
+ <?endif?>
+ </Component>
+ </DirectoryRef>
+
+ <?include $(var.JpConfigDir)/bundle.wxi ?>
+
+ <?ifdef JpInstallDirChooser ?>
+ <Binary Id="JpCaDll" SourceFile="wixhelper.dll"/>
+ <CustomAction Id="JpCheckInstallDir" BinaryKey="JpCaDll" DllEntry="CheckInstallDir" />
+ <?endif?>
+
+ <UI>
+ <?ifdef JpInstallDirChooser ?>
+ <Dialog Id="JpInvalidInstallDir" Width="300" Height="85" Title="[ProductName] Setup" NoMinimize="yes">
+ <Control Id="JpInvalidInstallDirYes" Type="PushButton" X="100" Y="55" Width="50" Height="15" Default="no" Cancel="no" Text="Yes">
+ <Publish Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
+ </Control>
+ <Control Id="JpInvalidInstallDirNo" Type="PushButton" X="150" Y="55" Width="50" Height="15" Default="yes" Cancel="yes" Text="No">
+ <Publish Event="NewDialog" Value="InstallDirDlg">1</Publish>
+ </Control>
+ <Control Id="Text" Type="Text" X="25" Y="15" Width="250" Height="30" TabSkip="no">
+ <Text>!(loc.message.install.dir.exist)</Text>
+ </Control>
+ </Dialog>
+
+ <!--
+ Run WixUI_InstallDir dialog in the default install directory.
+ -->
+ <Property Id="WIXUI_INSTALLDIR" Value="APPLICATIONFOLDER"/>
+ <UIRef Id="WixUI_InstallDir" />
+
+ <Publish Dialog="InstallDirDlg" Control="Next" Event="DoAction" Value="JpCheckInstallDir" Order="3">1</Publish>
+ <Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="JpInvalidInstallDir" Order="5">INSTALLDIR_VALID="0"</Publish>
+ <Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="5">INSTALLDIR_VALID="1"</Publish>
+
+ <?ifndef JpLicenseRtf ?>
+ <!--
+ No license file provided.
+ Override the dialog sequence in built-in dialog set "WixUI_InstallDir"
+ to exclude license dialog.
+ -->
+ <Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg" Order="2">1</Publish>
+ <Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg" Order="2">1</Publish>
+ <?endif?>
+
+ <?else?>
+
+ <?ifdef JpLicenseRtf ?>
+ <UIRef Id="WixUI_Minimal" />
+ <?endif?>
+
+ <?endif?>
+ </UI>
+
+ <?ifdef JpLicenseRtf ?>
+ <WixVariable Id="WixUILicenseRtf" Value="$(var.JpLicenseRtf)"/>
+ <?endif?>
+
+ <?include $(var.JpConfigDir)/icons.wxi ?>
+ </Product>
+</Wix>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/classes/module-info.java.extra Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018, 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.
+ */
+
+provides jdk.jpackage.internal.Bundler with
+ jdk.jpackage.internal.WinAppBundler,
+ jdk.jpackage.internal.WinExeBundler,
+ jdk.jpackage.internal.WinMsiBundler;
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/jpackageapplauncher/WinLauncher.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2012, 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.
+ */
+
+#include <Windows.h>
+#include <Shellapi.h>
+#include <locale.h>
+#include <tchar.h>
+#include <string>
+
+#define JPACKAGE_LIBRARY TEXT("applauncher.dll")
+
+typedef bool (*start_launcher)(int argc, TCHAR* argv[]);
+typedef void (*stop_launcher)();
+
+std::wstring GetTitle() {
+ std::wstring result;
+ wchar_t buffer[MAX_PATH];
+ GetModuleFileName(NULL, buffer, MAX_PATH - 1);
+ buffer[MAX_PATH - 1] = '\0';
+ result = buffer;
+ size_t slash = result.find_last_of('\\');
+
+ if (slash != std::wstring::npos)
+ result = result.substr(slash + 1, result.size() - slash - 1);
+
+ return result;
+}
+
+#ifdef LAUNCHERC
+int main(int argc0, char *argv0[]) {
+#else // LAUNCHERC
+int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
+ LPTSTR lpCmdLine, int nCmdShow) {
+#endif // LAUNCHERC
+ int result = 1;
+ TCHAR **argv;
+ int argc;
+
+ // [RT-31061] otherwise UI can be left in back of other windows.
+ ::AllowSetForegroundWindow(ASFW_ANY);
+
+ ::setlocale(LC_ALL, "en_US.utf8");
+ argv = CommandLineToArgvW(GetCommandLine(), &argc);
+
+ HMODULE library = ::LoadLibrary(JPACKAGE_LIBRARY);
+
+ if (library == NULL) {
+ std::wstring title = GetTitle();
+ std::wstring description = std::wstring(JPACKAGE_LIBRARY)
+ + std::wstring(TEXT(" not found."));
+ MessageBox(NULL, description.data(),
+ title.data(), MB_ICONERROR | MB_OK);
+ }
+ else {
+ start_launcher start =
+ (start_launcher)GetProcAddress(library, "start_launcher");
+ stop_launcher stop =
+ (stop_launcher)GetProcAddress(library, "stop_launcher");
+
+ if (start != NULL && stop != NULL) {
+ if (start(argc, argv) == true) {
+ result = 0;
+ stop();
+ }
+ }
+
+ ::FreeLibrary(library);
+ }
+
+ if (argv != NULL) {
+ LocalFree(argv);
+ }
+
+ return result;
+}
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libapplauncher/DllMain.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+#include <windows.h>
+
+extern "C" {
+
+ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason,
+ LPVOID lpvReserved) {
+ return true;
+ }
+}
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libapplauncher/FileAttribute.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#ifndef FILEATTRIBUTE_H
+#define FILEATTRIBUTE_H
+
+enum FileAttribute {
+ faArchive = FILE_ATTRIBUTE_ARCHIVE,
+ faCompressed = FILE_ATTRIBUTE_COMPRESSED,
+ faDevice = FILE_ATTRIBUTE_DEVICE,
+ faDirectory = FILE_ATTRIBUTE_DIRECTORY,
+ faEncrypted = FILE_ATTRIBUTE_ENCRYPTED,
+ faHidden = FILE_ATTRIBUTE_HIDDEN,
+ faNormal = FILE_ATTRIBUTE_NORMAL,
+ faNotContentIndexed = FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
+ faOffline = FILE_ATTRIBUTE_OFFLINE,
+ faSystem = FILE_ATTRIBUTE_SYSTEM,
+ faSymbolicLink = FILE_ATTRIBUTE_REPARSE_POINT,
+ faSparceFile = FILE_ATTRIBUTE_SPARSE_FILE,
+ faReadOnly = FILE_ATTRIBUTE_READONLY,
+ faTemporary = FILE_ATTRIBUTE_TEMPORARY,
+ faVirtual = FILE_ATTRIBUTE_VIRTUAL
+};
+
+#endif // FILEATTRIBUTE_H
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libapplauncher/FilePath.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,468 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "FilePath.h"
+
+#include <algorithm>
+#include <list>
+#include <ShellAPI.h>
+
+bool FilePath::FileExists(const TString FileName) {
+ bool result = false;
+ WIN32_FIND_DATA FindFileData;
+ TString fileName = FixPathForPlatform(FileName);
+ HANDLE handle = FindFirstFile(fileName.data(), &FindFileData);
+
+ if (handle != INVALID_HANDLE_VALUE) {
+ if (FILE_ATTRIBUTE_DIRECTORY & FindFileData.dwFileAttributes) {
+ result = true;
+ }
+ else {
+ result = true;
+ }
+
+ FindClose(handle);
+ }
+ return result;
+}
+
+bool FilePath::DirectoryExists(const TString DirectoryName) {
+ bool result = false;
+ WIN32_FIND_DATA FindFileData;
+ TString directoryName = FixPathForPlatform(DirectoryName);
+ HANDLE handle = FindFirstFile(directoryName.data(), &FindFileData);
+
+ if (handle != INVALID_HANDLE_VALUE) {
+ if (FILE_ATTRIBUTE_DIRECTORY & FindFileData.dwFileAttributes) {
+ result = true;
+ }
+
+ FindClose(handle);
+ }
+ return result;
+}
+
+std::string GetLastErrorAsString() {
+ // Get the error message, if any.
+ DWORD errorMessageID = ::GetLastError();
+
+ if (errorMessageID == 0) {
+ return "No error message has been recorded";
+ }
+
+ LPSTR messageBuffer = NULL;
+ size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER
+ | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL,
+ SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
+
+ std::string message(messageBuffer, size);
+
+ // Free the buffer.
+ LocalFree(messageBuffer);
+
+ return message;
+}
+
+bool FilePath::DeleteFile(const TString FileName) {
+ bool result = false;
+
+ if (FileExists(FileName) == true) {
+ TString lFileName = FixPathForPlatform(FileName);
+ FileAttributes attributes(lFileName);
+
+ if (attributes.Contains(faReadOnly) == true) {
+ attributes.Remove(faReadOnly);
+ }
+
+ result = ::DeleteFile(lFileName.data()) == TRUE;
+ }
+
+ return result;
+}
+
+bool FilePath::DeleteDirectory(const TString DirectoryName) {
+ bool result = false;
+
+ if (DirectoryExists(DirectoryName) == true) {
+ SHFILEOPSTRUCTW fos = {0};
+ TString directoryName = FixPathForPlatform(DirectoryName);
+ DynamicBuffer<TCHAR> lDirectoryName(directoryName.size() + 2);
+ if (lDirectoryName.GetData() == NULL) {
+ return false;
+ }
+ memcpy(lDirectoryName.GetData(), directoryName.data(),
+ (directoryName.size() + 2) * sizeof(TCHAR));
+ lDirectoryName[directoryName.size() + 1] = NULL;
+ // Double null terminate for SHFileOperation.
+
+ // Delete the folder and everything inside.
+ fos.wFunc = FO_DELETE;
+ fos.pFrom = lDirectoryName.GetData();
+ fos.fFlags = FOF_NO_UI;
+ result = SHFileOperation(&fos) == 0;
+ }
+
+ return result;
+}
+
+TString FilePath::IncludeTrailingSeparator(const TString value) {
+ TString result = value;
+
+ if (value.size() > 0) {
+ TString::iterator i = result.end();
+ i--;
+
+ if (*i != TRAILING_PATHSEPARATOR) {
+ result += TRAILING_PATHSEPARATOR;
+ }
+ }
+
+ return result;
+}
+
+TString FilePath::IncludeTrailingSeparator(const char* value) {
+ TString lvalue = PlatformString(value).toString();
+ return IncludeTrailingSeparator(lvalue);
+}
+
+TString FilePath::IncludeTrailingSeparator(const wchar_t* value) {
+ TString lvalue = PlatformString(value).toString();
+ return IncludeTrailingSeparator(lvalue);
+}
+
+TString FilePath::ExtractFilePath(TString Path) {
+ TString result;
+ size_t slash = Path.find_last_of(TRAILING_PATHSEPARATOR);
+ if (slash != TString::npos)
+ result = Path.substr(0, slash);
+ return result;
+}
+
+TString FilePath::ExtractFileExt(TString Path) {
+ TString result;
+ size_t dot = Path.find_last_of('.');
+
+ if (dot != TString::npos) {
+ result = Path.substr(dot, Path.size() - dot);
+ }
+
+ return result;
+}
+
+TString FilePath::ExtractFileName(TString Path) {
+ TString result;
+
+ size_t slash = Path.find_last_of(TRAILING_PATHSEPARATOR);
+ if (slash != TString::npos)
+ result = Path.substr(slash + 1, Path.size() - slash - 1);
+
+ return result;
+}
+
+TString FilePath::ChangeFileExt(TString Path, TString Extension) {
+ TString result;
+ size_t dot = Path.find_last_of('.');
+
+ if (dot != TString::npos) {
+ result = Path.substr(0, dot) + Extension;
+ }
+
+ if (result.empty() == true) {
+ result = Path;
+ }
+
+ return result;
+}
+
+TString FilePath::FixPathForPlatform(TString Path) {
+ TString result = Path;
+ std::replace(result.begin(), result.end(),
+ BAD_TRAILING_PATHSEPARATOR, TRAILING_PATHSEPARATOR);
+ // The maximum path that does not require long path prefix. On Windows the
+ // maximum path is 260 minus 1 (NUL) but for directories it is 260 minus
+ // 12 minus 1 (to allow for the creation of a 8.3 file in the directory).
+ const int maxPath = 247;
+ if (result.length() > maxPath &&
+ result.find(_T("\\\\?\\")) == TString::npos &&
+ result.find(_T("\\\\?\\UNC")) == TString::npos) {
+ const TString prefix(_T("\\\\"));
+ if (!result.compare(0, prefix.size(), prefix)) {
+ // UNC path, converting to UNC path in long notation
+ result = _T("\\\\?\\UNC") + result.substr(1, result.length());
+ } else {
+ // converting to non-UNC path in long notation
+ result = _T("\\\\?\\") + result;
+ }
+ }
+ return result;
+}
+
+TString FilePath::FixPathSeparatorForPlatform(TString Path) {
+ TString result = Path;
+ std::replace(result.begin(), result.end(),
+ BAD_PATH_SEPARATOR, PATH_SEPARATOR);
+ return result;
+}
+
+TString FilePath::PathSeparator() {
+ TString result;
+ result = PATH_SEPARATOR;
+ return result;
+}
+
+bool FilePath::CreateDirectory(TString Path, bool ownerOnly) {
+ bool result = false;
+
+ std::list<TString> paths;
+ TString lpath = Path;
+
+ while (lpath.empty() == false && DirectoryExists(lpath) == false) {
+ paths.push_front(lpath);
+ lpath = ExtractFilePath(lpath);
+ }
+
+ for (std::list<TString>::iterator iterator = paths.begin();
+ iterator != paths.end(); iterator++) {
+ lpath = *iterator;
+
+ if (_wmkdir(lpath.data()) == 0) {
+ result = true;
+ } else {
+ result = false;
+ break;
+ }
+ }
+
+ return result;
+}
+
+void FilePath::ChangePermissions(TString FileName, bool ownerOnly) {
+}
+
+#include <algorithm>
+
+FileAttributes::FileAttributes(const TString FileName, bool FollowLink) {
+ FFileName = FileName;
+ FFollowLink = FollowLink;
+ ReadAttributes();
+}
+
+bool FileAttributes::WriteAttributes() {
+ bool result = false;
+
+ DWORD attributes = 0;
+
+ for (std::vector<FileAttribute>::const_iterator iterator =
+ FAttributes.begin();
+ iterator != FAttributes.end(); iterator++) {
+ switch (*iterator) {
+ case faArchive: {
+ attributes = attributes & FILE_ATTRIBUTE_ARCHIVE;
+ break;
+ }
+ case faCompressed: {
+ attributes = attributes & FILE_ATTRIBUTE_COMPRESSED;
+ break;
+ }
+ case faDevice: {
+ attributes = attributes & FILE_ATTRIBUTE_DEVICE;
+ break;
+ }
+ case faDirectory: {
+ attributes = attributes & FILE_ATTRIBUTE_DIRECTORY;
+ break;
+ }
+ case faEncrypted: {
+ attributes = attributes & FILE_ATTRIBUTE_ENCRYPTED;
+ break;
+ }
+ case faHidden: {
+ attributes = attributes & FILE_ATTRIBUTE_HIDDEN;
+ break;
+ }
+ case faNormal: {
+ attributes = attributes & FILE_ATTRIBUTE_NORMAL;
+ break;
+ }
+ case faNotContentIndexed: {
+ attributes = attributes & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
+ break;
+ }
+ case faOffline: {
+ attributes = attributes & FILE_ATTRIBUTE_OFFLINE;
+ break;
+ }
+ case faSystem: {
+ attributes = attributes & FILE_ATTRIBUTE_SYSTEM;
+ break;
+ }
+ case faSymbolicLink: {
+ attributes = attributes & FILE_ATTRIBUTE_REPARSE_POINT;
+ break;
+ }
+ case faSparceFile: {
+ attributes = attributes & FILE_ATTRIBUTE_SPARSE_FILE;
+ break;
+ }
+ case faReadOnly: {
+ attributes = attributes & FILE_ATTRIBUTE_READONLY;
+ break;
+ }
+ case faTemporary: {
+ attributes = attributes & FILE_ATTRIBUTE_TEMPORARY;
+ break;
+ }
+ case faVirtual: {
+ attributes = attributes & FILE_ATTRIBUTE_VIRTUAL;
+ break;
+ }
+ }
+ }
+
+ if (::SetFileAttributes(FFileName.data(), attributes) != 0) {
+ result = true;
+ }
+
+ return result;
+}
+
+#define S_ISRUSR(m) (((m) & S_IRWXU) == S_IRUSR)
+#define S_ISWUSR(m) (((m) & S_IRWXU) == S_IWUSR)
+#define S_ISXUSR(m) (((m) & S_IRWXU) == S_IXUSR)
+
+#define S_ISRGRP(m) (((m) & S_IRWXG) == S_IRGRP)
+#define S_ISWGRP(m) (((m) & S_IRWXG) == S_IWGRP)
+#define S_ISXGRP(m) (((m) & S_IRWXG) == S_IXGRP)
+
+#define S_ISROTH(m) (((m) & S_IRWXO) == S_IROTH)
+#define S_ISWOTH(m) (((m) & S_IRWXO) == S_IWOTH)
+#define S_ISXOTH(m) (((m) & S_IRWXO) == S_IXOTH)
+
+bool FileAttributes::ReadAttributes() {
+ bool result = false;
+
+ DWORD attributes = ::GetFileAttributes(FFileName.data());
+
+ if (attributes != INVALID_FILE_ATTRIBUTES) {
+ result = true;
+
+ if (attributes | FILE_ATTRIBUTE_ARCHIVE) {
+ FAttributes.push_back(faArchive);
+ }
+ if (attributes | FILE_ATTRIBUTE_COMPRESSED) {
+ FAttributes.push_back(faCompressed);
+ }
+ if (attributes | FILE_ATTRIBUTE_DEVICE) {
+ FAttributes.push_back(faDevice);
+ }
+ if (attributes | FILE_ATTRIBUTE_DIRECTORY) {
+ FAttributes.push_back(faDirectory);
+ }
+ if (attributes | FILE_ATTRIBUTE_ENCRYPTED) {
+ FAttributes.push_back(faEncrypted);
+ }
+ if (attributes | FILE_ATTRIBUTE_HIDDEN) {
+ FAttributes.push_back(faHidden);
+ }
+ if (attributes | FILE_ATTRIBUTE_NORMAL) {
+ FAttributes.push_back(faNormal);
+ }
+ if (attributes | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED) {
+ FAttributes.push_back(faNotContentIndexed);
+ }
+ if (attributes | FILE_ATTRIBUTE_SYSTEM) {
+ FAttributes.push_back(faSystem);
+ }
+ if (attributes | FILE_ATTRIBUTE_OFFLINE) {
+ FAttributes.push_back(faOffline);
+ }
+ if (attributes | FILE_ATTRIBUTE_REPARSE_POINT) {
+ FAttributes.push_back(faSymbolicLink);
+ }
+ if (attributes | FILE_ATTRIBUTE_SPARSE_FILE) {
+ FAttributes.push_back(faSparceFile);
+ }
+ if (attributes | FILE_ATTRIBUTE_READONLY ) {
+ FAttributes.push_back(faReadOnly);
+ }
+ if (attributes | FILE_ATTRIBUTE_TEMPORARY) {
+ FAttributes.push_back(faTemporary);
+ }
+ if (attributes | FILE_ATTRIBUTE_VIRTUAL) {
+ FAttributes.push_back(faVirtual);
+ }
+ }
+
+ return result;
+}
+
+bool FileAttributes::Valid(const FileAttribute Value) {
+ bool result = false;
+
+ switch (Value) {
+ case faHidden:
+ case faReadOnly: {
+ result = true;
+ break;
+ }
+ default:
+ break;
+ }
+
+ return result;
+}
+
+void FileAttributes::Append(FileAttribute Value) {
+ if (Valid(Value) == true) {
+ FAttributes.push_back(Value);
+ WriteAttributes();
+ }
+}
+
+bool FileAttributes::Contains(FileAttribute Value) {
+ bool result = false;
+
+ std::vector<FileAttribute>::const_iterator iterator =
+ std::find(FAttributes.begin(), FAttributes.end(), Value);
+
+ if (iterator != FAttributes.end()) {
+ result = true;
+ }
+
+ return result;
+}
+
+void FileAttributes::Remove(FileAttribute Value) {
+ if (Valid(Value) == true) {
+ std::vector<FileAttribute>::iterator iterator =
+ std::find(FAttributes.begin(), FAttributes.end(), Value);
+
+ if (iterator != FAttributes.end()) {
+ FAttributes.erase(iterator);
+ WriteAttributes();
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libapplauncher/PlatformDefs.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#ifndef PLATFORM_DEFS_H
+#define PLATFORM_DEFS_H
+
+// Define Windows compatibility requirements XP or later
+#define WINVER 0x0600
+#define _WIN32_WINNT 0x0600
+
+#include <Windows.h>
+#include <tchar.h>
+#include <shlobj.h>
+#include <direct.h>
+#include <process.h>
+#include <malloc.h>
+#include <string>
+
+using namespace std;
+
+#ifndef WINDOWS
+#define WINDOWS
+#endif
+
+typedef std::wstring TString;
+#define StringLength wcslen
+
+#define TRAILING_PATHSEPARATOR '\\'
+#define BAD_TRAILING_PATHSEPARATOR '/'
+#define PATH_SEPARATOR ';'
+#define BAD_PATH_SEPARATOR ':'
+
+typedef ULONGLONG TPlatformNumber;
+typedef DWORD TProcessID;
+
+typedef void* Module;
+typedef void* Procedure;
+
+#endif // PLATFORM_DEFS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libapplauncher/WindowsPlatform.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,759 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#include "Platform.h"
+
+#include "JavaVirtualMachine.h"
+#include "WindowsPlatform.h"
+#include "Package.h"
+#include "Helpers.h"
+#include "PlatformString.h"
+#include "Macros.h"
+
+#include <map>
+#include <vector>
+#include <regex>
+#include <fstream>
+#include <locale>
+#include <codecvt>
+
+using namespace std;
+
+#define WINDOWS_JPACKAGE_TMP_DIR \
+ L"\\AppData\\Local\\Java\\JPackage\\tmp"
+
+class Registry {
+private:
+ HKEY FKey;
+ HKEY FOpenKey;
+ bool FOpen;
+
+public:
+
+ Registry(HKEY Key) {
+ FOpen = false;
+ FKey = Key;
+ }
+
+ ~Registry() {
+ Close();
+ }
+
+ void Close() {
+ if (FOpen == true) {
+ RegCloseKey(FOpenKey);
+ }
+ }
+
+ bool Open(TString SubKey) {
+ bool result = false;
+ Close();
+
+ if (RegOpenKeyEx(FKey, SubKey.data(), 0, KEY_READ, &FOpenKey) ==
+ ERROR_SUCCESS) {
+ result = true;
+ }
+
+ return result;
+ }
+
+ std::list<TString> GetKeys() {
+ std::list<TString> result;
+ DWORD count;
+
+ if (RegQueryInfoKey(FOpenKey, NULL, NULL, NULL, NULL, NULL, NULL,
+ &count, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) {
+
+ DWORD length = 255;
+ DynamicBuffer<TCHAR> buffer(length);
+ if (buffer.GetData() == NULL) {
+ return result;
+ }
+
+ for (unsigned int index = 0; index < count; index++) {
+ buffer.Zero();
+ DWORD status = RegEnumValue(FOpenKey, index, buffer.GetData(),
+ &length, NULL, NULL, NULL, NULL);
+
+ while (status == ERROR_MORE_DATA) {
+ length = length * 2;
+ if (!buffer.Resize(length)) {
+ return result;
+ }
+ status = RegEnumValue(FOpenKey, index, buffer.GetData(),
+ &length, NULL, NULL, NULL, NULL);
+ }
+
+ if (status == ERROR_SUCCESS) {
+ TString value = buffer.GetData();
+ result.push_back(value);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ TString ReadString(TString Name) {
+ TString result;
+ DWORD length;
+ DWORD dwRet;
+ DynamicBuffer<wchar_t> buffer(0);
+ length = 0;
+
+ dwRet = RegQueryValueEx(FOpenKey, Name.data(), NULL, NULL, NULL,
+ &length);
+ if (dwRet == ERROR_MORE_DATA || dwRet == 0) {
+ if (!buffer.Resize(length + 1)) {
+ return result;
+ }
+ dwRet = RegQueryValueEx(FOpenKey, Name.data(), NULL, NULL,
+ (LPBYTE) buffer.GetData(), &length);
+ result = buffer.GetData();
+ }
+
+ return result;
+ }
+};
+
+WindowsPlatform::WindowsPlatform(void) : Platform() {
+ FMainThread = ::GetCurrentThreadId();
+}
+
+WindowsPlatform::~WindowsPlatform(void) {
+}
+
+TString WindowsPlatform::GetPackageAppDirectory() {
+ return FilePath::IncludeTrailingSeparator(
+ GetPackageRootDirectory()) + _T("app");
+}
+
+TString WindowsPlatform::GetPackageLauncherDirectory() {
+ return GetPackageRootDirectory();
+}
+
+TString WindowsPlatform::GetPackageRuntimeBinDirectory() {
+ return FilePath::IncludeTrailingSeparator(GetPackageRootDirectory()) + _T("runtime\\bin");
+}
+
+TCHAR* WindowsPlatform::ConvertStringToFileSystemString(TCHAR* Source,
+ bool &release) {
+ // Not Implemented.
+ return NULL;
+}
+
+TCHAR* WindowsPlatform::ConvertFileSystemStringToString(TCHAR* Source,
+ bool &release) {
+ // Not Implemented.
+ return NULL;
+}
+
+TString WindowsPlatform::GetPackageRootDirectory() {
+ TString result;
+ TString filename = GetModuleFileName();
+ return FilePath::ExtractFilePath(filename);
+}
+
+TString WindowsPlatform::GetAppDataDirectory() {
+ TString result;
+ TCHAR path[MAX_PATH];
+
+ if (SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, path) == S_OK) {
+ result = path;
+ }
+
+ return result;
+}
+
+TString WindowsPlatform::GetAppName() {
+ TString result = GetModuleFileName();
+ result = FilePath::ExtractFileName(result);
+ result = FilePath::ChangeFileExt(result, _T(""));
+ return result;
+}
+
+void WindowsPlatform::ShowMessage(TString title, TString description) {
+ MessageBox(NULL, description.data(),
+ !title.empty() ? title.data() : description.data(),
+ MB_ICONERROR | MB_OK);
+}
+
+void WindowsPlatform::ShowMessage(TString description) {
+ TString appname = GetModuleFileName();
+ appname = FilePath::ExtractFileName(appname);
+ MessageBox(NULL, description.data(), appname.data(), MB_ICONERROR | MB_OK);
+}
+
+MessageResponse WindowsPlatform::ShowResponseMessage(TString title,
+ TString description) {
+ MessageResponse result = mrCancel;
+
+ if (::MessageBox(NULL, description.data(), title.data(), MB_OKCANCEL) ==
+ IDOK) {
+ result = mrOK;
+ }
+
+ return result;
+}
+
+TString WindowsPlatform::GetBundledJavaLibraryFileName(TString RuntimePath) {
+ TString result = FilePath::IncludeTrailingSeparator(RuntimePath) +
+ _T("jre\\bin\\jli.dll");
+
+ if (FilePath::FileExists(result) == false) {
+ result = FilePath::IncludeTrailingSeparator(RuntimePath) +
+ _T("bin\\jli.dll");
+ }
+
+ return result;
+}
+
+ISectionalPropertyContainer* WindowsPlatform::GetConfigFile(TString FileName) {
+ IniFile *result = new IniFile();
+ if (result == NULL) {
+ return NULL;
+ }
+
+ result->LoadFromFile(FileName);
+
+ return result;
+}
+
+TString WindowsPlatform::GetModuleFileName() {
+ TString result;
+ DynamicBuffer<wchar_t> buffer(MAX_PATH);
+ if (buffer.GetData() == NULL) {
+ return result;
+ }
+
+ ::GetModuleFileName(NULL, buffer.GetData(),
+ static_cast<DWORD> (buffer.GetSize()));
+
+ while (ERROR_INSUFFICIENT_BUFFER == GetLastError()) {
+ if (!buffer.Resize(buffer.GetSize() * 2)) {
+ return result;
+ }
+ ::GetModuleFileName(NULL, buffer.GetData(),
+ static_cast<DWORD> (buffer.GetSize()));
+ }
+
+ result = buffer.GetData();
+ return result;
+}
+
+Module WindowsPlatform::LoadLibrary(TString FileName) {
+ return ::LoadLibrary(FileName.data());
+}
+
+void WindowsPlatform::FreeLibrary(Module AModule) {
+ ::FreeLibrary((HMODULE) AModule);
+}
+
+Procedure WindowsPlatform::GetProcAddress(Module AModule,
+ std::string MethodName) {
+ return ::GetProcAddress((HMODULE) AModule, MethodName.c_str());
+}
+
+bool WindowsPlatform::IsMainThread() {
+ bool result = (FMainThread == ::GetCurrentThreadId());
+ return result;
+}
+
+TString WindowsPlatform::GetTempDirectory() {
+ TString result;
+ PWSTR userDir = 0;
+
+ if (SUCCEEDED(SHGetKnownFolderPath(
+ FOLDERID_Profile,
+ 0,
+ NULL,
+ &userDir))) {
+ result = userDir;
+ result += WINDOWS_JPACKAGE_TMP_DIR;
+ CoTaskMemFree(userDir);
+ }
+
+ return result;
+}
+
+static BOOL CALLBACK enumWindows(HWND winHandle, LPARAM lParam) {
+ DWORD pid = (DWORD) lParam, wPid = 0;
+ GetWindowThreadProcessId(winHandle, &wPid);
+ if (pid == wPid) {
+ SetForegroundWindow(winHandle);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+TPlatformNumber WindowsPlatform::GetMemorySize() {
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+ size_t result = (size_t) si.lpMaximumApplicationAddress;
+ result = result / 1048576; // Convert from bytes to megabytes.
+ return result;
+}
+
+std::vector<TString> FilterList(std::vector<TString> &Items,
+ std::wregex Pattern) {
+ std::vector<TString> result;
+
+ for (std::vector<TString>::iterator it = Items.begin();
+ it != Items.end(); ++it) {
+ TString item = *it;
+ std::wsmatch match;
+
+ if (std::regex_search(item, match, Pattern)) {
+ result.push_back(item);
+ }
+ }
+ return result;
+}
+
+Process* WindowsPlatform::CreateProcess() {
+ return new WindowsProcess();
+}
+
+void WindowsPlatform::InitStreamLocale(wios *stream) {
+ const std::locale empty_locale = std::locale::empty();
+ const std::locale utf8_locale =
+ std::locale(empty_locale, new std::codecvt_utf8<wchar_t>());
+ stream->imbue(utf8_locale);
+}
+
+void WindowsPlatform::addPlatformDependencies(JavaLibrary *pJavaLibrary) {
+ if (pJavaLibrary == NULL) {
+ return;
+ }
+
+ if (FilePath::FileExists(_T("msvcr100.dll")) == true) {
+ pJavaLibrary->AddDependency(_T("msvcr100.dll"));
+ }
+
+ TString runtimeBin = GetPackageRuntimeBinDirectory();
+ SetDllDirectory(runtimeBin.c_str());
+}
+
+void Platform::CopyString(char *Destination,
+ size_t NumberOfElements, const char *Source) {
+ strcpy_s(Destination, NumberOfElements, Source);
+
+ if (NumberOfElements > 0) {
+ Destination[NumberOfElements - 1] = '\0';
+ }
+}
+
+void Platform::CopyString(wchar_t *Destination,
+ size_t NumberOfElements, const wchar_t *Source) {
+ wcscpy_s(Destination, NumberOfElements, Source);
+
+ if (NumberOfElements > 0) {
+ Destination[NumberOfElements - 1] = '\0';
+ }
+}
+
+// Owner must free the return value.
+MultibyteString Platform::WideStringToMultibyteString(
+ const wchar_t* value) {
+ MultibyteString result;
+ size_t count = 0;
+
+ if (value == NULL) {
+ return result;
+ }
+
+ count = WideCharToMultiByte(CP_UTF8, 0, value, -1, NULL, 0, NULL, NULL);
+
+ if (count > 0) {
+ result.data = new char[count + 1];
+ result.length = WideCharToMultiByte(CP_UTF8, 0, value, -1,
+ result.data, (int)count, NULL, NULL);
+ }
+
+ return result;
+}
+
+// Owner must free the return value.
+WideString Platform::MultibyteStringToWideString(const char* value) {
+ WideString result;
+ size_t count = 0;
+
+ if (value == NULL) {
+ return result;
+ }
+
+ mbstowcs_s(&count, NULL, 0, value, _TRUNCATE);
+
+ if (count > 0) {
+ result.data = new wchar_t[count + 1];
+ mbstowcs_s(&result.length, result.data, count, value, count);
+ }
+
+ return result;
+}
+
+FileHandle::FileHandle(std::wstring FileName) {
+ FHandle = ::CreateFile(FileName.data(), GENERIC_READ, FILE_SHARE_READ,
+ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+}
+
+FileHandle::~FileHandle() {
+ if (IsValid() == true) {
+ ::CloseHandle(FHandle);
+ }
+}
+
+bool FileHandle::IsValid() {
+ return FHandle != INVALID_HANDLE_VALUE;
+}
+
+HANDLE FileHandle::GetHandle() {
+ return FHandle;
+}
+
+FileMappingHandle::FileMappingHandle(HANDLE FileHandle) {
+ FHandle = ::CreateFileMapping(FileHandle, NULL, PAGE_READONLY, 0, 0, NULL);
+}
+
+bool FileMappingHandle::IsValid() {
+ return FHandle != NULL;
+}
+
+FileMappingHandle::~FileMappingHandle() {
+ if (IsValid() == true) {
+ ::CloseHandle(FHandle);
+ }
+}
+
+HANDLE FileMappingHandle::GetHandle() {
+ return FHandle;
+}
+
+FileData::FileData(HANDLE Handle) {
+ FBaseAddress = ::MapViewOfFile(Handle, FILE_MAP_READ, 0, 0, 0);
+}
+
+FileData::~FileData() {
+ if (IsValid() == true) {
+ ::UnmapViewOfFile(FBaseAddress);
+ }
+}
+
+bool FileData::IsValid() {
+ return FBaseAddress != NULL;
+}
+
+LPVOID FileData::GetBaseAddress() {
+ return FBaseAddress;
+}
+
+WindowsLibrary::WindowsLibrary(std::wstring FileName) {
+ FFileName = FileName;
+}
+
+std::vector<TString> WindowsLibrary::GetImports() {
+ std::vector<TString> result;
+ FileHandle library(FFileName);
+
+ if (library.IsValid() == true) {
+ FileMappingHandle mapping(library.GetHandle());
+
+ if (mapping.IsValid() == true) {
+ FileData fileData(mapping.GetHandle());
+
+ if (fileData.IsValid() == true) {
+ PIMAGE_DOS_HEADER dosHeader =
+ (PIMAGE_DOS_HEADER) fileData.GetBaseAddress();
+ PIMAGE_FILE_HEADER pImgFileHdr =
+ (PIMAGE_FILE_HEADER) fileData.GetBaseAddress();
+ if (dosHeader->e_magic == IMAGE_DOS_SIGNATURE) {
+ result = DumpPEFile(dosHeader);
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+// Given an RVA, look up the section header that encloses it and return a
+// pointer to its IMAGE_SECTION_HEADER
+
+PIMAGE_SECTION_HEADER WindowsLibrary::GetEnclosingSectionHeader(DWORD rva,
+ PIMAGE_NT_HEADERS pNTHeader) {
+ PIMAGE_SECTION_HEADER result = 0;
+ PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(pNTHeader);
+
+ for (unsigned index = 0; index < pNTHeader->FileHeader.NumberOfSections;
+ index++, section++) {
+ // Is the RVA is within this section?
+ if ((rva >= section->VirtualAddress) &&
+ (rva < (section->VirtualAddress + section->Misc.VirtualSize))) {
+ result = section;
+ }
+ }
+
+ return result;
+}
+
+LPVOID WindowsLibrary::GetPtrFromRVA(DWORD rva, PIMAGE_NT_HEADERS pNTHeader,
+ DWORD imageBase) {
+ LPVOID result = 0;
+ PIMAGE_SECTION_HEADER pSectionHdr = GetEnclosingSectionHeader(rva,
+ pNTHeader);
+
+ if (pSectionHdr != NULL) {
+ INT delta = (INT) (
+ pSectionHdr->VirtualAddress - pSectionHdr->PointerToRawData);
+ DWORD_PTR dwp = (DWORD_PTR) (imageBase + rva - delta);
+ result = reinterpret_cast<LPVOID> (dwp); // VS2017 - FIXME
+ }
+
+ return result;
+}
+
+std::vector<TString> WindowsLibrary::GetImportsSection(DWORD base,
+ PIMAGE_NT_HEADERS pNTHeader) {
+ std::vector<TString> result;
+
+ // Look up where the imports section is located. Normally in
+ // the .idata section,
+ // but not necessarily so. Therefore, grab the RVA from the data dir.
+ DWORD importsStartRVA = pNTHeader->OptionalHeader.DataDirectory[
+ IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
+
+ if (importsStartRVA != NULL) {
+ // Get the IMAGE_SECTION_HEADER that contains the imports. This is
+ // usually the .idata section, but doesn't have to be.
+ PIMAGE_SECTION_HEADER pSection =
+ GetEnclosingSectionHeader(importsStartRVA, pNTHeader);
+
+ if (pSection != NULL) {
+ PIMAGE_IMPORT_DESCRIPTOR importDesc =
+ (PIMAGE_IMPORT_DESCRIPTOR) GetPtrFromRVA(
+ importsStartRVA, pNTHeader, base);
+
+ if (importDesc != NULL) {
+ while (true) {
+ // See if we've reached an empty IMAGE_IMPORT_DESCRIPTOR
+ if ((importDesc->TimeDateStamp == 0) &&
+ (importDesc->Name == 0)) {
+ break;
+ }
+
+ std::string filename = (char*) GetPtrFromRVA(
+ importDesc->Name, pNTHeader, base);
+ result.push_back(PlatformString(filename));
+ importDesc++; // advance to next IMAGE_IMPORT_DESCRIPTOR
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+std::vector<TString> WindowsLibrary::DumpPEFile(PIMAGE_DOS_HEADER dosHeader) {
+ std::vector<TString> result;
+ // all of this is VS2017 - FIXME
+ DWORD_PTR dwDosHeaders = reinterpret_cast<DWORD_PTR> (dosHeader);
+ DWORD_PTR dwPIHeaders = dwDosHeaders + (DWORD) (dosHeader->e_lfanew);
+
+ PIMAGE_NT_HEADERS pNTHeader =
+ reinterpret_cast<PIMAGE_NT_HEADERS> (dwPIHeaders);
+
+ // Verify that the e_lfanew field gave us a reasonable
+ // pointer and the PE signature.
+ // TODO: To really fix JDK-8131321 this condition needs to be changed.
+ // There is a matching change
+ // in JavaVirtualMachine.cpp that also needs to be changed.
+ if (pNTHeader->Signature == IMAGE_NT_SIGNATURE) {
+ DWORD base = (DWORD) (dwDosHeaders);
+ result = GetImportsSection(base, pNTHeader);
+ }
+
+ return result;
+}
+
+#include <TlHelp32.h>
+
+WindowsJob::WindowsJob() {
+ FHandle = NULL;
+}
+
+WindowsJob::~WindowsJob() {
+ if (FHandle != NULL) {
+ CloseHandle(FHandle);
+ }
+}
+
+HANDLE WindowsJob::GetHandle() {
+ if (FHandle == NULL) {
+ FHandle = CreateJobObject(NULL, NULL); // GLOBAL
+
+ if (FHandle == NULL) {
+ ::MessageBox(0, _T("Could not create job object"),
+ _T("TEST"), MB_OK);
+ } else {
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {0};
+
+ // Configure all child processes associated with
+ // the job to terminate when the
+ jeli.BasicLimitInformation.LimitFlags =
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+ if (0 == SetInformationJobObject(FHandle,
+ JobObjectExtendedLimitInformation, &jeli, sizeof (jeli))) {
+ ::MessageBox(0, _T("Could not SetInformationJobObject"),
+ _T("TEST"), MB_OK);
+ }
+ }
+ }
+
+ return FHandle;
+}
+
+// Initialize static member of WindowsProcess
+WindowsJob WindowsProcess::FJob;
+
+WindowsProcess::WindowsProcess() : Process() {
+ FRunning = false;
+}
+
+WindowsProcess::~WindowsProcess() {
+ Terminate();
+}
+
+void WindowsProcess::Cleanup() {
+ CloseHandle(FProcessInfo.hProcess);
+ CloseHandle(FProcessInfo.hThread);
+}
+
+bool WindowsProcess::IsRunning() {
+ bool result = false;
+
+ HANDLE handle = ::CreateToolhelp32Snapshot(TH32CS_SNAPALL, 0);
+ if (handle == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ PROCESSENTRY32 process = {0};
+ process.dwSize = sizeof (process);
+
+ if (::Process32First(handle, &process)) {
+ do {
+ if (process.th32ProcessID == FProcessInfo.dwProcessId) {
+ result = true;
+ break;
+ }
+ } while (::Process32Next(handle, &process));
+ }
+
+ CloseHandle(handle);
+
+ return result;
+}
+
+bool WindowsProcess::Terminate() {
+ bool result = false;
+
+ if (IsRunning() == true && FRunning == true) {
+ FRunning = false;
+ }
+
+ return result;
+}
+
+bool WindowsProcess::Execute(const TString Application,
+ const std::vector<TString> Arguments, bool AWait) {
+ bool result = false;
+
+ if (FRunning == false) {
+ FRunning = true;
+
+ STARTUPINFO startupInfo;
+ ZeroMemory(&startupInfo, sizeof (startupInfo));
+ startupInfo.cb = sizeof (startupInfo);
+ ZeroMemory(&FProcessInfo, sizeof (FProcessInfo));
+
+ TString command = Application;
+
+ for (std::vector<TString>::const_iterator iterator = Arguments.begin();
+ iterator != Arguments.end(); iterator++) {
+ command += TString(_T(" ")) + *iterator;
+ }
+
+ if (::CreateProcess(Application.data(), (wchar_t*)command.data(), NULL,
+ NULL, FALSE, 0, NULL, NULL, &startupInfo, &FProcessInfo)
+ == FALSE) {
+ TString message = PlatformString::Format(
+ _T("Error: Unable to create process %s"),
+ Application.data());
+ throw Exception(message);
+ } else {
+ if (FJob.GetHandle() != NULL) {
+ if (::AssignProcessToJobObject(FJob.GetHandle(),
+ FProcessInfo.hProcess) == 0) {
+ // Failed to assign process to job. It doesn't prevent
+ // anything from continuing so continue.
+ }
+ }
+
+ // Wait until child process exits.
+ if (AWait == true) {
+ Wait();
+ // Close process and thread handles.
+ Cleanup();
+ }
+ }
+ }
+
+ return result;
+}
+
+bool WindowsProcess::Wait() {
+ bool result = false;
+
+ WaitForSingleObject(FProcessInfo.hProcess, INFINITE);
+ return result;
+}
+
+TProcessID WindowsProcess::GetProcessID() {
+ return FProcessInfo.dwProcessId;
+}
+
+bool WindowsProcess::ReadOutput() {
+ bool result = false;
+ // TODO implement
+ return result;
+}
+
+void WindowsProcess::SetInput(TString Value) {
+ // TODO implement
+}
+
+std::list<TString> WindowsProcess::GetOutput() {
+ ReadOutput();
+ return Process::GetOutput();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libapplauncher/WindowsPlatform.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2014, 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.
+ */
+
+#ifndef WINDOWSPLATFORM_H
+#define WINDOWSPLATFORM_H
+
+#include <Windows.h>
+#include "Platform.h"
+
+class WindowsPlatform : virtual public Platform {
+private:
+ DWORD FMainThread;
+
+public:
+ WindowsPlatform(void);
+ virtual ~WindowsPlatform(void);
+
+ virtual TCHAR* ConvertStringToFileSystemString(TCHAR* Source,
+ bool &release);
+ virtual TCHAR* ConvertFileSystemStringToString(TCHAR* Source,
+ bool &release);
+
+ virtual void ShowMessage(TString title, TString description);
+ virtual void ShowMessage(TString description);
+ virtual MessageResponse ShowResponseMessage(TString title,
+ TString description);
+
+ virtual TString GetPackageRootDirectory();
+ virtual TString GetAppDataDirectory();
+ virtual TString GetAppName();
+ virtual TString GetBundledJavaLibraryFileName(TString RuntimePath);
+ TString GetPackageAppDirectory();
+ TString GetPackageLauncherDirectory();
+ TString GetPackageRuntimeBinDirectory();
+
+ virtual ISectionalPropertyContainer* GetConfigFile(TString FileName);
+
+ virtual TString GetModuleFileName();
+ virtual Module LoadLibrary(TString FileName);
+ virtual void FreeLibrary(Module AModule);
+ virtual Procedure GetProcAddress(Module AModule, std::string MethodName);
+
+ virtual Process* CreateProcess();
+
+ virtual bool IsMainThread();
+ virtual TPlatformNumber GetMemorySize();
+
+ virtual TString GetTempDirectory();
+ void InitStreamLocale(wios *stream);
+ void addPlatformDependencies(JavaLibrary *pJavaLibrary);
+};
+
+class FileHandle {
+private:
+ HANDLE FHandle;
+
+public:
+ FileHandle(std::wstring FileName);
+ ~FileHandle();
+
+ bool IsValid();
+ HANDLE GetHandle();
+};
+
+
+class FileMappingHandle {
+private:
+ HANDLE FHandle;
+
+public:
+ FileMappingHandle(HANDLE FileHandle);
+ ~FileMappingHandle();
+
+ bool IsValid();
+ HANDLE GetHandle();
+};
+
+
+class FileData {
+private:
+ LPVOID FBaseAddress;
+
+public:
+ FileData(HANDLE Handle);
+ ~FileData();
+
+ bool IsValid();
+ LPVOID GetBaseAddress();
+};
+
+
+class WindowsLibrary {
+private:
+ TString FFileName;
+
+ // Given an RVA, look up the section header that encloses it and return a
+ // pointer to its IMAGE_SECTION_HEADER
+ static PIMAGE_SECTION_HEADER GetEnclosingSectionHeader(DWORD rva,
+ PIMAGE_NT_HEADERS pNTHeader);
+ static LPVOID GetPtrFromRVA(DWORD rva, PIMAGE_NT_HEADERS pNTHeader,
+ DWORD imageBase);
+ static std::vector<TString> GetImportsSection(DWORD base,
+ PIMAGE_NT_HEADERS pNTHeader);
+ static std::vector<TString> DumpPEFile(PIMAGE_DOS_HEADER dosHeader);
+
+public:
+ WindowsLibrary(const TString FileName);
+
+ std::vector<TString> GetImports();
+};
+
+
+class WindowsJob {
+private:
+ HANDLE FHandle;
+
+public:
+ WindowsJob();
+ ~WindowsJob();
+
+ HANDLE GetHandle();
+};
+
+
+class WindowsProcess : public Process {
+private:
+ bool FRunning;
+
+ PROCESS_INFORMATION FProcessInfo;
+ static WindowsJob FJob;
+
+ void Cleanup();
+ bool ReadOutput();
+
+public:
+ WindowsProcess();
+ virtual ~WindowsProcess();
+
+ virtual bool IsRunning();
+ virtual bool Terminate();
+ virtual bool Execute(const TString Application,
+ const std::vector<TString> Arguments, bool AWait = false);
+ virtual bool Wait();
+ virtual TProcessID GetProcessID();
+ virtual void SetInput(TString Value);
+ virtual std::list<TString> GetOutput();
+};
+
+#endif // WINDOWSPLATFORM_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/ByteBuffer.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+#include "ByteBuffer.h"
+
+#include <stdio.h>
+
+ByteBuffer::ByteBuffer() {
+ buffer.reserve(1024);
+}
+
+ByteBuffer::~ByteBuffer() {
+}
+
+LPBYTE ByteBuffer::getPtr() {
+ return &buffer[0];
+}
+
+size_t ByteBuffer::getPos() {
+ return buffer.size();
+}
+
+void ByteBuffer::AppendString(wstring str) {
+ size_t len = (str.size() + 1) * sizeof (WCHAR);
+ AppendBytes((BYTE*) str.c_str(), len);
+}
+
+void ByteBuffer::AppendWORD(WORD word) {
+ AppendBytes((BYTE*) & word, sizeof (WORD));
+}
+
+void ByteBuffer::Align(size_t bytesNumber) {
+ size_t pos = getPos();
+ if (pos % bytesNumber) {
+ DWORD dwNull = 0;
+ size_t len = bytesNumber - pos % bytesNumber;
+ AppendBytes((BYTE*) & dwNull, len);
+ }
+}
+
+void ByteBuffer::AppendBytes(BYTE* ptr, size_t len) {
+ buffer.insert(buffer.end(), ptr, ptr + len);
+}
+
+void ByteBuffer::ReplaceWORD(size_t offset, WORD word) {
+ ReplaceBytes(offset, (BYTE*) & word, sizeof (WORD));
+}
+
+void ByteBuffer::ReplaceBytes(size_t offset, BYTE* ptr, size_t len) {
+ for (size_t i = 0; i < len; i++) {
+ buffer[offset + i] = *(ptr + i);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/ByteBuffer.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+#ifndef BYTEBUFFER_H
+#define BYTEBUFFER_H
+
+#include <windows.h>
+#include <vector>
+#include <string>
+
+using namespace std;
+
+class ByteBuffer {
+public:
+ ByteBuffer();
+ ~ByteBuffer();
+
+ LPBYTE getPtr();
+ size_t getPos();
+
+ void AppendString(wstring str);
+ void AppendWORD(WORD word);
+ void AppendBytes(BYTE* ptr, size_t len);
+
+ void ReplaceWORD(size_t offset, WORD word);
+ void ReplaceBytes(size_t offset, BYTE* ptr, size_t len);
+
+ void Align(size_t bytesNumber);
+
+private:
+ vector<BYTE> buffer;
+};
+
+#endif // BYTEBUFFER_H
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/ErrorHandling.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+#include <algorithm>
+
+#include "ErrorHandling.h"
+#include "Log.h"
+
+
+namespace {
+
+tstring getFilename(const SourceCodePos& pos) {
+ const std::string buf(pos.file);
+ const std::string::size_type idx = buf.find_last_of("\\/");
+ if (idx == std::string::npos) {
+ return tstrings::fromUtf8(buf);
+ }
+ return tstrings::fromUtf8(buf.substr(idx + 1));
+}
+
+void reportError(const SourceCodePos& pos, const tstring& msg) {
+ Logger::defaultLogger().log(Logger::LOG_ERROR, getFilename(pos).c_str(),
+ pos.lno, tstrings::fromUtf8(pos.func).c_str(), msg);
+}
+
+} // namespace
+
+void reportError(const SourceCodePos& pos, const std::exception& e) {
+ reportError(pos, (tstrings::any() << "Exception with message \'"
+ << e.what() << "\' caught").tstr());
+}
+
+
+void reportUnknownError(const SourceCodePos& pos) {
+ reportError(pos, _T("Unknown exception caught"));
+}
+
+
+std::string makeMessage(const std::exception& e, const SourceCodePos& pos) {
+ std::ostringstream printer;
+ printer << getFilename(pos) << "(" << pos.lno << ") at "
+ << pos.func << "(): "
+ << e.what();
+ return printer.str();
+}
+
+
+namespace {
+
+bool isNotSpace(int chr) {
+ return isspace(chr) == 0;
+}
+
+
+enum TrimMode {
+ TrimLeading = 0x10,
+ TrimTrailing = 0x20,
+ TrimBoth = TrimLeading | TrimTrailing
+};
+
+// Returns position of the last printed character in the given string.
+// Returns std::string::npos if nothing was printed.
+size_t printWithoutWhitespaces(std::ostream& out, const std::string& str,
+ TrimMode mode) {
+ std::string::const_reverse_iterator it = str.rbegin();
+ std::string::const_reverse_iterator end = str.rend();
+
+ if (mode & TrimLeading) {
+ // skip leading whitespace
+ std::string::const_iterator entry = std::find_if(str.begin(),
+ str.end(), isNotSpace);
+ end = std::string::const_reverse_iterator(entry);
+ }
+
+ if (mode & TrimTrailing) {
+ // skip trailing whitespace
+ it = std::find_if(it, end, isNotSpace);
+ }
+
+ if (it == end) {
+ return std::string::npos;
+ }
+
+ const size_t pos = str.rend() - end;
+ const size_t len = end - it;
+ out.write(str.c_str() + pos, len);
+ return pos + len - 1;
+}
+
+} // namespace
+
+std::string joinErrorMessages(const std::string& a, const std::string& b) {
+ const std::string endPhraseChars(";.,:!?");
+ const std::string space(" ");
+ const std::string dotAndSpace(". ");
+
+ std::ostringstream printer;
+ printer.exceptions(std::ios::failbit | std::ios::badbit);
+
+ size_t idx = printWithoutWhitespaces(printer, a, TrimTrailing);
+ size_t extra = 0;
+ if (idx < a.size() && endPhraseChars.find(a[idx]) == std::string::npos) {
+ printer << dotAndSpace;
+ extra = dotAndSpace.size();
+ } else if (idx != std::string::npos) {
+ printer << space;
+ extra = space.size();
+ }
+
+ idx = printWithoutWhitespaces(printer, b, TrimBoth);
+
+ const std::string str = printer.str();
+
+ if (std::string::npos == idx && extra) {
+ // Nothing printed from the 'b' message. Backout delimiter string.
+ return str.substr(0, str.size() - extra);
+ }
+ return str;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/ErrorHandling.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+
+#ifndef ErrorHandling_h
+#define ErrorHandling_h
+
+
+#include <stdexcept>
+
+#include "SourceCodePos.h"
+#include "tstrings.h"
+
+
+//
+// Exception handling helpers. Allow transparent exception logging.
+// Use as follows:
+//
+// void foo () {
+// JP_TRY;
+//
+// if (!do_something()) {
+// JP_THROW("do_something() failed");
+// }
+//
+// JP_CATCH_ALL;
+// }
+//
+
+
+// Logs std::exception caught at 'pos'.
+void reportError(const SourceCodePos& pos, const std::exception& e);
+// Logs unknown exception caught at 'pos'.
+// Assumed to be called from catch (...) {}
+void reportUnknownError(const SourceCodePos& pos);
+
+std::string makeMessage(const std::exception& e, const SourceCodePos& pos);
+
+std::string joinErrorMessages(const std::string& a, const std::string& b);
+
+
+template <class Base>
+class JpError: public Base {
+public:
+ JpError(const Base& e, const SourceCodePos& pos):
+ Base(e), msg(::makeMessage(e, pos)) {
+ }
+
+ ~JpError() throw() {
+ }
+
+ // override Base::what()
+ const char* what() const throw() {
+ return msg.c_str();
+ }
+private:
+ // Assert Base is derived from std::exception
+ enum { isDerivedFromStdException =
+ sizeof(static_cast<std::exception*>((Base*)0)) };
+
+ std::string msg;
+};
+
+template <class T>
+inline JpError<T> makeException(const T& obj, const SourceCodePos& p) {
+ return JpError<T>(obj, p);
+}
+
+inline JpError<std::runtime_error> makeException(
+ const std::string& msg, const SourceCodePos& p) {
+ return JpError<std::runtime_error>(std::runtime_error(msg), p);
+}
+
+inline JpError<std::runtime_error> makeException(
+ const tstrings::any& msg, const SourceCodePos& p) {
+ return makeException(msg.str(), p);
+}
+
+inline JpError<std::runtime_error> makeException(
+ std::string::const_pointer msg, const SourceCodePos& p) {
+ return makeException(std::string(msg), p);
+}
+
+
+#define JP_REPORT_ERROR(e) reportError(JP_SOURCE_CODE_POS, e)
+#define JP_REPORT_UNKNOWN_ERROR reportUnknownError(JP_SOURCE_CODE_POS)
+
+// Redefine locally in cpp file(s) if need more handling than just reporting
+#define JP_HANDLE_ERROR(e) JP_REPORT_ERROR(e)
+#define JP_HANDLE_UNKNOWN_ERROR JP_REPORT_UNKNOWN_ERROR
+
+
+#define JP_TRY \
+ try \
+ { \
+ do {} while(0)
+
+#define JP_DEFAULT_CATCH_EXCEPTIONS \
+ JP_CATCH_STD_EXCEPTION \
+ JP_CATCH_UNKNOWN_EXCEPTION
+
+#define JP_CATCH_EXCEPTIONS \
+ JP_DEFAULT_CATCH_EXCEPTIONS
+
+#define JP_CATCH_ALL \
+ } \
+ JP_CATCH_EXCEPTIONS \
+ do {} while(0)
+
+#define JP_CATCH_STD_EXCEPTION \
+ catch (const std::exception& e) \
+ { \
+ JP_HANDLE_ERROR(e); \
+ }
+
+#define JP_CATCH_UNKNOWN_EXCEPTION \
+ catch (...) \
+ { \
+ JP_HANDLE_UNKNOWN_ERROR; \
+ }
+
+
+#define JP_THROW(e) throw makeException((e), JP_SOURCE_CODE_POS)
+
+#define JP_NO_THROW(expr) \
+ JP_TRY; \
+ expr; \
+ JP_CATCH_ALL
+
+#endif // #ifndef ErrorHandling_h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/FileUtils.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,709 @@
+/*
+ * 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.
+ */
+
+#include <memory>
+#include <algorithm>
+#include <shlwapi.h>
+
+#include "FileUtils.h"
+#include "WinErrorHandling.h"
+#include "Log.h"
+
+
+// Needed by FileUtils::isDirectoryNotEmpty
+#pragma comment(lib, "shlwapi")
+
+
+namespace FileUtils {
+
+namespace {
+
+
+tstring reservedFilenameChars() {
+ tstring buf;
+ for (char charCode = 0; charCode < 32; ++charCode) {
+ buf.append(1, charCode);
+ }
+ buf += _T("<>:\"|?*/\\");
+ return buf;
+}
+
+} // namespace
+
+bool isDirSeparator(const tstring::value_type c) {
+ return (c == '/' || c == '\\');
+}
+
+bool isFileExists(const tstring &filePath) {
+ return GetFileAttributes(filePath.c_str()) != INVALID_FILE_ATTRIBUTES;
+}
+
+namespace {
+bool isDirectoryAttrs(const DWORD attrs) {
+ return attrs != INVALID_FILE_ATTRIBUTES
+ && (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0;
+}
+} // namespace
+
+bool isDirectory(const tstring &filePath) {
+ return isDirectoryAttrs(GetFileAttributes(filePath.c_str()));
+}
+
+bool isDirectoryNotEmpty(const tstring &dirPath) {
+ if (!isDirectory(dirPath)) {
+ return false;
+ }
+ return FALSE == PathIsDirectoryEmpty(dirPath.c_str());
+}
+
+tstring dirname(const tstring &path) {
+ tstring::size_type pos = path.find_last_of(_T("\\/"));
+ if (pos != tstring::npos) {
+ pos = path.find_last_not_of(_T("\\/"), pos); // skip trailing slashes
+ }
+ return pos == tstring::npos ? tstring() : path.substr(0, pos + 1);
+}
+
+tstring basename(const tstring &path) {
+ const tstring::size_type pos = path.find_last_of(_T("\\/"));
+ if (pos == tstring::npos) {
+ return path;
+ }
+ return path.substr(pos + 1);
+}
+
+tstring suffix(const tstring &path) {
+ const tstring::size_type pos = path.rfind('.');
+ if (pos == tstring::npos) {
+ return tstring();
+ }
+ const tstring::size_type dirSepPos = path.find_first_of(_T("\\/"),
+ pos + 1);
+ if (dirSepPos != tstring::npos) {
+ return tstring();
+ }
+ // test for '/..' and '..' cases
+ if (pos != 0 && path[pos - 1] == '.'
+ && (pos == 1 || isDirSeparator(path[pos - 2]))) {
+ return tstring();
+ }
+ return path.substr(pos);
+}
+
+tstring combinePath(const tstring& parent, const tstring& child) {
+ if (parent.empty()) {
+ return child;
+ }
+ if (child.empty()) {
+ return parent;
+ }
+
+ tstring parentWOSlash = removeTrailingSlash(parent);
+ // also handle the case when child contains starting slash
+ bool childHasSlash = isDirSeparator(child.front());
+ tstring childWOSlash = childHasSlash ? child.substr(1) : child;
+
+ return parentWOSlash + _T("\\") + childWOSlash;
+}
+
+tstring removeTrailingSlash(const tstring& path) {
+ if (path.empty()) {
+ return path;
+ }
+ tstring::const_reverse_iterator it = path.rbegin();
+ tstring::const_reverse_iterator end = path.rend();
+
+ while (it != end && isDirSeparator(*it)) {
+ ++it;
+ }
+ return path.substr(0, end - it);
+}
+
+tstring normalizePath(tstring v) {
+ std::replace(v.begin(), v.end(), '/', '\\');
+ return tstrings::toLower(v);
+}
+
+namespace {
+
+bool createNewFile(const tstring& path) {
+ HANDLE h = CreateFile(path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ // if the file exists => h == INVALID_HANDLE_VALUE & GetLastError
+ // returns ERROR_FILE_EXISTS
+ if (h != INVALID_HANDLE_VALUE) {
+ CloseHandle(h);
+ LOG_TRACE(tstrings::any() << "Created [" << path << "] file");
+ return true;
+ }
+ return false;
+}
+
+} // namespace
+
+tstring createTempFile(const tstring &prefix, const tstring &suffix,
+ const tstring &path) {
+ const tstring invalidChars = reservedFilenameChars();
+
+ if (prefix.find_first_of(invalidChars) != tstring::npos) {
+ JP_THROW(tstrings::any() << "Illegal characters in prefix=" << prefix);
+ }
+
+ if (suffix.find_first_of(invalidChars) != tstring::npos) {
+ JP_THROW(tstrings::any() << "Illegal characters in suffix=" << suffix);
+ }
+
+ int rnd = (int)GetTickCount();
+
+ // do no more than 100 attempts
+ for (int i=0; i<100; i++) {
+ const tstring filePath = mkpath() << path << (prefix
+ + (tstrings::any() << (rnd + i)).tstr() + suffix);
+ if (createNewFile(filePath)) {
+ return filePath;
+ }
+ }
+
+ // 100 attempts failed
+ JP_THROW(tstrings::any() << "createTempFile(" << prefix << ", "
+ << suffix << ", "
+ << path << ") failed");
+}
+
+tstring createTempDirectory(const tstring &prefix, const tstring &suffix,
+ const tstring &basedir) {
+ const tstring filePath = createTempFile(prefix, suffix, basedir);
+ // delete the file and create directory with the same name
+ deleteFile(filePath);
+ createDirectory(filePath);
+ return filePath;
+}
+
+tstring createUniqueFile(const tstring &prototype) {
+ if (createNewFile(prototype)) {
+ return prototype;
+ }
+
+ return createTempFile(replaceSuffix(basename(prototype)),
+ suffix(prototype), dirname(prototype));
+}
+
+namespace {
+
+void createDir(const tstring path, LPSECURITY_ATTRIBUTES saAttr,
+ tstring_array* createdDirs=0) {
+ if (CreateDirectory(path.c_str(), saAttr)) {
+ LOG_TRACE(tstrings::any() << "Created [" << path << "] directory");
+ if (createdDirs) {
+ createdDirs->push_back(removeTrailingSlash(path));
+ }
+ } else {
+ const DWORD createDirectoryErr = GetLastError();
+ // if saAttr is specified, fail even if the directory exists
+ if (saAttr != NULL || !isDirectory(path)) {
+ JP_THROW(SysError(tstrings::any() << "CreateDirectory("
+ << path << ") failed", CreateDirectory, createDirectoryErr));
+ }
+ }
+}
+
+}
+
+void createDirectory(const tstring &path, tstring_array* createdDirs) {
+ const tstring dirPath = removeTrailingSlash(path) + _T("\\");
+
+ tstring::size_type pos = dirPath.find_first_of(_T("\\/"));
+ while (pos != tstring::npos) {
+ const tstring subdirPath = dirPath.substr(0, pos + 1);
+ createDir(subdirPath, NULL, createdDirs);
+ pos = dirPath.find_first_of(_T("\\/"), pos + 1);
+ }
+}
+
+
+void copyFile(const tstring& fromPath, const tstring& toPath,
+ bool failIfExists) {
+ createDirectory(dirname(toPath));
+ if (!CopyFile(fromPath.c_str(), toPath.c_str(),
+ (failIfExists ? TRUE : FALSE))) {
+ JP_THROW(SysError(tstrings::any()
+ << "CopyFile(" << fromPath << ", " << toPath << ", "
+ << failIfExists << ") failed", CopyFile));
+ }
+ LOG_TRACE(tstrings::any() << "Copied [" << fromPath << "] file to ["
+ << toPath << "]");
+}
+
+
+namespace {
+
+void moveFileImpl(const tstring& fromPath, const tstring& toPath,
+ DWORD flags) {
+ const bool isDir = isDirectory(fromPath);
+ if (!MoveFileEx(fromPath.c_str(), toPath.empty() ? NULL : toPath.c_str(),
+ flags)) {
+ JP_THROW(SysError(tstrings::any() << "MoveFileEx(" << fromPath
+ << ", " << toPath << ", " << flags << ") failed", MoveFileEx));
+ }
+
+ const bool onReboot = 0 != (flags & MOVEFILE_DELAY_UNTIL_REBOOT);
+
+ const LPCTSTR label = isDir ? _T("folder") : _T("file");
+
+ tstrings::any msg;
+ if (!toPath.empty()) {
+ if (onReboot) {
+ msg << "Move";
+ } else {
+ msg << "Moved";
+ }
+ msg << " '" << fromPath << "' " << label << " to '" << toPath << "'";
+ } else {
+ if (onReboot) {
+ msg << "Delete";
+ } else {
+ msg << "Deleted";
+ }
+ msg << " '" << fromPath << "' " << label;
+ }
+ if (onReboot) {
+ msg << " on reboot";
+ }
+ LOG_TRACE(msg);
+}
+
+} // namespace
+
+
+void moveFile(const tstring& fromPath, const tstring& toPath,
+ bool failIfExists) {
+ createDirectory(dirname(toPath));
+
+ DWORD flags = MOVEFILE_COPY_ALLOWED;
+ if (!failIfExists) {
+ flags |= MOVEFILE_REPLACE_EXISTING;
+ }
+
+ moveFileImpl(fromPath, toPath, flags);
+}
+
+void deleteFile(const tstring &path)
+{
+ if (!deleteFile(path, std::nothrow)) {
+ JP_THROW(SysError(tstrings::any()
+ << "DeleteFile(" << path << ") failed", DeleteFile));
+ }
+}
+
+namespace {
+
+bool notFound(const DWORD status=GetLastError()) {
+ return status == ERROR_FILE_NOT_FOUND || status == ERROR_PATH_NOT_FOUND;
+}
+
+bool deleteFileImpl(const std::nothrow_t &, const tstring &path) {
+ const bool deleted = (DeleteFile(path.c_str()) != 0);
+ if (deleted) {
+ LOG_TRACE(tstrings::any() << "Deleted [" << path << "] file");
+ return true;
+ }
+ return notFound();
+}
+
+} // namespace
+
+bool deleteFile(const tstring &path, const std::nothrow_t &) throw()
+{
+ bool deleted = deleteFileImpl(std::nothrow, path);
+ const DWORD status = GetLastError();
+ if (!deleted && status == ERROR_ACCESS_DENIED) {
+ DWORD attrs = GetFileAttributes(path.c_str());
+ SetLastError(status);
+ if (attrs == INVALID_FILE_ATTRIBUTES) {
+ return false;
+ }
+ if (attrs & FILE_ATTRIBUTE_READONLY) {
+ // DeleteFile() failed because file is R/O.
+ // Remove R/O attribute and retry DeleteFile().
+ attrs &= ~FILE_ATTRIBUTE_READONLY;
+ if (SetFileAttributes(path.c_str(), attrs)) {
+ LOG_TRACE(tstrings::any() << "Discarded R/O attribute from ["
+ << path << "] file");
+ deleted = deleteFileImpl(std::nothrow, path);
+ } else {
+ LOG_WARNING(SysError(tstrings::any()
+ << "Failed to discard R/O attribute from ["
+ << path << "] file. File will not be deleted",
+ SetFileAttributes).what());
+ SetLastError(status);
+ }
+ }
+ }
+
+ return deleted || notFound();
+}
+
+void deleteDirectory(const tstring &path)
+{
+ if (!deleteDirectory(path, std::nothrow)) {
+ JP_THROW(SysError(tstrings::any()
+ << "RemoveDirectory(" << path << ") failed", RemoveDirectory));
+ }
+}
+
+bool deleteDirectory(const tstring &path, const std::nothrow_t &) throw()
+{
+ const bool deleted = (RemoveDirectory(path.c_str()) != 0);
+ if (deleted) {
+ LOG_TRACE(tstrings::any() << "Deleted [" << path << "] directory");
+ }
+ return deleted || notFound();
+}
+
+namespace {
+
+class DeleteFilesCallback: public DirectoryCallback {
+public:
+ explicit DeleteFilesCallback(bool ff): failfast(ff), failed(false) {
+ }
+
+ virtual bool onFile(const tstring& path) {
+ if (failfast) {
+ deleteFile(path);
+ } else {
+ updateStatus(deleteFile(path, std::nothrow));
+ }
+ return true;
+ }
+
+ bool good() const {
+ return !failed;
+ }
+
+protected:
+ void updateStatus(bool success) {
+ if (!success) {
+ failed = true;
+ }
+ }
+
+ const bool failfast;
+private:
+ bool failed;
+};
+
+class DeleteAllCallback: public DeleteFilesCallback {
+public:
+ explicit DeleteAllCallback(bool failfast): DeleteFilesCallback(failfast) {
+ }
+
+ virtual bool onDirectory(const tstring& path) {
+ if (failfast) {
+ deleteDirectoryRecursive(path);
+ } else {
+ updateStatus(deleteDirectoryRecursive(path, std::nothrow));
+ }
+ return true;
+ }
+};
+
+
+class BatchDeleter {
+ const tstring dirPath;
+ bool recursive;
+public:
+ explicit BatchDeleter(const tstring& path): dirPath(path) {
+ deleteSubdirs(false);
+ }
+
+ BatchDeleter& deleteSubdirs(bool v) {
+ recursive = v;
+ return *this;
+ }
+
+ void execute() const {
+ if (!isFileExists(dirPath)) {
+ return;
+ }
+ iterateDirectory(true /* fail fast */);
+ if (recursive) {
+ deleteDirectory(dirPath);
+ }
+ }
+
+ bool execute(const std::nothrow_t&) const {
+ if (!isFileExists(dirPath)) {
+ return true;
+ }
+
+ if (!isDirectory(dirPath)) {
+ return false;
+ }
+
+ JP_TRY;
+ if (!iterateDirectory(false /* ignore errors */)) {
+ return false;
+ }
+ if (recursive) {
+ return deleteDirectory(dirPath, std::nothrow);
+ }
+ return true;
+ JP_CATCH_ALL;
+
+ return false;
+ }
+
+private:
+ bool iterateDirectory(bool failfast) const {
+ std::unique_ptr<DeleteFilesCallback> callback;
+ if (recursive) {
+ callback = std::unique_ptr<DeleteFilesCallback>(
+ new DeleteAllCallback(failfast));
+ } else {
+ callback = std::unique_ptr<DeleteFilesCallback>(
+ new DeleteFilesCallback(failfast));
+ }
+
+ FileUtils::iterateDirectory(dirPath, *callback);
+ return callback->good();
+ }
+};
+
+} // namespace
+
+void deleteFilesInDirectory(const tstring &dirPath) {
+ BatchDeleter(dirPath).execute();
+}
+
+bool deleteFilesInDirectory(const tstring &dirPath,
+ const std::nothrow_t &) throw() {
+ return BatchDeleter(dirPath).execute(std::nothrow);
+}
+
+void deleteDirectoryRecursive(const tstring &dirPath) {
+ BatchDeleter(dirPath).deleteSubdirs(true).execute();
+}
+
+bool deleteDirectoryRecursive(const tstring &dirPath,
+ const std::nothrow_t &) throw() {
+ return BatchDeleter(dirPath).deleteSubdirs(true).execute(std::nothrow);
+}
+
+namespace {
+
+struct FindFileDeleter {
+ typedef HANDLE pointer;
+
+ void operator()(HANDLE h) {
+ if (h && h != INVALID_HANDLE_VALUE) {
+ FindClose(h);
+ }
+ }
+};
+
+typedef std::unique_ptr<HANDLE, FindFileDeleter> UniqueFindFileHandle;
+
+}; // namesace
+void iterateDirectory(const tstring &dirPath, DirectoryCallback& callback)
+{
+ const tstring searchString = combinePath(dirPath, _T("*"));
+ WIN32_FIND_DATA findData;
+ UniqueFindFileHandle h(FindFirstFile(searchString.c_str(), &findData));
+ if (h.get() == INVALID_HANDLE_VALUE) {
+ // GetLastError() == ERROR_FILE_NOT_FOUND is OK
+ // - no files in the directory
+ // ERROR_PATH_NOT_FOUND is returned
+ // if the parent directory does not exist
+ if (GetLastError() != ERROR_FILE_NOT_FOUND) {
+ JP_THROW(SysError(tstrings::any() << "FindFirstFile("
+ << dirPath << ") failed", FindFirstFile));
+ }
+ return;
+ }
+
+ do {
+ const tstring fname(findData.cFileName);
+ const tstring filePath = combinePath(dirPath, fname);
+ if (!isDirectoryAttrs(findData.dwFileAttributes)) {
+ if (!callback.onFile(filePath)) {
+ return;
+ }
+ } else if (fname != _T(".") && fname != _T("..")) {
+ if (!callback.onDirectory(filePath)) {
+ return;
+ }
+ }
+ } while (FindNextFile(h.get(), &findData));
+
+ // expect GetLastError() == ERROR_NO_MORE_FILES
+ if (GetLastError() != ERROR_NO_MORE_FILES) {
+ JP_THROW(SysError(tstrings::any() << "FindNextFile("
+ << dirPath << ") failed", FindNextFile));
+ }
+}
+
+
+tstring replaceSuffix(const tstring& path, const tstring& newSuffix) {
+ return (path.substr(0, path.size() - suffix(path).size()) + newSuffix);
+}
+
+
+DirectoryIterator& DirectoryIterator::findItems(tstring_array& v) {
+ if (!isDirectory(root)) {
+ return *this;
+ }
+
+ iterateDirectory(root, *this);
+ v.insert(v.end(), items.begin(), items.end());
+ items = tstring_array();
+ return *this;
+}
+
+bool DirectoryIterator::onFile(const tstring& path) {
+ if (theWithFiles) {
+ items.push_back(path);
+ }
+ return true;
+}
+
+bool DirectoryIterator::onDirectory(const tstring& path) {
+ if (theWithFolders) {
+ items.push_back(path);
+ }
+ if (theRecurse) {
+ DirectoryIterator(path).recurse(theRecurse)
+ .withFiles(theWithFiles)
+ .withFolders(theWithFolders)
+ .findItems(items);
+ }
+ return true;
+}
+
+
+namespace {
+
+struct DeleterFunctor {
+ // Order of items in the following enum is important!
+ // It controls order in which items of particular type will be deleted.
+ // See Deleter::execute().
+ enum {
+ File,
+ FilesInDirectory,
+ RecursiveDirectory,
+ EmptyDirectory
+ };
+
+ void operator () (const Deleter::Path& path) const {
+ switch (path.second) {
+#define DELETE_SOME(o, f)\
+ case o:\
+ f(path.first, std::nothrow);\
+ break
+
+ DELETE_SOME(File, deleteFile);
+ DELETE_SOME(EmptyDirectory, deleteDirectory);
+ DELETE_SOME(FilesInDirectory, deleteFilesInDirectory);
+ DELETE_SOME(RecursiveDirectory, deleteDirectoryRecursive);
+
+#undef DELETE_SOME
+ default:
+ break;
+ }
+ }
+};
+
+} // namespace
+
+void Deleter::execute() {
+ Paths tmp;
+ tmp.swap(paths);
+
+ // Reorder items to delete.
+ std::stable_sort(tmp.begin(), tmp.end(), [] (const Paths::value_type& a,
+ const Paths::value_type& b) {
+ return a.second < b.second;
+ });
+
+ std::for_each(tmp.begin(), tmp.end(), DeleterFunctor());
+}
+
+Deleter& Deleter::appendFile(const tstring& path) {
+ paths.push_back(std::make_pair(path, DeleterFunctor::File));
+ return *this;
+}
+
+Deleter& Deleter::appendEmptyDirectory(const Directory& dir) {
+ tstring path = normalizePath(removeTrailingSlash(dir));
+ const tstring parent = normalizePath(removeTrailingSlash(dir.parent));
+ while(parent != path) {
+ appendEmptyDirectory(path);
+ path = dirname(path);
+ }
+
+ return *this;
+}
+
+Deleter& Deleter::appendEmptyDirectory(const tstring& path) {
+ paths.push_back(std::make_pair(path, DeleterFunctor::EmptyDirectory));
+ return *this;
+}
+
+Deleter& Deleter::appendAllFilesInDirectory(const tstring& path) {
+ paths.push_back(std::make_pair(path, DeleterFunctor::FilesInDirectory));
+ return *this;
+}
+
+Deleter& Deleter::appendRecursiveDirectory(const tstring& path) {
+ paths.push_back(std::make_pair(path, DeleterFunctor::RecursiveDirectory));
+ return *this;
+}
+
+
+FileWriter::FileWriter(const tstring& path): dstPath(path) {
+ tmpFile = FileUtils::createTempFile(_T("jds"), _T(".tmp"),
+ FileUtils::dirname(path));
+
+ cleaner.appendFile(tmpFile);
+
+ // we want to get exception on error
+ tmp.exceptions(std::ifstream::failbit | std::ifstream::badbit);
+ tmp.open(tmpFile, std::ios::binary | std::ios::trunc);
+}
+
+FileWriter& FileWriter::write(const void* buf, size_t bytes) {
+ tmp.write(static_cast<const char*>(buf), bytes);
+ return *this;
+}
+
+void FileWriter::finalize() {
+ tmp.close();
+
+ FileUtils::moveFile(tmpFile, dstPath, false);
+
+ // cancel file deletion
+ cleaner.cancel();
+}
+
+} // namespace FileUtils
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/FileUtils.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,398 @@
+/*
+ * 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.
+ */
+
+#ifndef FILEUTILS_H
+#define FILEUTILS_H
+
+
+#include <fstream>
+#include "SysInfo.h"
+
+
+namespace FileUtils {
+
+ // Returns 'true' if the given character is a path separator.
+ bool isDirSeparator(const tstring::value_type c);
+
+ // checks if the file or directory exists
+ bool isFileExists(const tstring &filePath);
+
+ // checks is the specified file is a directory
+ // returns false if the path does not exist
+ bool isDirectory(const tstring &filePath);
+
+ // checks if the specified directory is not empty
+ // returns true if the path is an existing directory and
+ // it contains at least one file other than "." or "..".
+ bool isDirectoryNotEmpty(const tstring &dirPath);
+
+ // returns directory part of the path.
+ // returns empty string if the path contains only filename.
+ // if the path ends with slash/backslash,
+ // returns removeTrailingSlashes(path).
+ tstring dirname(const tstring &path);
+
+ // returns basename part of the path
+ // if the path ends with slash/backslash, returns empty string.
+ tstring basename(const tstring &path);
+
+ /**
+ * Translates forward slashes to back slashes and returns lower case version
+ * of the given string.
+ */
+ tstring normalizePath(tstring v);
+
+ // Returns suffix of the path. If the given path has a suffix the first
+ // character of the return value is '.'.
+ // Otherwise return value if empty string.
+ tstring suffix(const tstring &path);
+
+ // combines two strings into a path
+ tstring combinePath(const tstring& parent, const tstring& child);
+
+ // removes trailing slashes and backslashes in the path if any
+ tstring removeTrailingSlash(const tstring& path);
+
+ // Creates a file with unique name in the specified base directory,
+ // throws an exception if operation fails
+ // path is constructed as <prefix><random number><suffix>.
+ // The function fails and throws exception if 'path' doesn't exist.
+ tstring createTempFile(const tstring &prefix = _T(""),
+ const tstring &suffix = _T(".tmp"),
+ const tstring &path=SysInfo::getTempDir());
+
+ // Creates a directory with unique name in the specified base directory,
+ // throws an exception if operation fails
+ // path is constructed as <prefix><random number><suffix>
+ // The function fails and throws exception if 'path' doesn't exist.
+ tstring createTempDirectory(const tstring &prefix = _T(""),
+ const tstring &suffix = _T(".tmp"),
+ const tstring &basedir=SysInfo::getTempDir());
+
+ // If the file referenced with "prototype" parameter DOES NOT exist,
+ // the return value is the given path. No new files created.
+ // Otherwise the function creates another file in the same directory as
+ // the given file with the same suffix and with the basename from the
+ // basename of the given file with some random chars appended to ensure
+ // created file is unique.
+ tstring createUniqueFile(const tstring &prototype);
+
+ // Creates directory and subdirectories if don't exist.
+ // Currently supports only "standard" path like "c:\bla-bla"
+ // If 'createdDirs' parameter is not NULL, the given array is appended with
+ // all subdirectories created by this function call.
+ void createDirectory(const tstring &path, tstring_array* createdDirs=0);
+
+ // copies file from fromPath to toPath.
+ // Creates output directory if doesn't exist.
+ void copyFile(const tstring& fromPath, const tstring& toPath,
+ bool failIfExists);
+
+ // moves file from fromPath to toPath.
+ // Creates output directory if doesn't exist.
+ void moveFile(const tstring& fromPath, const tstring& toPath,
+ bool failIfExists);
+
+ // Throws exception if fails to delete specified 'path'.
+ // Exits normally if 'path' doesn't exist or it has been deleted.
+ // Attempts to strip R/O attribute if delete fails and retry delete.
+ void deleteFile(const tstring &path);
+ // Returns 'false' if fails to delete specified 'path'.
+ // Returns 'true' if 'path' doesn't exist or it has been deleted.
+ // Attempts to strip R/O attribute if delete fails and retry delete.
+ bool deleteFile(const tstring &path, const std::nothrow_t &) throw();
+
+ // Like deleteFile(), but applies to directories.
+ void deleteDirectory(const tstring &path);
+ bool deleteDirectory(const tstring &path, const std::nothrow_t &) throw();
+
+ // Deletes all files (not subdirectories) from the specified directory.
+ // Exits normally if all files in 'dirPath' have been deleted or if
+ // 'dirPath' doesn't exist.
+ // Throws exception if 'dirPath' references existing file system object
+ // which is not a directory or when the first failure of file delete
+ // occurs.
+ void deleteFilesInDirectory(const tstring &dirPath);
+ // Deletes all files (not subdirectories) from the specified directory.
+ // Returns 'true' normally if all files in 'dirPath' have been deleted or
+ // if 'dirPath' doesn't exist.
+ // Returns 'false' if 'dirPath' references existing file system object
+ // which is not a directory or if failed to delete one ore more files in
+ // 'dirPath' directory.
+ // Doesn't abort iteration over files if the given directory after the
+ // first failure to delete a file.
+ bool deleteFilesInDirectory(const tstring &dirPath,
+ const std::nothrow_t &) throw();
+ // Like deleteFilesInDirectory, but deletes subdirectories as well
+ void deleteDirectoryRecursive(const tstring &dirPath);
+ bool deleteDirectoryRecursive(const tstring &dirPath,
+ const std::nothrow_t &) throw();
+
+ class DirectoryCallback {
+ public:
+ virtual ~DirectoryCallback() {};
+
+ virtual bool onFile(const tstring& path) {
+ return true;
+ }
+ virtual bool onDirectory(const tstring& path) {
+ return true;
+ }
+ };
+
+ // Calls the given callback for every file and subdirectory of
+ // the given directory.
+ void iterateDirectory(const tstring &dirPath, DirectoryCallback& callback);
+
+ /**
+ * Replace file suffix, example replaceSuffix("file/path.txt", ".csv")
+ * @param path file path to replace suffix
+ * @param suffix new suffix for path
+ * @return return file path with new suffix
+ */
+ tstring replaceSuffix(const tstring& path, const tstring& suffix=tstring());
+
+ class DirectoryIterator: DirectoryCallback {
+ public:
+ DirectoryIterator(const tstring& root=tstring()): root(root) {
+ recurse().withFiles().withFolders();
+ }
+
+ DirectoryIterator& recurse(bool v=true) {
+ theRecurse = v;
+ return *this;
+ }
+
+ DirectoryIterator& withFiles(bool v=true) {
+ theWithFiles = v;
+ return *this;
+ }
+
+ DirectoryIterator& withFolders(bool v=true) {
+ theWithFolders = v;
+ return *this;
+ }
+
+ tstring_array findItems() {
+ tstring_array reply;
+ findItems(reply);
+ return reply;
+ }
+
+ DirectoryIterator& findItems(tstring_array& v);
+
+ private:
+ virtual bool onFile(const tstring& path);
+ virtual bool onDirectory(const tstring& path);
+
+ private:
+ bool theRecurse;
+ bool theWithFiles;
+ bool theWithFolders;
+ tstring root;
+ tstring_array items;
+ };
+
+ // Returns array of all the files/sub-folders from the given directory,
+ // empty array if basedir is not a directory. The returned
+ // array is ordered from top down (i.e. dirs are listed first followed
+ // by subfolders and files).
+ // Order of subfolders and files is undefined
+ // but usually they are sorted by names.
+ inline tstring_array listAllContents(const tstring& basedir) {
+ return DirectoryIterator(basedir).findItems();
+ }
+
+ // Helper to construct path from multiple components.
+ //
+ // Sample usage:
+ // Construct "c:\Program Files\Java" string from three components
+ //
+ // tstring path = FileUtils::mkpath() << _T("c:")
+ // << _T("Program Files")
+ // << _T("Java");
+ //
+ class mkpath {
+ public:
+ operator const tstring& () const {
+ return path;
+ }
+
+ mkpath& operator << (const tstring& p) {
+ path = combinePath(path, p);
+ return *this;
+ }
+
+ // mimic std::string
+ const tstring::value_type* c_str() const {
+ return path.c_str();
+ }
+ private:
+ tstring path;
+ };
+
+ struct Directory {
+ Directory() {
+ }
+
+ Directory(const tstring &parent,
+ const tstring &subdir) : parent(parent), subdir(subdir) {
+ }
+
+ operator tstring () const {
+ return getPath();
+ }
+
+ tstring getPath() const {
+ return combinePath(parent, subdir);
+ }
+
+ bool empty() const {
+ return (parent.empty() && subdir.empty());
+ }
+
+ tstring parent;
+ tstring subdir;
+ };
+
+ // Deletes list of files and directories in batch mode.
+ // Registered files and directories are deleted when destructor is called.
+ // Order or delete operations is following:
+ // - delete items registered with appendFile() calls;
+ // - delete items registered with appendAllFilesInDirectory() calls;
+ // - delete items registered with appendRecursiveDirectory() calls;
+ // - delete items registered with appendEmptyDirectory() calls.
+ class Deleter {
+ public:
+ Deleter() {
+ }
+
+ ~Deleter() {
+ execute();
+ }
+
+ typedef std::pair<tstring, int> Path;
+ typedef std::vector<Path> Paths;
+
+ /**
+ * Appends all records from the given deleter Deleter into this Deleter
+ * instance. On success array with records in the passed in Deleter
+ * instance is emptied.
+ */
+ Deleter& appendFrom(Deleter& other) {
+ Paths tmp(paths);
+ tmp.insert(tmp.end(), other.paths.begin(), other.paths.end());
+ Paths empty;
+ other.paths.swap(empty);
+ paths.swap(tmp);
+ return *this;
+ }
+
+ // Schedule file for deletion.
+ Deleter& appendFile(const tstring& path);
+
+ // Schedule files for deletion.
+ template <class It>
+ Deleter& appendFiles(It b, It e) {
+ for (It it = b; it != e; ++it) {
+ appendFile(*it);
+ }
+ return *this;
+ }
+
+ // Schedule files for deletion in the given directory.
+ template <class It>
+ Deleter& appendFiles(const tstring& dirname, It b, It e) {
+ for (It it = b; it != e; ++it) {
+ appendFile(FileUtils::mkpath() << dirname << *it);
+ }
+ return *this;
+ }
+
+ // Schedule empty directory for deletion with empty roots
+ // (up to Directory.parent).
+ Deleter& appendEmptyDirectory(const Directory& dir);
+
+ // Schedule empty directory for deletion without roots.
+ // This is a particular case of
+ // appendEmptyDirectory(const Directory& dir)
+ // with Directory(dirname(path), basename(path)).
+ Deleter& appendEmptyDirectory(const tstring& path);
+
+ // Schedule all file from the given directory for deletion.
+ Deleter& appendAllFilesInDirectory(const tstring& path);
+
+ // Schedule directory for recursive deletion.
+ Deleter& appendRecursiveDirectory(const tstring& path);
+
+ void cancel() {
+ paths.clear();
+ }
+
+ // Deletes scheduled files and directories. After this function
+ // is called internal list of scheduled items is emptied.
+ void execute();
+
+ private:
+ Paths paths;
+ };
+
+
+ /**
+ * Helper to write chunks of data into binary file.
+ * Creates temporary file in the same folder with destination file.
+ * All subsequent requests to save data chunks are redirected to temporary
+ * file. finalize() method closes temporary file stream and renames
+ * temporary file.
+ * If finalize() method is not called, temporary file is deleted in
+ * ~FileWriter(), destination file is not touched.
+ */
+ class FileWriter {
+ public:
+ explicit FileWriter(const tstring& path);
+
+ FileWriter& write(const void* buf, size_t bytes);
+
+ template <class Ctnr>
+ FileWriter& write(const Ctnr& buf) {
+ return write(buf.data(),
+ buf.size() * sizeof(typename Ctnr::value_type));
+ }
+
+ void finalize();
+
+ private:
+ // Not accessible by design!
+ FileWriter& write(const std::wstring& str);
+
+ private:
+ tstring tmpFile;
+ Deleter cleaner;
+ std::ofstream tmp;
+ tstring dstPath;
+ };
+} // FileUtils
+
+#endif // FILEUTILS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/IconSwap.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2012, 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.
+ */
+
+#include <stdio.h>
+#include <windows.h>
+#include <stdlib.h>
+#include <string>
+#include <malloc.h>
+
+using namespace std;
+
+// http://msdn.microsoft.com/en-us/library/ms997538.aspx
+
+typedef struct _ICONDIRENTRY {
+ BYTE bWidth;
+ BYTE bHeight;
+ BYTE bColorCount;
+ BYTE bReserved;
+ WORD wPlanes;
+ WORD wBitCount;
+ DWORD dwBytesInRes;
+ DWORD dwImageOffset;
+} ICONDIRENTRY, * LPICONDIRENTRY;
+
+typedef struct _ICONDIR {
+ WORD idReserved;
+ WORD idType;
+ WORD idCount;
+ ICONDIRENTRY idEntries[1];
+} ICONDIR, * LPICONDIR;
+
+// #pragmas are used here to insure that the structure's
+// packing in memory matches the packing of the EXE or DLL.
+#pragma pack(push)
+#pragma pack(2)
+
+typedef struct _GRPICONDIRENTRY {
+ BYTE bWidth;
+ BYTE bHeight;
+ BYTE bColorCount;
+ BYTE bReserved;
+ WORD wPlanes;
+ WORD wBitCount;
+ DWORD dwBytesInRes;
+ WORD nID;
+} GRPICONDIRENTRY, * LPGRPICONDIRENTRY;
+#pragma pack(pop)
+
+#pragma pack(push)
+#pragma pack(2)
+
+typedef struct _GRPICONDIR {
+ WORD idReserved;
+ WORD idType;
+ WORD idCount;
+ GRPICONDIRENTRY idEntries[1];
+} GRPICONDIR, * LPGRPICONDIR;
+#pragma pack(pop)
+
+void PrintError() {
+ LPVOID message = NULL;
+ DWORD error = GetLastError();
+
+ if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER
+ | FORMAT_MESSAGE_FROM_SYSTEM
+ | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, error,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (LPTSTR) & message, 0, NULL) != 0) {
+ printf("%S", (LPTSTR) message);
+ LocalFree(message);
+ }
+}
+
+// Note: We do not check here that iconTarget is valid icon.
+// Java code will already do this for us.
+
+bool ChangeIcon(wstring iconTarget, wstring launcher) {
+ WORD language = MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT);
+
+ HANDLE icon = CreateFile(iconTarget.c_str(), GENERIC_READ, 0, NULL,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (icon == INVALID_HANDLE_VALUE) {
+ PrintError();
+ return false;
+ }
+
+ // Reading .ICO file
+ WORD idReserved, idType, idCount;
+
+ DWORD dwBytesRead;
+ ReadFile(icon, &idReserved, sizeof (WORD), &dwBytesRead, NULL);
+ ReadFile(icon, &idType, sizeof (WORD), &dwBytesRead, NULL);
+ ReadFile(icon, &idCount, sizeof (WORD), &dwBytesRead, NULL);
+
+ LPICONDIR lpid = (LPICONDIR) malloc(
+ sizeof (ICONDIR) + (sizeof (ICONDIRENTRY) * (idCount - 1)));
+ if (lpid == NULL) {
+ CloseHandle(icon);
+ printf("Error: Failed to allocate memory\n");
+ return false;
+ }
+
+ lpid->idReserved = idReserved;
+ lpid->idType = idType;
+ lpid->idCount = idCount;
+
+ ReadFile(icon, &lpid->idEntries[0], sizeof (ICONDIRENTRY) * lpid->idCount,
+ &dwBytesRead, NULL);
+
+ LPGRPICONDIR lpgid = (LPGRPICONDIR) malloc(
+ sizeof (GRPICONDIR) + (sizeof (GRPICONDIRENTRY) * (idCount - 1)));
+ if (lpid == NULL) {
+ CloseHandle(icon);
+ free(lpid);
+ printf("Error: Failed to allocate memory\n");
+ return false;
+ }
+
+ lpgid->idReserved = idReserved;
+ lpgid->idType = idType;
+ lpgid->idCount = idCount;
+
+ for (int i = 0; i < lpgid->idCount; i++) {
+ lpgid->idEntries[i].bWidth = lpid->idEntries[i].bWidth;
+ lpgid->idEntries[i].bHeight = lpid->idEntries[i].bHeight;
+ lpgid->idEntries[i].bColorCount = lpid->idEntries[i].bColorCount;
+ lpgid->idEntries[i].bReserved = lpid->idEntries[i].bReserved;
+ lpgid->idEntries[i].wPlanes = lpid->idEntries[i].wPlanes;
+ lpgid->idEntries[i].wBitCount = lpid->idEntries[i].wBitCount;
+ lpgid->idEntries[i].dwBytesInRes = lpid->idEntries[i].dwBytesInRes;
+ lpgid->idEntries[i].nID = i + 1;
+ }
+
+ // Store images in .EXE
+ HANDLE update = BeginUpdateResource(launcher.c_str(), FALSE);
+ if (update == NULL) {
+ free(lpid);
+ free(lpgid);
+ CloseHandle(icon);
+ PrintError();
+ return false;
+ }
+
+ for (int i = 0; i < lpid->idCount; i++) {
+ LPBYTE lpBuffer = (LPBYTE) malloc(lpid->idEntries[i].dwBytesInRes);
+ SetFilePointer(icon, lpid->idEntries[i].dwImageOffset,
+ NULL, FILE_BEGIN);
+ ReadFile(icon, lpBuffer, lpid->idEntries[i].dwBytesInRes,
+ &dwBytesRead, NULL);
+ if (!UpdateResource(update, RT_ICON,
+ MAKEINTRESOURCE(lpgid->idEntries[i].nID),
+ language, &lpBuffer[0], lpid->idEntries[i].dwBytesInRes)) {
+ free(lpBuffer);
+ free(lpid);
+ free(lpgid);
+ CloseHandle(icon);
+ PrintError();
+ return false;
+ }
+ free(lpBuffer);
+ }
+
+ free(lpid);
+ CloseHandle(icon);
+
+ if (!UpdateResource(update, RT_GROUP_ICON, MAKEINTRESOURCE(1),
+ language, &lpgid[0], (sizeof (WORD) * 3)
+ + (sizeof (GRPICONDIRENTRY) * lpgid->idCount))) {
+ free(lpgid);
+ PrintError();
+ return false;
+ }
+
+ free(lpgid);
+
+ if (EndUpdateResource(update, FALSE) == FALSE) {
+ PrintError();
+ return false;
+ }
+
+ return true;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/IconSwap.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2016, 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.
+ */
+
+#ifndef ICONSWAP_H
+#define ICONSWAP_H
+
+#include <string>
+
+using namespace std;
+
+bool ChangeIcon(wstring iconTarget, wstring launcher);
+
+#endif // ICONSWAP_H
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/Log.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,208 @@
+/*
+ * 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.
+ */
+
+#include "Log.h"
+#include "SysInfo.h"
+#include "FileUtils.h"
+
+
+namespace {
+ //
+ // IMPORTANT: Static objects with non-trivial constructors are NOT allowed
+ // in logger module. Allocate buffers only and do lazy initialization of
+ // globals in Logger::getDefault().
+ //
+ // Logging subsystem is used almost in every module, and logging API can be
+ // called from constructors of static objects in various modules. As
+ // ordering of static objects initialization between modules is undefined,
+ // this means some module may call logging api before logging static
+ // variables are initialized if any. This will result in AV. To avoid such
+ // use cases keep logging module free from static variables that require
+ // initialization with functions called by CRT.
+ //
+
+ // by default log everything
+ const Logger::LogLevel defaultLogLevel = Logger::LOG_TRACE;
+
+ char defaultLogAppenderMemory[sizeof(StderrLogAppender)] = {};
+
+ char defaultLoggerMemory[sizeof(Logger)] = {};
+
+ NopLogAppender nopLogApender;
+
+ LPCTSTR getLogLevelStr(Logger::LogLevel level) {
+ switch (level) {
+ case Logger::LOG_TRACE:
+ return _T("TRACE");
+ case Logger::LOG_INFO:
+ return _T("INFO");
+ case Logger::LOG_WARNING:
+ return _T("WARNING");
+ case Logger::LOG_ERROR:
+ return _T("ERROR");
+ }
+ return _T("UNKNOWN");
+ }
+
+ tstring retrieveModuleName() {
+ try {
+ return FileUtils::basename(SysInfo::getCurrentModulePath());
+ } catch (const std::exception&) {
+ return _T("Unknown");
+ }
+ }
+
+ TCHAR moduleName[MAX_PATH] = { 'U', 'n', 'k', 'o', 'w', 'n', TCHAR(0) };
+
+ const LPCTSTR format = _T("[%04u/%02u/%02u %02u:%02u:%02u.%03u, %s (PID: %u, TID: %u), %s:%u (%s)]\n\t%s: %s\n");
+
+ enum State { NotInitialized, Initializing, Initialized };
+ State state = NotInitialized;
+}
+
+
+LogEvent::LogEvent() {
+ memset(this, 0, sizeof(*this));
+ moduleName = tstring();
+ logLevel = tstring();
+ fileName = tstring();
+ funcName = tstring();
+ message = tstring();
+}
+
+
+StderrLogAppender::StderrLogAppender() {
+}
+
+
+/*static*/
+Logger& Logger::defaultLogger() {
+ Logger* reply = reinterpret_cast<Logger*>(defaultLoggerMemory);
+
+ if (!reply->appender) {
+ // Memory leak by design. Not an issue at all as this is global
+ // object. OS will do resources clean up anyways when application
+ // terminates and the default log appender should live as long as
+ // application lives.
+ reply->appender = new (defaultLogAppenderMemory) StderrLogAppender();
+ }
+
+ if (Initializing == state) {
+ // Recursive call to Logger::defaultLogger.
+ moduleName[0] = TCHAR(0);
+ } else if (NotInitialized == state) {
+ state = Initializing;
+
+ tstring mname = retrieveModuleName();
+ mname.resize(_countof(moduleName) - 1);
+ std::memcpy(moduleName, mname.c_str(), mname.size());
+ moduleName[mname.size()] = TCHAR(0);
+
+ // if JPACKAGE_DEBUG environment variable is NOT set to "true" disable
+ // logging.
+ if (SysInfo::getEnvVariable(std::nothrow,
+ L"JPACKAGE_DEBUG") != L"true") {
+ reply->appender = &nopLogApender;
+ }
+
+ state = Initialized;
+ }
+
+ return *reply;
+}
+
+Logger::Logger(LogAppender& appender, LogLevel logLevel)
+ : level(logLevel), appender(&appender) {
+}
+
+void Logger::setLogLevel(LogLevel logLevel) {
+ level = logLevel;
+}
+
+Logger::~Logger() {
+}
+
+
+bool Logger::isLoggable(LogLevel logLevel) const {
+ return logLevel >= level;
+}
+
+void Logger::log(LogLevel logLevel, LPCTSTR fileName, int lineNum,
+ LPCTSTR funcName, const tstring& message) const {
+ LogEvent logEvent;
+
+ // [YYYY/MM/DD HH:MM:SS.ms, <module> (PID: processID, TID: threadID),
+ // fileName:lineNum (funcName)] <tab>LEVEL: message
+ GetLocalTime(&logEvent.ts);
+
+ logEvent.pid = GetCurrentProcessId();
+ logEvent.tid = GetCurrentThreadId();
+ logEvent.moduleName = moduleName;
+ logEvent.fileName = FileUtils::basename(fileName);
+ logEvent.funcName = funcName;
+ logEvent.logLevel = getLogLevelStr(logLevel);
+ logEvent.lineNum = lineNum;
+ logEvent.message = message;
+
+ appender->append(logEvent);
+}
+
+
+void StderrLogAppender::append(const LogEvent& v)
+{
+ const tstring out = tstrings::unsafe_format(format,
+ unsigned(v.ts.wYear), unsigned(v.ts.wMonth), unsigned(v.ts.wDay),
+ unsigned(v.ts.wHour), unsigned(v.ts.wMinute), unsigned(v.ts.wSecond),
+ unsigned(v.ts.wMilliseconds),
+ v.moduleName.c_str(), v.pid, v.tid,
+ v.fileName.c_str(), v.lineNum, v.funcName.c_str(),
+ v.logLevel.c_str(),
+ v.message.c_str());
+
+ std::cerr << tstrings::toUtf8(out);
+}
+
+
+// Logger::ScopeTracer
+Logger::ScopeTracer::ScopeTracer(Logger &logger, LogLevel logLevel,
+ LPCTSTR fileName, int lineNum, LPCTSTR funcName,
+ const tstring& scopeName) : log(logger), level(logLevel),
+ file(fileName), line(lineNum),
+ func(funcName), scope(scopeName), needLog(logger.isLoggable(logLevel)) {
+ if (needLog) {
+ log.log(level, file.c_str(), line, func.c_str(),
+ tstrings::any() << "Entering " << scope);
+ }
+}
+
+Logger::ScopeTracer::~ScopeTracer() {
+ if (needLog) {
+ // we don't know what line is end of scope at, so specify line 0
+ // and add note about line when the scope begins
+ log.log(level, file.c_str(), 0, func.c_str(),
+ tstrings::any() << "Exiting " << scope << " (entered at "
+ << FileUtils::basename(file) << ":" << line << ")");
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/Log.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,202 @@
+/*
+ * 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.
+ */
+
+#ifndef __LOG_H_INCLUDED_
+#define __LOG_H_INCLUDED_
+
+#include <windows.h>
+#include "tstrings.h"
+
+
+/* Default logger (Logger::defaultLogger()) writes log messages to
+ * the default log file.
+ * Common scenario:
+ * - main() function configures default logger:
+ * FileLogAppender appender(_T("my_log_filename.log"));
+ * Logger::defaultLogger().setAppender(appender);
+ * Logger::defaultLogger().setLogLevel(LOG_INFO);
+ * If the default file name and log level are not set,
+ * _T("jusched.log")/LOG_TRACE are used.
+ *
+ * Logger fileName specifies only file name,
+ * full path for the log file depends on the platform
+ * (usually value of the TMP env. var)
+ */
+
+struct LogEvent {
+ SYSTEMTIME ts;
+ long tid;
+ long pid;
+ tstring moduleName;
+ tstring logLevel;
+ tstring fileName;
+ int lineNum;
+ tstring funcName;
+ tstring message;
+
+ LogEvent();
+};
+
+
+class LogAppender {
+public:
+ virtual ~LogAppender() {
+ }
+ virtual void append(const LogEvent& v) = 0;
+};
+
+
+class NopLogAppender: public LogAppender {
+public:
+ virtual void append(const LogEvent& v) {};
+};
+
+
+class TeeLogAppender: public LogAppender {
+public:
+ TeeLogAppender(LogAppender* first, LogAppender* second):
+ first(first), second(second) {
+ }
+ virtual ~TeeLogAppender() {
+ }
+ virtual void append(const LogEvent& v) {
+ if (first) {
+ first->append(v);
+ }
+ if (second) {
+ second->append(v);
+ }
+ }
+private:
+ LogAppender* first;
+ LogAppender* second;
+};
+
+
+/**
+ * Writes log events to stderr.
+ */
+class StderrLogAppender: public LogAppender {
+public:
+ explicit StderrLogAppender();
+
+ virtual void append(const LogEvent& v);
+};
+
+
+class Logger {
+public:
+ enum LogLevel {
+ LOG_TRACE,
+ LOG_INFO,
+ LOG_WARNING,
+ LOG_ERROR
+ };
+
+ static Logger& defaultLogger();
+
+ explicit Logger(LogAppender& appender, LogLevel logLevel = LOG_TRACE);
+ ~Logger();
+
+ LogAppender& setAppender(LogAppender& v) {
+ LogAppender& oldAppender = *appender;
+ appender = &v;
+ return oldAppender;
+ }
+
+ LogAppender& getAppender() const {
+ return *appender;
+ }
+
+ void setLogLevel(LogLevel logLevel);
+
+ bool isLoggable(LogLevel logLevel) const ;
+ void log(LogLevel logLevel, LPCTSTR fileName, int lineNum,
+ LPCTSTR funcName, const tstring& message) const;
+ void log(LogLevel logLevel, LPCTSTR fileName, int lineNum,
+ LPCTSTR funcName, const tstrings::any& message) const {
+ return log(logLevel, fileName, lineNum, funcName, message.tstr());
+ }
+ void log(LogLevel logLevel, LPCTSTR fileName, int lineNum,
+ LPCTSTR funcName, tstring::const_pointer message) const {
+ return log(logLevel, fileName, lineNum, funcName, tstring(message));
+ }
+
+ // internal class for scope tracing
+ class ScopeTracer {
+ public:
+ ScopeTracer(Logger &logger, LogLevel logLevel, LPCTSTR fileName,
+ int lineNum, LPCTSTR funcName, const tstring& scopeName);
+ ~ScopeTracer();
+
+ private:
+ const Logger &log;
+ const LogLevel level;
+ const bool needLog;
+ const tstring file;
+ const int line;
+ const tstring func;
+ const tstring scope;
+ };
+
+private:
+ LogLevel level;
+ LogAppender* appender;
+};
+
+
+// base logging macro
+#define LOGGER_LOG(logger, logLevel, message) \
+ do { \
+ if (logger.isLoggable(logLevel)) { \
+ logger.log(logLevel, _T(__FILE__), __LINE__, _T(__FUNCTION__), message); \
+ } \
+ } while(false)
+
+
+// custom logger macros
+#define LOGGER_TRACE(logger, message) LOGGER_LOG(logger, Logger::LOG_TRACE, message)
+#define LOGGER_INFO(logger, message) LOGGER_LOG(logger, Logger::LOG_INFO, message)
+#define LOGGER_WARNING(logger, message) LOGGER_LOG(logger, Logger::LOG_WARNING, message)
+#define LOGGER_ERROR(logger, message) LOGGER_LOG(logger, Logger::LOG_ERROR, message)
+// scope tracing macros
+#define LOGGER_TRACE_SCOPE(logger, scopeName) \
+ Logger::ScopeTracer tracer__COUNTER__(logger, Logger::LOG_TRACE, _T(__FILE__), __LINE__, _T(__FUNCTION__), scopeName)
+#define LOGGER_TRACE_FUNCTION(logger) LOGGER_TRACE_SCOPE(logger, _T(__FUNCTION__))
+
+
+// default logger macros
+#define LOG_TRACE(message) LOGGER_LOG(Logger::defaultLogger(), Logger::LOG_TRACE, message)
+#define LOG_INFO(message) LOGGER_LOG(Logger::defaultLogger(), Logger::LOG_INFO, message)
+#define LOG_WARNING(message) LOGGER_LOG(Logger::defaultLogger(), Logger::LOG_WARNING, message)
+#define LOG_ERROR(message) LOGGER_LOG(Logger::defaultLogger(), Logger::LOG_ERROR, message)
+// scope tracing macros
+// logs (_T("Entering ") + scopeName) at the beging, (_T("Exiting ") + scopeName) at the end of scope
+#define LOG_TRACE_SCOPE(scopeName) LOGGER_TRACE_SCOPE(Logger::defaultLogger(), scopeName)
+// logs (_T("Entering ") + functionName) at the beging, (_T("Exiting ") + __FUNCTION__) at the end of scope
+#define LOG_TRACE_FUNCTION() LOGGER_TRACE_FUNCTION(Logger::defaultLogger())
+
+
+#endif // __LOG_H_INCLUDED_
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/ResourceEditor.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+
+#include <algorithm>
+#include <fstream>
+#include "ResourceEditor.h"
+#include "WinErrorHandling.h"
+#include "Log.h"
+
+
+ResourceEditor::FileLock::FileLock(const std::wstring& binaryPath) {
+ h = BeginUpdateResource(binaryPath.c_str(), FALSE);
+ if (NULL == h) {
+ JP_THROW(SysError(tstrings::any() << "BeginUpdateResource("
+ << binaryPath << ") failed", BeginUpdateResource));
+ }
+
+ discard(false);
+}
+
+
+ResourceEditor::FileLock::~FileLock() {
+ if (!EndUpdateResource(h, theDiscard)) {
+ JP_NO_THROW(JP_THROW(SysError(tstrings::any()
+ << "EndUpdateResource(" << h << ") failed.", EndUpdateResource)));
+ }
+}
+
+
+ResourceEditor::ResourceEditor() {
+ language(MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)).type(unsigned(0)).id(unsigned(0));
+}
+
+
+ResourceEditor& ResourceEditor::type(unsigned v) {
+ return type(MAKEINTRESOURCE(v));
+}
+
+
+ResourceEditor& ResourceEditor::type(LPCWSTR v) {
+ if (IS_INTRESOURCE(v)) {
+ std::wostringstream printer;
+ printer << L"#" << reinterpret_cast<size_t>(v);
+ theType = printer.str();
+ theTypePtr = MAKEINTRESOURCE(static_cast<DWORD>(reinterpret_cast<DWORD_PTR>(v)));
+ } else {
+ theType = v;
+ theTypePtr = theType.c_str();
+ }
+ return *this;
+}
+
+
+ResourceEditor& ResourceEditor::id(unsigned v) {
+ return id(MAKEINTRESOURCE(v));
+}
+
+
+ResourceEditor& ResourceEditor::id(LPCWSTR v) {
+ if (IS_INTRESOURCE(v)) {
+ std::wostringstream printer;
+ printer << L"#" << reinterpret_cast<size_t>(v);
+ theId = printer.str();
+ } else {
+ theId = v;
+ theIdPtr = theId.c_str();
+ }
+ return *this;
+}
+
+
+ResourceEditor& ResourceEditor::apply(const FileLock& dstBinary,
+ std::istream& srcStream, std::streamsize size) {
+
+ typedef std::vector<BYTE> ByteArray;
+ ByteArray buf;
+ if (size <= 0) {
+ // Read the entire stream.
+ buf = ByteArray((std::istreambuf_iterator<char>(srcStream)),
+ std::istreambuf_iterator<char>());
+ } else {
+ buf.resize(size_t(size));
+ srcStream.read(reinterpret_cast<char*>(buf.data()), size);
+ }
+
+ auto reply = UpdateResource(dstBinary.get(), theTypePtr, theIdPtr, lang,
+ buf.data(), static_cast<DWORD>(buf.size()));
+ if (reply == FALSE) {
+ JP_THROW(SysError("UpdateResource() failed", UpdateResource));
+ }
+
+ return *this;
+}
+
+
+ResourceEditor& ResourceEditor::apply(const FileLock& dstBinary,
+ const std::wstring& srcFile) {
+ std::ifstream input(srcFile, std::ios_base::binary);
+ input.exceptions(std::ios::failbit | std::ios::badbit);
+ return apply(dstBinary, input);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/ResourceEditor.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+
+#ifndef RESOURCEEDITOR_H
+#define RESOURCEEDITOR_H
+
+#include <windows.h>
+#include <vector>
+#include <string>
+
+
+class ResourceEditor {
+public:
+ class FileLock {
+ public:
+ FileLock(const std::wstring& binaryPath);
+ ~FileLock();
+
+ HANDLE get() const {
+ return h;
+ }
+
+ void discard(bool v = true) {
+ theDiscard = v;
+ }
+
+ private:
+ FileLock(const FileLock&);
+ FileLock& operator=(const FileLock&);
+ private:
+ HANDLE h;
+ bool theDiscard;
+ };
+
+public:
+ ResourceEditor();
+
+ /**
+ * Set the language identifier of the resource to be updated.
+ */
+ ResourceEditor& language(unsigned v) {
+ lang = v;
+ return *this;
+ }
+
+ /**
+ * Set the resource type to be updated.
+ */
+ ResourceEditor& type(unsigned v);
+
+ /**
+ * Set the resource type to be updated.
+ */
+ ResourceEditor& type(LPCWSTR v);
+
+ /**
+ * Set resource ID.
+ */
+ ResourceEditor& id(unsigned v);
+
+ /**
+ * Set resource ID.
+ */
+ ResourceEditor& id(LPCWSTR v);
+
+ /**
+ * Relaces resource configured in the given binary with the given data stream.
+ */
+ ResourceEditor& apply(const FileLock& dstBinary, std::istream& srcStream, std::streamsize size=0);
+
+ /**
+ * Relaces resource configured in the given binary with contents of
+ * the given binary file.
+ */
+ ResourceEditor& apply(const FileLock& dstBinary, const std::wstring& srcFile);
+
+private:
+ unsigned lang;
+ std::wstring theId;
+ LPCWSTR theIdPtr;
+ std::wstring theType;
+ LPCWSTR theTypePtr;
+};
+
+#endif // #ifndef RESOURCEEDITOR_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/SourceCodePos.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+
+#ifndef SourceCodePos_h
+#define SourceCodePos_h
+
+
+//
+// Position in source code.
+//
+
+struct SourceCodePos
+{
+ SourceCodePos(const char* fl, const char* fnc, int l):
+ file(fl), func(fnc), lno(l)
+ {
+ }
+
+ const char* file;
+ const char* func;
+ int lno;
+};
+
+
+// Initializes SourceCodePos instance with the
+// information from the point of calling.
+#define JP_SOURCE_CODE_POS SourceCodePos(__FILE__, __FUNCTION__, __LINE__)
+
+
+#endif // #ifndef SourceCodePos_h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/SysInfo.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ */
+
+
+#ifndef SYSINFO_H
+#define SYSINFO_H
+
+#include "tstrings.h"
+
+
+//
+// This namespace provides information about environment in which
+// the current application runs.
+// It is for general purpose use.
+// Functions in this namespaces are just queries about the environment.
+// Functions that change the existing environment like file or directory
+// creation should not be added to this namespace.
+//
+namespace SysInfo {
+ /**
+ * Returns temp dir (for the current user).
+ */
+ tstring getTempDir();
+
+ /**
+ * Returns absolute path to the process executable.
+ */
+ tstring getProcessModulePath();
+
+ /**
+ * Returns absolute path to the current executable module.
+ */
+ tstring getCurrentModulePath();
+
+ enum CommandArgProgramNameMode {
+ IncludeProgramName,
+ ExcludeProgramName
+ };
+ /**
+ * Retrieves the command-line arguments for the current process.
+ * With IncludeProgramName option returns result similar to argv/argc.
+ * With ExcludeProgramName option program name
+ * (the 1st element of command line)
+ * is excluded.
+ */
+ tstring_array getCommandArgs(
+ CommandArgProgramNameMode progNameMode = ExcludeProgramName);
+
+ /**
+ * Returns value of environment variable with the given name.
+ * Throws exception if variable is not set or any other error occurred
+ * reading the value.
+ */
+ tstring getEnvVariable(const tstring& name);
+
+ /**
+ * Returns value of environment variable with the given name.
+ * Returns value of 'defValue' parameter if variable is not set or any
+ * other error occurred reading the value.
+ */
+ tstring getEnvVariable(const std::nothrow_t&, const tstring& name,
+ const tstring& defValue=tstring());
+
+ /**
+ * Returns 'true' if environment variable with the given name is set.
+ */
+ bool isEnvVariableSet(const tstring& name);
+}
+
+#endif // SYSINFO_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/UniqueHandle.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#ifndef UNIQUEHANDLE_H
+#define UNIQUEHANDLE_H
+
+#include <windows.h>
+#include <memory>
+
+
+struct WndHandleDeleter {
+ typedef HANDLE pointer;
+
+ void operator()(HANDLE h) {
+ ::CloseHandle(h);
+ }
+};
+
+typedef std::unique_ptr<HANDLE, WndHandleDeleter> UniqueHandle;
+
+#endif // #ifndef UNIQUEHANDLE_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/Utils.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+#include "Windows.h"
+#include "Utils.h"
+
+#define BUFFER_SIZE 4096
+
+wstring GetStringFromJString(JNIEnv *pEnv, jstring jstr) {
+ const jchar *pJChars = pEnv->GetStringChars(jstr, NULL);
+ if (pJChars == NULL) {
+ return wstring(L"");
+ }
+
+ wstring wstr(pJChars);
+
+ pEnv->ReleaseStringChars(jstr, pJChars);
+
+ return wstr;
+}
+
+jstring GetJStringFromString(JNIEnv *pEnv,
+ const jchar *unicodeChars, jsize len) {
+ return pEnv->NewString(unicodeChars, len);
+}
+
+wstring GetLongPath(wstring path) {
+ wstring result(L"");
+
+ size_t len = path.length();
+ if (len > 1) {
+ if (path.at(len - 1) == '\\') {
+ path.erase(len - 1);
+ }
+ }
+
+ TCHAR *pBuffer = new TCHAR[BUFFER_SIZE];
+ if (pBuffer != NULL) {
+ DWORD dwResult = GetLongPathName(path.c_str(), pBuffer, BUFFER_SIZE);
+ if (dwResult > 0 && dwResult < BUFFER_SIZE) {
+ result = wstring(pBuffer);
+ } else {
+ delete [] pBuffer;
+ pBuffer = new TCHAR[dwResult];
+ if (pBuffer != NULL) {
+ DWORD dwResult2 =
+ GetLongPathName(path.c_str(), pBuffer, dwResult);
+ if (dwResult2 == (dwResult - 1)) {
+ result = wstring(pBuffer);
+ }
+ }
+ }
+
+ if (pBuffer != NULL) {
+ delete [] pBuffer;
+ }
+ }
+
+ return result;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/Utils.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <string>
+#include "jni.h"
+
+using namespace std;
+
+wstring GetStringFromJString(JNIEnv *pEnv, jstring jstr);
+jstring GetJStringFromString(JNIEnv *pEnv, const jchar *unicodeChars,
+ jsize len);
+
+wstring GetLongPath(wstring path);
+
+#endif // UTILS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/VersionInfoSwap.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+#include "VersionInfoSwap.h"
+
+#include <stdio.h>
+#include <tchar.h>
+
+#include <windows.h>
+#include <stdio.h>
+#include <Strsafe.h>
+#include <fstream>
+#include <locale>
+#include <codecvt>
+
+using namespace std;
+
+/*
+ * [Property file] contains key/value pairs
+ * The swap tool uses these pairs to create new version resource
+ *
+ * See MSDN docs for VS_VERSIONINFO structure that
+ * depicts organization of data in this version resource
+ * https://msdn.microsoft.com/en-us/library/ms647001(v=vs.85).aspx
+ *
+ * The swap tool makes changes in [Executable file]
+ * The tool assumes that the executable file has no version resource
+ * and it adds new resource in the executable file.
+ * If the executable file has an existing version resource, then
+ * the existing version resource will be replaced with new one.
+ */
+
+VersionInfoSwap::VersionInfoSwap(wstring executableProperties,
+ wstring launcher) {
+ m_executableProperties = executableProperties;
+ m_launcher = launcher;
+}
+
+bool VersionInfoSwap::PatchExecutable() {
+ bool b = LoadFromPropertyFile();
+ if (!b) {
+ return false;
+ }
+
+ ByteBuffer buf;
+ b = CreateNewResource(&buf);
+ if (!b) {
+ return false;
+ }
+
+ b = this->UpdateResource(buf.getPtr(), static_cast<DWORD> (buf.getPos()));
+ if (!b) {
+ return false;
+ }
+
+ return true;
+}
+
+bool VersionInfoSwap::LoadFromPropertyFile() {
+ wifstream stream(m_executableProperties.c_str());
+
+ const locale empty_locale = locale::empty();
+ const locale utf8_locale =
+ locale(empty_locale, new codecvt_utf8<wchar_t>());
+ stream.imbue(utf8_locale);
+
+ if (stream.is_open() == true) {
+ int lineNumber = 1;
+ while (stream.eof() == false) {
+ wstring line;
+ getline(stream, line);
+
+ // # at the first character will comment out the line.
+ if (line.empty() == false && line[0] != '#') {
+ wstring::size_type pos = line.find('=');
+ if (pos != wstring::npos) {
+ wstring name = line.substr(0, pos);
+ wstring value = line.substr(pos + 1);
+ m_props[name] = value;
+ }
+ }
+ lineNumber++;
+ }
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Creates new version resource
+ *
+ * MSND docs for VS_VERSION_INFO structure
+ * https://msdn.microsoft.com/en-us/library/ms647001(v=vs.85).aspx
+ */
+bool VersionInfoSwap::CreateNewResource(ByteBuffer *buf) {
+ size_t versionInfoStart = buf->getPos();
+ buf->AppendWORD(0);
+ buf->AppendWORD(sizeof VS_FIXEDFILEINFO);
+ buf->AppendWORD(0);
+ buf->AppendString(TEXT("VS_VERSION_INFO"));
+ buf->Align(4);
+
+ VS_FIXEDFILEINFO fxi;
+ if (!FillFixedFileInfo(&fxi)) {
+ return false;
+ }
+ buf->AppendBytes((BYTE*) & fxi, sizeof (VS_FIXEDFILEINFO));
+ buf->Align(4);
+
+ // String File Info
+ size_t stringFileInfoStart = buf->getPos();
+ buf->AppendWORD(0);
+ buf->AppendWORD(0);
+ buf->AppendWORD(1);
+ buf->AppendString(TEXT("StringFileInfo"));
+ buf->Align(4);
+
+ // String Table
+ size_t stringTableStart = buf->getPos();
+ buf->AppendWORD(0);
+ buf->AppendWORD(0);
+ buf->AppendWORD(1);
+
+ // "040904B0" = LANG_ENGLISH/SUBLANG_ENGLISH_US, Unicode CP
+ buf->AppendString(TEXT("040904B0"));
+ buf->Align(4);
+
+ // Strings
+ vector<wstring> keys;
+ for (map<wstring, wstring>::const_iterator it =
+ m_props.begin(); it != m_props.end(); ++it) {
+ keys.push_back(it->first);
+ }
+
+ for (size_t index = 0; index < keys.size(); index++) {
+ wstring name = keys[index];
+ wstring value = m_props[name];
+
+ size_t stringStart = buf->getPos();
+ buf->AppendWORD(0);
+ buf->AppendWORD(static_cast<WORD> (value.length()));
+ buf->AppendWORD(1);
+ buf->AppendString(name);
+ buf->Align(4);
+ buf->AppendString(value);
+ buf->ReplaceWORD(stringStart,
+ static_cast<WORD> (buf->getPos() - stringStart));
+ buf->Align(4);
+ }
+
+ buf->ReplaceWORD(stringTableStart,
+ static_cast<WORD> (buf->getPos() - stringTableStart));
+ buf->ReplaceWORD(stringFileInfoStart,
+ static_cast<WORD> (buf->getPos() - stringFileInfoStart));
+
+ // VarFileInfo
+ size_t varFileInfoStart = buf->getPos();
+ buf->AppendWORD(1);
+ buf->AppendWORD(0);
+ buf->AppendWORD(1);
+ buf->AppendString(TEXT("VarFileInfo"));
+ buf->Align(4);
+
+ buf->AppendWORD(0x24);
+ buf->AppendWORD(0x04);
+ buf->AppendWORD(0x00);
+ buf->AppendString(TEXT("Translation"));
+ buf->Align(4);
+ // "000004B0" = LANG_NEUTRAL/SUBLANG_ENGLISH_US, Unicode CP
+ buf->AppendWORD(0x0000);
+ buf->AppendWORD(0x04B0);
+
+ buf->ReplaceWORD(varFileInfoStart,
+ static_cast<WORD> (buf->getPos() - varFileInfoStart));
+ buf->ReplaceWORD(versionInfoStart,
+ static_cast<WORD> (buf->getPos() - versionInfoStart));
+
+ return true;
+}
+
+bool VersionInfoSwap::FillFixedFileInfo(VS_FIXEDFILEINFO *fxi) {
+ wstring fileVersion;
+ wstring productVersion;
+ int ret;
+
+ fileVersion = m_props[TEXT("FileVersion")];
+ productVersion = m_props[TEXT("ProductVersion")];
+
+ unsigned fv_1 = 0, fv_2 = 0, fv_3 = 0, fv_4 = 0;
+ unsigned pv_1 = 0, pv_2 = 0, pv_3 = 0, pv_4 = 0;
+
+ ret = _stscanf_s(fileVersion.c_str(),
+ TEXT("%d.%d.%d.%d"), &fv_1, &fv_2, &fv_3, &fv_4);
+ if (ret <= 0 || ret > 4) {
+ return false;
+ }
+
+ ret = _stscanf_s(productVersion.c_str(),
+ TEXT("%d.%d.%d.%d"), &pv_1, &pv_2, &pv_3, &pv_4);
+ if (ret <= 0 || ret > 4) {
+ return false;
+ }
+
+ fxi->dwSignature = 0xFEEF04BD;
+ fxi->dwStrucVersion = 0x00010000;
+
+ fxi->dwFileVersionMS = MAKELONG(fv_2, fv_1);
+ fxi->dwFileVersionLS = MAKELONG(fv_4, fv_3);
+ fxi->dwProductVersionMS = MAKELONG(pv_2, pv_1);
+ fxi->dwProductVersionLS = MAKELONG(pv_4, pv_3);
+
+ fxi->dwFileFlagsMask = 0;
+ fxi->dwFileFlags = 0;
+ fxi->dwFileOS = VOS_NT_WINDOWS32;
+
+ wstring exeExt =
+ m_launcher.substr(m_launcher.find_last_of(TEXT(".")));
+ if (exeExt == TEXT(".exe")) {
+ fxi->dwFileType = VFT_APP;
+ } else if (exeExt == TEXT(".dll")) {
+ fxi->dwFileType = VFT_DLL;
+ } else {
+ fxi->dwFileType = VFT_UNKNOWN;
+ }
+ fxi->dwFileSubtype = 0;
+
+ fxi->dwFileDateLS = 0;
+ fxi->dwFileDateMS = 0;
+
+ return true;
+}
+
+/*
+ * Adds new resource in the executable
+ */
+bool VersionInfoSwap::UpdateResource(LPVOID lpResLock, DWORD size) {
+
+ HANDLE hUpdateRes;
+ BOOL r;
+
+ hUpdateRes = ::BeginUpdateResource(m_launcher.c_str(), FALSE);
+ if (hUpdateRes == NULL) {
+ return false;
+ }
+
+ r = ::UpdateResource(hUpdateRes,
+ RT_VERSION,
+ MAKEINTRESOURCE(VS_VERSION_INFO),
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
+ lpResLock,
+ size);
+
+ if (!r) {
+ return false;
+ }
+
+ if (!::EndUpdateResource(hUpdateRes, FALSE)) {
+ return false;
+ }
+
+ return true;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/VersionInfoSwap.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+#ifndef VERSIONINFOSWAP_H
+#define VERSIONINFOSWAP_H
+
+#include "ByteBuffer.h"
+#include <map>
+
+using namespace std;
+
+class VersionInfoSwap {
+public:
+ VersionInfoSwap(wstring executableProperties, wstring launcher);
+
+ bool PatchExecutable();
+
+private:
+ wstring m_executableProperties;
+ wstring m_launcher;
+
+ map<wstring, wstring> m_props;
+
+ bool LoadFromPropertyFile();
+ bool CreateNewResource(ByteBuffer *buf);
+ bool UpdateResource(LPVOID lpResLock, DWORD size);
+ bool FillFixedFileInfo(VS_FIXEDFILEINFO *fxi);
+};
+
+#endif // VERSIONINFOSWAP_H
\ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/WinErrorHandling.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,127 @@
+/*
+ * 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.
+ */
+
+#include "WinErrorHandling.h"
+#include "Log.h"
+#include "SysInfo.h"
+#include "FileUtils.h"
+
+
+namespace {
+
+std::string makeMessage(const std::string& msg, const char* label,
+ const void* c, DWORD errorCode) {
+ std::ostringstream err;
+ err << (label ? label : "Some error") << " [" << errorCode << "]";
+
+ HMODULE hmodule = NULL;
+ if (c) {
+ GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
+ | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+ reinterpret_cast<LPCTSTR>(c), &hmodule);
+
+ if (!hmodule) {
+ LOG_WARNING(tstrings::any() << "GetModuleHandleEx() failed for "
+ << c << " address.");
+ }
+ }
+ if (hmodule || !c) {
+ err << "(" << SysError::getSysErrorMessage(errorCode, hmodule) << ")";
+ }
+
+ return joinErrorMessages(msg, err.str());
+}
+
+
+std::wstring getSystemMessageDescription(DWORD messageId, HMODULE moduleHandle) {
+ LPWSTR pMsg = NULL;
+ std::wstring descr;
+
+ // we always retrieve UNICODE description from system,
+ // convert it to utf8 if UNICODE is not defined
+
+ while (true) {
+ DWORD res = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER
+ | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS
+ | (moduleHandle != NULL ? FORMAT_MESSAGE_FROM_HMODULE : 0),
+ moduleHandle, messageId, 0, (LPWSTR)&pMsg, 0, NULL);
+ if (res > 0) {
+ // replace all non-printed chars with space
+ for (DWORD i=0; i<res; i++) {
+ if (pMsg[i] < L' ') {
+ pMsg[i] = L' ';
+ }
+ }
+ // trim right (spaces and dots)
+ for (DWORD i=res; i>0; i--) {
+ if (pMsg[i] > L' ' && pMsg[i] != L'.') {
+ break;
+ }
+ pMsg[i] = 0;
+ }
+
+ descr = pMsg;
+
+ LocalFree(pMsg);
+ } else {
+ // if we fail to get description for specific moduleHandle,
+ // try to get "common" description.
+ if (moduleHandle != NULL) {
+ moduleHandle = NULL;
+ continue;
+ }
+ descr = L"No description available";
+ }
+ break;
+ }
+
+ return descr;
+}
+
+} // namespace
+
+
+SysError::SysError(const tstrings::any& msg, const void* caller, DWORD ec,
+ const char* label):
+
+std::runtime_error(makeMessage(msg.str(), label, caller, ec)) {
+}
+
+std::wstring SysError::getSysErrorMessage(DWORD errCode, HMODULE moduleHandle) {
+ tstrings::any msg;
+ msg << "system error " << errCode
+ << " (" << getSystemMessageDescription(errCode, moduleHandle) << ")";
+ return msg.tstr();
+}
+
+std::wstring SysError::getComErrorMessage(HRESULT hr) {
+ HRESULT hrOrig = hr;
+ // for FACILITY_WIN32 facility we need to reset hiword
+ if(HRESULT_FACILITY(hr) == FACILITY_WIN32) {
+ hr = HRESULT_CODE(hr);
+ }
+ return tstrings::format(_T("COM error 0x%08X (%s)"), hrOrig,
+ getSystemMessageDescription(hr, NULL));
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/WinErrorHandling.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+
+#ifndef WinErrorHandling_h
+#define WinErrorHandling_h
+
+
+#include "ErrorHandling.h"
+
+
+class SysError : public std::runtime_error {
+public:
+ SysError(const tstrings::any& msg, const void* caller,
+ DWORD errorCode=GetLastError(), const char* label="System error");
+
+ // returns string "system error <errCode> (error_description)"
+ // in UNICODE is not defined, the string returned is utf8-encoded
+ static std::wstring getSysErrorMessage(DWORD errCode = GetLastError(),
+ HMODULE moduleHandle = NULL);
+
+ // returns string "COM error 0x<hr> (error_description)"
+ // in UNICODE is not defined, the string returned is utf8-encoded
+ static std::wstring getComErrorMessage(HRESULT hr);
+};
+
+#endif // #ifndef WinErrorHandling_h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/WinSysInfo.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,198 @@
+/*
+ * 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.
+ */
+
+#include <windows.h>
+#include <shellapi.h>
+
+#include "WinSysInfo.h"
+#include "FileUtils.h"
+#include "WinErrorHandling.h"
+
+#pragma comment(lib, "Shell32")
+
+namespace SysInfo {
+
+tstring getTempDir() {
+ std::vector<TCHAR> buffer(MAX_PATH);
+ DWORD res = GetTempPath(static_cast<DWORD>(buffer.size()), buffer.data());
+ if (res > buffer.size()) {
+ buffer.resize(res);
+ GetTempPath(static_cast<DWORD>(buffer.size()), buffer.data());
+ }
+ return FileUtils::removeTrailingSlash(buffer.data());
+}
+
+namespace {
+
+template <class Func>
+tstring getSystemDirImpl(Func func, const std::string& label) {
+ std::vector<TCHAR> buffer(MAX_PATH);
+ for (int i=0; i<2; i++) {
+ DWORD res = func(buffer.data(), static_cast<DWORD>(buffer.size()));
+ if (!res) {
+ JP_THROW(SysError(label + " failed", func));
+ }
+ if (res < buffer.size()) {
+ return FileUtils::removeTrailingSlash(buffer.data());
+ }
+ buffer.resize(res + 1);
+ }
+ JP_THROW("Unexpected reply from" + label);
+}
+
+} // namespace
+
+tstring getSystem32Dir() {
+ return getSystemDirImpl(GetSystemDirectory, "GetSystemDirectory");
+}
+
+tstring getWIPath() {
+ return FileUtils::mkpath() << getSystem32Dir() << _T("msiexec.exe");
+}
+
+namespace {
+
+tstring getModulePath(HMODULE h)
+{
+ std::vector<TCHAR> buf(MAX_PATH);
+ DWORD len = 0;
+ while (true) {
+ len = GetModuleFileName(h, buf.data(), (DWORD)buf.size());
+ if (len < buf.size()) {
+ break;
+ }
+ // buffer is too small, increase it
+ buf.resize(buf.size() * 2);
+ }
+
+ if (len == 0) {
+ // error occured
+ JP_THROW(SysError("GetModuleFileName failed", GetModuleFileName));
+ }
+ return tstring(buf.begin(), buf.begin() + len);
+}
+
+} // namespace
+
+tstring getProcessModulePath() {
+ return getModulePath(NULL);
+}
+
+HMODULE getCurrentModuleHandle()
+{
+ // get module handle for the address of this function
+ LPCWSTR address = reinterpret_cast<LPCWSTR>(getCurrentModuleHandle);
+ HMODULE hmodule = NULL;
+ if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
+ | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, address, &hmodule))
+ {
+ JP_THROW(SysError(tstrings::any() << "GetModuleHandleExW failed",
+ GetModuleHandleExW));
+ }
+ return hmodule;
+}
+
+tstring getCurrentModulePath()
+{
+ return getModulePath(getCurrentModuleHandle());
+}
+
+tstring_array getCommandArgs(CommandArgProgramNameMode progNameMode)
+{
+ int argc = 0;
+ tstring_array result;
+
+ LPWSTR *parsedArgs = CommandLineToArgvW(GetCommandLineW(), &argc);
+ if (parsedArgs == NULL) {
+ JP_THROW(SysError("CommandLineToArgvW failed", CommandLineToArgvW));
+ }
+ // the 1st element contains program name
+ for (int i = progNameMode == ExcludeProgramName ? 1 : 0; i < argc; i++) {
+ result.push_back(parsedArgs[i]);
+ }
+ LocalFree(parsedArgs);
+
+ return result;
+}
+
+namespace {
+
+tstring getEnvVariableImpl(const tstring& name, bool* errorOccured=0) {
+ std::vector<TCHAR> buf(10);
+ SetLastError(ERROR_SUCCESS);
+ const DWORD size = GetEnvironmentVariable(name.c_str(), buf.data(),
+ DWORD(buf.size()));
+ if (GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
+ if (errorOccured) {
+ *errorOccured = true;
+ return tstring();
+ }
+ JP_THROW(SysError(tstrings::any() << "GetEnvironmentVariable("
+ << name << ") failed. Variable not set", GetEnvironmentVariable));
+ }
+
+ if (size > buf.size()) {
+ buf.resize(size);
+ GetEnvironmentVariable(name.c_str(), buf.data(), DWORD(buf.size()));
+ if (GetLastError() != ERROR_SUCCESS) {
+ if (errorOccured) {
+ *errorOccured = true;
+ return tstring();
+ }
+ JP_THROW(SysError(tstrings::any() << "GetEnvironmentVariable("
+ << name << ") failed", GetEnvironmentVariable));
+ }
+ }
+
+ if (errorOccured) {
+ *errorOccured = false;
+ }
+ return tstring(buf.data());
+}
+
+} // namespace
+
+tstring getEnvVariable(const tstring& name) {
+ return getEnvVariableImpl(name);
+}
+
+tstring getEnvVariable(const std::nothrow_t&, const tstring& name,
+ const tstring& defValue) {
+ bool errorOccured = false;
+ const tstring reply = getEnvVariableImpl(name, &errorOccured);
+ if (errorOccured) {
+ return defValue;
+ }
+ return reply;
+}
+
+bool isEnvVariableSet(const tstring& name) {
+ TCHAR unused[1];
+ SetLastError(ERROR_SUCCESS);
+ GetEnvironmentVariable(name.c_str(), unused, _countof(unused));
+ return GetLastError() != ERROR_ENVVAR_NOT_FOUND;
+}
+
+} // end of namespace SysInfo
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/WinSysInfo.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+
+#ifndef WINSYSINFO_H
+#define WINSYSINFO_H
+
+#include "SysInfo.h"
+
+
+//
+// Windows specific SysInfo.
+//
+namespace SysInfo {
+ // gets Windows System folder. A typical path is C:\Windows\System32.
+ tstring getSystem32Dir();
+
+ // returns full path to msiexec.exe executable
+ tstring getWIPath();
+
+ // Returns handle of the current module (exe or dll).
+ // The function assumes this code is statically linked to the module.
+ HMODULE getCurrentModuleHandle();
+}
+
+
+#endif // WINSYSINFO_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/WindowsRegistry.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,168 @@
+/*
+ * 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.
+ */
+
+#include <Windows.h>
+#include <strsafe.h>
+#include <tchar.h>
+#include <jni.h>
+
+#include "Utils.h"
+
+// Max value name size per MSDN plus NULL
+#define VALUE_NAME_SIZE 16384
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef jdk_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE
+#define jdk_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE 1L
+
+ /*
+ * Class: jdk_jpackage_internal_WindowsRegistry
+ * Method: readDwordValue
+ * Signature: (ILjava/lang/String;Ljava/lang/String;I)I
+ */
+ JNIEXPORT jint JNICALL
+ Java_jdk_jpackage_internal_WindowsRegistry_readDwordValue(
+ JNIEnv *pEnv, jclass c, jint key, jstring jSubKey,
+ jstring jValue, jint defaultValue) {
+ jint jResult = defaultValue;
+
+ if (key != jdk_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE) {
+ return jResult;
+ }
+
+ wstring subKey = GetStringFromJString(pEnv, jSubKey);
+ wstring value = GetStringFromJString(pEnv, jValue);
+
+ HKEY hSubKey = NULL;
+ LSTATUS status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, subKey.c_str(), 0,
+ KEY_QUERY_VALUE, &hSubKey);
+ if (status == ERROR_SUCCESS) {
+ DWORD dwValue = 0;
+ DWORD cbData = sizeof (DWORD);
+ status = RegQueryValueEx(hSubKey, value.c_str(), NULL, NULL,
+ (LPBYTE) & dwValue, &cbData);
+ if (status == ERROR_SUCCESS) {
+ jResult = (jint) dwValue;
+ }
+
+ RegCloseKey(hSubKey);
+ }
+
+ return jResult;
+ }
+
+ /*
+ * Class: jdk_jpackage_internal_WindowsRegistry
+ * Method: openRegistryKey
+ * Signature: (ILjava/lang/String;)J
+ */
+ JNIEXPORT jlong JNICALL
+ Java_jdk_jpackage_internal_WindowsRegistry_openRegistryKey(
+ JNIEnv *pEnv, jclass c, jint key, jstring jSubKey) {
+ if (key != jdk_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE) {
+ return 0;
+ }
+
+ wstring subKey = GetStringFromJString(pEnv, jSubKey);
+ HKEY hSubKey = NULL;
+ LSTATUS status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, subKey.c_str(), 0,
+ KEY_QUERY_VALUE, &hSubKey);
+ if (status == ERROR_SUCCESS) {
+ return (jlong)hSubKey;
+ }
+
+ return 0;
+ }
+
+ /*
+ * Class: jdk_jpackage_internal_WindowsRegistry
+ * Method: enumRegistryValue
+ * Signature: (JI)Ljava/lang/String;
+ */
+ JNIEXPORT jstring JNICALL
+ Java_jdk_jpackage_internal_WindowsRegistry_enumRegistryValue(
+ JNIEnv *pEnv, jclass c, jlong lKey, jint jIndex) {
+ HKEY hKey = (HKEY)lKey;
+ TCHAR valueName[VALUE_NAME_SIZE] = {0}; // Max size per MSDN plus NULL
+ DWORD cchValueName = VALUE_NAME_SIZE;
+ LSTATUS status = RegEnumValue(hKey, (DWORD)jIndex, valueName,
+ &cchValueName, NULL, NULL, NULL, NULL);
+ if (status == ERROR_SUCCESS) {
+ size_t chLength = 0;
+ if (StringCchLength(valueName, VALUE_NAME_SIZE, &chLength)
+ == S_OK) {
+ return GetJStringFromString(pEnv, valueName, (jsize)chLength);
+ }
+ }
+
+ return NULL;
+ }
+
+ /*
+ * Class: jdk_jpackage_internal_WindowsRegistry
+ * Method: closeRegistryKey
+ * Signature: (J)V
+ */
+ JNIEXPORT void JNICALL
+ Java_jdk_jpackage_internal_WindowsRegistry_closeRegistryKey(
+ JNIEnv *pEnc, jclass c, jlong lKey) {
+ HKEY hKey = (HKEY)lKey;
+ RegCloseKey(hKey);
+ }
+
+ /*
+ * Class: jdk_jpackage_internal_WindowsRegistry
+ * Method: comparePaths
+ * Signature: (Ljava/lang/String;Ljava/lang/String;)Z
+ */
+ JNIEXPORT jboolean JNICALL
+ Java_jdk_jpackage_internal_WindowsRegistry_comparePaths(
+ JNIEnv *pEnv, jclass c, jstring jPath1, jstring jPath2) {
+ wstring path1 = GetStringFromJString(pEnv, jPath1);
+ wstring path2 = GetStringFromJString(pEnv, jPath2);
+
+ path1 = GetLongPath(path1);
+ path2 = GetLongPath(path2);
+
+ if (path1.length() == 0 || path2.length() == 0) {
+ return JNI_FALSE;
+ }
+
+ if (path1.length() != path2.length()) {
+ return JNI_FALSE;
+ }
+
+ if (_tcsnicmp(path1.c_str(), path2.c_str(), path1.length()) == 0) {
+ return JNI_TRUE;
+ }
+
+ return JNI_FALSE;
+ }
+
+#ifdef __cplusplus
+}
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/jpackage.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2011, 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+#include <windows.h>
+
+#include "ResourceEditor.h"
+#include "WinErrorHandling.h"
+#include "IconSwap.h"
+#include "VersionInfoSwap.h"
+#include "Utils.h"
+
+using namespace std;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ /*
+ * Class: jdk_jpackage_internal_WindowsAppImageBuilder
+ * Method: iconSwap
+ * Signature: (Ljava/lang/String;Ljava/lang/String;)I
+ */
+ JNIEXPORT jint JNICALL
+ Java_jdk_jpackage_internal_WindowsAppImageBuilder_iconSwap(
+ JNIEnv *pEnv, jclass c, jstring jIconTarget, jstring jLauncher) {
+ wstring iconTarget = GetStringFromJString(pEnv, jIconTarget);
+ wstring launcher = GetStringFromJString(pEnv, jLauncher);
+
+ if (ChangeIcon(iconTarget, launcher)) {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ /*
+ * Class: jdk_jpackage_internal_WindowsAppImageBuilder
+ * Method: versionSwap
+ * Signature: (Ljava/lang/String;Ljava/lang/String;)I
+ */
+ JNIEXPORT jint JNICALL
+ Java_jdk_jpackage_internal_WindowsAppImageBuilder_versionSwap(
+ JNIEnv *pEnv, jclass c, jstring jExecutableProperties,
+ jstring jLauncher) {
+
+ wstring executableProperties = GetStringFromJString(pEnv,
+ jExecutableProperties);
+ wstring launcher = GetStringFromJString(pEnv, jLauncher);
+
+ VersionInfoSwap vs(executableProperties, launcher);
+ if (vs.PatchExecutable()) {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ /*
+ * Class: jdk_jpackage_internal_WinExeBundler
+ * Method: embedMSI
+ * Signature: (Ljava/lang/String;Ljava/lang/String;)I
+ */
+ JNIEXPORT jint JNICALL Java_jdk_jpackage_internal_WinExeBundler_embedMSI(
+ JNIEnv *pEnv, jclass c, jstring jexePath, jstring jmsiPath) {
+
+ const wstring exePath = GetStringFromJString(pEnv, jexePath);
+ const wstring msiPath = GetStringFromJString(pEnv, jmsiPath);
+
+ JP_TRY;
+
+ ResourceEditor()
+ .id(L"msi")
+ .type(RT_RCDATA)
+ .apply(ResourceEditor::FileLock(exePath), msiPath);
+
+ return 0;
+
+ JP_CATCH_ALL;
+
+ return 1;
+ }
+
+ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason,
+ LPVOID lpvReserved) {
+ return TRUE;
+ }
+
+#ifdef __cplusplus
+}
+#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/tstrings.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,280 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdexcept>
+#include <algorithm>
+
+#include "tstrings.h"
+#include "ErrorHandling.h"
+
+
+namespace tstrings {
+
+/* Create formatted string
+ */
+tstring unsafe_format(tstring::const_pointer format, ...) {
+ if (!format) {
+ throw std::invalid_argument("Destination buffer can't be NULL");
+ }
+
+ tstring fmtout;
+ int ret;
+ const int inc = 256;
+
+ va_list args;
+ va_start(args, format);
+ do {
+ fmtout.resize(fmtout.size() + inc);
+#ifdef _MSC_VER
+ ret = _vsntprintf_s(&*fmtout.begin(), fmtout.size(), _TRUNCATE, format, args);
+#else
+ // With g++ this compiles only with '-std=gnu++0x' option
+ ret = vsnprintf(&*fmtout.begin(), fmtout.size(), format, args);
+#endif
+ } while(-1 == ret);
+ va_end(args);
+
+ //update string size by actual value
+ fmtout.resize(ret);
+
+ return fmtout;
+}
+
+/*
+ * Tests if two strings are equal according to CompareType.
+ *
+ * a - string to compare
+ * b - string to compare
+ * ct - CASE_SENSITIVE: case sensitive comparing type
+ * IGNORE_CASE: case insensitive comparing type
+ */
+bool equals(const tstring& a, const tstring& b, const CompareType ct) {
+ if (IGNORE_CASE==ct) {
+ return toLower(a) == toLower(b);
+ }
+ return a == b;
+}
+
+bool startsWith(const tstring &str, const tstring &substr, const CompareType ct)
+{
+ if (str.size() < substr.size()) {
+ return false;
+ }
+ const tstring startOfStr = str.substr(0, substr.size());
+ return tstrings::equals(startOfStr, substr, ct);
+}
+
+bool endsWith(const tstring &str, const tstring &substr, const CompareType ct)
+{
+ if (str.size() < substr.size()) {
+ return false;
+ }
+ const tstring endOfStr = str.substr(str.size() - substr.size());
+ return tstrings::equals(endOfStr, substr, ct);
+}
+
+/*
+ * Split string into a vector with given delimiter string
+ *
+ * strVector - string vector to store split tstring
+ * str - string to split
+ * delimiter - delimiter to split the string around
+ * st - ST_ALL: return value includes an empty string
+ * ST_EXCEPT_EMPTY_STRING: return value does not include an empty string
+ *
+ * Note: It does not support multiple delimiters
+ */
+void split(tstring_array &strVector, const tstring &str,
+ const tstring &delimiter, const SplitType st) {
+ tstring::size_type start = 0, end = 0, length = str.length();
+
+ if (length == 0 || delimiter.length() == 0) {
+ return;
+ }
+
+ end = str.find(delimiter, start);
+ while(end != tstring::npos) {
+ if(st == ST_ALL || end - start > 1 ) {
+ strVector.push_back(str.substr(start, end == tstring::npos ?
+ tstring::npos : end - start));
+ }
+ start = end > (tstring::npos - delimiter.size()) ?
+ tstring::npos : end + delimiter.size();
+ end = str.find(delimiter, start);
+ }
+
+ if(st == ST_ALL || start < length) {
+ strVector.push_back(str.substr(start, length - start));
+ }
+}
+
+/*
+ * Convert uppercase letters to lowercase
+ */
+tstring toLower(const tstring& str) {
+ tstring lower(str);
+ tstring::iterator ok = std::transform(lower.begin(), lower.end(),
+ lower.begin(), tolower);
+ if (ok!=lower.end()) {
+ lower.resize(0);
+ }
+ return lower;
+}
+
+
+/*
+ * Replace all substring occurrences in a tstring.
+ * If 'str' or 'search' is empty the function returns 'str'.
+ * The given 'str' remains unchanged in any case.
+ * The function returns changed copy of 'str'.
+ */
+tstring replace(const tstring &str, const tstring &search, const tstring &replace)
+{
+ if (search.empty()) {
+ return str;
+ }
+
+ tstring s(str);
+
+ for (size_t pos = 0; ; pos += replace.length()) {
+ pos = s.find(search, pos);
+ if (pos == tstring::npos) {
+ break;
+ }
+ s.erase(pos, search.length());
+ s.insert(pos, replace);
+ }
+ return s;
+}
+
+
+/*
+ * Remove trailing spaces
+ */
+
+tstring trim(const tstring& str, const tstring& whitespace) {
+ const size_t strBegin = str.find_first_not_of(whitespace);
+ if (strBegin == std::string::npos) {
+ return tstring(); // no content
+ }
+
+ const size_t strEnd = str.find_last_not_of(whitespace);
+ const size_t strRange = strEnd - strBegin + 1;
+
+ return str.substr(strBegin, strRange);
+}
+
+} // namespace tstrings
+
+
+#ifdef TSTRINGS_WITH_WCHAR
+namespace tstrings {
+
+namespace {
+/*
+ * Converts UTF16-encoded string into multi-byte string of the given encoding.
+ */
+std::string toMultiByte(const std::wstring& utf16str, int encoding) {
+ std::string reply;
+ do {
+ int cm = WideCharToMultiByte(encoding,
+ 0,
+ utf16str.c_str(),
+ int(utf16str.size()),
+ NULL,
+ 0,
+ NULL,
+ NULL);
+ if (cm < 0) {
+ JP_THROW("Unexpected reply from WideCharToMultiByte()");
+ }
+ if (0 == cm) {
+ break;
+ }
+
+ reply.resize(cm);
+ int cm2 = WideCharToMultiByte(encoding,
+ 0,
+ utf16str.c_str(),
+ int(utf16str.size()),
+ &*reply.begin(),
+ cm,
+ NULL,
+ NULL);
+ if (cm != cm2) {
+ JP_THROW("Unexpected reply from WideCharToMultiByte()");
+ }
+ } while(0);
+
+ return reply;
+}
+
+/*
+ * Converts multi-byte string of the given encoding into UTF16-encoded string.
+ */
+std::wstring fromMultiByte(const std::string& str, int encoding) {
+ std::wstring utf16;
+ do {
+ int cw = MultiByteToWideChar(encoding,
+ MB_ERR_INVALID_CHARS,
+ str.c_str(),
+ int(str.size()),
+ NULL,
+ 0);
+ if (cw < 0) {
+ JP_THROW("Unexpected reply from MultiByteToWideChar()");
+ }
+ if (0 == cw) {
+ break;
+ }
+
+ utf16.resize(cw);
+ int cw2 = MultiByteToWideChar(encoding,
+ MB_ERR_INVALID_CHARS,
+ str.c_str(),
+ int(str.size()),
+ &*utf16.begin(),
+ cw);
+ if (cw != cw2) {
+ JP_THROW("Unexpected reply from MultiByteToWideChar()");
+ }
+ } while(0);
+
+ return utf16;
+}
+} // namespace
+
+std::string toUtf8(const std::wstring& utf16str) {
+ return toMultiByte(utf16str, CP_UTF8);
+}
+
+std::wstring toUtf16(const std::string& utf8str) {
+ return fromMultiByte(utf8str, CP_UTF8);
+}
+
+} // namespace tstrings
+#endif // ifdef TSTRINGS_WITH_WCHAR
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libjpackage/tstrings.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,426 @@
+/*
+ * 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.
+ */
+
+#ifndef TSTRINGS_H
+#define TSTRINGS_H
+
+#ifdef _MSC_VER
+# define TSTRINGS_WITH_WCHAR
+#endif
+
+#ifdef TSTRINGS_WITH_WCHAR
+#include <windows.h>
+#include <tchar.h>
+// Want compiler issue C4995 warnings for encounters of deprecated functions.
+#include <strsafe.h>
+#endif
+
+// STL's string header depends on deprecated functions.
+// We don't care about warnings from STL header, so disable them locally.
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable:4995)
+#endif
+
+#include <string>
+#include <sstream>
+#include <iostream>
+#include <vector>
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+
+#ifndef _T
+# define _T(x) x
+#endif
+
+
+#ifdef TSTRINGS_WITH_WCHAR
+typedef std::wstring tstring;
+typedef std::wostringstream tostringstream;
+typedef std::wistringstream tistringstream;
+typedef std::wstringstream tstringstream;
+typedef std::wistream tistream;
+typedef std::wostream tostream;
+typedef std::wiostream tiostream;
+typedef std::wios tios;
+#else
+typedef std::string tstring;
+typedef std::ostringstream tostringstream;
+typedef std::istringstream tistringstream;
+typedef std::stringstream tstringstream;
+typedef std::istream tistream;
+typedef std::ostream tostream;
+typedef std::iostream tiostream;
+typedef std::ios tios;
+
+typedef const char* LPCTSTR;
+typedef char TCHAR;
+#endif
+
+// frequently used "array of tstrings" type
+typedef std::vector<tstring> tstring_array;
+
+namespace tstrings {
+ tstring unsafe_format(tstring::const_pointer format, ...);
+
+ enum CompareType {CASE_SENSITIVE, IGNORE_CASE};
+ bool equals(const tstring& a, const tstring& b,
+ const CompareType ct=CASE_SENSITIVE);
+ bool startsWith(const tstring &str, const tstring &substr,
+ const CompareType ct=CASE_SENSITIVE);
+ bool endsWith(const tstring &str, const tstring &substr,
+ const CompareType ct=CASE_SENSITIVE);
+
+ enum SplitType {ST_ALL, ST_EXCEPT_EMPTY_STRING};
+ void split(tstring_array &strVector, const tstring &str,
+ const tstring &delimiter, const SplitType st = ST_ALL);
+ inline tstring_array split(const tstring &str, const tstring &delimiter,
+ const SplitType st = ST_ALL) {
+ tstring_array result;
+ split(result, str, delimiter, st);
+ return result;
+ }
+ tstring trim(const tstring& str, const tstring& whitespace = _T(" \t"));
+
+ /**
+ * Writes sequence of values from [b, e) range into string buffer inserting
+ * 'delimiter' after each value except of the last one.
+ * Returns contents of string buffer.
+ */
+ template <class It>
+ tstring join(It b, It e, const tstring& delimiter=tstring()) {
+ tostringstream buf;
+ if (b != e) {
+ for (;;) {
+ buf << *b;
+ if (++b == e) {
+ break;
+ }
+ buf << delimiter;
+ }
+ }
+ return buf.str();
+ }
+
+ tstring toLower(const tstring& str);
+
+ tstring replace(const tstring &str, const tstring &search,
+ const tstring &replace);
+}
+
+
+namespace tstrings {
+ inline std::string toUtf8(const std::string& utf8str) {
+ return utf8str;
+ }
+
+#ifdef TSTRINGS_WITH_WCHAR
+ // conversion to Utf8
+ std::string toUtf8(const std::wstring& utf16str);
+
+ // conversion to Utf16
+ std::wstring toUtf16(const std::string& utf8str);
+
+ inline std::wstring fromUtf8(const std::string& utf8str) {
+ return toUtf16(utf8str);
+ }
+
+#else
+ inline std::string fromUtf8(const std::string& utf8str) {
+ return utf8str;
+ }
+#endif
+} // namespace tstrings
+
+
+namespace tstrings {
+namespace format_detail {
+
+ template <class T>
+ struct str_arg_value {
+ const tstring value;
+
+ str_arg_value(const std::string& v): value(fromUtf8(v)) {
+ }
+
+#ifdef TSTRINGS_WITH_WCHAR
+ str_arg_value(const std::wstring& v): value(v) {
+ }
+#endif
+
+ tstring::const_pointer operator () () const {
+ return value.c_str();
+ }
+ };
+
+ template <>
+ struct str_arg_value<tstring> {
+ const tstring::const_pointer value;
+
+ str_arg_value(const tstring& v): value(v.c_str()) {
+ }
+
+ str_arg_value(tstring::const_pointer v): value(v) {
+ }
+
+ tstring::const_pointer operator () () const {
+ return value;
+ }
+ };
+
+ inline str_arg_value<std::string> arg(const std::string& v) {
+ return v;
+ }
+
+ inline str_arg_value<std::string> arg(std::string::const_pointer v) {
+ return (v ? v : "(null)");
+ }
+
+#ifdef TSTRINGS_WITH_WCHAR
+ inline str_arg_value<std::wstring> arg(const std::wstring& v) {
+ return v;
+ }
+
+ inline str_arg_value<std::wstring> arg(std::wstring::const_pointer v) {
+ return (v ? v : L"(null)");
+ }
+#else
+ void arg(const std::wstring&); // Compilation error by design.
+ void arg(std::wstring::const_pointer); // Compilation error by design.
+#endif
+
+ template <class T>
+ struct arg_value {
+ arg_value(const T v): v(v) {
+ }
+ T operator () () const {
+ return v;
+ }
+ private:
+ const T v;
+ };
+
+ inline arg_value<int> arg(int v) {
+ return v;
+ }
+ inline arg_value<unsigned> arg(unsigned v) {
+ return v;
+ }
+ inline arg_value<long> arg(long v) {
+ return v;
+ }
+ inline arg_value<unsigned long> arg(unsigned long v) {
+ return v;
+ }
+ inline arg_value<long long> arg(long long v) {
+ return v;
+ }
+ inline arg_value<unsigned long long> arg(unsigned long long v) {
+ return v;
+ }
+ inline arg_value<float> arg(float v) {
+ return v;
+ }
+ inline arg_value<double> arg(double v) {
+ return v;
+ }
+ inline arg_value<bool> arg(bool v) {
+ return v;
+ }
+ inline arg_value<const void*> arg(const void* v) {
+ return v;
+ }
+
+} // namespace format_detail
+} // namespace tstrings
+
+
+namespace tstrings {
+ template <class T, class T2, class T3, class T4, class T5, class T6, class T7>
+ inline tstring format(const tstring& fmt, const T& v, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6, const T7& v7) {
+ return unsafe_format(fmt.c_str(), format_detail::arg(v)(),
+ format_detail::arg(v2)(),
+ format_detail::arg(v3)(),
+ format_detail::arg(v4)(),
+ format_detail::arg(v5)(),
+ format_detail::arg(v6)(),
+ format_detail::arg(v7)());
+ }
+
+ template <class T, class T2, class T3, class T4, class T5, class T6>
+ inline tstring format(const tstring& fmt, const T& v, const T2& v2, const T3& v3, const T4& v4, const T5& v5, const T6& v6) {
+ return unsafe_format(fmt.c_str(), format_detail::arg(v)(),
+ format_detail::arg(v2)(),
+ format_detail::arg(v3)(),
+ format_detail::arg(v4)(),
+ format_detail::arg(v5)(),
+ format_detail::arg(v6)());
+ }
+
+ template <class T, class T2, class T3, class T4, class T5>
+ inline tstring format(const tstring& fmt, const T& v, const T2& v2, const T3& v3, const T4& v4, const T5& v5) {
+ return unsafe_format(fmt.c_str(), format_detail::arg(v)(),
+ format_detail::arg(v2)(),
+ format_detail::arg(v3)(),
+ format_detail::arg(v4)(),
+ format_detail::arg(v5)());
+ }
+
+ template <class T, class T2, class T3, class T4>
+ inline tstring format(const tstring& fmt, const T& v, const T2& v2, const T3& v3, const T4& v4) {
+ return unsafe_format(fmt.c_str(), format_detail::arg(v)(),
+ format_detail::arg(v2)(),
+ format_detail::arg(v3)(),
+ format_detail::arg(v4)());
+ }
+
+ template <class T, class T2, class T3>
+ inline tstring format(const tstring& fmt, const T& v, const T2& v2, const T3& v3) {
+ return unsafe_format(fmt.c_str(), format_detail::arg(v)(),
+ format_detail::arg(v2)(),
+ format_detail::arg(v3)());
+ }
+
+ template <class T, class T2>
+ inline tstring format(const tstring& fmt, const T& v, const T2& v2) {
+ return unsafe_format(fmt.c_str(), format_detail::arg(v)(),
+ format_detail::arg(v2)());
+
+ }
+
+ template <class T>
+ inline tstring format(const tstring& fmt, const T& v) {
+ return unsafe_format(fmt.c_str(), format_detail::arg(v)());
+ }
+} // namespace tstrings
+
+
+namespace tstrings {
+ /**
+ * Buffer that accepts both std::wstring and std::string instances doing
+ * encoding conversions behind the scenes. All std::string-s assumed to be
+ * UTF8-encoded, all std::wstring-s assumed to be UTF16-encoded.
+ */
+ class any {
+ public:
+ any() {
+ }
+
+ any(std::string::const_pointer msg) {
+ data << fromUtf8(msg);
+ }
+
+ any(const std::string& msg) {
+ data << fromUtf8(msg);
+ }
+
+#ifdef TSTRINGS_WITH_WCHAR
+ any(std::wstring::const_pointer msg) {
+ data << msg;
+ }
+
+ any(const std::wstring& msg) {
+ data << msg;
+ }
+
+ any& operator << (const std::wstring& v) {
+ data << v;
+ return *this;
+ }
+
+ // need this specialization instead std::wstring::pointer,
+ // otherwise LPWSTR is handled as abstract pointer (void*)
+ any& operator << (LPWSTR v) {
+ data << (v ? v : L"NULL");
+ return *this;
+ }
+
+ // need this specialization instead std::wstring::const_pointer,
+ // otherwise LPCWSTR is handled as abstract pointer (const void*)
+ any& operator << (LPCWSTR v) {
+ data << (v ? v : L"NULL");
+ return *this;
+ }
+
+ std::wstring wstr() const {
+ return data.str();
+ }
+#endif
+
+ template <class T>
+ any& operator << (T v) {
+ data << v;
+ return *this;
+ }
+
+ any& operator << (tostream& (*pf)(tostream&)) {
+ data << pf;
+ return *this;
+ }
+
+ any& operator << (tios& (*pf)(tios&)) {
+ data << pf;
+ return *this;
+ }
+
+ any& operator << (std::ios_base& (*pf)(std::ios_base&)) {
+ data << pf;
+ return *this;
+ }
+
+ std::string str() const {
+ return toUtf8(data.str());
+ }
+
+ tstring tstr() const {
+ return data.str();
+ }
+
+ private:
+ tostringstream data;
+ };
+
+ inline tstring to_tstring(const any& val) {
+ return val.tstr();
+ }
+} // namespace tstrings
+
+
+inline std::ostream& operator << (std::ostream& os, const tstrings::any& buf) {
+ os << buf.str();
+ return os;
+}
+
+#ifdef TSTRINGS_WITH_WCHAR
+inline std::wostream& operator << (std::wostream& os, const tstrings::any& buf) {
+ os << buf.wstr();
+ return os;
+}
+#endif
+
+#endif //TSTRINGS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/libwixhelper/libwixhelper.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+#include <Windows.h>
+#include <msiquery.h>
+#include <shlwapi.h>
+
+extern "C" {
+
+#ifdef JP_EXPORT_FUNCTION
+#error Unexpected JP_EXPORT_FUNCTION define
+#endif
+#define JP_EXPORT_FUNCTION comment(linker, "/EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)
+
+ BOOL WINAPI DllMain(HINSTANCE hInst, ULONG ulReason,
+ LPVOID lpvReserved) {
+ return TRUE;
+ }
+
+ BOOL DirectoryExist(TCHAR *szValue) {
+ DWORD attr = GetFileAttributes(szValue);
+ if (attr == INVALID_FILE_ATTRIBUTES) {
+ return FALSE;
+ }
+
+ if (attr & FILE_ATTRIBUTE_DIRECTORY) {
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+ UINT __stdcall CheckInstallDir(MSIHANDLE hInstall) {
+ #pragma JP_EXPORT_FUNCTION
+
+ TCHAR *szValue = NULL;
+ DWORD cchSize = 0;
+
+ UINT result = MsiGetProperty(hInstall, TEXT("APPLICATIONFOLDER"),
+ TEXT(""), &cchSize);
+ if (result == ERROR_MORE_DATA) {
+ cchSize = cchSize + 1; // NULL termination
+ szValue = new TCHAR[cchSize];
+ if (szValue) {
+ result = MsiGetProperty(hInstall, TEXT("APPLICATIONFOLDER"),
+ szValue, &cchSize);
+ } else {
+ return ERROR_INSTALL_FAILURE;
+ }
+ }
+
+ if (result != ERROR_SUCCESS) {
+ delete [] szValue;
+ return ERROR_INSTALL_FAILURE;
+ }
+
+ if (DirectoryExist(szValue)) {
+ if (PathIsDirectoryEmpty(szValue)) {
+ MsiSetProperty(hInstall, TEXT("INSTALLDIR_VALID"), TEXT("1"));
+ } else {
+ MsiSetProperty(hInstall, TEXT("INSTALLDIR_VALID"), TEXT("0"));
+ }
+ } else {
+ MsiSetProperty(hInstall, TEXT("INSTALLDIR_VALID"), TEXT("1"));
+ }
+
+ delete [] szValue;
+
+ return ERROR_SUCCESS;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/msiwrapper/Executor.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+#include <algorithm>
+#include "Executor.h"
+#include "Log.h"
+#include "WinErrorHandling.h"
+
+
+namespace {
+
+void escapeArg(std::wstring& str) {
+ if (str.empty()) {
+ return;
+ }
+
+ if (str.front() == L'\"' && str.back() == L'\"' && str.size() > 1) {
+ return;
+ }
+
+ if (str.find_first_of(L" \t") != std::wstring::npos) {
+ str = L'"' + str + L'"';
+ }
+}
+
+} // namespace
+
+
+std::wstring Executor::args() const {
+ tstring_array tmpArgs;
+ // argv[0] is the module name.
+ tmpArgs.push_back(appPath);
+ tmpArgs.insert(tmpArgs.end(), argsArray.begin(), argsArray.end());
+
+ std::for_each(tmpArgs.begin(), tmpArgs.end(), escapeArg);
+ return tstrings::join(tmpArgs.begin(), tmpArgs.end(), _T(" "));
+}
+
+
+int Executor::execAndWaitForExit() const {
+ UniqueHandle h = startProcess();
+
+ const DWORD res = ::WaitForSingleObject(h.get(), INFINITE);
+ if (WAIT_FAILED == res) {
+ JP_THROW(SysError("WaitForSingleObject() failed", WaitForSingleObject));
+ }
+
+ DWORD exitCode = 0;
+ if (!GetExitCodeProcess(h.get(), &exitCode)) {
+ // Error reading process's exit code.
+ JP_THROW(SysError("GetExitCodeProcess() failed", GetExitCodeProcess));
+ }
+
+ const DWORD processId = GetProcessId(h.get());
+ if (!processId) {
+ JP_THROW(SysError("GetProcessId() failed.", GetProcessId));
+ }
+
+ LOG_TRACE(tstrings::any() << "Process with PID=" << processId
+ << " terminated. Exit code=" << exitCode);
+
+ return static_cast<int>(exitCode);
+}
+
+
+UniqueHandle Executor::startProcess() const {
+ const std::wstring argsStr = args();
+
+ std::vector<TCHAR> argsBuffer(argsStr.begin(), argsStr.end());
+ argsBuffer.push_back(0); // terminating '\0'
+
+ STARTUPINFO startupInfo;
+ ZeroMemory(&startupInfo, sizeof(startupInfo));
+ startupInfo.cb = sizeof(startupInfo);
+
+ PROCESS_INFORMATION processInfo;
+ ZeroMemory(&processInfo, sizeof(processInfo));
+
+ DWORD creationFlags = 0;
+
+ if (!theVisible) {
+ // For GUI applications.
+ startupInfo.dwFlags |= STARTF_USESHOWWINDOW;
+ startupInfo.wShowWindow = SW_HIDE;
+
+ // For console applications.
+ creationFlags |= CREATE_NO_WINDOW;
+ }
+
+ tstrings::any msg;
+ msg << "CreateProcess(" << appPath << ", " << argsStr << ")";
+
+ if (!CreateProcess(appPath.c_str(), argsBuffer.data(), NULL, NULL, FALSE,
+ creationFlags, NULL, NULL, &startupInfo, &processInfo)) {
+ msg << " failed";
+ JP_THROW(SysError(msg, CreateProcess));
+ }
+
+ msg << " succeeded; PID=" << processInfo.dwProcessId;
+ LOG_TRACE(msg);
+
+ // Close unneeded handles immediately.
+ UniqueHandle(processInfo.hThread);
+
+ // Return process handle.
+ return UniqueHandle(processInfo.hProcess);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/msiwrapper/Executor.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+#ifndef EXECUTOR_H
+#define EXECUTOR_H
+
+#include "tstrings.h"
+#include "UniqueHandle.h"
+
+
+class Executor {
+public:
+ explicit Executor(const std::wstring& appPath=std::wstring()) {
+ app(appPath).visible(false);
+ }
+
+ /**
+ * Returns command line configured with arg() calls so far.
+ */
+ std::wstring args() const;
+
+ /**
+ * Set path to application to execute.
+ */
+ Executor& app(const std::wstring& v) {
+ appPath = v;
+ return *this;
+ }
+
+ /**
+ * Adds another command line argument.
+ */
+ Executor& arg(const std::wstring& v) {
+ argsArray.push_back(v);
+ return *this;
+ }
+
+ /**
+ * Controls if application window should be visible.
+ */
+ Executor& visible(bool v) {
+ theVisible = v;
+ return *this;
+ }
+
+ /**
+ * Starts application process and blocks waiting when the started
+ * process terminates.
+ * Returns process exit code.
+ * Throws exception if process start failed.
+ */
+ int execAndWaitForExit() const;
+
+private:
+ UniqueHandle startProcess() const;
+
+ bool theVisible;
+ tstring_array argsArray;
+ std::wstring appPath;
+};
+
+#endif // #ifndef EXECUTOR_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/msiwrapper/MsiWrapper.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,42 @@
+#include <algorithm>
+#include <windows.h>
+
+#include "SysInfo.h"
+#include "FileUtils.h"
+#include "Executor.h"
+#include "Resources.h"
+#include "WinErrorHandling.h"
+
+
+int __stdcall WinMain(HINSTANCE, HINSTANCE, LPSTR lpCmdLine, int nShowCmd)
+{
+ JP_TRY;
+
+ // Create temporary directory where to extract msi file.
+ const auto tempMsiDir = FileUtils::createTempDirectory();
+
+ // Schedule temporary directory for deletion.
+ FileUtils::Deleter cleaner;
+ cleaner.appendRecursiveDirectory(tempMsiDir);
+
+ const auto msiPath = FileUtils::mkpath() << tempMsiDir << L"main.msi";
+
+ // Extract msi file.
+ Resource(L"msi", RT_RCDATA).saveToFile(msiPath);
+
+ // Setup executor to run msiexec
+ Executor msiExecutor(SysInfo::getWIPath());
+ msiExecutor.arg(L"/i").arg(msiPath);
+ const auto args = SysInfo::getCommandArgs();
+ std::for_each(args.begin(), args.end(),
+ [&msiExecutor] (const tstring& arg) {
+ msiExecutor.arg(arg);
+ });
+
+ // Install msi file.
+ return msiExecutor.execAndWaitForExit();
+
+ JP_CATCH_ALL;
+
+ return -1;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/msiwrapper/Resources.cpp Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,149 @@
+/*
+ * 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.
+ */
+
+#include "Resources.h"
+#include "FileUtils.h"
+#include "WinErrorHandling.h"
+
+#include <fstream>
+
+
+Resource::Resource(LPCTSTR name, LPCTSTR type, HINSTANCE module) {
+ init(name, type, module);
+}
+
+Resource::Resource(UINT id, LPCTSTR type, HINSTANCE module) {
+ init(MAKEINTRESOURCE(id), type, module);
+}
+
+void Resource::init(LPCTSTR name, LPCTSTR type, HINSTANCE module) {
+ if (IS_INTRESOURCE(name)) {
+ std::wostringstream printer;
+ printer << L"#" << reinterpret_cast<size_t>(name);
+ nameStr = printer.str();
+ namePtr = name;
+ } else {
+ nameStr = name;
+ namePtr = nameStr.c_str();
+ }
+ if (IS_INTRESOURCE(type)) {
+ std::wostringstream printer;
+ printer << L"#" << reinterpret_cast<size_t>(name);
+ typeStr = printer.str();
+ typePtr = type;
+ } else {
+ typeStr = type;
+ typePtr = typeStr.c_str();
+ }
+ instance = module;
+}
+
+std::string Resource::getErrMsg(const std::string &descr) const {
+ return (tstrings::any() << descr << " (name='" << nameStr <<
+ "', type='" << typeStr << "')").str();
+}
+
+HRSRC Resource::findResource() const {
+ LPCTSTR id = namePtr;
+ // string resources are stored in blocks (stringtables)
+ // id of the resource is (stringId / 16 + 1)
+ if (typePtr == RT_STRING) {
+ id = MAKEINTRESOURCE(UINT(size_t(id) / 16 + 1));
+ }
+ return FindResource(instance, id, typePtr);
+}
+
+LPVOID Resource::getPtr(DWORD &size) const
+{
+ // LoadString returns the same result if value is zero-length or
+ // if if the value does not exists,
+ // so wee need to ensure the stringtable exists
+ HRSRC resInfo = findResource();
+ if (resInfo == NULL) {
+ JP_THROW(SysError(getErrMsg("cannot find resource"), FindResource));
+ }
+
+ HGLOBAL res = LoadResource(instance, resInfo);
+ if (res == NULL) {
+ JP_THROW(SysError(getErrMsg("cannot load resource"), LoadResource));
+ }
+
+ LPVOID ptr = LockResource(res);
+ if (res == NULL) {
+ JP_THROW(SysError(getErrMsg("cannot lock resource"), LockResource));
+ }
+
+ if (typePtr == RT_STRING) {
+ // string resources are stored in stringtables and
+ // need special handling
+ // The simplest way (while we don't need handle resource locale)
+ // is LoadString
+ // But this adds dependency on user32.dll,
+ // so implement custom string extraction
+
+ // number in the block (namePtr is an integer)
+ size_t num = size_t(namePtr) & 0xf;
+ LPWSTR strPtr = (LPWSTR)ptr;
+ for (size_t i = 0; i < num; i++) {
+ // 1st symbol contains string length
+ strPtr += DWORD(*strPtr) + 1;
+ }
+ // *strPtr contains string length, string value starts at strPtr+1
+ size = DWORD(*strPtr) * sizeof(wchar_t);
+ ptr = strPtr+1;
+ } else {
+ size = SizeofResource(instance, resInfo);
+ }
+
+ return ptr;
+}
+
+bool Resource::available() const {
+ return NULL != findResource();
+}
+
+unsigned Resource::size() const {
+ DWORD size = 0;
+ getPtr(size);
+ return size;
+}
+
+LPCVOID Resource::rawData() const {
+ DWORD size = 0;
+ return getPtr(size);
+}
+
+void Resource::saveToFile(const std::wstring &filePath) const {
+ DWORD size = 0;
+ const char *resPtr = (const char *)getPtr(size);
+
+ FileUtils::FileWriter(filePath).write(resPtr, size).finalize();
+}
+
+Resource::ByteArray Resource::binary() const {
+ DWORD size = 0;
+ LPBYTE resPtr = (LPBYTE)getPtr(size);
+ return ByteArray(resPtr, resPtr+size);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jpackage/windows/native/msiwrapper/Resources.h Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+#ifndef RESOURCES_H
+#define RESOURCES_H
+
+#include "WinSysInfo.h"
+
+
+/**
+ * Classes for resource loading.
+ * Common use cases:
+ * - check if resource is available and save it to file:
+ * Resource res(_T("MyResource"), _T("CustomResourceType"));
+ * if (res.available()) {
+ * res.saveToFile(_T("c:\\temp\\my_resource.bin"));
+ * }
+ */
+
+class Resource {
+public:
+ // name and type can be specified by string id,
+ // by integer id (RT_* constants or MAKEINTRESOURCE)
+ Resource(LPCWSTR name, LPCWSTR type,
+ HINSTANCE module = SysInfo::getCurrentModuleHandle());
+ Resource(UINT id, LPCWSTR type,
+ HINSTANCE module = SysInfo::getCurrentModuleHandle());
+
+ bool available() const;
+
+ // all this methods throw exception if the resource is not available
+ unsigned size() const;
+ // gets raw pointer to the resource data
+ LPCVOID rawData() const;
+
+ // save the resource to a file
+ void saveToFile(const std::wstring &filePath) const;
+
+ typedef std::vector<BYTE> ByteArray;
+ // returns the resource as byte array
+ ByteArray binary() const;
+
+private:
+ std::wstring nameStr;
+ LPCWSTR namePtr; // can be integer value or point to nameStr.c_str()
+ std::wstring typeStr;
+ LPCWSTR typePtr; // can be integer value or point to nameStr.c_str()
+ HINSTANCE instance;
+
+ void init(LPCWSTR name, LPCWSTR type, HINSTANCE module);
+
+ // generates error message
+ std::string getErrMsg(const std::string &descr) const;
+ HRSRC findResource() const;
+ LPVOID getPtr(DWORD &size) const;
+
+private:
+ // disable copying
+ Resource(const Resource&);
+ Resource& operator = (const Resource&);
+};
+
+#endif // RESOURCES_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/apps/com.hello/com/hello/Hello.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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 com.hello;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+
+public class Hello {
+
+ private static final String MSG = "jpackage test application";
+ private static final int EXPECTED_NUM_OF_PARAMS = 3; // Starts at 1
+
+ public static void main(String[] args) {
+ String outputFile = "appOutput.txt";
+ File file = new File(outputFile);
+
+ try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)))) {
+ System.out.println(MSG);
+ out.println(MSG);
+
+ System.out.println("args.length: " + args.length);
+ out.println("args.length: " + args.length);
+
+ for (String arg : args) {
+ System.out.println(arg);
+ out.println(arg);
+ }
+
+ for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) {
+ String value = System.getProperty("param" + index);
+ if (value != null) {
+ System.out.println("-Dparam" + index + "=" + value);
+ out.println("-Dparam" + index + "=" + value);
+ }
+ }
+ } catch (Exception ex) {
+ System.err.println(ex.toString());
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/apps/com.hello/module-info.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+module com.hello {
+ exports com.hello;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/apps/com.other/com/other/Other.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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 com.other;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+
+public class Other {
+
+ private static final String MSG = "other jpackage test application";
+ private static final int EXPECTED_NUM_OF_PARAMS = 3; // Starts at 1
+
+ public static void main(String[] args) {
+ String outputFile = "appOutput.txt";
+ File file = new File(outputFile);
+
+ try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)))) {
+ System.out.println(MSG);
+ out.println(MSG);
+
+ System.out.println("args.length: " + args.length);
+ out.println("args.length: " + args.length);
+
+ for (String arg : args) {
+ System.out.println(arg);
+ out.println(arg);
+ }
+
+ for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) {
+ String value = System.getProperty("param" + index);
+ if (value != null) {
+ System.out.println("-Dparam" + index + "=" + value);
+ out.println("-Dparam" + index + "=" + value);
+ }
+ }
+ } catch (Exception ex) {
+ System.err.println(ex.toString());
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/apps/com.other/module-info.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+module com.other {
+ exports com.other;
+}
Binary file test/jdk/tools/jpackage/apps/dukeplug.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/apps/image/Hello.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.BufferedOutputStream;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.io.IOException;
+
+public class Hello {
+
+ private static final String MSG = "jpackage test application";
+ private static final int EXPECTED_NUM_OF_PARAMS = 3; // Starts at 1
+
+ public static void main(String[] args) throws IOException {
+ printArgs(args, System.out);
+
+ try (PrintStream out = new PrintStream(new BufferedOutputStream(
+ new FileOutputStream("appOutput.txt")))) {
+ printArgs(args, out);
+ }
+ }
+
+ private static void printArgs(String[] args, PrintStream out) {
+ out.println(MSG);
+
+ out.println("args.length: " + args.length);
+
+ for (String arg : args) {
+ out.println(arg);
+ }
+
+ for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) {
+ String value = System.getProperty("param" + index);
+ if (value != null) {
+ out.println("-Dparam" + index + "=" + value);
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/apps/installer/Hello.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.awt.Desktop;
+import java.awt.desktop.OpenFilesEvent;
+import java.awt.desktop.OpenFilesHandler;
+import java.util.List;
+
+public class Hello implements OpenFilesHandler {
+
+ private static final String MSG = "jpackage test application";
+ private static final int EXPECTED_NUM_OF_PARAMS = 3; // Starts at 1
+ private static List<File> files;
+
+ public static void main(String[] args) {
+ if(Desktop.getDesktop().isSupported(Desktop.Action.APP_OPEN_FILE)) {
+ Desktop.getDesktop().setOpenFileHandler(new Hello());
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ printToStdout(args);
+ if (args.length == 1 || (files != null && files.size() == 1)) { // Called via file association
+ printToFile(args);
+ }
+ }
+
+ private static void printToStdout(String[] args) {
+ System.out.println(MSG);
+
+ System.out.println("args.length: " + (files == null ? args.length : args.length + files.size()));
+
+ for (String arg : args) {
+ System.out.println(arg);
+ }
+
+ if (files != null) {
+ for (File file : files) {
+ System.out.println(file.getAbsolutePath());
+ }
+ }
+
+ for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) {
+ String value = System.getProperty("param" + index);
+ if (value != null) {
+ System.out.println("-Dparam" + index + "=" + value);
+ }
+ }
+ }
+
+ private static void printToFile(String[] args) {
+ File inputFile = files == null ? new File(args[0]) : files.get(0);
+ String outputFile = inputFile.getParent() + File.separator + "appOutput.txt";
+ File file = new File(outputFile);
+
+ try (PrintWriter out
+ = new PrintWriter(new BufferedWriter(new FileWriter(file)))) {
+ out.println(MSG);
+
+ out.println("args.length: " + (files == null ? args.length : args.length + files.size()));
+
+ for (String arg : args) {
+ out.println(arg);
+ }
+
+ if (files != null) {
+ for (File f : files) {
+ out.println(f.getAbsolutePath());
+ }
+ }
+
+ for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) {
+ String value = System.getProperty("param" + index);
+ if (value != null) {
+ out.println("-Dparam" + index + "=" + value);
+ }
+ }
+ } catch (Exception ex) {
+ System.err.println(ex.getMessage());
+ }
+ }
+
+ @Override
+ public void openFiles(OpenFilesEvent e) {
+ files = e.getFiles();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/JPackageHelper.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,692 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.BufferedWriter;
+import java.nio.file.FileVisitResult;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Arrays;
+
+import java.util.spi.ToolProvider;
+
+public class JPackageHelper {
+
+ private static final boolean VERBOSE = false;
+ private static final String OS = System.getProperty("os.name").toLowerCase();
+ private static final String JAVA_HOME = System.getProperty("java.home");
+ public static final String TEST_SRC_ROOT;
+ public static final String TEST_SRC;
+ private static final Path BIN_DIR = Path.of(JAVA_HOME, "bin");
+ private static final Path JPACKAGE;
+ private static final Path JAVAC;
+ private static final Path JAR;
+ private static final Path JLINK;
+
+ public static class ModuleArgs {
+ private final String version;
+ private final String mainClass;
+
+ ModuleArgs(String version, String mainClass) {
+ this.version = version;
+ this.mainClass = mainClass;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public String getMainClass() {
+ return mainClass;
+ }
+ }
+
+ static {
+ if (OS.startsWith("win")) {
+ JPACKAGE = BIN_DIR.resolve("jpackage.exe");
+ JAVAC = BIN_DIR.resolve("javac.exe");
+ JAR = BIN_DIR.resolve("jar.exe");
+ JLINK = BIN_DIR.resolve("jlink.exe");
+ } else {
+ JPACKAGE = BIN_DIR.resolve("jpackage");
+ JAVAC = BIN_DIR.resolve("javac");
+ JAR = BIN_DIR.resolve("jar");
+ JLINK = BIN_DIR.resolve("jlink");
+ }
+
+ // Figure out test src based on where we called
+ TEST_SRC = System.getProperty("test.src");
+ Path root = Path.of(TEST_SRC);
+ Path apps = Path.of(TEST_SRC, "apps");
+ if (apps.toFile().exists()) {
+ // fine - test is at root
+ } else {
+ apps = Path.of(TEST_SRC, "..", "apps");
+ if (apps.toFile().exists()) {
+ root = apps.getParent().normalize(); // test is 1 level down
+ } else {
+ apps = Path.of(TEST_SRC, "..", "..", "apps");
+ if (apps.toFile().exists()) {
+ root = apps.getParent().normalize(); // 2 levels down
+ } else {
+ apps = Path.of(TEST_SRC, "..", "..", "..", "apps");
+ if (apps.toFile().exists()) {
+ root = apps.getParent().normalize(); // 3 levels down
+ } else {
+ // if we ever have tests more than three levels
+ // down we need to add code here
+ throw new RuntimeException("we should never get here");
+ }
+ }
+ }
+ }
+ TEST_SRC_ROOT = root.toString();
+ }
+
+ static final ToolProvider JPACKAGE_TOOL =
+ ToolProvider.findFirst("jpackage").orElseThrow(
+ () -> new RuntimeException("jpackage tool not found"));
+
+ public static int execute(File out, String... command) throws Exception {
+ if (VERBOSE) {
+ System.out.print("Execute command: ");
+ for (String c : command) {
+ System.out.print(c);
+ System.out.print(" ");
+ }
+ System.out.println();
+ }
+
+ ProcessBuilder builder = new ProcessBuilder(command);
+ if (out != null) {
+ builder.redirectErrorStream(true);
+ builder.redirectOutput(out);
+ }
+
+ Process process = builder.start();
+ return process.waitFor();
+ }
+
+ public static Process executeNoWait(File out, String... command) throws Exception {
+ if (VERBOSE) {
+ System.out.print("Execute command: ");
+ for (String c : command) {
+ System.out.print(c);
+ System.out.print(" ");
+ }
+ System.out.println();
+ }
+
+ ProcessBuilder builder = new ProcessBuilder(command);
+ if (out != null) {
+ builder.redirectErrorStream(true);
+ builder.redirectOutput(out);
+ }
+
+ return builder.start();
+ }
+
+ private static String[] getCommand(String... args) {
+ String[] command;
+ if (args == null) {
+ command = new String[1];
+ } else {
+ command = new String[args.length + 1];
+ }
+
+ int index = 0;
+ command[index] = JPACKAGE.toString();
+
+ if (args != null) {
+ for (String arg : args) {
+ index++;
+ command[index] = arg;
+ }
+ }
+
+ return command;
+ }
+
+ public static void deleteRecursive(File path) throws IOException {
+ if (!path.exists()) {
+ return;
+ }
+
+ Path directory = path.toPath();
+ Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file,
+ BasicFileAttributes attr) throws IOException {
+ file.toFile().setWritable(true);
+ if (OS.startsWith("win")) {
+ try {
+ Files.setAttribute(file, "dos:readonly", false);
+ } catch (Exception ioe) {
+ // just report and try to contune
+ System.err.println("IOException: " + ioe);
+ ioe.printStackTrace(System.err);
+ }
+ }
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult preVisitDirectory(Path dir,
+ BasicFileAttributes attr) throws IOException {
+ if (OS.startsWith("win")) {
+ Files.setAttribute(dir, "dos:readonly", false);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path dir, IOException e)
+ throws IOException {
+ Files.delete(dir);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ public static void deleteOutputFolder(String output) throws IOException {
+ File outputFolder = new File(output);
+ System.out.println("deleteOutputFolder: " + outputFolder.getAbsolutePath());
+ try {
+ deleteRecursive(outputFolder);
+ } catch (IOException ioe) {
+ System.err.println("IOException: " + ioe);
+ ioe.printStackTrace(System.err);
+ deleteRecursive(outputFolder);
+ }
+ }
+
+ public static String executeCLI(boolean retValZero, String... args) throws Exception {
+ int retVal;
+ File outfile = new File("output.log");
+ String[] command = getCommand(args);
+ try {
+ retVal = execute(outfile, command);
+ } catch (Exception ex) {
+ if (outfile.exists()) {
+ System.err.println(Files.readString(outfile.toPath()));
+ }
+ throw ex;
+ }
+
+ String output = Files.readString(outfile.toPath());
+ if (retValZero) {
+ if (retVal != 0) {
+ System.err.println("command run:");
+ for (String s : command) { System.err.println(s); }
+ System.err.println("command output:");
+ System.err.println(output);
+ throw new AssertionError("jpackage exited with error: " + retVal);
+ }
+ } else {
+ if (retVal == 0) {
+ System.err.println(output);
+ throw new AssertionError("jpackage exited without error: " + retVal);
+ }
+ }
+
+ if (VERBOSE) {
+ System.out.println("output =");
+ System.out.println(output);
+ }
+
+ return output;
+ }
+
+ public static String executeToolProvider(boolean retValZero, String... args) throws Exception {
+ StringWriter writer = new StringWriter();
+ PrintWriter pw = new PrintWriter(writer);
+ int retVal = JPACKAGE_TOOL.run(pw, pw, args);
+ String output = writer.toString();
+
+ if (retValZero) {
+ if (retVal != 0) {
+ System.err.println(output);
+ throw new AssertionError("jpackage exited with error: " + retVal);
+ }
+ } else {
+ if (retVal == 0) {
+ System.err.println(output);
+ throw new AssertionError("jpackage exited without error");
+ }
+ }
+
+ if (VERBOSE) {
+ System.out.println("output =");
+ System.out.println(output);
+ }
+
+ return output;
+ }
+
+ public static boolean isWindows() {
+ return (OS.contains("win"));
+ }
+
+ public static boolean isOSX() {
+ return (OS.contains("mac"));
+ }
+
+ public static boolean isLinux() {
+ return ((OS.contains("nix") || OS.contains("nux")));
+ }
+
+ public static void createHelloImageJar(String inputDir) throws Exception {
+ createJar(false, "Hello", "image", inputDir);
+ }
+
+ public static void createHelloImageJar() throws Exception {
+ createJar(false, "Hello", "image", "input");
+ }
+
+ public static void createHelloImageJarWithMainClass() throws Exception {
+ createJar(true, "Hello", "image", "input");
+ }
+
+ public static void createHelloInstallerJar() throws Exception {
+ createJar(false, "Hello", "installer", "input");
+ }
+
+ public static void createHelloInstallerJarWithMainClass() throws Exception {
+ createJar(true, "Hello", "installer", "input");
+ }
+
+ private static void createJar(boolean mainClassAttribute, String name,
+ String testType, String inputDir) throws Exception {
+ int retVal;
+
+ File input = new File(inputDir);
+ if (!input.exists()) {
+ input.mkdirs();
+ }
+
+ Path src = Path.of(TEST_SRC_ROOT + File.separator + "apps"
+ + File.separator + testType + File.separator + name + ".java");
+ Path dst = Path.of(name + ".java");
+
+ if (dst.toFile().exists()) {
+ Files.delete(dst);
+ }
+ Files.copy(src, dst);
+
+
+ File javacLog = new File("javac.log");
+ try {
+ retVal = execute(javacLog, JAVAC.toString(), name + ".java");
+ } catch (Exception ex) {
+ if (javacLog.exists()) {
+ System.err.println(Files.readString(javacLog.toPath()));
+ }
+ throw ex;
+ }
+
+ if (retVal != 0) {
+ if (javacLog.exists()) {
+ System.err.println(Files.readString(javacLog.toPath()));
+ }
+ throw new AssertionError("javac exited with error: " + retVal);
+ }
+
+ File jarLog = new File("jar.log");
+ try {
+ List<String> args = new ArrayList<>();
+ args.add(JAR.toString());
+ args.add("-c");
+ args.add("-v");
+ args.add("-f");
+ args.add(inputDir + File.separator + name.toLowerCase() + ".jar");
+ if (mainClassAttribute) {
+ args.add("-e");
+ args.add(name);
+ }
+ args.add(name + ".class");
+ retVal = execute(jarLog, args.stream().toArray(String[]::new));
+ } catch (Exception ex) {
+ if (jarLog.exists()) {
+ System.err.println(Files.readString(jarLog.toPath()));
+ }
+ throw ex;
+ }
+
+ if (retVal != 0) {
+ if (jarLog.exists()) {
+ System.err.println(Files.readString(jarLog.toPath()));
+ }
+ throw new AssertionError("jar exited with error: " + retVal);
+ }
+ }
+
+ public static void createHelloModule() throws Exception {
+ createModule("Hello.java", "input", "hello", null, true);
+ }
+
+ public static void createHelloModule(ModuleArgs moduleArgs) throws Exception {
+ createModule("Hello.java", "input", "hello", moduleArgs, true);
+ }
+
+ public static void createOtherModule() throws Exception {
+ createModule("Other.java", "input-other", "other", null, false);
+ }
+
+ private static void createModule(String javaFile, String inputDir, String aName,
+ ModuleArgs moduleArgs, boolean createModularJar) throws Exception {
+ int retVal;
+
+ File input = new File(inputDir);
+ if (!input.exists()) {
+ input.mkdir();
+ }
+
+ File module = new File("module" + File.separator + "com." + aName);
+ if (!module.exists()) {
+ module.mkdirs();
+ }
+
+ File javacLog = new File("javac.log");
+ try {
+ List<String> args = new ArrayList<>();
+ args.add(JAVAC.toString());
+ args.add("-d");
+ args.add("module" + File.separator + "com." + aName);
+ args.add(TEST_SRC_ROOT + File.separator + "apps" + File.separator
+ + "com." + aName + File.separator + "module-info.java");
+ args.add(TEST_SRC_ROOT + File.separator + "apps"
+ + File.separator + "com." + aName + File.separator + "com"
+ + File.separator + aName + File.separator + javaFile);
+ retVal = execute(javacLog, args.stream().toArray(String[]::new));
+ } catch (Exception ex) {
+ if (javacLog.exists()) {
+ System.err.println(Files.readString(javacLog.toPath()));
+ }
+ throw ex;
+ }
+
+ if (retVal != 0) {
+ if (javacLog.exists()) {
+ System.err.println(Files.readString(javacLog.toPath()));
+ }
+ throw new AssertionError("javac exited with error: " + retVal);
+ }
+
+ if (createModularJar) {
+ File jarLog = new File("jar.log");
+ try {
+ List<String> args = new ArrayList<>();
+ args.add(JAR.toString());
+ args.add("--create");
+ args.add("--file");
+ args.add(inputDir + File.separator + "com." + aName + ".jar");
+ if (moduleArgs != null) {
+ if (moduleArgs.getVersion() != null) {
+ args.add("--module-version");
+ args.add(moduleArgs.getVersion());
+ }
+
+ if (moduleArgs.getMainClass()!= null) {
+ args.add("--main-class");
+ args.add(moduleArgs.getMainClass());
+ }
+ }
+ args.add("-C");
+ args.add("module" + File.separator + "com." + aName);
+ args.add(".");
+
+ retVal = execute(jarLog, args.stream().toArray(String[]::new));
+ } catch (Exception ex) {
+ if (jarLog.exists()) {
+ System.err.println(Files.readString(jarLog.toPath()));
+ }
+ throw ex;
+ }
+
+ if (retVal != 0) {
+ if (jarLog.exists()) {
+ System.err.println(Files.readString(jarLog.toPath()));
+ }
+ throw new AssertionError("jar exited with error: " + retVal);
+ }
+ }
+ }
+
+ public static void createRuntime() throws Exception {
+ List<String> moreArgs = new ArrayList<>();
+ createRuntime(moreArgs);
+ }
+
+ public static void createRuntime(List<String> moreArgs) throws Exception {
+ int retVal;
+
+ File jlinkLog = new File("jlink.log");
+ try {
+ List<String> args = new ArrayList<>();
+ args.add(JLINK.toString());
+ args.add("--output");
+ args.add("runtime");
+ args.add("--add-modules");
+ args.add("java.base");
+ args.addAll(moreArgs);
+
+ retVal = execute(jlinkLog, args.stream().toArray(String[]::new));
+ } catch (Exception ex) {
+ if (jlinkLog.exists()) {
+ System.err.println(Files.readString(jlinkLog.toPath()));
+ }
+ throw ex;
+ }
+
+ if (retVal != 0) {
+ if (jlinkLog.exists()) {
+ System.err.println(Files.readString(jlinkLog.toPath()));
+ }
+ throw new AssertionError("jlink exited with error: " + retVal);
+ }
+ }
+
+ public static String listToArgumentsMap(List<String> arguments, boolean toolProvider) {
+ if (arguments.isEmpty()) {
+ return "";
+ }
+
+ String argsStr = "";
+ for (int i = 0; i < arguments.size(); i++) {
+ String arg = arguments.get(i);
+ argsStr += quote(arg, toolProvider);
+ if ((i + 1) != arguments.size()) {
+ argsStr += " ";
+ }
+ }
+
+ if (!toolProvider && isWindows()) {
+ if (argsStr.contains(" ")) {
+ if (argsStr.contains("\"")) {
+ argsStr = escapeQuote(argsStr, toolProvider);
+ }
+ argsStr = "\"" + argsStr + "\"";
+ }
+ }
+ return argsStr;
+ }
+
+ public static String[] cmdWithAtFilename(String [] cmd, int ndx, int len)
+ throws IOException {
+ ArrayList<String> newAList = new ArrayList<>();
+ String fileString = null;
+ for (int i=0; i<cmd.length; i++) {
+ if (i == ndx) {
+ newAList.add("@argfile.cmds");
+ fileString = cmd[i];
+ } else if (i > ndx && i < ndx + len) {
+ fileString += " " + cmd[i];
+ } else {
+ newAList.add(cmd[i]);
+ }
+ }
+ if (fileString != null) {
+ Path path = new File("argfile.cmds").toPath();
+ try (BufferedWriter bw = Files.newBufferedWriter(path);
+ PrintWriter out = new PrintWriter(bw)) {
+ out.println(fileString);
+ }
+ }
+ return newAList.toArray(new String[0]);
+ }
+
+ public static String [] splitAndFilter(String output) {
+ if (output == null) {
+ return null;
+ }
+
+ String[] result = output.split("\n");
+ if (result == null || result.length == 0) {
+ return result;
+ }
+
+ List<String> origList = new ArrayList(Arrays.asList(result));
+ List<String> newlist = new ArrayList();
+ origList.stream().filter((str) ->
+ (!str.startsWith("Picked up") &&
+ !str.startsWith("WARNING: Using experimental")))
+ .forEachOrdered((str) -> {
+ newlist.add(str);
+ });
+
+ return newlist.toArray(new String[newlist.size()]);
+ }
+
+ private static String quote(String in, boolean toolProvider) {
+ if (in == null) {
+ return null;
+ }
+
+ if (in.isEmpty()) {
+ return "";
+ }
+
+ if (!in.contains("=")) {
+ // Not a property
+ if (in.contains(" ")) {
+ in = escapeQuote(in, toolProvider);
+ return "\"" + in + "\"";
+ }
+ return in;
+ }
+
+ if (!in.contains(" ")) {
+ return in; // No need to quote
+ }
+
+ int paramIndex = in.indexOf("=");
+ if (paramIndex <= 0) {
+ return in; // Something wrong, just skip quoting
+ }
+
+ String param = in.substring(0, paramIndex);
+ String value = in.substring(paramIndex + 1);
+
+ if (value.length() == 0) {
+ return in; // No need to quote
+ }
+
+ value = escapeQuote(value, toolProvider);
+
+ return param + "=" + "\"" + value + "\"";
+ }
+
+ private static String escapeQuote(String in, boolean toolProvider) {
+ if (in == null) {
+ return null;
+ }
+
+ if (in.isEmpty()) {
+ return "";
+ }
+
+ if (in.contains("\"")) {
+ // Use code points to preserve non-ASCII chars
+ StringBuilder sb = new StringBuilder();
+ int codeLen = in.codePointCount(0, in.length());
+ for (int i = 0; i < codeLen; i++) {
+ int code = in.codePointAt(i);
+ // Note: No need to escape '\' on Linux or OS X
+ // jpackage expects us to pass arguments and properties with
+ // quotes and spaces as a map
+ // with quotes being escaped with additional \ for
+ // internal quotes.
+ // So if we want two properties below:
+ // -Djnlp.Prop1=Some "Value" 1
+ // -Djnlp.Prop2=Some Value 2
+ // jpackage will need:
+ // "-Djnlp.Prop1=\"Some \\"Value\\" 1\" -Djnlp.Prop2=\"Some Value 2\""
+ // but since we using ProcessBuilder to run jpackage we will need to escape
+ // our escape symbols as well, so we will need to pass string below to ProcessBuilder:
+ // "-Djnlp.Prop1=\\\"Some \\\\\\\"Value\\\\\\\" 1\\\" -Djnlp.Prop2=\\\"Some Value 2\\\""
+ switch (code) {
+ case '"':
+ // " -> \" -> \\\"
+ if (i == 0 || in.codePointAt(i - 1) != '\\') {
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint(code);
+ }
+ break;
+ case '\\':
+ // We need to escape already escaped symbols as well
+ if ((i + 1) < codeLen) {
+ int nextCode = in.codePointAt(i + 1);
+ if (nextCode == '"') {
+ // \" -> \\\"
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint(nextCode);
+ } else {
+ sb.appendCodePoint('\\');
+ sb.appendCodePoint(code);
+ }
+ } else {
+ sb.appendCodePoint(code);
+ }
+ break;
+ default:
+ sb.appendCodePoint(code);
+ break;
+ }
+ }
+ return sb.toString();
+ }
+
+ return in;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/JPackageInstallerHelper.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+
+public class JPackageInstallerHelper {
+ private static final String JPACKAGE_TEST_OUTPUT = "jpackage.test.output";
+ private static final String JPACKAGE_VERIFY_INSTALL = "jpackage.verify.install";
+ private static final String JPACKAGE_VERIFY_UNINSTALL = "jpackage.verify.uninstall";
+ private static String testOutput;
+ private static final boolean isTestOutputSet;
+ private static final boolean isVerifyInstall;
+ private static final boolean isVerifyUnInstall;
+
+ static {
+ String out = System.getProperty(JPACKAGE_TEST_OUTPUT);
+ isTestOutputSet = (out != null);
+ if (isTestOutputSet) {
+ File file = new File(out);
+ if (!file.exists()) {
+ throw new AssertionError(file.getAbsolutePath() + " does not exist");
+ }
+
+ if (!file.isDirectory()) {
+ throw new AssertionError(file.getAbsolutePath() + " is not a directory");
+ }
+
+ if (!file.canWrite()) {
+ throw new AssertionError(file.getAbsolutePath() + " is not writable");
+ }
+
+ if (out.endsWith(File.separator)) {
+ out = out.substring(0, out.length() - 2);
+ }
+
+ testOutput = out;
+ }
+
+ isVerifyInstall = (System.getProperty(JPACKAGE_VERIFY_INSTALL) != null);
+ isVerifyUnInstall = (System.getProperty(JPACKAGE_VERIFY_UNINSTALL) != null);
+ }
+
+ public static boolean isTestOutputSet() {
+ return isTestOutputSet;
+ }
+
+ public static boolean isVerifyInstall() {
+ return isVerifyInstall;
+ }
+
+ public static boolean isVerifyUnInstall() {
+ return isVerifyUnInstall;
+ }
+
+ public static void copyTestResults(List<String> files) throws Exception {
+ if (!isTestOutputSet()) {
+ return;
+ }
+
+ File dest = new File(testOutput);
+ if (!dest.exists()) {
+ dest.mkdirs();
+ }
+
+ if (JPackageHelper.isWindows()) {
+ files.add(JPackagePath.getTestSrc() + File.separator + "install.bat");
+ files.add(JPackagePath.getTestSrc() + File.separator + "uninstall.bat");
+ } else {
+ files.add(JPackagePath.getTestSrc() + File.separator + "install.sh");
+ files.add(JPackagePath.getTestSrc() + File.separator + "uninstall.sh");
+ }
+
+ for (String file : files) {
+ Path source = Path.of(file);
+ Path target = Path.of(dest.toPath() + File.separator + source.getFileName());
+ Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+
+ public static void validateApp(String app) throws Exception {
+ File outFile = new File("appOutput.txt");
+ if (outFile.exists()) {
+ outFile.delete();
+ }
+
+ int retVal = JPackageHelper.execute(outFile, app);
+ if (retVal != 0) {
+ throw new AssertionError(
+ "Test application exited with error: " + retVal);
+ }
+
+ if (!outFile.exists()) {
+ throw new AssertionError(outFile.getAbsolutePath() + " was not created");
+ }
+
+ String output = Files.readString(outFile.toPath());
+ String[] result = output.split("\n");
+ if (result.length != 2) {
+ System.err.println(output);
+ throw new AssertionError(
+ "Unexpected number of lines: " + result.length);
+ }
+
+ if (!result[0].trim().equals("jpackage test application")) {
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ }
+
+ if (!result[1].trim().equals("args.length: 0")) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+ }
+
+ public static void validateOutput(String output) throws Exception {
+ File file = new File(output);
+ if (!file.exists()) {
+ // Try lower case in case of OS is case sensitive
+ file = new File(output.toLowerCase());
+ if (!file.exists()) {
+ throw new AssertionError("Cannot find " + file.getAbsolutePath());
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/JPackagePath.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.File;
+import java.nio.file.Path;
+
+/**
+ * Helper class which contains functions to get different system
+ * dependent paths used by tests
+ */
+public class JPackagePath {
+
+ // Return path to test src adjusted to location of caller
+ public static String getTestSrcRoot() {
+ return JPackageHelper.TEST_SRC_ROOT;
+ }
+
+ // Return path to calling test
+ public static String getTestSrc() {
+ return JPackageHelper.TEST_SRC;
+ }
+
+ // Returns path to generate test application
+ public static String getApp() {
+ return getApp("test");
+ }
+
+ public static String getApp(String name) {
+ return getAppSL(name, name);
+ }
+
+ // Returns path to generate test application icon
+ public static String getAppIcon() {
+ return getAppIcon("test");
+ }
+
+ public static String getAppIcon(String name) {
+ if (JPackageHelper.isWindows()) {
+ return Path.of("output", name, name + ".ico").toString();
+ } else if (JPackageHelper.isOSX()) {
+ return Path.of("output", name + ".app",
+ "Contents", "Resources", name + ".icns").toString();
+ } else if (JPackageHelper.isLinux()) {
+ return Path.of("output", name, "lib", name + ".png").toString();
+ } else {
+ throw new AssertionError("Cannot detect platform");
+ }
+ }
+
+ // Returns path to generate secondary launcher of given application
+ public static String getAppSL(String sl) {
+ return getAppSL("test", sl);
+ }
+
+ public static String getAppSL(String app, String sl) {
+ if (JPackageHelper.isWindows()) {
+ return Path.of("output", app, sl + ".exe").toString();
+ } else if (JPackageHelper.isOSX()) {
+ return Path.of("output", app + ".app",
+ "Contents", "MacOS", sl).toString();
+ } else if (JPackageHelper.isLinux()) {
+ return Path.of("output", app, "bin", sl).toString();
+ } else {
+ throw new AssertionError("Cannot detect platform");
+ }
+ }
+
+ // Returns path to test application cfg file
+ public static String getAppCfg() {
+ return getAppCfg("test");
+ }
+
+ public static String getAppCfg(String name) {
+ if (JPackageHelper.isWindows()) {
+ return Path.of("output", name, "app", name + ".cfg").toString();
+ } else if (JPackageHelper.isOSX()) {
+ return Path.of("output", name + ".app",
+ "Contents", "Java", name + ".cfg").toString();
+ } else if (JPackageHelper.isLinux()) {
+ return Path.of("output", name, "lib", "app", name + ".cfg").toString();
+ } else {
+ throw new AssertionError("Cannot detect platform");
+ }
+ }
+
+ // Returns path including executable to java in image runtime folder
+ public static String getRuntimeJava() {
+ return getRuntimeJava("test");
+ }
+
+ public static String getRuntimeJava(String name) {
+ if (JPackageHelper.isWindows()) {
+ return Path.of(getRuntimeBin(name), "java.exe").toString();
+ }
+ return Path.of(getRuntimeBin(name), "java").toString();
+ }
+
+ // Returns output file name generate by test application
+ public static String getAppOutputFile() {
+ return "appOutput.txt";
+ }
+
+ // Returns path to bin folder in image runtime
+ public static String getRuntimeBin() {
+ return getRuntimeBin("test");
+ }
+
+ public static String getRuntimeBin(String name) {
+ if (JPackageHelper.isWindows()) {
+ return Path.of("output", name, "runtime", "bin").toString();
+ } else if (JPackageHelper.isOSX()) {
+ return Path.of("output", name + ".app",
+ "Contents", "runtime",
+ "Contents", "Home", "bin").toString();
+ } else if (JPackageHelper.isLinux()) {
+ return Path.of("output", name, "lib", "runtime", "bin").toString();
+ } else {
+ throw new AssertionError("Cannot detect platform");
+ }
+ }
+
+ public static String getOSXInstalledApp(String testName) {
+ return File.separator + "Applications"
+ + File.separator + testName + ".app"
+ + File.separator + "Contents"
+ + File.separator + "MacOS"
+ + File.separator + testName;
+ }
+
+ public static String getOSXInstalledApp(String subDir, String testName) {
+ return File.separator + "Applications"
+ + File.separator + subDir
+ + File.separator + testName + ".app"
+ + File.separator + "Contents"
+ + File.separator + "MacOS"
+ + File.separator + testName;
+ }
+
+ // Returs path to test license file
+ public static String getLicenseFilePath() {
+ String path = JPackagePath.getTestSrcRoot()
+ + File.separator + "resources"
+ + File.separator + "license.txt";
+
+ return path;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Annotations.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+public class Annotations {
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface Test {
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ @Repeatable(Parameters.class)
+ public @interface Parameter {
+
+ String value();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface Parameters {
+
+ Parameter[] value();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CommandArguments.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class CommandArguments<T> {
+
+ CommandArguments() {
+ args = new ArrayList<>();
+ }
+
+ final public T addArgument(String v) {
+ args.add(v);
+ return (T) this;
+ }
+
+ final public T addArguments(List<String> v) {
+ args.addAll(v);
+ return (T) this;
+ }
+
+ final public T addArgument(Path v) {
+ return addArgument(v.toString());
+ }
+
+ final public T addArguments(String... v) {
+ return addArguments(Arrays.asList(v));
+ }
+
+ final public T addPathArguments(List<Path> v) {
+ return addArguments(v.stream().map((p) -> p.toString()).collect(
+ Collectors.toList()));
+ }
+
+ final public List<String> getAllArguments() {
+ return List.copyOf(args);
+ }
+
+ protected void verifyMutable() {
+ if (!isMutable()) {
+ throw new UnsupportedOperationException(
+ "Attempt to modify immutable object");
+ }
+ }
+
+ protected boolean isMutable() {
+ return true;
+ }
+
+ protected List<String> args;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,362 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.StringReader;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.spi.ToolProvider;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
+
+public final class Executor extends CommandArguments<Executor> {
+
+ public Executor() {
+ saveOutputType = new HashSet<>(Set.of(SaveOutputType.NONE));
+ }
+
+ public Executor setExecutable(String v) {
+ return setExecutable(Path.of(v));
+ }
+
+ public Executor setExecutable(Path v) {
+ executable = v;
+ if (executable != null) {
+ toolProvider = null;
+ }
+ return this;
+ }
+
+ public Executor setToolProvider(ToolProvider v) {
+ toolProvider = v;
+ filterOutJcovOutput = true;
+ if (toolProvider != null) {
+ executable = null;
+ }
+ return this;
+ }
+
+ public Executor setDirectory(Path v) {
+ directory = v;
+ return this;
+ }
+
+ public Executor setExecutable(JavaTool v) {
+ filterOutJcovOutput = true;
+ return setExecutable(v.getPath());
+ }
+
+ /**
+ * Configures this instance to save full output that command will produce.
+ * This function is mutual exclusive with
+ * saveFirstLineOfOutput() function.
+ *
+ * @return this
+ */
+ public Executor saveOutput() {
+ saveOutputType.remove(SaveOutputType.FIRST_LINE);
+ saveOutputType.add(SaveOutputType.FULL);
+ return this;
+ }
+
+ /**
+ * Configures how to save output that command will produce. If
+ * <code>v</code> is <code>true</code>, the function call is equivalent to
+ * <code>saveOutput()</code> call. If <code>v</code> is <code>false</code>,
+ * the function will result in not preserving command output.
+ *
+ * @return this
+ */
+ public Executor saveOutput(boolean v) {
+ if (v) {
+ saveOutput();
+ } else {
+ saveOutputType.remove(SaveOutputType.FIRST_LINE);
+ saveOutputType.remove(SaveOutputType.FULL);
+ }
+ return this;
+ }
+
+ /**
+ * Configures this instance to save only the first line out output that
+ * command will produce. This function is mutual exclusive with
+ * saveOutput() function.
+ *
+ * @return this
+ */
+ public Executor saveFirstLineOfOutput() {
+ saveOutputType.add(SaveOutputType.FIRST_LINE);
+ saveOutputType.remove(SaveOutputType.FULL);
+ return this;
+ }
+
+ /**
+ * Configures this instance to dump all output that command will produce to
+ * System.out and System.err. Can be used together with saveOutput() and
+ * saveFirstLineOfOutput() to save command output and also copy it in the
+ * default output streams.
+ *
+ * @return this
+ */
+ public Executor dumpOutput() {
+ return dumpOutput(true);
+ }
+
+ public Executor dumpOutput(boolean v) {
+ if (v) {
+ saveOutputType.add(SaveOutputType.DUMP);
+ } else {
+ saveOutputType.remove(SaveOutputType.DUMP);
+ }
+ return this;
+ }
+
+ public class Result {
+
+ Result(int exitCode) {
+ this.exitCode = exitCode;
+ }
+
+ public String getFirstLineOfOutput() {
+ return output.get(0);
+ }
+
+ public List<String> getOutput() {
+ return output;
+ }
+
+ public String getPrintableCommandLine() {
+ return Executor.this.getPrintableCommandLine();
+ }
+
+ public Result assertExitCodeIs(int expectedExitCode) {
+ TKit.assertEquals(expectedExitCode, exitCode, String.format(
+ "Check command %s exited with %d code",
+ getPrintableCommandLine(), expectedExitCode));
+ return this;
+ }
+
+ public Result assertExitCodeIsZero() {
+ return assertExitCodeIs(0);
+ }
+
+ final int exitCode;
+ private List<String> output;
+ }
+
+ public Result execute() {
+ return ThrowingSupplier.toSupplier(() -> {
+ if (toolProvider != null) {
+ return runToolProvider();
+ }
+
+ if (executable != null) {
+ return runExecutable();
+ }
+
+ throw new IllegalStateException("No command to execute");
+ }).get();
+ }
+
+ public String executeAndGetFirstLineOfOutput() {
+ return saveFirstLineOfOutput().execute().assertExitCodeIsZero().getFirstLineOfOutput();
+ }
+
+ public List<String> executeAndGetOutput() {
+ return saveOutput().execute().assertExitCodeIsZero().getOutput();
+ }
+
+ private boolean withSavedOutput() {
+ return saveOutputType.contains(SaveOutputType.FULL) || saveOutputType.contains(
+ SaveOutputType.FIRST_LINE);
+ }
+
+ private Result runExecutable() throws IOException, InterruptedException {
+ List<String> command = new ArrayList<>();
+ command.add(executable.toString());
+ command.addAll(args);
+ ProcessBuilder builder = new ProcessBuilder(command);
+ StringBuilder sb = new StringBuilder(getPrintableCommandLine());
+ if (withSavedOutput()) {
+ builder.redirectErrorStream(true);
+ sb.append("; save output");
+ } else if (saveOutputType.contains(SaveOutputType.DUMP)) {
+ builder.inheritIO();
+ sb.append("; inherit I/O");
+ } else {
+ builder.redirectError(ProcessBuilder.Redirect.DISCARD);
+ builder.redirectOutput(ProcessBuilder.Redirect.DISCARD);
+ sb.append("; discard I/O");
+ }
+ if (directory != null) {
+ builder.directory(directory.toFile());
+ sb.append(String.format("; in directory [%s]", directory));
+ }
+
+ TKit.trace("Execute " + sb.toString() + "...");
+ Process process = builder.start();
+
+ List<String> outputLines = null;
+ if (withSavedOutput()) {
+ try (BufferedReader outReader = new BufferedReader(
+ new InputStreamReader(process.getInputStream()))) {
+ if (saveOutputType.contains(SaveOutputType.DUMP) || saveOutputType.contains(
+ SaveOutputType.FULL)) {
+ outputLines = outReader.lines().collect(Collectors.toList());
+ } else {
+ outputLines = Arrays.asList(
+ outReader.lines().findFirst().orElse(null));
+ }
+ } finally {
+ if (saveOutputType.contains(SaveOutputType.DUMP) && outputLines != null) {
+ outputLines.stream().forEach(System.out::println);
+ if (saveOutputType.contains(SaveOutputType.FIRST_LINE)) {
+ // Pick the first line of saved output if there is one
+ for (String line: outputLines) {
+ outputLines = List.of(line);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ Result reply = new Result(process.waitFor());
+ TKit.trace("Done. Exit code: " + reply.exitCode);
+
+ if (outputLines != null) {
+ reply.output = Collections.unmodifiableList(outputLines);
+ }
+ return reply;
+ }
+
+ private Result runToolProvider(PrintStream out, PrintStream err) {
+ TKit.trace("Execute " + getPrintableCommandLine() + "...");
+ Result reply = new Result(toolProvider.run(out, err, args.toArray(
+ String[]::new)));
+ TKit.trace("Done. Exit code: " + reply.exitCode);
+ return reply;
+ }
+
+
+ private Result runToolProvider() throws IOException {
+ if (!withSavedOutput()) {
+ if (saveOutputType.contains(SaveOutputType.DUMP)) {
+ return runToolProvider(System.out, System.err);
+ }
+
+ PrintStream nullPrintStream = new PrintStream(new OutputStream() {
+ @Override
+ public void write(int b) {
+ // Nop
+ }
+ });
+ return runToolProvider(nullPrintStream, nullPrintStream);
+ }
+
+ try (ByteArrayOutputStream buf = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(buf)) {
+ Result reply = runToolProvider(ps, ps);
+ ps.flush();
+ try (BufferedReader bufReader = new BufferedReader(new StringReader(
+ buf.toString()))) {
+ if (saveOutputType.contains(SaveOutputType.FIRST_LINE)) {
+ String firstLine = filterJcovOutput(bufReader.lines()).findFirst().orElse(
+ null);
+ if (firstLine != null) {
+ reply.output = List.of(firstLine);
+ }
+ } else if (saveOutputType.contains(SaveOutputType.FULL)) {
+ reply.output = filterJcovOutput(bufReader.lines()).collect(
+ Collectors.toUnmodifiableList());
+ }
+
+ if (saveOutputType.contains(SaveOutputType.DUMP)) {
+ Stream<String> lines;
+ if (saveOutputType.contains(SaveOutputType.FULL)) {
+ lines = reply.output.stream();
+ } else {
+ lines = bufReader.lines();
+ }
+ lines.forEach(System.out::println);
+ }
+ }
+ return reply;
+ }
+ }
+
+ public String getPrintableCommandLine() {
+ final String exec;
+ String format = "[%s](%d)";
+ if (toolProvider == null && executable == null) {
+ exec = "<null>";
+ } else if (toolProvider != null) {
+ format = "tool provider " + format;
+ exec = toolProvider.name();
+ } else {
+ exec = executable.toString();
+ }
+
+ return String.format(format, printCommandLine(exec, args),
+ args.size() + 1);
+ }
+
+ private static String printCommandLine(String executable, List<String> args) {
+ // Want command line printed in a way it can be easily copy/pasted
+ // to be executed manally
+ Pattern regex = Pattern.compile("\\s");
+ return Stream.concat(Stream.of(executable), args.stream()).map(
+ v -> (v.isEmpty() || regex.matcher(v).find()) ? "\"" + v + "\"" : v).collect(
+ Collectors.joining(" "));
+ }
+
+ private Stream<String> filterJcovOutput(Stream<String> lines) {
+ if (filterOutJcovOutput) {
+ return lines.filter(line -> !line.startsWith("Picked up"));
+ }
+ return lines;
+ }
+
+ private ToolProvider toolProvider;
+ private Path executable;
+ private Set<SaveOutputType> saveOutputType;
+ private Path directory;
+ private boolean filterOutJcovOutput;
+
+ private static enum SaveOutputType {
+ NONE, FULL, FIRST_LINE, DUMP
+ };
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/FileAssociations.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.nio.file.Path;
+import java.util.Map;
+
+
+public class FileAssociations {
+ public FileAssociations(String faSuffixName) {
+ suffixName = faSuffixName;
+ setFilename("fa");
+ setDescription("jpackage test extention");
+ }
+
+ public void createFile() {
+ TKit.createPropertiesFile(file,
+ Map.entry("extension", suffixName),
+ Map.entry("mime-type", getMime()),
+ Map.entry("description", description));
+ }
+
+ final public FileAssociations setFilename(String v) {
+ file = TKit.workDir().resolve(v + ".properties");
+ return this;
+ }
+
+ final public FileAssociations setDescription(String v) {
+ description = v;
+ return this;
+ }
+
+ public Path getPropertiesFile() {
+ return file;
+ }
+
+ public String getSuffix() {
+ return "." + suffixName;
+ }
+
+ public String getMime() {
+ return "application/x-jpackage-" + suffixName;
+ }
+
+ private Path file;
+ final private String suffixName;
+ private String description;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Functional.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,143 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+
+public class Functional {
+ @FunctionalInterface
+ public interface ThrowingConsumer<T> {
+ void accept(T t) throws Throwable;
+
+ public static <T> Consumer<T> toConsumer(ThrowingConsumer<T> v) {
+ return o -> {
+ try {
+ v.accept(o);
+ } catch (Throwable ex) {
+ rethrowUnchecked(ex);
+ }
+ };
+ }
+ }
+
+ @FunctionalInterface
+ public interface ThrowingSupplier<T> {
+ T get() throws Throwable;
+
+ public static <T> Supplier<T> toSupplier(ThrowingSupplier<T> v) {
+ return () -> {
+ try {
+ return v.get();
+ } catch (Throwable ex) {
+ rethrowUnchecked(ex);
+ }
+ // Unreachable
+ return null;
+ };
+ }
+ }
+
+ @FunctionalInterface
+ public interface ThrowingFunction<T, R> {
+ R apply(T t) throws Throwable;
+
+ public static <T, R> Function<T, R> toFunction(ThrowingFunction<T, R> v) {
+ return (t) -> {
+ try {
+ return v.apply(t);
+ } catch (Throwable ex) {
+ rethrowUnchecked(ex);
+ }
+ // Unreachable
+ return null;
+ };
+ }
+ }
+
+ @FunctionalInterface
+ public interface ThrowingRunnable {
+ void run() throws Throwable;
+
+ public static Runnable toRunnable(ThrowingRunnable v) {
+ return () -> {
+ try {
+ v.run();
+ } catch (Throwable ex) {
+ rethrowUnchecked(ex);
+ }
+ };
+ }
+ }
+
+ public static <T> Supplier<T> identity(Supplier<T> v) {
+ return v;
+ }
+
+ public static <T> Consumer<T> identity(Consumer<T> v) {
+ return v;
+ }
+
+ public static Runnable identity(Runnable v) {
+ return v;
+ }
+
+ public static <T, R> Function<T, R> identity(Function<T, R> v) {
+ return v;
+ }
+
+ public static <T, R> Function<T, R> identityFunction(Function<T, R> v) {
+ return v;
+ }
+
+ public static <T> Predicate<T> identity(Predicate<T> v) {
+ return v;
+ }
+
+ public static <T> Predicate<T> identityPredicate(Predicate<T> v) {
+ return v;
+ }
+
+ public static class ExceptionBox extends RuntimeException {
+ public ExceptionBox(Throwable throwable) {
+ super(throwable);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static void rethrowUnchecked(Throwable throwable) throws ExceptionBox {
+ if (throwable instanceof ExceptionBox) {
+ throw (ExceptionBox)throwable;
+ }
+
+ if (throwable instanceof InvocationTargetException) {
+ new ExceptionBox(throwable.getCause());
+ }
+
+ throw new ExceptionBox(throwable);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,304 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import jdk.jpackage.test.Functional.ThrowingFunction;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
+
+public class HelloApp {
+
+ private HelloApp() {
+ setClassName(CLASS_NAME).setJarFileName("hello.jar");
+ }
+
+ /**
+ * Set fully qualified class name. E.g: foo.bar.Buzz.
+ */
+ private HelloApp setClassName(String v) {
+ qualifiedClassName = v;
+ return this;
+ }
+
+ private HelloApp setModuleName(String v) {
+ moduleName = v;
+ return this;
+ }
+
+ private HelloApp setJarFileName(String v) {
+ jarFileName = v;
+ return this;
+ }
+
+ private HelloApp setModuleVersion(String v) {
+ moduleVersion = v;
+ return this;
+ }
+
+ private JarBuilder prepareSources(Path srcDir) throws IOException {
+ final String className = qualifiedClassName.substring(
+ qualifiedClassName.lastIndexOf('.') + 1);
+ final String packageName = packageName();
+
+ final Path srcFile = srcDir.resolve(Path.of(String.join(
+ File.separator, qualifiedClassName.split("\\.")) + ".java"));
+ Files.createDirectories(srcFile.getParent());
+
+ JarBuilder jarBuilder = createJarBuilder().addSourceFile(srcFile);
+ if (moduleName != null) {
+ Path moduleInfoFile = srcDir.resolve("module-info.java");
+ TKit.createTextFile(moduleInfoFile, List.of(
+ String.format("module %s {", moduleName),
+ String.format(" exports %s;", packageName),
+ "}"
+ ));
+ jarBuilder.addSourceFile(moduleInfoFile);
+ if (moduleVersion != null) {
+ jarBuilder.setModuleVersion(moduleVersion);
+ }
+ }
+
+ // Add package directive and replace class name in java source file.
+ // Works with simple test Hello.java.
+ // Don't expect too much from these regexps!
+ Pattern classDeclaration = Pattern.compile("(^.*\\bclass\\s+)Hello(.*$)");
+ Pattern importDirective = Pattern.compile(
+ "(?<=import (?:static )?+)[^;]+");
+ AtomicBoolean classDeclared = new AtomicBoolean();
+ AtomicBoolean packageInserted = new AtomicBoolean(packageName == null);
+
+ var packageInserter = Functional.identityFunction((line) -> {
+ packageInserted.setPlain(true);
+ return String.format("package %s;%s%s", packageName,
+ System.lineSeparator(), line);
+ });
+
+ Files.write(srcFile, Files.readAllLines(HELLO_JAVA).stream().map(line -> {
+ if (classDeclared.getPlain()) {
+ return line;
+ }
+
+ Matcher m;
+ if (!packageInserted.getPlain() && importDirective.matcher(line).find()) {
+ line = packageInserter.apply(line);
+ } else if ((m = classDeclaration.matcher(line)).find()) {
+ classDeclared.setPlain(true);
+ line = m.group(1) + className + m.group(2);
+ if (!packageInserted.getPlain()) {
+ line = packageInserter.apply(line);
+ }
+ }
+ return line;
+ }).collect(Collectors.toList()));
+
+ return jarBuilder;
+ }
+
+ private JarBuilder createJarBuilder() {
+ return new JarBuilder().setMainClass(qualifiedClassName);
+ }
+
+ private void addTo(JPackageCommand cmd) {
+ if (moduleName != null && packageName() == null) {
+ throw new IllegalArgumentException(String.format(
+ "Module [%s] with default package", moduleName));
+ }
+
+ if (moduleName == null && CLASS_NAME.equals(qualifiedClassName)) {
+ // Use Hello.java as is.
+ cmd.addAction((self) -> {
+ File jarFile = self.inputDir().resolve(jarFileName).toFile();
+ createJarBuilder().setOutputJar(jarFile).addSourceFile(
+ HELLO_JAVA).create();
+ });
+ } else {
+ cmd.addAction((self) -> {
+ final Path jarFile;
+ if (moduleName == null) {
+ jarFile = self.inputDir().resolve(jarFileName);
+ } else {
+ // `--module-path` option should be set by the moment
+ // when this action is being executed.
+ jarFile = Path.of(self.getArgumentValue("--module-path",
+ () -> self.inputDir().toString()), jarFileName);
+ Files.createDirectories(jarFile.getParent());
+ }
+
+ TKit.withTempDirectory("src",
+ workDir -> prepareSources(workDir).setOutputJar(
+ jarFile.toFile()).create());
+ });
+ }
+
+ if (moduleName == null) {
+ cmd.addArguments("--main-jar", jarFileName);
+ cmd.addArguments("--main-class", qualifiedClassName);
+ } else {
+ cmd.addArguments("--module-path", TKit.workDir().resolve(
+ "input-modules"));
+ cmd.addArguments("--module", String.join("/", moduleName,
+ qualifiedClassName));
+ // For modular app assume nothing will go in input directory and thus
+ // nobody will create input directory, so remove corresponding option
+ // from jpackage command line.
+ cmd.removeArgumentWithValue("--input");
+ }
+ if (TKit.isWindows()) {
+ cmd.addArguments("--win-console");
+ }
+ }
+
+ private String packageName() {
+ int lastDotIdx = qualifiedClassName.lastIndexOf('.');
+ if (lastDotIdx == -1) {
+ return null;
+ }
+ return qualifiedClassName.substring(0, lastDotIdx);
+ }
+
+ /**
+ * Configures Java application to be used with the given jpackage command.
+ * Syntax of encoded Java application description is
+ * [jar_file:][module_name/]qualified_class_name[@module_version].
+ *
+ * E.g.: duke.jar:com.other/com.other.foo.bar.Buz@3.7 encodes modular
+ * application. Module name is `com.other`. Main class is
+ * `com.other.foo.bar.Buz`. Module version is `3.7`. Application will be
+ * compiled and packed in `duke.jar` jar file.
+ *
+ * @param cmd jpackage command to configure
+ * @param javaAppDesc encoded Java application description
+ */
+ static void addTo(JPackageCommand cmd, String javaAppDesc) {
+ HelloApp helloApp = new HelloApp();
+ if (javaAppDesc != null) {
+ String moduleNameAndOther = Functional.identity(() -> {
+ String[] components = javaAppDesc.split(":", 2);
+ if (components.length == 2) {
+ helloApp.setJarFileName(components[0]);
+ }
+ return components[components.length - 1];
+ }).get();
+
+ String classNameAndOther = Functional.identity(() -> {
+ String[] components = moduleNameAndOther.split("/", 2);
+ if (components.length == 2) {
+ helloApp.setModuleName(components[0]);
+ }
+ return components[components.length - 1];
+ }).get();
+
+ Functional.identity(() -> {
+ String[] components = classNameAndOther.split("@", 2);
+ helloApp.setClassName(components[0]);
+ if (components.length == 2) {
+ helloApp.setModuleVersion(components[1]);
+ }
+ }).run();
+ }
+ helloApp.addTo(cmd);
+ }
+
+ static void verifyOutputFile(Path outputFile, List<String> args) {
+ if (!outputFile.isAbsolute()) {
+ verifyOutputFile(outputFile.toAbsolutePath().normalize(), args);
+ return;
+ }
+
+ TKit.assertFileExists(outputFile);
+
+ List<String> contents = ThrowingSupplier.toSupplier(
+ () -> Files.readAllLines(outputFile)).get();
+
+ List<String> expected = new ArrayList<>(List.of(
+ "jpackage test application",
+ String.format("args.length: %d", args.size())
+ ));
+ expected.addAll(args);
+
+ TKit.assertStringListEquals(expected, contents, String.format(
+ "Check contents of [%s] file", outputFile));
+ }
+
+ public static void executeLauncherAndVerifyOutput(JPackageCommand cmd) {
+ final Path launcherPath;
+ if (cmd.packageType() == PackageType.IMAGE) {
+ launcherPath = cmd.appImage().resolve(cmd.launcherPathInAppImage());
+ if (cmd.isFakeRuntimeInAppImage(String.format(
+ "Not running [%s] launcher from application image",
+ launcherPath))) {
+ return;
+ }
+ } else {
+ launcherPath = cmd.launcherInstallationPath();
+ if (cmd.isFakeRuntimeInstalled(String.format(
+ "Not running [%s] launcher", launcherPath))) {
+ return;
+ }
+ }
+
+ executeAndVerifyOutput(launcherPath, cmd.getAllArgumentValues(
+ "--arguments"));
+ }
+
+ public static void executeAndVerifyOutput(Path helloAppLauncher,
+ String... defaultLauncherArgs) {
+ executeAndVerifyOutput(helloAppLauncher, List.of(defaultLauncherArgs));
+ }
+
+ public static void executeAndVerifyOutput(Path helloAppLauncher,
+ List<String> defaultLauncherArgs) {
+ // Output file will be created in the current directory.
+ Path outputFile = Path.of(OUTPUT_FILENAME);
+ ThrowingFunction.toFunction(Files::deleteIfExists).apply(outputFile);
+ new Executor()
+ .setDirectory(outputFile.getParent())
+ .setExecutable(helloAppLauncher)
+ .execute()
+ .assertExitCodeIsZero();
+
+ verifyOutputFile(outputFile, defaultLauncherArgs);
+ }
+
+ final static String OUTPUT_FILENAME = "appOutput.txt";
+
+ private String qualifiedClassName;
+ private String moduleName;
+ private String jarFileName;
+ private String moduleVersion;
+
+ private static final Path HELLO_JAVA = TKit.TEST_SRC_ROOT.resolve(
+ "apps/image/Hello.java");
+
+ private final static String CLASS_NAME = HELLO_JAVA.getFileName().toString().split(
+ "\\.", 2)[0];
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,645 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.SecureRandom;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+
+/**
+ * jpackage command line with prerequisite actions. Prerequisite actions can be
+ * anything. The simplest is to compile test application and pack in a jar for
+ * use on jpackage command line.
+ */
+public final class JPackageCommand extends CommandArguments<JPackageCommand> {
+
+ public JPackageCommand() {
+ actions = new ArrayList<>();
+ }
+
+ JPackageCommand createImmutableCopy() {
+ JPackageCommand reply = new JPackageCommand();
+ reply.immutable = true;
+ reply.args.addAll(args);
+ return reply;
+ }
+
+ public JPackageCommand setArgumentValue(String argName, String newValue) {
+ verifyMutable();
+
+ String prevArg = null;
+ ListIterator<String> it = args.listIterator();
+ while (it.hasNext()) {
+ String value = it.next();
+ if (prevArg != null && prevArg.equals(argName)) {
+ if (newValue != null) {
+ it.set(newValue);
+ } else {
+ it.remove();
+ it.previous();
+ it.remove();
+ }
+ return this;
+ }
+ prevArg = value;
+ }
+
+ if (newValue != null) {
+ addArguments(argName, newValue);
+ }
+
+ return this;
+ }
+
+ public JPackageCommand setArgumentValue(String argName, Path newValue) {
+ return setArgumentValue(argName, newValue.toString());
+ }
+
+ public JPackageCommand removeArgumentWithValue(String argName) {
+ return setArgumentValue(argName, (String)null);
+ }
+
+ public JPackageCommand removeArgument(String argName) {
+ args = args.stream().filter(arg -> !arg.equals(argName)).collect(
+ Collectors.toList());
+ return this;
+ }
+
+ public boolean hasArgument(String argName) {
+ return args.contains(argName);
+ }
+
+ public <T> T getArgumentValue(String argName,
+ Function<JPackageCommand, T> defaultValueSupplier,
+ Function<String, T> stringConverter) {
+ String prevArg = null;
+ for (String arg : args) {
+ if (prevArg != null && prevArg.equals(argName)) {
+ return stringConverter.apply(arg);
+ }
+ prevArg = arg;
+ }
+ if (defaultValueSupplier != null) {
+ return defaultValueSupplier.apply(this);
+ }
+ return null;
+ }
+
+ public String getArgumentValue(String argName,
+ Function<JPackageCommand, String> defaultValueSupplier) {
+ return getArgumentValue(argName, defaultValueSupplier, v -> v);
+ }
+
+ public <T> T getArgumentValue(String argName,
+ Supplier<T> defaultValueSupplier,
+ Function<String, T> stringConverter) {
+ return getArgumentValue(argName, (unused) -> defaultValueSupplier.get(),
+ stringConverter);
+ }
+
+ public String getArgumentValue(String argName,
+ Supplier<String> defaultValueSupplier) {
+ return getArgumentValue(argName, defaultValueSupplier, v -> v);
+ }
+
+ public String getArgumentValue(String argName) {
+ return getArgumentValue(argName, (Supplier<String>)null);
+ }
+
+ public String[] getAllArgumentValues(String argName) {
+ List<String> values = new ArrayList<>();
+ String prevArg = null;
+ for (String arg : args) {
+ if (prevArg != null && prevArg.equals(argName)) {
+ values.add(arg);
+ }
+ prevArg = arg;
+ }
+ return values.toArray(String[]::new);
+ }
+
+ public JPackageCommand addArguments(String name, Path value) {
+ return addArguments(name, value.toString());
+ }
+
+ public PackageType packageType() {
+ // Don't try to be in sync with jpackage defaults. Keep it simple:
+ // if no `--package-type` explicitely set on the command line, consider
+ // this is operator's fault.
+ return getArgumentValue("--package-type",
+ () -> {
+ throw new IllegalStateException("Package type not set");
+ }, PACKAGE_TYPES::get);
+ }
+
+ public Path outputDir() {
+ return getArgumentValue("--dest", () -> Path.of("."), Path::of);
+ }
+
+ public Path inputDir() {
+ return getArgumentValue("--input", () -> null, Path::of);
+ }
+
+ public String version() {
+ return getArgumentValue("--app-version", () -> "1.0");
+ }
+
+ public String name() {
+ return getArgumentValue("--name", () -> getArgumentValue("--main-class"));
+ }
+
+ public boolean isRuntime() {
+ return hasArgument("--runtime-image")
+ && !hasArgument("--main-jar")
+ && !hasArgument("--module")
+ && !hasArgument("--app-image");
+ }
+
+ public JPackageCommand setDefaultInputOutput() {
+ addArguments("--input", TKit.defaultInputDir());
+ addArguments("--dest", TKit.defaultOutputDir());
+ return this;
+ }
+
+ public JPackageCommand setFakeRuntime() {
+ verifyMutable();
+
+ try {
+ Path fakeRuntimeDir = TKit.workDir().resolve("fake_runtime");
+ Files.createDirectories(fakeRuntimeDir);
+
+ if (TKit.isWindows() || TKit.isLinux()) {
+ // Needed to make WindowsAppBundler happy as it copies MSVC dlls
+ // from `bin` directory.
+ // Need to make the code in rpm spec happy as it assumes there is
+ // always something in application image.
+ fakeRuntimeDir.resolve("bin").toFile().mkdir();
+ }
+
+ Path bulk = fakeRuntimeDir.resolve(Path.of("bin", "bulk"));
+
+ // Mak sure fake runtime takes some disk space.
+ // Package bundles with 0KB size are unexpected and considered
+ // an error by PackageTest.
+ Files.createDirectories(bulk.getParent());
+ try (FileOutputStream out = new FileOutputStream(bulk.toFile())) {
+ byte[] bytes = new byte[4 * 1024];
+ new SecureRandom().nextBytes(bytes);
+ out.write(bytes);
+ }
+
+ addArguments("--runtime-image", fakeRuntimeDir);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+
+ return this;
+ }
+
+ JPackageCommand addAction(ThrowingConsumer<JPackageCommand> action) {
+ verifyMutable();
+ actions.add(ThrowingConsumer.toConsumer(action));
+ return this;
+ }
+
+ public static JPackageCommand helloAppImage() {
+ return helloAppImage(null);
+ }
+
+ /**
+ * Creates new JPackageCommand instance configured with the test Java app.
+ * For the explanation of `javaAppDesc` parameter, see documentation for
+ * HelloApp.addTo() method.
+ *
+ * @param javaAppDesc Java application description
+ * @return this
+ */
+ public static JPackageCommand helloAppImage(String javaAppDesc) {
+ JPackageCommand cmd = new JPackageCommand();
+ cmd.setDefaultInputOutput().setDefaultAppName();
+ PackageType.IMAGE.applyTo(cmd);
+ HelloApp.addTo(cmd, javaAppDesc);
+ return cmd;
+ }
+
+ public JPackageCommand setPackageType(PackageType type) {
+ verifyMutable();
+ type.applyTo(this);
+ return this;
+ }
+
+ JPackageCommand setDefaultAppName() {
+ return addArguments("--name", TKit.getCurrentDefaultAppName());
+ }
+
+ public Path outputBundle() {
+ final PackageType type = packageType();
+ if (PackageType.IMAGE == type) {
+ return null;
+ }
+
+ String bundleName = null;
+ if (PackageType.LINUX.contains(type)) {
+ bundleName = LinuxHelper.getBundleName(this);
+ } else if (PackageType.WINDOWS.contains(type)) {
+ bundleName = WindowsHelper.getBundleName(this);
+ } else if (PackageType.MAC.contains(type)) {
+ bundleName = MacHelper.getBundleName(this);
+ }
+
+ return outputDir().resolve(bundleName);
+ }
+
+ /**
+ * Returns path to directory where application will be installed.
+ *
+ * E.g. on Linux for app named Foo default the function will return
+ * `/opt/foo`
+ */
+ public Path appInstallationDirectory() {
+ final PackageType type = packageType();
+ if (PackageType.IMAGE == type) {
+ return null;
+ }
+
+ if (PackageType.LINUX.contains(type)) {
+ if (isRuntime()) {
+ // Not fancy, but OK.
+ return Path.of(getArgumentValue("--install-dir", () -> "/opt"),
+ LinuxHelper.getPackageName(this));
+ }
+
+ // Launcher is in "bin" subfolder of the installation directory.
+ return launcherInstallationPath().getParent().getParent();
+ }
+
+ if (PackageType.WINDOWS.contains(type)) {
+ return WindowsHelper.getInstallationDirectory(this);
+ }
+
+ if (PackageType.MAC.contains(type)) {
+ return MacHelper.getInstallationDirectory(this);
+ }
+
+ throw throwUnexpectedPackageTypeError();
+ }
+
+ /**
+ * Returns path where application's Java runtime will be installed.
+ * If the command will package Java run-time only, still returns path to
+ * runtime subdirectory.
+ *
+ * E.g. on Linux for app named `Foo` the function will return
+ * `/opt/foo/runtime`
+ */
+ public Path appRuntimeInstallationDirectory() {
+ if (PackageType.IMAGE == packageType()) {
+ return null;
+ }
+ return appInstallationDirectory().resolve(appRuntimePath(packageType()));
+ }
+
+ /**
+ * Returns path where application launcher will be installed.
+ * If the command will package Java run-time only, still returns path to
+ * application launcher.
+ *
+ * E.g. on Linux for app named Foo default the function will return
+ * `/opt/foo/bin/Foo`
+ */
+ public Path launcherInstallationPath() {
+ final PackageType type = packageType();
+ if (PackageType.IMAGE == type) {
+ return null;
+ }
+
+ if (PackageType.LINUX.contains(type)) {
+ return outputDir().resolve(LinuxHelper.getLauncherPath(this));
+ }
+
+ if (PackageType.WINDOWS.contains(type)) {
+ return appInstallationDirectory().resolve(name() + ".exe");
+ }
+
+ if (PackageType.MAC.contains(type)) {
+ return appInstallationDirectory().resolve(Path.of("Contents", "MacOS", name()));
+ }
+
+ throw throwUnexpectedPackageTypeError();
+ }
+
+ /**
+ * Returns path to application image directory.
+ *
+ * E.g. if --dest is set to `foo` and --name is set to `bar` the function
+ * will return `foo/bar` path on Linux and Windows and `foo/bar.app` on macOS.
+ *
+ * @throws IllegalArgumentException is command is doing platform packaging
+ */
+ public Path appImage() {
+ verifyIsOfType(PackageType.IMAGE);
+ String dirName = name();
+ if (TKit.isOSX()) {
+ dirName = dirName + ".app";
+ }
+ return outputDir().resolve(dirName);
+ }
+
+ /**
+ * Returns path to application launcher relative to image directory.
+ *
+ * E.g. if --name is set to `Foo` the function will return `bin/Foo` path on
+ * Linux, and `Foo.exe` on Windows.
+ *
+ * @throws IllegalArgumentException is command is doing platform packaging
+ */
+ public Path launcherPathInAppImage() {
+ verifyIsOfType(PackageType.IMAGE);
+
+ if (TKit.isLinux()) {
+ return Path.of("bin", name());
+ }
+
+ if (TKit.isOSX()) {
+ return Path.of("Contents", "MacOS", name());
+ }
+
+ if (TKit.isWindows()) {
+ return Path.of(name() + ".exe");
+ }
+
+ throw TKit.throwUnknownPlatformError();
+ }
+
+ /**
+ * Returns path to runtime directory relative to image directory.
+ *
+ * @throws IllegalArgumentException if command is configured for platform
+ * packaging
+ */
+ public Path appRuntimeDirectoryInAppImage() {
+ verifyIsOfType(PackageType.IMAGE);
+ return appRuntimePath(packageType());
+ }
+
+ private static Path appRuntimePath(PackageType type) {
+ if (TKit.isLinux()) {
+ return Path.of("lib/runtime");
+ }
+ if (TKit.isOSX()) {
+ return Path.of("Contents/runtime");
+ }
+
+ return Path.of("runtime");
+ }
+
+ public boolean isFakeRuntimeInAppImage(String msg) {
+ return isFakeRuntime(appImage().resolve(
+ appRuntimeDirectoryInAppImage()), msg);
+ }
+
+ public boolean isFakeRuntimeInstalled(String msg) {
+ return isFakeRuntime(appRuntimeInstallationDirectory(), msg);
+ }
+
+ private static boolean isFakeRuntime(Path runtimeDir, String msg) {
+ final Collection<Path> criticalRuntimeFiles;
+ if (TKit.isWindows()) {
+ criticalRuntimeFiles = WindowsHelper.CRITICAL_RUNTIME_FILES;
+ } else if (TKit.isLinux()) {
+ criticalRuntimeFiles = LinuxHelper.CRITICAL_RUNTIME_FILES;
+ } else if (TKit.isOSX()) {
+ criticalRuntimeFiles = MacHelper.CRITICAL_RUNTIME_FILES;
+ } else {
+ throw TKit.throwUnknownPlatformError();
+ }
+
+ if (criticalRuntimeFiles.stream().filter(
+ v -> runtimeDir.resolve(v).toFile().exists()).findFirst().orElse(
+ null) == null) {
+ // Fake runtime
+ TKit.trace(String.format(
+ "%s because application runtime directory [%s] is incomplete",
+ msg, runtimeDir));
+ return true;
+ }
+ return false;
+ }
+
+ public static void useToolProviderByDefault() {
+ defaultWithToolProvider = true;
+ }
+
+ public static void useExecutableByDefault() {
+ defaultWithToolProvider = false;
+ }
+
+ public JPackageCommand useToolProvider(boolean v) {
+ verifyMutable();
+ withToolProvider = v;
+ return this;
+ }
+
+ public JPackageCommand saveConsoleOutput(boolean v) {
+ verifyMutable();
+ saveConsoleOutput = v;
+ return this;
+ }
+
+ public JPackageCommand dumpOutput(boolean v) {
+ verifyMutable();
+ suppressOutput = !v;
+ return this;
+ }
+
+ public boolean isWithToolProvider() {
+ return Optional.ofNullable(withToolProvider).orElse(
+ defaultWithToolProvider);
+ }
+
+ public void executePrerequisiteActions() {
+ verifyMutable();
+ if (!actionsExecuted) {
+ actionsExecuted = true;
+ if (actions != null) {
+ actions.stream().forEach(r -> r.accept(this));
+ }
+ }
+ }
+
+ public Executor.Result execute() {
+ executePrerequisiteActions();
+
+ if (packageType() == PackageType.IMAGE) {
+ TKit.deleteDirectoryContentsRecursive(outputDir());
+ }
+
+ Executor exec = new Executor()
+ .saveOutput(saveConsoleOutput).dumpOutput(!suppressOutput)
+ .addArguments(new JPackageCommand().addArguments(
+ args).adjustArgumentsBeforeExecution().args);
+
+ if (isWithToolProvider()) {
+ exec.setToolProvider(JavaTool.JPACKAGE.asToolProvider());
+ } else {
+ exec.setExecutable(JavaTool.JPACKAGE);
+ }
+ return exec.execute();
+ }
+
+ public JPackageCommand executeAndAssertHelloAppImageCreated() {
+ executeAndAssertImageCreated();
+ HelloApp.executeLauncherAndVerifyOutput(this);
+ return this;
+ }
+
+ public JPackageCommand executeAndAssertImageCreated() {
+ execute().assertExitCodeIsZero();
+ return assertImageCreated();
+ }
+
+ public JPackageCommand assertImageCreated() {
+ verifyIsOfType(PackageType.IMAGE);
+ TKit.assertExecutableFileExists(appImage().resolve(
+ launcherPathInAppImage()));
+ TKit.assertDirectoryExists(appImage().resolve(
+ appRuntimeDirectoryInAppImage()));
+ return this;
+ }
+
+ private JPackageCommand adjustArgumentsBeforeExecution() {
+ if (!hasArgument("--runtime-image") && !hasArgument("--app-image") && DEFAULT_RUNTIME_IMAGE != null) {
+ addArguments("--runtime-image", DEFAULT_RUNTIME_IMAGE);
+ }
+
+ if (!hasArgument("--verbose") && TKit.VERBOSE_JPACKAGE) {
+ addArgument("--verbose");
+ }
+
+ return this;
+ }
+
+ private static RuntimeException throwUnexpectedPackageTypeError() {
+ throw new IllegalArgumentException("Unexpected package type");
+ }
+
+ String getPrintableCommandLine() {
+ return new Executor()
+ .setExecutable(JavaTool.JPACKAGE)
+ .addArguments(args)
+ .getPrintableCommandLine();
+ }
+
+ public void verifyIsOfType(Collection<PackageType> types) {
+ verifyIsOfType(types.toArray(PackageType[]::new));
+ }
+
+ public void verifyIsOfType(PackageType ... types) {
+ if (!Arrays.asList(types).contains(packageType())) {
+ throwUnexpectedPackageTypeError();
+ }
+ }
+
+ public static String escapeAndJoin(String... args) {
+ return escapeAndJoin(List.of(args));
+ }
+
+ public static String escapeAndJoin(List<String> args) {
+ Pattern whitespaceRegexp = Pattern.compile("\\s");
+
+ return args.stream().map(v -> {
+ String str = v;
+ // Escape quotes.
+ str = str.replace("\"", "\\\"");
+ // Escape backslashes.
+ str = str.replace("\\", "\\\\");
+ // If value contains whitespace characters, put the value in quotes
+ if (whitespaceRegexp.matcher(str).find()) {
+ str = "\"" + str + "\"";
+ }
+ return str;
+ }).collect(Collectors.joining(" "));
+ }
+
+ public static Path relativePathInRuntime(JavaTool tool) {
+ Path path = tool.relativePathInJavaHome();
+ if (TKit.isOSX()) {
+ path = Path.of("Contents/Home").resolve(path);
+ }
+ return path;
+ }
+
+ public static Stream<String> filterOutput(Stream<String> jpackageOutput) {
+ // Skip "WARNING: Using experimental tool jpackage" first line of output
+ return jpackageOutput.skip(1);
+ }
+
+ public static List<String> filterOutput(List<String> jpackageOutput) {
+ return filterOutput(jpackageOutput.stream()).collect(Collectors.toList());
+ }
+
+ @Override
+ protected boolean isMutable() {
+ return !immutable;
+ }
+
+ private Boolean withToolProvider;
+ private boolean saveConsoleOutput;
+ private boolean suppressOutput;
+ private boolean immutable;
+ private boolean actionsExecuted;
+ private final List<Consumer<JPackageCommand>> actions;
+ private static boolean defaultWithToolProvider;
+
+ private final static Map<String, PackageType> PACKAGE_TYPES = Functional.identity(
+ () -> {
+ Map<String, PackageType> reply = new HashMap<>();
+ for (PackageType type : PackageType.values()) {
+ reply.put(type.getName(), type);
+ }
+ return reply;
+ }).get();
+
+ public final static Path DEFAULT_RUNTIME_IMAGE = Functional.identity(() -> {
+ // Set the property to the path of run-time image to speed up
+ // building app images and platform bundles by avoiding running jlink
+ // The value of the property will be automativcally appended to
+ // jpackage command line if the command line doesn't have
+ // `--runtime-image` parameter set.
+ String val = TKit.getConfigProperty("runtime-image");
+ if (val != null) {
+ return Path.of(val);
+ }
+ return null;
+ }).get();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JarBuilder.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,92 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Tool to compile Java sources and pack in a jar file.
+ */
+public final class JarBuilder {
+
+ public JarBuilder() {
+ sourceFiles = new ArrayList<>();
+ }
+
+ public JarBuilder setOutputJar(File v) {
+ outputJar = v;
+ return this;
+ }
+
+ public JarBuilder setMainClass(String v) {
+ mainClass = v;
+ return this;
+ }
+
+ public JarBuilder addSourceFile(Path v) {
+ sourceFiles.add(v);
+ return this;
+ }
+
+ public JarBuilder setModuleVersion(String v) {
+ moduleVersion = v;
+ return this;
+ }
+
+ public void create() {
+ TKit.withTempDirectory("jar-workdir", workDir -> {
+ Executor.Result javacReply = new Executor()
+ .setExecutable(JavaTool.JAVAC)
+ .addArguments("-d", workDir.toString())
+ .addPathArguments(sourceFiles).execute();
+ javacReply.assertExitCodeIsZero();
+ Path tmpJar = workDir.resolve("foo.jar");
+ Executor jarExe = new Executor();
+ jarExe.setExecutable(JavaTool.JAR).addArguments("-c", "-v", "-f",
+ tmpJar.toString());
+ if (moduleVersion != null) {
+ jarExe.addArguments(String.format("--module-version=%s",
+ moduleVersion));
+ }
+ if (mainClass != null) {
+ jarExe.addArguments("-e", mainClass);
+ }
+ jarExe.addArguments("-C", workDir.toString(), ".");
+ javacReply = jarExe.execute();
+ javacReply.assertExitCodeIsZero();
+ outputJar.getParentFile().mkdirs();
+ Files.copy(tmpJar, outputJar.toPath(), REPLACE_EXISTING);
+ });
+ }
+ private List<Path> sourceFiles;
+ private File outputJar;
+ private String mainClass;
+ private String moduleVersion;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JavaTool.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+
+import java.nio.file.Path;
+import java.util.spi.ToolProvider;
+
+public enum JavaTool {
+ JAVA("java"), JAVAC("javac"), JPACKAGE("jpackage"), JAR("jar"), JLINK("jlink");
+
+ JavaTool(String name) {
+ this.name = name;
+ this.path = Path.of(System.getProperty("java.home")).resolve(
+ relativePathInJavaHome()).toAbsolutePath().normalize();
+ if (!path.toFile().exists()) {
+ throw new RuntimeException(String.format(
+ "Unable to find tool [%s] at path=[%s]", name, path));
+ }
+ }
+
+ Path getPath() {
+ return path;
+ }
+
+ public ToolProvider asToolProvider() {
+ return ToolProvider.findFirst(name).orElse(null);
+ }
+
+ Path relativePathInJavaHome() {
+ Path path = Path.of("bin", name);
+ if (TKit.isWindows()) {
+ path = path.getParent().resolve(path.getFileName().toString() + ".exe");
+ }
+ return path;
+ }
+
+ private Path path;
+ private String name;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,408 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class LinuxHelper {
+ private static String getRelease(JPackageCommand cmd) {
+ return cmd.getArgumentValue("--linux-app-release", () -> "1");
+ }
+
+ public static String getPackageName(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.LINUX);
+ return cmd.getArgumentValue("--linux-package-name",
+ () -> cmd.name().toLowerCase());
+ }
+
+ static String getBundleName(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.LINUX);
+
+ final PackageType packageType = cmd.packageType();
+ String format = null;
+ switch (packageType) {
+ case LINUX_DEB:
+ format = "%s_%s-%s_%s";
+ break;
+
+ case LINUX_RPM:
+ format = "%s-%s-%s.%s";
+ break;
+ }
+
+ final String release = getRelease(cmd);
+ final String version = cmd.version();
+
+ return String.format(format,
+ getPackageName(cmd), version, release, getPackageArch(packageType))
+ + packageType.getSuffix();
+ }
+
+ public static Stream<Path> getPackageFiles(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.LINUX);
+
+ final PackageType packageType = cmd.packageType();
+ final Path packageFile = cmd.outputBundle();
+
+ Executor exec = new Executor();
+ switch (packageType) {
+ case LINUX_DEB:
+ exec.setExecutable("dpkg")
+ .addArgument("--contents")
+ .addArgument(packageFile);
+ break;
+
+ case LINUX_RPM:
+ exec.setExecutable("rpm")
+ .addArgument("-qpl")
+ .addArgument(packageFile);
+ break;
+ }
+
+ Stream<String> lines = exec.executeAndGetOutput().stream();
+ if (packageType == PackageType.LINUX_DEB) {
+ // Typical text lines produced by dpkg look like:
+ // drwxr-xr-x root/root 0 2019-08-30 05:30 ./opt/appcategorytest/runtime/lib/
+ // -rw-r--r-- root/root 574912 2019-08-30 05:30 ./opt/appcategorytest/runtime/lib/libmlib_image.so
+ // Need to skip all fields but absolute path to file.
+ lines = lines.map(line -> line.substring(line.indexOf(" ./") + 2));
+ }
+ return lines.map(Path::of);
+ }
+
+ public static List<String> getPrerequisitePackages(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.LINUX);
+ var packageType = cmd.packageType();
+ switch (packageType) {
+ case LINUX_DEB:
+ return Stream.of(getDebBundleProperty(cmd.outputBundle(),
+ "Depends").split(",")).map(String::strip).collect(
+ Collectors.toList());
+
+ case LINUX_RPM:
+ return new Executor().setExecutable("rpm")
+ .addArguments("-qp", "-R", cmd.outputBundle().toString())
+ .executeAndGetOutput();
+ }
+ // Unreachable
+ return null;
+ }
+
+ public static String getBundleProperty(JPackageCommand cmd,
+ String propertyName) {
+ return getBundleProperty(cmd,
+ Map.of(PackageType.LINUX_DEB, propertyName,
+ PackageType.LINUX_RPM, propertyName));
+ }
+
+ public static String getBundleProperty(JPackageCommand cmd,
+ Map<PackageType, String> propertyName) {
+ cmd.verifyIsOfType(PackageType.LINUX);
+ var packageType = cmd.packageType();
+ switch (packageType) {
+ case LINUX_DEB:
+ return getDebBundleProperty(cmd.outputBundle(), propertyName.get(
+ packageType));
+
+ case LINUX_RPM:
+ return getRpmBundleProperty(cmd.outputBundle(), propertyName.get(
+ packageType));
+ }
+ // Unrechable
+ return null;
+ }
+
+ static Path getLauncherPath(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.LINUX);
+
+ final String launcherName = cmd.name();
+ final String launcherRelativePath = Path.of("/bin", launcherName).toString();
+
+ return getPackageFiles(cmd).filter(path -> path.toString().endsWith(
+ launcherRelativePath)).findFirst().or(() -> {
+ TKit.assertUnexpected(String.format(
+ "Failed to find %s in %s package", launcherName,
+ getPackageName(cmd)));
+ return null;
+ }).get();
+ }
+
+ static long getInstalledPackageSizeKB(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.LINUX);
+
+ final Path packageFile = cmd.outputBundle();
+ switch (cmd.packageType()) {
+ case LINUX_DEB:
+ return Long.parseLong(getDebBundleProperty(packageFile,
+ "Installed-Size"));
+
+ case LINUX_RPM:
+ return Long.parseLong(getRpmBundleProperty(packageFile, "Size")) >> 10;
+ }
+
+ return 0;
+ }
+
+ static String getDebBundleProperty(Path bundle, String fieldName) {
+ return new Executor()
+ .setExecutable("dpkg-deb")
+ .addArguments("-f", bundle.toString(), fieldName)
+ .executeAndGetFirstLineOfOutput();
+ }
+
+ static String getRpmBundleProperty(Path bundle, String fieldName) {
+ return new Executor()
+ .setExecutable("rpm")
+ .addArguments(
+ "-qp",
+ "--queryformat",
+ String.format("%%{%s}", fieldName),
+ bundle.toString())
+ .executeAndGetFirstLineOfOutput();
+ }
+
+ static void verifyPackageBundleEssential(JPackageCommand cmd) {
+ String packageName = LinuxHelper.getPackageName(cmd);
+ TKit.assertNotEquals(0L, LinuxHelper.getInstalledPackageSizeKB(
+ cmd), String.format(
+ "Check installed size of [%s] package in KB is not zero",
+ packageName));
+
+ final boolean checkPrerequisites;
+ if (cmd.isRuntime()) {
+ Path runtimeDir = cmd.appRuntimeInstallationDirectory();
+ Set<Path> expectedCriticalRuntimePaths = CRITICAL_RUNTIME_FILES.stream().map(
+ runtimeDir::resolve).collect(Collectors.toSet());
+ Set<Path> actualCriticalRuntimePaths = getPackageFiles(cmd).filter(
+ expectedCriticalRuntimePaths::contains).collect(
+ Collectors.toSet());
+ checkPrerequisites = expectedCriticalRuntimePaths.equals(
+ actualCriticalRuntimePaths);
+ } else {
+ checkPrerequisites = true;
+ }
+
+ List<String> prerequisites = LinuxHelper.getPrerequisitePackages(cmd);
+ if (checkPrerequisites) {
+ final String vitalPackage = "libc";
+ TKit.assertTrue(prerequisites.stream().filter(
+ dep -> dep.contains(vitalPackage)).findAny().isPresent(),
+ String.format(
+ "Check [%s] package is in the list of required packages %s of [%s] package",
+ vitalPackage, prerequisites, packageName));
+ } else {
+ TKit.trace(String.format(
+ "Not cheking %s required packages of [%s] package",
+ prerequisites, packageName));
+ }
+ }
+
+ static void addBundleDesktopIntegrationVerifier(PackageTest test,
+ boolean integrated) {
+ final String xdgUtils = "xdg-utils";
+
+ test.addBundleVerifier(cmd -> {
+ List<String> prerequisites = getPrerequisitePackages(cmd);
+ boolean xdgUtilsFound = prerequisites.contains(xdgUtils);
+ if (integrated) {
+ TKit.assertTrue(xdgUtilsFound, String.format(
+ "Check [%s] is in the list of required packages %s",
+ xdgUtils, prerequisites));
+ } else {
+ TKit.assertFalse(xdgUtilsFound, String.format(
+ "Check [%s] is NOT in the list of required packages %s",
+ xdgUtils, prerequisites));
+ }
+ });
+
+ test.forTypes(PackageType.LINUX_DEB, () -> {
+ addDebBundleDesktopIntegrationVerifier(test, integrated);
+ });
+ }
+
+ private static void addDebBundleDesktopIntegrationVerifier(PackageTest test,
+ boolean integrated) {
+ Function<List<String>, String> verifier = (lines) -> {
+ // Lookup for xdg commands
+ return lines.stream().filter(line -> {
+ Set<String> words = Set.of(line.split("\\s+"));
+ return words.contains("xdg-desktop-menu") || words.contains(
+ "xdg-mime") || words.contains("xdg-icon-resource");
+ }).findFirst().orElse(null);
+ };
+
+ test.addBundleVerifier(cmd -> {
+ TKit.withTempDirectory("dpkg-control-files", tempDir -> {
+ // Extract control Debian package files into temporary directory
+ new Executor()
+ .setExecutable("dpkg")
+ .addArguments(
+ "-e",
+ cmd.outputBundle().toString(),
+ tempDir.toString()
+ ).execute().assertExitCodeIsZero();
+
+ Path controlFile = Path.of("postinst");
+
+ // Lookup for xdg commands in postinstall script
+ String lineWithXsdCommand = verifier.apply(
+ Files.readAllLines(tempDir.resolve(controlFile)));
+ String assertMsg = String.format(
+ "Check if %s@%s control file uses xdg commands",
+ cmd.outputBundle(), controlFile);
+ if (integrated) {
+ TKit.assertNotNull(lineWithXsdCommand, assertMsg);
+ } else {
+ TKit.assertNull(lineWithXsdCommand, assertMsg);
+ }
+ });
+ });
+ }
+
+ static void initFileAssociationsTestFile(Path testFile) {
+ try {
+ // Write something in test file.
+ // On Ubuntu and Oracle Linux empty files are considered
+ // plain text. Seems like a system bug.
+ //
+ // $ >foo.jptest1
+ // $ xdg-mime query filetype foo.jptest1
+ // text/plain
+ // $ echo > foo.jptest1
+ // $ xdg-mime query filetype foo.jptest1
+ // application/x-jpackage-jptest1
+ //
+ Files.write(testFile, Arrays.asList(""));
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static Path getSystemDesktopFilesFolder() {
+ return Stream.of("/usr/share/applications",
+ "/usr/local/share/applications").map(Path::of).filter(dir -> {
+ return Files.exists(dir.resolve("defaults.list"));
+ }).findFirst().orElseThrow(() -> new RuntimeException(
+ "Failed to locate system .desktop files folder"));
+ }
+
+ static void addFileAssociationsVerifier(PackageTest test, FileAssociations fa) {
+ test.addInstallVerifier(cmd -> {
+ PackageTest.withTestFileAssociationsFile(fa, testFile -> {
+ String mimeType = queryFileMimeType(testFile);
+
+ TKit.assertEquals(fa.getMime(), mimeType, String.format(
+ "Check mime type of [%s] file", testFile));
+
+ String desktopFileName = queryMimeTypeDefaultHandler(mimeType);
+
+ Path desktopFile = getSystemDesktopFilesFolder().resolve(
+ desktopFileName);
+
+ TKit.assertFileExists(desktopFile);
+
+ TKit.trace(String.format("Reading [%s] file...", desktopFile));
+ String mimeHandler = Files.readAllLines(desktopFile).stream().peek(
+ v -> TKit.trace(v)).filter(
+ v -> v.startsWith("Exec=")).map(
+ v -> v.split("=", 2)[1]).findFirst().orElseThrow();
+
+ TKit.trace(String.format("Done"));
+
+ TKit.assertEquals(cmd.launcherInstallationPath().toString(),
+ mimeHandler, String.format(
+ "Check mime type handler is the main application launcher"));
+
+ });
+ });
+
+ test.addUninstallVerifier(cmd -> {
+ PackageTest.withTestFileAssociationsFile(fa, testFile -> {
+ String mimeType = queryFileMimeType(testFile);
+
+ TKit.assertNotEquals(fa.getMime(), mimeType, String.format(
+ "Check mime type of [%s] file", testFile));
+
+ String desktopFileName = queryMimeTypeDefaultHandler(fa.getMime());
+
+ TKit.assertNull(desktopFileName, String.format(
+ "Check there is no default handler for [%s] mime type",
+ fa.getMime()));
+ });
+ });
+ }
+
+ private static String queryFileMimeType(Path file) {
+ return new Executor()
+ .setExecutable("xdg-mime")
+ .addArguments("query", "filetype", file.toString())
+ .executeAndGetFirstLineOfOutput();
+ }
+
+ private static String queryMimeTypeDefaultHandler(String mimeType) {
+ return new Executor()
+ .setExecutable("xdg-mime")
+ .addArguments("query", "default", mimeType)
+ .executeAndGetFirstLineOfOutput();
+ }
+
+ private static String getPackageArch(PackageType type) {
+ if (archs == null) {
+ archs = new HashMap<>();
+ }
+
+ String arch = archs.get(type);
+ if (arch == null) {
+ Executor exec = new Executor();
+ switch (type) {
+ case LINUX_DEB:
+ exec.setExecutable("dpkg").addArgument(
+ "--print-architecture");
+ break;
+
+ case LINUX_RPM:
+ exec.setExecutable("rpmbuild").addArgument(
+ "--eval=%{_target_cpu}");
+ break;
+ }
+ arch = exec.executeAndGetFirstLineOfOutput();
+ archs.put(type, arch);
+ }
+ return arch;
+ }
+
+ static final Set<Path> CRITICAL_RUNTIME_FILES = Set.of(Path.of(
+ "lib/server/libjvm.so"));
+
+ static private Map<PackageType, String> archs;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.nio.file.Path;
+import java.util.Set;
+
+public class MacHelper {
+
+ static String getBundleName(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.MAC);
+ return String.format("%s-%s%s", getPackageName(cmd), cmd.version(),
+ cmd.packageType().getSuffix());
+ }
+
+ static Path getInstallationDirectory(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.MAC);
+ String installDir = Path.of(cmd.getArgumentValue("--install-dir",
+ () -> ""), cmd.name()).toString() + ".app";
+ return Path.of("/Applications", installDir);
+ }
+
+ private static String getPackageName(JPackageCommand cmd) {
+ return cmd.getArgumentValue("--mac-package-name",
+ () -> cmd.name());
+ }
+
+ static final Set<Path> CRITICAL_RUNTIME_FILES = Set.of(Path.of(
+ "Contents/Home/lib/server/libjvm.dylib"));
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Main.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import static jdk.jpackage.test.TestBuilder.CMDLINE_ARG_PREFIX;
+
+
+public final class Main {
+ public static void main(String args[]) throws Throwable {
+ boolean listTests = false;
+ List<TestInstance> tests = new ArrayList<>();
+ try (TestBuilder testBuilder = new TestBuilder(tests::add)) {
+ for (var arg : args) {
+ if (TKit.VERBOSE_TEST_SETUP) {
+ TKit.log(String.format("Parsing [%s]...", arg));
+ }
+
+ if ((CMDLINE_ARG_PREFIX + "list").equals(arg)) {
+ listTests = true;
+ continue;
+ }
+
+ boolean success = false;
+ try {
+ testBuilder.processCmdLineArg(arg);
+ success = true;
+ } catch (Throwable throwable) {
+ TKit.unbox(throwable);
+ } finally {
+ if (!success) {
+ TKit.log(
+ String.format("Error processing parameter=[%s]",
+ arg));
+ }
+ }
+ }
+ }
+
+ // Order tests by their full names to have stable test sequence.
+ tests = tests.stream().sorted((a, b) -> a.fullName().compareTo(
+ b.fullName())).collect(Collectors.toList());
+
+ if (listTests) {
+ // Just list the tests
+ tests.stream().forEach(test -> System.out.println(test.fullName()));
+ return;
+ }
+
+ TKit.runTests(tests);
+
+ final long failedCount = tests.stream().filter(Functional.identity(
+ TestInstance::passed).negate()).count();
+ final long passedCount = tests.size() - failedCount;
+
+ TKit.log(String.format("[==========] %d tests ran", tests.size()));
+ TKit.log(String.format("[ PASSED ] %d %s", passedCount,
+ passedCount == 1 ? "test" : "tests"));
+ if (failedCount != 0) {
+ TKit.log(String.format("[ FAILED ] %d %s, listed below",
+ failedCount,
+ failedCount == 1 ? "test" : "tests"));
+ tests.stream().filter(
+ Functional.identity(TestInstance::passed).negate()).forEach(
+ test -> TKit.log(String.format("[ FAILED ] %s",
+ test.fullName())));
+
+ final String errorMessage = String.format("%d FAILED %s",
+ failedCount, failedCount == 1 ? "TEST" : "TESTS");
+ TKit.log(errorMessage);
+ throw new RuntimeException(errorMessage);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MethodCall.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
+import jdk.jpackage.test.TestInstance.TestDesc;
+
+class MethodCall implements ThrowingConsumer {
+
+ MethodCall(Method method, Object... args) {
+ this.method = method;
+ this.args = args;
+ }
+
+ TestDesc createDescription() {
+ return TestDesc.create(method, args);
+ }
+
+ Constructor getRequiredConstructor() throws NoSuchMethodException {
+ return MethodCall.getRequiredConstructor(method);
+ }
+
+ static Constructor getRequiredConstructor(Method method) throws
+ NoSuchMethodException {
+ if ((method.getModifiers() & Modifier.STATIC) == 0) {
+ return method.getDeclaringClass().getConstructor();
+ }
+ return null;
+ }
+
+ @Override
+ public void accept(Object thiz) throws Throwable {
+ method.invoke(thiz, args);
+ }
+
+ private final Object[] args;
+ private final Method method;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,430 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.awt.Desktop;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import static jdk.jpackage.test.PackageType.*;
+
+/**
+ * Instance of PackageTest is for configuring and running a single jpackage
+ * command to produce platform specific package bundle.
+ *
+ * Provides methods to hook up custom configuration of jpackage command and
+ * verification of the output bundle.
+ */
+public final class PackageTest {
+
+ /**
+ * Default test configuration for jpackage command. Default jpackage command
+ * initialization includes:
+ * <li>Set --input and --dest parameters.
+ * <li>Set --name parameter. Value of the parameter is the name of the first
+ * class with main function found in the callers stack. Defaults can be
+ * overridden with custom initializers set with subsequent addInitializer()
+ * function calls.
+ */
+ public PackageTest() {
+ action = DEFAULT_ACTION;
+ forTypes();
+ setExpectedExitCode(0);
+ handlers = new HashMap<>();
+ namedInitializers = new HashSet<>();
+ currentTypes.forEach(v -> handlers.put(v, new Handler(v)));
+ }
+
+ public PackageTest forTypes(PackageType... types) {
+ Collection<PackageType> newTypes;
+ if (types == null || types.length == 0) {
+ newTypes = PackageType.NATIVE;
+ } else {
+ newTypes = Set.of(types);
+ }
+ currentTypes = newTypes.stream().filter(type -> type.isSupported()).collect(
+ Collectors.toUnmodifiableSet());
+ return this;
+ }
+
+ public PackageTest forTypes(Collection<PackageType> types) {
+ return forTypes(types.toArray(PackageType[]::new));
+ }
+
+ public PackageTest setExpectedExitCode(int v) {
+ expectedJPackageExitCode = v;
+ return this;
+ }
+
+ private PackageTest addInitializer(ThrowingConsumer<JPackageCommand> v, String id) {
+ if (id != null) {
+ if (namedInitializers.contains(id)) {
+ return this;
+ }
+
+ namedInitializers.add(id);
+ }
+ currentTypes.stream().forEach(type -> handlers.get(type).addInitializer(
+ ThrowingConsumer.toConsumer(v)));
+ return this;
+ }
+
+ public PackageTest addInitializer(ThrowingConsumer<JPackageCommand> v) {
+ return addInitializer(v, null);
+ }
+
+ public PackageTest addBundleVerifier(
+ BiConsumer<JPackageCommand, Executor.Result> v) {
+ currentTypes.stream().forEach(
+ type -> handlers.get(type).addBundleVerifier(v));
+ return this;
+ }
+
+ public PackageTest addBundleVerifier(ThrowingConsumer<JPackageCommand> v) {
+ return addBundleVerifier(
+ (cmd, unused) -> ThrowingConsumer.toConsumer(v).accept(cmd));
+ }
+
+ public PackageTest addBundlePropertyVerifier(String propertyName,
+ BiConsumer<String, String> pred) {
+ return addBundleVerifier(cmd -> {
+ pred.accept(propertyName,
+ LinuxHelper.getBundleProperty(cmd, propertyName));
+ });
+ }
+
+ public PackageTest addBundlePropertyVerifier(String propertyName,
+ String expectedPropertyValue) {
+ return addBundlePropertyVerifier(propertyName, (unused, v) -> {
+ TKit.assertEquals(expectedPropertyValue, v, String.format(
+ "Check value of %s property is [%s]", propertyName, v));
+ });
+ }
+
+ public PackageTest addBundleDesktopIntegrationVerifier(boolean integrated) {
+ forTypes(LINUX, () -> {
+ LinuxHelper.addBundleDesktopIntegrationVerifier(this, integrated);
+ });
+ return this;
+ }
+
+ public PackageTest addInstallVerifier(ThrowingConsumer<JPackageCommand> v) {
+ currentTypes.stream().forEach(
+ type -> handlers.get(type).addInstallVerifier(
+ ThrowingConsumer.toConsumer(v)));
+ return this;
+ }
+
+ public PackageTest addUninstallVerifier(ThrowingConsumer<JPackageCommand> v) {
+ currentTypes.stream().forEach(
+ type -> handlers.get(type).addUninstallVerifier(
+ ThrowingConsumer.toConsumer(v)));
+ return this;
+ }
+
+ static void withTestFileAssociationsFile(FileAssociations fa, ThrowingConsumer<Path> consumer) {
+ final String testFileDefaultName = String.join(".", "test", fa.getSuffix());
+ TKit.withTempFile(testFileDefaultName, fa.getSuffix(), testFile -> {
+ if (TKit.isLinux()) {
+ LinuxHelper.initFileAssociationsTestFile(testFile);
+ }
+ consumer.accept(testFile);
+ });
+ }
+
+ public PackageTest addHelloAppFileAssociationsVerifier(FileAssociations fa,
+ String... faLauncherDefaultArgs) {
+
+ addInitializer(cmd -> HelloApp.addTo(cmd, null), "HelloApp");
+ addInstallVerifier(cmd -> {
+ if (cmd.isFakeRuntimeInstalled(
+ "Not running file associations test")) {
+ return;
+ }
+
+ withTestFileAssociationsFile(fa, testFile -> {
+ testFile = testFile.toAbsolutePath().normalize();
+
+ final Path appOutput = Path.of(HelloApp.OUTPUT_FILENAME);
+ Files.deleteIfExists(appOutput);
+
+ TKit.trace(String.format("Use desktop to open [%s] file",
+ testFile));
+ Desktop.getDesktop().open(testFile.toFile());
+ TKit.waitForFileCreated(appOutput, 7);
+
+ List<String> expectedArgs = new ArrayList<>(List.of(
+ faLauncherDefaultArgs));
+ expectedArgs.add(testFile.toString());
+
+ // Wait a little bit after file has been created to
+ // make sure there are no pending writes into it.
+ Thread.sleep(3000);
+ HelloApp.verifyOutputFile(appOutput, expectedArgs);
+ });
+ });
+
+ forTypes(PackageType.LINUX, () -> {
+ LinuxHelper.addFileAssociationsVerifier(this, fa);
+ });
+
+ return this;
+ }
+
+ PackageTest forTypes(Collection<PackageType> types, Runnable action) {
+ Set<PackageType> oldTypes = Set.of(currentTypes.toArray(
+ PackageType[]::new));
+ try {
+ forTypes(types);
+ action.run();
+ } finally {
+ forTypes(oldTypes);
+ }
+ return this;
+ }
+
+ PackageTest forTypes(PackageType type, Runnable action) {
+ return forTypes(List.of(type), action);
+ }
+
+ public PackageTest configureHelloApp() {
+ return configureHelloApp(null);
+ }
+
+ public PackageTest configureHelloApp(String encodedName) {
+ addInitializer(cmd -> HelloApp.addTo(cmd, encodedName));
+ addInstallVerifier(HelloApp::executeLauncherAndVerifyOutput);
+ return this;
+ }
+
+ public void run() {
+ List<Handler> supportedHandlers = handlers.values().stream()
+ .filter(entry -> !entry.isVoid())
+ .collect(Collectors.toList());
+
+ if (supportedHandlers.isEmpty()) {
+ // No handlers with initializers found. Nothing to do.
+ return;
+ }
+
+ Supplier<JPackageCommand> initializer = new Supplier<>() {
+ @Override
+ public JPackageCommand get() {
+ JPackageCommand cmd = new JPackageCommand().setDefaultInputOutput();
+ if (bundleOutputDir != null) {
+ cmd.setArgumentValue("--dest", bundleOutputDir.toString());
+ }
+ cmd.setDefaultAppName();
+ return cmd;
+ }
+ };
+
+ supportedHandlers.forEach(handler -> handler.accept(initializer.get()));
+ }
+
+ public PackageTest setAction(Action value) {
+ action = value;
+ return this;
+ }
+
+ public Action getAction() {
+ return action;
+ }
+
+ private class Handler implements Consumer<JPackageCommand> {
+
+ Handler(PackageType type) {
+ if (!PackageType.NATIVE.contains(type)) {
+ throw new IllegalArgumentException(
+ "Attempt to configure a test for image packaging");
+ }
+ this.type = type;
+ initializers = new ArrayList<>();
+ bundleVerifiers = new ArrayList<>();
+ installVerifiers = new ArrayList<>();
+ uninstallVerifiers = new ArrayList<>();
+ }
+
+ boolean isVoid() {
+ return initializers.isEmpty();
+ }
+
+ void addInitializer(Consumer<JPackageCommand> v) {
+ initializers.add(v);
+ }
+
+ void addBundleVerifier(BiConsumer<JPackageCommand, Executor.Result> v) {
+ bundleVerifiers.add(v);
+ }
+
+ void addInstallVerifier(Consumer<JPackageCommand> v) {
+ installVerifiers.add(v);
+ }
+
+ void addUninstallVerifier(Consumer<JPackageCommand> v) {
+ uninstallVerifiers.add(v);
+ }
+
+ @Override
+ public void accept(JPackageCommand cmd) {
+ type.applyTo(cmd);
+
+ initializers.stream().forEach(v -> v.accept(cmd));
+ switch (action) {
+ case CREATE:
+ Executor.Result result = cmd.execute();
+ result.assertExitCodeIs(expectedJPackageExitCode);
+ if (expectedJPackageExitCode == 0) {
+ TKit.assertFileExists(cmd.outputBundle());
+ } else {
+ TKit.assertPathExists(cmd.outputBundle(), false);
+ }
+ verifyPackageBundle(cmd.createImmutableCopy(), result);
+ break;
+
+ case VERIFY_INSTALL:
+ if (expectedJPackageExitCode == 0) {
+ verifyPackageInstalled(cmd.createImmutableCopy());
+ }
+ break;
+
+ case VERIFY_UNINSTALL:
+ if (expectedJPackageExitCode == 0) {
+ verifyPackageUninstalled(cmd.createImmutableCopy());
+ }
+ break;
+ }
+ }
+
+ private void verifyPackageBundle(JPackageCommand cmd,
+ Executor.Result result) {
+ if (expectedJPackageExitCode == 0) {
+ if (PackageType.LINUX.contains(cmd.packageType())) {
+ LinuxHelper.verifyPackageBundleEssential(cmd);
+ }
+ }
+ bundleVerifiers.stream().forEach(v -> v.accept(cmd, result));
+ }
+
+ private void verifyPackageInstalled(JPackageCommand cmd) {
+ TKit.trace(String.format("Verify installed: %s",
+ cmd.getPrintableCommandLine()));
+ if (cmd.isRuntime()) {
+ TKit.assertPathExists(
+ cmd.appRuntimeInstallationDirectory(), false);
+ } else {
+ TKit.assertExecutableFileExists(cmd.launcherInstallationPath());
+ }
+
+ if (PackageType.WINDOWS.contains(cmd.packageType())) {
+ new WindowsHelper.AppVerifier(cmd);
+ }
+
+ installVerifiers.stream().forEach(v -> v.accept(cmd));
+ }
+
+ private void verifyPackageUninstalled(JPackageCommand cmd) {
+ TKit.trace(String.format("Verify uninstalled: %s",
+ cmd.getPrintableCommandLine()));
+ if (!cmd.isRuntime()) {
+ TKit.assertPathExists(cmd.launcherInstallationPath(), false);
+ TKit.assertPathExists(cmd.appInstallationDirectory(), false);
+ }
+
+ if (PackageType.WINDOWS.contains(cmd.packageType())) {
+ new WindowsHelper.AppVerifier(cmd);
+ }
+
+ uninstallVerifiers.stream().forEach(v -> v.accept(cmd));
+ }
+
+ private final PackageType type;
+ private final List<Consumer<JPackageCommand>> initializers;
+ private final List<BiConsumer<JPackageCommand, Executor.Result>> bundleVerifiers;
+ private final List<Consumer<JPackageCommand>> installVerifiers;
+ private final List<Consumer<JPackageCommand>> uninstallVerifiers;
+ }
+
+ private Collection<PackageType> currentTypes;
+ private int expectedJPackageExitCode;
+ private Map<PackageType, Handler> handlers;
+ private Set<String> namedInitializers;
+ private Action action;
+
+ /**
+ * Test action.
+ */
+ static public enum Action {
+ /**
+ * Create bundle.
+ */
+ CREATE,
+ /**
+ * Verify bundle installed.
+ */
+ VERIFY_INSTALL,
+ /**
+ * Verify bundle uninstalled.
+ */
+ VERIFY_UNINSTALL;
+
+ @Override
+ public String toString() {
+ return name().toLowerCase().replace('_', '-');
+ }
+ };
+ private final static Action DEFAULT_ACTION;
+ private final static File bundleOutputDir;
+
+ static {
+ final String propertyName = "output";
+ String val = TKit.getConfigProperty(propertyName);
+ if (val == null) {
+ bundleOutputDir = null;
+ } else {
+ bundleOutputDir = new File(val).getAbsoluteFile();
+
+ if (!bundleOutputDir.isDirectory()) {
+ throw new IllegalArgumentException(String.format("Invalid value of %s sytem property: [%s]. Should be existing directory",
+ TKit.getConfigPropertyName(propertyName),
+ bundleOutputDir));
+ }
+ }
+ }
+
+ static {
+ final String propertyName = "action";
+ String action = Optional.ofNullable(TKit.getConfigProperty(propertyName)).orElse(
+ Action.CREATE.toString()).toLowerCase();
+ DEFAULT_ACTION = Stream.of(Action.values()).filter(
+ a -> a.toString().equals(action)).findFirst().orElseThrow(() -> new IllegalArgumentException(String.format("Unrecognized value of %s property: [%s]",
+ TKit.getConfigPropertyName(propertyName), action)));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageType.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,125 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * jpackage package type traits.
+ */
+public enum PackageType {
+ WIN_MSI(".msi",
+ TKit.isWindows() ? "jdk.jpackage.internal.WinMsiBundler" : null),
+ WIN_EXE(".exe",
+ TKit.isWindows() ? "jdk.jpackage.internal.WinMsiBundler" : null),
+ LINUX_DEB(".deb",
+ TKit.isLinux() ? "jdk.jpackage.internal.LinuxDebBundler" : null),
+ LINUX_RPM(".rpm",
+ TKit.isLinux() ? "jdk.jpackage.internal.LinuxRpmBundler" : null),
+ MAC_DMG(".dmg", TKit.isOSX() ? "jdk.jpackage.internal.MacDmgBundler" : null),
+ MAC_PKG(".pkg", TKit.isOSX() ? "jdk.jpackage.internal.MacPkgBundler" : null),
+ IMAGE("app-image", null, null);
+
+ PackageType(String packageName, String bundleSuffix, String bundlerClass) {
+ name = packageName;
+ suffix = bundleSuffix;
+ if (bundlerClass != null && !Inner.DISABLED_PACKAGERS.contains(getName())) {
+ supported = isBundlerSupported(bundlerClass);
+ } else {
+ supported = false;
+ }
+
+ if (suffix != null && supported) {
+ TKit.trace(String.format("Bundler %s supported", getName()));
+ }
+ }
+
+ PackageType(String bundleSuffix, String bundlerClass) {
+ this(bundleSuffix.substring(1), bundleSuffix, bundlerClass);
+ }
+
+ void applyTo(JPackageCommand cmd) {
+ cmd.addArguments("--package-type", getName());
+ }
+
+ String getSuffix() {
+ return suffix;
+ }
+
+ boolean isSupported() {
+ return supported;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ static PackageType fromSuffix(String packageFilename) {
+ if (packageFilename != null) {
+ for (PackageType v : values()) {
+ if (packageFilename.endsWith(v.getSuffix())) {
+ return v;
+ }
+ }
+ }
+ return null;
+ }
+
+ private static boolean isBundlerSupported(String bundlerClass) {
+ try {
+ Class clazz = Class.forName(bundlerClass);
+ Method supported = clazz.getMethod("supported", boolean.class);
+ return ((Boolean) supported.invoke(
+ clazz.getConstructor().newInstance(), true));
+ } catch (ClassNotFoundException | IllegalAccessException ex) {
+ } catch (InstantiationException | NoSuchMethodException
+ | InvocationTargetException ex) {
+ Functional.rethrowUnchecked(ex);
+ }
+ return false;
+ }
+
+ private final String name;
+ private final String suffix;
+ private final boolean supported;
+
+ public final static Set<PackageType> LINUX = Set.of(LINUX_DEB, LINUX_RPM);
+ public final static Set<PackageType> WINDOWS = Set.of(WIN_EXE, WIN_MSI);
+ public final static Set<PackageType> MAC = Set.of(MAC_PKG, MAC_DMG);
+ public final static Set<PackageType> NATIVE = Stream.concat(
+ Stream.concat(LINUX.stream(), WINDOWS.stream()),
+ MAC.stream()).collect(Collectors.toUnmodifiableSet());
+
+ private final static class Inner {
+
+ private final static Set<String> DISABLED_PACKAGERS = Stream.of(
+ Optional.ofNullable(
+ TKit.getConfigProperty("disabledPackagers")).orElse(
+ "").split(",")).collect(Collectors.toUnmodifiableSet());
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,652 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardWatchEventKinds;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Functional.ExceptionBox;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Functional.ThrowingFunction;
+import jdk.jpackage.test.Functional.ThrowingRunnable;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
+
+final public class TKit {
+
+ public static final Path TEST_SRC_ROOT = Functional.identity(() -> {
+ Path root = Path.of(System.getProperty("test.src"));
+
+ for (int i = 0; i != 10; ++i) {
+ if (root.resolve("apps").toFile().isDirectory()) {
+ return root.toAbsolutePath();
+ }
+ root = root.resolve("..");
+ }
+
+ throw new RuntimeException("Failed to locate apps directory");
+ }).get();
+
+ public static void run(String args[], ThrowingRunnable testBody) {
+ if (currentTest != null) {
+ throw new IllegalStateException(
+ "Unexpeced nested or concurrent Test.run() call");
+ }
+
+ TestInstance test = new TestInstance(testBody);
+ ThrowingRunnable.toRunnable(() -> runTests(List.of(test))).run();
+ if (!test.passed()) {
+ throw new RuntimeException();
+ }
+ }
+
+ static void runTests(List<TestInstance> tests) {
+ if (currentTest != null) {
+ throw new IllegalStateException(
+ "Unexpeced nested or concurrent Test.run() call");
+ }
+
+ try (PrintStream logStream = openLogStream()) {
+ extraLogStream = logStream;
+ tests.stream().forEach(test -> {
+ currentTest = test;
+ try {
+ ignoreExceptions(test).run();
+ } finally {
+ currentTest = null;
+ if (extraLogStream != null) {
+ extraLogStream.flush();
+ }
+ }
+ });
+ } finally {
+ extraLogStream = null;
+ }
+ }
+
+ static Runnable ignoreExceptions(ThrowingRunnable action) {
+ return () -> {
+ try {
+ try {
+ action.run();
+ } catch (Throwable ex) {
+ unbox(ex);
+ }
+ } catch (Throwable throwable) {
+ if (extraLogStream != null) {
+ throwable.printStackTrace(extraLogStream);
+ }
+ throwable.printStackTrace();
+ }
+ };
+ }
+
+ static void unbox(Throwable throwable) throws Throwable {
+ try {
+ throw throwable;
+ } catch (ExceptionBox | InvocationTargetException ex) {
+ unbox(ex.getCause());
+ }
+ }
+
+ public static Path workDir() {
+ Path result = Path.of(".");
+ String testFunctionName = currentTest.functionName();
+ if (testFunctionName != null) {
+ result = result.resolve(testFunctionName);
+ }
+ return result;
+ }
+
+ static Path defaultInputDir() {
+ return workDir().resolve("input");
+ }
+
+ static Path defaultOutputDir() {
+ return workDir().resolve("output");
+ }
+
+ static String getCurrentDefaultAppName() {
+ // Construct app name from swapping and joining test base name
+ // and test function name.
+ // Say the test name is `FooTest.testBasic`. Then app name would be `BasicFooTest`.
+ String appNamePrefix = currentTest.functionName();
+ if (appNamePrefix != null && appNamePrefix.startsWith("test")) {
+ appNamePrefix = appNamePrefix.substring("test".length());
+ }
+ return Stream.of(appNamePrefix, currentTest.baseName()).filter(
+ v -> v != null && !v.isEmpty()).collect(Collectors.joining());
+ }
+
+ public static boolean isWindows() {
+ return (OS.contains("win"));
+ }
+
+ public static boolean isOSX() {
+ return (OS.contains("mac"));
+ }
+
+ public static boolean isLinux() {
+ return ((OS.contains("nix") || OS.contains("nux")));
+ }
+
+ static void log(String v) {
+ System.out.println(v);
+ if (extraLogStream != null) {
+ extraLogStream.println(v);
+ }
+ }
+
+ public static void createTextFile(Path propsFilename, Collection<String> lines) {
+ trace(String.format("Create [%s] text file...",
+ propsFilename.toAbsolutePath().normalize()));
+ ThrowingRunnable.toRunnable(() -> Files.write(propsFilename,
+ lines.stream().peek(TKit::trace).collect(Collectors.toList()))).run();
+ trace("Done");
+ }
+
+ public static void createPropertiesFile(Path propsFilename,
+ Collection<Map.Entry<String, String>> props) {
+ trace(String.format("Create [%s] properties file...",
+ propsFilename.toAbsolutePath().normalize()));
+ ThrowingRunnable.toRunnable(() -> Files.write(propsFilename,
+ props.stream().map(e -> String.join("=", e.getKey(),
+ e.getValue())).peek(TKit::trace).collect(Collectors.toList()))).run();
+ trace("Done");
+ }
+
+ public static void createPropertiesFile(Path propsFilename,
+ Map.Entry<String, String>... props) {
+ createPropertiesFile(propsFilename, List.of(props));
+ }
+
+ public static void createPropertiesFile(Path propsFilename,
+ Map<String, String> props) {
+ createPropertiesFile(propsFilename, props.entrySet());
+ }
+
+ public static void trace(String v) {
+ if (TRACE) {
+ log("TRACE: " + v);
+ }
+ }
+
+ private static void traceAssert(String v) {
+ if (TRACE_ASSERTS) {
+ log("TRACE: " + v);
+ }
+ }
+
+ public static void error(String v) {
+ log("ERROR: " + v);
+ throw new AssertionError(v);
+ }
+
+ private final static String TEMP_FILE_PREFIX = null;
+
+ private static Path createUniqueFileName(String defaultName) {
+ final String[] nameComponents;
+
+ int separatorIdx = defaultName.lastIndexOf('.');
+ final String baseName;
+ if (separatorIdx == -1) {
+ baseName = defaultName;
+ nameComponents = new String[]{baseName};
+ } else {
+ baseName = defaultName.substring(0, separatorIdx);
+ nameComponents = new String[]{baseName, defaultName.substring(
+ separatorIdx + 1)};
+ }
+
+ final Path basedir = workDir();
+ int i = 0;
+ for (; i < 100; ++i) {
+ Path path = basedir.resolve(String.join(".", nameComponents));
+ if (!path.toFile().exists()) {
+ return path;
+ }
+ nameComponents[0] = String.format("%s.%d", baseName, i);
+ }
+ throw new IllegalStateException(String.format(
+ "Failed to create unique file name from [%s] basename after %d attempts",
+ baseName, i));
+ }
+
+ public static Path createTempDirectory(String role) throws IOException {
+ if (role == null) {
+ return Files.createTempDirectory(workDir(), TEMP_FILE_PREFIX);
+ }
+ return Files.createDirectory(createUniqueFileName(role));
+ }
+
+ public static Path createTempFile(String role, String suffix) throws
+ IOException {
+ if (role == null) {
+ return Files.createTempFile(workDir(), TEMP_FILE_PREFIX, suffix);
+ }
+ return Files.createFile(createUniqueFileName(role));
+ }
+
+ public static void withTempFile(String role, String suffix,
+ ThrowingConsumer<Path> action) {
+ final Path tempFile = ThrowingSupplier.toSupplier(() -> createTempFile(
+ role, suffix)).get();
+ boolean keepIt = true;
+ try {
+ ThrowingConsumer.toConsumer(action).accept(tempFile);
+ keepIt = false;
+ } finally {
+ if (tempFile != null && !keepIt) {
+ ThrowingRunnable.toRunnable(() -> Files.deleteIfExists(tempFile)).run();
+ }
+ }
+ }
+
+ public static void withTempDirectory(String role,
+ ThrowingConsumer<Path> action) {
+ final Path tempDir = ThrowingSupplier.toSupplier(
+ () -> createTempDirectory(role)).get();
+ boolean keepIt = true;
+ try {
+ ThrowingConsumer.toConsumer(action).accept(tempDir);
+ keepIt = false;
+ } finally {
+ if (tempDir != null && tempDir.toFile().isDirectory() && !keepIt) {
+ deleteDirectoryRecursive(tempDir, "");
+ }
+ }
+ }
+
+ /**
+ * Deletes contents of the given directory recursively. Shortcut for
+ * <code>deleteDirectoryContentsRecursive(path, null)</code>
+ *
+ * @param path path to directory to clean
+ */
+ public static void deleteDirectoryContentsRecursive(Path path) {
+ deleteDirectoryContentsRecursive(path, null);
+ }
+
+ /**
+ * Deletes contents of the given directory recursively. If <code>path<code> is not a
+ * directory, request is silently ignored.
+ *
+ * @param path path to directory to clean
+ * @param msg log message. If null, the default log message is used. If
+ * empty string, no log message will be saved.
+ */
+ public static void deleteDirectoryContentsRecursive(Path path, String msg) {
+ if (path.toFile().isDirectory()) {
+ if (msg == null) {
+ msg = String.format("Cleaning [%s] directory recursively", path);
+ }
+
+ if (!msg.isEmpty()) {
+ TKit.trace(msg);
+ }
+
+ // Walk all children of `path` in sorted order to hit files first
+ // and directories last and delete each item.
+ ThrowingRunnable.toRunnable(() -> Stream.of(
+ path.toFile().listFiles()).map(File::toPath).map(
+ ThrowingFunction.toFunction(Files::walk)).flatMap(x -> x).sorted(
+ Comparator.reverseOrder()).map(Path::toFile).forEach(
+ File::delete)).run();
+ }
+ }
+
+ /**
+ * Deletes the given directory recursively. Shortcut for
+ * <code>deleteDirectoryRecursive(path, null)</code>
+ *
+ * @param path path to directory to delete
+ */
+ public static void deleteDirectoryRecursive(Path path) {
+ deleteDirectoryRecursive(path, null);
+ }
+
+ /**
+ * Deletes the given directory recursively. If <code>path<code> is not a
+ * directory, request is silently ignored.
+ *
+ * @param path path to directory to delete
+ * @param msg log message. If null, the default log message is used. If
+ * empty string, no log message will be saved.
+ */
+ public static void deleteDirectoryRecursive(Path path, String msg) {
+ if (path.toFile().isDirectory()) {
+ if (msg == null) {
+ msg = String.format("Deleting [%s] directory recursively", path);
+ }
+ deleteDirectoryContentsRecursive(path, msg);
+ ThrowingConsumer.toConsumer(Files::delete).accept(path);
+ }
+ }
+
+ public static RuntimeException throwUnknownPlatformError() {
+ if (isWindows() || isLinux() || isOSX()) {
+ throw new IllegalStateException(
+ "Platform is known. throwUnknownPlatformError() called by mistake");
+ }
+ throw new IllegalStateException("Unknown platform");
+ }
+
+ static void waitForFileCreated(Path fileToWaitFor,
+ long timeoutSeconds) throws IOException {
+
+ trace(String.format("Wait for file [%s] to be available", fileToWaitFor));
+
+ WatchService ws = FileSystems.getDefault().newWatchService();
+
+ Path watchDirectory = fileToWaitFor.toAbsolutePath().getParent();
+ watchDirectory.register(ws, ENTRY_CREATE, ENTRY_MODIFY);
+
+ long waitUntil = System.currentTimeMillis() + timeoutSeconds * 1000;
+ for (;;) {
+ long timeout = waitUntil - System.currentTimeMillis();
+ assertTrue(timeout > 0, String.format(
+ "Check timeout value %d is positive", timeout));
+
+ WatchKey key = ThrowingSupplier.toSupplier(() -> ws.poll(timeout,
+ TimeUnit.MILLISECONDS)).get();
+ if (key == null) {
+ if (fileToWaitFor.toFile().exists()) {
+ trace(String.format(
+ "File [%s] is available after poll timeout expired",
+ fileToWaitFor));
+ return;
+ }
+ assertUnexpected(String.format("Timeout expired", timeout));
+ }
+
+ for (WatchEvent<?> event : key.pollEvents()) {
+ if (event.kind() == StandardWatchEventKinds.OVERFLOW) {
+ continue;
+ }
+ Path contextPath = (Path) event.context();
+ if (Files.isSameFile(watchDirectory.resolve(contextPath),
+ fileToWaitFor)) {
+ trace(String.format("File [%s] is available", fileToWaitFor));
+ return;
+ }
+ }
+
+ if (!key.reset()) {
+ assertUnexpected("Watch key invalidated");
+ }
+ }
+ }
+
+ private static String concatMessages(String msg, String msg2) {
+ if (msg2 != null && !msg2.isBlank()) {
+ return msg + ": " + msg2;
+ }
+ return msg;
+ }
+
+ public static void assertEquals(long expected, long actual, String msg) {
+ currentTest.notifyAssert();
+ if (expected != actual) {
+ error(concatMessages(String.format(
+ "Expected [%d]. Actual [%d]", expected, actual),
+ msg));
+ }
+
+ traceAssert(String.format("assertEquals(%d): %s", expected, msg));
+ }
+
+ public static void assertNotEquals(long expected, long actual, String msg) {
+ currentTest.notifyAssert();
+ if (expected == actual) {
+ error(concatMessages(String.format("Unexpected [%d] value", actual),
+ msg));
+ }
+
+ traceAssert(String.format("assertNotEquals(%d, %d): %s", expected,
+ actual, msg));
+ }
+
+ public static void assertEquals(String expected, String actual, String msg) {
+ currentTest.notifyAssert();
+ if ((actual != null && !actual.equals(expected))
+ || (expected != null && !expected.equals(actual))) {
+ error(concatMessages(String.format(
+ "Expected [%s]. Actual [%s]", expected, actual),
+ msg));
+ }
+
+ traceAssert(String.format("assertEquals(%s): %s", expected, msg));
+ }
+
+ public static void assertNotEquals(String expected, String actual, String msg) {
+ currentTest.notifyAssert();
+ if ((actual != null && !actual.equals(expected))
+ || (expected != null && !expected.equals(actual))) {
+
+ traceAssert(String.format("assertNotEquals(%s, %s): %s", expected,
+ actual, msg));
+ return;
+ }
+
+ error(concatMessages(String.format("Unexpected [%s] value", actual), msg));
+ }
+
+ public static void assertNull(Object value, String msg) {
+ currentTest.notifyAssert();
+ if (value != null) {
+ error(concatMessages(String.format("Unexpected not null value [%s]",
+ value), msg));
+ }
+
+ traceAssert(String.format("assertNull(): %s", msg));
+ }
+
+ public static void assertNotNull(Object value, String msg) {
+ currentTest.notifyAssert();
+ if (value == null) {
+ error(concatMessages("Unexpected null value", msg));
+ }
+
+ traceAssert(String.format("assertNotNull(%s): %s", value, msg));
+ }
+
+ public static void assertTrue(boolean actual, String msg) {
+ currentTest.notifyAssert();
+ if (!actual) {
+ error(concatMessages("Unexpected FALSE", msg));
+ }
+
+ traceAssert(String.format("assertTrue(): %s", msg));
+ }
+
+ public static void assertFalse(boolean actual, String msg) {
+ currentTest.notifyAssert();
+ if (actual) {
+ error(concatMessages("Unexpected TRUE", msg));
+ }
+
+ traceAssert(String.format("assertFalse(): %s", msg));
+ }
+
+ public static void assertPathExists(Path path, boolean exists) {
+ if (exists) {
+ assertTrue(path.toFile().exists(), String.format(
+ "Check [%s] path exists", path));
+ } else {
+ assertFalse(path.toFile().exists(), String.format(
+ "Check [%s] path doesn't exist", path));
+ }
+ }
+
+ public static void assertDirectoryExists(Path path) {
+ assertPathExists(path, true);
+ assertTrue(path.toFile().isDirectory(), String.format(
+ "Check [%s] is a directory", path));
+ }
+
+ public static void assertFileExists(Path path) {
+ assertPathExists(path, true);
+ assertTrue(path.toFile().isFile(), String.format("Check [%s] is a file",
+ path));
+ }
+
+ public static void assertExecutableFileExists(Path path) {
+ assertFileExists(path);
+ assertTrue(path.toFile().canExecute(), String.format(
+ "Check [%s] file is executable", path));
+ }
+
+ public static void assertReadableFileExists(Path path) {
+ assertFileExists(path);
+ assertTrue(path.toFile().canRead(), String.format(
+ "Check [%s] file is readable", path));
+ }
+
+ public static void assertUnexpected(String msg) {
+ currentTest.notifyAssert();
+ error(concatMessages("Unexpected", msg));
+ }
+
+ public static void assertStringListEquals(List<String> expected,
+ List<String> actual, String msg) {
+ currentTest.notifyAssert();
+
+ traceAssert(String.format("assertStringListEquals(): %s", msg));
+
+ String idxFieldFormat = Functional.identity(() -> {
+ int listSize = expected.size();
+ int width = 0;
+ while (listSize != 0) {
+ listSize = listSize / 10;
+ width++;
+ }
+ return "%" + width + "d";
+ }).get();
+
+ AtomicInteger counter = new AtomicInteger(0);
+ Iterator<String> actualIt = actual.iterator();
+ expected.stream().sequential().filter(expectedStr -> actualIt.hasNext()).forEach(expectedStr -> {
+ int idx = counter.incrementAndGet();
+ String actualStr = actualIt.next();
+
+ if ((actualStr != null && !actualStr.equals(expectedStr))
+ || (expectedStr != null && !expectedStr.equals(actualStr))) {
+ error(concatMessages(String.format(
+ "(" + idxFieldFormat + ") Expected [%s]. Actual [%s]",
+ idx, expectedStr, actualStr), msg));
+ }
+
+ traceAssert(String.format(
+ "assertStringListEquals(" + idxFieldFormat + ", %s)", idx,
+ expectedStr));
+ });
+
+ if (expected.size() < actual.size()) {
+ // Actual string list is longer than expected
+ error(concatMessages(String.format(
+ "Actual list is longer than expected by %d elements",
+ actual.size() - expected.size()), msg));
+ }
+
+ if (actual.size() < expected.size()) {
+ // Actual string list is shorter than expected
+ error(concatMessages(String.format(
+ "Actual list is longer than expected by %d elements",
+ expected.size() - actual.size()), msg));
+ }
+ }
+
+ private static PrintStream openLogStream() {
+ if (LOG_FILE == null) {
+ return null;
+ }
+
+ return ThrowingSupplier.toSupplier(() -> new PrintStream(
+ new FileOutputStream(LOG_FILE.toFile(), true))).get();
+ }
+
+ private static TestInstance currentTest;
+ private static PrintStream extraLogStream;
+
+ private static final boolean TRACE;
+ private static final boolean TRACE_ASSERTS;
+
+ static final boolean VERBOSE_JPACKAGE;
+ static final boolean VERBOSE_TEST_SETUP;
+
+ static String getConfigProperty(String propertyName) {
+ return System.getProperty(getConfigPropertyName(propertyName));
+ }
+
+ static String getConfigPropertyName(String propertyName) {
+ return "jpackage.test." + propertyName;
+ }
+
+ static final Path LOG_FILE = Functional.identity(() -> {
+ String val = getConfigProperty("logfile");
+ if (val == null) {
+ return null;
+ }
+ return Path.of(val);
+ }).get();
+
+ static {
+ String val = getConfigProperty("suppress-logging");
+ if (val == null) {
+ TRACE = true;
+ TRACE_ASSERTS = true;
+ VERBOSE_JPACKAGE = true;
+ VERBOSE_TEST_SETUP = true;
+ } else if ("all".equals(val.toLowerCase())) {
+ TRACE = false;
+ TRACE_ASSERTS = false;
+ VERBOSE_JPACKAGE = false;
+ VERBOSE_TEST_SETUP = false;
+ } else {
+ Set<String> logOptions = Set.of(val.toLowerCase().split(","));
+ TRACE = !(logOptions.contains("trace") || logOptions.contains("t"));
+ TRACE_ASSERTS = !(logOptions.contains("assert") || logOptions.contains(
+ "a"));
+ VERBOSE_JPACKAGE = !(logOptions.contains("jpackage") || logOptions.contains(
+ "jp"));
+ VERBOSE_TEST_SETUP = !logOptions.contains("init");
+ }
+ }
+
+ private static final String OS = System.getProperty("os.name").toLowerCase();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestBuilder.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,344 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Annotations.Parameter;
+import jdk.jpackage.test.Annotations.Parameters;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Functional.ThrowingFunction;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
+
+final class TestBuilder implements AutoCloseable {
+
+ @Override
+ public void close() throws Exception {
+ flushTestGroup(null);
+ }
+
+ TestBuilder(Consumer<TestInstance> testConsumer) {
+ argProcessors = Map.of(CMDLINE_ARG_PREFIX + "after-run",
+ arg -> getJavaMethodsFromArg(arg).forEach(
+ (method) -> afterActions.add(wrap(method, dryRun))),
+ CMDLINE_ARG_PREFIX + "before-run",
+ arg -> getJavaMethodsFromArg(arg).forEach(
+ (method) -> beforeActions.add(wrap(method, dryRun))),
+ CMDLINE_ARG_PREFIX + "run",
+ arg -> flushTestGroup(getJavaMethodsFromArg(arg).map(
+ TestBuilder::toMethodCalls).flatMap(List::stream).collect(
+ Collectors.toList())),
+ CMDLINE_ARG_PREFIX + "dry-run",
+ arg -> dryRun = true);
+ this.testConsumer = testConsumer;
+ clear();
+ }
+
+ void processCmdLineArg(String arg) throws Throwable {
+ int separatorIdx = arg.indexOf('=');
+ final String argName;
+ final String argValue;
+ if (separatorIdx != -1) {
+ argName = arg.substring(0, separatorIdx);
+ argValue = arg.substring(separatorIdx + 1);
+ } else {
+ argName = arg;
+ argValue = null;
+ }
+ try {
+ ThrowingConsumer<String> argProcessor = argProcessors.get(argName);
+ if (argProcessor == null) {
+ throw new ParseException("Unrecognized");
+ }
+ argProcessor.accept(argValue);
+ } catch (ParseException ex) {
+ ex.setContext(arg);
+ throw ex;
+ }
+ }
+
+ private void flushTestGroup(List<MethodCall> newTestGroup) {
+ if (testGroup != null) {
+ testGroup.forEach(testBody -> createTestInstance(testBody));
+ clear();
+ }
+ testGroup = newTestGroup;
+ }
+
+ private void createTestInstance(MethodCall testBody) {
+ ThrowingFunction<MethodCall, Object> testContructor;
+ if (dryRun) {
+ testContructor = (unused) -> null;
+ testBody = DRY_RUN_TEST_BODY;
+ } else {
+ testContructor = TestBuilder::constructTest;
+ }
+
+ TestInstance test = new TestInstance(testContructor, testBody,
+ beforeActions, afterActions);
+ trace(String.format("[%s] test constructed", test.fullName()));
+ testConsumer.accept(test);
+ }
+
+ public static void nop () {
+ }
+
+ private final static MethodCall DRY_RUN_TEST_BODY = ThrowingSupplier.toSupplier(() -> {
+ return new MethodCall(TestBuilder.class.getMethod("nop"));
+ }).get();
+
+ private static Object constructTest(MethodCall testBody) throws
+ NoSuchMethodException, InstantiationException,
+ IllegalAccessException, IllegalArgumentException,
+ InvocationTargetException {
+ Constructor ctor = testBody.getRequiredConstructor();
+ if (ctor == null) {
+ return null;
+ }
+ return ctor.newInstance();
+ }
+
+ private void clear() {
+ beforeActions = new ArrayList<>();
+ afterActions = new ArrayList<>();
+ testGroup = null;
+ }
+
+ private static Class probeClass(String name) {
+ try {
+ return Class.forName(name);
+ } catch (ClassNotFoundException ex) {
+ return null;
+ }
+ }
+
+ private static Stream<String> cmdLineArgValueToMethodNames(String v) {
+ List<String> result = new ArrayList<>();
+ String defaultClassName = null;
+ for (String token : v.split(",")) {
+ Class testSet = probeClass(token);
+ if (testSet != null) {
+ // Test set class specified. Pull in all public methods
+ // from the class with @Test annotation removing name duplicates.
+ // Overloads will be handled at the next phase of processing.
+ defaultClassName = token;
+ Stream.of(testSet.getMethods()).filter(
+ m -> m.isAnnotationPresent(Test.class)).map(
+ Method::getName).distinct().forEach(
+ name -> result.add(String.join(".", token, name)));
+ continue;
+ }
+
+ final String qualifiedMethodName;
+ final int lastDotIdx = token.lastIndexOf('.');
+ if (lastDotIdx != -1) {
+ qualifiedMethodName = token;
+ defaultClassName = token.substring(0, lastDotIdx);
+ } else if (defaultClassName == null) {
+ throw new ParseException("Default class name not found in");
+ } else {
+ qualifiedMethodName = String.join(".", defaultClassName, token);
+ }
+ result.add(qualifiedMethodName);
+ }
+ return result.stream();
+ }
+
+ private static boolean filterMethod(String expectedMethodName, Method method) {
+ if (!method.getName().equals(expectedMethodName)) {
+ return false;
+ }
+ switch (method.getParameterCount()) {
+ case 0:
+ return !isParametrized(method);
+ case 1:
+ return isParametrized(method);
+ }
+ return false;
+ }
+
+ private static boolean isParametrized(Method method) {
+ return method.isAnnotationPresent(Parameters.class) || method.isAnnotationPresent(
+ Parameter.class);
+ }
+
+ private static List<Method> getJavaMethodFromString(
+ String qualifiedMethodName) {
+ int lastDotIdx = qualifiedMethodName.lastIndexOf('.');
+ if (lastDotIdx == -1) {
+ throw new ParseException("Class name not found in");
+ }
+ String className = qualifiedMethodName.substring(0, lastDotIdx);
+ String methodName = qualifiedMethodName.substring(lastDotIdx + 1);
+ Class methodClass;
+ try {
+ methodClass = Class.forName(className);
+ } catch (ClassNotFoundException ex) {
+ throw new ParseException(String.format("Class [%s] not found;",
+ className));
+ }
+ // Get the list of all public methods as need to deal with overloads.
+ List<Method> methods = Stream.of(methodClass.getMethods()).filter(
+ (m) -> filterMethod(methodName, m)).collect(Collectors.toList());
+ if (methods.isEmpty()) {
+ new ParseException(String.format(
+ "Method [%s] not found in [%s] class;",
+ methodName, className));
+ }
+ // Make sure default constructor is accessible if the one is needed.
+ // Need to probe all methods as some of them might be static and
+ // some class members.
+ // Onlu class members require default ctor.
+ for (Method method : methods) {
+ try {
+ MethodCall.getRequiredConstructor(method);
+ } catch (NoSuchMethodException ex) {
+ throw new ParseException(String.format(
+ "Default constructor not found in [%s] class;",
+ className));
+ }
+ }
+
+ trace(String.format("%s -> %s", qualifiedMethodName, methods));
+ return methods;
+ }
+
+ private static Stream<Method> getJavaMethodsFromArg(String argValue) {
+ return cmdLineArgValueToMethodNames(argValue).map(
+ ThrowingFunction.toFunction(
+ TestBuilder::getJavaMethodFromString)).flatMap(
+ List::stream).sequential();
+ }
+
+ private static Parameter[] getParameters(Method method) {
+ if (method.isAnnotationPresent(Parameters.class)) {
+ return ((Parameters) method.getAnnotation(Parameters.class)).value();
+ }
+
+ if (method.isAnnotationPresent(Parameter.class)) {
+ return new Parameter[]{(Parameter) method.getAnnotation(
+ Parameter.class)};
+ }
+
+ // Unexpected
+ return null;
+ }
+
+ private static List<MethodCall> toMethodCalls(Method method) {
+ if (!isParametrized(method)) {
+ return List.of(new MethodCall(method));
+ }
+ Parameter[] annotations = getParameters(method);
+ if (annotations.length == 0) {
+ return List.of(new MethodCall(method));
+ }
+ return Stream.of(annotations).map((a) -> {
+ String annotationValue = a.value();
+ Class paramClass = method.getParameterTypes()[0];
+ return new MethodCall(method,
+ fromString(annotationValue, paramClass));
+ }).collect(Collectors.toList());
+ }
+
+ private static Object fromString(String value, Class toType) {
+ Function<String, Object> converter = conv.get(toType);
+ if (converter == null) {
+ throw new RuntimeException(String.format(
+ "Failed to find a conversion of [%s] string to %s type",
+ value, toType));
+ }
+ return converter.apply(value);
+ }
+
+ // Wraps Method.invike() into ThrowingRunnable.run()
+ private static ThrowingConsumer wrap(Method method, boolean dryRun) {
+ return (test) -> {
+ Class methodClass = method.getDeclaringClass();
+ String methodName = String.join(".", methodClass.getName(),
+ method.getName());
+ TKit.log(String.format("[ CALL ] %s()", methodName));
+ if (!dryRun) {
+ if (methodClass.isInstance(test)) {
+ method.invoke(test);
+ } else {
+ method.invoke(null);
+ }
+ }
+ };
+ }
+
+ private static class ParseException extends IllegalArgumentException {
+
+ ParseException(String msg) {
+ super(msg);
+ }
+
+ void setContext(String badCmdLineArg) {
+ this.badCmdLineArg = badCmdLineArg;
+ }
+
+ @Override
+ public String getMessage() {
+ String msg = super.getMessage();
+ if (badCmdLineArg != null) {
+ msg = String.format("%s parameter=[%s]", msg, badCmdLineArg);
+ }
+ return msg;
+ }
+ private String badCmdLineArg;
+ }
+
+ private static void trace(String msg) {
+ if (TKit.VERBOSE_TEST_SETUP) {
+ TKit.log(msg);
+ }
+ }
+
+ private final Map<String, ThrowingConsumer<String>> argProcessors;
+ private Consumer<TestInstance> testConsumer;
+ private List<MethodCall> testGroup;
+ private List<ThrowingConsumer> beforeActions;
+ private List<ThrowingConsumer> afterActions;
+ private boolean dryRun;
+
+ private final static Map<Class, Function<String, Object>> conv = Map.of(
+ boolean.class, Boolean::valueOf,
+ Boolean.class, Boolean::valueOf,
+ int.class, Integer::valueOf,
+ Integer.class, Integer::valueOf,
+ long.class, Long::valueOf,
+ Long.class, Long::valueOf,
+ String.class, String::valueOf);
+
+ final static String CMDLINE_ARG_PREFIX = "--jpt-";
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestInstance.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,160 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Functional.ThrowingFunction;
+import jdk.jpackage.test.Functional.ThrowingRunnable;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
+
+
+class TestInstance implements ThrowingRunnable {
+
+ static class TestDesc {
+ private TestDesc() {
+ }
+
+ String testFullName() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(clazz.getSimpleName());
+ if (functionName != null) {
+ sb.append('.');
+ sb.append(functionName);
+ if (args != null) {
+ sb.append('(').append(args).append(')');
+ }
+ }
+ return sb.toString();
+ }
+
+ private Class clazz;
+ private String functionName;
+ private String args;
+
+ private static TestDesc create() {
+ TestDesc desc = new TestDesc();
+ desc.clazz = enclosingMainMethodClass();
+ return desc;
+ }
+
+ static TestDesc create(Method m, Object... args) {
+ TestDesc desc = new TestDesc();
+ desc.clazz = m.getDeclaringClass();
+ desc.functionName = m.getName();
+ if (args.length != 0) {
+ desc.args = Stream.of(args).map(Object::toString).collect(
+ Collectors.joining(","));
+ }
+ return desc;
+ }
+ }
+
+ TestInstance(ThrowingRunnable testBody) {
+ assertCount = 0;
+ this.testConstructor = (unused) -> null;
+ this.testBody = (unused) -> testBody.run();
+ this.beforeActions = Collections.emptyList();
+ this.afterActions = Collections.emptyList();
+ this.testDesc = TestDesc.create();
+ }
+
+ TestInstance(ThrowingFunction testConstructor, MethodCall testBody,
+ List<ThrowingConsumer> beforeActions,
+ List<ThrowingConsumer> afterActions) {
+ assertCount = 0;
+ this.testConstructor = testConstructor;
+ this.testBody = testBody;
+ this.beforeActions = beforeActions;
+ this.afterActions = afterActions;
+ this.testDesc = testBody.createDescription();
+ }
+
+ void notifyAssert() {
+ assertCount++;
+ }
+
+ boolean passed() {
+ return success;
+ }
+
+ String functionName() {
+ return testDesc.functionName;
+ }
+
+ String baseName() {
+ return testDesc.clazz.getSimpleName();
+ }
+
+ String fullName() {
+ return testDesc.testFullName();
+ }
+
+ @Override
+ public void run() throws Throwable {
+ final String fullName = testDesc.testFullName();
+ TKit.log(String.format("[ RUN ] %s", fullName));
+ try {
+ Object testInstance = testConstructor.apply(testBody);
+ beforeActions.forEach((a) -> ThrowingConsumer.toConsumer(a).accept(
+ testInstance));
+ Files.createDirectories(TKit.workDir());
+ try {
+ testBody.accept(testInstance);
+ } finally {
+ afterActions.forEach(a -> TKit.ignoreExceptions(() -> a.accept(
+ testInstance)));
+ }
+ success = true;
+ } finally {
+ TKit.log(String.format("%s %s; checks=%d",
+ success ? "[ OK ]" : "[ FAILED ]", fullName,
+ assertCount));
+ }
+ }
+
+ private static Class enclosingMainMethodClass() {
+ StackTraceElement st[] = Thread.currentThread().getStackTrace();
+ for (StackTraceElement ste : st) {
+ if ("main".equals(ste.getMethodName())) {
+ return Functional.ThrowingSupplier.toSupplier(() -> Class.forName(
+ ste.getClassName())).get();
+ }
+ }
+ return null;
+ }
+
+ private int assertCount;
+ private boolean success;
+ private final TestDesc testDesc;
+ private final ThrowingFunction testConstructor;
+ private final ThrowingConsumer testBody;
+ private final List<ThrowingConsumer> beforeActions;
+ private final List<ThrowingConsumer> afterActions;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,253 @@
+/*
+ * 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.
+ *
+ * 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.test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class WindowsHelper {
+
+ static String getBundleName(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.WINDOWS);
+ return String.format("%s-%s%s", cmd.name(), cmd.version(),
+ cmd.packageType().getSuffix());
+ }
+
+ static Path getInstallationDirectory(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.WINDOWS);
+ Path installDir = Path.of(
+ cmd.getArgumentValue("--install-dir", () -> cmd.name()));
+ if (isUserLocalInstall(cmd)) {
+ return USER_LOCAL.resolve(installDir);
+ }
+ return PROGRAM_FILES.resolve(installDir);
+ }
+
+ private static boolean isUserLocalInstall(JPackageCommand cmd) {
+ return cmd.hasArgument("--win-per-user-install");
+ }
+
+ static class AppVerifier {
+
+ AppVerifier(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.WINDOWS);
+ this.cmd = cmd;
+ verifyStartMenuShortcut();
+ verifyDesktopShortcut();
+ verifyFileAssociationsRegistry();
+ }
+
+ private void verifyDesktopShortcut() {
+ boolean appInstalled = cmd.launcherInstallationPath().toFile().exists();
+ if (cmd.hasArgument("--win-shortcut")) {
+ if (isUserLocalInstall(cmd)) {
+ verifyUserLocalDesktopShortcut(appInstalled);
+ verifySystemDesktopShortcut(false);
+ } else {
+ verifySystemDesktopShortcut(appInstalled);
+ verifyUserLocalDesktopShortcut(false);
+ }
+ } else {
+ verifySystemDesktopShortcut(false);
+ verifyUserLocalDesktopShortcut(false);
+ }
+ }
+
+ private Path desktopShortcutPath() {
+ return Path.of(cmd.name() + ".lnk");
+ }
+
+ private void verifyShortcut(Path path, boolean exists) {
+ if (exists) {
+ TKit.assertFileExists(path);
+ } else {
+ TKit.assertPathExists(path, false);
+ }
+ }
+
+ private void verifySystemDesktopShortcut(boolean exists) {
+ Path dir = Path.of(queryRegistryValueCache(
+ SYSTEM_SHELL_FOLDERS_REGKEY, "Common Desktop"));
+ verifyShortcut(dir.resolve(desktopShortcutPath()), exists);
+ }
+
+ private void verifyUserLocalDesktopShortcut(boolean exists) {
+ Path dir = Path.of(
+ queryRegistryValueCache(USER_SHELL_FOLDERS_REGKEY, "Desktop"));
+ verifyShortcut(dir.resolve(desktopShortcutPath()), exists);
+ }
+
+ private void verifyStartMenuShortcut() {
+ boolean appInstalled = cmd.launcherInstallationPath().toFile().exists();
+ if (cmd.hasArgument("--win-menu")) {
+ if (isUserLocalInstall(cmd)) {
+ verifyUserLocalStartMenuShortcut(appInstalled);
+ verifySystemStartMenuShortcut(false);
+ } else {
+ verifySystemStartMenuShortcut(appInstalled);
+ verifyUserLocalStartMenuShortcut(false);
+ }
+ } else {
+ verifySystemStartMenuShortcut(false);
+ verifyUserLocalStartMenuShortcut(false);
+ }
+ }
+
+ private Path startMenuShortcutPath() {
+ return Path.of(cmd.getArgumentValue("--win-menu-group",
+ () -> "Unknown"), cmd.name() + ".lnk");
+ }
+
+ private void verifySystemStartMenuShortcut(boolean exists) {
+ Path dir = Path.of(queryRegistryValueCache(
+ SYSTEM_SHELL_FOLDERS_REGKEY, "Common Programs"));
+ verifyShortcut(dir.resolve(startMenuShortcutPath()), exists);
+ }
+
+ private void verifyUserLocalStartMenuShortcut(boolean exists) {
+ Path dir = Path.of(queryRegistryValueCache(
+ USER_SHELL_FOLDERS_REGKEY, "Programs"));
+ verifyShortcut(dir.resolve(startMenuShortcutPath()), exists);
+ }
+
+ private void verifyFileAssociationsRegistry() {
+ Stream.of(cmd.getAllArgumentValues("--file-associations")).map(
+ Path::of).forEach(this::verifyFileAssociationsRegistry);
+ }
+
+ private void verifyFileAssociationsRegistry(Path faFile) {
+ boolean appInstalled = cmd.launcherInstallationPath().toFile().exists();
+ try {
+ TKit.trace(String.format(
+ "Get file association properties from [%s] file",
+ faFile));
+ Map<String, String> faProps = Files.readAllLines(faFile).stream().filter(
+ line -> line.trim().startsWith("extension=") || line.trim().startsWith(
+ "mime-type=")).map(
+ line -> {
+ String[] keyValue = line.trim().split("=", 2);
+ return Map.entry(keyValue[0], keyValue[1]);
+ }).collect(Collectors.toMap(
+ entry -> entry.getKey(),
+ entry -> entry.getValue()));
+ String suffix = faProps.get("extension");
+ String contentType = faProps.get("mime-type");
+ TKit.assertNotNull(suffix, String.format(
+ "Check file association suffix [%s] is found in [%s] property file",
+ suffix, faFile));
+ TKit.assertNotNull(contentType, String.format(
+ "Check file association content type [%s] is found in [%s] property file",
+ contentType, faFile));
+ verifyFileAssociations(appInstalled, "." + suffix, contentType);
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private void verifyFileAssociations(boolean exists, String suffix,
+ String contentType) {
+ String contentTypeFromRegistry = queryRegistryValue(Path.of(
+ "HKLM\\Software\\Classes", suffix).toString(),
+ "Content Type");
+ String suffixFromRegistry = queryRegistryValue(
+ "HKLM\\Software\\Classes\\MIME\\Database\\Content Type\\" + contentType,
+ "Extension");
+
+ if (exists) {
+ TKit.assertEquals(suffix, suffixFromRegistry,
+ "Check suffix in registry is as expected");
+ TKit.assertEquals(contentType, contentTypeFromRegistry,
+ "Check content type in registry is as expected");
+ } else {
+ TKit.assertNull(suffixFromRegistry,
+ "Check suffix in registry not found");
+ TKit.assertNull(contentTypeFromRegistry,
+ "Check content type in registry not found");
+ }
+ }
+
+ private final JPackageCommand cmd;
+ }
+
+ private static String queryRegistryValue(String keyPath, String valueName) {
+ Executor.Result status = new Executor()
+ .setExecutable("reg")
+ .saveOutput()
+ .addArguments("query", keyPath, "/v", valueName)
+ .execute();
+ if (status.exitCode == 1) {
+ // Should be the case of no such registry value or key
+ String lookupString = "ERROR: The system was unable to find the specified registry key or value.";
+ status.getOutput().stream().filter(line -> line.equals(lookupString)).findFirst().orElseThrow(
+ () -> new RuntimeException(String.format(
+ "Failed to find [%s] string in the output",
+ lookupString)));
+ TKit.trace(String.format(
+ "Registry value [%s] at [%s] path not found", valueName,
+ keyPath));
+ return null;
+ }
+
+ String value = status.assertExitCodeIsZero().getOutput().stream().skip(2).findFirst().orElseThrow();
+ // Extract the last field from the following line:
+ // Common Desktop REG_SZ C:\Users\Public\Desktop
+ value = value.split(" REG_SZ ")[1];
+
+ TKit.trace(String.format("Registry value [%s] at [%s] path is [%s]",
+ valueName, keyPath, value));
+
+ return value;
+ }
+
+ private static String queryRegistryValueCache(String keyPath,
+ String valueName) {
+ String key = String.format("[%s][%s]", keyPath, valueName);
+ String value = REGISTRY_VALUES.get(key);
+ if (value == null) {
+ value = queryRegistryValue(keyPath, valueName);
+ REGISTRY_VALUES.put(key, value);
+ }
+
+ return value;
+ }
+
+ static final Set<Path> CRITICAL_RUNTIME_FILES = Set.of(Path.of(
+ "bin\\server\\jvm.dll"));
+
+ // jtreg resets %ProgramFiles% environment variable by some reason.
+ private final static Path PROGRAM_FILES = Path.of(Optional.ofNullable(
+ System.getenv("ProgramFiles")).orElse("C:\\Program Files"));
+
+ private final static Path USER_LOCAL = Path.of(System.getProperty(
+ "user.home"),
+ "AppData", "Local");
+
+ private final static String SYSTEM_SHELL_FOLDERS_REGKEY = "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
+ private final static String USER_SHELL_FOLDERS_REGKEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders";
+
+ private static final Map<String, String> REGISTRY_VALUES = new HashMap<>();
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/AppImageFileTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,146 @@
+/*
+ * 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.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.LinkedHashMap;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+
+public class AppImageFileTest {
+
+ @Rule
+ public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+ @Test
+ public void testIdentity() throws IOException {
+ Map<String, ? super Object> params = new LinkedHashMap<>();
+ params.put("name", "Foo");
+ params.put("app-version", "2.3");
+ params.put("description", "Duck is the King");
+ AppImageFile aif = create(params);
+
+ Assert.assertEquals("Foo", aif.getLauncherName());
+ }
+
+ @Test
+ public void testInvalidCommandLine() throws IOException {
+ // Just make sure AppImageFile will tolerate jpackage params that would
+ // never create app image at both load/save phases.
+ // People would edit this file just because they can.
+ // We should be ready to handle curious minds.
+ Map<String, ? super Object> params = new LinkedHashMap<>();
+ params.put("invalidParamName", "randomStringValue");
+ create(params);
+
+ params = new LinkedHashMap<>();
+ params.put("name", "foo");
+ params.put("app-version", "");
+ create(params);
+ }
+
+ @Test
+ public void testInavlidXml() throws IOException {
+ assertInvalid(createFromXml("<foo/>"));
+ assertInvalid(createFromXml("<jpackage-state/>"));
+ assertInvalid(createFromXml(
+ "<jpackage-state>",
+ "<main-launcher></main-launcher>",
+ "</jpackage-state>"));
+ }
+
+ @Test
+ public void testValidXml() throws IOException {
+ Assert.assertEquals("Foo", (createFromXml(
+ "<jpackage-state>",
+ "<main-launcher>Foo</main-launcher>",
+ "</jpackage-state>")).getLauncherName());
+
+ Assert.assertEquals("Foo", (createFromXml(
+ "<jpackage-state>",
+ "<main-launcher>Foo</main-launcher>",
+ "<main-launcher>Bar</main-launcher>",
+ "</jpackage-state>")).getLauncherName());
+
+ var file = createFromXml(
+ "<jpackage-state>",
+ "<main-launcher>Foo</main-launcher>",
+ "<launcher></launcher>",
+ "</jpackage-state>");
+ Assert.assertEquals("Foo", file.getLauncherName());
+ Assert.assertArrayEquals(new String[0],
+ file.getAddLauncherNames().toArray(String[]::new));
+ }
+
+ @Test
+ public void testMainLauncherName() throws IOException {
+ Map<String, ? super Object> params = new LinkedHashMap<>();
+ params.put("name", "Foo");
+ params.put("description", "Duck App Description");
+ AppImageFile aif = create(params);
+
+ Assert.assertEquals("Foo", aif.getLauncherName());
+ }
+
+ @Test
+ public void testAddLauncherNames() throws IOException {
+ }
+
+ private AppImageFile create(Map<String, Object> params) throws IOException {
+ AppImageFile.save(tempFolder.getRoot().toPath(), params);
+ return AppImageFile.load(tempFolder.getRoot().toPath());
+ }
+
+ private void assertInvalid(AppImageFile file) {
+ Assert.assertNull(file.getLauncherName());
+ Assert.assertNull(file.getAddLauncherNames());
+ }
+
+ private AppImageFile createFromXml(String... xmlData) throws IOException {
+ Path directory = tempFolder.getRoot().toPath();
+ Path path = directory.resolve(AppImageFile.XML_FILENAME);
+ path.toFile().mkdirs();
+ Files.delete(path);
+
+ ArrayList<String> data = new ArrayList();
+ data.add("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>");
+ data.addAll(List.of(xmlData));
+
+ Files.write(path, data, StandardOpenOption.CREATE,
+ StandardOpenOption.TRUNCATE_EXISTING);
+
+ AppImageFile image = AppImageFile.load(directory);
+ return image;
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/ApplicationLayoutTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,90 @@
+/*
+ * 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.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.TemporaryFolder;
+import static org.junit.Assert.*;
+
+
+public class ApplicationLayoutTest {
+
+ @Rule
+ public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+ private void fillLinuxAppImage() throws IOException {
+ appImage = tempFolder.newFolder("Foo").toPath();
+
+ Path base = appImage.getFileName();
+
+ tempFolder.newFolder(base.toString(), "bin");
+ tempFolder.newFolder(base.toString(), "lib", "app", "mods");
+ tempFolder.newFolder(base.toString(), "lib", "runtime", "bin");
+ tempFolder.newFile(base.resolve("bin/Foo").toString());
+ tempFolder.newFile(base.resolve("lib/app/Foo.cfg").toString());
+ tempFolder.newFile(base.resolve("lib/app/hello.jar").toString());
+ tempFolder.newFile(base.resolve("lib/Foo.png").toString());
+ tempFolder.newFile(base.resolve("lib/libapplauncher.so").toString());
+ tempFolder.newFile(base.resolve("lib/runtime/bin/java").toString());
+ }
+
+ @Test
+ public void testLinux() throws IOException {
+ fillLinuxAppImage();
+ testApplicationLayout(ApplicationLayout.linuxAppImage());
+ }
+
+ private void testApplicationLayout(ApplicationLayout layout) throws IOException {
+ ApplicationLayout srcLayout = layout.resolveAt(appImage);
+ assertApplicationLayout(srcLayout);
+
+ ApplicationLayout dstLayout = layout.resolveAt(
+ appImage.getParent().resolve(
+ "Copy" + appImage.getFileName().toString()));
+ srcLayout.move(dstLayout);
+ Files.deleteIfExists(appImage);
+ assertApplicationLayout(dstLayout);
+
+ dstLayout.copy(srcLayout);
+ assertApplicationLayout(srcLayout);
+ assertApplicationLayout(dstLayout);
+ }
+
+ private void assertApplicationLayout(ApplicationLayout layout) throws IOException {
+ assertTrue(Files.isRegularFile(layout.appDirectory().resolve("Foo.cfg")));
+ assertTrue(Files.isRegularFile(layout.appDirectory().resolve("hello.jar")));
+ assertTrue(Files.isDirectory(layout.appModsDirectory()));
+ assertTrue(Files.isRegularFile(layout.launchersDirectory().resolve("Foo")));
+ assertTrue(Files.isRegularFile(layout.destktopIntegrationDirectory().resolve("Foo.png")));
+ assertTrue(Files.isRegularFile(layout.dllDirectory().resolve("libapplauncher.so")));
+ assertTrue(Files.isRegularFile(layout.runtimeDirectory().resolve("bin/java")));
+ }
+
+ private Path appImage;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/DeployParamsTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.File;
+import java.io.IOException;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.junit.Rule;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+/**
+ * Test for JDK-8211285
+ */
+public class DeployParamsTest {
+
+ @Rule
+ public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ @Before
+ public void setUp() throws IOException {
+ testRoot = tempFolder.newFolder();
+ }
+
+ @Test
+ public void testValidAppName() throws PackagerException {
+ initParamsAppName();
+
+ setAppNameAndValidate("Test");
+
+ setAppNameAndValidate("Test Name");
+
+ setAppNameAndValidate("Test - Name !!!");
+ }
+
+ @Test
+ public void testInvalidAppName() throws PackagerException {
+ initForInvalidAppNamePackagerException();
+ initParamsAppName();
+ setAppNameAndValidate("Test\nName");
+ }
+
+ @Test
+ public void testInvalidAppName2() throws PackagerException {
+ initForInvalidAppNamePackagerException();
+ initParamsAppName();
+ setAppNameAndValidate("Test\rName");
+ }
+
+ @Test
+ public void testInvalidAppName3() throws PackagerException {
+ initForInvalidAppNamePackagerException();
+ initParamsAppName();
+ setAppNameAndValidate("TestName\\");
+ }
+
+ @Test
+ public void testInvalidAppName4() throws PackagerException {
+ initForInvalidAppNamePackagerException();
+ initParamsAppName();
+ setAppNameAndValidate("Test \" Name");
+ }
+
+ private void initForInvalidAppNamePackagerException() {
+ thrown.expect(PackagerException.class);
+
+ String msg = "Error: Invalid Application name";
+
+ // Unfortunately org.hamcrest.core.StringStartsWith is not available
+ // with older junit, DIY
+
+ // thrown.expectMessage(startsWith("Error: Invalid Application name"));
+ thrown.expectMessage(new BaseMatcher() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean matches(Object o) {
+ if (o instanceof String) {
+ return ((String) o).startsWith(msg);
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(Description d) {
+ d.appendText(msg);
+ }
+ });
+ }
+
+ // Returns deploy params initialized to pass all validation, except for
+ // app name
+ private void initParamsAppName() {
+ params = new DeployParams();
+
+ params.setOutput(testRoot);
+ params.addResource(testRoot, new File(testRoot, "test.jar"));
+ params.addBundleArgument(Arguments.CLIOptions.APPCLASS.getId(),
+ "TestClass");
+ params.addBundleArgument(Arguments.CLIOptions.MAIN_JAR.getId(),
+ "test.jar");
+ params.addBundleArgument(Arguments.CLIOptions.INPUT.getId(), "input");
+ }
+
+ private void setAppNameAndValidate(String appName) throws PackagerException {
+ params.addBundleArgument(Arguments.CLIOptions.NAME.getId(), appName);
+ params.validate();
+ }
+
+ private File testRoot = null;
+ private DeployParams params;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/PathGroupTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,131 @@
+/*
+ * 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.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+
+public class PathGroupTest {
+ public PathGroupTest() {
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testNullId() {
+ new PathGroup(Map.of()).getPath(null);
+ }
+
+ @Test
+ public void testEmptyPathGroup() {
+ PathGroup pg = new PathGroup(Map.of());
+
+ assertNull(pg.getPath("foo"));
+
+ assertEquals(0, pg.paths().size());
+ assertEquals(0, pg.roots().size());
+ }
+
+ @Test
+ public void testRootsSinglePath() {
+ final PathGroup pg = new PathGroup(Map.of("main", PATH_FOO));
+
+ List<Path> paths = pg.paths();
+ assertEquals(1, paths.size());
+ assertEquals(PATH_FOO, paths.iterator().next());
+
+ List<Path> roots = pg.roots();
+ assertEquals(1, roots.size());
+ assertEquals(PATH_FOO, roots.iterator().next());
+ }
+
+ @Test
+ public void testDuplicatedRoots() {
+ final PathGroup pg = new PathGroup(Map.of("main", PATH_FOO, "another",
+ PATH_FOO, "root", PATH_EMPTY));
+
+ List<Path> paths = pg.paths();
+ Collections.sort(paths);
+
+ assertEquals(3, paths.size());
+ assertEquals(PATH_EMPTY, paths.get(0));
+ assertEquals(PATH_FOO, paths.get(1));
+ assertEquals(PATH_FOO, paths.get(2));
+
+ List<Path> roots = pg.roots();
+ assertEquals(1, roots.size());
+ assertEquals(PATH_EMPTY, roots.get(0));
+ }
+
+ @Test
+ public void testRoots() {
+ final PathGroup pg = new PathGroup(Map.of(1, Path.of("foo"), 2, Path.of(
+ "foo", "bar"), 3, Path.of("foo", "bar", "buz")));
+
+ List<Path> paths = pg.paths();
+ assertEquals(3, paths.size());
+ assertTrue(paths.contains(Path.of("foo")));
+ assertTrue(paths.contains(Path.of("foo", "bar")));
+ assertTrue(paths.contains(Path.of("foo", "bar", "buz")));
+
+ List<Path> roots = pg.roots();
+ assertEquals(1, roots.size());
+ assertEquals(Path.of("foo"), roots.get(0));
+ }
+
+ @Test
+ public void testResolveAt() {
+ final PathGroup pg = new PathGroup(Map.of(0, PATH_FOO, 1, PATH_BAR, 2,
+ PATH_EMPTY));
+
+ final Path aPath = Path.of("a");
+
+ final PathGroup pg2 = pg.resolveAt(aPath);
+ assertThat(pg, not(equalTo(pg2)));
+
+ List<Path> paths = pg.paths();
+ assertEquals(3, paths.size());
+ assertTrue(paths.contains(PATH_EMPTY));
+ assertTrue(paths.contains(PATH_FOO));
+ assertTrue(paths.contains(PATH_BAR));
+ assertEquals(PATH_EMPTY, pg.roots().get(0));
+
+ paths = pg2.paths();
+ assertEquals(3, paths.size());
+ assertTrue(paths.contains(aPath.resolve(PATH_EMPTY)));
+ assertTrue(paths.contains(aPath.resolve(PATH_FOO)));
+ assertTrue(paths.contains(aPath.resolve(PATH_BAR)));
+ assertEquals(aPath, pg2.roots().get(0));
+ }
+
+ private final static Path PATH_FOO = Path.of("foo");
+ private final static Path PATH_BAR = Path.of("bar");
+ private final static Path PATH_EMPTY = Path.of("");
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/jpackage/internal/ToolValidatorTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,86 @@
+/*
+ * 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.nio.file.Path;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+
+public class ToolValidatorTest {
+
+ @Test
+ public void testAvailable() {
+ assertNull(new ToolValidator(TOOL_JAVA).validate());
+ }
+
+ @Test
+ public void testNotAvailable() {
+ assertValidationFailure(new ToolValidator(TOOL_UNKNOWN).validate(), true);
+ }
+
+ @Test
+ public void testVersionParserUsage() {
+ // Without minimal version configured, version parser should not be used
+ new ToolValidator(TOOL_JAVA).setVersionParser(unused -> {
+ throw new RuntimeException();
+ }).validate();
+
+ // Minimal version is 1, actual is 10. Should be OK.
+ assertNull(
+ new ToolValidator(TOOL_JAVA).setMinimalVersion(
+ "1").setVersionParser(unused -> "10").validate());
+
+ // Minimal version is 5, actual is 4.99.37. Error expected.
+ assertValidationFailure(new ToolValidator(TOOL_JAVA).setMinimalVersion(
+ "5").setVersionParser(unused -> "4.99.37").validate(), false);
+ }
+
+ private static void assertValidationFailure(ConfigException v,
+ boolean withCause) {
+ assertNotNull(v);
+ assertThat("", is(not(v.getMessage().strip())));
+ assertThat("", is(not(v.advice.strip())));
+ if (withCause) {
+ assertNotNull(v.getCause());
+ } else {
+ assertNull(v.getCause());
+ }
+ }
+
+ private final static String TOOL_JAVA;
+ private final static String TOOL_UNKNOWN = Path.of(System.getProperty(
+ "java.home"), "bin").toString();
+
+ static {
+ String fname = "java";
+ if (Platform.isWindows()) {
+ fname = fname + ".exe";
+ }
+ TOOL_JAVA = Path.of(System.getProperty("java.home"), "bin", fname).toString();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/junit.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+/*
+ * @test
+ * @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
+ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/run_junit.sh Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+set -x
+
+set -e
+if [ -z "$BASH" ]; then
+ # The script relies on Bash arrays, rerun in Bash.
+ /bin/bash $0 $@
+ exit
+fi
+
+classes=( "$@" )
+sources=()
+for c in "${classes[@]}"; do
+ sources+=( "${TESTSRC}/$(echo $c | sed -e 's|\.|/|g').java" )
+done
+
+common_args=(\
+ --patch-module jdk.jpackage="${TESTSRC}${PS}${TESTCLASSES}" \
+ --add-reads jdk.jpackage=ALL-UNNAMED \
+ --add-exports jdk.jpackage/jdk.jpackage.internal=ALL-UNNAMED \
+ -classpath "${TESTCLASSPATH}" \
+)
+
+# Compile classes for junit
+"${COMPILEJAVA}/bin/javac" ${TESTTOOLVMOPTS} ${TESTJAVACOPTS} \
+ "${common_args[@]}" -d "${TESTCLASSES}" "${sources[@]}"
+
+# Run junit
+"${TESTJAVA}/bin/java" ${TESTVMOPTS} ${TESTJAVAOPTS} \
+ "${common_args[@]}" org.junit.runner.JUnitCore "$@"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/AppCategoryTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+
+
+/**
+ * Test --linux-app-category parameter. Output of the test should be
+ * appcategorytest_1.0-1_amd64.deb or appcategorytest-1.0-1.amd64.rpm package
+ * bundle. The output package should provide the same functionality as the
+ * default package.
+ *
+ * deb:
+ * Section property of the package should be set to Foo value.
+ *
+ * rpm:
+ * Group property of the package should be set to Foo value.
+ */
+
+
+/*
+ * @test
+ * @summary jpackage with --linux-app-category
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m AppCategoryTest
+ */
+public class AppCategoryTest {
+
+ public static void main(String[] args) {
+ final String CATEGORY = "Foo";
+
+ TKit.run(args, () -> {
+ new PackageTest()
+ .forTypes(PackageType.LINUX)
+ .configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--linux-app-category", CATEGORY);
+ })
+ .forTypes(PackageType.LINUX_DEB)
+ .addBundlePropertyVerifier("Section", CATEGORY)
+ .forTypes(PackageType.LINUX_RPM)
+ .addBundlePropertyVerifier("Group", CATEGORY)
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/LicenseTypeTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+
+
+/**
+ * Test --linux-rpm-license-type parameter. Output of the test should be
+ * licensetypetest-1.0-1.amd64.rpm package bundle. The output package
+ * should provide the same functionality as the
+ * default package.
+ * License property of the package should be set to JP_LICENSE_TYPE.
+ */
+
+
+/*
+ * @test
+ * @summary jpackage with --linux-rpm-license-type
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m LicenseTypeTest
+ */
+public class LicenseTypeTest {
+
+ public static void main(String[] args) {
+ final String LICENSE_TYPE = "JP_LICENSE_TYPE";
+
+ TKit.run(args, () -> {
+ new PackageTest().forTypes(PackageType.LINUX_RPM).configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--linux-rpm-license-type", LICENSE_TYPE);
+ })
+ .addBundlePropertyVerifier("License", LICENSE_TYPE)
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/LinuxBundleNameTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+
+
+/**
+ * Test --linux-package-name parameter. Output of the test should be
+ * quickbrownfox2_1.0-1_amd64.deb or quickbrownfox2-1.0-1.amd64.rpm package
+ * bundle. The output package should provide the same functionality as the
+ * default package.
+ *
+ * deb:
+ * Package property of the package should be set to quickbrownfox2.
+ *
+ * rpm:
+ * Name property of the package should be set to quickbrownfox2.
+ */
+
+
+/*
+ * @test
+ * @summary jpackage with --linux-package-name
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m LinuxBundleNameTest
+ */
+public class LinuxBundleNameTest {
+
+ public static void main(String[] args) {
+ final String PACKAGE_NAME = "quickbrownfox2";
+
+ TKit.run(args, () -> {
+ new PackageTest()
+ .forTypes(PackageType.LINUX)
+ .configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--linux-package-name", PACKAGE_NAME);
+ })
+ .forTypes(PackageType.LINUX_DEB)
+ .addBundlePropertyVerifier("Package", PACKAGE_NAME)
+ .forTypes(PackageType.LINUX_RPM)
+ .addBundlePropertyVerifier("Name", PACKAGE_NAME)
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/MaintainerTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.TKit;
+
+
+/**
+ * Test --linux-deb-maintainer parameter. Output of the test should be
+ * maintainertest_1.0-1_amd64.deb package bundle. The output package
+ * should provide the same functionality as the
+ * default package.
+ * Value of Maintainer property of the package should contain
+ * jpackage-test@java.com email address.
+ */
+
+
+/*
+ * @test
+ * @summary jpackage with --linux-deb-maintainer
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m MaintainerTest
+ */
+public class MaintainerTest {
+
+ public static void main(String[] args) {
+ final String MAINTAINER = "jpackage-test@java.com";
+
+ TKit.run(args, () -> {
+ new PackageTest().forTypes(PackageType.LINUX_DEB).configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--linux-deb-maintainer", MAINTAINER);
+ })
+ .addBundlePropertyVerifier("Maintainer", (propName, propValue) -> {
+ String lookupValue = "<" + MAINTAINER + ">";
+ TKit.assertTrue(propValue.endsWith(lookupValue),
+ String.format("Check value of %s property [%s] ends with %s",
+ propName, propValue, lookupValue));
+ })
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/PackageDepsTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.LinuxHelper;
+
+
+/**
+ * Test --linux-package-deps parameter. Output of the test should be
+ * apackagedepstestprereq_1.0-1_amd64.deb and packagedepstest_1.0-1_amd64.deb or
+ * apackagedepstestprereq-1.0-1.amd64.rpm and packagedepstest-1.0-1.amd64.rpm
+ * package bundles. The output packages should provide the same functionality as
+ * the default package.
+ *
+ * deb: Value of Depends property of packagedepstest package should contain
+ * apackagedepstestprereq word.
+ *
+ * rpm: Value of Requires property of packagedepstest package should contain
+ * apackagedepstestprereq word.
+ */
+
+
+/*
+ * @test
+ * @summary jpackage with --linux-package-deps
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m PackageDepsTest
+ */
+public class PackageDepsTest {
+
+ public static void main(String[] args) {
+ // Pick the name of prerequisite package to be alphabetically
+ // preceeding the main package name.
+ // This is needed to make Bash script batch installing/uninstalling packages
+ // produced by jtreg tests install/uninstall packages in the right order.
+ final String PREREQ_PACKAGE_NAME = "apackagedepstestprereq";
+
+ TKit.run(args, () -> {
+ new PackageTest()
+ .forTypes(PackageType.LINUX)
+ .configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.setArgumentValue("--name", PREREQ_PACKAGE_NAME);
+ })
+ .run();
+
+ new PackageTest()
+ .forTypes(PackageType.LINUX)
+ .configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--linux-package-deps", PREREQ_PACKAGE_NAME);
+ })
+ .forTypes(PackageType.LINUX)
+ .addBundleVerifier(cmd -> {
+ TKit.assertTrue(
+ LinuxHelper.getPrerequisitePackages(cmd).contains(
+ PREREQ_PACKAGE_NAME), String.format(
+ "Check package depends on [%s] package",
+ PREREQ_PACKAGE_NAME));
+ })
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/ReleaseTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.TKit;
+
+
+/**
+ * Test --linux-app-release parameter. Output of the test should be
+ * releasetest_1.0-Rc3_amd64.deb or releasetest-1.0-Rc3.amd64.rpm package
+ * bundle. The output package should provide the same functionality as the
+ * default package.
+ *
+ * deb:
+ * Version property of the package should end with -Rc3 substring.
+ *
+ * rpm:
+ * Release property of the package should be set to Rc3 value.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --linux-app-release
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m ReleaseTest
+ */
+public class ReleaseTest {
+
+ public static void main(String[] args) {
+ final String RELEASE = "Rc3";
+
+ TKit.run(args, () -> {
+ new PackageTest()
+ .forTypes(PackageType.LINUX)
+ .configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--linux-app-release", RELEASE);
+ })
+ .forTypes(PackageType.LINUX_RPM)
+ .addBundlePropertyVerifier("Release", RELEASE)
+ .forTypes(PackageType.LINUX_DEB)
+ .addBundlePropertyVerifier("Version", (propName, propValue) -> {
+ TKit.assertTrue(propValue.endsWith("-" + RELEASE),
+ String.format("Check value of %s property [%s] ends with %s",
+ propName, propValue, RELEASE));
+ })
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/ShortcutHintTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,125 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+import java.util.Map;
+import java.nio.file.Path;
+import jdk.jpackage.test.FileAssociations;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.Annotations.Test;
+
+/**
+ * Test --linux-shortcut parameter. Output of the test should be
+ * shortcuthinttest_1.0-1_amd64.deb or shortcuthinttest-1.0-1.amd64.rpm package
+ * bundle. The output package should provide the same functionality as the
+ * default package and also create a desktop shortcut.
+ *
+ * Finding a shortcut of the application launcher through GUI depends on desktop
+ * environment.
+ *
+ * deb:
+ * Search online for `Ways To Open A Ubuntu Application` for instructions.
+ *
+ * rpm:
+ *
+ */
+
+/*
+ * @test
+ * @summary jpackage with --linux-shortcut
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @compile ShortcutHintTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=ShortcutHintTest
+ */
+public class ShortcutHintTest {
+
+ @Test
+ public static void testBasic() {
+ createTest().addInitializer(cmd -> {
+ cmd.addArgument("--linux-shortcut");
+ }).run();
+ }
+
+ private static PackageTest createTest() {
+ return new PackageTest()
+ .forTypes(PackageType.LINUX)
+ .configureHelloApp()
+ .addBundleDesktopIntegrationVerifier(true);
+
+ }
+
+ /**
+ * Adding `--icon` to jpackage command line should create desktop shortcut
+ * even though `--linux-shortcut` is omitted.
+ */
+ @Test
+ public static void testCustomIcon() {
+ createTest().addInitializer(cmd -> {
+ cmd.setFakeRuntime();
+ cmd.addArguments("--icon", TKit.TEST_SRC_ROOT.resolve(
+ "apps/dukeplug.png"));
+ }).run();
+ }
+
+ /**
+ * Adding `--file-associations` to jpackage command line should create
+ * desktop shortcut even though `--linux-shortcut` is omitted.
+ */
+ @Test
+ public static void testFileAssociations() {
+ createTest().addInitializer(cmd -> {
+ cmd.setFakeRuntime();
+
+ FileAssociations fa = new FileAssociations(
+ "ShortcutHintTest_testFileAssociations");
+ fa.createFile();
+ cmd.addArguments("--file-associations", fa.getPropertiesFile());
+ }).run();
+ }
+
+ /**
+ * Additional launcher with icon should create desktop shortcut even though
+ * `--linux-shortcut` is omitted.
+ */
+ @Test
+ public static void testAdditionaltLaunchers() {
+ createTest().addInitializer(cmd -> {
+ cmd.setFakeRuntime();
+
+ final String launcherName = "Foo";
+ final Path propsFile = TKit.workDir().resolve(
+ launcherName + ".properties");
+
+ cmd.addArguments("--add-launcher", String.format("%s=%s",
+ launcherName, propsFile));
+
+ TKit.createPropertiesFile(propsFile, Map.entry("icon",
+ TKit.TEST_SRC_ROOT.resolve("apps/dukeplug.png").toString()));
+ }).run();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/BundleIdentifierTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.nio.file.Files;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+
+/*
+ * @test
+ * @summary jpackage create image bundle identifier test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @modules jdk.jpackage
+ * @requires (os.family == "mac")
+ * @run main/othervm -Xmx512m BundleIdentifierTest
+ */
+public class BundleIdentifierTest {
+ private static final String OUTPUT = "output";
+ private static final String app = JPackagePath.getApp();
+ private static final String appOutput = JPackagePath.getAppOutputFile();
+ private static final String MAC_PACKAGE_IDENTIFIER = "TestBundleIdentifier";
+ private static final String APP_NAME = "test";
+ private static final String MAIN_CLASS = "Hello";
+
+ private static final String [] CMD_1 = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", APP_NAME,
+ "--main-jar", "hello.jar",
+ "--main-class", MAIN_CLASS
+ };
+
+ private static final String [] CMD_2 = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", APP_NAME,
+ "--main-jar", "hello.jar",
+ "--main-class", MAIN_CLASS,
+ "--mac-package-identifier", MAC_PACKAGE_IDENTIFIER
+ };
+
+ private static void validateResult(String[] result) throws Exception {
+ if (result.length != 2) {
+ throw new AssertionError(
+ "Unexpected number of lines: " + result.length);
+ }
+
+ if (!result[0].trim().equals("jpackage test application")) {
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ }
+
+ if (!result[1].trim().equals("args.length: 0")) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+ }
+
+ private static void validate() throws Exception {
+ int retVal = JPackageHelper.execute(null, app);
+ if (retVal != 0) {
+ throw new AssertionError(
+ "Test application exited with error: " + retVal);
+ }
+
+ File outfile = new File(appOutput);
+ if (!outfile.exists()) {
+ throw new AssertionError(appOutput + " was not created");
+ }
+
+ String output = Files.readString(outfile.toPath());
+ String[] result = output.split("\n");
+ validateResult(result);
+ }
+
+ private static void validateBundleIdentifier(String bundleIdentifier)
+ throws Exception {
+ System.out.println("Validating bundleIdentifier: " + bundleIdentifier);
+
+ File infoPList = new File(OUTPUT + File.separator + APP_NAME + ".app" +
+ File.separator + "Contents" + File.separator + "Info.plist");
+
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newDefaultInstance();
+ dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+ DocumentBuilder b = dbf.newDocumentBuilder();
+ org.w3c.dom.Document doc = b.parse(new FileInputStream(
+ infoPList.getAbsolutePath()));
+
+ XPath xPath = XPathFactory.newInstance().newXPath();
+ // Query for the value of <string> element preceding <key> element
+ // with value equal to CFBundleIdentifier
+ String v = (String)xPath.evaluate(
+ "//string[preceding-sibling::key = \"CFBundleIdentifier\"][1]",
+ doc, XPathConstants.STRING);
+
+ if (!v.equals(bundleIdentifier)) {
+ throw new AssertionError("Unexpected value of CFBundleIdentifier key: ["
+ + v + "]. Expected value: [" + bundleIdentifier + "]");
+ }
+ }
+
+ private static void testCreateAppImage(String [] cmd,
+ String bundleIdentifier,
+ boolean validateApp) throws Exception {
+ JPackageHelper.executeCLI(true, cmd);
+ if (validateApp) {
+ validate();
+ }
+ validateBundleIdentifier(bundleIdentifier);
+ }
+
+ private static void testCreateAppImageToolProvider(String [] cmd,
+ String bundleIdentifier,
+ boolean validateApp) throws Exception {
+ JPackageHelper.executeToolProvider(true, cmd);
+ if (validateApp) {
+ validate();
+ }
+ validateBundleIdentifier(bundleIdentifier);
+ }
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+ testCreateAppImage(CMD_1, MAIN_CLASS, false);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ testCreateAppImageToolProvider(CMD_1, MAIN_CLASS, false);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ testCreateAppImage(CMD_2, MAC_PACKAGE_IDENTIFIER, true);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ testCreateAppImageToolProvider(CMD_2, MAC_PACKAGE_IDENTIFIER, true);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/BundleNameTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.nio.file.Files;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+
+/*
+ * @test
+ * @summary jpackage create image bundle name test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @modules jdk.jpackage
+ * @requires (os.family == "mac")
+ * @run main/othervm -Xmx512m BundleNameTest
+ */
+public class BundleNameTest {
+ private static final String OUTPUT = "output";
+ private static final String app = JPackagePath.getApp();
+ private static final String appOutput = JPackagePath.getAppOutputFile();
+ private static final String MAC_BUNDLE_NAME = "TestBundleName";
+ private static final String APP_NAME = "test";
+
+ private static final String [] CMD_1 = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", APP_NAME,
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello"
+ };
+
+ private static final String [] CMD_2 = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", APP_NAME,
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--mac-package-name", MAC_BUNDLE_NAME
+ };
+
+ private static void validateResult(String[] result) throws Exception {
+ if (result.length != 2) {
+ throw new AssertionError(
+ "Unexpected number of lines: " + result.length);
+ }
+
+ if (!result[0].trim().equals("jpackage test application")) {
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ }
+
+ if (!result[1].trim().equals("args.length: 0")) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+ }
+
+ private static void validate() throws Exception {
+ int retVal = JPackageHelper.execute(null, app);
+ if (retVal != 0) {
+ throw new AssertionError(
+ "Test application exited with error: " + retVal);
+ }
+
+ File outfile = new File(appOutput);
+ if (!outfile.exists()) {
+ throw new AssertionError(appOutput + " was not created");
+ }
+
+ String output = Files.readString(outfile.toPath());
+ String[] result = output.split("\n");
+ validateResult(result);
+ }
+
+ private static void validateBundleName(String bundleName) throws Exception {
+ System.out.println("Validating bundleName: " + bundleName);
+
+ File infoPList = new File(OUTPUT + File.separator + APP_NAME + ".app" +
+ File.separator + "Contents" + File.separator + "Info.plist");
+
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newDefaultInstance();
+ dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+ DocumentBuilder b = dbf.newDocumentBuilder();
+ org.w3c.dom.Document doc = b.parse(new FileInputStream(
+ infoPList.getAbsolutePath()));
+
+ XPath xPath = XPathFactory.newInstance().newXPath();
+ // Query for the value of <string> element preceding <key> element
+ // with value equal to CFBundleName
+ String v = (String)xPath.evaluate(
+ "//string[preceding-sibling::key = \"CFBundleName\"][1]",
+ doc, XPathConstants.STRING);
+
+ if (!v.equals(bundleName)) {
+ throw new AssertionError("Unexpected value of CFBundleName key: ["
+ + v + "]. Expected value: [" + bundleName + "]");
+ }
+ }
+
+ private static void testCreateAppImage(String [] cmd,
+ String bundleName,
+ boolean validateApp) throws Exception {
+ JPackageHelper.executeCLI(true, cmd);
+ if (validateApp) {
+ validate();
+ }
+ validateBundleName(bundleName);
+ }
+
+ private static void testCreateAppImageToolProvider(String [] cmd,
+ String bundleName,
+ boolean validateApp) throws Exception {
+ JPackageHelper.executeToolProvider(true, cmd);
+ if (validateApp) {
+ validate();
+ }
+ validateBundleName(bundleName);
+ }
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+ testCreateAppImage(CMD_1, APP_NAME, false);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ testCreateAppImageToolProvider(CMD_1, APP_NAME, false);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ testCreateAppImage(CMD_2, MAC_BUNDLE_NAME, true);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ testCreateAppImageToolProvider(CMD_2, MAC_BUNDLE_NAME, true);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/base/OptionsBase.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamConstants;
+import javax.xml.stream.XMLStreamReader;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+
+public class OptionsBase {
+
+ static final String TEST_BUNDLE_NAME = "TestBundleName";
+ static final String TEST_BUNDLE_IDENTIFIER = "net.java.openjdk.packagerTest";
+ static final String TEST_CATECORY = "public.app-category.test";
+ private static String TEST_NAME;
+ private static String EXT;
+ private static String OUTPUT;
+ private static String[] CMD;
+
+ private static void testCreateInstaller() throws Exception {
+ JPackageHelper.executeCLI(true, CMD);
+
+ if (EXT.equals("dmg")) {
+ String disk = null;
+ try {
+ var log = new File("hdiutil.log");
+ JPackageHelper.execute(log, "/usr/bin/hdiutil",
+ "attach", OUTPUT);
+ try(var br = new BufferedReader(new FileReader(log))) {
+ var line = br.lines().reduce((a, b) -> b).orElse(null)
+ .split("\t");
+ if ((line.length < 3) || !line[2].contains(TEST_NAME)) {
+ throw new AssertionError(
+ "expected attach output to contain test name: "
+ + TEST_NAME);
+ }
+ }
+ } finally {
+ if (disk != null) {
+ JPackageHelper.execute(null,
+ "/usr/bin/hdiutil", "detach", disk);
+ }
+ }
+ } else {
+ testPkg(OUTPUT);
+ }
+ }
+
+ private static void testPkg(String path) throws Exception {
+ JPackageHelper.execute(null, "/usr/sbin/pkgutil",
+ "--expand-full", path, "expand");
+ var info = new File("expand/" + TEST_NAME + "-app.pkg/Payload/"
+ + TEST_NAME + ".app/Contents/Info.plist");
+ if (!info.exists()) {
+ throw new AssertionError("Info.plist not found");
+ }
+
+ String bundleName = null;
+ String bundleIdentifier = null;
+ String categoryType = null;
+ try (FileInputStream fis = new FileInputStream(info)) {
+ var xmlInFact = XMLInputFactory.newInstance();
+ xmlInFact.setProperty(XMLInputFactory.SUPPORT_DTD, false);
+ xmlInFact.setProperty(
+ XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
+ var reader = xmlInFact.createXMLStreamReader(fis);
+ while (reader.hasNext()) {
+ if (reader.next() == XMLStreamConstants.CHARACTERS) {
+ switch (reader.getText()) {
+ case "CFBundleName": {
+ bundleName = readValue(reader);
+ break;
+ }
+ case "CFBundleIdentifier" : {
+ bundleIdentifier = readValue(reader);
+ break;
+ }
+ case "LSApplicationCategoryType" : {
+ categoryType = readValue(reader);
+ break;
+ }
+ }
+ }
+ }
+ }
+ boolean passed = true;
+ if (!TEST_BUNDLE_NAME.equals(bundleName)) {
+ passed = false;
+ System.err.println("Wrong bundle name [" + bundleName +
+ "] expected [" + TEST_BUNDLE_NAME + "]" );
+ }
+ if (!TEST_BUNDLE_IDENTIFIER.equals(bundleIdentifier)) {
+ passed = false;
+ System.err.println("Wrong bundle identifier [" +
+ bundleIdentifier + "] expected [" + TEST_BUNDLE_IDENTIFIER
+ + "]" );
+ }
+ if (!TEST_CATECORY.equals(categoryType)) {
+ passed = false;
+ System.err.println("Wrong appstore category [" + categoryType +
+ "] expected [" + TEST_CATECORY + "]" );
+ }
+
+ if (!passed) {
+ throw new AssertionError("Test failed");
+ }
+ }
+
+ static private String readValue(XMLStreamReader reader) throws Exception {
+ while (reader.hasNext() && reader.next() != XMLStreamConstants.START_ELEMENT);
+ return reader.hasNext() ? reader.getElementText() : null;
+ }
+
+ private static void verifyInstall() throws Exception {
+ String app = JPackagePath.getOSXInstalledApp("jpackage", TEST_NAME);
+ JPackageInstallerHelper.validateApp(app);
+ }
+
+ private static void verifyUnInstall() throws Exception {
+ // Not needed on OS X, since we just deleting installed application
+ // without using generated installer. We keeping this for consistnency
+ // between platforms.
+ }
+
+ private static void init(String name, String ext) {
+ TEST_NAME = name;
+ EXT = ext;
+ OUTPUT = "output" + File.separator + TEST_NAME + "-1.0." + EXT;
+ CMD = new String[] {
+ "--package-type", EXT,
+ "--input", "input",
+ "--dest", "output",
+ "--name", TEST_NAME,
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--mac-package-name", TEST_BUNDLE_NAME,
+ "--mac-package-identifier", TEST_BUNDLE_IDENTIFIER,
+ "--mac-app-store-category", TEST_CATECORY
+ };
+ }
+
+ public static void run(String name, String ext) throws Exception {
+ init(name, ext);
+
+ if (JPackageInstallerHelper.isVerifyInstall()) {
+ verifyInstall();
+ } else if (JPackageInstallerHelper.isVerifyUnInstall()) {
+ verifyUnInstall();
+ } else {
+ JPackageHelper.createHelloInstallerJar();
+ testCreateInstaller();
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/dmg/OptionsTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary jpackage create installer test
+ * @library ../../helpers
+ * @library ../base
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build JPackageInstallerHelper
+ * @build OptionsBase
+ * @requires (os.family == "mac")
+ * @modules jdk.jpackage
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm -Xmx512m OptionsTest
+ */
+public class OptionsTest {
+ private static final String TEST_NAME = "OptionsTest";
+ private static final String EXT = "dmg";
+
+ public static void main(String[] args) throws Exception {
+ if (jdk.jpackage.internal.MacDmgBundler.isSupported()) {
+ OptionsBase.run(TEST_NAME, EXT);
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/dmg/install.sh Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,6 @@
+echo "Note: This script will install DMG files silently. In order to verify UI, each .dmg needs to launched manually via Finder."
+
+# OptionsTest
+hdiutil attach OptionsTest-1.0.dmg
+sudo /usr/sbin/installer -pkg /Volumes/OptionsTest/OptionsTest-1.0.pkg -target /
+hdiutil detach /Volumes/OptionsTest/
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/dmg/uninstall.sh Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,1 @@
+sudo rm -rf /Applications/OptionsTest.app
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/pkg/OptionsTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary jpackage create installer test
+ * @library ../../helpers
+ * @library ../base
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build JPackageInstallerHelper
+ * @build OptionsBase
+ * @requires (os.family == "mac")
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m OptionsTest
+ */
+public class OptionsTest {
+ private static final String TEST_NAME = "OptionsTest";
+ private static final String EXT = "pkg";
+
+ public static void main(String[] args) throws Exception {
+ OptionsBase.run(TEST_NAME, EXT);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/pkg/install.sh Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,2 @@
+echo Note: This script will install packages silently. In order to verify UI, each .pkg needs to launched manually via Finder.
+sudo /usr/sbin/installer -pkg OptionsTest-1.0.pkg -target /
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/pkg/uninstall.sh Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,1 @@
+sudo rm -rf /Applications/OptionsDirTest.app
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/manage_packages.sh Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,185 @@
+#!/bin/bash
+
+#
+# Script to install/uninstall packages produced by jpackage jtreg
+# tests doing platform specific packaging.
+#
+# The script will install/uninstall all packages from the files
+# found in the current directory or the one specified with command line option.
+#
+# When jtreg jpackage tests are executed with jpackage.test.output
+# Java property set, produced package files (msi, exe, deb, rpm, etc.) will
+# be saved in the directory specified with this property.
+#
+# Usage example:
+# # Set directory where to save package files from jtreg jpackage tests
+# JTREG_OUTPUT_DIR=/tmp/jpackage_jtreg_packages
+#
+# # Run tests and fill $JTREG_OUTPUT_DIR directory with package files
+# jtreg -Djpackage.test.output=$JTREG_OUTPUT_DIR ...
+#
+# # Install all packages
+# manage_pachages.sh -d $JTREG_OUTPUT_DIR
+#
+# # Uninstall all packages
+# manage_pachages.sh -d $JTREG_OUTPUT_DIR -u
+#
+
+#
+# When using with MSI installers, Cygwin shell from which this script is
+# executed should be started as administrator. Otherwise silent installation
+# won't work.
+#
+
+# Fail fast
+set -e; set -o pipefail;
+
+
+help_usage ()
+{
+ echo "Usage: `basename $0` [OPTION]"
+ echo "Options:"
+ echo " -h - print this message"
+ echo " -v - verbose output"
+ echo " -d <dir> - path to directory where to look for package files"
+ echo " -u - uninstall packages instead of the default install"
+ echo " -t - dry run, print commands but don't execute them"
+}
+
+error ()
+{
+ echo "$@" > /dev/stderr
+}
+
+fatal ()
+{
+ error "$@"
+ exit 1
+}
+
+fatal_with_help_usage ()
+{
+ error "$@"
+ help_usage
+ exit 1
+}
+
+
+# Directory where to look for package files.
+package_dir=$PWD
+
+# Script debug.
+verbose=
+
+# Operation mode.
+mode=install
+
+dryrun=
+
+while getopts "vhd:ut" argname; do
+ case "$argname" in
+ v) verbose=yes;;
+ t) dryrun=yes;;
+ u) mode=uninstall;;
+ d) package_dir="$OPTARG";;
+ h) help_usage; exit 0;;
+ ?) help_usage; exit 1;;
+ esac
+done
+shift $(( OPTIND - 1 ))
+
+[ -d "$package_dir" ] || fatal_with_help_usage "Package directory [$package_dir] is not a directory"
+
+[ -z "$verbose" ] || set -x
+
+
+function find_packages_of_type ()
+{
+ # sort output alphabetically
+ find "$package_dir" -maxdepth 1 -type f -name '*.'"$1" | sort
+}
+
+function find_packages ()
+{
+ local package_suffixes=(deb rpm msi exe)
+ for suffix in "${package_suffixes[@]}"; do
+ if [ "$mode" == "uninstall" ]; then
+ packages=$(find_packages_of_type $suffix | tac)
+ else
+ packages=$(find_packages_of_type $suffix)
+ fi
+ if [ -n "$packages" ]; then
+ package_type=$suffix
+ break;
+ fi
+ done
+}
+
+
+# RPM
+install_cmd_rpm ()
+{
+ echo sudo rpm --install "$@"
+}
+uninstall_cmd_rpm ()
+{
+ local package_name=$(rpm -qp --queryformat '%{Name}' "$@")
+ echo sudo rpm -e "$package_name"
+}
+
+# DEB
+install_cmd_deb ()
+{
+ echo sudo dpkg -i "$@"
+}
+uninstall_cmd_deb ()
+{
+ local package_name=$(dpkg-deb -f "$@" Package)
+ echo sudo dpkg -r "$package_name"
+}
+
+# MSI
+install_cmd_msi ()
+{
+ echo msiexec /qn /norestart /i $(cygpath -w "$@")
+}
+uninstall_cmd_msi ()
+{
+ echo msiexec /qn /norestart /x $(cygpath -w "$@")
+}
+
+# EXE
+install_cmd_exe ()
+{
+ echo "$@"
+}
+uninstall_cmd_exe ()
+{
+ error No implemented
+}
+
+
+# Find packages
+packages=
+find_packages
+if [ -z "$packages" ]; then
+ echo "No packages found in $package_dir directory"
+ exit
+fi
+
+# Build list of commands to execute
+declare -a commands
+for p in $packages; do
+ commands[${#commands[@]}]=$(${mode}_cmd_${package_type} "$p")
+done
+
+if [ -z "$dryrun" ]; then
+ # Run commands
+ for cmd in "${commands[@]}"; do
+ echo Running: $cmd
+ $cmd || true;
+ done
+else
+ # Print commands
+ for cmd in "${commands[@]}"; do echo $cmd; done
+fi
Binary file test/jdk/tools/jpackage/resources/icon.icns has changed
Binary file test/jdk/tools/jpackage/resources/icon.ico has changed
Binary file test/jdk/tools/jpackage/resources/icon.png has changed
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/resources/license.txt Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,1 @@
+jpackage test license file (just some sample text).
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/run_tests.sh Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,278 @@
+#!/bin/bash
+
+#
+# Script to run jpackage tests.
+#
+
+
+# Fail fast
+set -e; set -o pipefail;
+
+
+# Link obtained from https://openjdk.java.net/jtreg/ page
+jtreg_bundle=https://ci.adoptopenjdk.net/view/Dependencies/job/jtreg/lastSuccessfulBuild/artifact/jtreg-4.2.0-tip.tar.gz
+workdir=/tmp/jpackage_jtreg_testing
+jtreg_jar=$workdir/jtreg/lib/jtreg.jar
+
+# Names of shared packaging tests to run
+share_package_test_names="
+ FileAssociationsTest
+ InstallDirTest
+ LicenseTest
+ SimplePackageTest
+ RuntimePackageTest
+ AdditionalLaunchersTest
+ AppImagePackageTest
+"
+mapfile -t packaging_tests_share < <(for t in $share_package_test_names; do echo test/jdk/tools/jpackage/share/$t.java; done)
+packaging_tests_windows=test/jdk/tools/jpackage/windows
+packaging_tests_linux=test/jdk/tools/jpackage/linux
+packaging_tests_mac=test/jdk/tools/jpackage/macosx
+
+case "$(uname -s)" in
+ Darwin)
+ tests=( "$packaging_tests_mac" );;
+ Linux)
+ tests=( "$packaging_tests_linux" );;
+ CYGWIN*|MINGW32*|MSYS*)
+ tests=( "$packaging_tests_windows" );;
+ *)
+ fatal Failed to detect OS type;;
+esac
+tests+=(${packaging_tests_share[@]})
+
+
+help_usage ()
+{
+ echo "Usage: `basename $0` [options] [test_names]"
+ echo "Options:"
+ echo " -h - print this message"
+ echo " -v - verbose output"
+ echo " -c - keep jtreg cache"
+ echo " -d - dry run. Print jtreg command line, but don't execute it"
+ echo " -t <jdk> - path to JDK to be tested [ mandatory ]"
+ echo " -j <openjdk> - path to local copy of openjdk repo with jpackage jtreg tests"
+ echo " Optional, default is openjdk repo where this script resides"
+ echo " -o <outputdir> - path to folder where to copy artifacts for testing."
+ echo " Optional, default is the current directory."
+ echo ' -r <runtimedir> - value for `jpackage.test.runtime-image` property.'
+ echo " Optional, for jtreg tests debug purposes only."
+ echo ' -l <logfile> - value for `jpackage.test.logfile` property.'
+ echo " Optional, for jtreg tests debug purposes only."
+ echo " -m <mode> - mode to run jtreg tests."
+ echo ' Should be one of `create`, `update`, `verify-install` or `verify-uninstall`.'
+ echo ' Optional, default mode is `update`.'
+ echo ' - `create`'
+ echo ' Remove all package bundles from the output directory before running jtreg tests.'
+ echo ' - `update`'
+ echo ' Run jtreg tests and overrite existing package bundles in the output directory.'
+ echo ' - `verify-install`'
+ echo ' Verify installed packages created with the previous run of the script.'
+ echo ' - `verify-uninstall`'
+ echo ' Verify packages created with the previous run of the script were uninstalled cleanly.'
+ echo ' - `print-default-tests`'
+ echo ' Print default tests list and exit.'
+}
+
+error ()
+{
+ echo "$@" > /dev/stderr
+}
+
+fatal ()
+{
+ error "$@"
+ exit 1
+}
+
+fatal_with_help_usage ()
+{
+ error "$@"
+ help_usage
+ exit 1
+}
+
+if command -v cygpath &> /dev/null; then
+to_native_path ()
+{
+ cygpath -m "$@"
+}
+else
+to_native_path ()
+{
+ echo "$@"
+}
+fi
+
+exec_command ()
+{
+ if [ -n "$dry_run" ]; then
+ echo "$@"
+ else
+ eval "$@"
+ fi
+}
+
+expand_test_selector ()
+{
+ if [ -d "$open_jdk_with_jpackage_jtreg_tests/$1" ]; then
+ for java in $(find "$open_jdk_with_jpackage_jtreg_tests/$1" -maxdepth 1 -name '*.java'); do
+ ! grep -q '@test' "$java" || echo "$1/$(basename "$java")"
+ done
+ else
+ echo "$1"
+ fi
+}
+
+
+# Path to JDK to be tested.
+test_jdk=
+
+# Path to local copy of open jdk repo with jpackage jtreg tests
+# hg clone http://hg.openjdk.java.net/jdk/sandbox
+# cd sandbox; hg update -r JDK-8200758-branch
+open_jdk_with_jpackage_jtreg_tests=$(dirname $0)/../../../../
+
+# Directory where to save artifacts for testing.
+output_dir=$PWD
+
+# Script and jtreg debug.
+verbose=
+jtreg_verbose="-verbose:fail,error,summary"
+
+keep_jtreg_cache=
+
+# Mode in which to run jtreg tests
+mode=update
+
+# JVM extra arguments
+declare -a vm_args
+
+while getopts "vhdct:j:o:r:m:l:" argname; do
+ case "$argname" in
+ v) verbose=yes;;
+ d) dry_run=yes;;
+ c) keep_jtreg_cache=yes;;
+ t) test_jdk="$OPTARG";;
+ j) open_jdk_with_jpackage_jtreg_tests="$OPTARG";;
+ o) output_dir="$OPTARG";;
+ r) runtime_dir="$OPTARG";;
+ l) logfile="$OPTARG";;
+ m) mode="$OPTARG";;
+ h) help_usage; exit 0;;
+ ?) help_usage; exit 1;;
+ esac
+done
+shift $(( OPTIND - 1 ))
+
+[ -z "$verbose" ] || { set -x; jtreg_verbose=-va; }
+
+if [ -z "$open_jdk_with_jpackage_jtreg_tests" ]; then
+ fatal_with_help_usage "Path to openjdk repo with jpackage jtreg tests not specified"
+fi
+
+if [ "$mode" = "print-default-tests" ]; then
+ exec_command for t in ${tests[@]}";" do expand_test_selector '$t;' done
+ exit
+fi
+
+if [ -z "$test_jdk" ]; then
+ fatal_with_help_usage Path to test JDK not specified
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ echo JAVA_HOME environment variable not set, will use java from test JDK [$test_jdk] to run jtreg
+ JAVA_HOME="$test_jdk"
+fi
+if [ ! -e "$JAVA_HOME/bin/java" ]; then
+ fatal JAVA_HOME variable is set to [$JAVA_HOME] value, but $JAVA_HOME/bin/java not found.
+fi
+
+if [ -n "$runtime_dir" ]; then
+ if [ ! -d "$runtime_dir" ]; then
+ fatal 'Value of `-r` option is set to non-existing directory'.
+ fi
+ vm_args+=("-Djpackage.test.runtime-image=$(to_native_path "$(cd "$runtime_dir" && pwd)")")
+fi
+
+if [ -n "$logfile" ]; then
+ if [ ! -d "$(dirname "$logfile")" ]; then
+ fatal 'Value of `-l` option specified a file in non-existing directory'.
+ fi
+ logfile="$(cd "$(dirname "$logfile")" && pwd)/$(basename "$logfile")"
+ vm_args+=("-Djpackage.test.logfile=$(to_native_path "$logfile")")
+fi
+
+if [ "$mode" = create ]; then
+ true
+elif [ "$mode" = update ]; then
+ true
+elif [ "$mode" = verify-install ]; then
+ vm_args+=("-Djpackage.test.action=$mode")
+elif [ "$mode" = verify-uninstall ]; then
+ vm_args+=("-Djpackage.test.action=$mode")
+else
+ fatal_with_help_usage 'Invalid value of -m option:' [$mode]
+fi
+
+
+# All remaining command line arguments are tests to run that should override the defaults
+[ $# -eq 0 ] || tests=($@)
+
+
+installJtreg ()
+{
+ # Install jtreg if missing
+ if [ ! -f "$jtreg_jar" ]; then
+ exec_command mkdir -p "$workdir"
+ exec_command "(" cd "$workdir" "&&" wget "$jtreg_bundle" "&&" tar -xzf "$(basename $jtreg_bundle)" ";" rm -f "$(basename $jtreg_bundle)" ")"
+ fi
+}
+
+
+preRun ()
+{
+ local xargs_args=(-t --no-run-if-empty rm)
+ if [ -n "$dry_run" ]; then
+ xargs_args=(--no-run-if-empty echo rm)
+ fi
+
+ if [ ! -d "$output_dir" ]; then
+ exec_command mkdir -p "$output_dir"
+ fi
+ [ ! -d "$output_dir" ] || output_dir=$(cd "$output_dir" && pwd)
+
+ # Clean output directory
+ [ "$mode" != "create" ] || find $output_dir -maxdepth 1 -type f -name '*.exe' -or -name '*.msi' -or -name '*.rpm' -or -name '*.deb' | xargs "${xargs_args[@]}"
+}
+
+
+run ()
+{
+ local jtreg_cmdline=(\
+ $JAVA_HOME/bin/java -jar $(to_native_path "$jtreg_jar") \
+ "-Djpackage.test.output=$(to_native_path "$output_dir")" \
+ "${vm_args[@]}" \
+ -nr \
+ "$jtreg_verbose" \
+ -retain:all \
+ -automatic \
+ -ignore:run \
+ -testjdk:"$(to_native_path $test_jdk)" \
+ -dir:"$(to_native_path $open_jdk_with_jpackage_jtreg_tests)" \
+ -reportDir:"$(to_native_path $workdir/run/results)" \
+ -workDir:"$(to_native_path $workdir/run/support)" \
+ "${tests[@]}" \
+ )
+
+ # Clear previous results
+ [ -n "$keep_jtreg_cache" ] || exec_command rm -rf "$workdir"/run
+
+ # Run jpackage jtreg tests to create artifacts for testing
+ exec_command ${jtreg_cmdline[@]}
+}
+
+
+installJtreg
+preRun
+run
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/AddLauncherBase.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+
+public class AddLauncherBase {
+ private static final String app = JPackagePath.getApp();
+ private static final String appOutput = JPackagePath.getAppOutputFile();
+
+ // Note: quotes in argument for add launcher is not support by test
+ private static final String ARGUMENT1 = "argument 1";
+ private static final String ARGUMENT2 = "argument 2";
+ private static final String ARGUMENT3 = "argument 3";
+
+ private static final List<String> arguments = new ArrayList<>();
+
+ private static final String PARAM1 = "-Dparam1=Some Param 1";
+ private static final String PARAM2 = "-Dparam2=Some Param 2";
+ private static final String PARAM3 = "-Dparam3=Some Param 3";
+
+ private static final List<String> vmArguments = new ArrayList<>();
+ private static final List<String> empty = new ArrayList<>();
+
+ private static void validateResult(List<String> args, List<String> vmArgs)
+ throws Exception {
+ File outfile = new File(appOutput);
+ if (!outfile.exists()) {
+ throw new AssertionError(appOutput + " was not created");
+ }
+
+ String output = Files.readString(outfile.toPath());
+ String[] result = output.split("\n");
+
+ int expected = 2 + args.size() + vmArgs.size();
+
+ if (result.length != expected) {
+ throw new AssertionError("Unexpected number of lines: "
+ + result.length + " expected: " + expected + " - results: " + output);
+ }
+
+ if (!result[0].trim().endsWith("jpackage test application")) {
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ }
+
+ if (!result[1].trim().equals("args.length: " + args.size())) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+
+ int index = 2;
+ for (String arg : args) {
+ if (!result[index].trim().equals(arg)) {
+ throw new AssertionError("Unexpected result["
+ + index + "]: " + result[index]);
+ }
+ index++;
+ }
+
+ for (String vmArg : vmArgs) {
+ if (!result[index].trim().equals(vmArg)) {
+ throw new AssertionError("Unexpected result["
+ + index + "]: " + result[index]);
+ }
+ index++;
+ }
+ }
+
+ private static void validate(boolean includeArgs, String name)
+ throws Exception {
+ int retVal = JPackageHelper.execute(null, app);
+ if (retVal != 0) {
+ throw new AssertionError("Test application " + app
+ + " exited with error: " + retVal);
+ }
+ validateResult(new ArrayList<>(), new ArrayList<>());
+
+ String app2 = JPackagePath.getAppSL(name);
+ retVal = JPackageHelper.execute(null, app2);
+ if (retVal != 0) {
+ throw new AssertionError("Test application " + app2
+ + " exited with error: " + retVal);
+ }
+ if (includeArgs) {
+ validateResult(arguments, vmArguments);
+ } else {
+ validateResult(empty, empty);
+ }
+ }
+
+ public static void testCreateAppImage(String [] cmd) throws Exception {
+ testCreateAppImage(cmd, true, "test2");
+ }
+
+ public static void testCreateAppImage(String [] cmd,
+ boolean includeArgs, String name) throws Exception {
+ JPackageHelper.executeCLI(true, cmd);
+ validate(includeArgs, name);
+ }
+
+ public static void testCreateAppImageToolProvider(String [] cmd)
+ throws Exception {
+ testCreateAppImageToolProvider(cmd, true, "test2");
+ }
+
+ public static void testCreateAppImageToolProvider(String [] cmd,
+ boolean includeArgs, String name) throws Exception {
+ JPackageHelper.executeToolProvider(true, cmd);
+ validate(includeArgs, name);
+ }
+
+ public static void testCreateAppImage(String [] cmd,
+ ArrayList<String> argList, ArrayList <String> optionList)
+ throws Exception {
+ JPackageHelper.executeCLI(true, cmd);
+ int retVal = JPackageHelper.execute(null, app);
+ if (retVal != 0) {
+ throw new AssertionError("Test application " + app
+ + " exited with error: " + retVal);
+ }
+ validateResult(argList, optionList);
+ String name = "test4";
+
+ String app2 = JPackagePath.getAppSL(name);
+ retVal = JPackageHelper.execute(null, app2);
+ if (retVal != 0) {
+ throw new AssertionError("Test application " + app2
+ + " exited with error: " + retVal);
+ }
+ validateResult(arguments, vmArguments);
+ }
+
+ public static void createSLProperties() throws Exception {
+ arguments.add(ARGUMENT1);
+ arguments.add(ARGUMENT2);
+ arguments.add(ARGUMENT3);
+
+ String argumentsMap =
+ JPackageHelper.listToArgumentsMap(arguments, true);
+
+ vmArguments.add(PARAM1);
+ vmArguments.add(PARAM2);
+ vmArguments.add(PARAM3);
+
+ String vmArgumentsMap =
+ JPackageHelper.listToArgumentsMap(vmArguments, true);
+
+ try (PrintWriter out = new PrintWriter(new BufferedWriter(
+ new FileWriter("sl.properties")))) {
+ out.println("arguments=" + argumentsMap);
+ out.println("java-options=" + vmArgumentsMap);
+ }
+
+ try (PrintWriter out = new PrintWriter(new BufferedWriter(
+ new FileWriter("m1.properties")))) {
+ out.println("module=com.hello/com.hello.Hello");
+ out.println("main-jar=");
+ }
+
+ try (PrintWriter out = new PrintWriter(new BufferedWriter(
+ new FileWriter("j1.properties")))) {
+ out.println("main-jar hello.jar");
+ out.println("main-class Hello");
+ }
+
+
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/AddLauncherModuleTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+ /*
+ * @test
+ * @summary jpackage create image with additional launcher test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build AddLauncherBase
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m AddLauncherModuleTest
+ */
+public class AddLauncherModuleTest {
+ private static final String OUTPUT = "output";
+ private static final String [] CMD = {
+ "--package-type", "app-image",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "input",
+ "--add-launcher", "test2=sl.properties"};
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloModule();
+ AddLauncherBase.createSLProperties();
+ AddLauncherBase.testCreateAppImageToolProvider(
+ CMD);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/AddLauncherTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.util.ArrayList;
+
+/*
+ * @test
+ * @summary jpackage create image with additional launcher test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build AddLauncherBase
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m AddLauncherTest
+ */
+public class AddLauncherTest {
+ private static final String OUTPUT = "output";
+ private static final String [] CMD = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--add-launcher", "test2=sl.properties"};
+
+ private final static String OPT1 = "-Dparam1=xxx";
+ private final static String OPT2 = "-Dparam2=yyy";
+ private final static String OPT3 = "-Dparam3=zzz";
+ private final static String ARG1 = "original-argument";
+
+ private static final String [] CMD1 = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--java-options", OPT1,
+ "--java-options", OPT2,
+ "--java-options", OPT3,
+ "--arguments", ARG1,
+ "--add-launcher", "test4=sl.properties"};
+
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+ AddLauncherBase.createSLProperties();
+ AddLauncherBase.testCreateAppImage(CMD);
+
+ ArrayList <String> argList = new ArrayList <String> ();
+ argList.add(ARG1);
+
+ ArrayList <String> optList = new ArrayList <String> ();
+ optList.add(OPT1);
+ optList.add(OPT2);
+ optList.add(OPT3);
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ AddLauncherBase.testCreateAppImage(CMD1, argList, optList);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/AddLaunchersTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+ /*
+ * @test
+ * @summary jpackage create image with additional launcher test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build AddLauncherBase
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m AddLaunchersTest
+ */
+public class AddLaunchersTest {
+ private static final String OUTPUT = "output";
+ private static final String [] CMD1 = {
+ "--description", "Test non modular app with multiple add-launchers where one is modular app and other is non modular app",
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--module-path", "module",
+ "--add-modules", "com.hello",
+ "--add-launcher", "test3=j1.properties",
+ "--add-launcher", "test4=m1.properties"};
+
+ private static final String [] CMD2 = {
+ "--description", "Test modular app with multiple add-launchers where one is modular app and other is non modular app",
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "module",
+ "--add-launcher", "test5=jl.properties",
+ "--add-launcher", "test6=m1.properties"};
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+ JPackageHelper.createHelloModule();
+ AddLauncherBase.createSLProperties();
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ AddLauncherBase.testCreateAppImageToolProvider(
+ CMD1, false, "test3");
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ AddLauncherBase.testCreateAppImage(
+ CMD1, false, "test4");
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ AddLauncherBase.testCreateAppImage(
+ CMD2, false, "test5");
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ AddLauncherBase.testCreateAppImageToolProvider(
+ CMD2, false, "test6");
+
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/AddModulesTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+ /*
+ * @test
+ * @summary jpackage create image module test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build Base
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m AddModulesTest
+ */
+public class AddModulesTest {
+ private static final String OUTPUT = "output";
+
+ private static final String [] CMD1 = {
+ "--package-type", "app-image",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "input",
+ "--add-modules", "java.desktop",
+ };
+
+ private static final String [] CMD2 = {
+ "--package-type", "app-image",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "input",
+ "--add-modules", "java.desktop,java.xml",
+ };
+
+ private static final String [] CMD3 = {
+ "--package-type", "app-image",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "input",
+ "--add-modules", "java.desktop",
+ "--add-modules", "java.xml",
+ };
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloModule();
+ Base.testCreateAppImage(CMD1);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ Base.testCreateAppImageToolProvider(CMD1);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ Base.testCreateAppImageToolProvider(CMD2);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ Base.testCreateAppImageToolProvider(CMD3);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/AdditionalLaunchersTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+import java.util.Optional;
+import java.lang.invoke.MethodHandles;
+import jdk.jpackage.test.HelloApp;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.FileAssociations;
+import jdk.jpackage.test.TKit;
+
+/**
+ * Test --add-launcher parameter. Output of the test should be
+ * additionallauncherstest*.* installer. The output installer should provide the
+ * same functionality as the default installer (see description of the default
+ * installer in SimplePackageTest.java) plus install three extra application
+ * launchers.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --add-launcher
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m AdditionalLaunchersTest
+ */
+public class AdditionalLaunchersTest {
+
+ public static void main(String[] args) {
+ TKit.run(args, () -> {
+ FileAssociations fa = new FileAssociations(
+ MethodHandles.lookup().lookupClass().getSimpleName());
+
+ // Configure a bunch of additional launchers and also setup
+ // file association to make sure it will be linked only to the main
+ // launcher.
+
+ PackageTest packageTest = new PackageTest().configureHelloApp()
+ .addInitializer(cmd -> {
+ fa.createFile();
+ cmd.addArguments("--file-associations", fa.getPropertiesFile());
+ cmd.addArguments("--arguments", "Duke", "--arguments", "is",
+ "--arguments", "the", "--arguments", "King");
+ });
+
+ packageTest.addHelloAppFileAssociationsVerifier(fa);
+
+ new AdditionalLauncher("Baz2").setArguments().applyTo(packageTest);
+ new AdditionalLauncher("foo").setArguments("yep!").applyTo(packageTest);
+
+ AdditionalLauncher barLauncher = new AdditionalLauncher("Bar").setArguments(
+ "one", "two", "three");
+ packageTest.forTypes(PackageType.LINUX).addInitializer(cmd -> {
+ barLauncher.setIcon(TKit.TEST_SRC_ROOT.resolve("apps/dukeplug.png"));
+ });
+ barLauncher.applyTo(packageTest);
+
+ packageTest.run();
+ });
+ }
+
+ private static Path replaceFileName(Path path, String newFileName) {
+ String fname = path.getFileName().toString();
+ int lastDotIndex = fname.lastIndexOf(".");
+ if (lastDotIndex != -1) {
+ fname = newFileName + fname.substring(lastDotIndex);
+ }
+ return path.getParent().resolve(fname);
+ }
+
+ static class AdditionalLauncher {
+
+ AdditionalLauncher(String name) {
+ this.name = name;
+ }
+
+ AdditionalLauncher setArguments(String... args) {
+ arguments = List.of(args);
+ return this;
+ }
+
+ AdditionalLauncher setIcon(Path iconPath) {
+ icon = iconPath;
+ return this;
+ }
+
+ void applyTo(PackageTest test) {
+ final Path propsFile = TKit.workDir().resolve(name + ".properties");
+
+ test.addInitializer(cmd -> {
+ cmd.addArguments("--add-launcher", String.format("%s=%s", name,
+ propsFile));
+
+ Map<String, String> properties = new HashMap<>();
+ if (arguments != null) {
+ properties.put("arguments", String.join(" ",
+ arguments.toArray(String[]::new)));
+ }
+
+ if (icon != null) {
+ properties.put("icon", icon.toAbsolutePath().toString());
+ }
+
+ TKit.createPropertiesFile(propsFile, properties);
+ });
+ test.addInstallVerifier(cmd -> {
+ Path launcherPath = replaceFileName(
+ cmd.launcherInstallationPath(), name);
+
+ TKit.assertExecutableFileExists(launcherPath);
+
+ if (cmd.isFakeRuntimeInstalled(String.format(
+ "Not running %s launcher", launcherPath))) {
+ return;
+ }
+ HelloApp.executeAndVerifyOutput(launcherPath,
+ Optional.ofNullable(arguments).orElse(List.of()).toArray(
+ String[]::new));
+ });
+ test.addUninstallVerifier(cmd -> {
+ Path launcherPath = replaceFileName(
+ cmd.launcherInstallationPath(), name);
+
+ TKit.assertPathExists(launcherPath, false);
+ });
+ }
+
+ private List<String> arguments;
+ private Path icon;
+ private final String name;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/AppImagePackageTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.nio.file.Path;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.JPackageCommand;
+
+/**
+ * Test --app-image parameter. The output installer should provide the same
+ * functionality as the default installer (see description of the default
+ * installer in SimplePackageTest.java)
+ */
+
+/*
+ * @test
+ * @summary jpackage with --app-image
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m AppImagePackageTest
+ */
+public class AppImagePackageTest {
+
+ public static void main(String[] args) {
+ TKit.run(args, () -> {
+ Path appimageOutput = Path.of("appimage");
+
+ JPackageCommand appImageCmd = JPackageCommand.helloAppImage()
+ .setArgumentValue("--dest", appimageOutput)
+ .addArguments("--package-type", "app-image");
+
+ PackageTest packageTest = new PackageTest();
+ if (packageTest.getAction() == PackageTest.Action.CREATE) {
+ appImageCmd.execute();
+ }
+
+ packageTest.addInitializer(cmd -> {
+ Path appimageInput = appimageOutput.resolve(appImageCmd.name());
+
+ if (PackageType.MAC.contains(cmd.packageType())) {
+ // Why so complicated on macOS?
+ appimageInput = Path.of(appimageInput.toString() + ".app");
+ }
+
+ cmd.addArguments("--app-image", appimageInput);
+ cmd.removeArgumentWithValue("--input");
+ }).addBundleDesktopIntegrationVerifier(false).run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/AppMainClassModuleTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.File;
+import java.nio.file.Files;
+
+/*
+ * @test
+ * @summary jpackage create image using main class from main module
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m AppMainClassModuleTest
+ */
+public class AppMainClassModuleTest {
+
+ private static final String OUTPUT = "output";
+ private static final String app = JPackagePath.getApp();
+ private static final String appOutput = JPackagePath.getAppOutputFile();
+
+ private static final String[] CMD = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello",
+ "--module-path", "input"
+ };
+
+ private static final String[] CMD_MAIN_CLASS = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "input"
+ };
+
+ private static void validate(String buildOutput) throws Exception {
+
+ File outfile = new File(appOutput);
+ int retVal = JPackageHelper.execute(outfile, app);
+ if (retVal != 0) {
+ throw new AssertionError(
+ "Test application exited with error: ");
+ }
+
+ if (!outfile.exists()) {
+ throw new AssertionError(appOutput + " was not created");
+ }
+ String output = Files.readString(outfile.toPath());
+ String[] result = output.split("\n");
+
+ if (!result[0].trim().equals("jpackage test application")) {
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ }
+
+ if (!result[1].trim().equals("args.length: 0")) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+ }
+
+ private static void testMainClassFromModule() throws Exception {
+ JPackageHelper.createHelloModule(
+ new JPackageHelper.ModuleArgs(null, "com.hello.Hello"));
+
+ validate(JPackageHelper.executeCLI(true, CMD));
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ validate(JPackageHelper.executeToolProvider(true, CMD));
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ }
+
+ private static void testMainClassFromCLI() throws Exception {
+ JPackageHelper.createHelloModule(
+ new JPackageHelper.ModuleArgs(null, "com.hello.Hello2"));
+
+ validate(JPackageHelper.executeCLI(true, CMD_MAIN_CLASS));
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ validate(JPackageHelper.executeToolProvider(true, CMD_MAIN_CLASS));
+ }
+
+ public static void main(String[] args) throws Exception {
+ testMainClassFromModule();
+ testMainClassFromCLI();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/AppVersionModuleTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.File;
+import java.nio.file.Files;
+
+/*
+ * @test
+ * @summary jpackage create image using version from main module
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m AppVersionModuleTest
+ */
+public class AppVersionModuleTest {
+ private static final String OUTPUT = "output";
+ private static final String appCfg = JPackagePath.getAppCfg();
+ private static final String MODULE_VERSION = "2.7";
+ private static final String CLI_VERSION = "3.5";
+
+ private static final String[] CMD_MODULE_VERSION = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "input"
+ };
+
+ private static final String[] CMD_CLI_VERSION = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "input",
+ "--app-version", CLI_VERSION
+ };
+
+ private static void validate(String version)
+ throws Exception {
+ File outfile = new File(appCfg);
+ if (!outfile.exists()) {
+ throw new AssertionError(appCfg + " was not created");
+ }
+
+ String output = Files.readString(outfile.toPath());
+ if (version == null) {
+ version = MODULE_VERSION;
+ }
+
+ String expected = "app.version=" + version;
+ if (!output.contains(expected)) {
+ System.err.println("Expected: " + expected);
+ throw new AssertionError("Cannot find expected entry in config file");
+ }
+ }
+
+ private static void testVersion() throws Exception {
+ JPackageHelper.executeCLI(true, CMD_MODULE_VERSION);
+ validate(null);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JPackageHelper.executeCLI(true, CMD_CLI_VERSION);
+ validate(CLI_VERSION);
+ }
+
+ private static void testVersionToolProvider() throws Exception {
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JPackageHelper.executeToolProvider(true, CMD_MODULE_VERSION);
+ validate(null);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JPackageHelper.executeToolProvider(true, CMD_CLI_VERSION);
+ validate(CLI_VERSION);
+ }
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloModule(
+ new JPackageHelper.ModuleArgs(MODULE_VERSION, null));
+ testVersion();
+ testVersionToolProvider();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/AppVersionTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.File;
+import java.nio.file.Files;
+
+/*
+ * @test
+ * @summary jpackage create image --app-version test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m AppVersionTest
+ */
+public class AppVersionTest {
+ private static final String OUTPUT = "output";
+ private static final String appCfg = JPackagePath.getAppCfg();
+ private static final String VERSION = "2.3";
+ private static final String VERSION_DEFAULT = "1.0";
+
+ private static final String[] CMD = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ };
+
+ private static final String[] CMD_VERSION = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--app-version", VERSION};
+
+ private static void validate(String version)
+ throws Exception {
+ File outfile = new File(appCfg);
+ if (!outfile.exists()) {
+ throw new AssertionError(appCfg + " was not created");
+ }
+
+ String output = Files.readString(outfile.toPath());
+ if (version == null) {
+ version = VERSION_DEFAULT;
+ }
+
+ String expected = "app.version=" + version;
+ if (!output.contains(expected)) {
+ System.err.println("Expected: " + expected);
+ throw new AssertionError("Cannot find expected entry in config file");
+ }
+ }
+
+ private static void testVersion() throws Exception {
+ JPackageHelper.executeCLI(true, CMD);
+ validate(null);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JPackageHelper.executeCLI(true, CMD_VERSION);
+ validate(VERSION);
+ }
+
+ private static void testVersionToolProvider() throws Exception {
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JPackageHelper.executeToolProvider(true, CMD);
+ validate(null);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JPackageHelper.executeToolProvider(true, CMD_VERSION);
+ validate(VERSION);
+ }
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+ testVersion();
+ testVersionToolProvider();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/ArgumentsTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.nio.file.Path;
+import java.util.List;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.HelloApp;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.Annotations.*;
+
+
+/*
+ * Tricky arguments used in the test require a bunch of levels of character
+ * escaping for proper encoding them in a single string to be used as a value of
+ * `--arguments` option. String with encoded arguments doesn't go through the
+ * system to jpackage executable as is because OS is interpreting escape
+ * characters. This is true for Windows at least.
+ *
+ * String mapping performed by the system corrupts the string and jpackage exits
+ * with error. There is no problem with string corruption when jpackage is used
+ * as tool provider. This is not jpackage issue, so just always run this test
+ * with jpackage used as tool provider.
+ * /
+
+/*
+ * @test
+ * @summary jpackage create image with --arguments test
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.jpackage
+ * @compile ArgumentsTest.java
+ * @run main/othervm -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=ArgumentsTest
+ * --jpt-before-run=jdk.jpackage.test.JPackageCommand.useToolProviderByDefault
+ */
+public class ArgumentsTest {
+ @Test
+ @Parameter("Goodbye")
+ @Parameter("com.hello/com.hello.Hello")
+ public static void testApp(String javaAppDesc) {
+ testIt(javaAppDesc, null);
+ }
+
+ private static void testIt(String javaAppDesc,
+ ThrowingConsumer<JPackageCommand> initializer) {
+
+ JPackageCommand cmd = JPackageCommand.helloAppImage(javaAppDesc).addArguments(
+ "--arguments", JPackageCommand.escapeAndJoin(TRICKY_ARGUMENTS));
+ if (initializer != null) {
+ ThrowingConsumer.toConsumer(initializer).accept(cmd);
+ }
+
+ cmd.executeAndAssertImageCreated();
+
+ Path launcherPath = cmd.appImage().resolve(cmd.launcherPathInAppImage());
+ if (!cmd.isFakeRuntimeInAppImage(String.format(
+ "Not running [%s] launcher", launcherPath))) {
+ HelloApp.executeAndVerifyOutput(launcherPath, TRICKY_ARGUMENTS);
+ }
+ }
+
+ private final static List<String> TRICKY_ARGUMENTS = List.of(
+ "argument",
+ "Some Arguments",
+ "Value \"with\" quotes"
+ );
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/Base.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public abstract class Base {
+ private static final String appOutput = JPackagePath.getAppOutputFile();
+
+ private static void validateResult(String[] result) throws Exception {
+ if (result.length != 2) {
+ throw new AssertionError(
+ "Unexpected number of lines: " + result.length);
+ }
+
+ if (!result[0].trim().endsWith("jpackage test application")) {
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ }
+
+ if (!result[1].trim().equals("args.length: 0")) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+ }
+
+ public static void validate(String app) throws Exception {
+ Path outPath = Path.of(appOutput);
+ int retVal = JPackageHelper.execute(null, app);
+
+ if (outPath.toFile().exists()) {
+ System.out.println("output contents: ");
+ System.out.println(Files.readString(outPath) + "\n");
+ } else {
+ System.out.println("no output file: " + outPath
+ + " from command: " + app);
+ }
+
+ if (retVal != 0) {
+ throw new AssertionError(
+ "Test application (" + app + ") exited with error: " + retVal);
+ }
+
+ if (!outPath.toFile().exists()) {
+ throw new AssertionError(appOutput + " was not created");
+ }
+
+ String output = Files.readString(outPath);
+ String[] result = JPackageHelper.splitAndFilter(output);
+ validateResult(result);
+ }
+
+ public static void testCreateAppImage(String [] cmd) throws Exception {
+ JPackageHelper.executeCLI(true, cmd);
+ validate(JPackagePath.getApp());
+ }
+
+ public static void testCreateAppImageToolProvider(String [] cmd) throws Exception {
+ JPackageHelper.executeToolProvider(true, cmd);
+ validate(JPackagePath.getApp());
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/ErrorTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+ /*
+ * @test
+ * @summary jpackage create app image error test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build Base
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m ErrorTest
+ */
+import java.util.*;
+import java.io.*;
+import java.nio.*;
+import java.nio.file.*;
+import java.nio.file.attribute.*;
+
+public class ErrorTest {
+
+ private static final String OUTPUT = "output";
+
+ private static final String ARG1 = "--no-such-argument";
+ private static final String EXPECTED1 =
+ "Invalid Option: [--no-such-argument]";
+ private static final String ARG2 = "--dest";
+ private static final String EXPECTED2 = "--main-jar or --module";
+
+ private static final String [] CMD1 = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "non-existant.jar",
+ };
+ private static final String EXP1 = "main jar does not exist";
+
+ private static final String [] CMD2 = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ };
+ private static final String EXP2 = "class was not specified nor was";
+
+ private static void validate(String output, String expected, boolean single)
+ throws Exception {
+ String[] result = JPackageHelper.splitAndFilter(output);
+ if (single && result.length != 1) {
+ System.err.println(output);
+ throw new AssertionError("Unexpected multiple lines of output: "
+ + output);
+ }
+
+ if (!result[0].trim().contains(expected)) {
+ throw new AssertionError("Unexpected output: " + result[0]
+ + " - expected output to contain: " + expected);
+ }
+ }
+
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+
+ validate(JPackageHelper.executeToolProvider(false,
+ "--package-type", "app-image", ARG1), EXPECTED1, true);
+ validate(JPackageHelper.executeToolProvider(false,
+ "--package-type", "app-image", ARG2), EXPECTED2, true);
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ validate(JPackageHelper.executeToolProvider(false, CMD1), EXP1, false);
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ validate(JPackageHelper.executeToolProvider(false, CMD2), EXP2, false);
+
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/FileAssociationsTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.FileAssociations;
+
+/**
+ * Test --file-associations parameter. Output of the test should be
+ * fileassociationstest*.* installer. The output installer should provide the
+ * same functionality as the default installer (see description of the default
+ * installer in SimplePackageTest.java) plus configure file associations. After
+ * installation files with ".jptest1" and ".jptest2" suffixes should be
+ * associated with the test app.
+ *
+ * Suggested test scenario is to create empty file with ".jptest1" suffix,
+ * double click on it and make sure that test application was launched in
+ * response to double click event with the path to test .jptest1 file on the
+ * commend line. The same applies to ".jptest2" suffix.
+ *
+ * On Linux use "echo > foo.jptest1" and not "touch foo.jptest1" to create test
+ * file as empty files are always interpreted as plain text and will not be
+ * opened with the test app. This is a known bug.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --file-associations
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m FileAssociationsTest
+ */
+public class FileAssociationsTest {
+ public static void main(String[] args) {
+ TKit.run(args, () -> {
+ PackageTest packageTest = new PackageTest();
+
+ applyFileAssociations(packageTest, new FileAssociations("jptest1"));
+ applyFileAssociations(packageTest,
+ new FileAssociations("jptest2").setFilename("fa2"));
+ packageTest.run();
+ });
+ }
+
+ private static void applyFileAssociations(PackageTest test,
+ FileAssociations fa) {
+ test.addInitializer(cmd -> {
+ fa.createFile();
+ cmd.addArguments("--file-associations", fa.getPropertiesFile());
+ }).addHelloAppFileAssociationsVerifier(fa);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/HelpTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+ /*
+ * @test
+ * @summary jpackage help test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m HelpTest
+ */
+public class HelpTest {
+
+ // Platform specific help messages.
+ private static final String WINDOWS_HELP =
+ "--win-dir-chooser";
+ private static final String OSX_HELP =
+ "--mac-package-identifier";
+ private static final String LINUX_HELP =
+ "--linux-package-name";
+
+ private static void validate(String output1, String output2)
+ throws Exception {
+ if (output1.split("\n").length < 25) {
+ throw new AssertionError("jpacakger --help failed");
+ }
+
+ if (output2.split("\n").length < 25) {
+ throw new AssertionError("jpacakger -h failed");
+ }
+
+ // Make sure output matches for --help and -h
+ if (!output1.equals(output2)) {
+ System.err.println("================= --help =================");
+ System.err.println(output1);
+
+ System.err.println("=================== -h ===================");
+ System.err.println(output2);
+
+ throw new AssertionError(
+ "jpacakger help text does not match between --help and -h");
+ }
+
+ if (JPackageHelper.isWindows()) {
+ if (!output1.contains(WINDOWS_HELP)) {
+ throw new AssertionError(
+ "jpacakger help text missing Windows specific help");
+ }
+
+ if (output1.contains(OSX_HELP) || output1.contains(LINUX_HELP)) {
+ throw new AssertionError(
+ "jpacakger help text contains other platforms specific help");
+
+ }
+ } else if (JPackageHelper.isOSX()) {
+ if (!output1.contains(OSX_HELP)) {
+ throw new AssertionError(
+ "jpacakger help text missing OS X specific help");
+ }
+
+ if (output1.contains(WINDOWS_HELP) ||
+ output1.contains(LINUX_HELP)) {
+ throw new AssertionError(
+ "jpacakger help text contains other platforms specific help");
+ }
+ } else if (JPackageHelper.isLinux()) {
+ if (!output1.contains(LINUX_HELP)) {
+ throw new AssertionError(
+ "jpacakger help text missing Linux specific help");
+ }
+
+ if (output1.contains(OSX_HELP) || output1.contains(WINDOWS_HELP)) {
+ throw new AssertionError(
+ "jpacakger help text contains other platforms specific help");
+ }
+ }
+ }
+
+ private static void testHelp() throws Exception {
+ String output1 = JPackageHelper.executeCLI(true, "--help");
+ String output2 = JPackageHelper.executeCLI(true, "-h");
+ validate(output1, output2);
+ }
+
+ private static void testHelpToolProvider() throws Exception {
+ String output1 = JPackageHelper.executeToolProvider(true, "--help");
+ String output2 = JPackageHelper.executeToolProvider(true, "-h");
+ validate(output1, output2);
+ }
+
+ public static void main(String[] args) throws Exception {
+ testHelp();
+ testHelpToolProvider();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/IconTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.nio.file.Files;
+import jdk.jpackage.internal.ApplicationLayout;
+import java.nio.file.Path;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.Functional;
+import jdk.jpackage.test.JPackageCommand;
+
+/*
+ * @test
+ * @summary jpackage create image to verify --icon
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm -Xmx512m IconTest
+ */
+public class IconTest {
+ public static void main(String[] args) {
+ TKit.run(args, () -> {
+ JPackageCommand cmd = JPackageCommand.helloAppImage().addArguments("--icon", GOLDEN_ICON);
+ cmd.useToolProvider(true).executeAndAssertHelloAppImageCreated();
+
+ Path iconPath = ApplicationLayout.platformAppImage().resolveAt(
+ cmd.appImage()).destktopIntegrationDirectory().resolve(
+ cmd.launcherPathInAppImage().getFileName().toString().replaceAll(
+ "\\.[^.]*$", "") + ICON_SUFFIX);
+
+ TKit.assertFileExists(iconPath);
+ TKit.assertTrue(-1 == Files.mismatch(GOLDEN_ICON, iconPath),
+ String.format(
+ "Check application icon file [%s] is a copy of source icon file [%s]",
+ iconPath, GOLDEN_ICON));
+ });
+ }
+
+ private final static String ICON_SUFFIX = Functional.identity(() -> {
+ if (TKit.isOSX()) {
+ return ".icns";
+ }
+
+ if (TKit.isLinux()) {
+ return ".png";
+ }
+
+ if (TKit.isWindows()) {
+ return ".ico";
+ }
+
+ throw TKit.throwUnknownPlatformError();
+ }).get();
+
+ private final static Path GOLDEN_ICON = TKit.TEST_SRC_ROOT.resolve(Path.of(
+ "resources", "icon" + ICON_SUFFIX));
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/InstallDirTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.Functional;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.Annotations.Parameter;
+
+/**
+ * Test --install-dir parameter. Output of the test should be installdirtest*.*
+ * package bundle. The output package should provide the same functionality as
+ * the default package but install test application in specified directory.
+ *
+ * Linux:
+ *
+ * Application should be installed in /opt/jpackage/installdirtest folder.
+ *
+ * Mac:
+ *
+ * Application should be installed in /Applications/jpackage/installdirtest.app
+ * folder.
+ *
+ * Windows:
+ *
+ * Application should be installed in %ProgramFiles%/TestVendor/InstallDirTest1234
+ * folder.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --install-dir
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @compile InstallDirTest.java
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=InstallDirTest.testCommon
+ */
+
+/*
+ * @test
+ * @summary jpackage with --install-dir
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @compile InstallDirTest.java
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @requires (os.family == "linux")
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=InstallDirTest.testLinuxInvalid,testLinuxUnsupported
+ */
+public class InstallDirTest {
+
+ public static void testCommon() {
+ final Map<PackageType, Path> INSTALL_DIRS = Functional.identity(() -> {
+ Map<PackageType, Path> reply = new HashMap<>();
+ reply.put(PackageType.WIN_MSI, Path.of("TestVendor\\InstallDirTest1234"));
+ reply.put(PackageType.WIN_EXE, reply.get(PackageType.WIN_MSI));
+
+ reply.put(PackageType.LINUX_DEB, Path.of("/opt/jpackage"));
+ reply.put(PackageType.LINUX_RPM, reply.get(PackageType.LINUX_DEB));
+
+ reply.put(PackageType.MAC_PKG, Path.of("/Application/jpackage"));
+ reply.put(PackageType.MAC_DMG, reply.get(PackageType.MAC_PKG));
+
+ return reply;
+ }).get();
+
+ new PackageTest().configureHelloApp()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--install-dir", INSTALL_DIRS.get(
+ cmd.packageType()));
+ }).run();
+ }
+
+ @Parameter("/")
+ @Parameter(".")
+ @Parameter("foo")
+ @Parameter("/opt/foo/.././.")
+ public static void testLinuxInvalid(String installDir) {
+ testLinuxBad(installDir, "Invalid installation directory");
+ }
+
+ @Parameter("/usr")
+ @Parameter("/usr/local")
+ @Parameter("/usr/foo")
+ public static void testLinuxUnsupported(String installDir) {
+ testLinuxBad(installDir, "currently unsupported");
+ }
+
+ private static void testLinuxBad(String installDir,
+ String errorMessageSubstring) {
+ new PackageTest().configureHelloApp()
+ .setExpectedExitCode(1)
+ .forTypes(PackageType.LINUX)
+ .addInitializer(cmd -> {
+ cmd.addArguments("--install-dir", installDir);
+ cmd.saveConsoleOutput(true);
+ })
+ .addBundleVerifier((cmd, result) -> {
+ String errorMessage = JPackageCommand.filterOutput(result.
+ getOutput().stream()).filter(line -> line.contains(
+ errorMessageSubstring)).findFirst().orElse(null);
+ TKit.assertNotNull(errorMessage, String.format(
+ "Check output contains [%s] substring",
+ errorMessageSubstring));
+ })
+ .run();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/InvalidArgTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+ /*
+ * @test
+ * @summary jpackage invalid argument test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m InvalidArgTest
+ */
+public class InvalidArgTest {
+
+ private static final String ARG1 = "--no-such-argument";
+ private static final String ARG2 = "--dest";
+ private static final String RESULT1 =
+ "Invalid Option: [--no-such-argument]";
+ private static final String RESULT2 = "--main-jar or --module";
+
+ private static void validate(String arg, String output) throws Exception {
+ String[] result = JPackageHelper.splitAndFilter(output);
+ if (result.length != 1) {
+ System.err.println(output);
+ throw new AssertionError("Invalid number of lines in output: "
+ + result.length);
+ }
+
+ if (arg.equals(ARG1)) {
+ if (!result[0].trim().contains(RESULT1)) {
+ System.err.println("Expected: " + RESULT1);
+ System.err.println("Actual: " + result[0]);
+ throw new AssertionError("Unexpected output: " + result[0]);
+ }
+ } else if (arg.equals(ARG2)) {
+ if (!result[0].trim().contains(RESULT2)) {
+ System.err.println("Expected: " + RESULT2);
+ System.err.println("Actual: " + result[0]);
+ throw new AssertionError("Unexpected output: " + result[0]);
+ }
+ }
+ }
+
+ private static void testInvalidArg() throws Exception {
+ String output = JPackageHelper.executeCLI(false,
+ "--package-type", "app-image", ARG1);
+ validate(ARG1, output);
+
+ output = JPackageHelper.executeCLI(false,
+ "--package-type", "app-image", ARG2);
+ validate(ARG2, output);
+ }
+
+ private static void testInvalidArgToolProvider() throws Exception {
+ String output = JPackageHelper.executeToolProvider(false,
+ "--package-type", "app-image", ARG1);
+ validate(ARG1, output);
+
+ output = JPackageHelper.executeToolProvider(false,
+ "--package-type", "app-image", ARG2);
+ validate(ARG2, output);
+ }
+
+ public static void main(String[] args) throws Exception {
+ testInvalidArg();
+ testInvalidArgToolProvider();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/JavaOptionsBase.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.File;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+
+public class JavaOptionsBase {
+
+ private static final String app = JPackagePath.getApp();
+ private static final String appOutput = JPackagePath.getAppOutputFile();
+
+ private static final String ARGUMENT1 = "-Dparam1=Some Param 1";
+ private static final String ARGUMENT2 = "-Dparam2=Some \"Param\" 2";
+ private static final String ARGUMENT3 =
+ "-Dparam3=Some \"Param\" with \" 3";
+
+ private static final List<String> arguments = new ArrayList<>();
+
+ private static void initArguments(boolean toolProvider, String [] cmd) {
+ if (arguments.isEmpty()) {
+ arguments.add(ARGUMENT1);
+ arguments.add(ARGUMENT2);
+ arguments.add(ARGUMENT3);
+ }
+
+ String argumentsMap = JPackageHelper.listToArgumentsMap(arguments,
+ toolProvider);
+ cmd[cmd.length - 1] = argumentsMap;
+ }
+
+ private static void initArguments2(boolean toolProvider, String [] cmd) {
+ int index = cmd.length - 6;
+
+ cmd[index++] = "--java-options";
+ arguments.clear();
+ arguments.add(ARGUMENT1);
+ cmd[index++] = JPackageHelper.listToArgumentsMap(arguments,
+ toolProvider);
+
+ cmd[index++] = "--java-options";
+ arguments.clear();
+ arguments.add(ARGUMENT2);
+ cmd[index++] = JPackageHelper.listToArgumentsMap(arguments,
+ toolProvider);
+
+ cmd[index++] = "--java-options";
+ arguments.clear();
+ arguments.add(ARGUMENT3);
+ cmd[index++] = JPackageHelper.listToArgumentsMap(arguments,
+ toolProvider);
+
+ arguments.clear();
+ arguments.add(ARGUMENT1);
+ arguments.add(ARGUMENT2);
+ arguments.add(ARGUMENT3);
+ }
+
+ private static void validateResult(String[] result, List<String> args)
+ throws Exception {
+ if (result.length != (args.size() + 2)) {
+ for (String r : result) {
+ System.err.println(r.trim());
+ }
+ throw new AssertionError("Unexpected number of lines: "
+ + result.length);
+ }
+
+ if (!result[0].trim().equals("jpackage test application")) {
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ }
+
+ if (!result[1].trim().equals("args.length: 0")) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+
+ int index = 2;
+ for (String arg : args) {
+ if (!result[index].trim().equals(arg)) {
+ throw new AssertionError("Unexpected result[" + index + "]: "
+ + result[index]);
+ }
+ index++;
+ }
+ }
+
+ private static void validate(List<String> expectedArgs) throws Exception {
+ int retVal = JPackageHelper.execute(null, app);
+ if (retVal != 0) {
+ throw new AssertionError("Test application exited with error: "
+ + retVal);
+ }
+
+ File outfile = new File(appOutput);
+ if (!outfile.exists()) {
+ throw new AssertionError(appOutput + " was not created");
+ }
+
+ String output = Files.readString(outfile.toPath());
+ String[] result = JPackageHelper.splitAndFilter(output);
+ validateResult(result, expectedArgs);
+ }
+
+ public static void testCreateAppImageJavaOptions(String [] cmd) throws Exception {
+ initArguments(false, cmd);
+ JPackageHelper.executeCLI(true, cmd);
+ validate(arguments);
+ }
+
+ public static void testCreateAppImageJavaOptionsToolProvider(String [] cmd) throws Exception {
+ initArguments(true, cmd);
+ JPackageHelper.executeToolProvider(true, cmd);
+ validate(arguments);
+ }
+
+ public static void testCreateAppImageJavaOptions2(String [] cmd) throws Exception {
+ initArguments2(false, cmd);
+ JPackageHelper.executeCLI(true, cmd);
+ validate(arguments);
+ }
+
+ public static void testCreateAppImageJavaOptions2ToolProvider(String [] cmd) throws Exception {
+ initArguments2(true, cmd);
+ JPackageHelper.executeToolProvider(true, cmd);
+ validate(arguments);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/JavaOptionsEqualsTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.File;
+import java.nio.file.Files;
+
+/*
+ * @test
+ * @summary jpackage create image with --java-options test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build JavaOptionsBase
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m JavaOptionsEqualsTest
+ */
+public class JavaOptionsEqualsTest {
+
+ private static final String app = JPackagePath.getApp();
+
+ private static final String OUTPUT = "output";
+
+ private static final String WARNING_1
+ = "WARNING: Unknown module: me.mymodule.foo";
+
+ private static final String WARNING_2
+ = "WARNING: Unknown module: other.mod.bar";
+
+ private static final String[] CMD = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--description", "the two options below should cause two app execution "
+ + "Warnings with two lines output saying: "
+ + "WARNING: Unknown module: <module-name>",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--java-options",
+ "--add-exports=java.base/sun.util=me.mymodule.foo,ALL-UNNAMED",
+ "--java-options",
+ "--add-exports=java.base/sun.security.util=other.mod.bar,ALL-UNNAMED",
+ };
+
+ private static void validate() throws Exception {
+ File outfile = new File("app.out");
+
+ int retVal = JPackageHelper.execute(outfile, app);
+ if (retVal != 0) {
+ throw new AssertionError(
+ "Test application exited with error: " + retVal);
+ }
+
+ if (!outfile.exists()) {
+ throw new AssertionError(
+ "outfile: " + outfile + " was not created");
+ }
+
+ String output = Files.readString(outfile.toPath());
+ System.out.println("App output:");
+ System.out.print(output);
+
+ String[] result = JPackageHelper.splitAndFilter(output);
+ if (result.length != 4) {
+ throw new AssertionError(
+ "Unexpected number of lines: " + result.length
+ + " - output: " + output);
+ }
+
+ String nextWarning = WARNING_1;
+ if (!result[0].startsWith(nextWarning)){
+ nextWarning = WARNING_2;
+ if (!result[0].startsWith(WARNING_2)){
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ } else {
+ nextWarning = WARNING_1;
+ }
+ }
+
+ if (!result[1].startsWith(nextWarning)) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+
+ if (!result[2].trim().endsWith("jpackage test application")) {
+ throw new AssertionError("Unexpected result[2]: " + result[2]);
+ }
+
+ if (!result[3].trim().equals("args.length: 0")) {
+ throw new AssertionError("Unexpected result[3]: " + result[3]);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+ String output = JPackageHelper.executeCLI(true, CMD);
+ validate();
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ output = JPackageHelper.executeToolProvider(true, CMD);
+ validate();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/JavaOptionsModuleTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary jpackage create image with --java-options test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build JavaOptionsBase
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m JavaOptionsModuleTest
+ */
+public class JavaOptionsModuleTest {
+ private static final String OUTPUT = "output";
+
+ private static final String[] CMD = {
+ "--package-type", "app-image",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "input",
+ "--java-options", "TBD"};
+
+ private static final String[] CMD2 = {
+ "--package-type", "app-image",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "input",
+ "--java-options", "TBD",
+ "--java-options", "TBD",
+ "--java-options", "TBD"};
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloModule();
+
+ JavaOptionsBase.testCreateAppImageJavaOptions(CMD);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JavaOptionsBase.testCreateAppImageJavaOptionsToolProvider(CMD);
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JavaOptionsBase.testCreateAppImageJavaOptions2(CMD2);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JavaOptionsBase.testCreateAppImageJavaOptions2ToolProvider(CMD2);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/JavaOptionsTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary jpackage create image with --java-options test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build JavaOptionsBase
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m JavaOptionsTest
+ */
+public class JavaOptionsTest {
+ private static final String OUTPUT = "output";
+
+ private static final String[] CMD = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--java-options", "TBD"};
+
+ private static final String[] CMD2 = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--java-options", "TBD",
+ "--java-options", "TBD",
+ "--java-options", "TBD"};
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+ JavaOptionsBase.testCreateAppImageJavaOptions(CMD);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JavaOptionsBase.testCreateAppImageJavaOptionsToolProvider(CMD);
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JavaOptionsBase.testCreateAppImageJavaOptions2(CMD2);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JavaOptionsBase.testCreateAppImageJavaOptions2ToolProvider(CMD2);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/LicenseTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.LinuxHelper;
+import jdk.jpackage.test.Executor;
+import jdk.jpackage.test.TKit;
+
+/**
+ * Test --license-file parameter. Output of the test should be licensetest*.*
+ * package bundle. The output package should provide the same functionality as
+ * the default package and also incorporate license information from
+ * test/jdk/tools/jpackage/resources/license.txt file from OpenJDK repo.
+ *
+ * deb:
+ *
+ * Package should install license file /usr/share/doc/licensetest/copyright
+ * file.
+ *
+ * rpm:
+ *
+ * Package should install license file in
+ * %{_defaultlicensedir}/licensetest-1.0/license.txt file.
+ *
+ * Mac:
+ *
+ * Windows
+ *
+ * Installer should display license text matching contents of the license file
+ * during installation.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --license-file
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @compile LicenseTest.java
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=LicenseTest.testCommon
+ */
+
+/*
+ * @test
+ * @summary jpackage with --license-file
+ * @library ../helpers
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @compile LicenseTest.java
+ * @requires (os.family == "linux")
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=LicenseTest.testCustomDebianCopyright
+ * --jpt-run=LicenseTest.testCustomDebianCopyrightSubst
+ */
+
+public class LicenseTest {
+ public static void testCommon() {
+ new PackageTest().configureHelloApp()
+ .addInitializer(cmd -> {
+ Path licenseCopy = TKit.workDir().resolve(LICENSE_FILE.getFileName()).toAbsolutePath().normalize();
+ Files.copy(LICENSE_FILE, licenseCopy,
+ StandardCopyOption.REPLACE_EXISTING);
+ final Path basePath = Path.of(".").toAbsolutePath().normalize();
+ try {
+ licenseCopy = basePath.relativize(licenseCopy);
+ } catch (IllegalArgumentException ex) {
+ // May happen on Windows: java.lang.IllegalArgumentException: 'other' has different root
+ TKit.trace(String.format(
+ "Not using relative path to license file for --license-file parameter. Failed to relativize [%s] at [%s]",
+ licenseCopy, basePath));
+ ex.printStackTrace();
+ }
+ cmd.addArguments("--license-file", licenseCopy);
+ })
+ .forTypes(PackageType.LINUX)
+ .addBundleVerifier(cmd -> {
+ verifyLicenseFileInLinuxPackage(cmd, linuxLicenseFile(cmd));
+ })
+ .addInstallVerifier(cmd -> {
+ TKit.assertReadableFileExists(linuxLicenseFile(cmd));
+ })
+ .addUninstallVerifier(cmd -> {
+ verifyLicenseFileNotInstalledLinux(linuxLicenseFile(cmd));
+ })
+ .forTypes(PackageType.LINUX_DEB)
+ .addInstallVerifier(cmd -> {
+ verifyLicenseFileInstalledDebian(debLicenseFile(cmd));
+ })
+ .forTypes(PackageType.LINUX_RPM)
+ .addInstallVerifier(cmd -> {
+ verifyLicenseFileInstalledRpm(rpmLicenseFile(cmd));
+ })
+ .run();
+ }
+
+ public static void testCustomDebianCopyright() {
+ new CustomDebianCopyrightTest().run();
+ }
+
+ public static void testCustomDebianCopyrightSubst() {
+ new CustomDebianCopyrightTest().withSubstitution(true).run();
+ }
+
+ private static Path rpmLicenseFile(JPackageCommand cmd) {
+ final Path licenseRoot = Path.of(
+ new Executor()
+ .setExecutable("rpm")
+ .addArguments("--eval", "%{_defaultlicensedir}")
+ .executeAndGetFirstLineOfOutput());
+ final Path licensePath = licenseRoot.resolve(String.format("%s-%s",
+ LinuxHelper.getPackageName(cmd), cmd.version())).resolve(
+ LICENSE_FILE.getFileName());
+ return licensePath;
+ }
+
+ private static Path linuxLicenseFile(JPackageCommand cmd) {
+ cmd.verifyIsOfType(PackageType.LINUX);
+ switch (cmd.packageType()) {
+ case LINUX_DEB:
+ return debLicenseFile(cmd);
+
+ case LINUX_RPM:
+ return rpmLicenseFile(cmd);
+
+ default:
+ return null;
+ }
+ }
+
+ private static Path debLicenseFile(JPackageCommand cmd) {
+ return cmd.appInstallationDirectory().resolve("share/doc/copyright");
+ }
+
+ private static void verifyLicenseFileInLinuxPackage(JPackageCommand cmd,
+ Path expectedLicensePath) {
+ TKit.assertTrue(LinuxHelper.getPackageFiles(cmd).filter(path -> path.equals(
+ expectedLicensePath)).findFirst().orElse(null) != null,
+ String.format("Check license file [%s] is in %s package",
+ expectedLicensePath, LinuxHelper.getPackageName(cmd)));
+ }
+
+ private static void verifyLicenseFileInstalledRpm(Path licenseFile) throws
+ IOException {
+ TKit.assertStringListEquals(Files.readAllLines(LICENSE_FILE),
+ Files.readAllLines(licenseFile), String.format(
+ "Check contents of package license file [%s] are the same as contents of source license file [%s]",
+ licenseFile, LICENSE_FILE));
+ }
+
+ private static void verifyLicenseFileInstalledDebian(Path licenseFile)
+ throws IOException {
+
+ List<String> actualLines = Files.readAllLines(licenseFile).stream().dropWhile(
+ line -> !line.startsWith("License:")).collect(
+ Collectors.toList());
+ // Remove leading `License:` followed by the whitespace from the first text line.
+ actualLines.set(0, actualLines.get(0).split("\\s+", 2)[1]);
+
+ actualLines = DEBIAN_COPYRIGT_FILE_STRIPPER.apply(actualLines);
+
+ TKit.assertNotEquals(0, String.join("\n", actualLines).length(),
+ "Check stripped license text is not empty");
+
+ TKit.assertStringListEquals(DEBIAN_COPYRIGT_FILE_STRIPPER.apply(
+ Files.readAllLines(LICENSE_FILE)), actualLines, String.format(
+ "Check subset of package license file [%s] is a match of the source license file [%s]",
+ licenseFile, LICENSE_FILE));
+ }
+
+ private static void verifyLicenseFileNotInstalledLinux(Path licenseFile) {
+ TKit.assertPathExists(licenseFile.getParent(), false);
+ }
+
+ private static class CustomDebianCopyrightTest {
+ CustomDebianCopyrightTest() {
+ withSubstitution(false);
+ }
+
+ private List<String> licenseFileText(String copyright, String licenseText) {
+ List<String> lines = new ArrayList(List.of(
+ String.format("Copyright=%s", copyright),
+ "Foo",
+ "Bar",
+ "Buz"));
+ lines.addAll(List.of(licenseText.split("\\R", -1)));
+ return lines;
+ }
+
+ private List<String> licenseFileText() {
+ if (withSubstitution) {
+ return licenseFileText("APPLICATION_COPYRIGHT",
+ "APPLICATION_LICENSE_TEXT");
+ } else {
+ return expetedLicenseFileText();
+ }
+ }
+
+ private List<String> expetedLicenseFileText() {
+ return licenseFileText(copyright, licenseText);
+ }
+
+ CustomDebianCopyrightTest withSubstitution(boolean v) {
+ withSubstitution = v;
+ // Different values just to make easy to figure out from the test log which test was executed.
+ if (v) {
+ copyright = "Duke (C)";
+ licenseText = "The quick brown fox\n jumps over the lazy dog";
+ } else {
+ copyright = "Java (C)";
+ licenseText = "How vexingly quick daft zebras jump!";
+ }
+ return this;
+ }
+
+ void run() {
+ final Path srcLicenseFile = TKit.workDir().resolve("license");
+ new PackageTest().configureHelloApp().forTypes(PackageType.LINUX_DEB)
+ .addInitializer(cmd -> {
+ // Create source license file.
+ Files.write(srcLicenseFile, List.of(
+ licenseText.split("\\R", -1)));
+
+ cmd.setFakeRuntime();
+ cmd.setArgumentValue("--name", String.format("%s%s",
+ withSubstitution ? "CustomDebianCopyrightWithSubst" : "CustomDebianCopyright",
+ cmd.name()));
+ cmd.addArguments("--license-file", srcLicenseFile);
+ cmd.addArguments("--copyright", copyright);
+ cmd.addArguments("--resource-dir", RESOURCE_DIR);
+
+ // Create copyright template file in a resource dir.
+ Files.createDirectories(RESOURCE_DIR);
+ Files.write(RESOURCE_DIR.resolve("copyright"),
+ licenseFileText());
+ })
+ .addInstallVerifier(cmd -> {
+ Path installedLicenseFile = debLicenseFile(cmd);
+ TKit.assertStringListEquals(expetedLicenseFileText(),
+ DEBIAN_COPYRIGT_FILE_STRIPPER.apply(Files.readAllLines(
+ installedLicenseFile)), String.format(
+ "Check contents of package license file [%s] are the same as contents of source license file [%s]",
+ installedLicenseFile, srcLicenseFile));
+ })
+ .run();
+ }
+
+ private boolean withSubstitution;
+ private String copyright;
+ private String licenseText;
+
+ private final Path RESOURCE_DIR = TKit.workDir().resolve("resources");
+ }
+
+ private static final Path LICENSE_FILE = TKit.TEST_SRC_ROOT.resolve(
+ Path.of("resources", "license.txt"));
+
+ private static final Function<List<String>, List<String>> DEBIAN_COPYRIGT_FILE_STRIPPER = (lines) -> Arrays.asList(
+ String.join("\n", lines).stripTrailing().split("\n"));
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/MainClassAttributeTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.File;
+import java.nio.file.Files;
+
+/*
+ * @test
+ * @summary jpackage create image with no main class arguments and with main-class attribute
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m MainClassAttributeTest
+ */
+public class MainClassAttributeTest {
+ private static final String OUTPUT = "output";
+ private static final String app = JPackagePath.getApp();
+ private static final String appOutput = JPackagePath.getAppOutputFile();
+
+ private static final String[] CMD = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar"};
+
+ private static void validateResult(String[] result) throws Exception {
+ if (result.length != 2) {
+ throw new AssertionError(
+ "Unexpected number of lines: " + result.length);
+ }
+
+ if (!result[0].trim().equals("jpackage test application")) {
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ }
+
+ if (!result[1].trim().equals("args.length: 0")) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+ }
+
+ private static void validate() throws Exception {
+ int retVal = JPackageHelper.execute(null, app);
+ if (retVal != 0) {
+ throw new AssertionError(
+ "Test application exited with error: " + retVal);
+ }
+
+ File outfile = new File(appOutput);
+ if (!outfile.exists()) {
+ throw new AssertionError(appOutput + " was not created");
+ }
+
+ String output = Files.readString(outfile.toPath());
+ String[] result = output.split("\n");
+ validateResult(result);
+ }
+
+ private static void testMainClassAttribute() throws Exception {
+ JPackageHelper.executeCLI(true, CMD);
+ validate();
+ }
+
+ private static void testMainClassAttributeToolProvider() throws Exception {
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JPackageHelper.executeToolProvider(true, CMD);
+ validate();
+ }
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJarWithMainClass();
+ testMainClassAttribute();
+ testMainClassAttributeToolProvider();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/MainClassErrorTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.File;
+import java.nio.file.Files;
+
+/*
+ * @test
+ * @summary jpackage create image with no main class arguments and with main-class attribute
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m MainClassErrorTest
+ */
+public class MainClassErrorTest {
+ private static final String OUTPUT = "output";
+ private static final String app = JPackagePath.getApp();
+ private static final String appOutput = JPackagePath.getAppOutputFile();
+
+ private static final String[] CMD = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar"};
+
+ private static void validate(String output) throws Exception {
+ String[] result = JPackageHelper.splitAndFilter(output);
+ if (result.length != 2) {
+ throw new AssertionError(
+ "Unexpected number of lines: " + result.length);
+ }
+
+ if (!result[0].trim().contains("main class was not specified")) {
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ }
+
+ if (!result[1].trim().startsWith("Advice to fix: ")) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ validate(JPackageHelper.executeCLI(false, CMD));
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ validate(JPackageHelper.executeToolProvider(false, CMD));
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/MissingArgumentsTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+ /*
+ * @test
+ * @summary jpackage create image missing arguments test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m MissingArgumentsTest
+ */
+
+public class MissingArgumentsTest {
+ private static final String [] RESULT_1 = {"--input"};
+ private static final String [] CMD_1 = {
+ "--package-type", "app-image",
+ "--dest", "output",
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ };
+
+ private static final String [] RESULT_2 = {"--input", "--app-image"};
+ private static final String [] CMD_2 = {
+ "--package-type", "app-image",
+ "--package-type", "invalid-package-type",
+ "--dest", "output",
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ };
+
+ private static final String [] RESULT_3 = {"main class was not specified"};
+ private static final String [] CMD_3 = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", "output",
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ };
+
+ private static final String [] RESULT_4 = {"--main-jar"};
+ private static final String [] CMD_4 = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", "output",
+ "--name", "test",
+ "--main-class", "Hello",
+ };
+
+ private static final String [] RESULT_5 = {"--module-path", "--runtime-image"};
+ private static final String [] CMD_5 = {
+ "--package-type", "app-image",
+ "--dest", "output",
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ };
+
+ private static final String [] RESULT_6 = {"--module-path", "--runtime-image",
+ "--app-image"};
+ private static final String [] CMD_6 = {
+ "--package-type", "invalid-package-type",
+ "--dest", "output",
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ };
+
+ private static void validate(String output, String [] expected,
+ boolean single) throws Exception {
+ String[] result = JPackageHelper.splitAndFilter(output);
+ if (single && result.length != 1) {
+ System.err.println(output);
+ throw new AssertionError("Invalid number of lines in output: "
+ + result.length);
+ }
+
+ for (String s : expected) {
+ if (!result[0].contains(s)) {
+ System.err.println("Expected to contain: " + s);
+ System.err.println("Actual: " + result[0]);
+ throw new AssertionError("Unexpected error message");
+ }
+ }
+ }
+
+ private static void testMissingArg() throws Exception {
+ String output = JPackageHelper.executeCLI(false, CMD_1);
+ validate(output, RESULT_1, true);
+
+ output = JPackageHelper.executeCLI(false, CMD_2);
+ validate(output, RESULT_2, true);
+
+ output = JPackageHelper.executeCLI(false, CMD_3);
+ validate(output, RESULT_3, false);
+
+ output = JPackageHelper.executeCLI(false, CMD_4);
+ validate(output, RESULT_4, true);
+
+ output = JPackageHelper.executeCLI(false, CMD_5);
+ validate(output, RESULT_5, true);
+
+ output = JPackageHelper.executeCLI(false, CMD_6);
+ validate(output, RESULT_6, true);
+
+ }
+
+ private static void testMissingArgToolProvider() throws Exception {
+ String output = JPackageHelper.executeToolProvider(false, CMD_1);
+ validate(output, RESULT_1, true);
+
+ output = JPackageHelper.executeToolProvider(false, CMD_2);
+ validate(output, RESULT_2, true);
+
+ output = JPackageHelper.executeToolProvider(false, CMD_3);
+ validate(output, RESULT_3, false);
+
+ output = JPackageHelper.executeToolProvider(false, CMD_4);
+ validate(output, RESULT_4, true);
+
+ output = JPackageHelper.executeToolProvider(false, CMD_5);
+ validate(output, RESULT_5, true);
+
+ output = JPackageHelper.executeToolProvider(false, CMD_6);
+ validate(output, RESULT_6, true);
+ }
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+ testMissingArg();
+ testMissingArgToolProvider();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/ModuleMainClassErrorTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.File;
+import java.nio.file.Files;
+
+/*
+ * @test
+ * @summary jpackage create image with no main class arguments and with main-class attribute
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m ModuleMainClassErrorTest
+ */
+public class ModuleMainClassErrorTest {
+ private static final String OUTPUT = "output";
+ private static final String app = JPackagePath.getApp();
+ private static final String appOutput = JPackagePath.getAppOutputFile();
+
+ private static final String [] CMD1 = {
+ "--package-type", "app-image",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello",
+ "--module-path", "input"};
+
+ private static final String [] CMD2 = {
+ "--package-type", "app-image",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "input"};
+
+ private static void validate(String buildOutput) throws Exception {
+
+ File outfile = new File(appOutput);
+ int retVal = JPackageHelper.execute(outfile, app);
+ if (retVal != 0) {
+ throw new AssertionError(
+ "Test application exited with error: ");
+ }
+
+ if (!outfile.exists()) {
+ throw new AssertionError(appOutput + " was not created");
+ }
+ String output = Files.readString(outfile.toPath());
+ String[] result = output.split("\n");
+
+ if (!result[0].trim().equals("jpackage test application")) {
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ }
+
+ if (!result[1].trim().equals("args.length: 0")) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloModule();
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JPackageHelper.executeCLI(false, CMD1);
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JPackageHelper.executeToolProvider(false, CMD1);
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ validate(JPackageHelper.executeCLI(true, CMD2));
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ validate(JPackageHelper.executeToolProvider(true, CMD2));
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/ModulePathTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+ /*
+ * @test
+ * @summary jpackage create image module test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build Base
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m ModulePathTest
+ */
+
+import java.io.File;
+
+public class ModulePathTest {
+ private static final String OUTPUT = "output";
+
+ private static final String [] CMD1 = {
+ "--package-type", "app-image",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "input",
+ };
+
+ private static final String [] CMD2 = {
+ "--package-type", "app-image",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "input" + File.pathSeparator + "input-other",
+ };
+
+ private static final String [] CMD3 = {
+ "--package-type", "app-image",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--module", "com.hello/com.hello.Hello",
+ "--module-path", "input",
+ "--module-path", "input-other",
+ };
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloModule();
+ Base.testCreateAppImageToolProvider(CMD1);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ Base.testCreateAppImageToolProvider(CMD2);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ Base.testCreateAppImageToolProvider(CMD3);
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/ResourceTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.File;
+import java.nio.file.Files;
+
+/*
+ * @test
+ * @summary jpackage create image to verify --icon
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @requires ((os.family == "windows") | (os.family == "mac"))
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m ResourceTest
+ */
+public class ResourceTest {
+ private static final String OUTPUT = "output";
+ private static final String app = JPackagePath.getApp("icon");
+ private static final String appOutput = JPackagePath.getAppOutputFile();
+ private static final String resourceDir =
+ JPackagePath.getTestSrcRoot() + File.separator + "resources";
+
+ private static final String[] CMD = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--name", "icon",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--resource-dir", resourceDir,
+ "--dest", OUTPUT};
+
+ private static void validateResult(String[] result) throws Exception {
+ if (result.length != 2) {
+ throw new AssertionError(
+ "Unexpected number of lines: " + result.length);
+ }
+
+ if (!result[0].trim().equals("jpackage test application")) {
+ throw new AssertionError("Unexpected result[0]: " + result[0]);
+ }
+
+ if (!result[1].trim().equals("args.length: 0")) {
+ throw new AssertionError("Unexpected result[1]: " + result[1]);
+ }
+ }
+
+ private static void validate() throws Exception {
+ int retVal = JPackageHelper.execute(null, app);
+ if (retVal != 0) {
+ throw new AssertionError(
+ "Test application exited with error: " + retVal);
+ }
+
+ File outfile = new File(appOutput);
+ if (!outfile.exists()) {
+ throw new AssertionError(appOutput + " was not created");
+ }
+
+ String output = Files.readString(outfile.toPath());
+ String[] result = output.split("\n");
+ validateResult(result);
+ }
+
+ private static void validateIcon() throws Exception {
+ File origIcon = new File(getIconPath());
+ File icon = new File(JPackagePath.getAppIcon("icon"));
+ if (origIcon.length() != icon.length()) {
+ System.err.println("file: " + origIcon + " - origIcon.length(): "
+ + origIcon.length());
+ System.err.println("file: " + icon + " - icon.length(): "
+ + icon.length());
+ throw new AssertionError("Icons size does not match");
+ }
+ }
+
+ private static void testIcon() throws Exception {
+ JPackageHelper.executeCLI(true, CMD);
+ validate();
+ validateIcon();
+ }
+
+ private static void testIconToolProvider() throws Exception {
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JPackageHelper.executeToolProvider(true, CMD);
+ validate();
+ validateIcon();
+ }
+
+ private static String getResourcenPath() {
+ return JPackagePath.getTestSrcRoot() + File.separator + "resources";
+ }
+
+ private static String getIconPath() {
+ String ext = ".ico";
+ if (JPackageHelper.isOSX()) {
+ ext = ".icns";
+ } else if (JPackageHelper.isLinux()) {
+ ext = ".png";
+ }
+ return resourceDir + File.separator + "icon" + ext;
+ }
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+ testIcon();
+ testIconToolProvider();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/RuntimePackageTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.nio.file.Path;
+import java.util.Optional;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.JPackageCommand;
+
+/**
+ * Test --runtime-image parameter.
+ * Output of the test should be RuntimePackageTest*.* installer.
+ * The installer should install Java Runtime without an application.
+ * Installation directory should not have "app" subfolder and should not have
+ * an application launcher.
+ *
+ *
+ * Windows:
+ *
+ * Java runtime should be installed in %ProgramFiles%\RuntimePackageTest directory.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --runtime-image
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @comment Temporary disable for Linux and OSX until functionality implemented
+ * @requires (os.family != "mac")
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m RuntimePackageTest
+ */
+public class RuntimePackageTest {
+
+ public static void main(String[] args) {
+ TKit.run(args, () -> {
+ new PackageTest()
+ .addInitializer(cmd -> {
+ cmd.addArguments("--runtime-image", Optional.ofNullable(
+ JPackageCommand.DEFAULT_RUNTIME_IMAGE).orElse(Path.of(
+ System.getProperty("java.home"))));
+ // Remove --input parameter from jpackage command line as we don't
+ // create input directory in the test and jpackage fails
+ // if --input references non existant directory.
+ cmd.removeArgumentWithValue("--input");
+ })
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/SimplePackageTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+
+/**
+ * Simple platform specific packaging test. Output of the test should be
+ * simplepackagetest*.* package bundle.
+ *
+ * Windows:
+ *
+ * The installer should not have license text. It should not have an option
+ * to change the default installation directory.
+ * Test application should be installed in %ProgramFiles%\SimplePackageTest directory.
+ * Installer should install test app for all users (machine wide).
+ * Installer should not create any shortcuts.
+ */
+
+/*
+ * @test
+ * @summary Simple jpackage command run
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m SimplePackageTest
+ */
+public class SimplePackageTest {
+
+ public static void main(String[] args) {
+ TKit.run(args, () -> {
+ new PackageTest()
+ .configureHelloApp()
+ .addBundleDesktopIntegrationVerifier(false)
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/VerboseTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @summary jpackage create image verbose test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m VerboseTest
+ */
+public class VerboseTest {
+ private static final String OUTPUT = "output";
+ private static final String[] CMD = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ };
+
+ private static final String[] CMD_VERBOSE = {
+ "--package-type", "app-image",
+ "--input", "input",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ "--verbose"};
+
+ private static void validate(String result, String resultVerbose)
+ throws Exception {
+ String[] r = result.split("\n");
+ String[] rv = resultVerbose.split("\n");
+
+ if (r.length >= rv.length) {
+ System.err.println("r.length: " + r.length);
+ System.err.println(result);
+ System.err.println("rv.length: " + rv.length);
+ System.err.println(resultVerbose);
+ throw new AssertionError(
+ "non-verbose output is less or equal to verbose output");
+ }
+ }
+
+ private static void testCreateAppImage() throws Exception {
+ String result = JPackageHelper.executeCLI(true, CMD);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ String resultVerbose = JPackageHelper.executeCLI(true, CMD_VERBOSE);
+ validate(result, resultVerbose);
+ }
+
+ private static void testCreateAppImageToolProvider() throws Exception {
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ String result = JPackageHelper.executeToolProvider(true, CMD);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ String resultVerbose =
+ JPackageHelper.executeToolProvider(true, CMD_VERBOSE);
+ validate(result, resultVerbose);
+ }
+
+ public static void main(String[] args) throws Exception {
+ JPackageHelper.createHelloImageJar();
+ testCreateAppImage();
+ testCreateAppImageToolProvider();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/WithSpaceTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.io.File;
+
+ /*
+ * @test
+ * @summary jpackage create image test
+ * @library ../helpers
+ * @build JPackageHelper
+ * @build JPackagePath
+ * @build Base
+ * @modules jdk.jpackage
+ * @run main/othervm -Xmx512m WithSpaceTest
+ */
+public class WithSpaceTest {
+ private static final String OUTPUT = "output";
+
+ private static final String [] CMD1 = {
+ "--package-type", "app-image",
+ "--input", "input dir",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "hello.jar",
+ "--main-class", "Hello",
+ };
+
+ private static final String [] CMD2 = {
+ "--package-type", "app-image",
+ "--input", "input dir2",
+ "--dest", OUTPUT,
+ "--name", "test",
+ "--main-jar", "sub dir/hello.jar",
+ "--main-class", "Hello",
+ };
+
+ public static void main(String[] args) throws Exception {
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JPackageHelper.createHelloImageJar("input dir");
+ Base.testCreateAppImage(CMD1);
+
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ JPackageHelper.createHelloImageJar(
+ "input dir2" + File.separator + "sub dir");
+
+ Base.testCreateAppImageToolProvider(CMD2);
+ JPackageHelper.deleteOutputFolder(OUTPUT);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/jdk/jpackage/tests/BasicTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,254 @@
+/*
+ * 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.
+ *
+ * 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.tests;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.stream.Collectors;
+import jdk.jpackage.test.Executor;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.JavaTool;
+import jdk.jpackage.test.HelloApp;
+import jdk.jpackage.test.Annotations.*;
+
+/*
+ * @test
+ * @summary jpackage basic testing
+ * @library ../../../../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.jpackage
+ * @compile BasicTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=jdk.jpackage.tests.BasicTest
+ */
+
+public class BasicTest {
+ @Test
+ public void testNoArgs() {
+ List<String> output = JPackageCommand.filterOutput(
+ createVanillaJPackageCommand().executeAndGetOutput());
+ TKit.assertStringListEquals(List.of("Usage: jpackage <mode> <options>",
+ "Use jpackage --help (or -h) for a list of possible options"),
+ output, "Check jpackage output");
+ }
+
+ @Test
+ public void testVersion() {
+ List<String> output = JPackageCommand.filterOutput(
+ createVanillaJPackageCommand()
+ .addArgument("--version")
+ .executeAndGetOutput());
+ TKit.assertStringListEquals(List.of(System.getProperty("java.version")),
+ output, "Check jpackage output");
+ }
+
+ @Test
+ public void testNoName() {
+ final String mainClassName = "Greetings";
+
+ JPackageCommand cmd = new JPackageCommand()
+ .helloAppImage(mainClassName)
+ .removeArgumentWithValue("--name");
+
+ Path expectedImageDir = cmd.outputDir().resolve(mainClassName);
+ if (TKit.isOSX()) {
+ expectedImageDir = expectedImageDir.getParent().resolve(
+ expectedImageDir.getFileName().toString() + ".app");
+ }
+
+ cmd.executeAndAssertHelloAppImageCreated();
+ TKit.assertEquals(expectedImageDir.toAbsolutePath().normalize().toString(),
+ cmd.appImage().toAbsolutePath().normalize().toString(),
+ String.format(
+ "Check [%s] directory is filled with application image data",
+ expectedImageDir));
+ }
+
+ @Test
+ public void testApp() {
+ new JPackageCommand()
+ .helloAppImage()
+ .executeAndAssertHelloAppImageCreated();
+ }
+
+ @Test
+ public void testModularApp() {
+ new JPackageCommand()
+ .helloAppImage("com.other/com.other.Hello")
+ .executeAndAssertHelloAppImageCreated();
+ }
+
+ @Test
+// @Parameter("ALL-MODULE-PATH") ; This test fails
+ @Parameter("ALL-DEFAULT")
+ public void testAddModules(String addModulesArg) {
+ JPackageCommand cmd = new JPackageCommand()
+ .helloAppImage("com.other/com.other.Hello");
+ if (!addModulesArg.isEmpty()) {
+ cmd.addArguments("--add-modules", addModulesArg);
+ }
+ cmd.executeAndAssertHelloAppImageCreated();
+ }
+
+ /**
+ * Test --temp option. Doesn't make much sense for app image as temporary
+ * directory is used only on Windows.
+ * @throws IOException
+ */
+// @Test
+ public void testTemp() throws IOException {
+ JPackageCommand cmd = new JPackageCommand().helloAppImage();
+ TKit.withTempDirectory("temp-root", tempDir -> {
+ cmd.addArguments("--temp", tempDir);
+
+ cmd.executeAndAssertHelloAppImageCreated();
+
+ // Check jpackage actually used the supplied directory.
+ TKit.assertNotEquals(0, tempDir.toFile().list().length,
+ String.format(
+ "Check jpackage wrote some data in the supplied temporary directory [%s]",
+ tempDir));
+
+ // Temporary directory should not be empty,
+ // jpackage should exit with error.
+ cmd.execute().assertExitCodeIs(1);
+ });
+ }
+
+ @Test
+ public void testAtFile() throws IOException {
+ JPackageCommand cmd = new JPackageCommand().helloAppImage();
+
+ // Init options file with the list of options configured
+ // for JPackageCommand instance.
+ final Path optionsFile = TKit.workDir().resolve("options");
+ Files.write(optionsFile,
+ List.of(String.join(" ", cmd.getAllArguments())));
+
+ // Build app jar file.
+ cmd.executePrerequisiteActions();
+
+ // Make sure output directory is empty. Normally JPackageCommand would
+ // do this automatically.
+ TKit.deleteDirectoryContentsRecursive(cmd.outputDir());
+
+ // Instead of running jpackage command through configured
+ // JPackageCommand instance, run vanilla jpackage command with @ file.
+ createVanillaJPackageCommand()
+ .addArgument(String.format("@%s", optionsFile))
+ .execute().assertExitCodeIsZero();
+
+ // Verify output of jpackage command.
+ cmd.assertImageCreated();
+ HelloApp.executeLauncherAndVerifyOutput(cmd);
+ }
+
+ @Parameter("Hello")
+ @Parameter("com.foo/com.foo.main.Aloha")
+ @Test
+ public void testJLinkRuntime(String javaAppDesc) {
+ JPackageCommand cmd = new JPackageCommand().helloAppImage(javaAppDesc);
+
+ // If `--module` parameter was set on jpackage command line, get its
+ // value and extract module name.
+ // E.g.: foo.bar2/foo.bar.Buz -> foo.bar2
+ // Note: HelloApp class manages `--module` parameter on jpackage command line
+ final String moduleName = cmd.getArgumentValue("--module", () -> null,
+ (v) -> v.split("/", 2)[0]);
+
+ if (moduleName != null) {
+ // Build module jar.
+ cmd.executePrerequisiteActions();
+ }
+
+ TKit.withTempDirectory("runtime", runtimeDir -> {
+ TKit.deleteDirectoryRecursive(runtimeDir, String.format(
+ "Delete [%s] output directory for jlink command", runtimeDir));
+ Executor jlink = createJavaToolCommand(JavaTool.JLINK)
+ .saveOutput(false)
+ .addArguments(
+ "--add-modules", "java.base",
+ "--output", runtimeDir.toString(),
+ "--strip-debug",
+ "--no-header-files",
+ "--no-man-pages");
+
+ if (moduleName != null) {
+ jlink.addArguments("--add-modules", moduleName, "--module-path",
+ Path.of(cmd.getArgumentValue("--module-path")).resolve(
+ "hello.jar").toString());
+ }
+
+ jlink.execute().assertExitCodeIsZero();
+
+ cmd.addArguments("--runtime-image", runtimeDir);
+ cmd.executeAndAssertHelloAppImageCreated();
+
+ final Path appImageRuntimePath = cmd.appImage().resolve(
+ cmd.appRuntimeDirectoryInAppImage());
+
+ // This is an overkill to list modules in jlink output as we have
+ // already verified that Java app is functional and thus app's runtime
+ // is likely to be OK, but let's double check.
+ List<String> moduleList = new Executor().dumpOutput().setExecutable(
+ appImageRuntimePath.resolve(
+ JPackageCommand.relativePathInRuntime(JavaTool.JAVA))).addArguments(
+ "--list-modules").executeAndGetOutput().stream().sorted().collect(
+ Collectors.toList());
+
+ List<String> expectedModules = new ArrayList<>();
+ expectedModules.add(String.format("java.base@%s",
+ System.getProperty("java.version")));
+ if (moduleName != null) {
+ expectedModules.add(moduleName);
+ }
+ expectedModules = expectedModules.stream().sorted().collect(
+ Collectors.toList());
+
+ TKit.assertStringListEquals(expectedModules, moduleList,
+ String.format(
+ "Check modules in application image runtime directory at [%s]",
+ appImageRuntimePath));
+ });
+ }
+
+ private static Executor createVanillaJPackageCommand() {
+ return createJavaToolCommand(JavaTool.JPACKAGE);
+ }
+
+ private static Executor createJavaToolCommand(JavaTool tool) {
+ Executor exec = new Executor().dumpOutput().saveOutput();
+ if (new JPackageCommand().isWithToolProvider()) {
+ exec.setToolProvider(tool.asToolProvider());
+ } else {
+ exec.setExecutable(tool);
+ }
+
+ return exec;
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/test_jpackage.sh Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,68 @@
+#!/bin/bash
+
+#
+# Complete testing of jpackage platform-specific packaging.
+#
+# The script does the following:
+# 1. Create packages.
+# 2. Install created packages.
+# 3. Verifies packages are installed.
+# 4. Uninstall created packages.
+# 5. Verifies packages are uninstalled.
+#
+# For the list of accepted command line arguments see `run_tests.sh` script.
+#
+
+# Fail fast
+set -e; set -o pipefail;
+
+# Script debug
+dry_run=${JPACKAGE_TEST_DRY_RUN}
+
+# Default directory where jpackage should write bundle files
+output_dir=~/jpackage_bundles
+
+
+set_args ()
+{
+ args=()
+ local arg_is_output_dir=
+ local arg_is_mode=
+ local output_dir_set=
+ for arg in "$@"; do
+ if [ "$arg" == "-o" ]; then
+ arg_is_output_dir=yes
+ output_dir_set=yes
+ elif [ "$arg" == "-m" ]; then
+ arg_is_mode=yes
+ continue
+ elif [ -n "$arg_is_output_dir" ]; then
+ arg_is_output_dir=
+ output_dir="$arg"
+ elif [ -n "$arg_is_mode" ]; then
+ arg_is_mode=
+ continue
+ fi
+
+ args+=( "$arg" )
+ done
+ [ -n "$output_dir_set" ] || args=( -o "$output_dir" "${args[@]}" )
+}
+
+
+exec_command ()
+{
+ if [ -n "$dry_run" ]; then
+ echo "$@"
+ else
+ eval "$@"
+ fi
+}
+
+set_args "$@"
+basedir="$(dirname $0)"
+exec_command "$basedir/run_tests.sh" -m create "${args[@]}"
+exec_command "$basedir/manage_packages.sh" -d "$output_dir"
+exec_command "$basedir/run_tests.sh" -m verify-install "${args[@]}"
+exec_command "$basedir/manage_packages.sh" -d "$output_dir" -u
+exec_command "$basedir/run_tests.sh" -m verify-uninstall "${args[@]}"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinConsoleTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import java.nio.file.Path;
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.Annotations.Parameter;
+
+/*
+ * @test
+ * @summary jpackage with --win-console
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.jpackage
+ * @compile WinConsoleTest.java
+ *
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-before-run=jdk.jpackage.test.JPackageCommand.useToolProviderByDefault
+ * --jpt-run=WinConsoleTest
+ *
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ * --jpt-run=WinConsoleTest
+ */
+public class WinConsoleTest {
+
+ @Test
+ @Parameter("true")
+ @Parameter("false")
+ public static void test(boolean withWinConsole) throws IOException {
+ JPackageCommand cmd = JPackageCommand.helloAppImage();
+ if (!withWinConsole) {
+ cmd.removeArgument("--win-console");
+ }
+ cmd.executeAndAssertHelloAppImageCreated();
+ checkSubsystem(cmd.appImage().resolve(cmd.launcherPathInAppImage()),
+ withWinConsole);
+ }
+
+ private static void checkSubsystem(Path path, boolean isConsole) throws
+ IOException {
+ final int subsystemGui = 2;
+ final int subsystemConsole = 3;
+ final int bufferSize = 512;
+
+ final int expectedSubsystem = isConsole ? subsystemConsole : subsystemGui;
+
+ try (InputStream inputStream = new FileInputStream(path.toString())) {
+ byte[] bytes = new byte[bufferSize];
+ TKit.assertEquals(bufferSize, inputStream.read(bytes),
+ String.format("Check %d bytes were read from %s file",
+ bufferSize, path));
+
+ // Check PE header for console or Win GUI app.
+ // https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_image_nt_headers
+ for (int i = 0; i < (bytes.length - 4); i++) {
+ if (bytes[i] == 0x50 && bytes[i + 1] == 0x45
+ && bytes[i + 2] == 0x0 && bytes[i + 3] == 0x0) {
+
+ // Signature, File Header and subsystem offset.
+ i = i + 4 + 20 + 68;
+ byte subsystem = bytes[i];
+ TKit.assertEquals(expectedSubsystem, subsystem,
+ String.format("Check subsystem of PE [%s] file",
+ path));
+ return;
+ }
+ }
+ }
+
+ TKit.assertUnexpected(String.format(
+ "Subsystem not found in PE header of [%s] file", path));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinDirChooserTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+
+/**
+ * Test --win-dir-chooser parameter. Output of the test should be
+ * WinDirChooserTest-1.0.exe installer. The output installer should provide the
+ * same functionality as the default installer (see description of the default
+ * installer in SimplePackageTest.java) plus provide an option for user to
+ * change the default installation directory.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --win-dir-chooser
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m WinDirChooserTest
+ */
+
+public class WinDirChooserTest {
+ public static void main(String[] args) {
+ TKit.run(args, () -> {
+ new PackageTest()
+ .forTypes(PackageType.WINDOWS)
+ .configureHelloApp()
+ .addInitializer(cmd -> cmd.addArgument("--win-dir-chooser")).run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinMenuGroupTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+
+/**
+ * Test --win-menu and --win-menu-group parameters.
+ * Output of the test should be WinMenuGroupTest-1.0.exe installer.
+ * The output installer should provide the
+ * same functionality as the default installer (see description of the default
+ * installer in SimplePackageTest.java) plus
+ * it should create a shortcut for application launcher in Windows Menu in
+ * "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\WinMenuGroupTest_MenuGroup" folder.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --win-menu and --win-menu-group
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m WinMenuGroupTest
+ */
+
+public class WinMenuGroupTest {
+ public static void main(String[] args) {
+ TKit.run(args, () -> {
+ new PackageTest()
+ .forTypes(PackageType.WINDOWS)
+ .configureHelloApp()
+ .addInitializer(cmd -> cmd.addArguments(
+ "--win-menu", "--win-menu-group", "WinMenuGroupTest_MenuGroup"))
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinMenuTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+
+/**
+ * Test --win-menu parameter. Output of the test should be WinMenuTest-1.0.exe
+ * installer. The output installer should provide the same functionality as the
+ * default installer (see description of the default installer in
+ * SimplePackageTest.java), except it should create a menu shortcut.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --win-menu
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m WinMenuTest
+ */
+
+public class WinMenuTest {
+ public static void main(String[] args) {
+ TKit.run(args, () -> {
+ new PackageTest()
+ .forTypes(PackageType.WINDOWS)
+ .configureHelloApp()
+ .addInitializer(cmd -> cmd.addArgument("--win-menu")).run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinPerUserInstallTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+
+/**
+ * Test --win-per-user-install, --win-menu, --win-menu-group parameters.
+ * Output of the test should be WinPerUserInstallTest-1.0.exe installer. The
+ * output installer should provide the same functionality as the default
+ * installer (see description of the default installer in
+ * SimplePackageTest.java) plus it should create application menu in Windows
+ * Menu and installation should be per user and not machine wide.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --win-per-user-install, --win-menu, --win-menu-group
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m WinPerUserInstallTest
+ */
+
+public class WinPerUserInstallTest {
+ public static void main(String[] args) {
+ TKit.run(args, () -> {
+ new PackageTest()
+ .forTypes(PackageType.WINDOWS)
+ .configureHelloApp()
+ .addInitializer(cmd -> cmd.addArguments(
+ "--win-menu",
+ "--win-menu-group", "WinPerUserInstallTest_MenuGroup",
+ "--win-per-user-install"))
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinShortcutTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018, 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.
+ *
+ * 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.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+
+/**
+ * Test --win-shortcut parameter. Output of the test should be
+ * WinShortcutTest-1.0.exe installer. The output installer should provide the
+ * same functionality as the default installer (see description of the default
+ * installer in SimplePackageTest.java) plus install application shortcut on the
+ * desktop.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --win-shortcut
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m WinShortcutTest
+ */
+
+public class WinShortcutTest {
+ public static void main(String[] args) {
+ TKit.run(args, () -> {
+ new PackageTest()
+ .forTypes(PackageType.WINDOWS)
+ .configureHelloApp()
+ .addInitializer(cmd -> cmd.addArgument("--win-shortcut"))
+ .run();
+ });
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinUpgradeUUIDTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+
+/**
+ * Test both --win-upgrade-uuid and --app-version parameters. Output of the test
+ * should be WinUpgradeUUIDTest-1.0.exe and WinUpgradeUUIDTest-2.0.exe
+ * installers. Both output installers should provide the same functionality as
+ * the default installer (see description of the default installer in
+ * SimplePackageTest.java) but have the same product code and different
+ * versions. Running WinUpgradeUUIDTest-2.0.exe installer should automatically
+ * uninstall older version of the test application previously installed with
+ * WinUpgradeUUIDTest-1.0.exe installer.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --win-upgrade-uuid and --app-version
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.jpackage/jdk.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m WinUpgradeUUIDTest
+ */
+
+public class WinUpgradeUUIDTest {
+ public static void main(String[] args) {
+ TKit.run(args, () -> {
+ PackageTest test = init();
+ if (test.getAction() != PackageTest.Action.VERIFY_INSTALL) {
+ test.run();
+ }
+
+ test = init();
+ test.addInitializer(cmd -> {
+ cmd.setArgumentValue("--app-version", "2.0");
+ cmd.setArgumentValue("--arguments", "bar");
+ });
+ test.run();
+ });
+ }
+
+ private static PackageTest init() {
+ return new PackageTest()
+ .forTypes(PackageType.WINDOWS)
+ .configureHelloApp()
+ .addInitializer(cmd -> cmd.addArguments("--win-upgrade-uuid",
+ "F0B18E75-52AD-41A2-BC86-6BE4FCD50BEB"));
+ }
+}
--- a/test/jdk/tools/launcher/HelpFlagsTest.java Tue Oct 01 20:07:30 2019 -0700
+++ b/test/jdk/tools/launcher/HelpFlagsTest.java Tue Oct 01 23:54:39 2019 -0400
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@@ -156,13 +156,12 @@
new ToolHelpSpec("jstatd", 1, 1, 1, 0, 0, 0, 1), // -?, -h, --help
new ToolHelpSpec("keytool", 1, 1, 1, 0, 1, 0, 1), // none, prints help message anyways.
new ToolHelpSpec("pack200", 1, 1, 1, 0, 1, 0, 2), // -?, -h, --help, -help accepted but not documented.
- new ToolHelpSpec("rmic", 0, 0, 0, 0, 0, 0, 1), // none, pirnts help message anyways.
+ new ToolHelpSpec("rmic", 0, 0, 0, 0, 0, 0, 1), // none, prints help message anyways.
new ToolHelpSpec("rmid", 0, 0, 0, 0, 0, 0, 1), // none, prints help message anyways.
new ToolHelpSpec("rmiregistry", 0, 0, 0, 0, 0, 0, 1), // none, prints help message anyways.
new ToolHelpSpec("serialver", 0, 0, 0, 0, 0, 0, 1), // none, prints help message anyways.
new ToolHelpSpec("unpack200", 1, 1, 1, 0, 1, 0, 2), // -?, -h, --help, -help accepted but not documented.
- // Oracle proprietary tools:
- new ToolHelpSpec("javapackager",0, 0, 0, 0, 1, 0, 255), // -help accepted but not documented.
+ new ToolHelpSpec("jpackage", 0, 1, 1, 0, 0, 1, 1), // -h, --help,
};
// Returns true if the file is not a tool.
--- a/test/jdk/tools/launcher/VersionCheck.java Tue Oct 01 20:07:30 2019 -0700
+++ b/test/jdk/tools/launcher/VersionCheck.java Tue Oct 01 23:54:39 2019 -0400
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 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
@@ -62,7 +62,7 @@
"jmc",
"jmc.ini",
"jweblauncher",
- "packager",
+ "jpackage",
"ssvagent",
"unpack200",
};
@@ -108,7 +108,7 @@
"klist",
"ktab",
"pack200",
- "packager",
+ "jpackage",
"rmic",
"rmid",
"rmiregistry",