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.ByteArrayOutputStream;
  38 import java.io.File;
  39 import java.io.FileNotFoundException;
  40 import java.io.IOException;
  41 import java.io.PrintStream;
  42 import java.net.MalformedURLException;
  43 import java.net.URL;
  44 import java.nio.file.Files;
  45 import java.text.MessageFormat;
  46 import java.util.*;
  47 import java.util.concurrent.atomic.AtomicReference;
  48 import java.util.regex.Pattern;
  49 
  50 import static com.oracle.tools.packager.StandardBundlerParam.*;
  51 import static com.oracle.tools.packager.windows.WindowsBundlerParam.BIT_ARCH_64;
  52 import static com.oracle.tools.packager.windows.WindowsBundlerParam.BIT_ARCH_64_RUNTIME;
  53 import static com.oracle.tools.packager.windows.WindowsBundlerParam.WIN_RUNTIME;
  54 
  55 public class WinAppBundler extends AbstractImageBundler {
  56 
  57     private static final ResourceBundle I18N = 
  58             ResourceBundle.getBundle(WinAppBundler.class.getName());
  59 
  60     public static final BundlerParamInfo<File> CONFIG_ROOT = new WindowsBundlerParam<>(
  61             I18N.getString("param.config-root.name"),
  62             I18N.getString("param.config-root.description"), 
  63             "configRoot",
  64             File.class,
  65             params -> {
  66                 File imagesRoot = new File(BUILD_ROOT.fetchFrom(params), "windows");
  67                 imagesRoot.mkdirs();
  68                 return imagesRoot;
  69             },
  70             (s, p) -> null);
  71 
  72     private final static String EXECUTABLE_NAME = "WinLauncher.exe";
  73     private final static String LIBRARY_NAME = "packager.dll";
  74 
  75     private final static String[] VS_VERS = {"100", "110", "120"};
  76     private final static String REDIST_MSVCR = "msvcrVS_VER.dll";
  77     private final static String REDIST_MSVCP = "msvcpVS_VER.dll";
  78 
  79     private static final String TOOL_ICON_SWAP="IconSwap.exe";
  80 
  81     public static final BundlerParamInfo<URL> RAW_EXECUTABLE_URL = new WindowsBundlerParam<>(
  82             I18N.getString("param.raw-executable-url.name"),
  83             I18N.getString("param.raw-executable-url.description"),
  84             "win.launcher.url",
  85             URL.class,
  86             params -> WinResources.class.getResource(EXECUTABLE_NAME),
  87             (s, p) -> {
  88                 try {
  89                     return new URL(s);
  90                 } catch (MalformedURLException e) {
  91                     Log.info(e.toString());
  92                     return null;
  93                 }
  94             });
  95 
  96     public static final BundlerParamInfo<Boolean> REBRAND_EXECUTABLE = new WindowsBundlerParam<>(
  97             I18N.getString("param.rebrand-executable.name"),
  98             I18N.getString("param.rebrand-executable.description"),
  99             "win.launcher.rebrand",
 100             Boolean.class,
 101             params -> Boolean.TRUE,
 102             (s, p) -> Boolean.valueOf(s));
 103 
 104     public static final BundlerParamInfo<File> ICON_ICO = new StandardBundlerParam<>(
 105             I18N.getString("param.icon-ico.name"),
 106             I18N.getString("param.icon-ico.description"),
 107             "icon.ico",
 108             File.class,
 109             params -> {
 110                 File f = ICON.fetchFrom(params);
 111                 if (f != null && !f.getName().toLowerCase().endsWith(".ico")) {
 112                     Log.info(MessageFormat.format(I18N.getString("message.icon-not-ico"), f));
 113                     return null;
 114                 }
 115                 return f;
 116             },
 117             (s, p) -> new File(s));
 118 
 119     public WinAppBundler() {
 120         super();
 121         baseResourceLoader = WinResources.class;
 122     }
 123 
 124     public final static String WIN_BUNDLER_PREFIX =
 125             BUNDLER_PREFIX + "windows/";
 126 
 127     File getConfigRoot(Map<String, ? super Object> params) {
 128         return CONFIG_ROOT.fetchFrom(params);
 129     }
 130 
 131     @Override
 132     public boolean validate(Map<String, ? super Object> params) throws UnsupportedPlatformException, ConfigException {
 133         try {
 134             if (params == null) throw new ConfigException(
 135                     I18N.getString("error.parameters-null"),
 136                     I18N.getString("error.parameters-null.advice"));
 137 
 138             return doValidate(params);
 139         } catch (RuntimeException re) {
 140             if (re.getCause() instanceof ConfigException) {
 141                 throw (ConfigException) re.getCause();
 142             } else {
 143                 throw new ConfigException(re);
 144             }
 145         }
 146     }
 147 
 148     //to be used by chained bundlers, e.g. by EXE bundler to avoid
 149     // skipping validation if p.type does not include "image"
 150     boolean doValidate(Map<String, ? super Object> p) throws UnsupportedPlatformException, ConfigException {
 151         if (!System.getProperty("os.name").toLowerCase().startsWith("win")) {
 152             throw new UnsupportedPlatformException();
 153         }
 154         
 155         imageBundleValidation(p);
 156 
 157         if (WinResources.class.getResource(TOOL_ICON_SWAP) == null) {
 158             throw new ConfigException(
 159                     I18N.getString("error.no-windows-resources"),
 160                     I18N.getString("error.no-windows-resources.advice"));
 161         }
 162 
 163         //validate required inputs
 164         testRuntime(WIN_RUNTIME.fetchFrom(p), new String[] {
 165                 "bin\\\\[^\\\\]+\\\\jvm.dll", // most reliable
 166                 "lib\\\\rt.jar", // fallback canary for JDK 8
 167         });
 168         if (USE_FX_PACKAGING.fetchFrom(p)) {
 169             testRuntime(WIN_RUNTIME.fetchFrom(p), new String[] {"lib\\\\ext\\\\jfxrt.jar", "lib\\\\jfxrt.jar"});
 170         }
 171 
 172         //validate runtime bit-architectire
 173         testRuntimeBitArchitecture(p);
 174 
 175         return true;
 176     }
 177 
 178     private static void testRuntimeBitArchitecture(Map<String, ? super Object> params) throws ConfigException {
 179         if ("true".equalsIgnoreCase(System.getProperty("fxpackager.disableBitArchitectureMismatchCheck"))) {
 180             Log.debug(I18N.getString("message.disable-bit-architecture-check"));
 181             return;
 182         }
 183 
 184         if ((BIT_ARCH_64.fetchFrom(params) != BIT_ARCH_64_RUNTIME.fetchFrom(params)) && !"systemjre".equals(params.get(".runtime.autodetect"))) {
 185             throw new ConfigException(
 186                     I18N.getString("error.bit-architecture-mismatch"),
 187                     I18N.getString("error.bit-architecture-mismatch.advice"));
 188         }
 189     }
 190 
 191     //it is static for the sake of sharing with "Exe" bundles
 192     // that may skip calls to validate/bundle in this class!
 193     private static File getRootDir(File outDir, Map<String, ? super Object> p) {
 194         return new File(outDir, APP_NAME.fetchFrom(p));
 195     }
 196 
 197     public static String getLauncherName(Map<String, ? super Object> p) {
 198         return APP_NAME.fetchFrom(p) +".exe";
 199     }
 200 
 201     public static String getLauncherCfgName(Map<String, ? super Object> p) {
 202         return "app\\" + APP_NAME.fetchFrom(p) +".cfg";
 203     }
 204 
 205     private File getConfig_AppIcon(Map<String, ? super Object> params) {
 206         return new File(getConfigRoot(params), APP_NAME.fetchFrom(params) + ".ico");
 207     }
 208 
 209     private final static String TEMPLATE_APP_ICON ="javalogo_white_48.ico";
 210 
 211     //remove
 212     protected void cleanupConfigFiles(Map<String, ? super Object> params) {
 213         getConfig_AppIcon(params).delete();
 214     }
 215 
 216     private void prepareConfigFiles(Map<String, ? super Object> params) throws IOException {
 217         File iconTarget = getConfig_AppIcon(params);
 218 
 219         File icon = ICON_ICO.fetchFrom(params);
 220         if (icon != null && icon.exists()) {
 221             fetchResource(WIN_BUNDLER_PREFIX + iconTarget.getName(),
 222                     I18N.getString("resource.application-icon"),
 223                     icon,
 224                     iconTarget,
 225                     VERBOSE.fetchFrom(params),
 226                     DROP_IN_RESOURCES_ROOT.fetchFrom(params));
 227         } else {
 228             fetchResource(WIN_BUNDLER_PREFIX + iconTarget.getName(),
 229                     I18N.getString("resource.application-icon"),
 230                     WinAppBundler.TEMPLATE_APP_ICON,
 231                     iconTarget,
 232                     VERBOSE.fetchFrom(params),
 233                     DROP_IN_RESOURCES_ROOT.fetchFrom(params));
 234         }
 235     }
 236 
 237     public boolean bundle(Map<String, ? super Object> p, File outputDirectory) {
 238         return doBundle(p, outputDirectory, false) != null;
 239     }
 240 
 241     File doBundle(Map<String, ? super Object> p, File outputDirectory, boolean dependentTask) {
 242         Map<String, ? super Object> originalParams = new HashMap<>(p);
 243         if (!outputDirectory.isDirectory() && !outputDirectory.mkdirs()) {
 244             throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-create-output-dir"), outputDirectory.getAbsolutePath()));
 245         }
 246         if (!outputDirectory.canWrite()) {
 247             throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-write-to-output-dir"), outputDirectory.getAbsolutePath()));
 248         }
 249         try {
 250             if (!dependentTask) {
 251                 Log.info(MessageFormat.format(I18N.getString("message.creating-app-bundle"), APP_NAME.fetchFrom(p), outputDirectory.getAbsolutePath()));
 252             }
 253 
 254             // Create directory structure
 255             File rootDirectory = getRootDir(outputDirectory, p);
 256             IOUtils.deleteRecursive(rootDirectory);
 257             rootDirectory.mkdirs();
 258 
 259             File appDirectory = new File(rootDirectory, "app");
 260             appDirectory.mkdirs();
 261 
 262             // create the .exe launchers
 263             createLauncherForEntryPoint(p, rootDirectory);
 264 
 265             // copy the jars
 266             copyApplication(p, appDirectory);
 267 
 268             // Copy runtime 
 269             File runtimeDirectory = new File(rootDirectory, "runtime");
 270             copyRuntime(p, runtimeDirectory);
 271 
 272             // copy in the needed libraries
 273             IOUtils.copyFromURL(
 274                     WinResources.class.getResource(LIBRARY_NAME),
 275                     new File(rootDirectory, LIBRARY_NAME));
 276 
 277             copyMSVCDLLs(rootDirectory, runtimeDirectory);
 278 
 279             // create the secondary launchers, if any
 280             List<Map<String, ? super Object>> entryPoints = StandardBundlerParam.SECONDARY_LAUNCHERS.fetchFrom(p);
 281             for (Map<String, ? super Object> entryPoint : entryPoints) {
 282                 Map<String, ? super Object> tmp = new HashMap<>(originalParams);
 283                 tmp.putAll(entryPoint);
 284                 createLauncherForEntryPoint(tmp, rootDirectory);
 285             }
 286 
 287             if (!dependentTask) {
 288                 Log.info(MessageFormat.format(I18N.getString("message.result-dir"), outputDirectory.getAbsolutePath()));
 289             }
 290 
 291             return rootDirectory;
 292         } catch (IOException ex) {
 293             Log.info("Exception: "+ex);
 294             Log.debug(ex);
 295             return null;
 296         } finally {
 297             if (VERBOSE.fetchFrom(p)) {
 298                 Log.info(MessageFormat.format(I18N.getString("message.config-save-location"), getConfigRoot(p).getAbsolutePath()));
 299             } else {
 300                 cleanupConfigFiles(p);
 301             }
 302         }
 303 
 304     }
 305 
 306     private void copyMSVCDLLs(File rootDirectory, File jreDir) throws IOException {
 307         String vsVer = null;
 308         if (jreDir == null || !jreDir.isDirectory()) {
 309             jreDir = new File(System.getProperty("java.home"));
 310         }
 311 
 312         // first copy the ones needed for the launcher
 313         for (String thisVer : VS_VERS) {
 314             if (copyMSVCDLLs(rootDirectory, thisVer)) {
 315                 vsVer = thisVer;
 316                 break;
 317             }
 318         }
 319         if (vsVer == null) {
 320             throw new RuntimeException("Not found MSVC dlls");
 321         }
 322 
 323         AtomicReference<IOException> ioe = new AtomicReference<>();
 324         final String finalVsVer = vsVer;
 325         Files.list(jreDir.toPath().resolve("bin"))
 326                 .filter(p -> Pattern.matches("msvc(r|p)\\d\\d\\d.dll", p.toFile().getName().toLowerCase()))
 327                 .filter(p -> !p.toString().toLowerCase().endsWith(finalVsVer + ".dll"))
 328                 .forEach(p -> {
 329                     try {
 330                         IOUtils.copyFile(p.toFile(), new File(rootDirectory, p.toFile().getName()));
 331                     } catch (IOException e) {
 332                         ioe.set(e);
 333                     }
 334                 });
 335 
 336         IOException e = ioe.get();
 337         if (e != null) {
 338             throw e;
 339         }
 340     }
 341 
 342     private boolean copyMSVCDLLs(File rootDirectory, String VS_VER) throws IOException {
 343         final URL REDIST_MSVCR_URL = WinResources.class.getResource(
 344                                               REDIST_MSVCR.replaceAll("VS_VER", VS_VER));
 345         final URL REDIST_MSVCP_URL = WinResources.class.getResource(
 346                                               REDIST_MSVCP.replaceAll("VS_VER", VS_VER));
 347 
 348         if (REDIST_MSVCR_URL != null && REDIST_MSVCP_URL != null) {
 349             IOUtils.copyFromURL(
 350                     REDIST_MSVCR_URL,
 351                     new File(rootDirectory, REDIST_MSVCR.replaceAll("VS_VER", VS_VER)));
 352             IOUtils.copyFromURL(
 353                     REDIST_MSVCP_URL,
 354                     new File(rootDirectory, REDIST_MSVCP.replaceAll("VS_VER", VS_VER)));
 355             return true;
 356         }
 357 
 358         return false; // not found
 359     }
 360 
 361     private void createLauncherForEntryPoint(Map<String, ? super Object> p, File rootDirectory) throws IOException {
 362         prepareConfigFiles(p);
 363 
 364         // Generate launcher .cfg file
 365         if (LAUNCHER_CFG_FORMAT.fetchFrom(p).equals(CFG_FORMAT_PROPERTIES)) {
 366             writeCfgFile(p, rootDirectory);
 367         } else {
 368             writeCfgFile(p, new File(rootDirectory, getLauncherCfgName(p)), "$APPDIR\\runtime");
 369         }
 370 
 371         // Copy executable root folder
 372         File executableFile = new File(rootDirectory, getLauncherName(p));
 373         IOUtils.copyFromURL(
 374                 RAW_EXECUTABLE_URL.fetchFrom(p),
 375                 executableFile);
 376         executableFile.setExecutable(true, false);
 377 
 378         //Update branding of exe file
 379         if (REBRAND_EXECUTABLE.fetchFrom(p) && getConfig_AppIcon(p).exists()) {
 380             //extract helper tool
 381             File iconSwapTool = File.createTempFile("iconswap", ".exe");
 382             iconSwapTool.delete();
 383             IOUtils.copyFromURL(
 384                     WinResources.class.getResource(TOOL_ICON_SWAP),
 385                     iconSwapTool);
 386             iconSwapTool.setExecutable(true, false);
 387             iconSwapTool.deleteOnExit();
 388 
 389             //run it on launcher file
 390             executableFile.setWritable(true);
 391             ProcessBuilder pb = new ProcessBuilder(
 392                     iconSwapTool.getAbsolutePath(),
 393                     getConfig_AppIcon(p).getAbsolutePath(),
 394                     executableFile.getAbsolutePath());
 395             IOUtils.exec(pb, VERBOSE.fetchFrom(p));
 396             executableFile.setReadOnly();
 397             iconSwapTool.delete();
 398         }
 399 
 400         IOUtils.copyFile(getConfig_AppIcon(p),
 401                 new File(rootDirectory, APP_NAME.fetchFrom(p) + ".ico"));
 402     }
 403 
 404     private void copyApplication(Map<String, ? super Object> params, File appDirectory) throws IOException {
 405         List<RelativeFileSet> appResourcesList = APP_RESOURCES_LIST.fetchFrom(params);
 406         if (appResourcesList == null) {
 407             throw new RuntimeException("Null app resources?");
 408         }
 409         for (RelativeFileSet appResources : appResourcesList) {
 410             if (appResources == null) {
 411                 throw new RuntimeException("Null app resources?");
 412             }
 413             File srcdir = appResources.getBaseDirectory();
 414             for (String fname : appResources.getIncludedFiles()) {
 415                 IOUtils.copyFile(
 416                         new File(srcdir, fname), new File(appDirectory, fname));
 417             }
 418         }
 419     }
 420 
 421     private void writeCfgFile(Map<String, ? super Object> params, File rootDir) throws FileNotFoundException {
 422         File cfgFile = new File(rootDir, getLauncherCfgName(params));
 423 
 424         cfgFile.delete();
 425 
 426         PrintStream out = new PrintStream(cfgFile);
 427         if (WIN_RUNTIME.fetchFrom(params) == null) {
 428             out.println("app.runtime=");
 429         } else {
 430             out.println("app.runtime=$APPDIR\\runtime");
 431         }
 432         out.println("app.mainjar=" + MAIN_JAR.fetchFrom(params).getIncludedFiles().iterator().next());
 433         out.println("app.version=" + VERSION.fetchFrom(params));
 434         //for future AU support (to be able to find app in the registry)
 435         out.println("app.id=" + IDENTIFIER.fetchFrom(params));
 436         out.println("app.preferences.id=" + PREFERENCES_ID.fetchFrom(params));
 437         out.println("app.identifier=" + IDENTIFIER.fetchFrom(params));
 438 
 439         out.println("app.mainclass=" +
 440                 MAIN_CLASS.fetchFrom(params).replaceAll("\\.", "/"));
 441         out.println("app.classpath=" + CLASSPATH.fetchFrom(params));
 442 
 443         List<String> jvmargs = JVM_OPTIONS.fetchFrom(params);
 444         int idx = 1;
 445         for (String a : jvmargs) {
 446             out.println("jvmarg."+idx+"="+a);
 447             idx++;
 448         }
 449         Map<String, String> jvmProps = JVM_PROPERTIES.fetchFrom(params);
 450         for (Map.Entry<String, String> entry : jvmProps.entrySet()) {
 451             out.println("jvmarg."+idx+"=-D"+entry.getKey()+"="+entry.getValue());
 452             idx++;
 453         }
 454 
 455         String preloader = PRELOADER_CLASS.fetchFrom(params);
 456         if (preloader != null) {
 457             out.println("jvmarg."+idx+"=-Djavafx.preloader="+preloader);
 458         }
 459 
 460         Map<String, String> overridableJVMOptions = USER_JVM_OPTIONS.fetchFrom(params);
 461         idx = 1;
 462         for (Map.Entry<String, String> arg: overridableJVMOptions.entrySet()) {
 463             if (arg.getKey() == null || arg.getValue() == null) {
 464                 Log.info(I18N.getString("message.jvm-user-arg-is-null"));
 465             }
 466             else {
 467                 out.println("jvmuserarg."+idx+".name="+arg.getKey());
 468                 out.println("jvmuserarg."+idx+".value="+arg.getValue());
 469             }
 470             idx++;
 471         }
 472 
 473         // add command line args
 474         List<String> args = ARGUMENTS.fetchFrom(params);
 475         idx = 1;
 476         for (String a : args) {
 477             out.println("arg."+idx+"="+a);
 478             idx++;
 479         }
 480 
 481         out.close();
 482     }
 483 
 484     private void copyRuntime(Map<String, ? super Object> params, File runtimeDirectory) throws IOException {
 485         RelativeFileSet runtime = WIN_RUNTIME.fetchFrom(params);
 486         if (runtime == null) {
 487             //its ok, request to use system JRE
 488             return;
 489         }
 490         runtimeDirectory.mkdirs();
 491 
 492         File srcdir = runtime.getBaseDirectory();
 493         Set<String> filesToCopy = runtime.getIncludedFiles();
 494         for (String fname : filesToCopy) {
 495             IOUtils.copyFile(
 496                     new File(srcdir, fname), new File(runtimeDirectory, fname));
 497         }
 498     }
 499     
 500     public void extractRuntimeFlags(Map<String, ? super Object> params) {
 501         extractFlagsFromRuntime(params);
 502     }
 503 
 504     public static void extractFlagsFromRuntime(Map<String, ? super Object> params) {
 505         if (params.containsKey(".runtime.autodetect")) return;
 506 
 507         params.put(".runtime.autodetect", "attempted");
 508         RelativeFileSet runtime = WIN_RUNTIME.fetchFrom(params);
 509         String commandline;
 510         if (runtime == null) {
 511             //System JRE, report nothing useful
 512             params.put(".runtime.autodetect", "systemjre");
 513         } else {
 514             File runtimePath = runtime.getBaseDirectory();
 515             File launcherPath = new File(runtimePath, "bin\\java");
 516 
 517             ProcessBuilder pb = new ProcessBuilder(launcherPath.getAbsolutePath(), "-version");
 518             try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
 519                 try (PrintStream pout = new PrintStream(baos)) {
 520                     IOUtils.exec(pb, Log.isDebug(), true, pout);
 521                 }
 522 
 523                 commandline = baos.toString();
 524             } catch (IOException e) {
 525                 e.printStackTrace();
 526                 params.put(".runtime.autodetect", "failed");
 527                 return;
 528             }
 529             AbstractImageBundler.extractFlagsFromVersion(params, commandline);
 530             params.put(".runtime.autodetect", "succeeded");
 531         }
 532     }
 533 
 534 
 535     @Override
 536     public String getName() {
 537         return I18N.getString("bundler.name");
 538     }
 539 
 540     @Override
 541     public String getDescription() {
 542         return I18N.getString("bundler.description");
 543     }
 544 
 545     @Override
 546     public String getID() {
 547         return "windows.app";
 548     }
 549 
 550     @Override
 551     public String getBundleType() {
 552         return "IMAGE";
 553     }
 554 
 555     @Override
 556     public Collection<BundlerParamInfo<?>> getBundleParameters() {
 557         return getAppBundleParameters();
 558     }
 559 
 560     public static Collection<BundlerParamInfo<?>> getAppBundleParameters() {
 561         return Arrays.asList(
 562                 APP_NAME,
 563                 APP_RESOURCES,
 564                 // APP_RESOURCES_LIST, // ??
 565                 ARGUMENTS,
 566                 CLASSPATH,
 567                 ICON_ICO,
 568                 JVM_OPTIONS,
 569                 JVM_PROPERTIES,
 570                 MAIN_CLASS,
 571                 MAIN_JAR,
 572                 PREFERENCES_ID,
 573                 PRELOADER_CLASS,
 574                 USER_JVM_OPTIONS,
 575                 VERSION,
 576                 WIN_RUNTIME
 577             );
 578     }
 579 
 580     @Override
 581     public File execute(Map<String, ? super Object> params, File outputParentDir) {
 582         return doBundle(params, outputParentDir, false);
 583     }
 584 
 585     @Override
 586     protected String getCacheLocation(Map<String, ? super Object> params) {
 587         return "$CACHEDIR/";
 588     }
 589 }