1 /*
   2  * Copyright (c) 2011, 2016, 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.sun.javafx.tools.ant;
  27 
  28 import java.io.File;
  29 import java.util.ArrayList;
  30 import java.util.LinkedList;
  31 import java.util.List;
  32 import java.util.Map;
  33 import java.util.stream.Collectors;
  34 
  35 import com.oracle.tools.packager.StandardBundlerParam;
  36 import com.sun.javafx.tools.ant.Platform.Jvmarg;
  37 import com.sun.javafx.tools.ant.Platform.Property;
  38 import com.sun.javafx.tools.packager.DeployParams;
  39 import com.oracle.tools.packager.Log;
  40 import com.sun.javafx.tools.packager.PackagerException;
  41 import com.sun.javafx.tools.packager.PackagerLib;
  42 import com.sun.javafx.tools.packager.bundlers.Bundler;
  43 import com.sun.javafx.tools.packager.bundlers.Bundler.Bundle;
  44 import com.sun.javafx.tools.packager.bundlers.Bundler.BundleType;
  45 import java.util.ResourceBundle;
  46 import org.apache.tools.ant.BuildException;
  47 import org.apache.tools.ant.DynamicAttribute;
  48 import org.apache.tools.ant.Task;
  49 import org.apache.tools.ant.types.DataType;
  50 
  51 /**
  52  * Generates package for Web deployment and redistribution of application.
  53  * Package includes of set of jar files, JNLP file and HTML file.
  54  *
  55  * Minimal example:
  56  * <pre>
  57  *   &lt;fx:deploy width="600" height="400"
  58  *                 outdir="web-dist" outfile="Fish"&gt;
  59  *       &lt;info title="Sample application"/&gt;
  60  *       &lt;fx:application refid="myapp"/&gt;
  61  *       &lt;fx:resources refid="myresources"/&gt;
  62  *   &lt;/fx:deploy&gt;
  63  * </pre>
  64  * Above example will generate HTML/JNLP files into the web-dist directory
  65  * and use "Fish" as prefix for generated files. Details about application and
  66  * its resources are defined elsewhere in the application and resource elements.
  67  * <p>
  68  * Minimal complete example:
  69  * <pre>
  70  *   &lt;fx:deploy width="600" height="400"
  71  *                 outdir="web-dist" outfile="Fish"&gt;
  72  *       &lt;info title="Sample application"/&gt;
  73  *       &lt;fx:application name="SampleApp"
  74  *              mainClass="testapp.MainApp"
  75  *              preloaderClass="testpreloader.Preloader"/&gt;
  76  *       &lt;fx:resources&gt;
  77  *              &lt;fx:fileset requiredFor="preloader" dir="dist"&gt;
  78  *                &lt;include name="preloader.jar"/&gt;
  79  *             &lt;/fx:fileset&gt;
  80  *              &lt;fx:fileset dir="dist"&gt;
  81  *                &lt;include name="helloworld.jar"/&gt;
  82  *             &lt;/fx:fileset&gt;
  83  *       &lt;/fx:resources&gt;
  84  *   &lt;/fx:deploy&gt;
  85  * </pre>
  86  * Same as above but here application and resource details are defined in place.
  87  * Note that using references helps with reducing code duplication as fx:jar need
  88  * to be used for double clickable jars.
  89  *
  90  * @ant.task name="deploy" category="javafx"
  91  */
  92 public class DeployFXTask extends Task implements DynamicAttribute {
  93 
  94     private static final ResourceBundle I18N =
  95             ResourceBundle.getBundle(DeployFXTask.class.getName());
  96 
  97     private String width = null;
  98     private String height = null;
  99     private String embeddedWidth = null;
 100     private String embeddedHeight = null;
 101     private String outfile = null;
 102     private String outdir = null;
 103     private boolean embedJNLP;
 104     private boolean isExtension = false;
 105     private Boolean signBundle;
 106 
 107     //Before FCS default is to include DT files with app
 108     // to ensure tests are using latest and compatible.
 109     //After FCS default is to use shared copy.
 110     private boolean includeDT = false;
 111 
 112     private String updateMode="background";
 113     private Info appInfo = null;
 114     private Application app = null;
 115     private Resources resources = null;
 116     private Preferences prefs = null;
 117     private String codebase = null;
 118 
 119     //container to embed application into
 120     //could be either string id or js code. If it is string id then it needs to
 121     //be escaped
 122     private String placeholder;
 123 
 124     private PackagerLib packager;
 125     private DeployParams deployParams;
 126 
 127     private Callbacks callbacks;
 128 
 129     boolean offlineAllowed = true;
 130 
 131     //default native bundle settings
 132     // use NONE to avoid large disk space and build time overhead
 133     BundleType nativeBundles = BundleType.NONE;
 134     String bundleFormat = null;
 135     boolean versionCheck = true;
 136 
 137     private boolean verbose = false;
 138     public void setVerbose(boolean v) {
 139         verbose = v;
 140     }
 141 
 142     public void setCodebase(String str) {
 143         codebase = str;
 144     }
 145 
 146     public DeployFXTask() {
 147         packager = new PackagerLib();
 148         deployParams = new DeployParams();
 149     }
 150 
 151     @Override
 152     public void execute() {
 153         boolean isModular = (app.getModule() != null) && !app.getModule().isEmpty();
 154         deployParams.setOutfile(outfile);
 155         deployParams.setOutdir(new File(outdir));
 156 
 157         if (versionCheck) {
 158             if (!com.sun.javafx.tools.ant.VersionCheck.isSameVersion()) {
 159                 throw new BuildException(I18N.getString("message.java.version.mismatch"));
 160             }
 161         }
 162 
 163         if (!isModular &&
 164             (nativeBundles == BundleType.JNLP ||
 165              nativeBundles == BundleType.ALL ||
 166              nativeBundles == BundleType.NONE)) {
 167             deployParams.setOfflineAllowed(offlineAllowed);
 168             deployParams.setVerbose(verbose);
 169             deployParams.setCodebase(codebase);
 170             deployParams.setSignBundle(signBundle);
 171 
 172             if (app.getModule() == null) {
 173                 deployParams.setApplicationClass(app.get().mainClass);
 174             }
 175 
 176             if (width != null) {
 177                 deployParams.setWidth(Integer.valueOf(width));
 178             }
 179 
 180             if (height != null) {
 181                 deployParams.setHeight(Integer.valueOf(height));
 182             }
 183 
 184             if (embeddedWidth != null && embeddedHeight != null) {
 185                 deployParams.setEmbeddedDimensions(embeddedWidth, embeddedHeight);
 186             }
 187 
 188             deployParams.setEmbedJNLP(embedJNLP);
 189             if (perms != null) {
 190                deployParams.setAllPermissions(perms.getElevated());
 191             }
 192 
 193             deployParams.setUpdateMode(updateMode);
 194             deployParams.setExtension(isExtension);
 195             deployParams.setIncludeDT(includeDT);
 196 
 197             if (platform != null) {
 198                 Platform pl = platform.get();
 199                 if (pl.j2se != null) {
 200                     deployParams.setJRE(pl.j2se);
 201                 }
 202                 if (pl.javafx != null) {
 203                     deployParams.setJavafx(pl.javafx);
 204                 }
 205 
 206                 //only pass it further if it was explicitly set
 207                 // as we do not want to override default
 208                 if (pl.javaRoot != null) {
 209                     if (Platform.USE_SYSTEM_JRE.equals(pl.javaRoot)) {
 210                         deployParams.setJavaRuntimeSource(null);
 211                     } else {
 212                         deployParams.setJavaRuntimeSource(new File(pl.javaRoot));
 213                     }
 214                 }
 215 
 216                 for (Property p: pl.properties) {
 217                     deployParams.addJvmProperty(p.name, p.value);
 218                 }
 219                 for (Jvmarg a: pl.jvmargs) {
 220                     deployParams.addJvmArg(a.value);
 221                 }
 222                 for (Property a: pl.jvmUserArgs) {
 223                     deployParams.addJvmUserArg(a.name, a.value);
 224                 }
 225             }
 226 
 227             if (callbacks != null) {
 228                 for (Callback cb: callbacks.callbacks) {
 229                     deployParams.addCallback(cb.getName(), cb.getCmd());
 230                 }
 231             }
 232 
 233             if (prefs != null) {
 234                 deployParams.setNeedShortcut(prefs.getShortcut());
 235                 deployParams.setNeedInstall(prefs.getInstall());
 236                 deployParams.setNeedMenu(prefs.getMenu());
 237                 deployParams.setSystemWide(prefs.getSystemInstall());
 238                 deployParams.setInstalldirChooser(prefs.getInstalldirChooser());
 239             }
 240 
 241             for (Template t: templateList) {
 242                 deployParams.addTemplate(t.infile, t.outfile);
 243             }
 244         }
 245 
 246         if (isModular &&
 247             (nativeBundles == BundleType.NATIVE ||
 248              nativeBundles == BundleType.IMAGE ||
 249              nativeBundles == BundleType.INSTALLER ||
 250              nativeBundles == BundleType.ALL)) {
 251             if (app != null) {
 252                 if (app.getModule() == null) {
 253                     deployParams.setApplicationClass(app.get().mainClass);
 254                 }
 255                 else {
 256                     int index = app.getModule().indexOf("/");
 257 
 258                     if (index > 0) {
 259                         deployParams.setModule(app.getModule());
 260                     }
 261                     else {
 262                         deployParams.setModule(app.getModule() + "/" + app.get().mainClass);
 263                     }
 264                 }
 265 
 266                 deployParams.setPreloader(app.get().preloaderClass);
 267                 deployParams.setAppId(app.get().id);
 268                 deployParams.setAppName(app.get().name);
 269                 deployParams.setParams(app.get().parameters);
 270                 deployParams.setArguments(app.get().getArguments());
 271                 deployParams.setHtmlParams(app.get().htmlParameters);
 272                 deployParams.setFallback(app.get().fallbackApp);
 273                 deployParams.setSwingAppWithEmbeddedJavaFX(app.get().embeddedIntoSwing);
 274                 deployParams.setVersion(app.get().version);
 275                 deployParams.setId(app.get().id);
 276                 deployParams.setServiceHint(app.get().daemon);
 277 
 278                 if (runtime != null) {
 279                     for (String s : runtime.getAddModules()) {
 280                         deployParams.addAddModule(s);
 281                     }
 282 
 283                     for (String s : runtime.getLimitModules()) {
 284                         deployParams.addLimitModule(s);
 285                     }
 286 
 287                     deployParams.setModulePath(runtime.getModulePath());
 288 
 289                     Boolean stripNativeCommands = runtime.getStripNativeCommands();
 290 
 291                     if (stripNativeCommands != null) {
 292                         deployParams.setStripNativeCommands(stripNativeCommands);
 293                     }
 294 
 295                     Boolean detectModules = runtime.getDetectModules();
 296 
 297                     if (detectModules != null) {
 298                         deployParams.setDetectModules(detectModules);
 299                     }
 300                 }
 301             }
 302 
 303             if (appInfo != null) {
 304                 deployParams.setTitle(appInfo.title);
 305                 deployParams.setVendor(appInfo.vendor);
 306                 deployParams.setDescription(appInfo.appDescription);
 307                 deployParams.setCategory(appInfo.category);
 308                 deployParams.setLicenseType(appInfo.licenseType);
 309                 deployParams.setCopyright(appInfo.copyright);
 310                 deployParams.setEmail(appInfo.email);
 311 
 312                 for (Info.Icon i: appInfo.icons) {
 313                     if (i instanceof Info.Splash) {
 314                        deployParams.addIcon(i.href, i.kind, i.width, i.height, i.depth,
 315                             ((Info.Splash) i).mode);
 316                     } else {
 317                        deployParams.addIcon(i.href, i.kind, i.width, i.height, i.depth,
 318                             DeployParams.RunMode.WEBSTART);
 319                     }
 320                 }
 321 
 322                 deployParams.addBundleArgument(StandardBundlerParam.FILE_ASSOCIATIONS.getID(),
 323                         appInfo.fileAssociations.stream()
 324                             .map(FileAssociation::createLauncherMap)
 325                             .collect(Collectors.toList()));
 326             }
 327         }
 328 
 329         if (!isModular &&
 330             (nativeBundles == BundleType.NATIVE ||
 331              nativeBundles == BundleType.IMAGE ||
 332              nativeBundles == BundleType.INSTALLER ||
 333              nativeBundles == BundleType.ALL)) {
 334             if (app != null) {
 335                 deployParams.setApplicationClass(app.get().mainClass);
 336                 deployParams.setPreloader(app.get().preloaderClass);
 337                 deployParams.setAppId(app.get().id);
 338                 deployParams.setAppName(app.get().name);
 339                 deployParams.setParams(app.get().parameters);
 340                 deployParams.setArguments(app.get().getArguments());
 341                 deployParams.setHtmlParams(app.get().htmlParameters);
 342                 deployParams.setFallback(app.get().fallbackApp);
 343                 deployParams.setSwingAppWithEmbeddedJavaFX(app.get().embeddedIntoSwing);
 344                 deployParams.setVersion(app.get().version);
 345                 deployParams.setId(app.get().id);
 346                 deployParams.setServiceHint(app.get().daemon);
 347             }
 348 
 349             if (appInfo != null) {
 350                 deployParams.setTitle(appInfo.title);
 351                 deployParams.setVendor(appInfo.vendor);
 352                 deployParams.setDescription(appInfo.appDescription);
 353                 deployParams.setCategory(appInfo.category);
 354                 deployParams.setLicenseType(appInfo.licenseType);
 355                 deployParams.setCopyright(appInfo.copyright);
 356                 deployParams.setEmail(appInfo.email);
 357 
 358                 for (Info.Icon i: appInfo.icons) {
 359                     if (i instanceof Info.Splash) {
 360                        deployParams.addIcon(i.href, i.kind, i.width, i.height, i.depth,
 361                             ((Info.Splash) i).mode);
 362                     } else {
 363                        deployParams.addIcon(i.href, i.kind, i.width, i.height, i.depth,
 364                             DeployParams.RunMode.WEBSTART);
 365                     }
 366                 }
 367 
 368                 deployParams.addBundleArgument(StandardBundlerParam.FILE_ASSOCIATIONS.getID(),
 369                         appInfo.fileAssociations.stream()
 370                             .map(FileAssociation::createLauncherMap)
 371                             .collect(Collectors.toList()));
 372             }
 373         }
 374 
 375         for (BundleArgument ba : bundleArgumentList) {
 376             deployParams.addBundleArgument(ba.arg, ba.value);
 377         }
 378 
 379         deployParams.setPlaceholder(placeholder);
 380 
 381         if (resources != null) {
 382             for (FileSet fs: resources.getResources()) {
 383                 Utils.addResources(deployParams, fs);
 384             }
 385         }
 386 
 387         List<Map<String, ? super Object>> launchersAsMap = new ArrayList<>();
 388         for (SecondaryLauncher sl : secondaryLaunchers) {
 389             launchersAsMap.add(sl.createLauncherMap());
 390         }
 391 
 392         deployParams.addBundleArgument(
 393                 StandardBundlerParam.SECONDARY_LAUNCHERS.getID(),
 394                 launchersAsMap);
 395 
 396         deployParams.setBundleType(nativeBundles);
 397         deployParams.setTargetFormat(bundleFormat);
 398 
 399         Log.setLogger(new AntLog(this.getProject()));
 400 
 401         try {
 402             packager.generateDeploymentPackages(deployParams);
 403         } catch (PackagerException pe) {
 404             if (pe.getCause() != null) {
 405                throw new BuildException(pe.getCause().getMessage(), pe.getCause());
 406             } else {
 407                 throw new BuildException(pe.getMessage(), pe);
 408             }
 409         } catch (Exception e) {
 410             throw new BuildException(e.getMessage(), e);
 411         } finally {
 412             Log.setLogger(null);
 413         }
 414     }
 415 
 416     /**
 417      * Set to true if we are generating an 'extension' JNLP.
 418      *
 419      * @ant.not-required Default is false.
 420      */
 421     public void setExtension(boolean v) {
 422         isExtension = v;
 423     }
 424 
 425     public void setNativeBundles(String v) {
 426         Bundle bundle = Bundler.stringToBundle(v);
 427         this.nativeBundles = bundle.type;
 428         this.bundleFormat = bundle.format;
 429     }
 430 
 431     public void setVersionCheck(String value) {
 432         this.versionCheck = Boolean.valueOf(value);
 433     }
 434 
 435     /**
 436      * Indicates the preferences for when checks for application updates
 437      * are performed. Supported modes are always, timeout and background.
 438      *
 439      * @ant.not-required Default is background.
 440      */
 441     public void setUpdateMode(String v) {
 442         String l = v.toLowerCase();
 443         if ("eager".equals(l)) {
 444             //workaround for doc bug in 2.0
 445             l="always";
 446         }
 447         if (!"always".equals(l) && !"background".equals(l)
 448                 && !"timeout".equals(l)) {
 449             throw new BuildException("Unknown update mode: ["+l+"]." +
 450                     "Supported modes are: 'always', 'timeout' and 'background'");
 451         }
 452         updateMode = l;
 453     }
 454 
 455     /**
 456      * Indicates if the application can be launched offline.
 457      *
 458      * If application is already downloaded and update mode is eager then
 459      * the check will timeout after a few seconds, in which case the cached
 460      * application will be launched instead.
 461      *
 462      * Given a reasonably fast server connection,
 463      * the latest version of the application will usually be run,
 464      * but it is not guaranteed. The application, however, can be run offline.
 465      *
 466      * @ant.not-required Default is true.
 467      */
 468     public void setOfflineAllowed(boolean v) {
 469         offlineAllowed = v;
 470     }
 471 
 472     /**
 473      * Application width for embedding application into Web page
 474      *
 475      * @ant.optional
 476      */
 477     public void setEmbeddedWidth(String w) {
 478         embeddedWidth = w;
 479     }
 480 
 481     /**
 482      * Application width. Used for webstart and embedded applications
 483      * unless emdeddedWidth is specified
 484      *
 485      * @ant.required
 486      */
 487     public void setWidth(String v) {
 488         width = v;
 489     }
 490 
 491     /**
 492      * Application width for embedding application into Web page
 493      *
 494      * @ant.optional
 495      */
 496     public void setEmbeddedHeight(String w) {
 497         embeddedHeight = w;
 498     }
 499 
 500     /**
 501      * Application height. Used for webstart and embedded applications
 502      * unless emdeddedHeight is specified
 503      *
 504      * @ant.required
 505      */
 506     public void setHeight(String v) {
 507         height = v;
 508     }
 509 
 510     /**
 511      * Enable embedding JNLP descriptor into Web page.
 512      * Reduces number of network connections to be made on startup and
 513      * help to improve startup time.
 514      *
 515      * @ant.not-required Default is false.
 516      */
 517     public void setEmbedJNLP(boolean v) {
 518         embedJNLP = v;
 519     }
 520 
 521     /**
 522      * Directory where application package will be saved.
 523      *
 524      * @ant.required
 525      */
 526     public void setOutdir(String v) {
 527         outdir = v;
 528     }
 529 
 530     /**
 531      * Prefix to be used for new generated files.
 532      *
 533      * @ant.required
 534      */
 535     public void setOutfile(String v) {
 536         outfile = v;
 537     }
 538 
 539     /**
 540      * If true then web deployment is done using javascript files
 541      * on java.com. Otherwise copy of javascript file is included into
 542      * application package.
 543      *
 544      * @ant.not-required Before FCS default is false. For FCS default is true.
 545      */
 546     public void setIncludeDT(Boolean v) {
 547         includeDT = v;
 548     }
 549 
 550     /**
 551      * Placeholder in the web page where application will be embedded.
 552      * This is expected to be Javascript DOM object.
 553      *
 554      * @ant.required Either reference or id of placeholder is required.
 555      */
 556     public void setPlaceholderRef(String p) {
 557         this.placeholder = p;
 558     }
 559 
 560     /**
 561      * Id of the placeholder in the web page where application will be embedded.
 562      * Javascript's document.getElementById() is expected to be able to resolve it.
 563      *
 564      * @ant.required Either reference or id of placeholder is required.
 565      */
 566     public void setPlaceholderId(String id) {
 567         //raw id of the placeholder, need to escape it
 568         this.placeholder = "'"+id+"'";
 569     }
 570 
 571     public void setSignBundle(boolean signBundle) {
 572         this.signBundle = signBundle;
 573     }
 574 
 575     public Info createInfo() {
 576         appInfo = new Info();
 577         return appInfo;
 578     }
 579 
 580     public Application createApplication() {
 581         app = new Application();
 582         return app;
 583     }
 584 
 585     public Preferences createPreferences() {
 586         prefs = new Preferences();
 587         return prefs;
 588     }
 589 
 590     public Callbacks createCallbacks() {
 591         if (callbacks != null) {
 592             throw new BuildException("Only one callbacks element is supported.");
 593         }
 594         callbacks = new Callbacks();
 595         return callbacks;
 596     }
 597 
 598     public Resources createResources() {
 599         if (resources != null) {
 600             throw new BuildException("Only one resources element is supported.");
 601         }
 602         resources = new Resources();
 603         return resources;
 604     }
 605 
 606     List<Template> templateList = new LinkedList<>();
 607 
 608     public Template createTemplate() {
 609         Template t = new Template();
 610         templateList.add(t);
 611         return t;
 612     }
 613 
 614     Platform platform;
 615 
 616     public Platform createPlatform() {
 617         platform = new Platform();
 618         return platform;
 619     }
 620 
 621     private Permissions perms = null;
 622 
 623     public Permissions createPermissions() {
 624         perms = new Permissions();
 625         return perms;
 626     }
 627 
 628     List<BundleArgument> bundleArgumentList = new LinkedList<>();
 629 
 630     public BundleArgument createBundleArgument() {
 631         BundleArgument ba = new BundleArgument();
 632         bundleArgumentList.add(ba);
 633         return ba;
 634     }
 635 
 636     private List<SecondaryLauncher> secondaryLaunchers = new ArrayList<>();
 637 
 638     public SecondaryLauncher createSecondaryLauncher() {
 639         SecondaryLauncher sl = new SecondaryLauncher();
 640         secondaryLaunchers.add(sl);
 641         return sl;
 642     }
 643 
 644     private Runtime runtime = null;
 645 
 646     public Runtime createRuntime() {
 647         runtime = new Runtime();
 648         return runtime;
 649     }
 650 
 651     @Override
 652     public void setDynamicAttribute(String name, String value) throws BuildException {
 653         //Use qName and value - can't really validate anything until we know which bundlers we have, so this has
 654         //to done (way) downstream
 655         bundleArgumentList.add(new BundleArgument(name, value));
 656     }
 657 
 658     /**
 659      * Template to preprocess.
 660      * <p>
 661      * Template is the HTML file containing markers to be replaced with
 662      * javascript or HTML snippets needed to deploy JavaFX application on the
 663      * Web page. This allows to deploy application into "real" Web pages
 664      * and simplify development process if application is tightly
 665      * integrated with the page (e.g. uses javascript to communicate to it).
 666      * <p>
 667      * Marker has the form of #XXX# or #XXX(id)#. Where id is identifier
 668      * of an application and XXX is one of following:
 669      * <ul>
 670      *   <li>DT.SCRIPT.URL - location of dtjava.js
 671      *   <li>DT.SCRIPT.CODE - script element to include dtjava.js
 672      *   <li>DT.EMBED.CODE.DYNAMIC - code to embed application into given placeholder
 673      *         It is expected it will be wrapped into function()
 674      *   <li>DT.EMBED.CODE.ONLOAD - all code needed to embed application into Web page
 675      *               using onload hook (except inclusion of dtjava.js)
 676      *   <li>DT.LAUNCH.CODE - code need to launch application.
 677      *          Expected to be wrappend into function().
 678      * </ul>
 679      *
 680      * Page with multiple different applications can be processed multiple times
 681      * - one per application. To avoid confusion markers need to use
 682      * application ids  (alphanumeric string no spaces).
 683      * <p>
 684      * If input and output files are the same then template is processed in place.
 685      * <p>
 686      * Example:
 687      * <pre>
 688      *     &lt;template file="App_template.html" tofile="App.html"/&gt;
 689      * </pre>
 690      *
 691      * @ant.type name="Template" category="javafx"
 692      */
 693     public static class Template extends DataType {
 694         File infile = null;
 695         File outfile = null;
 696 
 697         /**
 698          * Input file.
 699          *
 700          * @ant.required
 701          */
 702         public void setFile(File f) {
 703             infile = f;
 704         }
 705 
 706         /**
 707          * Output file (after preprocessing).
 708          *
 709          * @ant.not-required Default is the same as input file.
 710          */
 711         public void setTofile(File f) {
 712             outfile = f;
 713         }
 714     }
 715 
 716     /**
 717      * An argument to be passed off to the bundlers.
 718      *
 719      * Each bundler uses a set of arguments that may be shared across
 720      * the different bundlers or it may be specific to each bundler.
 721      *
 722      * Some bundlers declare argument types that are not known to the JDK
 723      * and may be specific to the particular bundler (such as Mac App Store
 724      * categories).  These arguments allow you to set and adjust these a
 725      * rguments.
 726      *
 727      * @ant.type name="BundleArgument" category="javafx"
 728      */
 729     public static class BundleArgument extends DataType {
 730         String arg = null;
 731         String value = null;
 732 
 733         BundleArgument() {
 734 
 735         }
 736 
 737         BundleArgument(String arg, String value) {
 738             this.arg = arg;
 739             this.value = value;
 740         }
 741 
 742         /**
 743          * Name of the bundle argument.
 744          *
 745          * @ant.required
 746          */
 747         public void setArg(String arg) {
 748             this.arg = arg;
 749         }
 750 
 751         /**
 752          * Value for the bundle argument.
 753          *
 754          * @ant.not-required Default is a literal null
 755          */
 756         public void setValue(String value) {
 757             this.value = value;
 758         }
 759     }
 760 }