Merge JDK-8200758-branch
authorherrick
Tue, 12 Nov 2019 10:18:11 -0500
branchJDK-8200758-branch
changeset 59033 5ac41b9db9da
parent 59031 af9779f2d6bf (diff)
parent 59029 3786a0962570 (current diff)
child 59034 76bec73a91f0
Merge
--- a/make/CompileDemos.gmk	Fri Nov 08 11:15:16 2019 +0000
+++ b/make/CompileDemos.gmk	Tue Nov 12 10:18:11 2019 -0500
@@ -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	Fri Nov 08 11:15:16 2019 +0000
+++ b/make/CompileJavaModules.gmk	Tue Nov 12 10:18:11 2019 -0500
@@ -380,6 +380,13 @@
 
 ################################################################################
 
+jdk.incubator.jpackage_COPY += .gif .png .txt .spec .script .prerm .preinst .postrm .postinst .list .sh \
+    .desktop .copyright .control .plist .template .icns .scpt .entitlements .wxs .wxl .wxi .ico .bmp
+
+jdk.incubator.jpackage_CLEAN += .properties
+
+################################################################################
+
 jdk.jconsole_COPY += .gif .png
 
 jdk.jconsole_CLEAN_FILES += $(wildcard \
--- a/make/common/Modules.gmk	Fri Nov 08 11:15:16 2019 +0000
+++ b/make/common/Modules.gmk	Tue Nov 12 10:18:11 2019 -0500
@@ -128,6 +128,7 @@
 
 JRE_TOOL_MODULES += \
     jdk.jdwp.agent \
+    jdk.incubator.jpackage \
     jdk.pack \
     jdk.scripting.nashorn.shell \
     #
@@ -149,6 +150,7 @@
     jdk.editpad \
     jdk.hotspot.agent \
     jdk.httpserver \
+    jdk.incubator.jpackage \
     jdk.jartool \
     jdk.javadoc \
     jdk.jcmd \
@@ -243,6 +245,13 @@
 endif
 
 ################################################################################
+# jpackage is only on windows, macosx, and linux
+
+ifeq ($(call isTargetOs, windows macosx linux), false)
+  MODULES_FILTER += jdk.incubator.jpackage
+endif
+
+################################################################################
 # Module list macros
 
 # Use append so that the custom extension may add to these variables
--- a/make/common/NativeCompilation.gmk	Fri Nov 08 11:15:16 2019 +0000
+++ b/make/common/NativeCompilation.gmk	Tue Nov 12 10:18:11 2019 -0500
@@ -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!
@@ -533,8 +534,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)))
 
@@ -911,30 +910,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,
@@ -956,14 +956,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
@@ -999,6 +999,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))
@@ -1100,7 +1101,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.incubator.jpackage.gmk	Tue Nov 12 10:18:11 2019 -0500
@@ -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.
+#
+
+include LauncherCommon.gmk
+
+$(eval $(call SetupBuildLauncher, jpackage, \
+    MAIN_CLASS := jdk.incubator.jpackage.main.Main, \
+))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/make/lib/Lib-jdk.incubator.jpackage.gmk	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,140 @@
+#
+# 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/incubator/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)
+
+JPACKAGE_APPLAUNCHER_SRC := \
+    $(TOPDIR)/src/jdk.incubator.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/incubator/jpackage/internal/resources, \
+    SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/jpackageapplauncher, \
+    SRC := $(JPACKAGE_APPLAUNCHER_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)
+
+################################################################################
+
+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/incubator/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)
+
+  # Build exe installer wrapper for msi installer
+  $(eval $(call SetupJdkExecutable, BUILD_JPACKAGE_MSIWRAPPER, \
+      NAME := msiwrapper, \
+      OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/incubator/jpackage/internal/resources, \
+      SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/msiwrapper, \
+      SRC := $(TOPDIR)/src/jdk.incubator.jpackage/$(OPENJDK_TARGET_OS)/native/msiwrapper, \
+      EXTRA_FILES := $(addprefix $(TOPDIR)/src/jdk.incubator.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.incubator.jpackage/$(OPENJDK_TARGET_OS)/native/, msiwrapper libjpackage), \
+      CFLAGS_windows := -EHsc -DUNICODE -D_UNICODE, \
+      LDFLAGS := $(LDFLAGS_JDKEXE), \
+      LIBS := $(LIBCXX), \
+  ))
+
+  TARGETS += $(BUILD_JPACKAGE_MSIWRAPPER)
+
+  # Build non-console version of launcher
+  $(eval $(call SetupJdkExecutable, BUILD_JPACKAGE_APPLAUNCHERWEXE, \
+      NAME := jpackageapplauncherw, \
+      OUTPUT_DIR := $(JDK_OUTPUTDIR)/modules/$(MODULE)/jdk/incubator/jpackage/internal/resources, \
+      SYMBOLS_DIR := $(SUPPORT_OUTPUTDIR)/native/$(MODULE)/jpackageapplauncherw, \
+      SRC := $(JPACKAGE_APPLAUNCHER_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/src/demo/share/jpackage/JNLPConverter/README.txt	Tue Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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="&lt;nothing&gt;"/>
+        <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 &quot;${ant.java.version}"/>
+        <condition property="have-jdk-older-than-1.4">
+            <or>
+                <contains string="${version-output}" substring="java version &quot;1.0"/>
+                <contains string="${version-output}" substring="java version &quot;1.1"/>
+                <contains string="${version-output}" substring="java version &quot;1.2"/>
+                <contains string="${version-output}" substring="java version &quot;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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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/java.base/share/classes/module-info.java	Fri Nov 08 11:15:16 2019 +0000
+++ b/src/java.base/share/classes/module-info.java	Tue Nov 12 10:18:11 2019 -0500
@@ -203,7 +203,8 @@
         java.management.rmi,
         jdk.jartool,
         jdk.jfr,
-        jdk.jlink;
+        jdk.jlink,
+        jdk.incubator.jpackage;
     exports jdk.internal.perf to
         java.management,
         jdk.management.agent,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/DesktopIntegration.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,503 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.awt.image.BufferedImage;
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.imageio.ImageIO;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import static jdk.incubator.jpackage.internal.LinuxAppBundler.ICON_PNG;
+import static jdk.incubator.jpackage.internal.LinuxAppImageBuilder.DEFAULT_ICON;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+/**
+ * Helper to create files for desktop integration.
+ */
+final class DesktopIntegration {
+
+    static final String DESKTOP_COMMANDS_INSTALL = "DESKTOP_COMMANDS_INSTALL";
+    static final String DESKTOP_COMMANDS_UNINSTALL = "DESKTOP_COMMANDS_UNINSTALL";
+    static final String UTILITY_SCRIPTS = "UTILITY_SCRIPTS";
+
+    DesktopIntegration(PlatformPackage thePackage,
+            Map<String, ? super Object> params) {
+
+        associations = FileAssociation.fetchFrom(params).stream()
+                .filter(fa -> !fa.mimeTypes.isEmpty())
+                .map(LinuxFileAssociation::new)
+                .collect(Collectors.toUnmodifiableList());
+
+        launchers = ADD_LAUNCHERS.fetchFrom(params);
+
+        this.thePackage = thePackage;
+
+        final File customIconFile = ICON_PNG.fetchFrom(params);
+
+        iconResource = createResource(DEFAULT_ICON, params)
+                .setCategory(I18N.getString("resource.menu-icon"))
+                .setExternal(customIconFile);
+        desktopFileResource = createResource("template.desktop", params)
+                .setCategory(I18N.getString("resource.menu-shortcut-descriptor"))
+                .setPublicName(APP_NAME.fetchFrom(params) + ".desktop");
+
+        // 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(APP_NAME.fetchFrom(params)
+                    + IOUtils.getSuffix(Path.of(DEFAULT_ICON)));
+        } 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 {
+        associations.forEach(assoc -> assoc.data.verify());
+
+        if (iconFile != null) {
+            // Create application icon file.
+            iconResource.saveToFile(iconFile.srcPath());
+        }
+
+        Map<String, String> data = new HashMap<>(desktopFileData);
+
+        final ShellCommands shellCommands;
+        if (desktopFile != null) {
+            // Create application desktop description file.
+            createDesktopFile(data);
+
+            // Shell commands will be created only if desktop file
+            // should be installed.
+            shellCommands = new ShellCommands();
+        } else {
+            shellCommands = null;
+        }
+
+        if (!associations.isEmpty()) {
+            // Create XML file with mime types corresponding to file associations.
+            createFileAssociationsMimeInfoFile();
+
+            shellCommands.setFileAssociations();
+
+            // Create icon files corresponding to file associations
+            addFileAssociationIconFiles(shellCommands);
+        }
+
+        // Create shell commands to install/uninstall integration with desktop of the app.
+        if (shellCommands != null) {
+            shellCommands.applyTo(data);
+        }
+
+        boolean needCleanupScripts = !associations.isEmpty();
+
+        // Take care of additional launchers if there are any.
+        // Process every additional launcher as the main application launcher.
+        // Collect shell commands to install/uninstall integration with desktop
+        // of the additional launchers and append them to the corresponding
+        // commands of the main launcher.
+        List<String> installShellCmds = new ArrayList<>(Arrays.asList(
+                data.get(DESKTOP_COMMANDS_INSTALL)));
+        List<String> uninstallShellCmds = new ArrayList<>(Arrays.asList(
+                data.get(DESKTOP_COMMANDS_UNINSTALL)));
+        for (var integration: nestedIntegrations) {
+            if (!integration.associations.isEmpty()) {
+                needCleanupScripts = true;
+            }
+
+            Map<String, String> launcherData = integration.create();
+
+            installShellCmds.add(launcherData.get(DESKTOP_COMMANDS_INSTALL));
+            uninstallShellCmds.add(launcherData.get(
+                    DESKTOP_COMMANDS_UNINSTALL));
+        }
+
+        data.put(DESKTOP_COMMANDS_INSTALL, stringifyShellCommands(
+                installShellCmds));
+        data.put(DESKTOP_COMMANDS_UNINSTALL, stringifyShellCommands(
+                uninstallShellCmds));
+
+        if (needCleanupScripts) {
+            // Pull in utils.sh scrips library.
+            try (InputStream is = OverridableResource.readDefault("utils.sh");
+                    InputStreamReader isr = new InputStreamReader(is);
+                    BufferedReader reader = new BufferedReader(isr)) {
+                data.put(UTILITY_SCRIPTS, reader.lines().collect(
+                        Collectors.joining(System.lineSeparator())));
+            }
+        } else {
+            data.put(UTILITY_SCRIPTS, "");
+        }
+
+        return data;
+    }
+
+    private List<String> requiredPackagesSelf() {
+        if (desktopFile != null) {
+            return List.of("xdg-utils");
+        }
+        return Collections.emptyList();
+    }
+
+    private Map<String, String> createDataForDesktopFile(
+            Map<String, ? super Object> params) {
+        Map<String, String> data = new HashMap<>();
+        data.put("APPLICATION_NAME", APP_NAME.fetchFrom(params));
+        data.put("APPLICATION_DESCRIPTION", DESCRIPTION.fetchFrom(params));
+        data.put("APPLICATION_ICON",
+                iconFile != null ? iconFile.installPath().toString() : null);
+        data.put("DEPLOY_BUNDLE_CATEGORY", MENU_GROUP.fetchFrom(params));
+        data.put("APPLICATION_LAUNCHER",
+                thePackage.installedApplicationLayout().launchersDirectory().resolve(
+                        LinuxAppImageBuilder.getLauncherName(params)).toString());
+
+        return data;
+    }
+
+    /**
+     * Shell commands to integrate something with desktop.
+     */
+    private class ShellCommands {
+
+        ShellCommands() {
+            registerIconCmds = new ArrayList<>();
+            unregisterIconCmds = new ArrayList<>();
+
+            registerDesktopFileCmd = String.join(" ", "xdg-desktop-menu",
+                    "install", desktopFile.installPath().toString());
+            unregisterDesktopFileCmd = String.join(" ", "xdg-desktop-menu",
+                    "uninstall", desktopFile.installPath().toString());
+        }
+
+        void setFileAssociations() {
+            registerFileAssociationsCmd = String.join(" ", "xdg-mime",
+                    "install",
+                    mimeInfoFile.installPath().toString());
+            unregisterFileAssociationsCmd = String.join(" ", "xdg-mime",
+                    "uninstall", mimeInfoFile.installPath().toString());
+
+            //
+            // Add manual cleanup of system files to get rid of
+            // the default mime type handlers.
+            //
+            // Even after mime type is unregisterd with `xdg-mime uninstall`
+            // command and desktop file deleted with `xdg-desktop-menu uninstall`
+            // command, records in
+            // `/usr/share/applications/defaults.list` (Ubuntu 16) or
+            // `/usr/local/share/applications/defaults.list` (OracleLinux 7)
+            // files remain referencing deleted mime time and deleted
+            // desktop file which makes `xdg-mime query default` output name
+            // of non-existing desktop file.
+            //
+            String cleanUpCommand = String.join(" ",
+                    "uninstall_default_mime_handler",
+                    desktopFile.installPath().getFileName().toString(),
+                    String.join(" ", getMimeTypeNamesFromFileAssociations()));
+
+            unregisterFileAssociationsCmd = stringifyShellCommands(
+                    unregisterFileAssociationsCmd, cleanUpCommand);
+        }
+
+        void addIcon(String mimeType, Path iconFile) {
+            addIcon(mimeType, iconFile, getSquareSizeOfImage(iconFile.toFile()));
+        }
+
+        void addIcon(String mimeType, Path iconFile, int imgSize) {
+            imgSize = normalizeIconSize(imgSize);
+            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, "--size", Integer.toString(imgSize)));
+        }
+
+        void applyTo(Map<String, String> data) {
+            List<String> cmds = new ArrayList<>();
+
+            cmds.add(registerDesktopFileCmd);
+            cmds.add(registerFileAssociationsCmd);
+            cmds.addAll(registerIconCmds);
+            data.put(DESKTOP_COMMANDS_INSTALL, stringifyShellCommands(cmds));
+
+            cmds.clear();
+            cmds.add(unregisterDesktopFileCmd);
+            cmds.add(unregisterFileAssociationsCmd);
+            cmds.addAll(unregisterIconCmds);
+            data.put(DESKTOP_COMMANDS_UNINSTALL, stringifyShellCommands(cmds));
+        }
+
+        private String registerDesktopFileCmd;
+        private String unregisterDesktopFileCmd;
+
+        private String registerFileAssociationsCmd;
+        private String unregisterFileAssociationsCmd;
+
+        private List<String> registerIconCmds;
+        private List<String> unregisterIconCmds;
+    }
+
+    /**
+     * Desktop integration file. xml, icon, etc.
+     * Resides somewhere in application installation tree.
+     * Has two paths:
+     *  - path where it should be placed at package build time;
+     *  - path where it should be installed by package manager;
+     */
+    private class DesktopFile {
+
+        DesktopFile(String fileName) {
+            installPath = thePackage
+                    .installedApplicationLayout()
+                    .destktopIntegrationDirectory().resolve(fileName);
+            srcPath = thePackage
+                    .sourceApplicationLayout()
+                    .destktopIntegrationDirectory().resolve(fileName);
+        }
+
+        private final Path installPath;
+        private final Path srcPath;
+
+        Path installPath() {
+            return installPath;
+        }
+
+        Path srcPath() {
+            return srcPath;
+        }
+    }
+
+    private void appendFileAssociation(XMLStreamWriter xml,
+            FileAssociation assoc) throws XMLStreamException {
+
+        for (var mimeType : assoc.mimeTypes) {
+            xml.writeStartElement("mime-type");
+            xml.writeAttribute("type", mimeType);
+
+            final String description = assoc.description;
+            if (description != null && !description.isEmpty()) {
+                xml.writeStartElement("comment");
+                xml.writeCharacters(description);
+                xml.writeEndElement();
+            }
+
+            for (String ext : assoc.extensions) {
+                xml.writeStartElement("glob");
+                xml.writeAttribute("pattern", "*." + ext);
+                xml.writeEndElement();
+            }
+
+            xml.writeEndElement();
+        }
+    }
+
+    private void createFileAssociationsMimeInfoFile() throws IOException {
+        IOUtils.createXml(mimeInfoFile.srcPath(), xml -> {
+            xml.writeStartElement("mime-info");
+            xml.writeDefaultNamespace(
+                    "http://www.freedesktop.org/standards/shared-mime-info");
+
+            for (var assoc : associations) {
+                appendFileAssociation(xml, assoc.data);
+            }
+
+            xml.writeEndElement();
+        });
+    }
+
+    private void addFileAssociationIconFiles(ShellCommands shellCommands)
+            throws IOException {
+        Set<String> processedMimeTypes = new HashSet<>();
+        for (var assoc : associations) {
+            if (assoc.iconSize <= 0) {
+                // No icon.
+                continue;
+            }
+
+            for (var mimeType : assoc.data.mimeTypes) {
+                if (processedMimeTypes.contains(mimeType)) {
+                    continue;
+                }
+
+                processedMimeTypes.add(mimeType);
+
+                // Create icon name for mime type from mime type.
+                DesktopFile faIconFile = new DesktopFile(mimeType.replace(
+                        File.separatorChar, '-') + IOUtils.getSuffix(
+                                assoc.data.iconPath));
+
+                IOUtils.copyFile(assoc.data.iconPath.toFile(),
+                        faIconFile.srcPath().toFile());
+
+                shellCommands.addIcon(mimeType, faIconFile.installPath(),
+                        assoc.iconSize);
+            }
+        }
+    }
+
+    private void createDesktopFile(Map<String, String> data) throws IOException {
+        List<String> mimeTypes = getMimeTypeNamesFromFileAssociations();
+        data.put("DESKTOP_MIMES", "MimeType=" + String.join(";", mimeTypes));
+
+        // prepare desktop shortcut
+        desktopFileResource
+                .setSubstitutionData(data)
+                .saveToFile(desktopFile.srcPath());
+    }
+
+    private List<String> getMimeTypeNamesFromFileAssociations() {
+        return associations.stream()
+                .map(fa -> fa.data.mimeTypes)
+                .flatMap(List::stream)
+                .collect(Collectors.toUnmodifiableList());
+    }
+
+    private static int getSquareSizeOfImage(File f) {
+        try {
+            BufferedImage bi = ImageIO.read(f);
+            return Math.max(bi.getWidth(), bi.getHeight());
+        } catch (IOException e) {
+            Log.verbose(e);
+        }
+        return 0;
+    }
+
+    private static int normalizeIconSize(int iconSize) {
+        // If register icon with "uncommon" size, it will be ignored.
+        // So find the best matching "common" size.
+        List<Integer> commonIconSizes = List.of(16, 22, 32, 48, 64, 128);
+
+        int idx = Collections.binarySearch(commonIconSizes, iconSize);
+        if (idx < 0) {
+            // Given icon size is greater than the largest common icon size.
+            return commonIconSizes.get(commonIconSizes.size() - 1);
+        }
+
+        if (idx == 0) {
+            // Given icon size is less or equal than the smallest common icon size.
+            return commonIconSizes.get(idx);
+        }
+
+        int commonIconSize = commonIconSizes.get(idx);
+        if (iconSize < commonIconSize) {
+            // It is better to scale down original icon than to scale it up for
+            // better visual quality.
+            commonIconSize = commonIconSizes.get(idx - 1);
+        }
+
+        return commonIconSize;
+    }
+
+    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 static class LinuxFileAssociation {
+        LinuxFileAssociation(FileAssociation fa) {
+            this.data = fa;
+            if (fa.iconPath != null && Files.isReadable(fa.iconPath)) {
+                iconSize = getSquareSizeOfImage(fa.iconPath.toFile());
+            } else {
+                iconSize = -1;
+            }
+        }
+
+        final FileAssociation data;
+        final int iconSize;
+    }
+
+    private final PlatformPackage thePackage;
+
+    private final List<LinuxFileAssociation> associations;
+
+    private final List<Map<String, ? super Object>> launchers;
+
+    private final OverridableResource iconResource;
+    private final OverridableResource desktopFileResource;
+
+    private final DesktopFile mimeInfoFile;
+    private final DesktopFile desktopFile;
+    private final DesktopFile iconFile;
+
+    private final List<DesktopIntegration> nestedIntegrations;
+
+    private final Map<String, String> desktopFileData;
+
+    private static final BundlerParamInfo<String> MENU_GROUP =
+        new StandardBundlerParam<>(
+                Arguments.CLIOptions.LINUX_MENU_GROUP.getId(),
+                String.class,
+                params -> I18N.getString("param.menu-group.default"),
+                (s, p) -> s
+        );
+
+    private static final StandardBundlerParam<Boolean> SHORTCUT_HINT =
+        new StandardBundlerParam<>(
+                Arguments.CLIOptions.LINUX_SHORTCUT_HINT.getId(),
+                Boolean.class,
+                params -> false,
+                (s, p) -> (s == null || "null".equalsIgnoreCase(s))
+                        ? false : Boolean.valueOf(s)
+        );
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LibProvidersLookup.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,166 @@
+/*
+ * 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.incubator.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;
+        try (Stream<Path> stream = Files.walk(root)) {
+            allPackageFiles = stream.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.incubator.jpackage17911687595930080396/images/opt/simplepackagetest/lib/runtime/lib/libawt_headless.so'
+    //  linux-vdso.so.1 =>  (0x00007ffce6bfd000)
+    //  libawt.so => /tmp/jdk.incubator.jpackage17911687595930080396/images/opt/simplepackagetest/lib/runtime/lib/libawt.so (0x00007f4e00c75000)
+    //  libjvm.so => not found
+    //  libjava.so => /tmp/jdk.incubator.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.incubator.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.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LinuxAppBundler.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,164 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.File;
+import java.text.MessageFormat;
+import java.util.*;
+
+import static jdk.incubator.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 {
+            Objects.requireNonNull(params);
+            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.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LinuxAppImageBuilder.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,195 @@
+/*
+ * 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.incubator.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.text.MessageFormat;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+
+import static jdk.incubator.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("$ROOTDIR").resolve(
+                ApplicationLayout.linuxAppImage().appDirectory()).toString()
+                + File.separator;
+    }
+
+    @Override
+    protected String getCfgRuntimeDir() {
+        return Path.of("$ROOTDIR").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 {
+
+        Path iconTarget = appLayout.destktopIntegrationDirectory().resolve(
+                APP_NAME.fetchFrom(params) + IOUtils.getSuffix(Path.of(
+                DEFAULT_ICON)));
+
+        createResource(DEFAULT_ICON, params)
+                .setCategory("icon")
+                .setExternal(ICON_PNG.fetchFrom(params))
+                .saveToFile(iconTarget);
+    }
+
+    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.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LinuxDebBundler.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,487 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.*;
+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.incubator.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+
+import static jdk.incubator.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.readString(Path.of(licenseFile));
+                    }
+                } 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();
+        });
+    }
+
+    @Override
+    protected List<ConfigException> verifyOutputBundle(
+            Map<String, ? super Object> params, Path packageBundle) {
+        List<ConfigException> errors = new ArrayList<>();
+
+        String controlFileName = "control";
+
+        List<PackageProperty> properties = List.of(
+                new PackageProperty("Package", PACKAGE_NAME.fetchFrom(params),
+                        "APPLICATION_PACKAGE", controlFileName),
+                new PackageProperty("Version", String.format("%s-%s",
+                        VERSION.fetchFrom(params), RELEASE.fetchFrom(params)),
+                        "APPLICATION_VERSION-APPLICATION_RELEASE",
+                        controlFileName),
+                new PackageProperty("Architecture", DEB_ARCH, "APPLICATION_ARCH",
+                        controlFileName));
+
+        List<String> cmdline = new ArrayList<>(List.of(TOOL_DPKG_DEB, "-f",
+                packageBundle.toString()));
+        properties.forEach(property -> cmdline.add(property.name));
+        try {
+            Map<String, String> actualValues = Executor.of(cmdline.toArray(String[]::new))
+                    .saveOutput(true)
+                    .executeExpectSuccess()
+                    .getOutput().stream()
+                            .map(line -> line.split(":\\s+", 2))
+                            .collect(Collectors.toMap(
+                                    components -> components[0],
+                                    components -> components[1]));
+            properties.forEach(property -> errors.add(property.verifyValue(
+                    actualValues.get(property.name))));
+        } catch (IOException ex) {
+            // Ignore error as it is not critical. Just report it.
+            Log.verbose(ex);
+        }
+
+        return errors;
+    }
+
+    /*
+     * 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 {
+            createResource("template." + dstFilePath.getFileName().toString(),
+                    params)
+                    .setCategory(I18N.getString(comment))
+                    .setSubstitutionData(data)
+                    .saveToFile(dstFilePath);
+            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());
+
+        if (!StandardBundlerParam.isRuntimeInstaller(params)) {
+            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.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LinuxPackageBundler.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,357 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.*;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import static jdk.incubator.jpackage.internal.DesktopIntegration.*;
+import static jdk.incubator.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
+import static jdk.incubator.jpackage.internal.LinuxAppBundler.LINUX_PACKAGE_DEPENDENCIES;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+
+abstract class LinuxPackageBundler extends AbstractBundler {
+
+    LinuxPackageBundler(BundlerParamInfo<String> packageName) {
+        this.packageName = packageName;
+    }
+
+    @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);
+
+        Function<File, ApplicationLayout> initAppImageLayout = imageRoot -> {
+            ApplicationLayout layout = appImageLayout(params);
+            layout.pathGroup().setPath(new Object(),
+                    AppImageFile.getPathInAppImage(Path.of("")));
+            return layout.resolveAt(imageRoot.toPath());
+        };
+
+        try {
+            File appImage = StandardBundlerParam.getPredefinedAppImage(params);
+
+            // we either have an application image or need to build one
+            if (appImage != null) {
+                initAppImageLayout.apply(appImage).copy(
+                        thePackage.sourceApplicationLayout());
+            } else {
+                appImage = APP_BUNDLER.fetchFrom(params).doBundle(params,
+                        thePackage.sourceRoot().toFile(), true);
+                ApplicationLayout srcAppLayout = initAppImageLayout.apply(
+                        appImage);
+                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));
+
+            File packageBundle = buildPackageBundle(Collections.unmodifiableMap(
+                    data), params, outputParentDir);
+
+            verifyOutputBundle(params, packageBundle.toPath()).stream()
+                    .filter(Objects::nonNull)
+                    .forEachOrdered(ex -> {
+                Log.verbose(ex.getLocalizedMessage());
+                Log.verbose(ex.getAdvice());
+            });
+
+            return packageBundle;
+        } 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 List<ConfigException> verifyOutputBundle(
+            Map<String, ? super Object> params, Path packageBundle);
+
+    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"));
+            }
+        }
+    }
+
+    private final BundlerParamInfo<String> packageName;
+    private boolean withFindNeededPackages;
+    private DesktopIntegration desktopIntegration;
+
+    private static final BundlerParamInfo<LinuxAppBundler> APP_BUNDLER =
+        new StandardBundlerParam<>(
+                "linux.app.bundler",
+                LinuxAppBundler.class,
+                (params) -> new LinuxAppBundler(),
+                null
+        );
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LinuxRpmBundler.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,323 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.*;
+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 java.util.stream.Stream;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+import static jdk.incubator.jpackage.internal.LinuxAppBundler.LINUX_INSTALL_DIR;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+
+/**
+ * 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 DottedVersion TOOL_RPMBUILD_MIN_VERSION = DottedVersion.lazy(
+            "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
+        createResource(DEFAULT_SPEC_TEMPLATE, params)
+                .setCategory(I18N.getString("resource.rpm-spec-file"))
+                .setSubstitutionData(replacementData)
+                .saveToFile(specFile);
+
+        return buildRPM(params, outputParentDir.toPath()).toFile();
+    }
+
+    @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 = Path.of(licenseFile).toAbsolutePath().normalize().toString();
+        }
+        data.put("APPLICATION_LICENSE_FILE", licenseFile);
+        data.put("APPLICATION_GROUP", GROUP.fetchFrom(params));
+
+        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();
+        });
+    }
+
+    @Override
+    protected List<ConfigException> verifyOutputBundle(
+            Map<String, ? super Object> params, Path packageBundle) {
+        List<ConfigException> errors = new ArrayList<>();
+
+        String specFileName = specFile(params).getFileName().toString();
+
+        try {
+            List<PackageProperty> properties = List.of(
+                    new PackageProperty("Name", PACKAGE_NAME.fetchFrom(params),
+                            "APPLICATION_PACKAGE", specFileName),
+                    new PackageProperty("Version", VERSION.fetchFrom(params),
+                            "APPLICATION_VERSION", specFileName),
+                    new PackageProperty("Release", RELEASE.fetchFrom(params),
+                            "APPLICATION_RELEASE", specFileName),
+                    new PackageProperty("Arch", rpmArch(), null, specFileName));
+
+            List<String> actualValues = Executor.of(TOOL_RPM, "-qp", "--queryformat",
+                    properties.stream().map(entry -> String.format("%%{%s}",
+                    entry.name)).collect(Collectors.joining("\\n")),
+                    packageBundle.toString()).saveOutput(true).executeExpectSuccess().getOutput();
+
+            Iterator<String> actualValuesIt = actualValues.iterator();
+            properties.forEach(property -> errors.add(property.verifyValue(
+                    actualValuesIt.next())));
+        } catch (IOException ex) {
+            // Ignore error as it is not critical. Just report it.
+            Log.verbose(ex);
+        }
+
+        return errors;
+    }
+
+    /**
+     * Various ways to get rpm arch. Needed to address JDK-8233143. rpmbuild is
+     * mandatory for rpm packaging, try it first. rpm is optional and may not be
+     * available, use as the last resort.
+     */
+    private enum RpmArchReader {
+        Rpmbuild(TOOL_RPMBUILD, "--eval=%{_target_cpu}"),
+        Rpm(TOOL_RPM, "--eval=%{_target_cpu}");
+
+        RpmArchReader(String... cmdline) {
+            this.cmdline = cmdline;
+        }
+
+        String getRpmArch() throws IOException {
+            Executor exec = Executor.of(cmdline).saveOutput(true);
+            if (this == values()[values().length - 1]) {
+                exec.executeExpectSuccess();
+            } else if (exec.execute() != 0) {
+                return null;
+            }
+
+            return exec.getOutput().get(0);
+        }
+
+        private final String[] cmdline;
+    }
+
+    private String rpmArch() throws IOException {
+        if (rpmArch == null) {
+            for (var rpmArchReader : RpmArchReader.values()) {
+                rpmArch = rpmArchReader.getRpmArch();
+                if (rpmArch != null) {
+                    break;
+                }
+            }
+        }
+        return rpmArch;
+    }
+
+    private Path specFile(Map<String, ? super Object> params) {
+        return TEMP_ROOT.fetchFrom(params).toPath().resolve(Path.of("SPECS",
+                PACKAGE_NAME.fetchFrom(params) + ".spec"));
+    }
+
+    private Path buildRPM(Map<String, ? super Object> params,
+            Path outdir) throws IOException {
+
+        Path rpmFile = outdir.toAbsolutePath().resolve(String.format(
+                "%s-%s-%s.%s.rpm", PACKAGE_NAME.fetchFrom(params),
+                VERSION.fetchFrom(params), RELEASE.fetchFrom(params), rpmArch()));
+
+        Log.verbose(MessageFormat.format(I18N.getString(
+                "message.outputting-bundle-location"),
+                rpmFile.getParent()));
+
+        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", rpmFile.getParent()),
+                // do not use other system directories to build as current user
+                "--define", String.format("%%_topdir %s",
+                        TEMP_ROOT.fetchFrom(params).toPath().toAbsolutePath()),
+                "--define", String.format("%%_rpmfilename %s", rpmFile.getFileName())
+        ).executeExpectSuccess();
+
+        Log.verbose(MessageFormat.format(
+                I18N.getString("message.output-bundle-location"),
+                rpmFile.getParent()));
+
+        return rpmFile;
+    }
+
+    @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();
+    }
+
+    private String rpmArch;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/PackageProperty.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,75 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.text.MessageFormat;
+
+final class PackageProperty {
+    /**
+     * Constructor
+     *
+     * @param name property name
+     * @param expectedValue expected property value
+     * @param substString substitution string to be placed in resource file to
+     * be replaced with the expected property value by jpackage at package build
+     * time
+     * @param customResource name of custom resource from resource directory in
+     * which this package property can be set
+     */
+    PackageProperty(String name, String expectedValue, String substString,
+            String customResource) {
+        this.name = name;
+        this.expectedValue = expectedValue;
+        this.substString = substString;
+        this.customResource = customResource;
+    }
+    
+    ConfigException verifyValue(String actualValue) {
+        if (expectedValue.equals(actualValue)) {
+            return null;
+        }
+        
+        final String advice;
+        if (substString != null) {
+            advice = MessageFormat.format(I18N.getString(
+                    "error.unexpected-package-property.advice"), substString,
+                    actualValue, name, customResource);
+        } else {
+            advice = MessageFormat.format(I18N.getString(
+                    "error.unexpected-default-package-property.advice"), name,
+                    customResource);
+        }
+        
+        return new ConfigException(MessageFormat.format(I18N.getString(
+                "error.unexpected-package-property"), name,
+                expectedValue, actualValue, customResource, substString), advice);
+    }
+
+    final String name;
+    private final String expectedValue;
+    private final String substString;
+    private final String customResource;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/LinuxResources.properties	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,69 @@
+#
+# 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.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.
+
+error.unexpected-package-property=Expected value of "{0}" property is [{1}]. Actual value in output package is [{2}]. Looks like custom "{3}" file from resource directory contained hard coded value of "{0}" property
+error.unexpected-package-property.advice=Use [{0}] pattern string instead of hard coded value [{1}] of {2} property in custom "{3}" file
+error.unexpected-default-package-property.advice=Don't explicitly set value of {0} property in custom "{1}" file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/LinuxResources_ja.properties	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,69 @@
+#
+# 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.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.
+
+error.unexpected-package-property=Expected value of "{0}" property is [{1}]. Actual value in output package is [{2}]. Looks like custom "{3}" file from resource directory contained hard coded value of "{0}" property
+error.unexpected-package-property.advice=Use [{0}] pattern string instead of hard coded value [{1}] of {2} property in custom "{3}" file
+error.unexpected-default-package-property.advice=Don't explicitly set value of {0} property in custom "{1}" file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/LinuxResources_zh_CN.properties	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,69 @@
+#
+# 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.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.
+
+error.unexpected-package-property=Expected value of "{0}" property is [{1}]. Actual value in output package is [{2}]. Looks like custom "{3}" file from resource directory contained hard coded value of "{0}" property
+error.unexpected-package-property.advice=Use [{0}] pattern string instead of hard coded value [{1}] of {2} property in custom "{3}" file
+error.unexpected-default-package-property.advice=Don't explicitly set value of {0} property in custom "{1}" file
Binary file src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/java32.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.control	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.copyright	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.desktop	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.postinst	Tue Nov 12 10:18:11 2019 -0500
@@ -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 https://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.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.postrm	Tue Nov 12 10:18:11 2019 -0500
@@ -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 https://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.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.preinst	Tue Nov 12 10:18:11 2019 -0500
@@ -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 https://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.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.prerm	Tue Nov 12 10:18:11 2019 -0500
@@ -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 https://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.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.spec	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,58 @@
+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
+
+#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.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/utils.sh	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/linux/classes/module-info.java.extra	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage.internal.Bundler with
+    jdk.incubator.jpackage.internal.LinuxAppBundler,
+    jdk.incubator.jpackage.internal.LinuxDebBundler,
+    jdk.incubator.jpackage.internal.LinuxRpmBundler;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/linux/native/jpackageapplauncher/launcher.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/linux/native/libapplauncher/LinuxPlatform.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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:
+ * &amp; (&), &lt; (<), &gt; (>), &apos; ('), and &quote(")
+ * 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("&amp;"), 5) == 0) {
+                *q++ = '&';
+                SKIP_CHARS(p, 5);
+            } else if (JPACKAGE_STRNCMP(p, _T("&lt;"), 4) == 0) {
+                *q = '<';
+                SKIP_CHARS(p, 4);
+            } else if (JPACKAGE_STRNCMP(p, _T("&gt;"), 4) == 0) {
+                *q = '>';
+                SKIP_CHARS(p, 4);
+            } else if (JPACKAGE_STRNCMP(p, _T("&apos;"), 6) == 0) {
+                *q = '\'';
+                SKIP_CHARS(p, 6);
+            } else if (JPACKAGE_STRNCMP(p, _T("&quote;"), 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.incubator.jpackage/linux/native/libapplauncher/LinuxPlatform.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/linux/native/libapplauncher/PlatformDefs.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/EnumeratedBundlerParam.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacAppBundler.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,298 @@
+/*
+ * 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.incubator.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.incubator.jpackage.internal.StandardBundlerParam.*;
+import static jdk.incubator.jpackage.internal.MacBaseInstallerBundler.*;
+
+public class MacAppBundler extends AbstractImageBundler {
+
+    private static final ResourceBundle I18N = ResourceBundle.getBundle(
+            "jdk.incubator.jpackage.internal.resources.MacResources");
+
+    private static final String TEMPLATE_BUNDLE_ICON = "java.icns";
+
+    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);
+
+                        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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacAppImageBuilder.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,945 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+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 java.util.stream.Stream;
+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.incubator.jpackage.internal.StandardBundlerParam.*;
+import static jdk.incubator.jpackage.internal.MacBaseInstallerBundler.*;
+import static jdk.incubator.jpackage.internal.MacAppBundler.*;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+
+public class MacAppImageBuilder extends AbstractAppImageBuilder {
+
+    private static final ResourceBundle I18N = ResourceBundle.getBundle(
+            "jdk.incubator.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 appDir;
+    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 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<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.appDir = contentsDir.resolve("app");
+        this.javaModsDir = appDir.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(appDir);
+        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 appDir;
+    }
+
+    @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(appDir, params);
+
+        /*********** Take care of "config" files *******/
+
+        createResource(TEMPLATE_BUNDLE_ICON, params)
+                .setCategory("icon")
+                .setExternal(ICON_ICNS.fetchFrom(params))
+                .saveToFile(resourcesDir.resolve(APP_NAME.fetchFrom(params)
+                        + ".icns"));
+
+        // copy file association icons
+        for (Map<String, ?
+                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/app/" + 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));
+
+        createResource(TEMPLATE_RUNTIME_INFO_PLIST, params)
+                .setPublicName("Runtime-Info.plist")
+                .setCategory(I18N.getString("resource.runtime-info-plist"))
+                .setSubstitutionData(data)
+                .saveToFile(file);
+    }
+
+    private void writeInfoPlist(File file, Map<String, ? super Object> params)
+            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_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");
+
+        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);
+
+        createResource(TEMPLATE_INFO_PLIST_LITE, params)
+                .setCategory(I18N.getString("resource.app-info-plist"))
+                .setSubstitutionData(data)
+                .setPublicName("Info.plist")
+                .saveToFile(file);
+    }
+
+    private void writePkgInfo(File file) throws IOException {
+        //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
+        try (Stream<Path> stream = Files.walk(appLocation)) {
+            stream.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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacAppStoreBundler.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,298 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.*;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+import static jdk.incubator.jpackage.internal.MacAppBundler.*;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+
+public class MacAppStoreBundler extends MacBaseInstallerBundler {
+
+    private static final ResourceBundle I18N = ResourceBundle.getBundle(
+            "jdk.incubator.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);
+
+                    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);
+
+                    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 {
+        createResource(DEFAULT_ENTITLEMENTS, params)
+                .setCategory(
+                        I18N.getString("resource.mac-app-store-entitlements"))
+                .setExternal(MAC_APP_STORE_ENTITLEMENTS.fetchFrom(params))
+                .saveToFile(getConfig_Entitlements(params));
+
+        createResource(DEFAULT_INHERIT_ENTITLEMENTS, params)
+                .setCategory(I18N.getString(
+                        "resource.mac-app-store-inherit-entitlements"))
+                .saveToFile(getConfig_Entitlements(params));
+    }
+
+    ///////////////////////////////////////////////////////////////////////
+    // 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 {
+            Objects.requireNonNull(params);
+
+            // 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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacBaseInstallerBundler.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage.internal.StandardBundlerParam.*;
+
+public abstract class MacBaseInstallerBundler extends AbstractBundler {
+
+    private static final ResourceBundle I18N = ResourceBundle.getBundle(
+            "jdk.incubator.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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacCertificate.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,144 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+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;
+
+public final class MacCertificate {
+    private final String certificate;
+
+    public MacCertificate(String certificate) {
+        this.certificate = certificate;
+    }
+
+    public boolean isValid() {
+        return verifyCertificate(this.certificate);
+    }
+
+    private static File findCertificate(String certificate) {
+        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) {
+        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 result = false;
+
+        try {
+            File file = null;
+            Date certificateDate = null;
+
+            try {
+                file = findCertificate(certificate);
+
+                if (file != null) {
+                    certificateDate = findCertificateDate(
+                            file.getCanonicalPath());
+                }
+            }
+            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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacDmgBundler.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,480 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.text.MessageFormat;
+import java.util.*;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+public class MacDmgBundler extends MacBaseInstallerBundler {
+
+    private static final ResourceBundle I18N = ResourceBundle.getBundle(
+            "jdk.incubator.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");
+
+        createResource(DEFAULT_DMG_SETUP_SCRIPT, params)
+                .setCategory(I18N.getString("resource.dmg-setup-script"))
+                .setSubstitutionData(data)
+                .saveToFile(dmgSetup);
+    }
+
+    private File getConfig_VolumeScript(Map<String, ? super Object> params) {
+        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);
+
+            createResource(DEFAULT_LICENSE_PLIST, params)
+                    .setCategory(I18N.getString("resource.license-setup"))
+                    .setSubstitutionData(data)
+                    .saveToFile(getConfig_LicenseFile(params));
+
+        } catch (IOException ex) {
+            Log.verbose(ex);
+        }
+    }
+
+    private boolean prepareConfigFiles(Map<String, ? super Object> params)
+            throws IOException {
+
+        createResource(DEFAULT_BACKGROUND_IMAGE, params)
+                    .setCategory(I18N.getString("resource.dmg-background"))
+                    .saveToFile(getConfig_VolumeBackground(params));
+
+        createResource(TEMPLATE_BUNDLE_ICON, params)
+                .setCategory(I18N.getString("resource.volume-icon"))
+                .setExternal(MacAppBundler.ICON_ICNS.fetchFrom(params))
+                .saveToFile(getConfig_VolumeIcon(params));
+
+        createResource(null, params)
+                .setCategory(I18N.getString("resource.post-install-script"))
+                .saveToFile(getConfig_Script(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
+                    // (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 {
+            Objects.requireNonNull(params);
+
+            //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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacPkgBundler.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,555 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.*;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.*;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+import static jdk.incubator.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEYCHAIN;
+import static jdk.incubator.jpackage.internal.MacBaseInstallerBundler.SIGNING_KEY_USER;
+import static jdk.incubator.jpackage.internal.MacAppImageBuilder.MAC_CF_BUNDLE_IDENTIFIER;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+
+public class MacPkgBundler extends MacBaseInstallerBundler {
+
+    private static final ResourceBundle I18N = ResourceBundle.getBundle(
+            "jdk.incubator.jpackage.internal.resources.MacResources");
+
+    private static final String DEFAULT_BACKGROUND_IMAGE = "background_pkg.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);
+
+                        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 getConfig_BackgroundImageDarkAqua(Map<String, ? super Object> params) {
+        return new File(CONFIG_ROOT.fetchFrom(params),
+                APP_NAME.fetchFrom(params) + "-background-darkAqua.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 MAC_CF_BUNDLE_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<>();
+
+        Path appLocation = Path.of(MAC_INSTALL_DIR.fetchFrom(params),
+                         APP_NAME.fetchFrom(params) + ".app", "Contents", "app");
+
+        data.put("INSTALL_LOCATION", MAC_INSTALL_DIR.fetchFrom(params));
+        data.put("APP_LOCATION", appLocation.toString());
+
+        createResource(TEMPLATE_PREINSTALL_SCRIPT, params)
+                .setCategory(I18N.getString("resource.pkg-preinstall-script"))
+                .setSubstitutionData(data)
+                .saveToFile(getScripts_PreinstallFile(params));
+        getScripts_PreinstallFile(params).setExecutable(true, false);
+
+        createResource(TEMPLATE_POSTINSTALL_SCRIPT, params)
+                .setCategory(I18N.getString("resource.pkg-postinstall-script"))
+                .setSubstitutionData(data)
+                .saveToFile(getScripts_PostinstallFile(params));
+        getScripts_PostinstallFile(params).setExecutable(true, false);
+    }
+
+    private static String URLEncoding(String pkgName) throws URISyntaxException {
+        URI uri = new URI(null, null, pkgName, null);
+        return uri.toASCIIString();
+    }
+
+    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()));
+
+        IOUtils.createXml(f.toPath(), xml -> {
+            xml.writeStartElement("installer-gui-script");
+            xml.writeAttribute("minSpecVersion", "1");
+
+            xml.writeStartElement("title");
+            xml.writeCharacters(APP_NAME.fetchFrom(params));
+            xml.writeEndElement();
+
+            xml.writeStartElement("background");
+            xml.writeAttribute("file", getConfig_BackgroundImage(params).getName());
+            xml.writeAttribute("mime-type", "image/png");
+            xml.writeAttribute("alignment", "bottomleft");
+            xml.writeAttribute("scaling", "none");
+            xml.writeEndElement();
+
+            xml.writeStartElement("background-darkAqua");
+            xml.writeAttribute("file", getConfig_BackgroundImageDarkAqua(params).getName());
+            xml.writeAttribute("mime-type", "image/png");
+            xml.writeAttribute("alignment", "bottomleft");
+            xml.writeAttribute("scaling", "none");
+            xml.writeEndElement();
+
+            String licFileStr = LICENSE_FILE.fetchFrom(params);
+            if (licFileStr != null) {
+                File licFile = new File(licFileStr);
+                xml.writeStartElement("license");
+                xml.writeAttribute("file", licFile.getAbsolutePath());
+                xml.writeAttribute("mime-type", "text/rtf");
+                xml.writeEndElement();
+            }
+
+            /*
+             * Note that the content of the distribution file
+             * below is generated by productbuild --synthesize
+             */
+            String appId = getAppIdentifier(params);
+
+            xml.writeStartElement("pkg-ref");
+            xml.writeAttribute("id", appId);
+            xml.writeEndElement(); // </pkg-ref>
+            xml.writeStartElement("options");
+            xml.writeAttribute("customize", "never");
+            xml.writeAttribute("require-scripts", "false");
+            xml.writeEndElement(); // </options>
+            xml.writeStartElement("choices-outline");
+            xml.writeStartElement("line");
+            xml.writeAttribute("choice", "default");
+            xml.writeStartElement("line");
+            xml.writeAttribute("choice", appId);
+            xml.writeEndElement(); // </line>
+            xml.writeEndElement(); // </line>
+            xml.writeEndElement(); // </choices-outline>
+            xml.writeStartElement("choice");
+            xml.writeAttribute("id", "default");
+            xml.writeEndElement(); // </choice>
+            xml.writeStartElement("choice");
+            xml.writeAttribute("id", appId);
+            xml.writeAttribute("visible", "false");
+            xml.writeStartElement("pkg-ref");
+            xml.writeAttribute("id", appId);
+            xml.writeEndElement(); // </pkg-ref>
+            xml.writeEndElement(); // </choice>
+            xml.writeStartElement("pkg-ref");
+            xml.writeAttribute("id", appId);
+            xml.writeAttribute("version", VERSION.fetchFrom(params));
+            xml.writeAttribute("onConclusion", "none");
+            try {
+                xml.writeCharacters(URLEncoding(
+                        getPackages_AppPackage(params).getName()));
+            } catch (URISyntaxException ex) {
+                throw new IOException(ex);
+            }
+            xml.writeEndElement(); // </pkg-ref>
+
+            xml.writeEndElement(); // </installer-gui-script>
+        });
+    }
+
+    private boolean prepareConfigFiles(Map<String, ? super Object> params)
+            throws IOException {
+
+        createResource(DEFAULT_BACKGROUND_IMAGE, params)
+                .setCategory(I18N.getString("resource.pkg-background-image"))
+                .saveToFile(getConfig_BackgroundImage(params));
+
+        createResource(DEFAULT_BACKGROUND_IMAGE, params)
+                .setCategory(I18N.getString("resource.pkg-background-image"))
+                .saveToFile(getConfig_BackgroundImageDarkAqua(params));
+
+        prepareDistributionXMLFile(params);
+
+        createResource(null, params)
+                .setCategory(I18N.getString("resource.post-install-script"))
+                .saveToFile(getConfig_Script(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 {
+            Objects.requireNonNull(params);
+
+            // 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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/DMGsetup.scpt	Tue Nov 12 10:18:11 2019 -0500
@@ -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, 130}
+      else if theFilePath is "/DEPLOY_INSTALL_NAME"
+        -- Position install location
+        set position of item theFile of container window to {390, 130}
+      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, 130}
+      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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/Info-lite.plist.template	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,37 @@
+<?xml version="1.0" ?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://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 https://developer.apple.com/app-store/categories/ for list of AppStore categories -->
+  <key>LSApplicationCategoryType</key>
+  <string>Unknown</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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/MacAppStore.entitlements	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/MacAppStore_Inherit.entitlements	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/MacResources.properties	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,89 @@
+#
+# 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.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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/MacResources_ja.properties	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,89 @@
+#
+# 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.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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/MacResources_zh_CN.properties	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,89 @@
+#
+# 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.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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/Runtime-Info.plist.template	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/background_dmg.png has changed
Binary file src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/background_pkg.png has changed
Binary file src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/java.icns has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/lic_template.plist	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,244 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://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.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/postinstall.template	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,7 @@
+#!/usr/bin/env sh
+
+chown root:wheel "INSTALL_LOCATION"
+chmod a+rX "INSTALL_LOCATION"
+chmod +r "APP_LOCATION/"*.jar
+
+exit 0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/preinstall.template	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/macosx/classes/module-info.java.extra	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage.internal.Bundler with
+    jdk.incubator.jpackage.internal.MacAppBundler,
+    jdk.incubator.jpackage.internal.MacAppStoreBundler,
+    jdk.incubator.jpackage.internal.MacDmgBundler,
+    jdk.incubator.jpackage.internal.MacPkgBundler;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/macosx/native/jpackageapplauncher/main.m	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/macosx/native/libapplauncher/MacPlatform.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/macosx/native/libapplauncher/MacPlatform.mm	Tue Nov 12 10:18:11 2019 -0500
@@ -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("app");
+}
+
+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.incubator.jpackage/macosx/native/libapplauncher/PlatformDefs.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/classes/jdk/incubator/jpackage/ToolProviderFactory.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,69 @@
+/*
+ * 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
+ * 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.incubator.jpackage;
+
+import jdk.incubator.jpackage.internal.JPackageToolProvider;
+import java.io.PrintWriter;
+import java.util.Optional;
+import java.util.spi.ToolProvider;
+
+/**
+ * A factory class to obtain a {@linkplain ToolProvider tool provider}
+ * for the incubating {@code jpackage} tool.
+ * 
+ * It is planned to implement {@code jpackage} tool as a service provider
+ * to {@link ToolProvider} in a future release at which point
+ * {@link ToolProvider#findFirst} can be used to look up jpackage tool.
+ *
+ * @since   14
+ */
+
+public class ToolProviderFactory { 
+
+    private static ToolProvider provider = new JPackageToolProvider();
+
+    // Prevent creating an instance of this class
+    private ToolProviderFactory() {
+    }
+
+    /**
+     * Returns an {@link Optional} containing the {@code ToolProvider}
+     * if the given toolname is "jpackage". Returns an empty
+     * {@code Optional} if the given toolname is not "jpackage".
+     *
+     * @param   toolname {@code String} name of tool to look for.
+     * @return  an {@link Optional} containing the {@code ToolPovider}
+     *
+     * @since 14
+     */
+    public static Optional<ToolProvider> findFirst(String toolName) {
+        if ("jpackage".equals(toolName)) {
+            return Optional.of(provider);
+        } else {
+            return Optional.empty();
+        }
+    } 
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/AbstractAppImageBuilder.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,191 @@
+/*
+ * 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.incubator.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.incubator.jpackage.internal.resources.ResourceLocator;
+
+import static jdk.incubator.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.incubator.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);
+        }
+    }
+
+    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 =
+                        StandardBundlerParam.MAIN_CLASS.fetchFrom(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 "$ROOTDIR" + File.separator
+                + getAppDir().getFileName() + File.separator;
+    }
+
+    protected String getCfgRuntimeDir() {
+        return "$ROOTDIR" + 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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/AbstractBundler.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,64 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+
+/**
+ * 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.
+ */
+abstract class AbstractBundler implements Bundler {
+
+    static final BundlerParamInfo<File> IMAGES_ROOT =
+            new StandardBundlerParam<>(
+            "imagesRoot",
+            File.class,
+            params -> new File(
+                StandardBundlerParam.TEMP_ROOT.fetchFrom(params), "images"),
+            (s, p) -> null);
+
+    @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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/AbstractImageBundler.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/AddLauncherArguments.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage.internal;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.io.File;
+import jdk.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/AppImageFile.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,241 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+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.incubator.jpackage.internal.StandardBundlerParam.*;
+
+public 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;
+
+    private final static String 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;
+    }
+
+    /**
+     * Returns list of additional launchers configured for the application.
+     * Each item in the list is not null or empty string.
+     * Returns empty list for application without additional launchers.
+     */
+    List<String> getAddLauncherNames() {
+        return addLauncherNames;
+    }
+
+    /**
+     * Returns main application launcher name. Never returns null or empty value.
+     */
+    String getLauncherName() {
+        return launcherName;
+    }
+
+    void verifyCompatible() throws ConfigException {
+        // Just do nothing for now.
+    }
+
+    /**
+     * Returns path to application image info file.
+     * @param appImageDir - path to application image
+     */
+    public static Path getPathInAppImage(Path appImageDir) {
+        return appImageDir.resolve(FILENAME);
+    }
+
+    /**
+     * Saves file with application image info in application image.
+     * @param appImageDir - path to application image
+     * @throws IOException
+     */
+    static void save(Path appImageDir, Map<String, Object> params)
+            throws IOException {
+        IOUtils.createXml(getPathInAppImage(appImageDir), xml -> {
+            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();
+            }
+        });
+    }
+
+    /**
+     * Loads application image info from application image.
+     * @param appImageDir - path to application image
+     * @return valid info about application image or null
+     * @throws IOException
+     */
+    static AppImageFile load(Path appImageDir) throws IOException {
+        try {
+            Path path = getPathInAppImage(appImageDir);
+            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);
+        }
+    }
+
+    /**
+     * Returns list of launcher names configured for the application.
+     * The first item in the returned list is main launcher name.
+     * Following items in the list are names of additional launchers.
+     */
+    static List<String> getLauncherNames(Path appImageDir,
+            Map<String, ? super Object> params) {
+        List<String> launchers = new ArrayList<>();
+        try {
+            AppImageFile appImageInfo = AppImageFile.load(appImageDir);
+            if (appImageInfo != null) {
+                launchers.add(appImageInfo.getLauncherName());
+                launchers.addAll(appImageInfo.getAddLauncherNames());
+                return launchers;
+            }
+        } catch (IOException ioe) {
+            Log.verbose(ioe);
+        }
+
+        launchers.add(APP_NAME.fetchFrom(params));
+        ADD_LAUNCHERS.fetchFrom(params).stream().map(APP_NAME::fetchFrom).forEach(
+                launchers::add);
+        return launchers;
+    }
+
+    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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ApplicationLayout.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,153 @@
+/*
+ * 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.incubator.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/app"),
+                PathRole.RUNTIME, Path.of("Contents/runtime"),
+                PathRole.DESKTOP, Path.of("Contents/Resources"),
+                PathRole.DLLS, Path.of("Contents/MacOS"),
+                PathRole.APP_MODS, Path.of("Contents/app/mods")
+        ));
+    }
+
+    public static ApplicationLayout platformAppImage() {
+        if (Platform.isWindows()) {
+            return windowsAppImage();
+        }
+
+        if (Platform.isLinux()) {
+            return linuxAppImage();
+        }
+
+        if (Platform.isMac()) {
+            return macAppImage();
+        }
+
+        throw Platform.throwUnknownPlatformError();
+    }
+
+    public 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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ArgAction.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage.internal;
+
+@FunctionalInterface
+interface ArgAction {
+    void execute();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Arguments.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,802 @@
+/*
+ * 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.incubator.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.incubator.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("type", "t", 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),
+
+        BIND_SERVICES ("bind-services", OptionCategories.PROPERTY, () -> {
+            setOptionValue("bind-services", true);
+        }),
+
+        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_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.incubator.jpackage.internal.Bundler getPlatformBundler() {
+        boolean appImage = deployParams.isTargetAppImage();
+        String type = deployParams.getTargetFormat();
+        String bundleType = (appImage ?  "IMAGE" : "INSTALLER");
+
+        for (jdk.incubator.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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/BasicBundlers.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/BundleParams.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Bundler.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/BundlerParamInfo.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Bundlers.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/CLIHelp.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ConfigException.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/DeployParams.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/DottedVersion.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,145 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+/**
+ * Dotted numeric version string.
+ * E.g.: 1.0.37, 10, 0.5
+ */
+class DottedVersion implements Comparable<String> {
+
+    public DottedVersion(String version) {
+        greedy = true;
+        components = parseVersionString(version, greedy);
+        value = version;
+    }
+
+    private DottedVersion(String version, boolean greedy) {
+        this.greedy = greedy;
+        components = parseVersionString(version, greedy);
+        value = version;
+    }
+
+    public static DottedVersion greedy(String version) {
+        return new DottedVersion(version);
+    }
+
+    public static DottedVersion lazy(String version) {
+        return new DottedVersion(version, false);
+    }
+
+    @Override
+    public int compareTo(String o) {
+        int result = 0;
+        int[] otherComponents = parseVersionString(o, greedy);
+        for (int i = 0; i < Math.min(components.length, otherComponents.length)
+                && result == 0; ++i) {
+            result = components[i] - otherComponents[i];
+        }
+
+        if (result == 0) {
+            result = components.length - otherComponents.length;
+        }
+
+        return result;
+    }
+
+    private static int[] parseVersionString(String version, boolean greedy) {
+        Objects.requireNonNull(version);
+        if (version.isEmpty()) {
+            if (!greedy) {
+                return new int[] {0};
+            }
+            throw new IllegalArgumentException("Version may not be empty string");
+        }
+
+        int lastNotZeroIdx = -1;
+        List<Integer> components = new ArrayList<>();
+        for (var component : version.split("\\.", -1)) {
+            if (component.isEmpty()) {
+                if (!greedy) {
+                    break;
+                }
+
+                throw new IllegalArgumentException(String.format(
+                        "Version [%s] contains a zero lenght component", version));
+            }
+
+            if (!DIGITS.matcher(component).matches()) {
+                // Catch "+N" and "-N"  cases.
+                if (!greedy) {
+                    break;
+                }
+
+                throw new IllegalArgumentException(String.format(
+                        "Version [%s] contains invalid component [%s]", version,
+                        component));
+            }
+
+            final int num;
+            try {
+                num = Integer.parseInt(component);
+            } catch (NumberFormatException ex) {
+                if (!greedy) {
+                    break;
+                }
+
+                throw ex;
+            }
+
+            if (num != 0) {
+                lastNotZeroIdx = components.size();
+            }
+            components.add(num);
+        }
+
+        if (lastNotZeroIdx + 1 != components.size()) {
+            // Strip trailing zeros.
+            components = components.subList(0, lastNotZeroIdx + 1);
+        }
+
+        if (components.isEmpty()) {
+            components.add(0);
+        }
+        return components.stream().mapToInt(Integer::intValue).toArray();
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
+
+    final private int[] components;
+    final private String value;
+    final private boolean greedy;
+
+    private static final Pattern DIGITS = Pattern.compile("\\d+");
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Executor.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/FileAssociation.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,70 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.stream.Collectors;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+final class FileAssociation {
+    void verify() {
+        if (extensions.isEmpty()) {
+            Log.error(I18N.getString(
+                    "message.creating-association-with-null-extension"));
+        }
+    }
+
+    static List<FileAssociation> fetchFrom(Map<String, ? super Object> params) {
+        String launcherName = APP_NAME.fetchFrom(params);
+
+        return FILE_ASSOCIATIONS.fetchFrom(params).stream().filter(
+                Objects::nonNull).map(fa -> {
+                    FileAssociation assoc = new FileAssociation();
+
+                    assoc.launcherPath = Path.of(launcherName);
+                    assoc.description = FA_DESCRIPTION.fetchFrom(fa);
+                    assoc.extensions = Optional.ofNullable(
+                            FA_EXTENSIONS.fetchFrom(fa)).orElse(Collections.emptyList());
+                    assoc.mimeTypes = Optional.ofNullable(
+                            FA_CONTENT_TYPE.fetchFrom(fa)).orElse(Collections.emptyList());
+
+                    File icon = FA_ICON.fetchFrom(fa);
+                    if (icon != null) {
+                        assoc.iconPath = icon.toPath();
+                    }
+
+                    return assoc;
+                }).collect(Collectors.toList());
+    }
+
+    Path launcherPath;
+    Path iconPath;
+    List<String> mimeTypes;
+    List<String> extensions;
+    String description;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/I18N.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage.internal.resources.MainResources");
+
+    private static final ResourceBundle PLATFORM;
+
+    static {
+        if (Platform.isLinux()) {
+            PLATFORM = ResourceBundle.getBundle(
+                    "jdk.incubator.jpackage.internal.resources.LinuxResources");
+        } else if (Platform.isWindows()) {
+            PLATFORM = ResourceBundle.getBundle(
+                    "jdk.incubator.jpackage.internal.resources.WinResources");
+        } else if (Platform.isMac()) {
+            PLATFORM = ResourceBundle.getBundle(
+                    "jdk.incubator.jpackage.internal.resources.MacResources");
+        } else {
+            throw new IllegalStateException("Unknwon platform");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/IOUtils.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,335 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.*;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+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.*;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+
+/**
+ * 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());
+        }
+    }
+
+    public static Path replaceSuffix(Path path, String suffix) {
+        Path parent = path.getParent();
+        String filename = path.getFileName().toString().replaceAll("\\.[^.]*$", "")
+                + Optional.ofNullable(suffix).orElse("");
+        return parent != null ? parent.resolve(filename) : Path.of(filename);
+    }
+
+    public static Path addSuffix(Path path, String suffix) {
+        Path parent = path.getParent();
+        String filename = path.getFileName().toString() + suffix;
+        return parent != null ? parent.resolve(filename) : Path.of(filename);
+    }
+
+    public static String getSuffix(Path path) {
+        String filename = replaceSuffix(path.getFileName(), null).toString();
+        return path.getFileName().toString().substring(filename.length());
+    }
+
+    @FunctionalInterface
+    public static interface XmlConsumer {
+        void accept(XMLStreamWriter xml) throws IOException, XMLStreamException;
+    }
+
+    public static void createXml(Path dstFile, XmlConsumer xmlConsumer) throws
+            IOException {
+        XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
+        try (Writer w = Files.newBufferedWriter(dstFile)) {
+            // Wrap with pretty print proxy
+            XMLStreamWriter xml = (XMLStreamWriter) Proxy.newProxyInstance(
+                    XMLStreamWriter.class.getClassLoader(), new Class<?>[]{
+                XMLStreamWriter.class}, new PrettyPrintHandler(
+                    xmlFactory.createXMLStreamWriter(w)));
+
+            xml.writeStartDocument();
+            xmlConsumer.accept(xml);
+            xml.writeEndDocument();
+            xml.flush();
+            xml.close();
+        } catch (XMLStreamException ex) {
+            throw new IOException(ex);
+        } catch (IOException ex) {
+            throw ex;
+        }
+    }
+
+    private static class PrettyPrintHandler implements InvocationHandler {
+
+        PrettyPrintHandler(XMLStreamWriter target) {
+            this.target = target;
+        }
+
+        @Override
+        public Object invoke(Object proxy, Method method, Object[] args) throws
+                Throwable {
+            switch (method.getName()) {
+                case "writeStartElement":
+                    // update state of parent node
+                    if (depth > 0) {
+                        hasChildElement.put(depth - 1, true);
+                    }
+                    // reset state of current node
+                    hasChildElement.put(depth, false);
+                    // indent for current depth
+                    target.writeCharacters(EOL);
+                    target.writeCharacters(repeat(depth, INDENT));
+                    depth++;
+                    break;
+                case "writeEndElement":
+                    depth--;
+                    if (hasChildElement.get(depth) == true) {
+                        target.writeCharacters(EOL);
+                        target.writeCharacters(repeat(depth, INDENT));
+                    }
+                    break;
+                case "writeProcessingInstruction":
+                case "writeEmptyElement":
+                    // update state of parent node
+                    if (depth > 0) {
+                        hasChildElement.put(depth - 1, true);
+                    }
+                    // indent for current depth
+                    target.writeCharacters(EOL);
+                    target.writeCharacters(repeat(depth, INDENT));
+                    break;
+                default:
+                    break;
+            }
+            method.invoke(target, args);
+            return null;
+        }
+
+        private static String repeat(int d, String s) {
+            StringBuilder sb = new StringBuilder();
+            while (d-- > 0) {
+                sb.append(s);
+            }
+            return sb.toString();
+        }
+
+        private final XMLStreamWriter target;
+        private int depth = 0;
+        private final Map<Integer, Boolean> hasChildElement = new HashMap<>();
+        private static final String INDENT = "  ";
+        private static final String EOL = "\n";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/JLinkBundlerHelper.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,398 @@
+/*
+ * 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.incubator.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.util.jar.JarFile;
+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;
+import jdk.internal.module.ModulePath;
+
+
+final class JLinkBundlerHelper {
+
+    private static final ResourceBundle I18N = ResourceBundle.getBundle(
+            "jdk.incubator.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 getMainClassFromModule(Map<String, ? super Object> params) {
+        String mainModule = StandardBundlerParam.MODULE.fetchFrom(params);
+        if (mainModule != null)  {
+
+            int index = mainModule.indexOf("/");
+            if (index > 0) {
+                return 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)));
+                        return mainClass.get();
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    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;
+    }
+
+    static void execute(Map<String, ? super Object> params,
+            AbstractAppImageBuilder imageBuilder)
+            throws IOException, Exception {
+
+        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 =
+                StandardBundlerParam.BIND_SERVICES.fetchFrom(params);
+
+        // 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> modules = new ModuleHelper(
+                modulePath, addModules, limitModules).modules();
+
+        if (mainModule != null) {
+            modules.add(mainModule);
+        }
+
+        runJLink(outputDir, modulePath, modules, 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(
+            Collection<Path> paths, Collection<String> addModules) {
+
+        // the modules in the run-time image that export an API
+        Stream<String> systemRoots = ModuleFinder.ofSystem().findAll().stream()
+                .map(ModuleReference::descriptor)
+                .filter(JLinkBundlerHelper::exportsAPI)
+                .map(ModuleDescriptor::name);
+
+        Set<String> roots = Stream.concat(systemRoots,
+                 addModules.stream()).collect(Collectors.toSet());
+
+        ModuleFinder finder = createModuleFinder(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()
+                .anyMatch(e -> !e.isQualified());
+    }
+
+    private static ModuleFinder createModuleFinder(Collection<Path> modulePath) {
+        return ModuleFinder.compose(
+                ModulePath.of(JarFile.runtimeVersion(), true,
+                        modulePath.toArray(Path[]::new)),
+                ModuleFinder.ofSystem());
+    }
+
+    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, addModules));
+            }
+        }
+
+        Set<String> modules() {
+            return modules;
+        }
+
+        private static Set<String> getModuleNamesFromPath(List<Path> paths) {
+
+            return createModuleFinder(paths)
+                    .findAll()
+                    .stream()
+                    .map(ModuleReference::descriptor)
+                    .map(ModuleDescriptor::name)
+                    .collect(Collectors.toSet());
+        }
+    }
+
+    private static void runJLink(Path output, List<Path> modulePath,
+            Set<String> modules, Set<String> limitModules,
+            HashMap<String, String> user, boolean bindServices)
+            throws PackagerException {
+
+        // This is just to ensure jlink is given a non-existant directory
+        // The passed in output path should be non-existant or empty directory
+        try {
+            IOUtils.deleteRecursive(output.toFile());
+        } catch (IOException ioe) {
+            throw new PackagerException(ioe);
+        }
+
+        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 PackagerException("error.jlink.failed" , 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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/JPackageToolProvider.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Log.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ModFile.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/OverridableResource.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,219 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.RESOURCE_DIR;
+import jdk.incubator.jpackage.internal.resources.ResourceLocator;
+
+/**
+ * Resource file that may have the default value supplied by jpackage. It can be
+ * overridden by a file from resource directory set with {@code --resource-dir}
+ * jpackage parameter.
+ *
+ * Resource has default name and public name. Default name is the name of a file
+ * in {@code jdk.incubator.jpackage.internal.resources} package that provides the default
+ * value of the resource.
+ *
+ * Public name is a path relative to resource directory to a file with custom
+ * value of the resource.
+ *
+ * Use #setPublicName to set the public name.
+ *
+ * If #setPublicName was not called, name of file passed in #saveToFile function
+ * will be used as a public name.
+ *
+ * Use #setExternal to set arbitrary file as a source of resource. If non-null
+ * value was passed in #setExternal call that value will be used as a path to file
+ * to copy in the destination file passed in #saveToFile function call.
+ */
+final class OverridableResource {
+
+    OverridableResource(String defaultName) {
+        this.defaultName = defaultName;
+    }
+
+    OverridableResource setSubstitutionData(Map<String, String> v) {
+        if (v != null) {
+            // Disconnect `v`
+            substitutionData = new HashMap<>(v);
+        } else {
+            substitutionData = null;
+        }
+        return this;
+    }
+
+    OverridableResource setCategory(String v) {
+        category = v;
+        return this;
+    }
+
+    OverridableResource setResourceDir(Path v) {
+        resourceDir = v;
+        return this;
+    }
+
+    OverridableResource setResourceDir(File v) {
+        return setResourceDir(toPath(v));
+    }
+
+    /**
+     * Set name of file to look for in resource dir.
+     *
+     * @return this
+     */
+    OverridableResource setPublicName(Path v) {
+        publicName = v;
+        return this;
+    }
+
+    OverridableResource setPublicName(String v) {
+        return setPublicName(Path.of(v));
+    }
+
+    OverridableResource setExternal(Path v) {
+        externalPath = v;
+        return this;
+    }
+
+    OverridableResource setExternal(File v) {
+        return setExternal(toPath(v));
+    }
+
+    void saveToFile(Path dest) throws IOException {
+        final String printableCategory;
+        if (category != null) {
+            printableCategory = String.format("[%s]", category);
+        } else {
+            printableCategory = "";
+        }
+
+        if (externalPath != null && externalPath.toFile().exists()) {
+            Log.verbose(MessageFormat.format(I18N.getString(
+                    "message.using-custom-resource-from-file"),
+                    printableCategory,
+                    externalPath.toAbsolutePath().normalize()));
+
+            try (InputStream in = Files.newInputStream(externalPath)) {
+                processResourceStream(in, dest);
+            }
+            return;
+        }
+
+        final Path resourceName = Optional.ofNullable(publicName).orElse(
+                dest.getFileName());
+
+        if (resourceDir != null) {
+            final Path customResource = resourceDir.resolve(resourceName);
+            if (customResource.toFile().exists()) {
+                Log.verbose(MessageFormat.format(I18N.getString(
+                        "message.using-custom-resource"), printableCategory,
+                        resourceDir.normalize().toAbsolutePath().relativize(
+                                customResource.normalize().toAbsolutePath())));
+
+                try (InputStream in = Files.newInputStream(customResource)) {
+                    processResourceStream(in, dest);
+                }
+                return;
+            }
+        }
+
+        if (defaultName != null) {
+            Log.verbose(MessageFormat.format(
+                    I18N.getString("message.using-default-resource"),
+                    defaultName, printableCategory, resourceName));
+
+            try (InputStream in = readDefault(defaultName)) {
+                processResourceStream(in, dest);
+            }
+        }
+    }
+
+    void saveToFile(File dest) throws IOException {
+        saveToFile(dest.toPath());
+    }
+
+    static InputStream readDefault(String resourceName) {
+        return ResourceLocator.class.getResourceAsStream(resourceName);
+    }
+
+    static OverridableResource createResource(String defaultName,
+            Map<String, ? super Object> params) {
+        return new OverridableResource(defaultName).setResourceDir(
+                RESOURCE_DIR.fetchFrom(params));
+    }
+
+    private static List<String> substitute(Stream<String> lines,
+            Map<String, String> substitutionData) {
+        return lines.map(line -> {
+            String result = line;
+            for (var entry : substitutionData.entrySet()) {
+                result = result.replace(entry.getKey(), Optional.ofNullable(
+                        entry.getValue()).orElse(""));
+            }
+            return result;
+        }).collect(Collectors.toList());
+    }
+
+    private static Path toPath(File v) {
+        if (v != null) {
+            return v.toPath();
+        }
+        return null;
+    }
+
+    private void processResourceStream(InputStream rawResource, Path dest)
+            throws IOException {
+        if (substitutionData == null) {
+            Files.createDirectories(dest.getParent());
+            Files.copy(rawResource, dest, StandardCopyOption.REPLACE_EXISTING);
+        } else {
+            // Utf8 in and out
+            try (BufferedReader reader = new BufferedReader(
+                    new InputStreamReader(rawResource, StandardCharsets.UTF_8))) {
+                Files.createDirectories(dest.getParent());
+                Files.write(dest, substitute(reader.lines(), substitutionData));
+            }
+        }
+    }
+
+    private Map<String, String> substitutionData;
+    private String category;
+    private Path resourceDir;
+    private Path publicName;
+    private Path externalPath;
+    private final String defaultName;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/PackagerException.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/PathGroup.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,261 @@
+/*
+ * 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.incubator.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;
+import java.util.stream.Stream;
+
+
+/**
+ * Group of paths.
+ * Each path in the group is assigned a unique id.
+ */
+final class PathGroup {
+    PathGroup(Map<Object, Path> paths) {
+        entries = new HashMap<>(paths);
+    }
+
+    Path getPath(Object id) {
+        if (id == null) {
+            throw new NullPointerException();
+        }
+        return entries.get(id);
+    }
+
+    void setPath(Object id, Path path) {
+        if (path != null) {
+            entries.put(id, path);
+        } else {
+            entries.remove(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())) {
+            try (Stream<Path> stream = Files.walk(dir)) {
+                reply += stream.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, null, false);
+    }
+
+    void move(PathGroup dst) throws IOException {
+        copy(this, dst, null, true);
+    }
+
+    void transform(PathGroup dst, TransformHandler handler) throws IOException {
+        copy(this, dst, handler, false);
+    }
+
+    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());
+        }
+
+        default void transform(Facade<T> dst, TransformHandler handler) throws
+                IOException {
+            pathGroup().transform(dst.pathGroup(), handler);
+        }
+    }
+
+    static interface TransformHandler {
+        public void copyFile(Path src, Path dst) throws IOException;
+        public void createDirectory(Path dir) throws IOException;
+    }
+
+    private static void copy(PathGroup src, PathGroup dst,
+            TransformHandler handler, boolean move) throws IOException {
+        List<Map.Entry<Path, Path>> copyItems = new ArrayList<>();
+        List<Path> excludeItems = new ArrayList<>();
+
+        for (var id: src.entries.keySet()) {
+            Path srcPath = src.entries.get(id);
+            if (dst.entries.containsKey(id)) {
+                copyItems.add(Map.entry(srcPath, dst.entries.get(id)));
+            } else {
+                excludeItems.add(srcPath);
+            }
+        }
+
+        copy(move, copyItems, excludeItems, handler);
+    }
+
+    private static void copy(boolean move, List<Map.Entry<Path, Path>> entries,
+            List<Path> excludePaths, TransformHandler handler) throws
+            IOException {
+
+        if (handler == null) {
+            handler = new TransformHandler() {
+                @Override
+                public void copyFile(Path src, Path dst) throws IOException {
+                    Files.createDirectories(dst.getParent());
+                    if (move) {
+                        Files.move(src, dst);
+                    } else {
+                        Files.copy(src, dst);
+                    }
+                }
+
+                @Override
+                public void createDirectory(Path dir) throws IOException {
+                    Files.createDirectories(dir);
+                }
+            };
+        }
+
+        // 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()) {
+               try (Stream<Path> stream = Files.walk(src)) {
+                   stream.sequential().forEach(path -> actions.put(dst.resolve(
+                            src.relativize(path)).normalize(), path));
+               }
+            } else {
+                actions.put(dst.normalize(), src);
+            }
+        }
+
+        for (var action : actions.entrySet()) {
+            Path dst = action.getKey();
+            Path src = action.getValue();
+
+            if (excludePaths.stream().anyMatch(src::startsWith)) {
+                continue;
+            }
+
+            if (src.equals(dst) || !src.toFile().exists()) {
+                continue;
+            }
+
+            if (src.toFile().isDirectory()) {
+                handler.createDirectory(dst);
+            } else {
+                handler.copyFile(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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Platform.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,121 @@
+/*
+ * 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.incubator.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;
+    }
+
+    static RuntimeException throwUnknownPlatformError() {
+        throw new IllegalArgumentException("Unknown platform");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/PlatformPackage.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/RelativeFileSet.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ScriptRunner.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,119 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.APP_NAME;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.CONFIG_ROOT;
+
+/**
+ * Runs custom script from resource directory.
+ */
+class ScriptRunner {
+    ScriptRunner() {
+        environment = new ProcessBuilder().environment();
+    }
+
+    ScriptRunner setResourceCategoryId(String v) {
+        resourceCategoryId = v;
+        return this;
+    }
+
+    ScriptRunner setDirectory(Path v) {
+        directory = v;
+        return this;
+    }
+
+    ScriptRunner setScriptNameSuffix(String v) {
+        scriptNameSuffix = v;
+        return this;
+    }
+
+    ScriptRunner addEnvironment(Map<String, String> v) {
+        environment.putAll(v);
+        return this;
+    }
+
+    ScriptRunner setEnvironmentVariable(String envVarName, String envVarValue) {
+        Objects.requireNonNull(envVarName);
+        if (envVarValue == null) {
+            environment.remove(envVarName);
+        } else {
+            environment.put(envVarName, envVarValue);
+        }
+        return this;
+    }
+
+    public void run(Map<String, ? super Object> params) throws IOException {
+        String scriptName = String.format("%s-%s%s", APP_NAME.fetchFrom(params),
+                scriptNameSuffix, scriptSuffix());
+        Path scriptPath = CONFIG_ROOT.fetchFrom(params).toPath().resolve(
+                scriptName);
+        createResource(null, params)
+                .setCategory(I18N.getString(resourceCategoryId))
+                .saveToFile(scriptPath);
+        if (!Files.exists(scriptPath)) {
+            return;
+        }
+
+        ProcessBuilder pb = new ProcessBuilder(shell(),
+                scriptPath.toAbsolutePath().toString());
+        Map<String, String> workEnvironment = pb.environment();
+        workEnvironment.clear();
+        workEnvironment.putAll(environment);
+
+        if (directory != null) {
+            pb.directory(directory.toFile());
+        }
+
+        Executor.of(pb).executeExpectSuccess();
+    }
+
+    private static String shell() {
+        if (Platform.isWindows()) {
+            return "cscript";
+        }
+        return Optional.ofNullable(System.getenv("SHELL")).orElseGet(() -> "sh");
+    }
+
+    private static String scriptSuffix() {
+        if (Platform.isWindows()) {
+            return ".wsf";
+        }
+        return ".sh";
+    }
+
+    private String scriptNameSuffix;
+    private String resourceCategoryId;
+    private Path directory;
+    private Map<String, String> environment;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/StandardBundlerParam.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,790 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.module.ModuleDescriptor;
+import java.lang.module.ModuleDescriptor.Version;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 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.incubator.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.getMainClassFromModule(
+                                    params);
+                        }
+                        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.incubator.jpackage").toFile();
+                        } catch (IOException ioe) {
+                            return null;
+                        }
+                    },
+                    (s, p) -> new File(s)
+            );
+
+    public static final StandardBundlerParam<File> CONFIG_ROOT =
+            new StandardBundlerParam<>(
+                "configRoot",
+                File.class,
+                params -> {
+                    File root =
+                            new File(TEMP_ROOT.fetchFrom(params), "config");
+                    root.mkdirs();
+                    return root;
+                },
+                (s, p) -> null
+            );
+
+    static final StandardBundlerParam<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> BIND_SERVICES =
+            new StandardBundlerParam<>(
+                    Arguments.CLIOptions.BIND_SERVICES.getId(),
+                    Boolean.class,
+                    params -> false,
+                    (s, p) -> (s == null || "null".equalsIgnoreCase(s)) ?
+                            true : Boolean.valueOf(s)
+            );
+
+
+    static final StandardBundlerParam<Boolean> VERBOSE  =
+            new StandardBundlerParam<>(
+                    Arguments.CLIOptions.VERBOSE.getId(),
+                    Boolean.class,
+                    params -> false,
+                    // 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 ||
+               hasAppImage || isRuntimeInstaller(params)) {
+            return;
+        }
+        if (hasModule) {
+            if (JLinkBundlerHelper.getMainClassFromModule(params) == null) {
+                throw new ConfigException(
+                        I18N.getString("ERR_NoMainClass"), null);
+            }
+        } else {
+            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 {
+                    try (Stream<Path> stream = Files.walk(f.toPath())) {
+                        stream.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ToolValidator.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,136 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+
+public final class ToolValidator {
+
+    ToolValidator(String tool) {
+        this(Path.of(tool));
+    }
+
+    ToolValidator(Path toolPath) {
+        this.toolPath = toolPath;
+        args = new ArrayList<>();
+
+        if (Platform.getPlatform() == Platform.LINUX) {
+            setCommandLine("--version");
+        }
+
+        setToolNotFoundErrorHandler(null);
+        setToolOldVersionErrorHandler(null);
+    }
+
+    ToolValidator setCommandLine(String... args) {
+        this.args = List.of(args);
+        return this;
+    }
+
+    ToolValidator setMinimalVersion(Comparable<String> v) {
+        minimalVersion = v;
+        return this;
+    }
+
+    ToolValidator setVersionParser(Function<Stream<String>, String> v) {
+        versionParser = v;
+        return this;
+    }
+
+    ToolValidator setToolNotFoundErrorHandler(
+            BiFunction<String, IOException, ConfigException> v) {
+        toolNotFoundErrorHandler = v;
+        return this;
+    }
+
+    ToolValidator setToolOldVersionErrorHandler(BiFunction<String, String, ConfigException> v) {
+        toolOldVersionErrorHandler = v;
+        return this;
+    }
+
+    ConfigException validate() {
+        List<String> cmdline = new ArrayList<>();
+        cmdline.add(toolPath.toString());
+        cmdline.addAll(args);
+
+        String name = toolPath.getFileName().toString();
+        try {
+            ProcessBuilder pb = new ProcessBuilder(cmdline);
+            AtomicBoolean canUseTool = new AtomicBoolean();
+            if (minimalVersion == null) {
+                // No version check.
+                canUseTool.setPlain(true);
+            }
+
+            String[] version = new String[1];
+            Executor.of(pb).setOutputConsumer(lines -> {
+                if (versionParser != null && minimalVersion != null) {
+                    version[0] = versionParser.apply(lines);
+                    if (minimalVersion.compareTo(version[0]) < 0) {
+                        canUseTool.setPlain(true);
+                    }
+                }
+            }).execute();
+
+            if (!canUseTool.getPlain()) {
+                if (toolOldVersionErrorHandler != null) {
+                    return toolOldVersionErrorHandler.apply(name, version[0]);
+                }
+                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) {
+            if (toolNotFoundErrorHandler != null) {
+                return toolNotFoundErrorHandler.apply(name, 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 Path toolPath;
+    private List<String> args;
+    private Comparable<String> minimalVersion;
+    private Function<Stream<String>, String> versionParser;
+    private BiFunction<String, IOException, ConfigException> toolNotFoundErrorHandler;
+    private BiFunction<String, String, ConfigException> toolOldVersionErrorHandler;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ValidOptions.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage.internal;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import jdk.incubator.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.BIND_SERVICES.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_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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/HelpResources.properties	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,275 @@
+#
+# 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 an application package suitable for the host system:\n\
+\        For a modular application:\n\
+\            jpackage -n name -p modulePath -m moduleName/className\n\
+\        For a non-modular application:\n\
+\            jpackage -i inputDir -n name \\\n\
+\                --main-class className --main-jar myJar.jar\n\
+\        From a pre-built application image:\n\
+\            jpackage -n name --app-image appImageDir\n\
+\    Generate an application image:\n\
+\        For a modular application:\n\
+\            jpackage --type app-image -n name -p modulePath \\\n\
+\                -m moduleName/className\n\
+\        For a non-modular application:\n\
+\            jpackage --type app-image -i inputDir -n name \\\n\
+\                --main-class className --main-jar myJar.jar\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 --type app-image -n name \\\n\
+\                -m moduleName/className --runtime-image appRuntimeImage\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\
+\  --type -t <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 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\
+\  --bind-services \n\
+\          Pass on --bind-services option to jlink (which will link in \n\
+\          service provider modules and their dependences) \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.\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 <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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/HelpResources_ja.properties	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,275 @@
+#
+# 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 an application package suitable for the host system:\n\
+\        For a modular application:\n\
+\            jpackage -n name -p modulePath -m moduleName/className\n\
+\        For a non-modular application:\n\
+\            jpackage -i inputDir -n name \\\n\
+\                --main-class className --main-jar myJar.jar\n\
+\        From a pre-built application image:\n\
+\            jpackage -n name --app-image appImageDir\n\
+\    Generate an application image:\n\
+\        For a modular application:\n\
+\            jpackage --type app-image -n name -p modulePath \\\n\
+\                -m moduleName/className\n\
+\        For a non-modular application:\n\
+\            jpackage --type app-image -i inputDir -n name \\\n\
+\                --main-class className --main-jar myJar.jar\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 --type app-image -n name \\\n\
+\                -m moduleName/className --runtime-image appRuntimeImage\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\
+\  --type -t <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 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\
+\  --bind-services \n\
+\          Pass on --bind-services option to jlink (which will link in \n\
+\          service provider modules and their dependences) \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.\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 <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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/HelpResources_zh_CN.properties	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,275 @@
+#
+# 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 an application package suitable for the host system:\n\
+\        For a modular application:\n\
+\            jpackage -n name -p modulePath -m moduleName/className\n\
+\        For a non-modular application:\n\
+\            jpackage -i inputDir -n name \\\n\
+\                --main-class className --main-jar myJar.jar\n\
+\        From a pre-built application image:\n\
+\            jpackage -n name --app-image appImageDir\n\
+\    Generate an application image:\n\
+\        For a modular application:\n\
+\            jpackage --type app-image -n name -p modulePath \\\n\
+\                -m moduleName/className\n\
+\        For a non-modular application:\n\
+\            jpackage --type app-image -i inputDir -n name \\\n\
+\                --main-class className --main-jar myJar.jar\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 --type app-image -n name \\\n\
+\                -m moduleName/className --runtime-image appRuntimeImage\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\
+\  --type -t <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 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\
+\  --bind-services \n\
+\          Pass on --bind-services option to jlink (which will link in \n\
+\          service provider modules and their dependences) \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.\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 <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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/MainResources.properties	Tue Nov 12 10:18:11 2019 -0500
@@ -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
+
+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
+error.jlink.failed=jlink failed with: {0}
+
+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 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 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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/MainResources_ja.properties	Tue Nov 12 10:18:11 2019 -0500
@@ -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
+
+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
+error.jlink.failed=jlink failed with: {0}
+
+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 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 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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/MainResources_zh_CN.properties	Tue Nov 12 10:18:11 2019 -0500
@@ -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
+
+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
+error.jlink.failed=jlink failed with: {0}
+
+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 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 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.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/ResourceLocator.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/main/CommandLine.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage/share/classes/jdk/incubator/jpackage/main/Main.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,126 @@
+/*
+ * 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.incubator.jpackage.main;
+
+import jdk.incubator.jpackage.internal.Arguments;
+import jdk.incubator.jpackage.internal.Log;
+import jdk.incubator.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.incubator.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.incubator.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) {
+        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.incubator.jpackage/share/classes/module-info.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.  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.
+ *
+ * @moduleGraph
+ * @since 14
+ */
+
+module jdk.incubator.jpackage {
+    requires jdk.jlink;
+
+    requires java.desktop;
+
+    uses jdk.incubator.jpackage.internal.Bundler;
+    uses jdk.incubator.jpackage.internal.Bundlers;
+
+    provides jdk.incubator.jpackage.internal.Bundlers with
+        jdk.incubator.jpackage.internal.BasicBundlers;
+
+/*
+ * while jpackage is incubating, do not provide ToolProvider directly, but
+ * instead export jdk.incubator.jpackage.ToolProviderFactory
+ *
+ *    provides java.util.spi.ToolProvider
+ *        with jdk.incubator.jpackage.internal.JPackageToolProvider;
+ */
+    exports jdk.incubator.jpackage;
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/share/native/libapplauncher/FileAttributes.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/FilePath.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/Helpers.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/Helpers.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/IniFile.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/IniFile.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/JavaVirtualMachine.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/JavaVirtualMachine.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/Library.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/Library.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/Macros.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,74 @@
+/*
+ * 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("$ROOTDIR"), package.GetPackageRootDirectory());
+    macros.AddMacro(_T("$APPDIR"), package.GetPackageAppDirectory());
+    macros.AddMacro(_T("$BINDIR"), package.GetPackageLauncherDirectory());
+}
+
+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.incubator.jpackage/share/native/libapplauncher/Macros.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/Messages.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/Messages.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/OrderedMap.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/Package.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/Package.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/Platform.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/Platform.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/PlatformString.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/PlatformString.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/Properties.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/PropertyFile.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/PropertyFile.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/share/native/libapplauncher/main.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/unix/native/libapplauncher/FileAttribute.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/unix/native/libapplauncher/FileAttributes.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/unix/native/libapplauncher/FilePath.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/unix/native/libapplauncher/PosixPlatform.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/unix/native/libapplauncher/PosixPlatform.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WinAppBundler.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,150 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.*;
+
+import static jdk.incubator.jpackage.internal.WindowsBundlerParam.*;
+
+public class WinAppBundler extends AbstractImageBundler {
+
+    private static final ResourceBundle I18N = ResourceBundle.getBundle(
+            "jdk.incubator.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 {
+            Objects.requireNonNull(params);
+            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);
+        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.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WinExeBundler.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,164 @@
+/*
+ * 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.incubator.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.incubator.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 msiBundler.supported(platformInstaller);
+    }
+
+    @Override
+    public boolean isDefault() {
+        return true;
+    }
+
+    @Override
+    public boolean validate(Map<String, ? super Object> params)
+            throws ConfigException {
+        return msiBundler.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 = msiBundler.bundle(params, exeImageDir);
+
+        try {
+            new ScriptRunner()
+            .setDirectory(msi.toPath().getParent())
+            .setResourceCategoryId("resource.post-msi-script")
+            .setScriptNameSuffix("post-msi")
+            .setEnvironmentVariable("JpMsiFile", msi.getAbsolutePath().toString())
+            .run(params);
+
+            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 = OverridableResource.readDefault(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 final WinMsiBundler msiBundler = new WinMsiBundler();
+
+    private static native int embedMSI(String exePath, String msiPath);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WinMsiBundler.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,580 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import static jdk.incubator.jpackage.internal.OverridableResource.createResource;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+import static jdk.incubator.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.wxf. Source file with application and Java run-time directory tree
+ * description.
+ * </ul>
+ * <p>
+ * main.wxs file is a copy of main.wxs resource from
+ * jdk.incubator.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
+ * </ul>
+ */
+public class WinMsiBundler  extends AbstractBundler {
+
+    public static final BundlerParamInfo<WinAppBundler> APP_BUNDLER =
+            new WindowsBundlerParam<>(
+            "win.app.bundler",
+            WinAppBundler.class,
+            params -> new WinAppBundler(),
+            null);
+
+    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
+            );
+
+    private static final BundlerParamInfo<String> UPGRADE_UUID =
+            new WindowsBundlerParam<>(
+            Arguments.CLIOptions.WIN_UPGRADE_UUID.getId(),
+            String.class,
+            null,
+            (s, p) -> 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) {
+        try {
+            if (wixToolset == null) {
+                wixToolset = WixTool.toolset();
+            }
+            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;
+    }
+
+    @Override
+    public boolean isDefault() {
+        return false;
+    }
+
+    private static UUID getUpgradeCode(Map<String, ? super Object> params) {
+        String upgradeCode = UPGRADE_UUID.fetchFrom(params);
+        if (upgradeCode != null) {
+            return UUID.fromString(upgradeCode);
+        }
+        return createNameUUID("UpgradeCode", params, List.of(VENDOR, APP_NAME));
+    }
+
+    private static UUID getProductCode(Map<String, ? super Object> params) {
+        return createNameUUID("ProductCode", params, List.of(VENDOR, APP_NAME,
+                VERSION));
+    }
+
+    private static UUID createNameUUID(String prefix,
+            Map<String, ? super Object> params,
+            List<StandardBundlerParam<String>> components) {
+        String key = Stream.concat(Stream.of(prefix), components.stream().map(
+                c -> c.fetchFrom(params))).collect(Collectors.joining("/"));
+        return UUID.nameUUIDFromBytes(key.getBytes(StandardCharsets.UTF_8));
+    }
+
+    @Override
+    public boolean validate(Map<String, ? super Object> params)
+            throws ConfigException {
+        try {
+            if (wixToolset == null) {
+                wixToolset = WixTool.toolset();
+            }
+
+            try {
+                getUpgradeCode(params);
+            } catch (IllegalArgumentException ex) {
+                throw new ConfigException(ex);
+            }
+
+            for (var toolInfo: wixToolset.values()) {
+                Log.verbose(MessageFormat.format(I18N.getString(
+                        "message.tool-version"), toolInfo.path.getFileName(),
+                        toolInfo.version));
+            }
+
+            wixSourcesBuilder.setWixVersion(wixToolset.get(WixTool.Light).version);
+
+            wixSourcesBuilder.logWixFeatures();
+
+            /********* 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);
+            }
+        }
+    }
+
+    // https://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 void 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);
+        }
+    }
+
+    public File bundle(Map<String, ? super Object> params, File outdir)
+            throws PackagerException {
+
+        IOUtils.writableOutputDir(outdir.toPath());
+
+        Path imageDir = MSI_IMAGE_DIR.fetchFrom(params).toPath();
+        try {
+            Files.createDirectories(imageDir);
+
+            prepareProto(params);
+
+            wixSourcesBuilder
+            .initFromParams(WIN_APP_IMAGE.fetchFrom(params).toPath(), params)
+            .createMainFragment(CONFIG_ROOT.fetchFrom(params).toPath().resolve(
+                    "bundle.wxf"));
+
+            Map<String, String> wixVars = prepareMainProjectFile(params);
+
+            new ScriptRunner()
+            .setDirectory(imageDir)
+            .setResourceCategoryId("resource.post-app-image-script")
+            .setScriptNameSuffix("post-image")
+            .setEnvironmentVariable("JpAppImageDir", imageDir.toAbsolutePath().toString())
+            .run(params);
+
+            return buildMSI(params, wixVars, outdir);
+        } catch (IOException ex) {
+            Log.verbose(ex);
+            throw new PackagerException(ex);
+        }
+    }
+
+    Map<String, String> prepareMainProjectFile(
+            Map<String, ? super Object> params) throws IOException {
+        Map<String, String> data = new HashMap<>();
+
+        final UUID productCode = getProductCode(params);
+        final UUID upgradeCode = getUpgradeCode(params);
+
+        data.put("JpProductCode", productCode.toString());
+        data.put("JpProductUpgradeCode", upgradeCode.toString());
+
+        Log.verbose(MessageFormat.format(I18N.getString("message.product-code"),
+                productCode));
+        Log.verbose(MessageFormat.format(I18N.getString("message.upgrade-code"),
+                upgradeCode));
+
+        data.put("JpAllowUpgrades", "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));
+
+        final Path configDir = CONFIG_ROOT.fetchFrom(params).toPath();
+
+        data.put("JpConfigDir", configDir.toAbsolutePath().toString());
+
+        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 = OverridableResource.readDefault(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 = OverridableResource.readDefault(fname)) {
+                Files.copy(is, Paths.get(
+                        CONFIG_ROOT.fetchFrom(params).getAbsolutePath(),
+                        fname));
+            }
+        }
+
+        createResource("main.wxs", params)
+                .setCategory(I18N.getString("resource.main-wix-file"))
+                .saveToFile(configDir.resolve("main.wxs"));
+
+        createResource("overrides.wxi", params)
+                .setCategory(I18N.getString("resource.overrides-wix-file"))
+                .saveToFile(configDir.resolve("overrides.wxi"));
+
+        return data;
+    }
+
+    private File buildMSI(Map<String, ? super Object> params,
+            Map<String, String> wixVars, File outdir)
+            throws IOException {
+
+        File msiOut = new File(
+                outdir, INSTALLER_FILE_NAME.fetchFrom(params) + ".msi");
+
+        Log.verbose(MessageFormat.format(I18N.getString(
+                "message.preparing-msi-config"), msiOut.getAbsolutePath()));
+
+        WixPipeline wixPipeline = new WixPipeline()
+        .setToolset(wixToolset.entrySet().stream().collect(
+                Collectors.toMap(
+                        entry -> entry.getKey(),
+                        entry -> entry.getValue().path)))
+        .setWixObjDir(TEMP_ROOT.fetchFrom(params).toPath().resolve("wixobj"))
+        .setWorkDir(WIN_APP_IMAGE.fetchFrom(params).toPath())
+        .addSource(CONFIG_ROOT.fetchFrom(params).toPath().resolve("main.wxs"), wixVars)
+        .addSource(CONFIG_ROOT.fetchFrom(params).toPath().resolve("bundle.wxf"), null);
+
+        Log.verbose(MessageFormat.format(I18N.getString(
+                "message.generating-msi"), msiOut.getAbsolutePath()));
+
+        boolean enableLicenseUI = (LICENSE_FILE.fetchFrom(params) != null);
+        boolean enableInstalldirUI = INSTALLDIR_CHOOSER.fetchFrom(params);
+
+        List<String> lightArgs = new ArrayList<>();
+
+        if (!MSI_SYSTEM_WIDE.fetchFrom(params)) {
+            wixPipeline.addLightOptions("-sice:ICE91");
+        }
+        if (enableLicenseUI || enableInstalldirUI) {
+            wixPipeline.addLightOptions("-ext", "WixUIExtension");
+        }
+
+        wixPipeline.addLightOptions("-loc",
+                CONFIG_ROOT.fetchFrom(params).toPath().resolve(I18N.getString(
+                        "resource.wxl-file-name")).toAbsolutePath().toString());
+
+        // Only needed if we using CA dll, so Wix can find it
+        if (enableInstalldirUI) {
+            wixPipeline.addLightOptions("-b", CONFIG_ROOT.fetchFrom(params).getAbsolutePath());
+        }
+
+        wixPipeline.buildMsi(msiOut.toPath().toAbsolutePath());
+
+        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);
+        }
+
+    }
+
+    private Map<WixTool, WixTool.ToolInfo> wixToolset;
+    private WixSourcesBuilder wixSourcesBuilder = new WixSourcesBuilder();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WindowsAppImageBuilder.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,367 @@
+/*
+ * 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.incubator.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.incubator.jpackage.internal.OverridableResource.createResource;
+
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+
+public class WindowsAppImageBuilder extends AbstractAppImageBuilder {
+
+    static {
+        System.loadLibrary("jpackage");
+    }
+
+    private static final ResourceBundle I18N = ResourceBundle.getBundle(
+            "jdk.incubator.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);
+
+        createResource(EXECUTABLE_PROPERTIES_TEMPLATE, params)
+                .setCategory(I18N.getString("resource.executable-properties-template"))
+                .setSubstitutionData(data)
+                .saveToFile(getConfig_ExecutableProperties(params));
+    }
+
+    private void createLauncherForEntryPoint(
+            Map<String, ? super Object> params) throws IOException {
+
+        File iconTarget = getConfig_AppIcon(params);
+
+        createResource(TEMPLATE_APP_ICON, params)
+                .setCategory("icon")
+                .setExternal(ICON_ICO.fetchFrom(params))
+                .saveToFile(iconTarget);
+
+        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.verbose(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.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WindowsBundlerParam.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,104 @@
+/*
+ * 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.incubator.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.incubator.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 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.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WindowsDefender.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WindowsRegistry.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WixPipeline.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,145 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.function.UnaryOperator;
+import java.util.stream.Stream;
+
+/**
+ * WiX pipeline. Compiles and links WiX sources.
+ */
+public class WixPipeline {
+    WixPipeline() {
+        sources = new ArrayList<>();
+        lightOptions = new ArrayList<>();
+    }
+
+    WixPipeline setToolset(Map<WixTool, Path> v) {
+        toolset = v;
+        return this;
+    }
+
+    WixPipeline setWixVariables(Map<String, String> v) {
+        wixVariables = v;
+        return this;
+    }
+
+    WixPipeline setWixObjDir(Path v) {
+        wixObjDir = v;
+        return this;
+    }
+
+    WixPipeline setWorkDir(Path v) {
+        workDir = v;
+        return this;
+    }
+
+    WixPipeline addSource(Path source, Map<String, String> wixVariables) {
+        WixSource entry = new WixSource();
+        entry.source = source;
+        entry.variables = wixVariables;
+        sources.add(entry);
+        return this;
+    }
+
+    WixPipeline addLightOptions(String ... v) {
+        lightOptions.addAll(List.of(v));
+        return this;
+    }
+
+    void buildMsi(Path msi) throws IOException {
+        List<Path> wixObjs = new ArrayList<>();
+        for (var source : sources) {
+            wixObjs.add(compile(source));
+        }
+
+        List<String> lightCmdline = new ArrayList<>(List.of(
+                toolset.get(WixTool.Light).toString(),
+                "-nologo",
+                "-spdb",
+                "-ext", "WixUtilExtension",
+                "-out", msi.toString()
+        ));
+
+        lightCmdline.addAll(lightOptions);
+        wixObjs.stream().map(Path::toString).forEach(lightCmdline::add);
+
+        Files.createDirectories(msi.getParent());
+        execute(lightCmdline);
+    }
+
+    private Path compile(WixSource wixSource) throws IOException {
+        UnaryOperator<Path> adjustPath = path -> {
+            return workDir != null ? path.toAbsolutePath() : path;
+        };
+
+        Path wixObj = adjustPath.apply(wixObjDir).resolve(IOUtils.replaceSuffix(
+                wixSource.source.getFileName(), ".wixobj"));
+
+        List<String> cmdline = new ArrayList<>(List.of(
+                toolset.get(WixTool.Candle).toString(),
+                "-nologo",
+                adjustPath.apply(wixSource.source).toString(),
+                "-ext", "WixUtilExtension",
+                "-arch", "x64",
+                "-out", wixObj.toAbsolutePath().toString()
+        ));
+
+        Map<String, String> appliedVaribales = new HashMap<>();
+        Stream.of(wixVariables, wixSource.variables)
+                .filter(Objects::nonNull)
+                .forEachOrdered(appliedVaribales::putAll);
+
+        appliedVaribales.entrySet().stream().map(wixVar -> String.format("-d%s=%s",
+                wixVar.getKey(), wixVar.getValue())).forEachOrdered(
+                cmdline::add);
+
+        execute(cmdline);
+
+        return wixObj;
+    }
+
+    private void execute(List<String> cmdline) throws IOException {
+        Executor.of(new ProcessBuilder(cmdline).directory(
+                workDir != null ? workDir.toFile() : null)).executeExpectSuccess();
+    }
+
+    private final static class WixSource {
+        Path source;
+        Map<String, String> variables;
+    }
+
+    private Map<WixTool, Path> toolset;
+    private Map<String, String> wixVariables;
+    private List<String> lightOptions;
+    private Path wixObjDir;
+    private Path workDir;
+    private List<WixSource> sources;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WixSourcesBuilder.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,847 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import jdk.incubator.jpackage.internal.IOUtils.XmlConsumer;
+import static jdk.incubator.jpackage.internal.StandardBundlerParam.*;
+import static jdk.incubator.jpackage.internal.WinMsiBundler.*;
+import static jdk.incubator.jpackage.internal.WindowsBundlerParam.MENU_GROUP;
+import static jdk.incubator.jpackage.internal.WindowsBundlerParam.WINDOWS_INSTALL_DIR;
+
+/**
+ * Creates application WiX source files.
+ */
+class WixSourcesBuilder {
+
+    WixSourcesBuilder setWixVersion(DottedVersion v) {
+        wixVersion = v;
+        return this;
+    }
+
+    WixSourcesBuilder initFromParams(Path appImageRoot,
+            Map<String, ? super Object> params) {
+        Supplier<ApplicationLayout> appImageSupplier = () -> {
+            if (StandardBundlerParam.isRuntimeInstaller(params)) {
+                return ApplicationLayout.javaRuntime();
+            } else {
+                return ApplicationLayout.platformAppImage();
+            }
+        };
+
+        systemWide = MSI_SYSTEM_WIDE.fetchFrom(params);
+
+        registryKeyPath = Path.of("Software",
+                VENDOR.fetchFrom(params),
+                APP_NAME.fetchFrom(params),
+                VERSION.fetchFrom(params)).toString();
+
+        installDir = (systemWide ? PROGRAM_FILES : LOCAL_PROGRAM_FILES).resolve(
+                WINDOWS_INSTALL_DIR.fetchFrom(params));
+
+        do {
+            ApplicationLayout layout = appImageSupplier.get();
+            // Don't want AppImageFile.FILENAME in installed application.
+            // Register it with app image at a role without a match in installed
+            // app layout to exclude it from layout transformation.
+            layout.pathGroup().setPath(new Object(),
+                    AppImageFile.getPathInAppImage(Path.of("")));
+
+            // Want absolute paths to source files in generated WiX sources.
+            // This is to handle scenario if sources would be processed from
+            // differnt current directory.
+            appImage = layout.resolveAt(appImageRoot.toAbsolutePath().normalize());
+        } while (false);
+
+        installedAppImage = appImageSupplier.get().resolveAt(INSTALLDIR);
+
+        shortcutFolders = new HashSet<>();
+        if (SHORTCUT_HINT.fetchFrom(params)) {
+            shortcutFolders.add(ShortcutsFolder.Desktop);
+        }
+        if (MENU_HINT.fetchFrom(params)) {
+            shortcutFolders.add(ShortcutsFolder.ProgramMenu);
+        }
+
+        if (StandardBundlerParam.isRuntimeInstaller(params)) {
+            launcherPaths = Collections.emptyList();
+        } else {
+            launcherPaths = AppImageFile.getLauncherNames(appImageRoot, params).stream()
+                    .map(name -> installedAppImage.launchersDirectory().resolve(name))
+                    .map(WixSourcesBuilder::addExeSuffixToPath)
+                    .collect(Collectors.toList());
+        }
+
+        programMenuFolderName = MENU_GROUP.fetchFrom(params);
+
+        initFileAssociations(params);
+
+        return this;
+    }
+
+    void createMainFragment(Path file) throws IOException {
+        removeFolderItems = new HashMap<>();
+        defaultedMimes = new HashSet<>();
+        IOUtils.createXml(file, xml -> {
+            xml.writeStartElement("Wix");
+            xml.writeDefaultNamespace("http://schemas.microsoft.com/wix/2006/wi");
+            xml.writeNamespace("util",
+                    "http://schemas.microsoft.com/wix/UtilExtension");
+
+            xml.writeStartElement("Fragment");
+
+            addFaComponentGroup(xml);
+
+            addShortcutComponentGroup(xml);
+
+            addFilesComponentGroup(xml);
+
+            xml.writeEndElement();  // <Fragment>
+
+            addIconsFragment(xml);
+
+            xml.writeEndElement(); // <Wix>
+        });
+    }
+
+    void logWixFeatures() {
+        if (wixVersion.compareTo("3.6") >= 0) {
+            Log.verbose(MessageFormat.format(I18N.getString(
+                    "message.use-wix36-features"), wixVersion));
+        }
+    }
+
+    private void normalizeFileAssociation(FileAssociation fa) {
+        fa.launcherPath = addExeSuffixToPath(
+                installedAppImage.launchersDirectory().resolve(fa.launcherPath));
+
+        if (fa.iconPath != null && !fa.iconPath.toFile().exists()) {
+            fa.iconPath = null;
+        }
+
+        if (fa.iconPath != null) {
+            fa.iconPath = fa.iconPath.toAbsolutePath();
+        }
+
+        // Filter out empty extensions.
+        fa.extensions = fa.extensions.stream().filter(Predicate.not(
+                String::isEmpty)).collect(Collectors.toList());
+    }
+
+    private static Path addExeSuffixToPath(Path path) {
+        return IOUtils.addSuffix(path, ".exe");
+    }
+
+    private Path getInstalledFaIcoPath(FileAssociation fa) {
+        String fname = String.format("fa_%s.ico", String.join("_", fa.extensions));
+        return installedAppImage.destktopIntegrationDirectory().resolve(fname);
+    }
+
+    private void initFileAssociations(Map<String, ? super Object> params) {
+        associations = FileAssociation.fetchFrom(params).stream()
+                .peek(this::normalizeFileAssociation)
+                // Filter out file associations without extensions.
+                .filter(fa -> !fa.extensions.isEmpty())
+                .collect(Collectors.toList());
+
+        associations.stream().filter(fa -> fa.iconPath != null).forEach(fa -> {
+            // Need to add fa icon in the image.
+            Object key = new Object();
+            appImage.pathGroup().setPath(key, fa.iconPath);
+            installedAppImage.pathGroup().setPath(key, getInstalledFaIcoPath(fa));
+        });
+    }
+
+    private static UUID createNameUUID(String str) {
+        return UUID.nameUUIDFromBytes(str.getBytes(StandardCharsets.UTF_8));
+    }
+
+    private static UUID createNameUUID(Path path, String role) {
+        if (path.isAbsolute() || !ROOT_DIRS.contains(path.getName(0))) {
+            throw throwInvalidPathException(path);
+        }
+        // Paths are case insensitive on Windows
+        String keyPath = path.toString().toLowerCase();
+        if (role != null) {
+            keyPath = role + "@" + keyPath;
+        }
+        return createNameUUID(keyPath);
+    }
+
+    /**
+     * Value for Id attribute of various WiX elements.
+     */
+    enum Id {
+        File,
+        Folder("dir"),
+        Shortcut,
+        ProgId,
+        Icon,
+        CreateFolder("mkdir"),
+        RemoveFolder("rm");
+
+        Id() {
+            this.prefix = name().toLowerCase();
+        }
+
+        Id(String prefix) {
+            this.prefix = prefix;
+        }
+
+        String of(Path path) {
+            if (this == Folder && KNOWN_DIRS.contains(path)) {
+                return path.getFileName().toString();
+            }
+
+            String result = of(path, prefix, name());
+
+            if (this == Icon) {
+                // Icon id constructed from UUID value is too long and triggers
+                // CNDL1000 warning, so use Java hash code instead.
+                result = String.format("%s%d", prefix, result.hashCode()).replace(
+                        "-", "_");
+            }
+
+            return result;
+        }
+
+        private static String of(Path path, String prefix, String role) {
+            Objects.requireNonNull(role);
+            Objects.requireNonNull(prefix);
+            return String.format("%s%s", prefix,
+                    createNameUUID(path, role).toString().replace("-", ""));
+        }
+
+        static String of(Path path, String prefix) {
+            return of(path, prefix, prefix);
+        }
+
+        private final String prefix;
+    }
+
+    enum Component {
+        File(cfg().file()),
+        Shortcut(cfg().file().withRegistryKeyPath()),
+        ProgId(cfg().file().withRegistryKeyPath()),
+        CreateFolder(cfg().withRegistryKeyPath()),
+        RemoveFolder(cfg().withRegistryKeyPath());
+
+        Component() {
+            this.cfg = cfg();
+            this.id = Id.valueOf(name());
+        }
+
+        Component(Config cfg) {
+            this.cfg = cfg;
+            this.id = Id.valueOf(name());
+        }
+
+        UUID guidOf(Path path) {
+            return createNameUUID(path, name());
+        }
+
+        String idOf(Path path) {
+            return id.of(path);
+        }
+
+        boolean isRegistryKeyPath() {
+            return cfg.withRegistryKeyPath;
+        }
+
+        boolean isFile() {
+            return cfg.isFile;
+        }
+
+        static void startElement(XMLStreamWriter xml, String componentId,
+                String componentGuid) throws XMLStreamException, IOException {
+            xml.writeStartElement("Component");
+            xml.writeAttribute("Win64", "yes");
+            xml.writeAttribute("Id", componentId);
+            xml.writeAttribute("Guid", componentGuid);
+        }
+
+        private static final class Config {
+            Config withRegistryKeyPath() {
+                withRegistryKeyPath = true;
+                return this;
+            }
+
+            Config file() {
+                isFile = true;
+                return this;
+            }
+
+            private boolean isFile;
+            private boolean withRegistryKeyPath;
+        }
+
+        private static Config cfg() {
+            return new Config();
+        }
+
+        private final Config cfg;
+        private final Id id;
+    };
+
+    private static void addComponentGroup(XMLStreamWriter xml, String id,
+            List<String> componentIds) throws XMLStreamException, IOException {
+        xml.writeStartElement("ComponentGroup");
+        xml.writeAttribute("Id", id);
+        componentIds = componentIds.stream().filter(Objects::nonNull).collect(
+                Collectors.toList());
+        for (var componentId : componentIds) {
+            xml.writeStartElement("ComponentRef");
+            xml.writeAttribute("Id", componentId);
+            xml.writeEndElement();
+        }
+        xml.writeEndElement();
+    }
+
+    private String addComponent(XMLStreamWriter xml, Path path,
+            Component role, XmlConsumer xmlConsumer) throws XMLStreamException,
+            IOException {
+
+        final Path directoryRefPath;
+        if (role.isFile()) {
+            directoryRefPath = path.getParent();
+        } else {
+            directoryRefPath = path;
+        }
+
+        xml.writeStartElement("DirectoryRef");
+        xml.writeAttribute("Id", Id.Folder.of(directoryRefPath));
+
+        final String componentId = "c" + role.idOf(path);
+        Component.startElement(xml, componentId, String.format("{%s}",
+                role.guidOf(path)));
+
+        boolean isRegistryKeyPath = !systemWide || role.isRegistryKeyPath();
+        if (isRegistryKeyPath) {
+            addRegistryKeyPath(xml, directoryRefPath);
+            if ((role.isFile() || (role == Component.CreateFolder
+                    && !systemWide)) && !SYSTEM_DIRS.contains(directoryRefPath)) {
+                xml.writeStartElement("RemoveFolder");
+                int counter = Optional.ofNullable(removeFolderItems.get(
+                        directoryRefPath)).orElse(Integer.valueOf(0)).intValue() + 1;
+                removeFolderItems.put(directoryRefPath, counter);
+                xml.writeAttribute("Id", String.format("%s_%d", Id.RemoveFolder.of(
+                        directoryRefPath), counter));
+                xml.writeAttribute("On", "uninstall");
+                xml.writeEndElement();
+            }
+        }
+
+        xml.writeStartElement(role.name());
+        if (role != Component.CreateFolder) {
+            xml.writeAttribute("Id", role.idOf(path));
+        }
+
+        if (!isRegistryKeyPath) {
+            xml.writeAttribute("KeyPath", "yes");
+        }
+
+        xmlConsumer.accept(xml);
+        xml.writeEndElement();
+
+        xml.writeEndElement(); // <Component>
+        xml.writeEndElement(); // <DirectoryRef>
+
+        return componentId;
+    }
+
+    private void addFaComponentGroup(XMLStreamWriter xml)
+            throws XMLStreamException, IOException {
+
+        List<String> componentIds = new ArrayList<>();
+        for (var fa : associations) {
+            componentIds.addAll(addFaComponents(xml, fa));
+        }
+        addComponentGroup(xml, "FileAssociations", componentIds);
+    }
+
+    private void addShortcutComponentGroup(XMLStreamWriter xml) throws
+            XMLStreamException, IOException {
+        List<String> componentIds = new ArrayList<>();
+        Set<ShortcutsFolder> defineShortcutFolders = new HashSet<>();
+        for (var launcherPath : launcherPaths) {
+            for (var folder : shortcutFolders) {
+                String componentId = addShortcutComponent(xml, launcherPath,
+                        folder);
+                if (componentId != null) {
+                    defineShortcutFolders.add(folder);
+                    componentIds.add(componentId);
+                }
+            }
+        }
+
+        for (var folder : defineShortcutFolders) {
+            Path path = folder.getPath(this);
+            componentIds.addAll(addRootBranch(xml, path));
+        }
+
+        addComponentGroup(xml, "Shortcuts", componentIds);
+    }
+
+    private String addShortcutComponent(XMLStreamWriter xml, Path launcherPath,
+            ShortcutsFolder folder) throws XMLStreamException, IOException {
+        Objects.requireNonNull(folder);
+
+        if (!INSTALLDIR.equals(launcherPath.getName(0))) {
+            throw throwInvalidPathException(launcherPath);
+        }
+
+        String launcherBasename = IOUtils.replaceSuffix(
+                launcherPath.getFileName(), "").toString();
+
+        Path shortcutPath = folder.getPath(this).resolve(launcherBasename);
+        return addComponent(xml, shortcutPath, Component.Shortcut, unused -> {
+            final Path icoFile = IOUtils.addSuffix(
+                    installedAppImage.destktopIntegrationDirectory().resolve(
+                            launcherBasename), ".ico");
+
+            xml.writeAttribute("Name", launcherBasename);
+            xml.writeAttribute("WorkingDirectory", INSTALLDIR.toString());
+            xml.writeAttribute("Advertise", "no");
+            xml.writeAttribute("IconIndex", "0");
+            xml.writeAttribute("Target", String.format("[#%s]",
+                    Component.File.idOf(launcherPath)));
+            xml.writeAttribute("Icon", Id.Icon.of(icoFile));
+        });
+    }
+
+    private List<String> addFaComponents(XMLStreamWriter xml,
+            FileAssociation fa) throws XMLStreamException, IOException {
+        List<String> components = new ArrayList<>();
+        for (var extension: fa.extensions) {
+            Path path = INSTALLDIR.resolve(String.format("%s_%s", extension,
+                    fa.launcherPath.getFileName()));
+            components.add(addComponent(xml, path, Component.ProgId, unused -> {
+                xml.writeAttribute("Description", fa.description);
+
+                if (fa.iconPath != null) {
+                    xml.writeAttribute("Icon", Id.File.of(getInstalledFaIcoPath(
+                            fa)));
+                    xml.writeAttribute("IconIndex", "0");
+                }
+
+                xml.writeStartElement("Extension");
+                xml.writeAttribute("Id", extension);
+                xml.writeAttribute("Advertise", "no");
+
+                var mimeIt = fa.mimeTypes.iterator();
+                if (mimeIt.hasNext()) {
+                    String mime = mimeIt.next();
+                    xml.writeAttribute("ContentType", mime);
+
+                    if (!defaultedMimes.contains(mime)) {
+                        xml.writeStartElement("MIME");
+                        xml.writeAttribute("ContentType", mime);
+                        xml.writeAttribute("Default", "yes");
+                        xml.writeEndElement();
+                        defaultedMimes.add(mime);
+                    }
+                }
+
+                xml.writeStartElement("Verb");
+                xml.writeAttribute("Id", "open");
+                xml.writeAttribute("Command", "Open");
+                xml.writeAttribute("Argument", "%1");
+                xml.writeAttribute("TargetFile", Id.File.of(fa.launcherPath));
+                xml.writeEndElement(); // <Verb>
+
+                xml.writeEndElement(); // <Extension>
+            }));
+        }
+
+        return components;
+    }
+
+    private List<String> addRootBranch(XMLStreamWriter xml, Path path)
+            throws XMLStreamException, IOException {
+        if (!ROOT_DIRS.contains(path.getName(0))) {
+            throw throwInvalidPathException(path);
+        }
+
+        Function<Path, String> createDirectoryName = dir -> null;
+
+        boolean sysDir = true;
+        int levels = 1;
+        var dirIt = path.iterator();
+        xml.writeStartElement("DirectoryRef");
+        xml.writeAttribute("Id", dirIt.next().toString());
+
+        path = path.getName(0);
+        while (dirIt.hasNext()) {
+            levels++;
+            Path name = dirIt.next();
+            path = path.resolve(name);
+
+            if (sysDir && !SYSTEM_DIRS.contains(path)) {
+                sysDir = false;
+                createDirectoryName = dir -> dir.getFileName().toString();
+            }
+
+            final String directoryId;
+            if (!sysDir && path.equals(installDir)) {
+                directoryId = INSTALLDIR.toString();
+            } else {
+                directoryId = Id.Folder.of(path);
+            }
+            xml.writeStartElement("Directory");
+            xml.writeAttribute("Id", directoryId);
+
+            String directoryName = createDirectoryName.apply(path);
+            if (directoryName != null) {
+                xml.writeAttribute("Name", directoryName);
+            }
+        }
+
+        while (0 != levels--) {
+            xml.writeEndElement();
+        }
+
+        List<String> componentIds = new ArrayList<>();
+        while (!SYSTEM_DIRS.contains(path = path.getParent())) {
+            componentIds.add(addRemoveDirectoryComponent(xml, path));
+        }
+
+        return componentIds;
+    }
+
+    private String addRemoveDirectoryComponent(XMLStreamWriter xml, Path path)
+            throws XMLStreamException, IOException {
+        return addComponent(xml, path, Component.RemoveFolder,
+                unused -> xml.writeAttribute("On", "uninstall"));
+    }
+
+    private List<String> addDirectoryHierarchy(XMLStreamWriter xml)
+            throws XMLStreamException, IOException {
+
+        Set<Path> allDirs = new HashSet<>();
+        Set<Path> emptyDirs = new HashSet<>();
+        appImage.transform(installedAppImage, new PathGroup.TransformHandler() {
+            @Override
+            public void copyFile(Path src, Path dst) throws IOException {
+                Path dir = dst.getParent();
+                createDirectory(dir);
+                emptyDirs.remove(dir);
+            }
+
+            @Override
+            public void createDirectory(final Path dir) throws IOException {
+                if (!allDirs.contains(dir)) {
+                    emptyDirs.add(dir);
+                }
+
+                Path it = dir;
+                while (it != null && allDirs.add(it)) {
+                    it = it.getParent();
+                }
+
+                it = dir;
+                while ((it = it.getParent()) != null && emptyDirs.remove(it));
+            }
+        });
+
+        List<String> componentIds = new ArrayList<>();
+        for (var dir : emptyDirs) {
+            componentIds.add(addComponent(xml, dir, Component.CreateFolder,
+                    unused -> {}));
+        }
+
+        if (!systemWide) {
+            // Per-user install requires <RemoveFolder> component in every
+            // directory.
+            for (var dir : allDirs.stream()
+                    .filter(Predicate.not(emptyDirs::contains))
+                    .filter(Predicate.not(removeFolderItems::containsKey))
+                    .collect(Collectors.toList())) {
+                componentIds.add(addRemoveDirectoryComponent(xml, dir));
+            }
+        }
+
+        allDirs.remove(INSTALLDIR);
+        for (var dir : allDirs) {
+            xml.writeStartElement("DirectoryRef");
+            xml.writeAttribute("Id", Id.Folder.of(dir.getParent()));
+            xml.writeStartElement("Directory");
+            xml.writeAttribute("Id", Id.Folder.of(dir));
+            xml.writeAttribute("Name", dir.getFileName().toString());
+            xml.writeEndElement();
+            xml.writeEndElement();
+        }
+
+        componentIds.addAll(addRootBranch(xml, installDir));
+
+        return componentIds;
+    }
+
+    private void addFilesComponentGroup(XMLStreamWriter xml)
+            throws XMLStreamException, IOException {
+
+        List<Map.Entry<Path, Path>> files = new ArrayList<>();
+        appImage.transform(installedAppImage, new PathGroup.TransformHandler() {
+            @Override
+            public void copyFile(Path src, Path dst) throws IOException {
+                files.add(Map.entry(src, dst));
+            }
+
+            @Override
+            public void createDirectory(final Path dir) throws IOException {
+            }
+        });
+
+        List<String> componentIds = new ArrayList<>();
+        for (var file : files) {
+            Path src = file.getKey();
+            Path dst = file.getValue();
+
+            componentIds.add(addComponent(xml, dst, Component.File, unused -> {
+                xml.writeAttribute("Source", src.normalize().toString());
+                Path name = dst.getFileName();
+                if (!name.equals(src.getFileName())) {
+                    xml.writeAttribute("Name", name.toString());
+                }
+            }));
+        }
+
+        componentIds.addAll(addDirectoryHierarchy(xml));
+
+        componentIds.add(addDirectoryCleaner(xml, INSTALLDIR));
+
+        addComponentGroup(xml, "Files", componentIds);
+    }
+
+    private void addIconsFragment(XMLStreamWriter xml) throws
+            XMLStreamException, IOException {
+
+        PathGroup srcPathGroup = appImage.pathGroup();
+        PathGroup dstPathGroup = installedAppImage.pathGroup();
+
+        // Build list of copy operations for all .ico files in application image
+        List<Map.Entry<Path, Path>> icoFiles = new ArrayList<>();
+        srcPathGroup.transform(dstPathGroup, new PathGroup.TransformHandler() {
+            @Override
+            public void copyFile(Path src, Path dst) throws IOException {
+                if (src.getFileName().toString().endsWith(".ico")) {
+                    icoFiles.add(Map.entry(src, dst));
+                }
+            }
+
+            @Override
+            public void createDirectory(Path dst) throws IOException {
+            }
+        });
+
+        xml.writeStartElement("Fragment");
+        for (var icoFile : icoFiles) {
+            xml.writeStartElement("Icon");
+            xml.writeAttribute("Id", Id.Icon.of(icoFile.getValue()));
+            xml.writeAttribute("SourceFile", icoFile.getKey().toString());
+            xml.writeEndElement();
+        }
+        xml.writeEndElement();
+    }
+
+    private void addRegistryKeyPath(XMLStreamWriter xml, Path path) throws
+            XMLStreamException, IOException {
+        addRegistryKeyPath(xml, path, () -> "ProductCode", () -> "[ProductCode]");
+    }
+
+    private void addRegistryKeyPath(XMLStreamWriter xml, Path path,
+            Supplier<String> nameAttr, Supplier<String> valueAttr) throws
+            XMLStreamException, IOException {
+
+        String regRoot = USER_PROFILE_DIRS.stream().anyMatch(path::startsWith)
+                || !systemWide ? "HKCU" : "HKLM";
+
+        xml.writeStartElement("RegistryKey");
+        xml.writeAttribute("Root", regRoot);
+        xml.writeAttribute("Key", registryKeyPath);
+        if (wixVersion.compareTo("3.6") < 0) {
+            xml.writeAttribute("Action", "createAndRemoveOnUninstall");
+        }
+        xml.writeStartElement("RegistryValue");
+        xml.writeAttribute("Type", "string");
+        xml.writeAttribute("KeyPath", "yes");
+        xml.writeAttribute("Name", nameAttr.get());
+        xml.writeAttribute("Value", valueAttr.get());
+        xml.writeEndElement(); // <RegistryValue>
+        xml.writeEndElement(); // <RegistryKey>
+    }
+
+    private String addDirectoryCleaner(XMLStreamWriter xml, Path path) throws
+            XMLStreamException, IOException {
+        if (wixVersion.compareTo("3.6") < 0) {
+            return null;
+        }
+
+        // rm -rf
+        final String baseId = Id.of(path, "rm_rf");
+        final String propertyId = baseId.toUpperCase();
+        final String componentId = ("c" + baseId);
+
+        xml.writeStartElement("Property");
+        xml.writeAttribute("Id", propertyId);
+        xml.writeStartElement("RegistrySearch");
+        xml.writeAttribute("Id", Id.of(path, "regsearch"));
+        xml.writeAttribute("Root", systemWide ? "HKLM" : "HKCU");
+        xml.writeAttribute("Key", registryKeyPath);
+        xml.writeAttribute("Type", "raw");
+        xml.writeAttribute("Name", propertyId);
+        xml.writeEndElement(); // <RegistrySearch>
+        xml.writeEndElement(); // <Property>
+
+        xml.writeStartElement("DirectoryRef");
+        xml.writeAttribute("Id", INSTALLDIR.toString());
+        Component.startElement(xml, componentId, "*");
+
+        addRegistryKeyPath(xml, INSTALLDIR, () -> propertyId, () -> {
+            // The following code converts a path to value to be saved in registry.
+            // E.g.:
+            //  INSTALLDIR -> [INSTALLDIR]
+            //  TERGETDIR/ProgramFiles64Folder/foo/bar -> [ProgramFiles64Folder]foo/bar
+            final Path rootDir = KNOWN_DIRS.stream()
+                    .sorted(Comparator.comparing(Path::getNameCount).reversed())
+                    .filter(path::startsWith)
+                    .findFirst().get();
+            StringBuilder sb = new StringBuilder();
+            sb.append(String.format("[%s]", rootDir.getFileName().toString()));
+            sb.append(rootDir.relativize(path).toString());
+            return sb.toString();
+        });
+
+        xml.writeStartElement(
+                "http://schemas.microsoft.com/wix/UtilExtension",
+                "RemoveFolderEx");
+        xml.writeAttribute("On", "uninstall");
+        xml.writeAttribute("Property", propertyId);
+        xml.writeEndElement(); // <RemoveFolderEx>
+        xml.writeEndElement(); // <Component>
+        xml.writeEndElement(); // <DirectoryRef>
+
+        return componentId;
+    }
+
+    private static IllegalArgumentException throwInvalidPathException(Path v) {
+        throw new IllegalArgumentException(String.format("Invalid path [%s]", v));
+    }
+
+    enum ShortcutsFolder {
+        ProgramMenu(PROGRAM_MENU_PATH),
+        Desktop(DESKTOP_PATH);
+
+        private ShortcutsFolder(Path root) {
+            this.root = root;
+        }
+
+        Path getPath(WixSourcesBuilder outer) {
+            if (this == ProgramMenu) {
+                return root.resolve(outer.programMenuFolderName);
+            }
+            return root;
+        }
+
+        private final Path root;
+    }
+
+    private DottedVersion wixVersion;
+
+    private boolean systemWide;
+
+    private String registryKeyPath;
+
+    private Path installDir;
+
+    private String programMenuFolderName;
+
+    private List<FileAssociation> associations;
+
+    private Set<ShortcutsFolder> shortcutFolders;
+
+    private List<Path> launcherPaths;
+
+    private ApplicationLayout appImage;
+    private ApplicationLayout installedAppImage;
+
+    private Map<Path, Integer> removeFolderItems;
+    private Set<String> defaultedMimes;
+
+    private final static Path TARGETDIR = Path.of("TARGETDIR");
+
+    private final static Path INSTALLDIR = Path.of("INSTALLDIR");
+
+    private final static Set<Path> ROOT_DIRS = Set.of(INSTALLDIR, TARGETDIR);
+
+    private final static Path PROGRAM_MENU_PATH = TARGETDIR.resolve("ProgramMenuFolder");
+
+    private final static Path DESKTOP_PATH = TARGETDIR.resolve("DesktopFolder");
+
+    private final static Path PROGRAM_FILES = TARGETDIR.resolve("ProgramFiles64Folder");
+
+    private final static Path LOCAL_PROGRAM_FILES = TARGETDIR.resolve("LocalAppDataFolder");
+
+    private final static Set<Path> SYSTEM_DIRS = Set.of(TARGETDIR,
+            PROGRAM_MENU_PATH, DESKTOP_PATH, PROGRAM_FILES, LOCAL_PROGRAM_FILES);
+
+    private final static Set<Path> KNOWN_DIRS = Stream.of(Set.of(INSTALLDIR),
+            SYSTEM_DIRS).flatMap(Set::stream).collect(
+            Collectors.toUnmodifiableSet());
+
+    private final static Set<Path> USER_PROFILE_DIRS = Set.of(LOCAL_PROGRAM_FILES,
+            PROGRAM_MENU_PATH, DESKTOP_PATH);
+
+    private 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)
+        );
+
+    private 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)
+        );
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WixTool.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,165 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.file.*;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * WiX tool.
+ */
+public enum WixTool {
+    Candle, Light;
+
+    static final class ToolInfo {
+        ToolInfo(Path path, String version) {
+            this.path = path;
+            this.version = new DottedVersion(version);
+        }
+
+        final Path path;
+        final DottedVersion version;
+    }
+
+    static Map<WixTool, ToolInfo> toolset() throws ConfigException {
+        Map<WixTool, ToolInfo> toolset = new HashMap<>();
+        for (var tool : values()) {
+            toolset.put(tool, tool.find());
+        }
+        return toolset;
+    }
+
+    ToolInfo find() throws ConfigException {
+        final Path toolFileName = IOUtils.addSuffix(
+                Path.of(name().toLowerCase()), ".exe");
+
+        String[] version = new String[1];
+        ConfigException reason = createToolValidator(toolFileName, version).get();
+        if (version[0] != null) {
+            if (reason == null) {
+                // Found in PATH.
+                return new ToolInfo(toolFileName, version[0]);
+            }
+
+            // Found in PATH, but something went wrong.
+            throw reason;
+        }
+
+        for (var dir : findWixInstallDirs()) {
+            Path path = dir.resolve(toolFileName);
+            if (path.toFile().exists()) {
+                reason = createToolValidator(path, version).get();
+                if (reason != null) {
+                    throw reason;
+                }
+                return new ToolInfo(path, version[0]);
+            }
+        }
+
+        throw reason;
+    }
+
+    private static Supplier<ConfigException> createToolValidator(Path toolPath,
+            String[] versionCtnr) {
+        return new ToolValidator(toolPath)
+                .setCommandLine("/?")
+                .setMinimalVersion(MINIMAL_VERSION)
+                .setToolNotFoundErrorHandler(
+                        (name, ex) -> new ConfigException(
+                                I18N.getString("error.no-wix-tools"),
+                                I18N.getString("error.no-wix-tools.advice")))
+                .setToolOldVersionErrorHandler(
+                        (name, version) -> new ConfigException(
+                                MessageFormat.format(I18N.getString(
+                                        "message.wrong-tool-version"), name,
+                                        version, MINIMAL_VERSION),
+                                I18N.getString("error.no-wix-tools.advice")))
+                .setVersionParser(output -> {
+                    versionCtnr[0] = "";
+                    String firstLineOfOutput = output.findFirst().orElse("");
+                    int separatorIdx = firstLineOfOutput.lastIndexOf(' ');
+                    if (separatorIdx == -1) {
+                        return null;
+                    }
+                    versionCtnr[0] = firstLineOfOutput.substring(separatorIdx + 1);
+                    return versionCtnr[0];
+                })::validate;
+    }
+
+    private final static DottedVersion MINIMAL_VERSION = DottedVersion.lazy("3.0");
+
+    static Path getSystemDir(String envVar, String knownDir) {
+        return Optional
+                .ofNullable(getEnvVariableAsPath(envVar))
+                .orElseGet(() -> Optional
+                        .ofNullable(getEnvVariableAsPath("SystemDrive"))
+                        .orElseGet(() -> Path.of("C:")).resolve(knownDir));
+    }
+
+    private static Path getEnvVariableAsPath(String envVar) {
+        String path = System.getenv(envVar);
+        if (path != null) {
+            try {
+                return Path.of(path);
+            } catch (InvalidPathException ex) {
+                Log.error(MessageFormat.format(I18N.getString(
+                        "error.invalid-envvar"), envVar));
+            }
+        }
+        return null;
+    }
+
+    private static List<Path> findWixInstallDirs() {
+        PathMatcher wixInstallDirMatcher = FileSystems.getDefault().getPathMatcher(
+                "glob:WiX Toolset v*");
+
+        Path programFiles = getSystemDir("ProgramFiles", "\\Program Files");
+        Path programFilesX86 = getSystemDir("ProgramFiles(x86)",
+                "\\Program Files (x86)");
+
+        // Returns list of WiX install directories ordered by WiX version number.
+        // Newer versions go first.
+        return Stream.of(programFiles, programFilesX86).map(path -> {
+            List<Path> result;
+            try (var paths = Files.walk(path, 1)) {
+                result = paths.collect(Collectors.toList());
+            } catch (IOException ex) {
+                Log.verbose(ex);
+                result = Collections.emptyList();
+            }
+            return result;
+        }).flatMap(List::stream)
+        .filter(path -> wixInstallDirMatcher.matches(path.getFileName()))
+        .sorted(Comparator.comparing(Path::getFileName).reversed())
+        .map(path -> path.resolve("bin"))
+        .collect(Collectors.toList());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/MsiInstallerStrings_en.wxl	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,7 @@
+<?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 [INSTALLDIR] already exist. Would you like to install to that folder anyway?</String>
+  <String Id="MainFeatureTitle">Main Feature</String>
+  <String Id="DowngradeErrorMessage">A higher version of [ProductName] is already installed. Downgrades disabled. Setup will now exit.</String>
+  <String Id="DisallowUpgradeErrorMessage">A lower version of [ProductName] is already installed. Upgrades disabled. Setup will now exit.</String>
+</WixLocalization>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/MsiInstallerStrings_ja.wxl	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization" Codepage="932">
+  <String Id="message.install.dir.exist">The folder [INSTALLDIR] already exist. Would you like to install to that folder anyway?</String>
+  <String Id="MainFeatureTitle">Main Feature</String>
+  <String Id="DowngradeErrorMessage">A higher version of [ProductName] is already installed. Downgrades disabled. Setup will now exit.</String>
+  <String Id="DisallowUpgradeErrorMessage">A lower version of [ProductName] is already installed. Upgrades disabled. Setup will now exit.</String>
+</WixLocalization>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/MsiInstallerStrings_zh_CN.wxl	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization" Codepage="936">
+  <String Id="message.install.dir.exist">The folder [INSTALLDIR] already exist. Would you like to install to that folder anyway?</String>
+  <String Id="MainFeatureTitle">Main Feature</String>
+  <String Id="DowngradeErrorMessage">A higher version of [ProductName] is already installed. Downgrades disabled. Setup will now exit.</String>
+  <String Id="DisallowUpgradeErrorMessage">A lower version of [ProductName] is already installed. Upgrades disabled. Setup will now exit.</String>
+</WixLocalization>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinLauncher.template	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinResources.properties	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,67 @@
+#
+# 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-app-image-script=script to run after application image is populated
+resource.post-msi-script=script to run after msi file for exe installer is created
+resource.wxl-file-name=MsiInstallerStrings_en.wxl
+resource.main-wix-file=Main WiX project file
+resource.overrides-wix-file=Overrides WiX project file
+
+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 https://wixtoolset.org 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: https://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.version-swap=Failed to update version information for {0}
+error.invalid-envvar=Invalid value of {0} environment variable
+
+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.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.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.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.use-wix36-features=WiX {0} detected. Enabling advanced cleanup action.
+message.product-code=MSI ProductCode: {0}.
+message.upgrade-code=MSI UpgradeCode: {0}.
+message.preparing-msi-config=Preparing MSI config: {0}.
+message.generating-msi=Generating MSI: {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.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinResources_ja.properties	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,67 @@
+#
+# 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-app-image-script=script to run after application image is populated
+resource.post-msi-script=script to run after msi file for exe installer is created
+resource.wxl-file-name=MsiInstallerStrings_en.wxl
+resource.main-wix-file=Main WiX project file
+resource.overrides-wix-file=Overrides WiX project file
+
+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 https://wixtoolset.org 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: https://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.version-swap=Failed to update version information for {0}
+error.invalid-envvar=Invalid value of {0} environment variable
+
+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.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.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.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.use-wix36-features=WiX {0} detected. Enabling advanced cleanup action.
+message.product-code=MSI ProductCode: {0}.
+message.upgrade-code=MSI UpgradeCode: {0}.
+message.preparing-msi-config=Preparing MSI config: {0}.
+message.generating-msi=Generating MSI: {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.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinResources_zh_CN.properties	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,67 @@
+#
+# 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-app-image-script=script to run after application image is populated
+resource.post-msi-script=script to run after msi file for exe installer is created
+resource.wxl-file-name=MsiInstallerStrings_en.wxl
+resource.main-wix-file=Main WiX project file
+resource.overrides-wix-file=Overrides WiX project file
+
+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 https://wixtoolset.org 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: https://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.version-swap=Failed to update version information for {0}
+error.invalid-envvar=Invalid value of {0} environment variable
+
+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.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.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.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.use-wix36-features=WiX {0} detected. Enabling advanced cleanup action.
+message.product-code=MSI ProductCode: {0}.
+message.upgrade-code=MSI UpgradeCode: {0}.
+message.preparing-msi-config=Preparing MSI config: {0}.
+message.generating-msi=Generating MSI: {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.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/java48.ico has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/main.wxs	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,137 @@
+<?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"?>
+  <?else?>
+    <?define JpInstallScope="perUser"?>
+  <?endif?>
+
+  <?define JpProductLanguage=1033 ?>
+  <?define JpInstallerVersion=200 ?>
+  <?define JpCompressedMsi=yes ?>
+
+  <?include $(var.JpConfigDir)/overrides.wxi ?>
+
+  <Product
+    Id="$(var.JpProductCode)"
+    Name="$(var.JpAppName)"
+    Language="$(var.JpProductLanguage)"
+    Version="$(var.JpAppVersion)"
+    Manufacturer="$(var.JpAppVendor)"
+    UpgradeCode="$(var.JpProductUpgradeCode)">
+
+    <Package
+      Description="$(var.JpAppDescription)"
+      Manufacturer="$(var.JpAppVendor)"
+      InstallerVersion="$(var.JpInstallerVersion)"
+      Compressed="$(var.JpCompressedMsi)"
+      InstallScope="$(var.JpInstallScope)" Platform="x64"
+    />
+
+    <Media Id="1" Cabinet="Data.cab" EmbedCab="yes" />
+
+    <?ifdef JpAllowDowngrades ?>
+    <?ifdef JpAllowUpgrades ?>
+    <MajorUpgrade
+      AllowDowngrades="yes"
+      Disallow="no"
+    />
+    <?endif?>
+    <?endif?>
+
+    <?ifdef JpAllowDowngrades ?>
+    <?ifndef JpAllowUpgrades ?>
+    <MajorUpgrade
+      AllowDowngrades="yes"
+      Disallow="yes"
+      DisallowUpgradeErrorMessage="!(loc.DisallowUpgradeErrorMessage)"
+    />
+    <?endif?>
+    <?endif?>
+
+    <?ifndef JpAllowDowngrades ?>
+    <?ifdef JpAllowUpgrades ?>
+    <MajorUpgrade
+      AllowDowngrades="no"
+      Disallow="no"
+      DowngradeErrorMessage="!(loc.DowngradeErrorMessage)"
+    />
+    <?endif?>
+    <?endif?>
+
+    <?ifndef JpAllowDowngrades ?>
+    <?ifndef JpAllowUpgrades ?>
+    <MajorUpgrade
+      AllowDowngrades="no"
+      Disallow="yes"
+      DowngradeErrorMessage="!(loc.DowngradeErrorMessage)"
+      DisallowUpgradeErrorMessage="!(loc.DisallowUpgradeErrorMessage)"
+    />
+    <?endif?>
+    <?endif?>
+
+    <!-- Standard required root -->
+    <Directory Id="TARGETDIR" Name="SourceDir"/>
+
+    <Feature Id="DefaultFeature" Title="!(loc.MainFeatureTitle)" Level="1">
+      <ComponentGroupRef Id="Shortcuts"/>
+      <ComponentGroupRef Id="Files"/>
+      <ComponentGroupRef Id="FileAssociations"/>
+    </Feature>
+
+    <?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="INSTALLDIR"/>
+      <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?>
+
+  </Product>
+</Wix>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/overrides.wxi	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Stub by design -->
+<Include/>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/classes/module-info.java.extra	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage.internal.Bundler with
+    jdk.incubator.jpackage.internal.WinAppBundler,
+    jdk.incubator.jpackage.internal.WinExeBundler,
+    jdk.incubator.jpackage.internal.WinMsiBundler;
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/jpackageapplauncher/WinLauncher.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libapplauncher/DllMain.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,35 @@
+/*
+ * 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;
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libapplauncher/FileAttribute.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libapplauncher/FilePath.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libapplauncher/PlatformDefs.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libapplauncher/WindowsPlatform.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libapplauncher/WindowsPlatform.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/ByteBuffer.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/ByteBuffer.h	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,57 @@
+/*
+ * 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
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/ErrorHandling.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/ErrorHandling.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/FileUtils.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/FileUtils.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/IconSwap.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/IconSwap.h	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,36 @@
+/*
+ * 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
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/Log.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/Log.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/ResourceEditor.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/ResourceEditor.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/SourceCodePos.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/SysInfo.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/UniqueHandle.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/Utils.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/Utils.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/VersionInfoSwap.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/VersionInfoSwap.h	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,53 @@
+/*
+ * 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
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.jpackage/windows/native/libjpackage/WinErrorHandling.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/WinErrorHandling.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/WinSysInfo.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/WinSysInfo.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/WindowsRegistry.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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_incubator_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE
+#define jdk_incubator_jpackage_internal_WindowsRegistry_HKEY_LOCAL_MACHINE 1L
+
+    /*
+     * Class:     jdk_incubator_jpackage_internal_WindowsRegistry
+     * Method:    readDwordValue
+     * Signature: (ILjava/lang/String;Ljava/lang/String;I)I
+     */
+    JNIEXPORT jint JNICALL
+            Java_jdk_incubator_jpackage_internal_WindowsRegistry_readDwordValue(
+            JNIEnv *pEnv, jclass c, jint key, jstring jSubKey,
+            jstring jValue, jint defaultValue) {
+        jint jResult = defaultValue;
+
+        if (key != jdk_incubator_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_incubator_jpackage_internal_WindowsRegistry
+     * Method:    openRegistryKey
+     * Signature: (ILjava/lang/String;)J
+     */
+    JNIEXPORT jlong JNICALL
+            Java_jdk_incubator_jpackage_internal_WindowsRegistry_openRegistryKey(
+            JNIEnv *pEnv, jclass c, jint key, jstring jSubKey) {
+        if (key != jdk_incubator_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_incubator_jpackage_internal_WindowsRegistry
+     * Method:    enumRegistryValue
+     * Signature: (JI)Ljava/lang/String;
+     */
+    JNIEXPORT jstring JNICALL
+            Java_jdk_incubator_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_incubator_jpackage_internal_WindowsRegistry
+     * Method:    closeRegistryKey
+     * Signature: (J)V
+     */
+    JNIEXPORT void JNICALL
+            Java_jdk_incubator_jpackage_internal_WindowsRegistry_closeRegistryKey(
+            JNIEnv *pEnc, jclass c, jlong lKey) {
+        HKEY hKey = (HKEY)lKey;
+        RegCloseKey(hKey);
+    }
+
+    /*
+     * Class:     jdk_incubator_jpackage_internal_WindowsRegistry
+     * Method:    comparePaths
+     * Signature: (Ljava/lang/String;Ljava/lang/String;)Z
+     */
+     JNIEXPORT jboolean JNICALL
+            Java_jdk_incubator_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.incubator.jpackage/windows/native/libjpackage/jpackage.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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_incubator_jpackage_internal_WindowsAppImageBuilder
+     * Method:    iconSwap
+     * Signature: (Ljava/lang/String;Ljava/lang/String;)I
+     */
+    JNIEXPORT jint JNICALL
+            Java_jdk_incubator_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_incubator_jpackage_internal_WindowsAppImageBuilder
+     * Method:    versionSwap
+     * Signature: (Ljava/lang/String;Ljava/lang/String;)I
+     */
+    JNIEXPORT jint JNICALL
+            Java_jdk_incubator_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_incubator_jpackage_internal_WinExeBundler
+     * Method:    embedMSI
+     * Signature: (Ljava/lang/String;Ljava/lang/String;)I
+     */
+    JNIEXPORT jint JNICALL Java_jdk_incubator_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.incubator.jpackage/windows/native/libjpackage/tstrings.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libjpackage/tstrings.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/libwixhelper/libwixhelper.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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("INSTALLDIR"),
+                TEXT(""), &cchSize);
+        if (result == ERROR_MORE_DATA) {
+            cchSize = cchSize + 1; // NULL termination
+            szValue = new TCHAR[cchSize];
+            if (szValue) {
+                result = MsiGetProperty(hInstall, TEXT("INSTALLDIR"),
+                        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.incubator.jpackage/windows/native/msiwrapper/Executor.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/msiwrapper/Executor.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/msiwrapper/MsiWrapper.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/msiwrapper/Resources.cpp	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage/windows/native/msiwrapper/Resources.h	Tue Nov 12 10:18:11 2019 -0500
@@ -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
--- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/packager/AppRuntimeImageBuilder.java	Fri Nov 08 11:15:16 2019 +0000
+++ /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/test/jdk/tools/jpackage/TEST.properties	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,3 @@
+keys=jpackagePlatformPackage
+requires.properties=jpackage.test.SQETest
+maxOutputSize=2000000
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/apps/com.hello/com/hello/Hello.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,177 @@
+/*
+ * 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.awt.AWTError;
+import java.awt.Desktop;
+import java.awt.GraphicsEnvironment;
+import java.awt.desktop.OpenFilesEvent;
+import java.awt.desktop.OpenFilesHandler;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.Path;
+import java.nio.file.Files;
+import java.util.stream.Collectors;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.stream.Stream;
+import java.util.Collections;
+
+public class Hello implements OpenFilesHandler {
+
+    public static void main(String[] args) throws IOException, InterruptedException {
+        var faFiles = getFaFiles();
+        if (faFiles != null) {
+            // Some files got opened through fa mechanizm.
+            // They are the arguments then.
+            args = faFiles.toArray(String[]::new);
+        }
+
+        var lines = printArgs(args);
+
+        lines.forEach(System.out::println);
+
+        var outputFile = getOutputFile(args);
+        trace(String.format("Output file: [%s]", outputFile));
+        Files.write(outputFile, lines);
+    }
+
+    private static List<String> printArgs(String[] args) {
+        List<String> lines = new ArrayList<>();
+        lines.add(MSG);
+
+        lines.add("args.length: " + args.length);
+
+        lines.addAll(List.of(args));
+
+        for (int index = 1; index <= EXPECTED_NUM_OF_PARAMS; index++) {
+            String value = System.getProperty("param" + index);
+            if (value != null) {
+                lines.add("-Dparam" + index + "=" + value);
+            }
+        }
+
+        return lines;
+    }
+
+    private static Path getOutputFile(String[] args) {
+        Path outputFilePath = Path.of("appOutput.txt");
+
+        // If first arg is a file (most likely from fa), then put output in the same folder as
+        // the file from fa.
+        if (args.length >= 1) {
+            Path faPath = Path.of(args[0]);
+            if (Files.exists(faPath)) {
+                return faPath.toAbsolutePath().getParent().resolve(outputFilePath);
+            }
+        }
+
+        try {
+            // Try writing in the default output file.
+            Files.write(outputFilePath, Collections.emptyList());
+            return outputFilePath;
+        } catch (IOException ex) {
+            // Log reason of a failure.
+            StringWriter errors = new StringWriter();
+            ex.printStackTrace(new PrintWriter(errors));
+            Stream.of(errors.toString().split("\\R")).forEachOrdered(Hello::trace);
+        }
+
+        return Path.of(System.getProperty("user.home")).resolve(outputFilePath);
+    }
+
+    @Override
+    public void openFiles(OpenFilesEvent e) {
+        synchronized(lock) {
+            trace("openFiles");
+            files = e.getFiles().stream()
+                .map(File::toString)
+                .collect(Collectors.toList());
+
+            lock.notifyAll();
+        }
+    }
+
+    private static List<String> getFaFiles() throws InterruptedException {
+        if (openFilesHandler == null) {
+            return null;
+        }
+
+        synchronized(openFilesHandler.lock) {
+            trace("getFaFiles: wait");
+            openFilesHandler.lock.wait(1000);
+            if (openFilesHandler.files == null) {
+                trace(String.format("getFaFiles: no files"));
+                return null;
+            }
+            // Return copy of `files` to keep access to `files` field synchronized.
+            trace(String.format("getFaFiles: file count %d",
+                    openFilesHandler.files.size()));
+            return new ArrayList<>(openFilesHandler.files);
+        }
+    }
+
+    private List<String> files;
+    private final Object lock = new Object();
+    private final static Hello openFilesHandler = createInstance();
+
+    private static Hello createInstance() {
+        if (GraphicsEnvironment.isHeadless()) {
+            return null;
+        }
+
+        trace("Environment supports a display");
+
+        try {
+            // Disable JAB.
+            // Needed to suppress error:
+            // Exception in thread "main" java.awt.AWTError: Assistive Technology not found: com.sun.java.accessibility.AccessBridge
+            System.setProperty("javax.accessibility.assistive_technologies", "");
+        } catch (SecurityException ex) {
+            ex.printStackTrace();
+        }
+
+        try {
+            var desktop = Desktop.getDesktop();
+            if (desktop.isSupported(Desktop.Action.APP_OPEN_FILE)) {
+                trace("Set file handler");
+                Hello instance = new Hello();
+                desktop.setOpenFileHandler(instance);
+                return instance;
+            }
+        } catch (AWTError ex) {
+            trace("Set file handler failed");
+            ex.printStackTrace();
+        }
+
+        return null;
+    }
+
+    private static final String MSG = "jpackage test application";
+    private static final int EXPECTED_NUM_OF_PARAMS = 3; // Starts at 1
+
+    private static void trace(String msg) {
+        System.out.println("hello: " + msg);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/apps/installer/Hello.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,684 @@
+/*
+ * 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.stream.Collectors;
+import java.util.stream.Stream;
+
+import java.util.spi.ToolProvider;
+import jdk.incubator.jpackage.ToolProviderFactory;
+
+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 =
+            ToolProviderFactory.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;
+        }
+
+        return Stream.of(output.split("\\R"))
+                .filter(str -> !str.startsWith("Picked up"))
+                .filter(str -> !str.startsWith("WARNING: Using incubator"))
+                .filter(str -> !str.startsWith("hello: "))
+                .collect(Collectors.toList()).toArray(String[]::new);
+    }
+
+    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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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", "app", 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 Nov 12 10:18:11 2019 -0500
@@ -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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please 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 BeforeEach {
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.METHOD)
+    public @interface AfterEach {
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.METHOD)
+    public @interface Test {
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.METHOD)
+    @Repeatable(ParameterGroup.class)
+    public @interface Parameter {
+
+        String[] value();
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.METHOD)
+    public @interface ParameterGroup {
+
+        Parameter[] value();
+    }
+
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.METHOD)
+    public @interface Parameters {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CfgFile.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,96 @@
+/*
+ * 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.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public final class CfgFile {
+    public String getValue(String section, String key) {
+        Objects.requireNonNull(section);
+        Objects.requireNonNull(key);
+
+        Map<String, String> entries = data.get(section);
+        TKit.assertTrue(entries != null, String.format(
+                "Check section [%s] is found in [%s] cfg file", section, id));
+
+        String value = entries.get(key);
+        TKit.assertNotNull(value, String.format(
+                "Check key [%s] is found in [%s] section of [%s] cfg file", key,
+                section, id));
+
+        return value;
+    }
+
+    private CfgFile(Map<String, Map<String, String>> data, String id) {
+        this.data = data;
+        this.id = id;
+    }
+
+    public static CfgFile readFromFile(Path path) throws IOException {
+        TKit.trace(String.format("Read [%s] jpackage cfg file", path));
+
+        final Pattern sectionBeginRegex = Pattern.compile( "\\s*\\[([^]]*)\\]\\s*");
+        final Pattern keyRegex = Pattern.compile( "\\s*([^=]*)=(.*)" );
+
+        Map<String, Map<String, String>> result = new HashMap<>();
+
+        String currentSectionName = null;
+        Map<String, String> currentSection = new HashMap<>();
+        for (String line : Files.readAllLines(path)) {
+            Matcher matcher = sectionBeginRegex.matcher(line);
+            if (matcher.find()) {
+                if (currentSectionName != null) {
+                    result.put(currentSectionName, Collections.unmodifiableMap(
+                            new HashMap<>(currentSection)));
+                }
+                currentSectionName = matcher.group(1);
+                currentSection.clear();
+                continue;
+            }
+
+            matcher = keyRegex.matcher(line);
+            if (matcher.find()) {
+                currentSection.put(matcher.group(1), matcher.group(2));
+                continue;
+            }
+        }
+
+        if (!currentSection.isEmpty()) {
+            result.put("", Collections.unmodifiableMap(currentSection));
+        }
+
+        return new CfgFile(Collections.unmodifiableMap(result), path.toString());
+    }
+
+    private final Map<String, Map<String, String>> data;
+    private final String id;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CommandArguments.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,364 @@
+/*
+ * 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.*;
+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 = Objects.requireNonNull(v);
+        toolProvider = null;
+        return this;
+    }
+
+    public Executor setToolProvider(ToolProvider v) {
+        toolProvider = Objects.requireNonNull(v);
+        executable = null;
+        return this;
+    }
+
+    public Executor setToolProvider(JavaTool v) {
+        return setToolProvider(v.asToolProvider());
+    }
+
+    public Executor setDirectory(Path v) {
+        directory = v;
+        return this;
+    }
+
+    public Executor setExecutable(JavaTool v) {
+        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() {
+        if (toolProvider != null && directory != null) {
+            throw new IllegalArgumentException(
+                    "Can't change directory when using tool provider");
+        }
+
+        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 Path executablePath() {
+        if (directory == null || executable.isAbsolute()) {
+            return executable;
+        }
+
+        // If relative path to executable is used it seems to be broken when
+        // ProcessBuilder changes the directory. On Windows it changes the
+        // directory first and on Linux it looks up for executable before
+        // changing the directory. So to stay of safe side, use absolute path
+        // to executable.
+        return executable.toAbsolutePath();
+    }
+
+    private Result runExecutable() throws IOException, InterruptedException {
+        List<String> command = new ArrayList<>();
+        command.add(executablePath().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 = bufReader.lines().findFirst().orElse(null);
+                    if (firstLine != null) {
+                        reply.output = List.of(firstLine);
+                    }
+                } else if (saveOutputType.contains(SaveOutputType.FULL)) {
+                    reply.output = 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 = executablePath().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 ToolProvider toolProvider;
+    private Path executable;
+    private Set<SaveOutputType> saveOutputType;
+    private Path directory;
+
+    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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,94 @@
+/*
+ * 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.HashMap;
+import java.util.Map;
+
+
+final public class FileAssociations {
+    public FileAssociations(String faSuffixName) {
+        suffixName = faSuffixName;
+        setFilename("fa");
+        setDescription("jpackage test extention");
+    }
+
+    private void createFile() {
+        Map<String, String> entries = new HashMap<>(Map.of(
+            "extension", suffixName,
+            "mime-type", getMime(),
+            "description", description
+        ));
+        if (icon != null) {
+            if (TKit.isWindows()) {
+                entries.put("icon", icon.toString().replace("\\", "/"));
+            } else {
+                entries.put("icon", icon.toString());
+            }
+        }
+        TKit.createPropertiesFile(file, entries);
+    }
+
+    public FileAssociations setFilename(String v) {
+        file = TKit.workDir().resolve(v + ".properties");
+        return this;
+    }
+
+    public FileAssociations setDescription(String v) {
+        description = v;
+        return this;
+    }
+
+    public FileAssociations setIcon(Path v) {
+        icon = v;
+        return this;
+    }
+
+    Path getPropertiesFile() {
+        return file;
+    }
+
+    String getSuffix() {
+        return "." + suffixName;
+    }
+
+    String getMime() {
+        return "application/x-jpackage-" + suffixName;
+    }
+
+    public void applyTo(PackageTest test) {
+        test.notForTypes(PackageType.MAC_DMG, () -> {
+            test.addInitializer(cmd -> {
+                createFile();
+                cmd.addArguments("--file-associations", getPropertiesFile());
+            });
+            test.addHelloAppFileAssociationsVerifier(this);
+        });
+    }
+
+    private Path file;
+    final private String suffixName;
+    private String description;
+    private Path icon;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Functional.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,239 @@
+/*
+ * 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 {
+
+    HelloApp(JavaAppDesc appDesc) {
+        if (appDesc == null) {
+            this.appDesc = createDefaltAppDesc();
+        } else {
+            this.appDesc = appDesc;
+        }
+    }
+
+    private JarBuilder prepareSources(Path srcDir) throws IOException {
+        final String qualifiedClassName = appDesc.className();
+
+        final String className = qualifiedClassName.substring(
+                qualifiedClassName.lastIndexOf('.') + 1);
+        final String packageName = appDesc.packageName();
+
+        final Path srcFile = srcDir.resolve(Path.of(String.join(
+                File.separator, qualifiedClassName.split("\\.")) + ".java"));
+        Files.createDirectories(srcFile.getParent());
+
+        JarBuilder jarBuilder = createJarBuilder().addSourceFile(srcFile);
+        final String moduleName = appDesc.moduleName();
+        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),
+                    "    requires java.desktop;",
+                    "}"
+            ));
+            jarBuilder.addSourceFile(moduleInfoFile);
+            jarBuilder.setModuleVersion(appDesc.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 classNameRegex = Pattern.compile("\\bHello\\b");
+        Pattern classDeclaration = Pattern.compile(
+                "(^.*\\bclass\\s+)\\bHello\\b(.*$)");
+        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 -> {
+            Matcher m;
+            if (classDeclared.getPlain()) {
+                if ((m = classNameRegex.matcher(line)).find()) {
+                    line = m.replaceAll(className);
+                }
+                return line;
+            }
+
+            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() {
+        JarBuilder builder = new JarBuilder();
+        if (appDesc.jarWithMainClass()) {
+            builder.setMainClass(appDesc.className());
+        }
+        return builder;
+    }
+
+    void addTo(JPackageCommand cmd) {
+        final String moduleName = appDesc.moduleName();
+        final String jarFileName = appDesc.jarFileName();
+        final String qualifiedClassName = appDesc.className();
+
+        if (moduleName != null && appDesc.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) -> {
+                Path jarFile = self.inputDir().resolve(jarFileName);
+                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).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");
+        }
+    }
+
+    static JavaAppDesc createDefaltAppDesc() {
+        return new JavaAppDesc().setClassName(CLASS_NAME).setJarFileName(
+                "hello.jar");
+    }
+
+    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 = cmd.appLauncherPath();
+        if (!cmd.isFakeRuntime(String.format("Not running [%s] launcher",
+                launcherPath))) {
+            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 = TKit.workDir().resolve(OUTPUT_FILENAME);
+        ThrowingFunction.toFunction(Files::deleteIfExists).apply(outputFile);
+        new Executor()
+                .setDirectory(outputFile.getParent())
+                .setExecutable(helloAppLauncher)
+                .dumpOutput()
+                .execute()
+                .assertExitCodeIsZero();
+
+        verifyOutputFile(outputFile, defaultLauncherArgs);
+    }
+
+    final static String OUTPUT_FILENAME = "appOutput.txt";
+
+    private final JavaAppDesc appDesc;
+
+    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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,732 @@
+/*
+ * 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.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.incubator.jpackage.internal.ApplicationLayout;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Functional.ThrowingFunction;
+
+/**
+ * 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<>();
+    }
+
+    public JPackageCommand(JPackageCommand cmd) {
+        this();
+        args.addAll(cmd.args);
+        withToolProvider = cmd.withToolProvider;
+        saveConsoleOutput = cmd.saveConsoleOutput;
+        suppressOutput = cmd.suppressOutput;
+        ignoreDefaultRuntime = cmd.ignoreDefaultRuntime;
+        immutable = cmd.immutable;
+        actionsExecuted = cmd.actionsExecuted;
+    }
+
+    JPackageCommand createImmutableCopy() {
+        JPackageCommand reply = new JPackageCommand(this);
+        reply.immutable = true;
+        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 boolean isImagePackageType() {
+        return PackageType.IMAGE == getArgumentValue("--type",
+                () -> null, PACKAGE_TYPES::get);
+    }
+
+    public PackageType packageType() {
+        // Don't try to be in sync with jpackage defaults. Keep it simple:
+        // if no `--type` explicitely set on the command line, consider
+        // this is operator's fault.
+        return getArgumentValue("--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();
+
+        ThrowingConsumer<Path> createBulkFile = path -> {
+            Files.createDirectories(path.getParent());
+            try (FileOutputStream out = new FileOutputStream(path.toFile())) {
+                byte[] bytes = new byte[4 * 1024];
+                new SecureRandom().nextBytes(bytes);
+                out.write(bytes);
+            }
+        };
+
+        addAction(cmd -> {
+            Path fakeRuntimeDir = TKit.workDir().resolve("fake_runtime");
+
+            TKit.trace(String.format("Init fake runtime in [%s] directory",
+                    fakeRuntimeDir));
+
+            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();
+            }
+
+            if (TKit.isOSX()) {
+                // Make MacAppImageBuilder happy
+                createBulkFile.accept(fakeRuntimeDir.resolve(Path.of(
+                        "Contents/Home/lib/jli/libjli.dylib")));
+            }
+
+            // Mak sure fake runtime takes some disk space.
+            // Package bundles with 0KB size are unexpected and considered
+            // an error by PackageTest.
+            createBulkFile.accept(fakeRuntimeDir.resolve(Path.of("bin", "bulk")));
+
+            cmd.addArguments("--runtime-image", fakeRuntimeDir);
+        });
+
+        return this;
+    }
+
+    JPackageCommand addAction(ThrowingConsumer<JPackageCommand> action) {
+        verifyMutable();
+        actions.add(ThrowingConsumer.toConsumer(action));
+        return this;
+    }
+
+    /**
+     * Shorthand for {@code helloAppImage(null)}.
+     */
+    public static JPackageCommand helloAppImage() {
+        JavaAppDesc javaAppDesc = null;
+        return helloAppImage(javaAppDesc);
+    }
+
+    /**
+     * Creates new JPackageCommand instance configured with the test Java app.
+     * For the explanation of `javaAppDesc` parameter, see documentation for
+     * #JavaAppDesc.parse() method.
+     *
+     * @param javaAppDesc Java application description
+     * @return this
+     */
+    public static JPackageCommand helloAppImage(String javaAppDesc) {
+        final JavaAppDesc appDesc;
+        if (javaAppDesc == null) {
+            appDesc = null;
+        } else {
+            appDesc = JavaAppDesc.parse(javaAppDesc);
+        }
+        return helloAppImage(appDesc);
+    }
+
+    public static JPackageCommand helloAppImage(JavaAppDesc javaAppDesc) {
+        JPackageCommand cmd = new JPackageCommand();
+        cmd.setDefaultInputOutput().setDefaultAppName();
+        PackageType.IMAGE.applyTo(cmd);
+        new HelloApp(javaAppDesc).addTo(cmd);
+        return cmd;
+    }
+
+    public JPackageCommand setPackageType(PackageType type) {
+        verifyMutable();
+        type.applyTo(this);
+        return this;
+    }
+
+    JPackageCommand setDefaultAppName() {
+        return addArguments("--name", TKit.getCurrentDefaultAppName());
+    }
+
+    /**
+     * Returns path to output bundle of configured jpackage command.
+     *
+     * If this is build image command, returns path to application image directory.
+     */
+    public Path outputBundle() {
+        final String bundleName;
+        if (isImagePackageType()) {
+            String dirName = name();
+            if (TKit.isOSX()) {
+                dirName = dirName + ".app";
+            }
+            bundleName = dirName;
+        } else if (TKit.isLinux()) {
+            bundleName = LinuxHelper.getBundleName(this);
+        } else if (TKit.isWindows()) {
+            bundleName = WindowsHelper.getBundleName(this);
+        } else if (TKit.isOSX()) {
+            bundleName = MacHelper.getBundleName(this);
+        } else {
+            throw TKit.throwUnknownPlatformError();
+        }
+
+        return outputDir().resolve(bundleName);
+    }
+
+    /**
+     * Returns application layout.
+     *
+     * If this is build image command, returns application image layout of the
+     * output bundle relative to output directory. Otherwise returns layout of
+     * installed application relative to the root directory.
+     *
+     * If this command builds Java runtime, not an application, returns
+     * corresponding layout.
+     */
+    public ApplicationLayout appLayout() {
+        final ApplicationLayout layout;
+        if (isRuntime()) {
+            layout = ApplicationLayout.javaRuntime();
+        } else {
+            layout = ApplicationLayout.platformAppImage();
+        }
+
+        if (isImagePackageType()) {
+            return layout.resolveAt(outputBundle());
+        }
+
+        return layout.resolveAt(appInstallationDirectory());
+    }
+
+    /**
+     * Returns path to directory where application will be installed or null if
+     * this is build image command.
+     *
+     * E.g. on Linux for app named Foo default the function will return
+     * `/opt/foo`
+     */
+    public Path appInstallationDirectory() {
+        if (isImagePackageType()) {
+            return null;
+        }
+
+        if (TKit.isLinux()) {
+            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 appLauncherPath().getParent().getParent();
+        }
+
+        if (TKit.isWindows()) {
+            return WindowsHelper.getInstallationDirectory(this);
+        }
+
+        if (TKit.isOSX()) {
+            return MacHelper.getInstallationDirectory(this);
+        }
+
+        throw TKit.throwUnknownPlatformError();
+    }
+
+    /**
+     * Returns path to application's Java runtime.
+     * If the command will package Java runtime only, returns correct path to
+     * runtime directory.
+     *
+     * E.g.:
+     * [jpackage --name Foo --type rpm] -> `/opt/foo/lib/runtime`
+     * [jpackage --name Foo --type app-image --dest bar] -> `bar/Foo/lib/runtime`
+     * [jpackage --name Foo --type rpm --runtime-image java] -> `/opt/foo`
+     */
+    public Path appRuntimeDirectory() {
+        return appLayout().runtimeDirectory();
+    }
+
+    /**
+     * Returns path for application launcher with the given name.
+     *
+     * E.g.: [jpackage --name Foo --type rpm] -> `/opt/foo/bin/Foo`
+     * [jpackage --name Foo --type app-image --dest bar] ->
+     * `bar/Foo/bin/Foo`
+     *
+     * @param launcherName name of launcher or {@code null} for the main
+     * launcher
+     *
+     * @throws IllegalArgumentException if the command is configured for
+     * packaging Java runtime
+     */
+    public Path appLauncherPath(String launcherName) {
+        verifyNotRuntime();
+        if (launcherName == null) {
+            launcherName = name();
+        }
+
+        if (TKit.isWindows()) {
+            launcherName = launcherName + ".exe";
+        }
+
+        if (isImagePackageType()) {
+            return appLayout().launchersDirectory().resolve(launcherName);
+        }
+
+        if (TKit.isLinux()) {
+            return LinuxHelper.getLauncherPath(this).getParent().resolve(launcherName);
+        }
+
+        return appLayout().launchersDirectory().resolve(launcherName);
+    }
+
+    /**
+     * Shorthand for {@code appLauncherPath(null)}.
+     */
+    public Path appLauncherPath() {
+        return appLauncherPath(null);
+    }
+
+    private void verifyNotRuntime() {
+        if (isRuntime()) {
+            throw new IllegalArgumentException("Java runtime packaging");
+        }
+    }
+
+    /**
+     * Returns path to .cfg file of the given application launcher.
+     *
+     * E.g.:
+     * [jpackage --name Foo --type rpm] -> `/opt/foo/lib/app/Foo.cfg`
+     * [jpackage --name Foo --type app-image --dest bar] -> `bar/Foo/lib/app/Foo.cfg`
+     *
+     * @param launcher name of launcher or {@code null} for the main launcher
+     *
+     * @throws IllegalArgumentException if the command is configured for
+     * packaging Java runtime
+     */
+    public Path appLauncherCfgPath(String launcherName) {
+        verifyNotRuntime();
+        if (launcherName == null) {
+            launcherName = name();
+        }
+        return appLayout().appDirectory().resolve(launcherName + ".cfg");
+    }
+
+    public boolean isFakeRuntime(String msg) {
+        Path runtimeDir = appRuntimeDirectory();
+
+        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 JPackageCommand ignoreDefaultRuntime(boolean v) {
+        verifyMutable();
+        ignoreDefaultRuntime = v;
+        return this;
+    }
+
+    public boolean isWithToolProvider() {
+        return Optional.ofNullable(withToolProvider).orElse(
+                defaultWithToolProvider);
+    }
+
+    public JPackageCommand executePrerequisiteActions() {
+        verifyMutable();
+        if (!actionsExecuted) {
+            actionsExecuted = true;
+            if (actions != null) {
+                actions.stream().forEach(r -> r.accept(this));
+            }
+        }
+        return this;
+    }
+
+    public Executor createExecutor() {
+        verifyMutable();
+        Executor exec = new Executor()
+                .saveOutput(saveConsoleOutput).dumpOutput(!suppressOutput)
+                .addArguments(args);
+
+        if (isWithToolProvider()) {
+            exec.setToolProvider(JavaTool.JPACKAGE);
+        } else {
+            exec.setExecutable(JavaTool.JPACKAGE);
+        }
+
+        return exec;
+    }
+
+    public Executor.Result execute() {
+        executePrerequisiteActions();
+
+        if (isImagePackageType()) {
+            TKit.deleteDirectoryContentsRecursive(outputDir());
+        }
+
+        return new JPackageCommand(this)
+                .adjustArgumentsBeforeExecution()
+                .createExecutor()
+                .execute();
+    }
+
+    public JPackageCommand executeAndAssertHelloAppImageCreated() {
+        executeAndAssertImageCreated();
+        HelloApp.executeLauncherAndVerifyOutput(this);
+        return this;
+    }
+
+    public JPackageCommand executeAndAssertImageCreated() {
+        execute().assertExitCodeIsZero();
+        return assertImageCreated();
+    }
+
+    public JPackageCommand assertImageCreated() {
+        verifyIsOfType(PackageType.IMAGE);
+        TKit.assertDirectoryExists(appRuntimeDirectory());
+
+        if (!isRuntime()) {
+            TKit.assertExecutableFileExists(appLauncherPath());
+            TKit.assertFileExists(appLauncherCfgPath(null));
+        }
+
+        return this;
+    }
+
+    private JPackageCommand adjustArgumentsBeforeExecution() {
+        if (!hasArgument("--runtime-image") && !hasArgument("--app-image") && DEFAULT_RUNTIME_IMAGE != null && !ignoreDefaultRuntime) {
+            addArguments("--runtime-image", DEFAULT_RUNTIME_IMAGE);
+        }
+
+        if (!hasArgument("--verbose") && TKit.VERBOSE_JPACKAGE) {
+            addArgument("--verbose");
+        }
+
+        return this;
+    }
+
+    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) {
+        final var typesSet = Stream.of(types).collect(Collectors.toSet());
+        if (!hasArgument("--type")) {
+            if (!isImagePackageType()) {
+                if (TKit.isLinux() && typesSet.equals(PackageType.LINUX)) {
+                    return;
+                }
+
+                if (TKit.isWindows() && typesSet.equals(PackageType.WINDOWS)) {
+                    return;
+                }
+
+                if (TKit.isOSX() && typesSet.equals(PackageType.MAC)) {
+                    return;
+                }
+            } else if (typesSet.equals(Set.of(PackageType.IMAGE))) {
+                return;
+            }
+        }
+
+        if (!typesSet.contains(packageType())) {
+            throw new IllegalArgumentException("Unexpected type");
+        }
+    }
+
+    public CfgFile readLaunherCfgFile() {
+        return readLaunherCfgFile(null);
+    }
+
+    public CfgFile readLaunherCfgFile(String launcherName) {
+        verifyIsOfType(PackageType.IMAGE);
+        if (isRuntime()) {
+            return null;
+        }
+        return ThrowingFunction.toFunction(CfgFile::readFromFile).apply(
+                appLauncherCfgPath(launcherName));
+    }
+
+    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 incubator ..." 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 ignoreDefaultRuntime;
+    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 Nov 12 10:18:11 2019 -0500
@@ -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.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(Path 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 -> {
+            if (!sourceFiles.isEmpty()) {
+                new Executor()
+                        .setToolProvider(JavaTool.JAVAC)
+                        .addArguments("-d", workDir.toString())
+                        .addPathArguments(sourceFiles)
+                        .execute().assertExitCodeIsZero();
+            }
+
+            Files.createDirectories(outputJar.getParent());
+            if (Files.exists(outputJar)) {
+                TKit.trace(String.format("Delete [%s] existing jar file", outputJar));
+                Files.deleteIfExists(outputJar);
+            }
+
+            Executor jarExe = new Executor()
+                    .setToolProvider(JavaTool.JAR)
+                    .addArguments("-c", "-f", outputJar.toString());
+            if (moduleVersion != null) {
+                jarExe.addArguments(String.format("--module-version=%s",
+                        moduleVersion));
+            }
+            if (mainClass != null) {
+                jarExe.addArguments("-e", mainClass);
+            }
+            jarExe.addArguments("-C", workDir.toString(), ".");
+            jarExe.execute().assertExitCodeIsZero();
+        });
+    }
+    private List<Path> sourceFiles;
+    private Path 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/JavaAppDesc.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,175 @@
+/*
+ * 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.Path;
+
+
+public final class JavaAppDesc {
+    public JavaAppDesc() {
+    }
+
+    public JavaAppDesc setClassName(String v) {
+        qualifiedClassName = v;
+        return this;
+    }
+
+    public JavaAppDesc setModuleName(String v) {
+        moduleName = v;
+        return this;
+    }
+
+    public JavaAppDesc setJarFileName(String v) {
+        jarFileName = v;
+        return this;
+    }
+
+    public JavaAppDesc setModuleVersion(String v) {
+        moduleVersion = v;
+        return this;
+    }
+
+    public JavaAppDesc setJarWithMainClass(boolean v) {
+        jarWithMainClass = v;
+        return this;
+    }
+
+    public String className() {
+        return qualifiedClassName;
+    }
+
+    public Path classFilePath() {
+        return Path.of(qualifiedClassName.replace(".", File.separator)
+                + ".class");
+    }
+
+    public String moduleName() {
+        return moduleName;
+    }
+
+    public String packageName() {
+        int lastDotIdx = qualifiedClassName.lastIndexOf('.');
+        if (lastDotIdx == -1) {
+            return null;
+        }
+        return qualifiedClassName.substring(0, lastDotIdx);
+    }
+
+    public String jarFileName() {
+        return jarFileName;
+    }
+
+    public String moduleVersion() {
+        return moduleVersion;
+    }
+
+    public boolean jarWithMainClass() {
+        return jarWithMainClass;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        if (jarFileName != null) {
+            sb.append(jarFileName).append(':');
+        }
+        if (moduleName != null) {
+            sb.append(moduleName).append('/');
+        }
+        if (qualifiedClassName != null) {
+            sb.append(qualifiedClassName);
+        }
+        if (jarWithMainClass) {
+            sb.append('!');
+        }
+        if (moduleVersion != null) {
+            sb.append('@').append(moduleVersion);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Create Java application description form encoded string value.
+     *
+     * 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. jar command will set module
+     * version (3.7) and main class (Buz) attributes in the jar file.
+     *
+     * E.g.: `Ciao` encodes non-modular `Ciao` class in the default package.
+     * jar command will not put main class attribute in the jar file.
+     * Default name will be picked for jar file - `hello.jar`.
+     *
+     * @param cmd jpackage command to configure
+     * @param javaAppDesc encoded Java application description
+     */
+    public static JavaAppDesc parse(String javaAppDesc) {
+        JavaAppDesc desc = HelloApp.createDefaltAppDesc();
+
+        if (javaAppDesc == null) {
+            return desc;
+        }
+
+        String moduleNameAndOther = Functional.identity(() -> {
+            String[] components = javaAppDesc.split(":", 2);
+            if (components.length == 2) {
+                desc.setJarFileName(components[0]);
+            }
+            return components[components.length - 1];
+        }).get();
+
+        String classNameAndOther = Functional.identity(() -> {
+            String[] components = moduleNameAndOther.split("/", 2);
+            if (components.length == 2) {
+                desc.setModuleName(components[0]);
+            }
+            return components[components.length - 1];
+        }).get();
+
+        Functional.identity(() -> {
+            String[] components = classNameAndOther.split("@", 2);
+            if (components[0].endsWith("!")) {
+                components[0] = components[0].substring(0,
+                        components[0].length() - 1);
+                desc.setJarWithMainClass(true);
+            }
+            desc.setClassName(components[0]);
+            if (components.length == 2) {
+                desc.setModuleVersion(components[1]);
+            }
+        }).run();
+
+        return desc;
+    }
+
+    private String qualifiedClassName;
+    private String moduleName;
+    private String jarFileName;
+    private String moduleVersion;
+    private boolean jarWithMainClass;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JavaTool.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.spi.ToolProvider;
+import jdk.incubator.jpackage.ToolProviderFactory;
+
+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() {
+        if (this == JPACKAGE) {
+            return ToolProviderFactory.findFirst("jpackage").orElseThrow(
+                    () -> new RuntimeException("jpackage tool not found"));
+        } else {
+            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 Nov 12 10:18:11 2019 -0500
@@ -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,
+                getDefaultPackageArch(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.appRuntimeDirectory();
+            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 = Stream.of(line.split("\\s+")).collect(
+                        Collectors.toSet());
+                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.appLauncherPath().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();
+    }
+
+    public static String getDefaultPackageArch(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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,138 @@
+/*
+ * 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.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
+import org.xml.sax.SAXException;
+
+public class MacHelper {
+
+    public static void withExplodedDmg(JPackageCommand cmd,
+            ThrowingConsumer<Path> consumer) {
+        cmd.verifyIsOfType(PackageType.MAC_DMG);
+
+        var plist = readPList(new Executor()
+                .setExecutable("/usr/bin/hdiutil")
+                .dumpOutput()
+                .addArguments("attach", cmd.outputBundle().toString(), "-plist")
+                .executeAndGetOutput());
+
+        final Path mountPoint = Path.of(plist.queryValue("mount-point"));
+        try {
+            Path dmgImage = mountPoint.resolve(cmd.name() + ".app");
+            TKit.trace(String.format("Exploded [%s] in [%s] directory",
+                    cmd.outputBundle(), dmgImage));
+            ThrowingConsumer.toConsumer(consumer).accept(dmgImage);
+        } finally {
+            new Executor()
+                    .setExecutable("/usr/bin/hdiutil")
+                    .addArgument("detach").addArgument(mountPoint)
+                    .execute().assertExitCodeIsZero();
+        }
+    }
+
+    public static PListWrapper readPListFromAppImage(Path appImage) {
+        return readPList(appImage.resolve("Contents/Info.plist"));
+    }
+
+    public static PListWrapper readPList(Path path) {
+        TKit.assertReadableFileExists(path);
+        return ThrowingSupplier.toSupplier(() -> readPList(Files.readAllLines(
+                path))).get();
+    }
+
+    public static PListWrapper readPList(List<String> lines) {
+        return readPList(lines.stream());
+    }
+
+    public static PListWrapper readPList(Stream<String> lines) {
+        return ThrowingSupplier.toSupplier(() -> new PListWrapper(lines.collect(
+                Collectors.joining()))).get();
+    }
+
+    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);
+        return Path.of(cmd.getArgumentValue("--install-dir", () -> "/Applications"))
+                .resolve(cmd.name() + ".app");
+    }
+
+    private static String getPackageName(JPackageCommand cmd) {
+        return cmd.getArgumentValue("--mac-package-name",
+                () -> cmd.name());
+    }
+
+    public static final class PListWrapper {
+        public String queryValue(String keyName) {
+            XPath xPath = XPathFactory.newInstance().newXPath();
+            // Query for the value of <string> element preceding <key> element
+            // with value equal to `keyName`
+            String query = String.format(
+                    "//string[preceding-sibling::key = \"%s\"][1]", keyName);
+            return ThrowingSupplier.toSupplier(() -> (String) xPath.evaluate(
+                    query, doc, XPathConstants.STRING)).get();
+        }
+
+        PListWrapper(String xml) throws ParserConfigurationException,
+                SAXException, IOException {
+            doc = createDocumentBuilder().parse(new ByteArrayInputStream(
+                    xml.getBytes(StandardCharsets.UTF_8)));
+        }
+
+        private static DocumentBuilder createDocumentBuilder() throws
+                ParserConfigurationException {
+            DocumentBuilderFactory dbf = DocumentBuilderFactory.newDefaultInstance();
+            dbf.setFeature(
+                    "http://apache.org/xml/features/nonvalidating/load-external-dtd",
+                    false);
+            return dbf.newDocumentBuilder();
+        }
+
+        private final org.w3c.dom.Document doc;
+    }
+
+    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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,135 @@
+/*
+ * 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.function.Function;
+import java.util.function.Predicate;
+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) {
+                TestBuilder.trace(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.
+        List<TestInstance> orderedTests = tests.stream()
+                .sorted((a, b) -> a.fullName().compareTo(b.fullName()))
+                .collect(Collectors.toList());
+
+        if (listTests) {
+            // Just list the tests
+            orderedTests.stream().forEach(test -> System.out.println(String.format(
+                    "%s; workDir=[%s]", test.fullName(), test.workDir())));
+            return;
+        }
+
+        TKit.withExtraLogStream(() -> runTests(orderedTests));
+    }
+
+    private static void runTests(List<TestInstance> tests) {
+        TKit.runTests(tests);
+
+        final long passedCount = tests.stream().filter(TestInstance::passed).count();
+        TKit.log(String.format("[==========] %d tests ran", tests.size()));
+        TKit.log(String.format("[  PASSED  ] %d %s", passedCount,
+                passedCount == 1 ? "test" : "tests"));
+
+        reportDetails(tests, "[  SKIPPED ]", TestInstance::skipped, false);
+        reportDetails(tests, "[  FAILED  ]", TestInstance::failed, true);
+
+        var withSkipped = reportSummary(tests, "SKIPPED", TestInstance::skipped);
+        var withFailures = reportSummary(tests, "FAILED", TestInstance::failed);
+
+        if (withFailures != null) {
+            throw withFailures;
+        }
+
+        if (withSkipped != null) {
+            tests.stream().filter(TestInstance::skipped).findFirst().get().rethrowIfSkipped();
+        }
+    }
+
+    private static long reportDetails(List<TestInstance> tests,
+            String label, Predicate<TestInstance> selector, boolean printWorkDir) {
+
+        final Function<TestInstance, String> makeMessage = test -> {
+            if (printWorkDir) {
+                return String.format("%s %s; workDir=[%s]", label,
+                        test.fullName(), test.workDir());
+            }
+            return String.format("%s %s", label, test.fullName());
+        };
+
+        final long count = tests.stream().filter(selector).count();
+        if (count != 0) {
+            TKit.log(String.format("%s %d %s, listed below", label, count, count
+                    == 1 ? "test" : "tests"));
+            tests.stream().filter(selector).map(makeMessage).forEachOrdered(
+                    TKit::log);
+        }
+
+        return count;
+    }
+
+    private static RuntimeException reportSummary(List<TestInstance> tests,
+            String label, Predicate<TestInstance> selector) {
+        final long count = tests.stream().filter(selector).count();
+        if (count != 0) {
+            final String message = String.format("%d %s %s", count, label, count
+                    == 1 ? "TEST" : "TESTS");
+            TKit.log(message);
+            return new RuntimeException(message);
+        }
+
+        return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MethodCall.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,153 @@
+/*
+ * 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.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.TestInstance.TestDesc;
+
+class MethodCall implements ThrowingConsumer {
+
+    MethodCall(Object[] instanceCtorArgs, Method method) {
+        this.ctorArgs = Optional.ofNullable(instanceCtorArgs).orElse(
+                DEFAULT_CTOR_ARGS);
+        this.method = method;
+        this.methodArgs = new Object[0];
+    }
+
+    MethodCall(Object[] instanceCtorArgs, Method method, Object arg) {
+        this.ctorArgs = Optional.ofNullable(instanceCtorArgs).orElse(
+                DEFAULT_CTOR_ARGS);
+        this.method = method;
+        this.methodArgs = new Object[]{arg};
+    }
+
+    TestDesc createDescription() {
+        var descBuilder = TestDesc.createBuilder().method(method);
+        if (methodArgs.length != 0) {
+            descBuilder.methodArgs(methodArgs);
+        }
+
+        if (ctorArgs.length != 0) {
+            descBuilder.ctorArgs(ctorArgs);
+        }
+
+        return descBuilder.get();
+    }
+
+    Method getMethod() {
+        return method;
+    }
+
+    Object newInstance() throws NoSuchMethodException, InstantiationException,
+            IllegalAccessException, IllegalArgumentException,
+            InvocationTargetException {
+        if ((method.getModifiers() & Modifier.STATIC) != 0) {
+            return null;
+        }
+
+        Constructor ctor = findRequiredConstructor(method.getDeclaringClass(),
+                ctorArgs);
+        if (ctor.isVarArgs()) {
+            // Assume constructor doesn't have fixed, only variable parameters.
+            return ctor.newInstance(new Object[]{ctorArgs});
+        }
+
+        return ctor.newInstance(ctorArgs);
+    }
+
+    void checkRequiredConstructor() throws NoSuchMethodException {
+        if ((method.getModifiers() & Modifier.STATIC) == 0) {
+            findRequiredConstructor(method.getDeclaringClass(), ctorArgs);
+        }
+    }
+
+    private static Constructor findVarArgConstructor(Class type) {
+        return Stream.of(type.getConstructors()).filter(
+                Constructor::isVarArgs).findFirst().orElse(null);
+    }
+
+    private Constructor findRequiredConstructor(Class type, Object... ctorArgs)
+            throws NoSuchMethodException {
+
+        Supplier<NoSuchMethodException> notFoundException = () -> {
+            return new NoSuchMethodException(String.format(
+                    "No public contructor in %s for %s arguments", type,
+                    Arrays.deepToString(ctorArgs)));
+        };
+
+        if (Stream.of(ctorArgs).allMatch(Objects::nonNull)) {
+            // No `null` in constructor args, take easy path
+            try {
+                return type.getConstructor(Stream.of(ctorArgs).map(
+                        Object::getClass).collect(Collectors.toList()).toArray(
+                        Class[]::new));
+            } catch (NoSuchMethodException ex) {
+                // Failed to find ctor that can take the given arguments.
+                Constructor varArgCtor = findVarArgConstructor(type);
+                if (varArgCtor != null) {
+                    // There is one with variable number of arguments. Use it.
+                    return varArgCtor;
+                }
+                throw notFoundException.get();
+            }
+        }
+
+        List<Constructor> ctors = Stream.of(type.getConstructors())
+                .filter(ctor -> ctor.getParameterCount() == ctorArgs.length)
+                .collect(Collectors.toList());
+
+        if (ctors.isEmpty()) {
+            // No public constructors that can handle the given arguments.
+            throw notFoundException.get();
+        }
+
+        if (ctors.size() == 1) {
+            return ctors.iterator().next();
+        }
+
+        // Revisit this tricky case when it will start bothering.
+        throw notFoundException.get();
+    }
+
+    @Override
+    public void accept(Object thiz) throws Throwable {
+        method.invoke(thiz, methodArgs);
+    }
+
+    private final Object[] methodArgs;
+    private final Method method;
+    private final Object[] ctorArgs;
+
+    final static Object[] DEFAULT_CTOR_ARGS = new Object[0];
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,487 @@
+/*
+ * 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.awt.GraphicsEnvironment;
+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.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.incubator.jpackage.internal.AppImageFile;
+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;
+        excludeTypes = new HashSet<>();
+        forTypes();
+        setExpectedExitCode(0);
+        handlers = new HashMap<>();
+        namedInitializers = new HashSet<>();
+        currentTypes.forEach(v -> handlers.put(v, new Handler(v)));
+    }
+
+    public PackageTest excludeTypes(PackageType... types) {
+        excludeTypes.addAll(Stream.of(types).collect(Collectors.toSet()));
+        return forTypes(currentTypes);
+    }
+
+    public PackageTest excludeTypes(Collection<PackageType> types) {
+        return excludeTypes(types.toArray(PackageType[]::new));
+    }
+
+    public PackageTest forTypes(PackageType... types) {
+        Collection<PackageType> newTypes;
+        if (types == null || types.length == 0) {
+            newTypes = PackageType.NATIVE;
+        } else {
+            newTypes = Stream.of(types).collect(Collectors.toSet());
+        }
+        currentTypes = newTypes.stream()
+                .filter(PackageType::isSupported)
+                .filter(Predicate.not(excludeTypes::contains))
+                .collect(Collectors.toUnmodifiableSet());
+        return this;
+    }
+
+    public PackageTest forTypes(Collection<PackageType> types) {
+        return forTypes(types.toArray(PackageType[]::new));
+    }
+
+    public PackageTest notForTypes(PackageType... types) {
+        return notForTypes(List.of(types));
+    }
+
+    public PackageTest notForTypes(Collection<PackageType> types) {
+        Set<PackageType> workset = new HashSet<>(currentTypes);
+        workset.removeAll(types);
+        return forTypes(workset);
+    }
+
+    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);
+        });
+    }
+
+    PackageTest addHelloAppFileAssociationsVerifier(FileAssociations fa,
+            String... faLauncherDefaultArgs) {
+
+        // Setup test app to have valid jpackage command line before
+        // running check of type of environment.
+        addInitializer(cmd -> new HelloApp(null).addTo(cmd), "HelloApp");
+
+        String noActionMsg = "Not running file associations test";
+        if (GraphicsEnvironment.isHeadless()) {
+            TKit.trace(String.format(
+                    "%s because running in headless environment", noActionMsg));
+            return this;
+        }
+
+        addInstallVerifier(cmd -> {
+            if (cmd.isFakeRuntime(noActionMsg)) {
+                return;
+            }
+
+            withTestFileAssociationsFile(fa, testFile -> {
+                testFile = testFile.toAbsolutePath().normalize();
+
+                final Path appOutput = testFile.getParent()
+                        .resolve(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);
+    }
+
+    PackageTest notForTypes(Collection<PackageType> types, Runnable action) {
+        Set<PackageType> workset = new HashSet<>(currentTypes);
+        workset.removeAll(types);
+        return forTypes(workset, action);
+    }
+
+    PackageTest notForTypes(PackageType type, Runnable action) {
+        return notForTypes(List.of(type), action);
+    }
+
+    public PackageTest configureHelloApp() {
+        return configureHelloApp(null);
+    }
+
+    public PackageTest configureHelloApp(String encodedName) {
+        addInitializer(
+                cmd -> new HelloApp(JavaAppDesc.parse(encodedName)).addTo(cmd));
+        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));
+            cmd.executePrerequisiteActions();
+
+            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()));
+            TKit.assertDirectoryExists(cmd.appRuntimeDirectory());
+            if (!cmd.isRuntime()) {
+                TKit.assertExecutableFileExists(cmd.appLauncherPath());
+
+                if (PackageType.WINDOWS.contains(cmd.packageType())) {
+                    new WindowsHelper.AppVerifier(cmd);
+                }
+            }
+
+            TKit.assertPathExists(AppImageFile.getPathInAppImage(
+                    cmd.appInstallationDirectory()), false);
+
+            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.appLauncherPath(), false);
+
+                if (PackageType.WINDOWS.contains(cmd.packageType())) {
+                    new WindowsHelper.AppVerifier(cmd);
+                }
+            }
+
+            TKit.assertPathExists(cmd.appInstallationDirectory(), false);
+
+            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 Set<PackageType> excludeTypes;
+    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 Nov 12 10:18:11 2019 -0500
@@ -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.Collections;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * jpackage type traits.
+ */
+public enum PackageType {
+    WIN_MSI(".msi",
+            TKit.isWindows() ? "jdk.incubator.jpackage.internal.WinMsiBundler" : null),
+    WIN_EXE(".exe",
+            TKit.isWindows() ? "jdk.incubator.jpackage.internal.WinMsiBundler" : null),
+    LINUX_DEB(".deb",
+            TKit.isLinux() ? "jdk.incubator.jpackage.internal.LinuxDebBundler" : null),
+    LINUX_RPM(".rpm",
+            TKit.isLinux() ? "jdk.incubator.jpackage.internal.LinuxRpmBundler" : null),
+    MAC_DMG(".dmg", TKit.isOSX() ? "jdk.incubator.jpackage.internal.MacDmgBundler" : null),
+    MAC_PKG(".pkg", TKit.isOSX() ? "jdk.incubator.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("--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 = Optional.ofNullable(
+                TKit.tokenizeConfigProperty("disabledPackagers")).orElse(
+                Collections.emptySet());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,852 @@
+/*
+ * 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.io.PrintStream;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.file.*;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+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.ThrowingRunnable;
+import jdk.jpackage.test.Functional.ThrowingSupplier;
+
+final public class TKit {
+
+    private static final String OS = System.getProperty("os.name").toLowerCase();
+
+    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 final static String ICON_SUFFIX = Functional.identity(() -> {
+        if (isOSX()) {
+            return ".icns";
+        }
+
+        if (isLinux()) {
+            return ".png";
+        }
+
+        if (isWindows()) {
+            return ".ico";
+        }
+
+        throw throwUnknownPlatformError();
+    }).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();
+        test.rethrowIfSkipped();
+        if (!test.passed()) {
+            throw new RuntimeException();
+        }
+    }
+
+    static void withExtraLogStream(ThrowingRunnable action) {
+        if (extraLogStream != null) {
+            ThrowingRunnable.toRunnable(action).run();
+        } else {
+            try (PrintStream logStream = openLogStream()) {
+                extraLogStream = logStream;
+                ThrowingRunnable.toRunnable(action).run();
+            } finally {
+                extraLogStream = null;
+            }
+        }
+    }
+
+    static void runTests(List<TestInstance> tests) {
+        if (currentTest != null) {
+            throw new IllegalStateException(
+                    "Unexpeced nested or concurrent Test.run() call");
+        }
+
+        withExtraLogStream(() -> {
+            tests.stream().forEach(test -> {
+                currentTest = test;
+                try {
+                    ignoreExceptions(test).run();
+                } finally {
+                    currentTest = null;
+                    if (extraLogStream != null) {
+                        extraLogStream.flush();
+                    }
+                }
+            });
+        });
+    }
+
+    static Runnable ignoreExceptions(ThrowingRunnable action) {
+        return () -> {
+            try {
+                try {
+                    action.run();
+                } catch (Throwable ex) {
+                    unbox(ex);
+                }
+            } catch (Throwable throwable) {
+                printStackTrace(throwable);
+            }
+        };
+    }
+
+    static void unbox(Throwable throwable) throws Throwable {
+        try {
+            throw throwable;
+        } catch (ExceptionBox | InvocationTargetException ex) {
+            unbox(ex.getCause());
+        }
+    }
+
+    public static Path workDir() {
+        return currentTest.workDir();
+    }
+
+    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) {
+        createTextFile(propsFilename, lines.stream());
+    }
+
+    public static void createTextFile(Path propsFilename, Stream<String> lines) {
+        trace(String.format("Create [%s] text file...",
+                propsFilename.toAbsolutePath().normalize()));
+        ThrowingRunnable.toRunnable(() -> Files.write(propsFilename,
+                lines.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 Path 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;
+            return tempFile;
+        } finally {
+            if (tempFile != null && !keepIt) {
+                ThrowingRunnable.toRunnable(() -> Files.deleteIfExists(tempFile)).run();
+            }
+        }
+    }
+
+    public static Path 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;
+            return tempDir;
+        } finally {
+            if (tempDir != null && tempDir.toFile().isDirectory() && !keepIt) {
+                deleteDirectoryRecursive(tempDir, "");
+            }
+        }
+    }
+
+    private static class DirectoryCleaner implements Consumer<Path> {
+        DirectoryCleaner traceMessage(String v) {
+            msg = v;
+            return this;
+        }
+
+        DirectoryCleaner contentsOnly(boolean v) {
+            contentsOnly = v;
+            return this;
+        }
+
+        @Override
+        public void accept(Path root) {
+            if (msg == null) {
+                if (contentsOnly) {
+                    msg = String.format("Cleaning [%s] directory recursively",
+                            root);
+                } else {
+                    msg = String.format("Deleting [%s] directory recursively",
+                            root);
+                }
+            }
+
+            if (!msg.isEmpty()) {
+                trace(msg);
+            }
+
+            List<Throwable> errors = new ArrayList<>();
+            try {
+                final List<Path> paths;
+                if (contentsOnly) {
+                    try (var pathStream = Files.walk(root, 0)) {
+                        paths = pathStream.collect(Collectors.toList());
+                    }
+                } else {
+                    paths = List.of(root);
+                }
+
+                for (var path : paths) {
+                    try (var pathStream = Files.walk(path)) {
+                        pathStream
+                        .sorted(Comparator.reverseOrder())
+                        .sequential()
+                        .forEachOrdered(file -> {
+                            try {
+                                if (isWindows()) {
+                                    Files.setAttribute(file, "dos:readonly", false);
+                                }
+                                Files.delete(file);
+                            } catch (IOException ex) {
+                                errors.add(ex);
+                            }
+                        });
+                    }
+                }
+
+            } catch (IOException ex) {
+                errors.add(ex);
+            }
+            errors.forEach(error -> trace(error.toString()));
+        }
+
+        private String msg;
+        private boolean contentsOnly;
+    }
+
+    /**
+     * 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()) {
+            new DirectoryCleaner().contentsOnly(true).traceMessage(msg).accept(
+                    path);
+        }
+    }
+
+    /**
+     * 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()) {
+            new DirectoryCleaner().traceMessage(msg).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");
+    }
+
+    public static RuntimeException throwSkippedException(String reason) {
+        trace("Skip the test: " + reason);
+        RuntimeException ex = ThrowingSupplier.toSupplier(
+                () -> (RuntimeException) Class.forName("jtreg.SkippedException").getConstructor(
+                        String.class).newInstance(reason)).get();
+
+        currentTest.notifySkipped(ex);
+        throw ex;
+    }
+
+    public static Path createRelativePathCopy(final Path file) {
+        Path fileCopy = workDir().resolve(file.getFileName()).toAbsolutePath().normalize();
+
+        ThrowingRunnable.toRunnable(() -> Files.copy(file, fileCopy,
+                StandardCopyOption.REPLACE_EXISTING)).run();
+
+        final Path basePath = Path.of(".").toAbsolutePath().normalize();
+        try {
+            return basePath.relativize(fileCopy);
+        } catch (IllegalArgumentException ex) {
+            // May happen on Windows: java.lang.IllegalArgumentException: 'other' has different root
+            trace(String.format("Failed to relativize [%s] at [%s]", fileCopy,
+                    basePath));
+            printStackTrace(ex);
+        }
+        return file;
+    }
+
+    static void waitForFileCreated(Path fileToWaitFor,
+            long timeoutSeconds) throws IOException {
+
+        trace(String.format("Wait for file [%s] to be available",
+                                                fileToWaitFor.toAbsolutePath()));
+
+        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");
+            }
+        }
+    }
+
+    static void printStackTrace(Throwable throwable) {
+        if (extraLogStream != null) {
+            throwable.printStackTrace(extraLogStream);
+        }
+        throwable.printStackTrace();
+    }
+
+    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) {
+        assertTrue(actual, msg, null);
+    }
+
+    public static void assertFalse(boolean actual, String msg) {
+        assertFalse(actual, msg, null);
+    }
+
+    public static void assertTrue(boolean actual, String msg, Runnable onFail) {
+        currentTest.notifyAssert();
+        if (!actual) {
+            if (onFail != null) {
+                onFail.run();
+            }
+            error(concatMessages("Failed", msg));
+        }
+
+        traceAssert(String.format("assertTrue(): %s", msg));
+    }
+
+    public static void assertFalse(boolean actual, String msg, Runnable onFail) {
+        currentTest.notifyAssert();
+        if (actual) {
+            if (onFail != null) {
+                onFail.run();
+            }
+            error(concatMessages("Failed", 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));
+        }
+    }
+
+    public final static class TextStreamAsserter {
+        TextStreamAsserter(String value) {
+            this.value = value;
+            predicate(String::contains);
+        }
+
+        public TextStreamAsserter label(String v) {
+            label = v;
+            return this;
+        }
+
+        public TextStreamAsserter predicate(BiPredicate<String, String> v) {
+            predicate = v;
+            return this;
+        }
+
+        public TextStreamAsserter negate() {
+            negate = true;
+            return this;
+        }
+
+        public TextStreamAsserter orElseThrow(RuntimeException v) {
+            return orElseThrow(() -> v);
+        }
+
+        public TextStreamAsserter orElseThrow(Supplier<RuntimeException> v) {
+            createException = v;
+            return this;
+        }
+
+        public void apply(Stream<String> lines) {
+            String matchedStr = lines.filter(line -> predicate.test(line, value)).findFirst().orElse(
+                    null);
+            String labelStr = Optional.ofNullable(label).orElse("output");
+            if (negate) {
+                String msg = String.format(
+                        "Check %s doesn't contain [%s] string", labelStr, value);
+                if (createException == null) {
+                    assertNull(matchedStr, msg);
+                } else {
+                    trace(msg);
+                    if (matchedStr != null) {
+                        throw createException.get();
+                    }
+                }
+            } else {
+                String msg = String.format("Check %s contains [%s] string",
+                        labelStr, value);
+                if (createException == null) {
+                    assertNotNull(matchedStr, msg);
+                } else {
+                    trace(msg);
+                    if (matchedStr == null) {
+                        throw createException.get();
+                    }
+                }
+            }
+        }
+
+        private BiPredicate<String, String> predicate;
+        private String label;
+        private boolean negate;
+        private Supplier<RuntimeException> createException;
+        final private String value;
+    }
+
+    public static TextStreamAsserter assertTextStream(String what) {
+        return new TextStreamAsserter(what);
+    }
+
+    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 Set<String> tokenizeConfigProperty(String propertyName) {
+        final String val = TKit.getConfigProperty(propertyName);
+        if (val == null) {
+            return null;
+        }
+        return Stream.of(val.toLowerCase().split(",")).map(String::strip).filter(
+                Predicate.not(String::isEmpty)).collect(Collectors.toSet());
+    }
+
+    static final Path LOG_FILE = Functional.identity(() -> {
+        String val = getConfigProperty("logfile");
+        if (val == null) {
+            return null;
+        }
+        return Path.of(val);
+    }).get();
+
+    static {
+        Set<String> logOptions = tokenizeConfigProperty("suppress-logging");
+        if (logOptions == null) {
+            TRACE = true;
+            TRACE_ASSERTS = true;
+            VERBOSE_JPACKAGE = true;
+            VERBOSE_TEST_SETUP = true;
+        } else if (logOptions.contains("all")) {
+            TRACE = false;
+            TRACE_ASSERTS = false;
+            VERBOSE_JPACKAGE = false;
+            VERBOSE_TEST_SETUP = false;
+        } else {
+            Predicate<Set<String>> isNonOf = options -> {
+                return Collections.disjoint(logOptions, options);
+            };
+
+            TRACE = isNonOf.test(Set.of("trace", "t"));
+            TRACE_ASSERTS = isNonOf.test(Set.of("assert", "a"));
+            VERBOSE_JPACKAGE = isNonOf.test(Set.of("jpackage", "jp"));
+            VERBOSE_TEST_SETUP = isNonOf.test(Set.of("init", "i"));
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestBuilder.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,462 @@
+/*
+ * 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.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Annotations.AfterEach;
+import jdk.jpackage.test.Annotations.BeforeEach;
+import jdk.jpackage.test.Annotations.Parameter;
+import jdk.jpackage.test.Annotations.ParameterGroup;
+import jdk.jpackage.test.Annotations.Parameters;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Functional.ThrowingFunction;
+
+final class TestBuilder implements AutoCloseable {
+
+    @Override
+    public void close() throws Exception {
+        flushTestGroup();
+    }
+
+    TestBuilder(Consumer<TestInstance> testConsumer) {
+        argProcessors = Map.of(
+                CMDLINE_ARG_PREFIX + "after-run",
+                arg -> getJavaMethodsFromArg(arg).map(
+                        this::wrap).forEachOrdered(afterActions::add),
+
+                CMDLINE_ARG_PREFIX + "before-run",
+                arg -> getJavaMethodsFromArg(arg).map(
+                        this::wrap).forEachOrdered(beforeActions::add),
+
+                CMDLINE_ARG_PREFIX + "run",
+                arg -> addTestGroup(getJavaMethodsFromArg(arg).map(
+                        ThrowingFunction.toFunction(
+                                TestBuilder::toMethodCalls)).flatMap(s -> s).collect(
+                        Collectors.toList())),
+
+                CMDLINE_ARG_PREFIX + "exclude",
+                arg -> (excludedTests = Optional.ofNullable(
+                        excludedTests).orElse(new HashSet<String>())).add(arg),
+
+                CMDLINE_ARG_PREFIX + "include",
+                arg -> (includedTests = Optional.ofNullable(
+                        includedTests).orElse(new HashSet<String>())).add(arg),
+
+                CMDLINE_ARG_PREFIX + "space-subst",
+                arg -> spaceSubstitute = arg,
+
+                CMDLINE_ARG_PREFIX + "group",
+                arg -> flushTestGroup(),
+
+                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 addTestGroup(List<MethodCall> newTestGroup) {
+        if (testGroup != null) {
+            testGroup.addAll(newTestGroup);
+        } else {
+            testGroup = newTestGroup;
+        }
+    }
+
+    private static Stream<MethodCall> filterTests(Stream<MethodCall> tests,
+            Set<String> filters, UnaryOperator<Boolean> pred, String logMsg) {
+        if (filters == null) {
+            return tests;
+        }
+
+        // Log all matches before returning from the function
+        return tests.filter(test -> {
+            String testDescription = test.createDescription().testFullName();
+            boolean match = filters.stream().anyMatch(
+                    v -> testDescription.contains(v));
+            if (match) {
+                trace(String.format(logMsg + ": %s", testDescription));
+            }
+            return pred.apply(match);
+        }).collect(Collectors.toList()).stream();
+    }
+
+    private Stream<MethodCall> filterTestGroup() {
+        Objects.requireNonNull(testGroup);
+
+        UnaryOperator<Set<String>> restoreSpaces = filters -> {
+            if (spaceSubstitute == null || filters == null) {
+                return filters;
+            }
+            return filters.stream().map(
+                    filter -> filter.replace(spaceSubstitute, " ")).collect(
+                            Collectors.toSet());
+        };
+
+        if (includedTests != null) {
+            return filterTests(testGroup.stream(), restoreSpaces.apply(
+                    includedTests), x -> x, "Include");
+        }
+
+        return filterTests(testGroup.stream(),
+                restoreSpaces.apply(excludedTests), x -> !x, "Exclude");
+    }
+
+    private void flushTestGroup() {
+        if (testGroup != null) {
+            filterTestGroup().forEach(testBody -> createTestInstance(testBody));
+            clear();
+        }
+    }
+
+    private void createTestInstance(MethodCall testBody) {
+        final List<ThrowingConsumer> curBeforeActions;
+        final List<ThrowingConsumer> curAfterActions;
+
+        Method testMethod = testBody.getMethod();
+        if (Stream.of(BeforeEach.class, AfterEach.class).anyMatch(
+                type -> testMethod.isAnnotationPresent(type))) {
+            curBeforeActions = beforeActions;
+            curAfterActions = afterActions;
+        } else {
+            curBeforeActions = new ArrayList<>(beforeActions);
+            curAfterActions = new ArrayList<>(afterActions);
+
+            selectFrameMethods(testMethod.getDeclaringClass(), BeforeEach.class).map(
+                    this::wrap).forEachOrdered(curBeforeActions::add);
+            selectFrameMethods(testMethod.getDeclaringClass(), AfterEach.class).map(
+                    this::wrap).forEachOrdered(curAfterActions::add);
+        }
+
+        TestInstance test = new TestInstance(testBody, curBeforeActions,
+                curAfterActions, dryRun);
+        if (includedTests == null) {
+            trace(String.format("Create: %s", test.fullName()));
+        }
+        testConsumer.accept(test);
+    }
+
+    private void clear() {
+        beforeActions = new ArrayList<>();
+        afterActions = new ArrayList<>();
+        excludedTests = null;
+        includedTests = null;
+        spaceSubstitute = null;
+        testGroup = null;
+    }
+
+    private static Class probeClass(String name) {
+        try {
+            return Class.forName(name);
+        } catch (ClassNotFoundException ex) {
+            return null;
+        }
+    }
+
+    private static Stream<Method> selectFrameMethods(Class type, Class annotationType) {
+        return Stream.of(type.getMethods())
+                .filter(m -> m.getParameterCount() == 0)
+                .filter(m -> !m.isAnnotationPresent(Test.class))
+                .filter(m -> m.isAnnotationPresent(annotationType))
+                .sorted((a, b) -> a.getName().compareTo(b.getName()));
+    }
+
+    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(ParameterGroup.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));
+        }
+
+        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[] getMethodParameters(Method method) {
+        if (method.isAnnotationPresent(ParameterGroup.class)) {
+            return ((ParameterGroup) method.getAnnotation(ParameterGroup.class)).value();
+        }
+
+        if (method.isAnnotationPresent(Parameter.class)) {
+            return new Parameter[]{(Parameter) method.getAnnotation(
+                Parameter.class)};
+        }
+
+        // Unexpected
+        return null;
+    }
+
+    private static Stream<Object[]> toCtorArgs(Method method) throws
+            IllegalAccessException, InvocationTargetException {
+        Class type = method.getDeclaringClass();
+        List<Method> paremetersProviders = Stream.of(type.getMethods())
+                .filter(m -> m.getParameterCount() == 0)
+                .filter(m -> (m.getModifiers() & Modifier.STATIC) != 0)
+                .filter(m -> m.isAnnotationPresent(Parameters.class))
+                .sorted()
+                .collect(Collectors.toList());
+        if (paremetersProviders.isEmpty()) {
+            // Single instance using the default constructor.
+            return Stream.ofNullable(MethodCall.DEFAULT_CTOR_ARGS);
+        }
+
+        // Pick the first method from the list.
+        Method paremetersProvider = paremetersProviders.iterator().next();
+        if (paremetersProviders.size() > 1) {
+            trace(String.format(
+                    "Found %d public static methods without arguments with %s annotation. Will use %s",
+                    paremetersProviders.size(), Parameters.class,
+                    paremetersProvider));
+            paremetersProviders.stream().map(Method::toString).forEach(
+                    TestBuilder::trace);
+        }
+
+        // Construct collection of arguments for test class instances.
+        return ((Collection) paremetersProvider.invoke(null)).stream();
+    }
+
+    private static Stream<MethodCall> toMethodCalls(Method method) throws
+            IllegalAccessException, InvocationTargetException {
+        return toCtorArgs(method).map(v -> toMethodCalls(v, method)).flatMap(
+                s -> s).peek(methodCall -> {
+                    // Make sure required constructor is accessible if the one is needed.
+                    // Need to probe all methods as some of them might be static
+                    // and some class members.
+                    // Only class members require ctors.
+                    try {
+                        methodCall.checkRequiredConstructor();
+                    } catch (NoSuchMethodException ex) {
+                        throw new ParseException(ex.getMessage() + ".");
+                    }
+                });
+    }
+
+    private static Stream<MethodCall> toMethodCalls(Object[] ctorArgs, Method method) {
+        if (!isParametrized(method)) {
+            return Stream.of(new MethodCall(ctorArgs, method));
+        }
+        Parameter[] annotations = getMethodParameters(method);
+        if (annotations.length == 0) {
+            return Stream.of(new MethodCall(ctorArgs, method));
+        }
+        return Stream.of(annotations).map((a) -> {
+            Class paramType = method.getParameterTypes()[0];
+            final Object annotationValue;
+            if (!paramType.isArray()) {
+                annotationValue = fromString(a.value()[0], paramType);
+            } else {
+                Class paramComponentType = paramType.getComponentType();
+                annotationValue = Array.newInstance(paramComponentType, a.value().length);
+                var idx = new AtomicInteger(-1);
+                Stream.of(a.value()).map(v -> fromString(v, paramComponentType)).sequential().forEach(
+                        v -> Array.set(annotationValue, idx.incrementAndGet(), v));
+            }
+            return new MethodCall(ctorArgs, method, annotationValue);
+        });
+    }
+
+    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 ThrowingConsumer wrap(Method method) {
+        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;
+    }
+
+    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 Set<String> excludedTests;
+    private Set<String> includedTests;
+    private String spaceSubstitute;
+    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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,346 @@
+/*
+ * 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.Array;
+import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+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;
+
+final class TestInstance implements ThrowingRunnable {
+
+    static class TestDesc {
+        private TestDesc() {
+        }
+
+        String testFullName() {
+            StringBuilder sb = new StringBuilder();
+            sb.append(clazz.getSimpleName());
+            if (instanceArgs != null) {
+                sb.append('(').append(instanceArgs).append(')');
+            }
+            if (functionName != null) {
+                sb.append('.');
+                sb.append(functionName);
+                if (functionArgs != null) {
+                    sb.append('(').append(functionArgs).append(')');
+                }
+            }
+            return sb.toString();
+        }
+
+        static Builder createBuilder() {
+            return new Builder();
+        }
+
+        static final class Builder implements Supplier<TestDesc> {
+            private Builder() {
+            }
+
+            Builder method(Method v) {
+                method = v;
+                return this;
+            }
+
+            Builder ctorArgs(Object... v) {
+                ctorArgs = ofNullable(v);
+                return this;
+            }
+
+            Builder methodArgs(Object... v) {
+                methodArgs = ofNullable(v);
+                return this;
+            }
+
+            @Override
+            public TestDesc get() {
+                TestDesc desc = new TestDesc();
+                if (method == null) {
+                    desc.clazz = enclosingMainMethodClass();
+                } else {
+                    desc.clazz = method.getDeclaringClass();
+                    desc.functionName = method.getName();
+                    desc.functionArgs = formatArgs(methodArgs);
+                    desc.instanceArgs = formatArgs(ctorArgs);
+                }
+                return desc;
+            }
+
+            private static String formatArgs(List<Object> values) {
+                if (values == null) {
+                    return null;
+                }
+                return values.stream().map(v -> {
+                    if (v != null && v.getClass().isArray()) {
+                        return String.format("%s(length=%d)",
+                                Arrays.deepToString((Object[]) v),
+                                Array.getLength(v));
+                    }
+                    return String.format("%s", v);
+                }).collect(Collectors.joining(", "));
+            }
+
+            private static List<Object> ofNullable(Object... values) {
+                List<Object> result = new ArrayList();
+                for (var v: values) {
+                    result.add(v);
+                }
+                return result;
+            }
+
+            private List<Object> ctorArgs;
+            private List<Object> methodArgs;
+            private Method method;
+        }
+
+        static TestDesc create(Method m, Object... args) {
+            TestDesc desc = new TestDesc();
+            desc.clazz = m.getDeclaringClass();
+            desc.functionName = m.getName();
+            if (args.length != 0) {
+                desc.functionArgs = Stream.of(args).map(v -> {
+                    if (v.getClass().isArray()) {
+                        return String.format("%s(length=%d)",
+                                Arrays.deepToString((Object[]) v),
+                                Array.getLength(v));
+                    }
+                    return String.format("%s", v);
+                }).collect(Collectors.joining(", "));
+            }
+            return desc;
+        }
+
+        private Class clazz;
+        private String functionName;
+        private String functionArgs;
+        private String instanceArgs;
+    }
+
+    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.createBuilder().get();
+        this.dryRun = false;
+        this.workDir = createWorkDirName(testDesc);
+    }
+
+    TestInstance(MethodCall testBody, List<ThrowingConsumer> beforeActions,
+            List<ThrowingConsumer> afterActions, boolean dryRun) {
+        assertCount = 0;
+        this.testConstructor = v -> ((MethodCall)v).newInstance();
+        this.testBody = testBody;
+        this.beforeActions = beforeActions;
+        this.afterActions = afterActions;
+        this.testDesc = testBody.createDescription();
+        this.dryRun = dryRun;
+        this.workDir = createWorkDirName(testDesc);
+    }
+
+    void notifyAssert() {
+        assertCount++;
+    }
+
+    void notifySkipped(RuntimeException ex) {
+        skippedTestException = ex;
+    }
+
+    boolean passed() {
+        return status == Status.Passed;
+    }
+
+    boolean skipped() {
+        return status == Status.Skipped;
+    }
+
+    boolean failed() {
+        return status == Status.Failed;
+    }
+
+    String functionName() {
+        return testDesc.functionName;
+    }
+
+    String baseName() {
+        return testDesc.clazz.getSimpleName();
+    }
+
+    String fullName() {
+        return testDesc.testFullName();
+    }
+
+    void rethrowIfSkipped() {
+        if (skippedTestException != null) {
+            throw skippedTestException;
+        }
+    }
+
+    Path workDir() {
+        return workDir;
+    }
+
+    @Override
+    public void run() throws Throwable {
+        final String fullName = fullName();
+        TKit.log(String.format("[ RUN      ] %s", fullName));
+        try {
+            Object testInstance = testConstructor.apply(testBody);
+            beforeActions.forEach(a -> ThrowingConsumer.toConsumer(a).accept(
+                    testInstance));
+            try {
+                if (!dryRun) {
+                    Files.createDirectories(workDir);
+                    testBody.accept(testInstance);
+                }
+            } finally {
+                afterActions.forEach(a -> TKit.ignoreExceptions(() -> a.accept(
+                        testInstance)));
+            }
+            status = Status.Passed;
+        } finally {
+            if (skippedTestException != null) {
+                status = Status.Skipped;
+            } else if (status == null) {
+                status = Status.Failed;
+            }
+
+            if (!KEEP_WORK_DIR.contains(status)) {
+                TKit.deleteDirectoryRecursive(workDir);
+            }
+
+            TKit.log(String.format("%s %s; checks=%d", status, 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 static boolean isCalledByJavatest() {
+        StackTraceElement st[] = Thread.currentThread().getStackTrace();
+        for (StackTraceElement ste : st) {
+            if (ste.getClassName().startsWith("com.sun.javatest.")) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static Path createWorkDirName(TestDesc testDesc) {
+        Path result = Path.of(".");
+        if (!isCalledByJavatest()) {
+            result = result.resolve(testDesc.clazz.getSimpleName());
+        }
+
+        List<String> components = new ArrayList<>();
+
+        final String testFunctionName = testDesc.functionName;
+        if (testFunctionName != null) {
+            components.add(testFunctionName);
+        }
+
+        final boolean isPrametrized = Stream.of(testDesc.functionArgs,
+                testDesc.instanceArgs).anyMatch(Objects::nonNull);
+        if (isPrametrized) {
+            components.add(String.format("%08x", testDesc.testFullName().hashCode()));
+        }
+
+        if (!components.isEmpty()) {
+            result = result.resolve(String.join(".", components));
+        }
+
+        return result;
+    }
+
+    private enum Status {
+        Passed("[       OK ]"),
+        Failed("[  FAILED  ]"),
+        Skipped("[  SKIPPED ]");
+
+        Status(String msg) {
+            this.msg = msg;
+        }
+
+        @Override
+        public String toString() {
+            return msg;
+        }
+
+        private final String msg;
+    }
+
+    private int assertCount;
+    private Status status;
+    private RuntimeException skippedTestException;
+    private final TestDesc testDesc;
+    private final ThrowingFunction testConstructor;
+    private final ThrowingConsumer testBody;
+    private final List<ThrowingConsumer> beforeActions;
+    private final List<ThrowingConsumer> afterActions;
+    private final boolean dryRun;
+    private final Path workDir;
+
+    private final static Set<Status> KEEP_WORK_DIR = Functional.identity(
+            () -> {
+                final String propertyName = "keep-work-dir";
+                Set<String> keepWorkDir = TKit.tokenizeConfigProperty(
+                        propertyName);
+                if (keepWorkDir == null) {
+                    return Set.of(Status.Failed);
+                }
+
+                Predicate<Set<String>> isOneOf = options -> {
+                    return !Collections.disjoint(keepWorkDir, options);
+                };
+
+                Set<Status> result = new HashSet<>();
+                if (isOneOf.test(Set.of("pass", "p"))) {
+                    result.add(Status.Passed);
+                }
+                if (isOneOf.test(Set.of("fail", "f"))) {
+                    result.add(Status.Failed);
+                }
+
+                return Collections.unmodifiableSet(result);
+            }).get();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,260 @@
+/*
+ * 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.appLauncherPath().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.appLauncherPath().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 verifyStartMenuShortcut(Path shortcutsRoot, boolean exists) {
+            Path shortcutPath = shortcutsRoot.resolve(startMenuShortcutPath());
+            verifyShortcut(shortcutPath, exists);
+            if (!exists) {
+                TKit.assertPathExists(shortcutPath.getParent(), false);
+            }
+        }
+
+        private void verifySystemStartMenuShortcut(boolean exists) {
+            verifyStartMenuShortcut(Path.of(queryRegistryValueCache(
+                    SYSTEM_SHELL_FOLDERS_REGKEY, "Common Programs")), exists);
+
+        }
+
+        private void verifyUserLocalStartMenuShortcut(boolean exists) {
+            verifyStartMenuShortcut(Path.of(queryRegistryValueCache(
+                    USER_SHELL_FOLDERS_REGKEY, "Programs")), exists);
+        }
+
+        private void verifyFileAssociationsRegistry() {
+            Stream.of(cmd.getAllArgumentValues("--file-associations")).map(
+                    Path::of).forEach(this::verifyFileAssociationsRegistry);
+        }
+
+        private void verifyFileAssociationsRegistry(Path faFile) {
+            boolean appInstalled = cmd.appLauncherPath().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/incubator/jpackage/internal/AppImageFileTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,172 @@
+/*
+ * 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.incubator.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>"));
+        assertInvalid(createFromXml(
+                "<jpackage-state>",
+                    "<launcher>A</launcher>",
+                    "<launcher>B</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("Boo", (createFromXml(
+                "<jpackage-state>",
+                    "<main-launcher>Boo</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 {
+        Map<String, ? super Object> params = new LinkedHashMap<>();
+        List<Map<String, ? super Object>> launchersAsMap = new ArrayList<>();
+
+        Map<String, ? super Object> addLauncher2Params = new LinkedHashMap();
+        addLauncher2Params.put("name", "Launcher2Name");
+        launchersAsMap.add(addLauncher2Params);
+
+        Map<String, ? super Object> addLauncher3Params = new LinkedHashMap();
+        addLauncher3Params.put("name", "Launcher3Name");
+        launchersAsMap.add(addLauncher3Params);
+
+        params.put("name", "Duke App");
+        params.put("description", "Duke App Description");
+        params.put("add-launcher", launchersAsMap);
+        AppImageFile aif = create(params);
+
+        List<String> addLauncherNames = aif.getAddLauncherNames();
+        Assert.assertEquals(2, addLauncherNames.size());
+        Assert.assertTrue(addLauncherNames.contains("Launcher2Name"));
+        Assert.assertTrue(addLauncherNames.contains("Launcher3Name"));
+
+    }
+
+    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 = AppImageFile.getPathInAppImage(directory);
+        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/incubator/jpackage/internal/ApplicationLayoutTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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/incubator/jpackage/internal/CompareDottedVersionTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,106 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import static org.junit.Assert.*;
+
+@RunWith(Parameterized.class)
+public class CompareDottedVersionTest {
+
+    public CompareDottedVersionTest(boolean greedy, String version1,
+            String version2, int result) {
+        this.version1 = version1;
+        this.version2 = version2;
+        this.expectedResult = result;
+
+        if (greedy) {
+            createTestee = DottedVersion::greedy;
+        } else {
+            createTestee = DottedVersion::lazy;
+        }
+    }
+
+    @Parameters
+    public static List<Object[]> data() {
+        List<Object[]> data = new ArrayList<>();
+        for (var greedy : List.of(true, false)) {
+            data.addAll(List.of(new Object[][] {
+                { greedy, "00.0.0", "0", 0 },
+                { greedy, "0.035", "0.0035", 0 },
+                { greedy, "1", "1", 0 },
+                { greedy, "2", "2.0", 0 },
+                { greedy, "2.00", "2.0", 0 },
+                { greedy, "1.2.3.4", "1.2.3.4.5", -1 },
+                { greedy, "34", "33", 1 },
+                { greedy, "34.0.78", "34.1.78", -1 }
+            }));
+        }
+
+        data.addAll(List.of(new Object[][] {
+            { false, "", "1", -1 },
+            { false, "1.2.4-R4", "1.2.4-R5", 0 },
+            { false, "1.2.4.-R4", "1.2.4.R5", 0 },
+            { false, "7+1", "7+4", 0 },
+            { false, "2+14", "2-14", 0 },
+            { false, "23.4.RC4", "23.3.RC10", 1 },
+            { false, "77.0", "77.99999999999999999999999999999999999999999999999", 0 },
+        }));
+
+        return data;
+    }
+
+    @Test
+    public void testIt() {
+        int actualResult = compare(version1, version2);
+        assertEquals(expectedResult, actualResult);
+
+        int actualNegateResult = compare(version2, version1);
+        assertEquals(actualResult, -1 * actualNegateResult);
+    }
+
+    private int compare(String x, String y) {
+        int result = createTestee.apply(x).compareTo(y);
+
+        if (result < 0) {
+            return -1;
+        }
+
+        if (result > 0) {
+            return 1;
+        }
+
+        return 0;
+    }
+
+    private final String version1;
+    private final String version2;
+    private final int expectedResult;
+    private final Function<String, DottedVersion> createTestee;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/DeployParamsTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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/incubator/jpackage/internal/DottedVersionTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,116 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import static org.junit.Assert.*;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DottedVersionTest {
+
+    public DottedVersionTest(boolean greedy) {
+        this.greedy = greedy;
+        if (greedy) {
+            createTestee = DottedVersion::greedy;
+        } else {
+            createTestee = DottedVersion::lazy;
+        }
+    }
+
+    @Parameterized.Parameters
+    public static List<Object[]> data() {
+        return List.of(new Object[] { true }, new Object[] { false });
+    }
+
+    @Rule
+    public ExpectedException exceptionRule = ExpectedException.none();
+
+    @Test
+    public void testValid() {
+        final List<String> validStrings = List.of(
+            "1.0",
+            "1",
+            "2.234.045",
+            "2.234.0",
+            "0",
+            "0.1"
+        );
+
+        final List<String> validLazyStrings;
+        if (greedy) {
+            validLazyStrings = Collections.emptyList();
+        } else {
+            validLazyStrings = List.of(
+                "1.-1",
+                "5.",
+                "4.2.",
+                "3..2",
+                "2.a",
+                "0a",
+                ".",
+                " ",
+                " 1",
+                "1. 2",
+                "+1",
+                "-1",
+                "-0",
+                "1234567890123456789012345678901234567890"
+            );
+        }
+
+        Stream.concat(validStrings.stream(), validLazyStrings.stream())
+        .forEach(value -> {
+            DottedVersion version = createTestee.apply(value);
+            assertEquals(version.toString(), value);
+        });
+    }
+
+    @Test
+    public void testNull() {
+        exceptionRule.expect(NullPointerException.class);
+        createTestee.apply(null);
+    }
+
+    @Test
+    public void testEmpty() {
+        if (greedy) {
+            exceptionRule.expect(IllegalArgumentException.class);
+            exceptionRule.expectMessage("Version may not be empty string");
+            createTestee.apply("");
+        } else {
+            assertTrue(0 == createTestee.apply("").compareTo(""));
+            assertTrue(0 == createTestee.apply("").compareTo("0"));
+        }
+    }
+
+    private final boolean greedy;
+    private final Function<String, DottedVersion> createTestee;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/InvalidDottedVersionTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,72 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class InvalidDottedVersionTest {
+
+    public InvalidDottedVersionTest(String version) {
+        this.version = version;
+    }
+
+    @Parameters
+    public static List<Object[]> data() {
+        return Stream.of(
+            "1.-1",
+            "5.",
+            "4.2.",
+            "3..2",
+            "2.a",
+            "0a",
+            ".",
+            " ",
+            " 1",
+            "1. 2",
+            "+1",
+            "-1",
+            "-0",
+            "1234567890123456789012345678901234567890"
+        ).map(version -> new Object[] { version }).collect(Collectors.toList());
+    }
+
+    @Rule
+    public ExpectedException exceptionRule = ExpectedException.none();
+
+    @Test
+    public void testIt() {
+        exceptionRule.expect(IllegalArgumentException.class);
+        new DottedVersion(version);
+    }
+
+    private final String version;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/OverridableResourceTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jdk.incubator.jpackage.internal;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import jdk.incubator.jpackage.internal.resources.ResourceLocator;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class OverridableResourceTest {
+
+    @Rule
+    public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+    @Test
+    public void testDefault() throws IOException {
+        byte[] actualBytes = saveToFile(new OverridableResource(DEFAULT_NAME));
+
+        try (InputStream is = ResourceLocator.class.getResourceAsStream(
+                DEFAULT_NAME)) {
+            assertArrayEquals(is.readAllBytes(), actualBytes);
+        }
+    }
+
+    @Test
+    public void testDefaultWithSubstitution() throws IOException {
+        OverridableResource resource = new OverridableResource(DEFAULT_NAME);
+
+        List<String> linesBeforeSubstitution = convertToStringList(saveToFile(
+                resource));
+
+        if (SUBSTITUTION_DATA.size() != 1) {
+            // Test setup issue
+            throw new IllegalArgumentException(
+                    "Substitution map should contain only a single entry");
+        }
+
+        resource.setSubstitutionData(SUBSTITUTION_DATA);
+        List<String> linesAfterSubstitution = convertToStringList(saveToFile(
+                resource));
+
+        assertEquals(linesBeforeSubstitution.size(), linesAfterSubstitution.size());
+
+        Iterator<String> beforeIt = linesBeforeSubstitution.iterator();
+        Iterator<String> afterIt = linesAfterSubstitution.iterator();
+
+        var substitutionEntry = SUBSTITUTION_DATA.entrySet().iterator().next();
+
+        boolean linesMismatch = false;
+        while (beforeIt.hasNext()) {
+            String beforeStr = beforeIt.next();
+            String afterStr = afterIt.next();
+
+            if (beforeStr.equals(afterStr)) {
+                assertFalse(beforeStr.contains(substitutionEntry.getKey()));
+            } else {
+                linesMismatch = true;
+                assertTrue(beforeStr.contains(substitutionEntry.getKey()));
+                assertTrue(afterStr.contains(substitutionEntry.getValue()));
+                assertFalse(afterStr.contains(substitutionEntry.getKey()));
+            }
+        }
+
+        assertTrue(linesMismatch);
+    }
+
+    @Test
+    public void testCustom() throws IOException {
+        testCustom(DEFAULT_NAME);
+    }
+
+    @Test
+    public void testCustomNoDefault() throws IOException {
+        testCustom(null);
+    }
+
+    private void testCustom(String defaultName) throws IOException {
+        List<String> expectedResourceData = List.of("A", "B", "C");
+
+        Path customFile = createCustomFile("foo", expectedResourceData);
+
+        List<String> actualResourceData = convertToStringList(saveToFile(
+                new OverridableResource(defaultName)
+                        .setPublicName(customFile.getFileName())
+                        .setResourceDir(customFile.getParent())));
+
+        assertArrayEquals(expectedResourceData.toArray(String[]::new),
+                actualResourceData.toArray(String[]::new));
+    }
+
+    @Test
+    public void testCustomtWithSubstitution() throws IOException {
+        testCustomtWithSubstitution(DEFAULT_NAME);
+    }
+
+    @Test
+    public void testCustomtWithSubstitutionNoDefault() throws IOException {
+        testCustomtWithSubstitution(null);
+    }
+
+    private void testCustomtWithSubstitution(String defaultName) throws IOException {
+        final List<String> resourceData = List.of("A", "[BB]", "C", "Foo",
+                "GoodbyeHello");
+        final Path customFile = createCustomFile("foo", resourceData);
+
+        final Map<String, String> substitutionData = new HashMap(Map.of("B",
+                "Bar", "Foo", "B"));
+        substitutionData.put("Hello", null);
+
+        final List<String> expectedResourceData = List.of("A", "[BarBar]", "C",
+                "B", "Goodbye");
+
+        final List<String> actualResourceData = convertToStringList(saveToFile(
+                new OverridableResource(defaultName)
+                        .setPublicName(customFile.getFileName())
+                        .setSubstitutionData(substitutionData)
+                        .setResourceDir(customFile.getParent())));
+        assertArrayEquals(expectedResourceData.toArray(String[]::new),
+                actualResourceData.toArray(String[]::new));
+
+        // Don't call setPublicName()
+        final Path dstFile = tempFolder.newFolder().toPath().resolve(customFile.getFileName());
+        new OverridableResource(defaultName)
+                .setSubstitutionData(substitutionData)
+                .setResourceDir(customFile.getParent())
+                .saveToFile(dstFile);
+        assertArrayEquals(expectedResourceData.toArray(String[]::new),
+                convertToStringList(Files.readAllBytes(dstFile)).toArray(
+                        String[]::new));
+
+        // Verify setSubstitutionData() stores a copy of passed in data
+        Map<String, String> substitutionData2 = new HashMap(substitutionData);
+        var resource = new OverridableResource(defaultName)
+                .setResourceDir(customFile.getParent());
+
+        resource.setSubstitutionData(substitutionData2);
+        substitutionData2.clear();
+        Files.delete(dstFile);
+        resource.saveToFile(dstFile);
+        assertArrayEquals(expectedResourceData.toArray(String[]::new),
+                convertToStringList(Files.readAllBytes(dstFile)).toArray(
+                        String[]::new));
+    }
+
+    @Test
+    public void testNoDefault() throws IOException {
+        Path dstFolder = tempFolder.newFolder().toPath();
+        Path dstFile = dstFolder.resolve(Path.of("foo", "bar"));
+
+        new OverridableResource(null).saveToFile(dstFile);
+
+        assertFalse(dstFile.toFile().exists());
+    }
+
+    private final static String DEFAULT_NAME;
+    private final static Map<String, String> SUBSTITUTION_DATA;
+    static {
+        if (Platform.isWindows()) {
+            DEFAULT_NAME = "WinLauncher.template";
+            SUBSTITUTION_DATA = Map.of("COMPANY_NAME", "Foo9090345");
+        } else if (Platform.isLinux()) {
+            DEFAULT_NAME = "template.control";
+            SUBSTITUTION_DATA = Map.of("APPLICATION_PACKAGE", "Package1967");
+        } else if (Platform.isMac()) {
+            DEFAULT_NAME = "Info-lite.plist.template";
+            SUBSTITUTION_DATA = Map.of("DEPLOY_BUNDLE_IDENTIFIER", "12345");
+        } else {
+            throw Platform.throwUnknownPlatformError();
+        }
+    }
+
+    private byte[] saveToFile(OverridableResource resource) throws IOException {
+        Path dstFile = tempFolder.newFile().toPath();
+        resource.saveToFile(dstFile);
+        assertThat(0, is(not(dstFile.toFile().length())));
+
+        return Files.readAllBytes(dstFile);
+    }
+
+    private Path createCustomFile(String publicName, List<String> data) throws
+            IOException {
+        Path resourceFolder = tempFolder.newFolder().toPath();
+        Path customFile = resourceFolder.resolve(publicName);
+
+        Files.write(customFile, data);
+
+        return customFile;
+    }
+
+    private static List<String> convertToStringList(byte[] data) {
+        return List.of(new String(data, StandardCharsets.UTF_8).split("\\R"));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/PathGroupTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,271 @@
+/*
+ * 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.incubator.jpackage.internal;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+
+public class PathGroupTest {
+
+    @Rule
+    public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+    @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));
+    }
+
+    @Test
+    public void testTransform() throws IOException {
+        for (var transform : TransformType.values()) {
+            testTransform(false, transform);
+        }
+    }
+
+    @Test
+    public void testTransformWithExcludes() throws IOException {
+        for (var transform : TransformType.values()) {
+            testTransform(true, transform);
+        }
+    }
+
+    enum TransformType { Copy, Move, Handler };
+
+    private void testTransform(boolean withExcludes, TransformType transform)
+            throws IOException {
+        final PathGroup pg = new PathGroup(Map.of(0, PATH_FOO, 1, PATH_BAR, 2,
+                PATH_EMPTY, 3, PATH_BAZ));
+
+        final Path srcDir = tempFolder.newFolder().toPath();
+        final Path dstDir = tempFolder.newFolder().toPath();
+
+        Files.createDirectories(srcDir.resolve(PATH_FOO).resolve("a/b/c/d"));
+        Files.createFile(srcDir.resolve(PATH_FOO).resolve("a/b/c/file1"));
+        Files.createFile(srcDir.resolve(PATH_FOO).resolve("a/b/file2"));
+        Files.createFile(srcDir.resolve(PATH_FOO).resolve("a/b/file3"));
+        Files.createFile(srcDir.resolve(PATH_BAR));
+        Files.createFile(srcDir.resolve(PATH_EMPTY).resolve("file4"));
+        Files.createDirectories(srcDir.resolve(PATH_BAZ).resolve("1/2/3"));
+
+        var dst = pg.resolveAt(dstDir);
+        var src = pg.resolveAt(srcDir);
+        if (withExcludes) {
+            // Exclude from transformation.
+            src.setPath(new Object(), srcDir.resolve(PATH_FOO).resolve("a/b/c"));
+            src.setPath(new Object(), srcDir.resolve(PATH_EMPTY).resolve("file4"));
+        }
+
+        var srcFilesBeforeTransform = walkFiles(srcDir);
+
+        if (transform == TransformType.Handler) {
+            List<Map.Entry<Path, Path>> copyFile = new ArrayList<>();
+            List<Path> createDirectory = new ArrayList<>();
+            src.transform(dst, new PathGroup.TransformHandler() {
+                @Override
+                public void copyFile(Path src, Path dst) throws IOException {
+                    copyFile.add(Map.entry(src, dst));
+                }
+
+                @Override
+                public void createDirectory(Path dir) throws IOException {
+                    createDirectory.add(dir);
+                }
+            });
+
+            Consumer<Path> assertFile = path -> {
+                var entry = Map.entry(srcDir.resolve(path), dstDir.resolve(path));
+                assertTrue(copyFile.contains(entry));
+            };
+
+            Consumer<Path> assertDir = path -> {
+                assertTrue(createDirectory.contains(dstDir.resolve(path)));
+            };
+
+            assertEquals(withExcludes ? 3 : 5, copyFile.size());
+            assertEquals(withExcludes ? 8 : 10, createDirectory.size());
+
+            assertFile.accept(PATH_FOO.resolve("a/b/file2"));
+            assertFile.accept(PATH_FOO.resolve("a/b/file3"));
+            assertFile.accept(PATH_BAR);
+            assertDir.accept(PATH_FOO.resolve("a/b"));
+            assertDir.accept(PATH_FOO.resolve("a"));
+            assertDir.accept(PATH_FOO);
+            assertDir.accept(PATH_BAZ);
+            assertDir.accept(PATH_BAZ.resolve("1"));
+            assertDir.accept(PATH_BAZ.resolve("1/2"));
+            assertDir.accept(PATH_BAZ.resolve("1/2/3"));
+            assertDir.accept(PATH_EMPTY);
+
+            if (!withExcludes) {
+                assertFile.accept(PATH_FOO.resolve("a/b/c/file1"));
+                assertFile.accept(PATH_EMPTY.resolve("file4"));
+                assertDir.accept(PATH_FOO.resolve("a/b/c/d"));
+                assertDir.accept(PATH_FOO.resolve("a/b/c"));
+            }
+
+            assertArrayEquals(new Path[] { Path.of("") }, walkFiles(dstDir));
+            return;
+        }
+
+        if (transform == TransformType.Copy) {
+            src.copy(dst);
+        } else if (transform == TransformType.Move) {
+            src.move(dst);
+        }
+
+        final List<Path> excludedPaths;
+        if (withExcludes) {
+            excludedPaths = List.of(
+                PATH_EMPTY.resolve("file4"),
+                PATH_FOO.resolve("a/b/c")
+            );
+        } else {
+            excludedPaths = Collections.emptyList();
+        }
+        UnaryOperator<Path[]> removeExcludes = paths -> {
+            return Stream.of(paths)
+                    .filter(path -> !excludedPaths.stream().anyMatch(
+                            path::startsWith))
+                    .collect(Collectors.toList()).toArray(Path[]::new);
+        };
+
+        var dstFiles = walkFiles(dstDir);
+        assertArrayEquals(removeExcludes.apply(srcFilesBeforeTransform), dstFiles);
+
+        if (transform == TransformType.Copy) {
+            assertArrayEquals(dstFiles, removeExcludes.apply(walkFiles(srcDir)));
+        } else if (transform == TransformType.Move) {
+            assertFalse(Files.exists(srcDir));
+        }
+    }
+
+    private static Path[] walkFiles(Path root) throws IOException {
+        try (var files = Files.walk(root)) {
+            return files.map(root::relativize).sorted().collect(
+                    Collectors.toList()).toArray(Path[]::new);
+        }
+    }
+
+    private final static Path PATH_FOO = Path.of("foo");
+    private final static Path PATH_BAR = Path.of("bar");
+    private final static Path PATH_BAZ = Path.of("baz");
+    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/incubator/jpackage/internal/ToolValidatorTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,94 @@
+/*
+ * 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.incubator.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(
+                new DottedVersion("1")).setVersionParser(unused -> "10").validate());
+
+        // Minimal version is 5, actual is 4.99.37. Error expected.
+        assertValidationFailure(new ToolValidator(TOOL_JAVA).setMinimalVersion(
+                new DottedVersion("5")).setVersionParser(unused -> "4.99.37").validate(),
+                false);
+
+        // Minimal version is 8, actual is 10, lexicographical comparison is used. Error expected.
+        assertValidationFailure(new ToolValidator(TOOL_JAVA).setMinimalVersion(
+                "8").setVersionParser(unused -> "10").validate(), false);
+
+        // Minimal version is 8, actual is 10, Use DottedVersion class for comparison. Should be OK.
+        assertNull(new ToolValidator(TOOL_JAVA).setMinimalVersion(
+                new DottedVersion("8")).setVersionParser(unused -> "10").validate());
+    }
+
+    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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,31 @@
+/*
+ * 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
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/junit/run_junit.sh	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+set -x
+
+set -e
+if [ -z "$BASH" ]; then
+  # The script relies on Bash arrays, rerun in Bash.
+  /bin/bash $0 $@
+  exit
+fi
+
+sources=()
+classes=()
+for s in $(find "${TESTSRC}" -name  "*.java" | grep -v junit.java); do
+  sources+=( "$s" )
+  classes+=( $(echo "$s" | sed -e "s|${TESTSRC}/||" -e 's|/|.|g' -e 's/.java$//') )
+done
+
+common_args=(\
+  --add-modules jdk.incubator.jpackage \
+  --patch-module jdk.incubator.jpackage="${TESTSRC}${PS}${TESTCLASSES}" \
+  --add-reads jdk.incubator.jpackage=ALL-UNNAMED \
+  --add-exports jdk.incubator.jpackage/jdk.incubator.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 "${classes[@]}"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/AppCategoryTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,72 @@
+/*
+ * 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
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.incubator.jpackage/jdk.incubator.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 Nov 12 10:18:11 2019 -0500
@@ -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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please 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
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.incubator.jpackage/jdk.incubator.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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,72 @@
+/*
+ * 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
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.incubator.jpackage/jdk.incubator.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/LinuxResourceTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,137 @@
+/*
+ * 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.io.IOException;
+import java.nio.file.Path;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.LinuxHelper;
+import jdk.jpackage.test.Annotations.Test;
+import java.util.List;
+
+/*
+ * @test
+ * @summary jpackage with --resource-dir
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile LinuxResourceTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=LinuxResourceTest
+ */
+
+public class LinuxResourceTest {
+    @Test
+    public static void testHardcodedProperties() throws IOException {
+        new PackageTest()
+        .forTypes(PackageType.LINUX)
+        .configureHelloApp()
+        .addInitializer(cmd -> {
+            cmd
+            .setFakeRuntime()
+            .saveConsoleOutput(true)
+            .addArguments("--resource-dir", TKit.createTempDirectory("resources"));
+        })
+        .forTypes(PackageType.LINUX_DEB)
+        .addInitializer(cmd -> {
+            Path controlFile = Path.of(cmd.getArgumentValue("--resource-dir"),
+                    "control");
+            TKit.createTextFile(controlFile, List.of(
+                "Package: dont-install-me",
+                "Version: 1.2.3-R2",
+                "Section: APPLICATION_SECTION",
+                "Maintainer: APPLICATION_MAINTAINER",
+                "Priority: optional",
+                "Architecture: bar",
+                "Provides: dont-install-me",
+                "Description: APPLICATION_DESCRIPTION",
+                "Installed-Size: APPLICATION_INSTALLED_SIZE",
+                "Depends: PACKAGE_DEFAULT_DEPENDENCIES"
+            ));
+        })
+        .addBundleVerifier((cmd, result) -> {
+            TKit.assertTextStream("Using custom package resource [DEB control file]")
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+            TKit.assertTextStream(String.format(
+                    "Expected value of \"Package\" property is [%s]. Actual value in output package is [dont-install-me]",
+                    LinuxHelper.getPackageName(cmd)))
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+            TKit.assertTextStream(
+                    "Expected value of \"Version\" property is [1.0-1]. Actual value in output package is [1.2.3-R2]")
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+            TKit.assertTextStream(String.format(
+                    "Expected value of \"Architecture\" property is [%s]. Actual value in output package is [bar]",
+                    LinuxHelper.getDefaultPackageArch(cmd.packageType())))
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+        })
+        .forTypes(PackageType.LINUX_RPM)
+        .addInitializer(cmd -> {
+            Path specFile = Path.of(cmd.getArgumentValue("--resource-dir"),
+                    LinuxHelper.getPackageName(cmd) + ".spec");
+            TKit.createTextFile(specFile, List.of(
+                "Name: dont-install-me",
+                "Version: 1.2.3",
+                "Release: R2",
+                "Summary: APPLICATION_SUMMARY",
+                "License: APPLICATION_LICENSE_TYPE",
+                "Prefix: %{dirname:APPLICATION_DIRECTORY}",
+                "Provides: dont-install-me",
+                "%description",
+                "APPLICATION_DESCRIPTION",
+                "%prep",
+                "%build",
+                "%install",
+                "rm -rf %{buildroot}",
+                "install -d -m 755 %{buildroot}APPLICATION_DIRECTORY",
+                "cp -r %{_sourcedir}APPLICATION_DIRECTORY/* %{buildroot}APPLICATION_DIRECTORY",
+                "%files",
+                "APPLICATION_DIRECTORY"
+            ));
+        })
+        .addBundleVerifier((cmd, result) -> {
+            TKit.assertTextStream("Using custom package resource [RPM spec file]")
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+            TKit.assertTextStream(String.format(
+                    "Expected value of \"Name\" property is [%s]. Actual value in output package is [dont-install-me]",
+                    LinuxHelper.getPackageName(cmd)))
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+            TKit.assertTextStream(
+                    "Expected value of \"Version\" property is [1.0]. Actual value in output package is [1.2.3]")
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+            TKit.assertTextStream(
+                    "Expected value of \"Release\" property is [1]. Actual value in output package is [R2]")
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+        })
+        .run();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/linux/MaintainerTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.
+ */
+
+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
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.incubator.jpackage/jdk.incubator.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 Nov 12 10:18:11 2019 -0500
@@ -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.
+ */
+
+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
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.incubator.jpackage/jdk.incubator.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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,75 @@
+/*
+ * 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
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.incubator.jpackage/jdk.incubator.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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,182 @@
+/*
+ * 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.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Map;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.stream.Collectors;
+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;
+import jdk.jpackage.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
+ * @key jpackagePlatformPackage
+ * @requires jpackage.test.SQETest == null
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile ShortcutHintTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=ShortcutHintTest
+ */
+
+/*
+ * @test
+ * @summary jpackage with --linux-shortcut
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "linux")
+ * @requires jpackage.test.SQETest != null
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile ShortcutHintTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=ShortcutHintTest.testBasic
+ */
+
+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() {
+        PackageTest test = createTest().addInitializer(
+                JPackageCommand::setFakeRuntime);
+        new FileAssociations("ShortcutHintTest_testFileAssociations").applyTo(
+                test);
+        test.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();
+    }
+
+    /**
+     * .desktop file from resource dir.
+     */
+    @Test
+    public static void testDesktopFileFromResourceDir() {
+        final String expectedVersionString = "Version=12345678";
+        TKit.withTempDirectory("resources", tempDir -> {
+            createTest().addInitializer(cmd -> {
+                cmd.setFakeRuntime();
+
+                cmd.addArgument("--linux-shortcut");
+                cmd.addArguments("--resource-dir", tempDir);
+
+                // Create custom .desktop file in resource directory
+                TKit.createTextFile(tempDir.resolve(cmd.name() + ".desktop"),
+                        List.of(
+                                "[Desktop Entry]",
+                                "Name=APPLICATION_NAME",
+                                "Exec=APPLICATION_LAUNCHER",
+                                "Terminal=false",
+                                "Type=Application",
+                                "Categories=DEPLOY_BUNDLE_CATEGORY",
+                                expectedVersionString
+                        ));
+            })
+            .addInstallVerifier(cmd -> {
+                Path desktopFile = cmd.appLayout().destktopIntegrationDirectory().resolve(
+                        String.format("%s-%s.desktop",
+                                LinuxHelper.getPackageName(cmd), cmd.name()));
+                TKit.assertFileExists(desktopFile);
+                TKit.assertTextStream(expectedVersionString)
+                        .label(String.format("[%s] file", desktopFile))
+                        .predicate(String::equals)
+                        .apply(Files.readAllLines(desktopFile).stream());
+            }).run();
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/MacPropertiesTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.MacHelper;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.Annotations.Parameter;
+
+
+/**
+ * Test --mac-package-name, --mac-package-identifier parameters.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --mac-package-name, --mac-package-identifier
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "mac")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile MacPropertiesTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=MacPropertiesTest
+ */
+public class MacPropertiesTest {
+    @Test
+    @Parameter("MacPackageNameTest")
+    public void testPackageName(String packageName) {
+        testParameterInAppImage("--mac-package-name", "CFBundleName",
+                packageName);
+    }
+
+    @Test
+    @Parameter("Foo")
+    public void testPackageIdetifier(String packageId) {
+        testParameterInAppImage("--mac-package-identifier", "CFBundleIdentifier",
+                packageId);
+    }
+
+    private static void testParameterInAppImage(String jpackageParameterName,
+            String plistKeyName, String value) {
+        JPackageCommand cmd = JPackageCommand.helloAppImage()
+                .addArguments(jpackageParameterName, value);
+
+        cmd.executeAndAssertHelloAppImageCreated();
+
+        var plist = MacHelper.readPListFromAppImage(cmd.outputBundle());
+
+        TKit.assertEquals(value, plist.queryValue(plistKeyName), String.format(
+                "Check value of %s plist key", plistKeyName));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/NameWithSpaceTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please 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.Annotations.Test;
+
+/**
+ * Name with space packaging test. Output of the test should be
+ * "Name With Space-*.*" package bundle.
+ *
+ * macOS only:
+ *
+ * Test should generates basic pkg and dmg. Name of packages and application itself
+ * should have name: "Name With Space". Package should be installed into "/Applications"
+ * folder and verified that it can be installed and run.
+ */
+
+/*
+ * @test
+ * @summary jpackage test with name containing spaces
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile NameWithSpaceTest.java
+ * @requires (os.family == "mac")
+ * @key jpackagePlatformPackage
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=NameWithSpaceTest
+ */
+public class NameWithSpaceTest {
+
+    @Test
+    public static void test() {
+        new PackageTest()
+        .configureHelloApp()
+        .addBundleDesktopIntegrationVerifier(false)
+        .addInitializer(cmd -> {
+            cmd.setArgumentValue("--name", "Name With Space");
+        })
+        .run();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/SigningAppImageTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,70 @@
+/*
+ * 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.nio.file.Path;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.TKit;
+
+/**
+ * Tests generation of app image with --mac-sign and related arguments. Test will
+ * generate app image and verify signature of main launcher and app bundle itself.
+ * This test requires that machine is configured with test certificate for
+ * "Developer ID Application: jpackage.openjdk.java.net" in jpackagerTest keychain with
+ * always allowed access to this keychain for user which runs test.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --type app-image --mac-sign
+ * @library ../helpers
+ * @library /test/lib
+ * @library base
+ * @build SigningBase
+ * @build SigningCheck
+ * @build jtreg.SkippedException
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @requires (os.family == "mac")
+ * @run main/othervm -Xmx512m SigningAppImageTest
+ */
+public class SigningAppImageTest {
+
+    public static void main(String[] args) throws Exception {
+        TKit.run(args, () -> {
+            SigningCheck.checkCertificates();
+
+            JPackageCommand cmd = JPackageCommand.helloAppImage();
+            cmd.addArguments("--mac-sign", "--mac-signing-key-user-name",
+                    SigningBase.DEV_NAME, "--mac-signing-keychain",
+                    "jpackagerTest.keychain");
+            cmd.executeAndAssertHelloAppImageCreated();
+
+            Path launcherPath = cmd.appLauncherPath();
+            SigningBase.verifyCodesign(launcherPath, true);
+
+            Path appImage = cmd.outputBundle();
+            SigningBase.verifyCodesign(appImage, true);
+            SigningBase.verifySpctl(appImage, "exec");
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/SigningPackageTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please 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.nio.file.Paths;
+import jdk.jpackage.test.*;
+
+/**
+ * Tests generation of dmg and pkg with --mac-sign and related arguments. Test will
+ * generate pkg and verifies its signature. It verifies that dmg is not signed, but app
+ * image inside dmg is signed. This test requires that machine is configured with test
+ * certificate for "Developer ID Installer: jpackage.openjdk.java.net" in jpackagerTest
+ * keychain with always allowed access to this keychain for user which runs test.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --type pkg,dmg --mac-sign
+ * @library ../helpers
+ * @library /test/lib
+ * @library base
+ * @key jpackagePlatformPackage
+ * @build SigningBase
+ * @build SigningCheck
+ * @build jtreg.SkippedException
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @requires (os.family == "mac")
+ * @run main/othervm -Xmx512m SigningPackageTest
+ */
+public class SigningPackageTest {
+
+    private static void verifyPKG(JPackageCommand cmd) {
+        Path outputBundle = cmd.outputBundle();
+        SigningBase.verifyPkgutil(outputBundle);
+        SigningBase.verifySpctl(outputBundle, "install");
+    }
+
+    private static void verifyDMG(JPackageCommand cmd) {
+        Path outputBundle = cmd.outputBundle();
+        SigningBase.verifyCodesign(outputBundle, false);
+    }
+
+    private static void verifyAppImageInDMG(JPackageCommand cmd) {
+        MacHelper.withExplodedDmg(cmd, dmgImage -> {
+            Path launcherPath = dmgImage.resolve(Path.of("Contents", "MacOS", cmd.name()));
+            SigningBase.verifyCodesign(launcherPath, true);
+            SigningBase.verifyCodesign(dmgImage, true);
+            SigningBase.verifySpctl(dmgImage, "exec");
+        });
+    }
+
+    public static void main(String[] args) throws Exception {
+        TKit.run(args, () -> {
+            SigningCheck.checkCertificates();
+
+            new PackageTest()
+                    .configureHelloApp()
+                    .forTypes(PackageType.MAC)
+                    .addInitializer(cmd -> {
+                        cmd.addArguments("--mac-sign",
+                                "--mac-signing-key-user-name", SigningBase.DEV_NAME,
+                                "--mac-signing-keychain", "jpackagerTest.keychain");
+                    })
+                    .forTypes(PackageType.MAC_PKG)
+                    .addBundleVerifier(SigningPackageTest::verifyPKG)
+                    .forTypes(PackageType.MAC_DMG)
+                    .addBundleVerifier(SigningPackageTest::verifyDMG)
+                    .addBundleVerifier(SigningPackageTest::verifyAppImageInDMG)
+                    .run();
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/base/SigningBase.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please 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.Executor;
+
+public class SigningBase {
+
+    public static String DEV_NAME = "jpackage.openjdk.java.net";
+    public static String APP_CERT
+            = "Developer ID Application: " + DEV_NAME;
+    public static String INSTALLER_CERT
+            = "Developer ID Installer: " + DEV_NAME;
+    public static String KEYCHAIN = "jpackagerTest.keychain";
+
+    private static void checkString(List<String> result, String lookupString) {
+        TKit.assertTextStream(lookupString).predicate(
+                (line, what) -> line.trim().equals(what)).apply(result.stream());
+    }
+
+    private static List<String> codesignResult(Path target, boolean signed) {
+        int exitCode = signed ? 0 : 1;
+        List<String> result = new Executor()
+                .setExecutable("codesign")
+                .addArguments("--verify", "--deep", "--strict", "--verbose=2",
+                        target.toString())
+                .saveOutput()
+                .execute()
+                .assertExitCodeIs(exitCode).getOutput();
+
+        return result;
+    }
+
+    private static void verifyCodesignResult(List<String> result, Path target,
+            boolean signed) {
+        result.stream().forEachOrdered(TKit::trace);
+        if (signed) {
+            String lookupString = target.toString() + ": valid on disk";
+            checkString(result, lookupString);
+            lookupString = target.toString() + ": satisfies its Designated Requirement";
+            checkString(result, lookupString);
+        } else {
+            String lookupString = target.toString()
+                    + ": code object is not signed at all";
+            checkString(result, lookupString);
+        }
+    }
+
+    private static List<String> spctlResult(Path target, String type) {
+        List<String> result = new Executor()
+                .setExecutable("/usr/sbin/spctl")
+                .addArguments("-vvv", "--assess", "--type", type,
+                        target.toString())
+                .executeAndGetOutput();
+
+        return result;
+    }
+
+    private static void verifySpctlResult(List<String> result, Path target, String type) {
+        result.stream().forEachOrdered(TKit::trace);
+        String lookupString = target.toString() + ": accepted";
+        checkString(result, lookupString);
+        lookupString = "source=" + DEV_NAME;
+        checkString(result, lookupString);
+        if (type.equals("install")) {
+            lookupString = "origin=" + INSTALLER_CERT;
+        } else {
+            lookupString = "origin=" + APP_CERT;
+        }
+        checkString(result, lookupString);
+    }
+
+    private static List<String> pkgutilResult(Path target) {
+        List<String> result = new Executor()
+                .setExecutable("/usr/sbin/pkgutil")
+                .addArguments("--check-signature",
+                        target.toString())
+                .executeAndGetOutput();
+
+        return result;
+    }
+
+    private static void verifyPkgutilResult(List<String> result) {
+        result.stream().forEachOrdered(TKit::trace);
+        String lookupString = "Status: signed by a certificate trusted for current user";
+        checkString(result, lookupString);
+        lookupString = "1. " + INSTALLER_CERT;
+        checkString(result, lookupString);
+    }
+
+    public static void verifyCodesign(Path target, boolean signed) {
+        List<String> result = codesignResult(target, signed);
+        verifyCodesignResult(result, target, signed);
+    }
+
+    public static void verifySpctl(Path target, String type) {
+        List<String> result = spctlResult(target, type);
+        verifySpctlResult(result, target, type);
+    }
+
+    public static void verifyPkgutil(Path target) {
+        List<String> result = pkgutilResult(target);
+        verifyPkgutilResult(result);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/macosx/base/SigningCheck.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,99 @@
+/*
+ * 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.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.Executor;
+
+import jdk.incubator.jpackage.internal.MacCertificate;
+
+public class SigningCheck {
+
+    public static void checkCertificates() {
+        List<String> result = findCertificate(SigningBase.APP_CERT, SigningBase.KEYCHAIN);
+        String key = findKey(SigningBase.APP_CERT, result);
+        validateCertificate(key);
+        validateCertificateTrust(SigningBase.APP_CERT);
+
+        result = findCertificate(SigningBase.INSTALLER_CERT, SigningBase.KEYCHAIN);
+        key = findKey(SigningBase.INSTALLER_CERT, result);
+        validateCertificate(key);
+        validateCertificateTrust(SigningBase.INSTALLER_CERT);
+    }
+
+    private static List<String> findCertificate(String name, String keyChain) {
+        List<String> result = new Executor()
+                .setExecutable("security")
+                .addArguments("find-certificate", "-c", name, "-a", keyChain)
+                .executeAndGetOutput();
+
+        return result;
+    }
+
+    private static String findKey(String name, List<String> result) {
+        Pattern p = Pattern.compile("\"alis\"<blob>=\"([^\"]+)\"");
+        Matcher m = p.matcher(result.stream().collect(Collectors.joining()));
+        if (!m.find()) {
+            TKit.trace("Did not found a key for '" + name + "'");
+            return null;
+        }
+        String matchedKey = m.group(1);
+        if (m.find()) {
+            TKit.trace("Found more than one key for '" + name + "'");
+            return null;
+        }
+        TKit.trace("Using key '" + matchedKey);
+        return matchedKey;
+    }
+
+    private static void validateCertificate(String key) {
+        if (key != null) {
+            MacCertificate certificate = new MacCertificate(key);
+            if (!certificate.isValid()) {
+                TKit.throwSkippedException("Certifcate expired: " + key);
+            } else {
+                return;
+            }
+        }
+
+        TKit.throwSkippedException("Cannot find required certifciates: " + key);
+    }
+
+    private static void validateCertificateTrust(String name) {
+        List<String> result = new Executor()
+                .setExecutable("security")
+                .addArguments("dump-trust-settings")
+                .executeAndGetOutput();
+        result.stream().forEachOrdered(TKit::trace);
+        TKit.assertTextStream(name)
+                .predicate((line, what) -> line.trim().endsWith(what))
+                .orElseThrow(() -> TKit.throwSkippedException(
+                        "Certifcate not trusted by current user: " + name))
+                .apply(result.stream());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/manage_packages.sh	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,231 @@
+#!/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
+}
+
+# For macOS
+if !(type "tac" &> /dev/null;) then
+    tac_cmd='tail -r'
+else
+    tac_cmd=tac
+fi
+
+# 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 pkg dmg)
+    for suffix in "${package_suffixes[@]}"; do
+        if [ "$mode" == "uninstall" ]; then
+            packages=$(find_packages_of_type $suffix | $tac_cmd)
+        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
+}
+
+# PKG
+install_cmd_pkg ()
+{
+    echo sudo /usr/sbin/installer -allowUntrusted -pkg "\"$@\"" -target /
+}
+uninstall_cmd_pkg ()
+{
+    local pname=`basename $@`
+    local appname="$(cut -d'-' -f1 <<<"$pname")"
+    if [ "$appname" = "CommonInstallDirTest" ]; then
+        echo sudo rm -rf "/Applications/jpackage/\"$appname.app\""
+    else
+        echo sudo rm -rf "/Applications/\"$appname.app\""
+    fi
+}
+
+# DMG
+install_cmd_dmg ()
+{
+    local pname=`basename $@`
+    local appname="$(cut -d'-' -f1 <<<"$pname")"
+    local command=()
+    if [ "$appname" = "CommonLicenseTest" ]; then
+        command+=("{" yes "|" hdiutil attach "\"$@\"" ">" /dev/null)
+    else
+        command+=("{" hdiutil attach "\"$@\"" ">" /dev/null)
+    fi
+
+    command+=(";" sudo cp -R "\"/Volumes/$appname/$appname.app\"" /Applications ">" /dev/null)
+    command+=(";" hdiutil detach "\"/Volumes/$appname\"" ">" /dev/null ";}")
+
+    echo "${command[@]}"
+}
+uninstall_cmd_dmg ()
+{
+    local pname=`basename $@`
+    local appname="$(cut -d'-' -f1 <<<"$pname")"
+    echo sudo rm -rf "/Applications/\"$appname.app\""
+}
+
+# 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
+IFS=$'\n'
+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
+        eval $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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,275 @@
+#!/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
+jpackage_test_selector=test/jdk/tools/jpackage
+
+
+find_packaging_tests ()
+{
+  (cd "$open_jdk_with_jpackage_jtreg_tests" && \
+    find "$jpackage_test_selector/$1" -type f -name '*.java' \
+    | xargs grep -E -l '@key[[:space:]]+jpackagePlatformPackage')
+}
+
+
+find_all_packaging_tests ()
+{
+  find_packaging_tests share
+  case "$(uname -s)" in
+    Darwin)
+      find_packaging_tests macosx;;
+    Linux)
+      find_packaging_tests linux;;
+    CYGWIN*|MINGW32*|MSYS*)
+      find_packaging_tests windows;;
+    *)
+      fatal Failed to detect OS type;;
+  esac
+}
+
+
+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 "  -a              - run all, not only SQE tests"
+  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 list of packaging tests 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
+}
+
+
+# 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
+
+# jtreg extra arguments
+declare -a jtreg_args
+
+# Run all tests
+run_all_tests=
+
+mapfile -t tests < <(find_all_packaging_tests)
+
+while getopts "vahdct:j:o:r:m:l:" argname; do
+  case "$argname" in
+    v) verbose=yes;;
+    a) run_all_tests=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 echo '$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
+  jtreg_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")"
+  jtreg_args+=("-Djpackage.test.logfile=$(to_native_path "$logfile")")
+fi
+
+if [ "$mode" = create ]; then
+  true
+elif [ "$mode" = update ]; then
+  true
+elif [ "$mode" = verify-install ]; then
+  jtreg_args+=("-Djpackage.test.action=$mode")
+elif [ "$mode" = verify-uninstall ]; then
+  jtreg_args+=("-Djpackage.test.action=$mode")
+else
+  fatal_with_help_usage 'Invalid value of -m option:' [$mode]
+fi
+
+if [ -z "$run_all_tests" ]; then
+  jtreg_args+=(-Djpackage.test.SQETest=yes)
+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")" \
+    "${jtreg_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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage
+ * @run main/othervm -Xmx512m AddLauncherModuleTest
+ */
+public class AddLauncherModuleTest {
+    private static final String OUTPUT = "output";
+    private static final String [] CMD = {
+        "--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 Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage
+ * @run main/othervm -Xmx512m AddLauncherTest
+ */
+public class AddLauncherTest {
+    private static final String OUTPUT = "output";
+    private static final String [] CMD = {
+        "--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 = {
+        "--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 Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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",
+        "--type", "app-image",
+        "--input", "input",
+        "--dest", OUTPUT,
+        "--name", "test",
+        "--main-jar", "hello.jar",
+        "--main-class", "Hello",
+        "--module-path", "module",
+        "--add-modules", "com.hello,java.desktop",
+        "--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",
+        "--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/AdditionalLaunchersTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.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.Annotations.Test;
+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
+ * @key jpackagePlatformPackage
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile AdditionalLaunchersTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=AdditionalLaunchersTest
+ */
+
+public class AdditionalLaunchersTest {
+
+    @Test
+    public void test() {
+        // 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();
+        packageTest.addInitializer(cmd -> {
+            cmd.addArguments("--arguments", "Duke", "--arguments", "is",
+                    "--arguments", "the", "--arguments", "King");
+        });
+
+        new FileAssociations(
+                MethodHandles.lookup().lookupClass().getSimpleName()).applyTo(
+                packageTest);
+
+        new AdditionalLauncher("Baz2").setArguments().applyTo(packageTest);
+        new AdditionalLauncher("foo").setArguments("yep!").applyTo(packageTest);
+
+        AdditionalLauncher barLauncher = new AdditionalLauncher("Bar").setArguments(
+                "one", "two", "three");
+        if (TKit.isLinux()) {
+            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);
+        } else {
+            fname = newFileName;
+        }
+        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.appLauncherPath(), name);
+
+                TKit.assertExecutableFileExists(launcherPath);
+
+                if (cmd.isFakeRuntime(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.appLauncherPath(), 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 Nov 12 10:18:11 2019 -0500
@@ -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.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
+ * @key jpackagePlatformPackage
+ * @library ../helpers
+ * @requires (jpackage.test.SQETest == null)
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @run main/othervm/timeout=540 -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("--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/ArgumentsTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,93 @@
+/*
+ * 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.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile ArgumentsTest.java
+ * @run main/othervm -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=ArgumentsTest
+ */
+public class ArgumentsTest {
+
+    @BeforeEach
+    public static void useJPackageToolProvider() {
+        JPackageCommand.useToolProviderByDefault();
+    }
+
+    @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.appLauncherPath();
+        if (!cmd.isFakeRuntime(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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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 = {
+        "--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 = {
+        "--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,
+            "--type", "app-image", ARG1), EXPECTED1, true);
+        validate(JPackageHelper.executeToolProvider(false,
+            "--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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,87 @@
+/*
+ * 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.FileAssociations;
+import jdk.jpackage.test.Annotations.Test;
+
+/**
+ * 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.
+ *
+ * Icon associated with the main launcher should be associated with files with
+ * ".jptest1" suffix. Different icon should be associated with files with with
+ * ".jptest2" suffix. Icon for files with ".jptest1" suffix is platform specific
+ * and is one of 'icon.*' files in test/jdk/tools/jpackage/resources directory.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --file-associations
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile FileAssociationsTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=FileAssociationsTest
+ */
+public class FileAssociationsTest {
+    @Test
+    public static void test() {
+        PackageTest packageTest = new PackageTest();
+
+        // Not supported
+        packageTest.excludeTypes(PackageType.MAC_DMG);
+
+        new FileAssociations("jptest1").applyTo(packageTest);
+
+        Path icon = TKit.TEST_SRC_ROOT.resolve(Path.of("resources", "icon"
+                + TKit.ICON_SUFFIX));
+
+        icon = TKit.createRelativePathCopy(icon);
+
+        new FileAssociations("jptest2")
+                .setFilename("fa2")
+                .setIcon(icon)
+                .applyTo(packageTest);
+
+        packageTest.run();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/IconTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,93 @@
+/*
+ * 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 jdk.incubator.jpackage.internal.IOUtils;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.Functional;
+import jdk.jpackage.test.Annotations.*;
+import jdk.jpackage.test.JPackageCommand;
+
+/*
+ * @test
+ * @summary jpackage create image with custom icon
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile IconTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=IconTest
+ */
+
+public class IconTest {
+    @Test
+    public static void testResourceDir() throws IOException {
+        TKit.withTempDirectory("resources", tempDir -> {
+            JPackageCommand cmd = JPackageCommand.helloAppImage()
+                    .addArguments("--resource-dir", tempDir);
+
+            Files.copy(GOLDEN_ICON, tempDir.resolve(appIconFileName(cmd)),
+                    StandardCopyOption.REPLACE_EXISTING);
+
+            testIt(cmd);
+        });
+    }
+
+    @Test
+    @Parameter("true")
+    @Parameter("false")
+    public static void testParameter(boolean relativePath) throws IOException {
+        final Path iconPath;
+        if (relativePath) {
+            iconPath = TKit.createRelativePathCopy(GOLDEN_ICON);
+        } else {
+            iconPath = GOLDEN_ICON;
+        }
+
+        testIt(JPackageCommand.helloAppImage().addArguments("--icon", iconPath));
+    }
+
+    private static String appIconFileName(JPackageCommand cmd) {
+        return IOUtils.replaceSuffix(cmd.appLauncherPath().getFileName(),
+                TKit.ICON_SUFFIX).toString();
+    }
+
+    private static void testIt(JPackageCommand cmd) throws IOException {
+        cmd.executeAndAssertHelloAppImageCreated();
+
+        Path iconPath = cmd.appLayout().destktopIntegrationDirectory().resolve(
+                appIconFileName(cmd));
+
+        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 Path GOLDEN_ICON = TKit.TEST_SRC_ROOT.resolve(Path.of(
+            "resources", "icon" + TKit.ICON_SUFFIX));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/InstallDirTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,138 @@
+/*
+ * 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
+ * commoninstalldirtest*.* 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/commoninstalldirtest folder.
+ *
+ * Mac:
+ *
+ * Application should be installed in /Applications/jpackage/commoninstalldirtest.app
+ * folder.
+ *
+ * Windows:
+ *
+ * Application should be installed in %ProgramFiles%/TestVendor/InstallDirTest1234
+ * folder.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --install-dir
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @compile InstallDirTest.java
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @run main/othervm/timeout=540 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=InstallDirTest.testCommon
+ */
+
+/*
+ * @test
+ * @summary jpackage with --install-dir
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @compile InstallDirTest.java
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @requires (os.family == "linux")
+ * @requires (jpackage.test.SQETest == null)
+ * @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("/Applications/jpackage"));
+
+            return reply;
+        }).get();
+
+        new PackageTest().excludeTypes(PackageType.MAC_DMG).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 Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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,
+                "--type", "app-image", ARG1);
+        validate(ARG1, output);
+
+        output = JPackageHelper.executeCLI(false,
+                "--type", "app-image", ARG2);
+        validate(ARG2, output);
+    }
+
+    private static void testInvalidArgToolProvider() throws Exception {
+        String output = JPackageHelper.executeToolProvider(false,
+                "--type", "app-image", ARG1);
+        validate(ARG1, output);
+
+        output = JPackageHelper.executeToolProvider(false,
+                "--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 Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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.incubator.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 = {
+        "--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 Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage
+ * @run main/othervm -Xmx512m JavaOptionsModuleTest
+ */
+public class JavaOptionsModuleTest {
+    private static final String OUTPUT = "output";
+
+    private static final String[] CMD = {
+        "--type", "app-image",
+        "--dest", OUTPUT,
+        "--name", "test",
+        "--module", "com.hello/com.hello.Hello",
+        "--module-path", "input",
+        "--java-options", "TBD"};
+
+    private static final String[] CMD2 = {
+        "--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 Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage
+ * @run main/othervm -Xmx512m JavaOptionsTest
+ */
+public class JavaOptionsTest {
+    private static final String OUTPUT = "output";
+
+    private static final String[] CMD = {
+        "--type", "app-image",
+        "--input", "input",
+        "--dest", OUTPUT,
+        "--name", "test",
+        "--main-jar", "hello.jar",
+        "--main-class", "Hello",
+        "--java-options", "TBD"};
+
+    private static final String[] CMD2 = {
+        "--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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,281 @@
+/*
+ * 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 commonlicensetest*.*
+ * 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 /opt/commonlicensetest/share/doc/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
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @compile LicenseTest.java
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=LicenseTest.testCommon
+ */
+
+/*
+ * @test
+ * @summary jpackage with --license-file
+ * @library ../helpers
+ * @key jpackagePlatformPackage
+ * @compile LicenseTest.java
+ * @requires (os.family == "linux")
+ * @requires (jpackage.test.SQETest == null)
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @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 -> {
+            cmd.addArguments("--license-file", TKit.createRelativePathCopy(
+                    LICENSE_FILE));
+        })
+        .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/MissingArgumentsTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.incubator.jpackage
+ * @run main/othervm -Xmx512m MissingArgumentsTest
+ */
+
+public class MissingArgumentsTest {
+    private static final String [] RESULT_1 = {"--input"};
+    private static final String [] CMD_1 = {
+        "--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 = {
+        "--type", "app-image",
+        "--type", "invalid-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 = {
+        "--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 = {
+        "--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 = {
+        "--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 = {
+        "--type", "invalid-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/RuntimePackageTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,107 @@
+/*
+ * 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.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import jdk.jpackage.test.*;
+import jdk.jpackage.test.Annotations.Test;
+
+/**
+ * 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
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @comment Temporary disable for Linux and OSX until functionality implemented
+ * @requires (os.family != "mac")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile RuntimePackageTest.java
+ * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=RuntimePackageTest
+ */
+public class RuntimePackageTest {
+
+    @Test
+    public static void test() {
+        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");
+        })
+        .addInstallVerifier(cmd -> {
+            Set<Path> srcRuntime = listFiles(Path.of(cmd.getArgumentValue("--runtime-image")));
+            Set<Path> dstRuntime = listFiles(cmd.appRuntimeDirectory());
+
+            Set<Path> intersection = new HashSet<>(srcRuntime);
+            intersection.retainAll(dstRuntime);
+
+            srcRuntime.removeAll(intersection);
+            dstRuntime.removeAll(intersection);
+
+            assertFileListEmpty(srcRuntime, "Missing");
+            assertFileListEmpty(dstRuntime, "Unexpected");
+        })
+        .run();
+    }
+
+    private static Set<Path> listFiles(Path root) throws IOException {
+        try (var files = Files.walk(root)) {
+            return files.map(root::relativize).collect(Collectors.toSet());
+        }
+    }
+
+    private static void assertFileListEmpty(Set<Path> paths, String msg) {
+        TKit.assertTrue(paths.isEmpty(), String.format(
+                "Check there are no %s files in installed image",
+                msg.toLowerCase()), () -> {
+            String msg2 = String.format("%s %d files", msg, paths.size());
+            TKit.trace(msg2 + ":");
+            paths.stream().map(Path::toString).sorted().forEachOrdered(
+                    TKit::trace);
+            TKit.trace("Done");
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/SimplePackageTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.Annotations.Test;
+
+/**
+ * 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
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile SimplePackageTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=SimplePackageTest
+ */
+public class SimplePackageTest {
+
+    @Test
+    public static void test() {
+        new PackageTest()
+        .configureHelloApp()
+        .addBundleDesktopIntegrationVerifier(false)
+        .run();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/jdk/jpackage/tests/AppVersionTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please 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.util.Collection;
+import java.util.List;
+import jdk.jpackage.test.Annotations.Parameters;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.TKit;
+
+/*
+ * @test
+ * @summary jpackage application version testing
+ * @library ../../../../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile AppVersionTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=jdk.jpackage.tests.AppVersionTest
+ */
+
+public final class AppVersionTest {
+
+    @Parameters
+    public static Collection input() {
+        return List.of(new Object[][]{
+            // Default jpackage version
+            {"1.0", "Hello", null},
+            {"1.0", "com.other/com.other.Hello", null},
+            // Version should be picked from --app-version
+            {"3.1", "Hello", new String[]{"--app-version", "3.1"}},
+            {"3.2", "com.other/com.other.Hello", new String[]{"--app-version",
+                "3.2"}},
+            // Version should be picked from the last --app-version
+            {"3.3", "Hello", new String[]{"--app-version", "4", "--app-version",
+                "3.3"}},
+            {"7.8", "com.other/com.other.Hello", new String[]{"--app-version",
+                "4", "--app-version", "7.8"}},
+            // Pick version from jar
+            {"3.10.17", "com.other/com.other.Hello@3.10.17", null},
+            // Ignore version in jar if --app-version given
+            {"7.5.81", "com.other/com.other.Hello@3.10.17", new String[]{
+                "--app-version", "7.5.81"}}
+        });
+    }
+
+    public AppVersionTest(String expectedVersion, String javaAppDesc,
+            String[] jpackageArgs) {
+        this.expectedVersion = expectedVersion;
+
+        cmd = JPackageCommand.helloAppImage(javaAppDesc);
+        if (jpackageArgs != null) {
+            cmd.addArguments(jpackageArgs);
+        }
+    }
+
+    @Test
+    public void test() {
+        cmd.executeAndAssertHelloAppImageCreated();
+        String actualVersion = cmd.readLaunherCfgFile().getValue("Application",
+                "app.version");
+        TKit.assertEquals(expectedVersion, actualVersion,
+                "Check application version");
+    }
+
+    private final String expectedVersion;
+    private final JPackageCommand cmd;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/jdk/jpackage/tests/BasicTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,348 @@
+/*
+ * 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.function.Function;
+import java.util.function.Predicate;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.*;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import jdk.jpackage.test.Annotations.*;
+
+/*
+ * @test
+ * @summary jpackage basic testing
+ * @library ../../../../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile BasicTest.java
+ * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=jdk.jpackage.tests.BasicTest
+ */
+
+public final class BasicTest {
+    @Test
+    public void testNoArgs() {
+        List<String> output =
+                getJPackageToolProvider().executeAndGetOutput();
+        TKit.assertStringListEquals(List.of("Usage: jpackage <options>",
+                "Use jpackage --help (or -h) for a list of possible options"),
+                output, "Check jpackage output");
+    }
+
+    @Test
+    public void testVersion() {
+        List<String> output =
+                getJPackageToolProvider()
+                        .addArgument("--version")
+                        .executeAndGetOutput();
+        TKit.assertStringListEquals(List.of(System.getProperty("java.version")),
+                output, "Check jpackage output");
+    }
+
+    @Test
+    public void testHelp() {
+        List<String> hOutput = getJPackageToolProvider()
+                .addArgument("-h").executeAndGetOutput();
+        List<String> helpOutput = getJPackageToolProvider()
+                .addArgument("--help").executeAndGetOutput();
+
+        TKit.assertStringListEquals(hOutput, helpOutput,
+                "Check -h and --help parameters produce the same output");
+
+        final String windowsPrefix = "--win-";
+        final String linuxPrefix = "--linux-";
+        final String osxPrefix = "--mac-";
+
+        final String expectedPrefix;
+        final List<String> unexpectedPrefixes;
+
+        if (TKit.isWindows()) {
+            expectedPrefix = windowsPrefix;
+            unexpectedPrefixes = List.of(osxPrefix, linuxPrefix);
+        } else if (TKit.isLinux()) {
+            expectedPrefix = linuxPrefix;
+            unexpectedPrefixes = List.of(windowsPrefix, osxPrefix);
+        } else if (TKit.isOSX()) {
+            expectedPrefix = osxPrefix;
+            unexpectedPrefixes = List.of(linuxPrefix,  windowsPrefix);
+        } else {
+            throw TKit.throwUnknownPlatformError();
+        }
+
+        Function<String, Predicate<String>> createPattern = (prefix) -> {
+            return Pattern.compile("^  " + prefix).asPredicate();
+        };
+
+        Function<List<String>, Long> countStrings = (prefixes) -> {
+            return hOutput.stream().filter(
+                    prefixes.stream().map(createPattern).reduce(x -> false,
+                            Predicate::or)).peek(TKit::trace).count();
+        };
+
+        TKit.trace("Check parameters in help text");
+        TKit.assertNotEquals(0, countStrings.apply(List.of(expectedPrefix)),
+                "Check help text contains plaform specific parameters");
+        TKit.assertEquals(0, countStrings.apply(unexpectedPrefixes),
+                "Check help text doesn't contain unexpected parameters");
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testVerbose() {
+        JPackageCommand cmd = JPackageCommand.helloAppImage()
+                .setFakeRuntime().executePrerequisiteActions();
+
+        List<String> expectedVerboseOutputStrings = new ArrayList<>();
+        expectedVerboseOutputStrings.add("Creating app package:");
+        if (TKit.isWindows()) {
+            expectedVerboseOutputStrings.add("Result application bundle:");
+            expectedVerboseOutputStrings.add(
+                    "Succeeded in building Windows Application Image package");
+        } else if (TKit.isLinux()) {
+            expectedVerboseOutputStrings.add(
+                    "Succeeded in building Linux Application Image package");
+        } else if (TKit.isOSX()) {
+            expectedVerboseOutputStrings.add("Preparing Info.plist:");
+            expectedVerboseOutputStrings.add(
+                    "Succeeded in building Mac Application Image package");
+        } else {
+            TKit.throwUnknownPlatformError();
+        }
+
+        TKit.deleteDirectoryContentsRecursive(cmd.outputDir());
+        List<String> nonVerboseOutput = cmd.createExecutor().executeAndGetOutput();
+        List<String>[] verboseOutput = (List<String>[])new List<?>[1];
+
+        // Directory clean up is not 100% reliable on Windows because of
+        // antivirus software that can lock .exe files. Setup
+        // diffreent output directory instead of cleaning the default one for
+        // verbose jpackage run.
+        TKit.withTempDirectory("verbose-output", tempDir -> {
+            cmd.setArgumentValue("--dest", tempDir);
+            verboseOutput[0] = cmd.createExecutor().addArgument(
+                    "--verbose").executeAndGetOutput();
+        });
+
+        TKit.assertTrue(nonVerboseOutput.size() < verboseOutput[0].size(),
+                "Check verbose output is longer than regular");
+
+        expectedVerboseOutputStrings.forEach(str -> {
+            TKit.assertTextStream(str).label("regular output")
+                    .predicate(String::contains).negate()
+                    .apply(nonVerboseOutput.stream());
+        });
+
+        expectedVerboseOutputStrings.forEach(str -> {
+            TKit.assertTextStream(str).label("verbose output")
+                    .apply(verboseOutput[0].stream());
+        });
+    }
+
+    @Test
+    public void testNoName() {
+        final String mainClassName = "Greetings";
+
+        JPackageCommand cmd = 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.outputBundle().toAbsolutePath().normalize().toString(),
+                String.format(
+                        "Check [%s] directory is filled with application image data",
+                        expectedImageDir));
+    }
+
+    @Test
+     // Regular app
+    @Parameter("Hello")
+    // Modular app
+    @Parameter("com.other/com.other.Hello")
+    public void testApp(String javaAppDesc) {
+        JPackageCommand.helloAppImage(javaAppDesc)
+        .executeAndAssertHelloAppImageCreated();
+    }
+
+    @Test
+    public void testWhitespaceInPaths() {
+        JPackageCommand.helloAppImage("a/b c.jar:Hello")
+        .setArgumentValue("--input", TKit.workDir().resolve("The quick brown fox"))
+        .setArgumentValue("--dest", TKit.workDir().resolve("jumps over the lazy dog"))
+        .executeAndAssertHelloAppImageCreated();
+    }
+
+    @Test
+    @Parameter("ALL-MODULE-PATH")
+    @Parameter("ALL-DEFAULT")
+    @Parameter("java.desktop")
+    @Parameter("java.desktop,jdk.jartool")
+    @Parameter({ "java.desktop", "jdk.jartool" })
+    public void testAddModules(String... addModulesArg) {
+        JPackageCommand cmd = JPackageCommand
+                .helloAppImage("goodbye.jar:com.other/com.other.Hello");
+        Stream.of(addModulesArg).map(v -> Stream.of("--add-modules", v)).flatMap(
+                s -> s).forEachOrdered(cmd::addArgument);
+        cmd.executeAndAssertHelloAppImageCreated();
+    }
+
+    /**
+     * Test --temp option. Doesn't make much sense for app image as temporary
+     * directory is used only on Windows. Test it in packaging mode.
+     * @throws IOException
+     */
+    @Test
+    public void testTemp() throws IOException {
+        TKit.withTempDirectory("temp-root", tempRoot -> {
+            Function<JPackageCommand, Path> getTempDir = cmd -> {
+                return tempRoot.resolve(cmd.outputBundle().getFileName());
+            };
+
+            ThrowingConsumer<JPackageCommand> addTempDir = cmd -> {
+                Path tempDir = getTempDir.apply(cmd);
+                Files.createDirectories(tempDir);
+                cmd.addArguments("--temp", tempDir);
+            };
+
+            new PackageTest().configureHelloApp().addInitializer(addTempDir)
+            .addBundleVerifier(cmd -> {
+                // Check jpackage actually used the supplied directory.
+                Path tempDir = getTempDir.apply(cmd);
+                TKit.assertNotEquals(0, tempDir.toFile().list().length,
+                        String.format(
+                                "Check jpackage wrote some data in the supplied temporary directory [%s]",
+                                tempDir));
+            })
+            .run();
+
+            new PackageTest().configureHelloApp().addInitializer(addTempDir)
+            .addInitializer(cmd -> {
+                // Clean output from the previus jpackage run.
+                Files.delete(cmd.outputBundle());
+            })
+            // Temporary directory should not be empty,
+            // jpackage should exit with error.
+            .setExpectedExitCode(1)
+            .run();
+        });
+    }
+
+    @Test
+    public void testAtFile() throws IOException {
+        JPackageCommand cmd = 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.
+        getJPackageToolProvider()
+                .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 = 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", tempDir -> {
+            final Path runtimeDir = tempDir.resolve("data");
+
+            // List of modules required for test app.
+            final var modules = new String[] {
+                "java.base",
+                "java.desktop"
+            };
+
+            Executor jlink = getToolProvider(JavaTool.JLINK)
+            .saveOutput(false)
+            .addArguments(
+                    "--add-modules", String.join(",", modules),
+                    "--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();
+        });
+    }
+
+    private static Executor getJPackageToolProvider() {
+        return getToolProvider(JavaTool.JPACKAGE);
+    }
+
+    private static Executor getToolProvider(JavaTool tool) {
+        return new Executor().dumpOutput().saveOutput().setToolProvider(tool);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/jdk/jpackage/tests/MainClassTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,316 @@
+/*
+ * 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.util.Collection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.jar.JarFile;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.nio.file.Path;
+import java.util.function.Predicate;
+import java.util.jar.JarEntry;
+import jdk.jpackage.test.Annotations.Parameters;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.*;
+import jdk.jpackage.test.Functional.ThrowingConsumer;
+import static jdk.jpackage.tests.MainClassTest.Script.MainClassType.*;
+
+
+/*
+ * @test
+ * @summary test different settings of main class name for jpackage
+ * @library ../../../../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile MainClassTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=jdk.jpackage.tests.MainClassTest
+ */
+
+public final class MainClassTest {
+
+    static final class Script {
+        Script() {
+            appDesc = JavaAppDesc.parse("test.Hello");
+        }
+
+        Script modular(boolean v) {
+            appDesc.setModuleName(v ? "com.other" : null);
+            return this;
+        }
+
+        Script withJLink(boolean v) {
+            withJLink = v;
+            return this;
+        }
+
+        Script withMainClass(MainClassType v) {
+            mainClass = v;
+            return this;
+        }
+
+        Script withJarMainClass(MainClassType v) {
+            appDesc.setJarWithMainClass(v != NotSet);
+            jarMainClass = v;
+            return this;
+        }
+
+        Script expectedErrorMessage(String v) {
+            expectedErrorMessage = v;
+            return this;
+        }
+
+        @Override
+        public String toString() {
+            return Stream.of(
+                    format("modular", appDesc.moduleName() != null ? 'y' : 'n'),
+                    format("main-class", mainClass),
+                    format("jar-main-class", jarMainClass),
+                    format("jlink", withJLink ? 'y' : 'n'),
+                    format("error", expectedErrorMessage)
+            ).filter(Objects::nonNull).collect(Collectors.joining("; "));
+        }
+
+        private static String format(String key, Object value) {
+            if (value == null) {
+                return null;
+            }
+            return String.join("=", key, value.toString());
+        }
+
+        enum MainClassType {
+            NotSet("n"),
+            SetWrong("b"),
+            SetRight("y");
+
+            MainClassType(String label) {
+                this.label = label;
+            }
+
+            @Override
+            public String toString() {
+                return label;
+            }
+
+            private final String label;
+        };
+
+        private JavaAppDesc appDesc;
+        private boolean withJLink;
+        private MainClassType mainClass;
+        private MainClassType jarMainClass;
+        private String expectedErrorMessage;
+    }
+
+    public MainClassTest(Script script) {
+        this.script = script;
+
+        nonExistingMainClass = Stream.of(
+                script.appDesc.packageName(), "ThereIsNoSuchClass").filter(
+                Objects::nonNull).collect(Collectors.joining("."));
+
+        cmd = JPackageCommand
+                .helloAppImage(script.appDesc)
+                .ignoreDefaultRuntime(true);
+        if (!script.withJLink) {
+            cmd.addArguments("--runtime-image", Path.of(System.getProperty(
+                    "java.home")));
+        }
+
+        final String moduleName = script.appDesc.moduleName();
+        switch (script.mainClass) {
+            case NotSet:
+                if (moduleName != null) {
+                    // Don't specify class name, only module name.
+                    cmd.setArgumentValue("--module", moduleName);
+                } else {
+                    cmd.removeArgumentWithValue("--main-class");
+                }
+                break;
+
+            case SetWrong:
+                if (moduleName != null) {
+                    cmd.setArgumentValue("--module",
+                            String.join("/", moduleName, nonExistingMainClass));
+                } else {
+                    cmd.setArgumentValue("--main-class", nonExistingMainClass);
+                }
+        }
+    }
+
+    @Parameters
+    public static Collection scripts() {
+        final var withMainClass = Set.of(SetWrong, SetRight);
+
+        List<Script[]> scripts = new ArrayList<>();
+        for (var withJLink : List.of(true, false)) {
+            for (var modular : List.of(true, false)) {
+                for (var mainClass : Script.MainClassType.values()) {
+                    for (var jarMainClass : Script.MainClassType.values()) {
+                        Script script = new Script()
+                            .modular(modular)
+                            .withJLink(withJLink)
+                            .withMainClass(mainClass)
+                            .withJarMainClass(jarMainClass);
+
+                        if (withMainClass.contains(jarMainClass)
+                                || withMainClass.contains(mainClass)) {
+                        } else if (modular) {
+                            script.expectedErrorMessage(
+                                    "Error: Main application class is missing");
+                        } else {
+                            script.expectedErrorMessage(
+                                    "A main class was not specified nor was one found in the jar");
+                        }
+
+                        scripts.add(new Script[]{script});
+                    }
+                }
+            }
+        }
+        return scripts;
+    }
+
+    @Test
+    public void test() throws IOException {
+        if (script.jarMainClass == SetWrong) {
+            initJarWithWrongMainClass();
+        }
+
+        if (script.expectedErrorMessage != null) {
+            // This is the case when main class is not found nor in jar
+            // file nor on command line.
+            List<String> output = cmd
+                    .saveConsoleOutput(true)
+                    .execute()
+                    .assertExitCodeIs(1)
+                    .getOutput();
+            TKit.assertTextStream(script.expectedErrorMessage).apply(output.stream());
+            return;
+        }
+
+        // Get here only if main class is specified.
+        boolean appShouldSucceed = false;
+
+        // Should succeed if valid main class is set on the command line.
+        appShouldSucceed |= (script.mainClass == SetRight);
+
+        // Should succeed if main class is not set on the command line but set
+        // to valid value in the jar.
+        appShouldSucceed |= (script.mainClass == NotSet && script.jarMainClass == SetRight);
+
+        if (appShouldSucceed) {
+            cmd.executeAndAssertHelloAppImageCreated();
+        } else {
+            cmd.executeAndAssertImageCreated();
+            if (!cmd.isFakeRuntime(String.format("Not running [%s]",
+                    cmd.appLauncherPath()))) {
+                List<String> output = new Executor()
+                    .setDirectory(cmd.outputDir())
+                    .setExecutable(cmd.appLauncherPath())
+                    .dumpOutput().saveOutput()
+                    .execute().assertExitCodeIs(1).getOutput();
+                TKit.assertTextStream(String.format(
+                        "Error: Could not find or load main class %s",
+                        nonExistingMainClass)).apply(output.stream());
+            }
+        }
+    }
+
+    private void initJarWithWrongMainClass() throws IOException {
+        // Call JPackageCommand.executePrerequisiteActions() to build app's jar.
+        // executePrerequisiteActions() is called by JPackageCommand instance
+        // only once.
+        cmd.executePrerequisiteActions();
+
+        final Path jarFile;
+        if (script.appDesc.moduleName() != null) {
+            jarFile = Path.of(cmd.getArgumentValue("--module-path"),
+                    script.appDesc.jarFileName());
+        } else {
+            jarFile = cmd.inputDir().resolve(cmd.getArgumentValue("--main-jar"));
+        }
+
+        // Create new jar file filtering out main class from the old jar file.
+        TKit.withTempDirectory("repack-jar", workDir -> {
+            // Extract app's class from the old jar.
+            explodeJar(jarFile, workDir,
+                    jarEntry -> Path.of(jarEntry.getName()).equals(
+                            script.appDesc.classFilePath()));
+
+            // Create app's jar file with different main class.
+            var badAppDesc = JavaAppDesc.parse(script.appDesc.toString()).setClassName(
+                    nonExistingMainClass);
+            JPackageCommand.helloAppImage(badAppDesc).executePrerequisiteActions();
+
+            // Extract new jar but skip app's class.
+            explodeJar(jarFile, workDir,
+                    jarEntry -> !Path.of(jarEntry.getName()).equals(
+                            badAppDesc.classFilePath()));
+
+            // At this point we should have:
+            // 1. Manifest from the new jar referencing non-existing class
+            //  as the main class.
+            // 2. Module descriptor referencing non-existing class as the main
+            //  class in case of modular app.
+            // 3. App's class from the old jar. We need it to let jlink find some
+            //  classes in the package declared in module descriptor
+            //  in case of modular app.
+
+            Files.delete(jarFile);
+            new Executor().setToolProvider(JavaTool.JAR)
+            .addArguments("-v", "-c", "-M", "-f", jarFile.toString())
+            .addArguments("-C", workDir.toString(), ".")
+            .dumpOutput()
+            .execute().assertExitCodeIsZero();
+        });
+    }
+
+    private static void explodeJar(Path jarFile, Path workDir,
+            Predicate<JarEntry> filter) throws IOException {
+        try (var jar = new JarFile(jarFile.toFile())) {
+            jar.stream()
+            .filter(Predicate.not(JarEntry::isDirectory))
+            .filter(filter)
+            .sequential().forEachOrdered(ThrowingConsumer.toConsumer(
+                jarEntry -> {
+                    try (var in = jar.getInputStream(jarEntry)) {
+                        Path fileName = workDir.resolve(jarEntry.getName());
+                        Files.createDirectories(fileName.getParent());
+                        Files.copy(in, fileName);
+                    }
+                }));
+        }
+    }
+
+    private final JPackageCommand cmd;
+    private final Script script;
+    private final String nonExistingMainClass;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/share/jdk/jpackage/tests/ModulePathTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,137 @@
+/*
+ * 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.File;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import jdk.jpackage.test.Annotations.Parameters;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.JPackageCommand;
+import jdk.jpackage.test.TKit;
+
+
+/*
+ * @test
+ * @summary jpackage with --module-path testing
+ * @library ../../../../helpers
+ * @build jdk.jpackage.test.*
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile ModulePathTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=jdk.jpackage.tests.ModulePathTest
+ */
+
+public final class ModulePathTest {
+
+    @Parameters
+    public static Collection data() {
+        return List.of(new String[][]{
+            {GOOD_PATH, EMPTY_DIR, NON_EXISTING_DIR},
+            {EMPTY_DIR, NON_EXISTING_DIR, GOOD_PATH},
+            {GOOD_PATH + "/a/b/c/d", GOOD_PATH},
+            {String.join(File.pathSeparator, EMPTY_DIR, NON_EXISTING_DIR,
+                GOOD_PATH)},
+            {String.join(File.pathSeparator, EMPTY_DIR, NON_EXISTING_DIR),
+                String.join(File.pathSeparator, EMPTY_DIR, NON_EXISTING_DIR,
+                GOOD_PATH)},
+            {},
+            {EMPTY_DIR}
+        });
+    }
+
+    public ModulePathTest(String... modulePathArgs) {
+        this.modulePathArgs = List.of(modulePathArgs);
+    }
+
+    @Test
+    public void test() {
+        final String moduleName = "com.foo";
+        JPackageCommand cmd = JPackageCommand.helloAppImage(
+                "benvenuto.jar:" + moduleName + "/com.foo.Hello");
+        // Build app jar file.
+        cmd.executePrerequisiteActions();
+
+        // Ignore runtime that can be set for all tests. Usually if default
+        // runtime is set, it is fake one to save time on running jlink and
+        // copying megabytes of data from Java home to application image.
+        // We need proper runtime for this test.
+        cmd.ignoreDefaultRuntime(true);
+
+        // --module-path should be set in JPackageCommand.helloAppImage call
+        String goodModulePath = Objects.requireNonNull(cmd.getArgumentValue(
+                "--module-path"));
+        cmd.removeArgumentWithValue("--module-path");
+        TKit.withTempDirectory("empty-dir", emptyDir -> {
+            Path nonExistingDir = TKit.withTempDirectory("non-existing-dir",
+                    unused -> {
+                    });
+
+            Function<String, String> substitute = str -> {
+                String v = str;
+                v = v.replace(GOOD_PATH, goodModulePath);
+                v = v.replace(EMPTY_DIR, emptyDir.toString());
+                v = v.replace(NON_EXISTING_DIR, nonExistingDir.toString());
+                return v;
+            };
+
+            boolean withGoodPath = modulePathArgs.stream().anyMatch(
+                    s -> s.contains(GOOD_PATH));
+
+            cmd.addArguments(modulePathArgs.stream().map(arg -> Stream.of(
+                    "--module-path", substitute.apply(arg))).flatMap(s -> s).collect(
+                    Collectors.toList()));
+
+            if (withGoodPath) {
+                cmd.executeAndAssertHelloAppImageCreated();
+            } else {
+                final String expectedErrorMessage;
+                if (modulePathArgs.isEmpty()) {
+                    expectedErrorMessage = "Error: Missing argument: --runtime-image or --module-path";
+                } else {
+                    expectedErrorMessage = String.format(
+                            "Error: Module %s not found", moduleName);
+                }
+
+                List<String> output = cmd
+                        .saveConsoleOutput(true)
+                        .execute()
+                        .assertExitCodeIs(1)
+                        .getOutput();
+                TKit.assertTextStream(expectedErrorMessage).apply(output.stream());
+            }
+        });
+    }
+
+    private final List<String> modulePathArgs;
+
+    private final static String GOOD_PATH = "@GoodPath@";
+    private final static String EMPTY_DIR = "@EmptyDir@";
+    private final static String NON_EXISTING_DIR = "@NonExistingDir@";
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/test_jpackage.sh	Tue Nov 12 10:18:11 2019 -0500
@@ -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 Nov 12 10:18:11 2019 -0500
@@ -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.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.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @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.appLauncherPath(), 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 Nov 12 10:18:11 2019 -0500
@@ -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-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
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.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 Nov 12 10:18:11 2019 -0500
@@ -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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please 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.Annotations.Test;
+
+/**
+ * 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
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile WinMenuGroupTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=WinMenuGroupTest
+ */
+
+public class WinMenuGroupTest {
+    @Test
+    public static void test() {
+        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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,57 @@
+/*
+ * 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.Annotations.Test;
+
+/**
+ * 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
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile WinMenuTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=WinMenuTest
+ */
+
+public class WinMenuTest {
+    @Test
+    public static void test() {
+        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 Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,63 @@
+/*
+ * 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.Annotations.Test;
+
+/**
+ * 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
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile WinPerUserInstallTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=WinPerUserInstallTest
+ */
+
+public class WinPerUserInstallTest {
+    @Test
+    public static void test() {
+        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/WinResourceTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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.io.IOException;
+import java.nio.file.Path;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.Annotations.Parameters;
+import java.util.List;
+
+/**
+ * Test --resource-dir option. The test should set --resource-dir to point to
+ * a dir with an empty "main.wxs" file.  As a result, jpackage should try to
+ * use the customized resource and fail.
+ */
+
+/*
+ * @test
+ * @summary jpackage with --resource-dir
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile WinResourceTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=WinResourceTest
+ */
+
+public class WinResourceTest {
+
+    public WinResourceTest(String wixSource, String expectedLogMessage) {
+         this.wixSource = wixSource;
+         this.expectedLogMessage = expectedLogMessage;
+    }
+
+    @Parameters
+    public static List<Object[]> data() {
+        return List.of(new Object[][]{
+            {"main.wxs", "Using custom package resource [Main WiX project file]"},
+            {"overrides.wxi", "Using custom package resource [Overrides WiX project file]"},
+        });
+    }
+
+    @Test
+    public void test() throws IOException {
+        new PackageTest()
+        .forTypes(PackageType.WINDOWS)
+        .configureHelloApp()
+        .addInitializer(cmd -> {
+            Path resourceDir = TKit.createTempDirectory("resources");
+
+            // 1. Set fake run time to save time by skipping jlink step of jpackage.
+            // 2. Instruct test to save jpackage output.
+            cmd.setFakeRuntime().saveConsoleOutput(true);
+
+            cmd.addArguments("--resource-dir", resourceDir);
+            // Create invalid WiX source file in a resource dir.
+            TKit.createTextFile(resourceDir.resolve(wixSource), List.of(
+                    "any string that is an invalid WiX source file"));
+        })
+        .addBundleVerifier((cmd, result) -> {
+            // Assert jpackage picked custom main.wxs and failed as expected by
+            // examining its output
+            TKit.assertTextStream(expectedLogMessage)
+                    .predicate(String::startsWith)
+                    .apply(result.getOutput().stream());
+            TKit.assertTextStream("error CNDL0104 : Not a valid source file")
+                    .apply(result.getOutput().stream());
+        })
+        .setExpectedExitCode(1)
+        .run();
+    }
+
+    final String wixSource;
+    final String expectedLogMessage;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinScriptTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -0,0 +1,174 @@
+/*
+ * 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.Path;
+import java.util.List;
+import java.util.ArrayList;
+import jdk.incubator.jpackage.internal.IOUtils;
+import jdk.jpackage.test.TKit;
+import jdk.jpackage.test.PackageTest;
+import jdk.jpackage.test.PackageType;
+import jdk.jpackage.test.Annotations.Test;
+import jdk.jpackage.test.Annotations.Parameter;
+import jdk.jpackage.test.Annotations.Parameters;
+import jdk.jpackage.test.JPackageCommand;
+
+/*
+ * @test usage of scripts from resource dir
+ * @summary jpackage with
+ * @library ../helpers
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile WinScriptTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=WinScriptTest
+ */
+
+public class WinScriptTest {
+
+    public WinScriptTest(PackageType type) {
+        this.packageType = type;
+
+        test = new PackageTest()
+        .forTypes(type)
+        .configureHelloApp()
+        .addInitializer(cmd -> {
+            cmd.setFakeRuntime().saveConsoleOutput(true);
+        });
+    }
+
+    @Parameters
+    public static List<Object[]> data() {
+        return List.of(new Object[][]{
+            {PackageType.WIN_MSI},
+            {PackageType.WIN_EXE}
+        });
+    }
+
+    @Test
+    @Parameter("0")
+    @Parameter("10")
+    public void test(int wsfExitCode) {
+        final ScriptData appImageScriptData;
+        if (wsfExitCode != 0 && packageType == PackageType.WIN_EXE) {
+            appImageScriptData = new ScriptData(PackageType.WIN_MSI, 0);
+        } else {
+            appImageScriptData = new ScriptData(PackageType.WIN_MSI, wsfExitCode);
+        }
+
+        final ScriptData msiScriptData = new ScriptData(PackageType.WIN_EXE, wsfExitCode);
+
+        test.setExpectedExitCode(wsfExitCode == 0 ? 0 : 1);
+        TKit.withTempDirectory("resources", tempDir -> {
+            test.addInitializer(cmd -> {
+                cmd.addArguments("--resource-dir", tempDir);
+
+                appImageScriptData.createScript(cmd);
+                msiScriptData.createScript(cmd);
+            });
+
+            if (packageType == PackageType.WIN_MSI) {
+                test.addBundleVerifier((cmd, result) -> {
+                    appImageScriptData.assertJPackageOutput(result.getOutput());
+                });
+            }
+
+            if (packageType == PackageType.WIN_EXE) {
+                test.addBundleVerifier((cmd, result) -> {
+                    appImageScriptData.assertJPackageOutput(result.getOutput());
+                    msiScriptData.assertJPackageOutput(result.getOutput());
+                });
+            }
+
+            test.run();
+        });
+    }
+
+    private static class ScriptData {
+        ScriptData(PackageType scriptType, int wsfExitCode) {
+            if (scriptType == PackageType.WIN_MSI) {
+                echoText = "post app image wsf";
+                envVarName = "JpAppImageDir";
+                scriptSuffixName = "post-image";
+            } else {
+                echoText = "post msi wsf";
+                envVarName = "JpMsiFile";
+                scriptSuffixName = "post-msi";
+            }
+            this.wsfExitCode = wsfExitCode;
+        }
+
+        void assertJPackageOutput(List<String> output) {
+            TKit.assertTextStream(String.format("jp: %s", echoText))
+                    .predicate(String::equals)
+                    .apply(output.stream());
+
+            String cwdPattern = String.format("jp: CWD(%s)=", envVarName);
+            TKit.assertTextStream(cwdPattern)
+                    .predicate(String::startsWith)
+                    .apply(output.stream());
+            String cwd = output.stream().filter(line -> line.startsWith(
+                    cwdPattern)).findFirst().get().substring(cwdPattern.length());
+
+            String envVarPattern = String.format("jp: %s=", envVarName);
+            TKit.assertTextStream(envVarPattern)
+                    .predicate(String::startsWith)
+                    .apply(output.stream());
+            String envVar = output.stream().filter(line -> line.startsWith(
+                    envVarPattern)).findFirst().get().substring(envVarPattern.length());
+
+            TKit.assertTrue(envVar.startsWith(cwd), String.format(
+                    "Check value of %s environment variable [%s] starts with the current directory [%s] set for %s script",
+                    envVarName, envVar, cwd, echoText));
+        }
+
+        void createScript(JPackageCommand cmd) throws IOException {
+           IOUtils.createXml(Path.of(cmd.getArgumentValue("--resource-dir"),
+                    String.format("%s-%s.wsf", cmd.name(), scriptSuffixName)), xml -> {
+                xml.writeStartElement("job");
+                xml.writeAttribute("id", "main");
+                xml.writeStartElement("script");
+                xml.writeAttribute("language", "JScript");
+                xml.writeCData(String.join("\n", List.of(
+                    "var shell = new ActiveXObject('WScript.Shell')",
+                    "WScript.Echo('jp: " + envVarName + "=' + shell.ExpandEnvironmentStrings('%" + envVarName + "%'))",
+                    "WScript.Echo('jp: CWD(" + envVarName + ")=' + shell.CurrentDirectory)",
+                    String.format("WScript.Echo('jp: %s')", echoText),
+                    String.format("WScript.Quit(%d)", wsfExitCode)
+                )));
+                xml.writeEndElement();
+                xml.writeEndElement();
+            });
+        }
+
+        private final int wsfExitCode;
+        private final String scriptSuffixName;
+        private final String echoText;
+        private final String envVarName;
+    }
+
+    private final PackageType packageType;
+    private PackageTest test;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/tools/jpackage/windows/WinShortcutTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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;
+import jdk.jpackage.test.Annotations.Test;
+
+/**
+ * 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
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.jpackage.internal
+ * @compile WinShortcutTest.java
+ * @run main/othervm/timeout=360 -Xmx512m jdk.jpackage.test.Main
+ *  --jpt-run=WinShortcutTest
+ */
+
+public class WinShortcutTest {
+    @Test
+    public static void test() {
+        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 Nov 12 10:18:11 2019 -0500
@@ -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.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
+ * @key jpackagePlatformPackage
+ * @build jdk.jpackage.test.*
+ * @requires (os.family == "windows")
+ * @modules jdk.incubator.jpackage/jdk.incubator.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	Fri Nov 08 11:15:16 2019 +0000
+++ b/test/jdk/tools/launcher/HelpFlagsTest.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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	Fri Nov 08 11:15:16 2019 +0000
+++ b/test/jdk/tools/launcher/VersionCheck.java	Tue Nov 12 10:18:11 2019 -0500
@@ -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",