changeset 57245:9f9e7c969f78

8212780: Packaging Tool Implementation Reviewed-by: asemenyuk, almatvee, herrick, kcr, prr, erikj, ihse, rriggs, mchung, alanb Contributed-by: alexey.semenyuk@oracle.com, alexander.matveev@oracle.com, andy.herrick@oracle.com, kevin.rushforth@oracle.com, philip.race@oracle.com
author herrick
date Thu, 05 Dec 2019 11:25:33 -0500
parents 0a94d3675782
children 1d3c5da689d0
files make/CompileJavaModules.gmk make/common/Modules.gmk make/common/NativeCompilation.gmk make/launcher/Launcher-jdk.incubator.jpackage.gmk make/lib/Lib-jdk.incubator.jpackage.gmk src/java.base/share/classes/module-info.java src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/DesktopIntegration.java src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LibProvidersLookup.java src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LinuxAppBundler.java src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LinuxAppImageBuilder.java src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LinuxDebBundler.java src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LinuxPackageBundler.java src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/LinuxRpmBundler.java src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/PackageProperty.java src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/LinuxResources.properties src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/LinuxResources_ja.properties src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/LinuxResources_zh_CN.properties src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/java32.png src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.control src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.copyright src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.desktop src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.postinst src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.postrm src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.preinst src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.prerm src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/template.spec src/jdk.incubator.jpackage/linux/classes/jdk/incubator/jpackage/internal/resources/utils.sh src/jdk.incubator.jpackage/linux/classes/module-info.java.extra src/jdk.incubator.jpackage/linux/native/jpackageapplauncher/launcher.cpp src/jdk.incubator.jpackage/linux/native/libapplauncher/LinuxPlatform.cpp src/jdk.incubator.jpackage/linux/native/libapplauncher/LinuxPlatform.h src/jdk.incubator.jpackage/linux/native/libapplauncher/PlatformDefs.h src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/EnumeratedBundlerParam.java src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacAppBundler.java src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacAppImageBuilder.java src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacAppStoreBundler.java src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacBaseInstallerBundler.java src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacCertificate.java src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacDmgBundler.java src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/MacPkgBundler.java src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/DMGsetup.scpt src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/Info-lite.plist.template src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/MacAppStore.entitlements src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/MacAppStore_Inherit.entitlements src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/MacResources.properties src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/MacResources_ja.properties src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/MacResources_zh_CN.properties src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/Runtime-Info.plist.template src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/background_dmg.png src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/background_pkg.png src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/java.icns src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/lic_template.plist src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/postinstall.template src/jdk.incubator.jpackage/macosx/classes/jdk/incubator/jpackage/internal/resources/preinstall.template src/jdk.incubator.jpackage/macosx/classes/module-info.java.extra src/jdk.incubator.jpackage/macosx/native/jpackageapplauncher/main.m src/jdk.incubator.jpackage/macosx/native/libapplauncher/MacPlatform.h src/jdk.incubator.jpackage/macosx/native/libapplauncher/MacPlatform.mm src/jdk.incubator.jpackage/macosx/native/libapplauncher/PlatformDefs.h src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/AbstractAppImageBuilder.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/AbstractBundler.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/AbstractImageBundler.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/AddLauncherArguments.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/AppImageFile.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ApplicationLayout.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ArgAction.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Arguments.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/BasicBundlers.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/BundleParams.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Bundler.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/BundlerParamInfo.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Bundlers.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/CLIHelp.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ConfigException.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/DeployParams.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/DottedVersion.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Executor.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/FileAssociation.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/I18N.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/IOUtils.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/JLinkBundlerHelper.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/JPackageToolProvider.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Log.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ModFile.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/OverridableResource.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/PackagerException.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/PathGroup.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/Platform.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/PlatformPackage.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/RelativeFileSet.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ScriptRunner.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/StandardBundlerParam.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ToolValidator.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/ValidOptions.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/HelpResources.properties src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/HelpResources_ja.properties src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/HelpResources_zh_CN.properties src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/MainResources.properties src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/MainResources_ja.properties src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/MainResources_zh_CN.properties src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/internal/resources/ResourceLocator.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/main/CommandLine.java src/jdk.incubator.jpackage/share/classes/jdk/incubator/jpackage/main/Main.java src/jdk.incubator.jpackage/share/classes/module-info.java src/jdk.incubator.jpackage/share/native/libapplauncher/FileAttributes.h src/jdk.incubator.jpackage/share/native/libapplauncher/FilePath.h src/jdk.incubator.jpackage/share/native/libapplauncher/Helpers.cpp src/jdk.incubator.jpackage/share/native/libapplauncher/Helpers.h src/jdk.incubator.jpackage/share/native/libapplauncher/IniFile.cpp src/jdk.incubator.jpackage/share/native/libapplauncher/IniFile.h src/jdk.incubator.jpackage/share/native/libapplauncher/JavaVirtualMachine.cpp src/jdk.incubator.jpackage/share/native/libapplauncher/JavaVirtualMachine.h src/jdk.incubator.jpackage/share/native/libapplauncher/Library.cpp src/jdk.incubator.jpackage/share/native/libapplauncher/Library.h src/jdk.incubator.jpackage/share/native/libapplauncher/Macros.cpp src/jdk.incubator.jpackage/share/native/libapplauncher/Macros.h src/jdk.incubator.jpackage/share/native/libapplauncher/Messages.cpp src/jdk.incubator.jpackage/share/native/libapplauncher/Messages.h src/jdk.incubator.jpackage/share/native/libapplauncher/OrderedMap.h src/jdk.incubator.jpackage/share/native/libapplauncher/Package.cpp src/jdk.incubator.jpackage/share/native/libapplauncher/Package.h src/jdk.incubator.jpackage/share/native/libapplauncher/Platform.cpp src/jdk.incubator.jpackage/share/native/libapplauncher/Platform.h src/jdk.incubator.jpackage/share/native/libapplauncher/PlatformString.cpp src/jdk.incubator.jpackage/share/native/libapplauncher/PlatformString.h src/jdk.incubator.jpackage/share/native/libapplauncher/Properties.h src/jdk.incubator.jpackage/share/native/libapplauncher/PropertyFile.cpp src/jdk.incubator.jpackage/share/native/libapplauncher/PropertyFile.h src/jdk.incubator.jpackage/share/native/libapplauncher/main.cpp src/jdk.incubator.jpackage/unix/native/libapplauncher/FileAttribute.h src/jdk.incubator.jpackage/unix/native/libapplauncher/FileAttributes.cpp src/jdk.incubator.jpackage/unix/native/libapplauncher/FilePath.cpp src/jdk.incubator.jpackage/unix/native/libapplauncher/PosixPlatform.cpp src/jdk.incubator.jpackage/unix/native/libapplauncher/PosixPlatform.h src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WinAppBundler.java src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WinExeBundler.java src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WinMsiBundler.java src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WindowsAppImageBuilder.java src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WindowsBundlerParam.java src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WindowsDefender.java src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WindowsRegistry.java src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WixPipeline.java src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WixSourcesBuilder.java src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/WixTool.java src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/MsiInstallerStrings_en.wxl src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/MsiInstallerStrings_ja.wxl src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/MsiInstallerStrings_zh_CN.wxl src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinLauncher.template src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinResources.properties src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinResources_ja.properties src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/WinResources_zh_CN.properties src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/java48.ico src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/main.wxs src/jdk.incubator.jpackage/windows/classes/jdk/incubator/jpackage/internal/resources/overrides.wxi src/jdk.incubator.jpackage/windows/classes/module-info.java.extra src/jdk.incubator.jpackage/windows/native/jpackageapplauncher/WinLauncher.cpp src/jdk.incubator.jpackage/windows/native/libapplauncher/DllMain.cpp src/jdk.incubator.jpackage/windows/native/libapplauncher/FileAttribute.h src/jdk.incubator.jpackage/windows/native/libapplauncher/FilePath.cpp src/jdk.incubator.jpackage/windows/native/libapplauncher/PlatformDefs.h src/jdk.incubator.jpackage/windows/native/libapplauncher/WindowsPlatform.cpp src/jdk.incubator.jpackage/windows/native/libapplauncher/WindowsPlatform.h src/jdk.incubator.jpackage/windows/native/libjpackage/ByteBuffer.cpp src/jdk.incubator.jpackage/windows/native/libjpackage/ByteBuffer.h src/jdk.incubator.jpackage/windows/native/libjpackage/ErrorHandling.cpp src/jdk.incubator.jpackage/windows/native/libjpackage/ErrorHandling.h src/jdk.incubator.jpackage/windows/native/libjpackage/FileUtils.cpp src/jdk.incubator.jpackage/windows/native/libjpackage/FileUtils.h src/jdk.incubator.jpackage/windows/native/libjpackage/IconSwap.cpp src/jdk.incubator.jpackage/windows/native/libjpackage/IconSwap.h src/jdk.incubator.jpackage/windows/native/libjpackage/Log.cpp src/jdk.incubator.jpackage/windows/native/libjpackage/Log.h src/jdk.incubator.jpackage/windows/native/libjpackage/ResourceEditor.cpp src/jdk.incubator.jpackage/windows/native/libjpackage/ResourceEditor.h src/jdk.incubator.jpackage/windows/native/libjpackage/SourceCodePos.h src/jdk.incubator.jpackage/windows/native/libjpackage/SysInfo.h src/jdk.incubator.jpackage/windows/native/libjpackage/UniqueHandle.h src/jdk.incubator.jpackage/windows/native/libjpackage/Utils.cpp src/jdk.incubator.jpackage/windows/native/libjpackage/Utils.h src/jdk.incubator.jpackage/windows/native/libjpackage/VersionInfoSwap.cpp src/jdk.incubator.jpackage/windows/native/libjpackage/VersionInfoSwap.h src/jdk.incubator.jpackage/windows/native/libjpackage/WinErrorHandling.cpp src/jdk.incubator.jpackage/windows/native/libjpackage/WinErrorHandling.h src/jdk.incubator.jpackage/windows/native/libjpackage/WinSysInfo.cpp src/jdk.incubator.jpackage/windows/native/libjpackage/WinSysInfo.h src/jdk.incubator.jpackage/windows/native/libjpackage/WindowsRegistry.cpp src/jdk.incubator.jpackage/windows/native/libjpackage/jpackage.cpp src/jdk.incubator.jpackage/windows/native/libjpackage/tstrings.cpp src/jdk.incubator.jpackage/windows/native/libjpackage/tstrings.h src/jdk.incubator.jpackage/windows/native/libwixhelper/libwixhelper.cpp src/jdk.incubator.jpackage/windows/native/msiwrapper/Executor.cpp src/jdk.incubator.jpackage/windows/native/msiwrapper/Executor.h src/jdk.incubator.jpackage/windows/native/msiwrapper/MsiWrapper.cpp src/jdk.incubator.jpackage/windows/native/msiwrapper/Resources.cpp src/jdk.incubator.jpackage/windows/native/msiwrapper/Resources.h src/jdk.jlink/share/classes/jdk/tools/jlink/internal/packager/AppRuntimeImageBuilder.java test/jdk/tools/jpackage/TEST.properties test/jdk/tools/jpackage/apps/com.hello/com/hello/Hello.java test/jdk/tools/jpackage/apps/com.hello/module-info.java test/jdk/tools/jpackage/apps/com.other/com/other/Other.java test/jdk/tools/jpackage/apps/com.other/module-info.java test/jdk/tools/jpackage/apps/dukeplug.png test/jdk/tools/jpackage/apps/image/Hello.java test/jdk/tools/jpackage/apps/installer/Hello.java test/jdk/tools/jpackage/helpers/JPackageHelper.java test/jdk/tools/jpackage/helpers/JPackageInstallerHelper.java test/jdk/tools/jpackage/helpers/JPackagePath.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Annotations.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CfgFile.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/CommandArguments.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Executor.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/FileAssociations.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Functional.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/HelloApp.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JarBuilder.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JavaAppDesc.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JavaTool.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/LinuxHelper.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/Main.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MethodCall.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageTest.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/PackageType.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TKit.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestBuilder.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/TestInstance.java test/jdk/tools/jpackage/helpers/jdk/jpackage/test/WindowsHelper.java test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/AppImageFileTest.java test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/ApplicationLayoutTest.java test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/CompareDottedVersionTest.java test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/DeployParamsTest.java test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/DottedVersionTest.java test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/InvalidDottedVersionTest.java test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/OverridableResourceTest.java test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/PathGroupTest.java test/jdk/tools/jpackage/junit/jdk/incubator/jpackage/internal/ToolValidatorTest.java test/jdk/tools/jpackage/junit/junit.java test/jdk/tools/jpackage/junit/run_junit.sh test/jdk/tools/jpackage/linux/AppCategoryTest.java test/jdk/tools/jpackage/linux/LicenseTypeTest.java test/jdk/tools/jpackage/linux/LinuxBundleNameTest.java test/jdk/tools/jpackage/linux/LinuxResourceTest.java test/jdk/tools/jpackage/linux/MaintainerTest.java test/jdk/tools/jpackage/linux/PackageDepsTest.java test/jdk/tools/jpackage/linux/ReleaseTest.java test/jdk/tools/jpackage/linux/ShortcutHintTest.java test/jdk/tools/jpackage/macosx/MacPropertiesTest.java test/jdk/tools/jpackage/macosx/NameWithSpaceTest.java test/jdk/tools/jpackage/macosx/SigningAppImageTest.java test/jdk/tools/jpackage/macosx/SigningPackageTest.java test/jdk/tools/jpackage/macosx/base/SigningBase.java test/jdk/tools/jpackage/macosx/base/SigningCheck.java test/jdk/tools/jpackage/manage_packages.sh test/jdk/tools/jpackage/resources/icon.icns test/jdk/tools/jpackage/resources/icon.ico test/jdk/tools/jpackage/resources/icon.png test/jdk/tools/jpackage/resources/license.txt test/jdk/tools/jpackage/run_tests.sh test/jdk/tools/jpackage/share/AddLauncherBase.java test/jdk/tools/jpackage/share/AddLauncherModuleTest.java test/jdk/tools/jpackage/share/AddLauncherTest.java test/jdk/tools/jpackage/share/AddLaunchersTest.java test/jdk/tools/jpackage/share/AdditionalLaunchersTest.java test/jdk/tools/jpackage/share/AppImagePackageTest.java test/jdk/tools/jpackage/share/ArgumentsTest.java test/jdk/tools/jpackage/share/Base.java test/jdk/tools/jpackage/share/ErrorTest.java test/jdk/tools/jpackage/share/FileAssociationsTest.java test/jdk/tools/jpackage/share/IconTest.java test/jdk/tools/jpackage/share/InstallDirTest.java test/jdk/tools/jpackage/share/InvalidArgTest.java test/jdk/tools/jpackage/share/JavaOptionsBase.java test/jdk/tools/jpackage/share/JavaOptionsEqualsTest.java test/jdk/tools/jpackage/share/JavaOptionsModuleTest.java test/jdk/tools/jpackage/share/JavaOptionsTest.java test/jdk/tools/jpackage/share/LicenseTest.java test/jdk/tools/jpackage/share/MissingArgumentsTest.java test/jdk/tools/jpackage/share/RuntimePackageTest.java test/jdk/tools/jpackage/share/SimplePackageTest.java test/jdk/tools/jpackage/share/jdk/jpackage/tests/AppVersionTest.java test/jdk/tools/jpackage/share/jdk/jpackage/tests/BasicTest.java test/jdk/tools/jpackage/share/jdk/jpackage/tests/MainClassTest.java test/jdk/tools/jpackage/share/jdk/jpackage/tests/ModulePathTest.java test/jdk/tools/jpackage/test_jpackage.sh test/jdk/tools/jpackage/windows/WinConsoleTest.java test/jdk/tools/jpackage/windows/WinDirChooserTest.java test/jdk/tools/jpackage/windows/WinMenuGroupTest.java test/jdk/tools/jpackage/windows/WinMenuTest.java test/jdk/tools/jpackage/windows/WinPerUserInstallTest.java test/jdk/tools/jpackage/windows/WinResourceTest.java test/jdk/tools/jpackage/windows/WinScriptTest.java test/jdk/tools/jpackage/windows/WinShortcutTest.java test/jdk/tools/jpackage/windows/WinUpgradeUUIDTest.java test/jdk/tools/launcher/HelpFlagsTest.java test/jdk/tools/launcher/VersionCheck.java
diffstat 296 files changed, 45687 insertions(+), 168 deletions(-) [+]
line wrap: on
line diff
--- a/make/CompileJavaModules.gmk	Thu Dec 05 15:45:58 2019 +0000
+++ b/make/CompileJavaModules.gmk	Thu Dec 05 11:25:33 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	Thu Dec 05 15:45:58 2019 +0000
+++ b/make/common/Modules.gmk	Thu Dec 05 11:25:33 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	Thu Dec 05 15:45:58 2019 +0000
+++ b/make/common/NativeCompilation.gmk	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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
--- a/src/java.base/share/classes/module-info.java	Thu Dec 05 15:45:58 2019 +0000
+++ b/src/java.base/share/classes/module-info.java	Thu Dec 05 11:25:33 2019 -0500
@@ -207,7 +207,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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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	Thu Dec 05 11:25:33 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>