1 /*
   2  * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.oracle.tools.packager.windows;
  27 
  28 import com.oracle.tools.packager.AbstractImageBundler;
  29 import com.oracle.tools.packager.BundlerParamInfo;
  30 import com.oracle.tools.packager.StandardBundlerParam;
  31 import com.oracle.tools.packager.Log;
  32 import com.oracle.tools.packager.ConfigException;
  33 import com.oracle.tools.packager.IOUtils;
  34 import com.oracle.tools.packager.RelativeFileSet;
  35 import com.oracle.tools.packager.UnsupportedPlatformException;
  36 
  37 import java.io.*;
  38 import java.net.MalformedURLException;
  39 import java.net.URL;
  40 import java.nio.file.Files;
  41 import java.text.MessageFormat;
  42 import java.util.*;
  43 import java.util.concurrent.atomic.AtomicReference;
  44 import java.util.regex.Pattern;
  45 
  46 import static com.oracle.tools.packager.StandardBundlerParam.*;
  47 import static com.oracle.tools.packager.windows.WindowsBundlerParam.BIT_ARCH_64;
  48 import static com.oracle.tools.packager.windows.WindowsBundlerParam.BIT_ARCH_64_RUNTIME;
  49 import static com.oracle.tools.packager.windows.WindowsBundlerParam.WIN_RUNTIME;
  50 
  51 public class WinAppBundler extends AbstractImageBundler {
  52 
  53     private static final ResourceBundle I18N =
  54             ResourceBundle.getBundle(WinAppBundler.class.getName());
  55 
  56     public static final BundlerParamInfo<File> CONFIG_ROOT = new WindowsBundlerParam<>(
  57             I18N.getString("param.config-root.name"),
  58             I18N.getString("param.config-root.description"),
  59             "configRoot",
  60             File.class,
  61             params -> {
  62                 File imagesRoot = new File(BUILD_ROOT.fetchFrom(params), "windows");
  63                 imagesRoot.mkdirs();
  64                 return imagesRoot;
  65             },
  66             (s, p) -> null);
  67 
  68     private final static String EXECUTABLE_NAME = "WinLauncher.exe";
  69     private final static String LIBRARY_NAME = "packager.dll";
  70 
  71     private final static String[] VS_VERS = {"100", "110", "120"};
  72     private final static String REDIST_MSVCR = "msvcrVS_VER.dll";
  73     private final static String REDIST_MSVCP = "msvcpVS_VER.dll";
  74 
  75     private static final String TOOL_ICON_SWAP="IconSwap.exe";
  76     private static final String TOOL_VERSION_INFO_SWAP="VersionInfoSwap.exe";
  77 
  78     private static final String EXECUTABLE_PROPERTIES_TEMPLATE = "WinLauncher.properties";
  79 
  80     public static final BundlerParamInfo<URL> RAW_EXECUTABLE_URL = new WindowsBundlerParam<>(
  81             I18N.getString("param.raw-executable-url.name"),
  82             I18N.getString("param.raw-executable-url.description"),
  83             "win.launcher.url",
  84             URL.class,
  85             params -> WinResources.class.getResource(EXECUTABLE_NAME),
  86             (s, p) -> {
  87                 try {
  88                     return new URL(s);
  89                 } catch (MalformedURLException e) {
  90                     Log.info(e.toString());
  91                     return null;
  92                 }
  93             });
  94 
  95     public static final BundlerParamInfo<Boolean> REBRAND_EXECUTABLE = new WindowsBundlerParam<>(
  96             I18N.getString("param.rebrand-executable.name"),
  97             I18N.getString("param.rebrand-executable.description"),
  98             "win.launcher.rebrand",
  99             Boolean.class,
 100             params -> Boolean.TRUE,
 101             (s, p) -> Boolean.valueOf(s));
 102 
 103     public static final BundlerParamInfo<File> ICON_ICO = new StandardBundlerParam<>(
 104             I18N.getString("param.icon-ico.name"),
 105             I18N.getString("param.icon-ico.description"),
 106             "icon.ico",
 107             File.class,
 108             params -> {
 109                 File f = ICON.fetchFrom(params);
 110                 if (f != null && !f.getName().toLowerCase().endsWith(".ico")) {
 111                     Log.info(MessageFormat.format(I18N.getString("message.icon-not-ico"), f));
 112                     return null;
 113                 }
 114                 return f;
 115             },
 116             (s, p) -> new File(s));
 117 
 118     public WinAppBundler() {
 119         super();
 120         baseResourceLoader = WinResources.class;
 121     }
 122 
 123     public final static String WIN_BUNDLER_PREFIX =
 124             BUNDLER_PREFIX + "windows/";
 125 
 126     File getConfigRoot(Map<String, ? super Object> params) {
 127         return CONFIG_ROOT.fetchFrom(params);
 128     }
 129 
 130     @Override
 131     public boolean validate(Map<String, ? super Object> params) throws UnsupportedPlatformException, ConfigException {
 132         try {
 133             if (params == null) throw new ConfigException(
 134                     I18N.getString("error.parameters-null"),
 135                     I18N.getString("error.parameters-null.advice"));
 136 
 137             return doValidate(params);
 138         } catch (RuntimeException re) {
 139             if (re.getCause() instanceof ConfigException) {
 140                 throw (ConfigException) re.getCause();
 141             } else {
 142                 throw new ConfigException(re);
 143             }
 144         }
 145     }
 146 
 147     //to be used by chained bundlers, e.g. by EXE bundler to avoid
 148     // skipping validation if p.type does not include "image"
 149     boolean doValidate(Map<String, ? super Object> p) throws UnsupportedPlatformException, ConfigException {
 150         if (!System.getProperty("os.name").toLowerCase().startsWith("win")) {
 151             throw new UnsupportedPlatformException();
 152         }
 153 
 154         imageBundleValidation(p);
 155 
 156         if (WinResources.class.getResource(TOOL_ICON_SWAP) == null ||
 157             WinResources.class.getResource(TOOL_VERSION_INFO_SWAP) == null)
 158         {
 159             throw new ConfigException(
 160                     I18N.getString("error.no-windows-resources"),
 161                     I18N.getString("error.no-windows-resources.advice"));
 162         }
 163 
 164         //validate required inputs
 165         testRuntime(WIN_RUNTIME.fetchFrom(p), new String[] {
 166                 "bin\\\\[^\\\\]+\\\\jvm.dll", // most reliable
 167                 "lib\\\\rt.jar", // fallback canary for JDK 8
 168         });
 169         if (USE_FX_PACKAGING.fetchFrom(p)) {
 170             testRuntime(WIN_RUNTIME.fetchFrom(p), new String[] {"lib\\\\ext\\\\jfxrt.jar", "lib\\\\jfxrt.jar"});
 171         }
 172 
 173         //validate runtime bit-architectire
 174         testRuntimeBitArchitecture(p);
 175 
 176         return true;
 177     }
 178 
 179     private static void testRuntimeBitArchitecture(Map<String, ? super Object> params) throws ConfigException {
 180         if ("true".equalsIgnoreCase(System.getProperty("fxpackager.disableBitArchitectureMismatchCheck"))) {
 181             Log.debug(I18N.getString("message.disable-bit-architecture-check"));
 182             return;
 183         }
 184 
 185         if ((BIT_ARCH_64.fetchFrom(params) != BIT_ARCH_64_RUNTIME.fetchFrom(params)) && !"systemjre".equals(params.get(".runtime.autodetect"))) {
 186             throw new ConfigException(
 187                     I18N.getString("error.bit-architecture-mismatch"),
 188                     I18N.getString("error.bit-architecture-mismatch.advice"));
 189         }
 190     }
 191 
 192     //it is static for the sake of sharing with "Exe" bundles
 193     // that may skip calls to validate/bundle in this class!
 194     private static File getRootDir(File outDir, Map<String, ? super Object> p) {
 195         return new File(outDir, APP_NAME.fetchFrom(p));
 196     }
 197 
 198     public static String getLauncherName(Map<String, ? super Object> p) {
 199         return APP_NAME.fetchFrom(p) +".exe";
 200     }
 201 
 202     public static String getLauncherCfgName(Map<String, ? super Object> p) {
 203         return "app\\" + APP_NAME.fetchFrom(p) +".cfg";
 204     }
 205 
 206     private File getConfig_AppIcon(Map<String, ? super Object> params) {
 207         return new File(getConfigRoot(params), APP_NAME.fetchFrom(params) + ".ico");
 208     }
 209 
 210     private File getConfig_ExecutableProperties(Map<String, ? super Object> params) {
 211         return new File(getConfigRoot(params), APP_NAME.fetchFrom(params) + ".properties");
 212     }
 213 
 214     private final static String TEMPLATE_APP_ICON ="javalogo_white_48.ico";
 215 
 216     //remove
 217     protected void cleanupConfigFiles(Map<String, ? super Object> params) {
 218         getConfig_AppIcon(params).delete();
 219         getConfig_ExecutableProperties(params).delete();
 220     }
 221 
 222     private void validateValueAndPut(Map<String, String> data, String key,
 223                                      BundlerParamInfo<String> param, Map<String, ? super Object> params)
 224     {
 225         String value = param.fetchFrom(params);
 226         if (value.contains("\r") || value.contains("\n")) {
 227             Log.info("Configuration Parameter " + param.getID() + " contains multiple lines of text, ignore it");
 228             data.put(key, "");
 229             return;
 230         }
 231         data.put(key, value);
 232     }
 233 
 234     protected void prepareExecutableProperties(Map<String, ? super Object> params)
 235         throws IOException
 236     {
 237         Map<String, String> data = new HashMap<>();
 238 
 239         // mapping Java parameters in strings for version resource
 240         data.put("COMMENTS", "");
 241         validateValueAndPut(data, "COMPANY_NAME", VENDOR, params);
 242         validateValueAndPut(data, "FILE_DESCRIPTION", DESCRIPTION, params);
 243         validateValueAndPut(data, "FILE_VERSION", VERSION, params);
 244         data.put("INTERNAL_NAME", getLauncherName(params));
 245         validateValueAndPut(data, "LEGAL_COPYRIGHT", COPYRIGHT, params);
 246         data.put("LEGAL_TRADEMARK", "");
 247         data.put("ORIGINAL_FILENAME", getLauncherName(params));
 248         data.put("PRIVATE_BUILD", "");
 249         validateValueAndPut(data, "PRODUCT_NAME", APP_NAME, params);
 250         validateValueAndPut(data, "PRODUCT_VERSION", VERSION, params);
 251         data.put("SPECIAL_BUILD", "");
 252 
 253         Writer w = new BufferedWriter(new FileWriter(getConfig_ExecutableProperties(params)));
 254         String content = preprocessTextResource(
 255                 WinAppBundler.WIN_BUNDLER_PREFIX + getConfig_ExecutableProperties(params).getName(),
 256                 I18N.getString("resource.executable-properties-template"), EXECUTABLE_PROPERTIES_TEMPLATE, data,
 257                 VERBOSE.fetchFrom(params),
 258                 DROP_IN_RESOURCES_ROOT.fetchFrom(params));
 259         w.write(content);
 260         w.close();
 261     }
 262 
 263     private void prepareConfigFiles(Map<String, ? super Object> params) throws IOException {
 264         File iconTarget = getConfig_AppIcon(params);
 265 
 266         File icon = ICON_ICO.fetchFrom(params);
 267         if (icon != null && icon.exists()) {
 268             fetchResource(WIN_BUNDLER_PREFIX + iconTarget.getName(),
 269                     I18N.getString("resource.application-icon"),
 270                     icon,
 271                     iconTarget,
 272                     VERBOSE.fetchFrom(params),
 273                     DROP_IN_RESOURCES_ROOT.fetchFrom(params));
 274         } else {
 275             fetchResource(WIN_BUNDLER_PREFIX + iconTarget.getName(),
 276                     I18N.getString("resource.application-icon"),
 277                     WinAppBundler.TEMPLATE_APP_ICON,
 278                     iconTarget,
 279                     VERBOSE.fetchFrom(params),
 280                     DROP_IN_RESOURCES_ROOT.fetchFrom(params));
 281         }
 282 
 283         prepareExecutableProperties(params);
 284     }
 285 
 286     public boolean bundle(Map<String, ? super Object> p, File outputDirectory) {
 287         return doBundle(p, outputDirectory, false) != null;
 288     }
 289 
 290     File doBundle(Map<String, ? super Object> p, File outputDirectory, boolean dependentTask) {
 291         Map<String, ? super Object> originalParams = new HashMap<>(p);
 292         if (!outputDirectory.isDirectory() && !outputDirectory.mkdirs()) {
 293             throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-create-output-dir"), outputDirectory.getAbsolutePath()));
 294         }
 295         if (!outputDirectory.canWrite()) {
 296             throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-write-to-output-dir"), outputDirectory.getAbsolutePath()));
 297         }
 298         try {
 299             if (!dependentTask) {
 300                 Log.info(MessageFormat.format(I18N.getString("message.creating-app-bundle"), APP_NAME.fetchFrom(p), outputDirectory.getAbsolutePath()));
 301             }
 302 
 303             // Create directory structure
 304             File rootDirectory = getRootDir(outputDirectory, p);
 305             IOUtils.deleteRecursive(rootDirectory);
 306             rootDirectory.mkdirs();
 307 
 308             File appDirectory = new File(rootDirectory, "app");
 309             appDirectory.mkdirs();
 310 
 311             // create the .exe launchers
 312             createLauncherForEntryPoint(p, rootDirectory);
 313 
 314             // copy the jars
 315             copyApplication(p, appDirectory);
 316 
 317             // Copy runtime
 318             File runtimeDirectory = new File(rootDirectory, "runtime");
 319             copyRuntime(p, runtimeDirectory);
 320 
 321             // copy in the needed libraries
 322             IOUtils.copyFromURL(
 323                     WinResources.class.getResource(LIBRARY_NAME),
 324                     new File(rootDirectory, LIBRARY_NAME));
 325 
 326             copyMSVCDLLs(rootDirectory, runtimeDirectory);
 327 
 328             // create the secondary launchers, if any
 329             List<Map<String, ? super Object>> entryPoints = StandardBundlerParam.SECONDARY_LAUNCHERS.fetchFrom(p);
 330             for (Map<String, ? super Object> entryPoint : entryPoints) {
 331                 Map<String, ? super Object> tmp = new HashMap<>(originalParams);
 332                 tmp.putAll(entryPoint);
 333                 createLauncherForEntryPoint(tmp, rootDirectory);
 334             }
 335 
 336             if (!dependentTask) {
 337                 Log.info(MessageFormat.format(I18N.getString("message.result-dir"), outputDirectory.getAbsolutePath()));
 338             }
 339 
 340             return rootDirectory;
 341         } catch (IOException ex) {
 342             Log.info("Exception: "+ex);
 343             Log.debug(ex);
 344             return null;
 345         } finally {
 346             if (VERBOSE.fetchFrom(p)) {
 347                 Log.info(MessageFormat.format(I18N.getString("message.config-save-location"), getConfigRoot(p).getAbsolutePath()));
 348             } else {
 349                 cleanupConfigFiles(p);
 350             }
 351         }
 352 
 353     }
 354 
 355     private void copyMSVCDLLs(File rootDirectory, File jreDir) throws IOException {
 356         String vsVer = null;
 357         if (jreDir == null || !jreDir.isDirectory()) {
 358             jreDir = new File(System.getProperty("java.home"));
 359         }
 360 
 361         // first copy the ones needed for the launcher
 362         for (String thisVer : VS_VERS) {
 363             if (copyMSVCDLLs(rootDirectory, thisVer)) {
 364                 vsVer = thisVer;
 365                 break;
 366             }
 367         }
 368         if (vsVer == null) {
 369             throw new RuntimeException("Not found MSVC dlls");
 370         }
 371 
 372         AtomicReference<IOException> ioe = new AtomicReference<>();
 373         final String finalVsVer = vsVer;
 374         Files.list(jreDir.toPath().resolve("bin"))
 375                 .filter(p -> Pattern.matches("msvc(r|p)\\d\\d\\d.dll", p.toFile().getName().toLowerCase()))
 376                 .filter(p -> !p.toString().toLowerCase().endsWith(finalVsVer + ".dll"))
 377                 .forEach(p -> {
 378                     try {
 379                         IOUtils.copyFile(p.toFile(), new File(rootDirectory, p.toFile().getName()));
 380                     } catch (IOException e) {
 381                         ioe.set(e);
 382                     }
 383                 });
 384 
 385         IOException e = ioe.get();
 386         if (e != null) {
 387             throw e;
 388         }
 389     }
 390 
 391     private boolean copyMSVCDLLs(File rootDirectory, String VS_VER) throws IOException {
 392         final URL REDIST_MSVCR_URL = WinResources.class.getResource(
 393                                               REDIST_MSVCR.replaceAll("VS_VER", VS_VER));
 394         final URL REDIST_MSVCP_URL = WinResources.class.getResource(
 395                 REDIST_MSVCP.replaceAll("VS_VER", VS_VER));
 396 
 397         if (REDIST_MSVCR_URL != null && REDIST_MSVCP_URL != null) {
 398             IOUtils.copyFromURL(
 399                     REDIST_MSVCR_URL,
 400                     new File(rootDirectory, REDIST_MSVCR.replaceAll("VS_VER", VS_VER)));
 401             IOUtils.copyFromURL(
 402                     REDIST_MSVCP_URL,
 403                     new File(rootDirectory, REDIST_MSVCP.replaceAll("VS_VER", VS_VER)));
 404             return true;
 405         }
 406 
 407         return false; // not found
 408     }
 409 
 410     private void createLauncherForEntryPoint(Map<String, ? super Object> p, File rootDirectory) throws IOException {
 411         prepareConfigFiles(p);
 412 
 413         // Generate launcher .cfg file
 414         if (LAUNCHER_CFG_FORMAT.fetchFrom(p).equals(CFG_FORMAT_PROPERTIES)) {
 415             writeCfgFile(p, rootDirectory);
 416         } else {
 417             writeCfgFile(p, new File(rootDirectory, getLauncherCfgName(p)), getRuntimeLocation(p));
 418         }
 419 
 420         // Copy executable root folder
 421         File executableFile = new File(rootDirectory, getLauncherName(p));
 422         IOUtils.copyFromURL(
 423                 RAW_EXECUTABLE_URL.fetchFrom(p),
 424                 executableFile);
 425         executableFile.setExecutable(true, false);
 426 
 427         //Update branding of exe file
 428         if (REBRAND_EXECUTABLE.fetchFrom(p) && getConfig_AppIcon(p).exists()) {
 429             //extract IconSwap helper tool
 430             File iconSwapTool = File.createTempFile("iconswap", ".exe");
 431             IOUtils.copyFromURL(
 432                     WinResources.class.getResource(TOOL_ICON_SWAP),
 433                     iconSwapTool,
 434                     true);
 435             iconSwapTool.setExecutable(true, false);
 436             iconSwapTool.deleteOnExit();
 437 
 438             //run it on launcher file
 439             executableFile.setWritable(true);
 440             ProcessBuilder pb = new ProcessBuilder(
 441                     iconSwapTool.getAbsolutePath(),
 442                     getConfig_AppIcon(p).getAbsolutePath(),
 443                     executableFile.getAbsolutePath());
 444             IOUtils.exec(pb, VERBOSE.fetchFrom(p));
 445             executableFile.setReadOnly();
 446             iconSwapTool.delete();
 447         }
 448 
 449         IOUtils.copyFile(getConfig_AppIcon(p),
 450                 new File(rootDirectory, APP_NAME.fetchFrom(p) + ".ico"));
 451 
 452         if (REBRAND_EXECUTABLE.fetchFrom(p) && getConfig_ExecutableProperties(p).exists()) {
 453             // extract VersionInfoHelper tool
 454             File versionInfoTool = File.createTempFile("versioninfoswap", ".exe");
 455             IOUtils.copyFromURL(
 456                     WinResources.class.getResource(TOOL_VERSION_INFO_SWAP),
 457                     versionInfoTool,
 458                     true);
 459             versionInfoTool.setExecutable(true, false);
 460             versionInfoTool.deleteOnExit();
 461 
 462             // run it on launcher file
 463             executableFile.setWritable(true);
 464             ProcessBuilder pb = new ProcessBuilder(
 465                     versionInfoTool.getAbsolutePath(),
 466                     getConfig_ExecutableProperties(p).getAbsolutePath(),
 467                     executableFile.getAbsolutePath());
 468             IOUtils.exec(pb, VERBOSE.fetchFrom(p));
 469             executableFile.setReadOnly();
 470             versionInfoTool.delete();
 471         }
 472     }
 473 
 474     private void copyApplication(Map<String, ? super Object> params, File appDirectory) throws IOException {
 475         List<RelativeFileSet> appResourcesList = APP_RESOURCES_LIST.fetchFrom(params);
 476         if (appResourcesList == null) {
 477             throw new RuntimeException("Null app resources?");
 478         }
 479         for (RelativeFileSet appResources : appResourcesList) {
 480             if (appResources == null) {
 481                 throw new RuntimeException("Null app resources?");
 482             }
 483             File srcdir = appResources.getBaseDirectory();
 484             for (String fname : appResources.getIncludedFiles()) {
 485                 IOUtils.copyFile(
 486                         new File(srcdir, fname), new File(appDirectory, fname));
 487             }
 488         }
 489     }
 490 
 491     private String getRuntimeLocation(Map<String, ? super Object> params) {
 492         if (WIN_RUNTIME.fetchFrom(params) == null) {
 493             return "";
 494         } else {
 495             return "$APPDIR\\runtime";
 496         }
 497     }
 498 
 499     private void writeCfgFile(Map<String, ? super Object> params, File rootDir) throws FileNotFoundException {
 500         File cfgFile = new File(rootDir, getLauncherCfgName(params));
 501 
 502         cfgFile.delete();
 503 
 504         PrintStream out = new PrintStream(cfgFile);
 505         out.println("app.runtime=" + getRuntimeLocation(params));
 506         out.println("app.mainjar=" + MAIN_JAR.fetchFrom(params).getIncludedFiles().iterator().next());
 507         out.println("app.version=" + VERSION.fetchFrom(params));
 508         //for future AU support (to be able to find app in the registry)
 509         out.println("app.id=" + IDENTIFIER.fetchFrom(params));
 510         out.println("app.preferences.id=" + PREFERENCES_ID.fetchFrom(params));
 511         out.println("app.identifier=" + IDENTIFIER.fetchFrom(params));
 512 
 513         out.println("app.mainclass=" +
 514                 MAIN_CLASS.fetchFrom(params).replaceAll("\\.", "/"));
 515         out.println("app.classpath=" + CLASSPATH.fetchFrom(params));
 516 
 517         List<String> jvmargs = JVM_OPTIONS.fetchFrom(params);
 518         int idx = 1;
 519         for (String a : jvmargs) {
 520             out.println("jvmarg."+idx+"="+a);
 521             idx++;
 522         }
 523         Map<String, String> jvmProps = JVM_PROPERTIES.fetchFrom(params);
 524         for (Map.Entry<String, String> entry : jvmProps.entrySet()) {
 525             out.println("jvmarg."+idx+"=-D"+entry.getKey()+"="+entry.getValue());
 526             idx++;
 527         }
 528 
 529         String preloader = PRELOADER_CLASS.fetchFrom(params);
 530         if (preloader != null) {
 531             out.println("jvmarg."+idx+"=-Djavafx.preloader="+preloader);
 532         }
 533 
 534         Map<String, String> overridableJVMOptions = USER_JVM_OPTIONS.fetchFrom(params);
 535         idx = 1;
 536         for (Map.Entry<String, String> arg: overridableJVMOptions.entrySet()) {
 537             if (arg.getKey() == null || arg.getValue() == null) {
 538                 Log.info(I18N.getString("message.jvm-user-arg-is-null"));
 539             }
 540             else {
 541                 out.println("jvmuserarg."+idx+".name="+arg.getKey());
 542                 out.println("jvmuserarg."+idx+".value="+arg.getValue());
 543             }
 544             idx++;
 545         }
 546 
 547         // add command line args
 548         List<String> args = ARGUMENTS.fetchFrom(params);
 549         idx = 1;
 550         for (String a : args) {
 551             out.println("arg."+idx+"="+a);
 552             idx++;
 553         }
 554 
 555         out.close();
 556     }
 557 
 558     private void copyRuntime(Map<String, ? super Object> params, File runtimeDirectory) throws IOException {
 559         RelativeFileSet runtime = WIN_RUNTIME.fetchFrom(params);
 560         if (runtime == null) {
 561             //its ok, request to use system JRE
 562             return;
 563         }
 564         runtimeDirectory.mkdirs();
 565 
 566         File srcdir = runtime.getBaseDirectory();
 567         Set<String> filesToCopy = runtime.getIncludedFiles();
 568         for (String fname : filesToCopy) {
 569             IOUtils.copyFile(
 570                     new File(srcdir, fname), new File(runtimeDirectory, fname));
 571         }
 572     }
 573 
 574     public void extractRuntimeFlags(Map<String, ? super Object> params) {
 575         extractFlagsFromRuntime(params);
 576     }
 577 
 578     public static void extractFlagsFromRuntime(Map<String, ? super Object> params) {
 579         if (params.containsKey(".runtime.autodetect")) return;
 580 
 581         params.put(".runtime.autodetect", "attempted");
 582         RelativeFileSet runtime = WIN_RUNTIME.fetchFrom(params);
 583         String commandline;
 584         if (runtime == null) {
 585             //System JRE, report nothing useful
 586             params.put(".runtime.autodetect", "systemjre");
 587         } else {
 588             File runtimePath = runtime.getBaseDirectory();
 589             File launcherPath = new File(runtimePath, "bin\\java");
 590 
 591             ProcessBuilder pb = new ProcessBuilder(launcherPath.getAbsolutePath(), "-version");
 592             try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
 593                 try (PrintStream pout = new PrintStream(baos)) {
 594                     IOUtils.exec(pb, Log.isDebug(), true, pout);
 595                 }
 596 
 597                 commandline = baos.toString();
 598             } catch (IOException e) {
 599                 e.printStackTrace();
 600                 params.put(".runtime.autodetect", "failed");
 601                 return;
 602             }
 603             AbstractImageBundler.extractFlagsFromVersion(params, commandline);
 604             params.put(".runtime.autodetect", "succeeded");
 605         }
 606     }
 607 
 608 
 609     @Override
 610     public String getName() {
 611         return I18N.getString("bundler.name");
 612     }
 613 
 614     @Override
 615     public String getDescription() {
 616         return I18N.getString("bundler.description");
 617     }
 618 
 619     @Override
 620     public String getID() {
 621         return "windows.app";
 622     }
 623 
 624     @Override
 625     public String getBundleType() {
 626         return "IMAGE";
 627     }
 628 
 629     @Override
 630     public Collection<BundlerParamInfo<?>> getBundleParameters() {
 631         return getAppBundleParameters();
 632     }
 633 
 634     public static Collection<BundlerParamInfo<?>> getAppBundleParameters() {
 635         return Arrays.asList(
 636                 APP_NAME,
 637                 APP_RESOURCES,
 638                 // APP_RESOURCES_LIST, // ??
 639                 ARGUMENTS,
 640                 CLASSPATH,
 641                 ICON_ICO,
 642                 JVM_OPTIONS,
 643                 JVM_PROPERTIES,
 644                 MAIN_CLASS,
 645                 MAIN_JAR,
 646                 PREFERENCES_ID,
 647                 PRELOADER_CLASS,
 648                 USER_JVM_OPTIONS,
 649                 VERSION,
 650                 WIN_RUNTIME
 651             );
 652     }
 653 
 654     @Override
 655     public File execute(Map<String, ? super Object> params, File outputParentDir) {
 656         return doBundle(params, outputParentDir, false);
 657     }
 658 
 659     @Override
 660     protected String getCacheLocation(Map<String, ? super Object> params) {
 661         return "$CACHEDIR/";
 662     }
 663 }