1 /*
   2  * Copyright (c) 2012, 2014, 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.packager;
  27 
  28 import com.oracle.bundlers.Bundlers;
  29 import com.sun.javafx.tools.ant.Utils;
  30 import com.sun.javafx.tools.packager.DeployParams.Icon;
  31 import com.sun.javafx.tools.packager.JarSignature.InputStreamSource;
  32 import com.sun.javafx.tools.packager.bundlers.*;
  33 import com.sun.javafx.tools.resource.DeployResource;
  34 import com.sun.javafx.tools.resource.PackagerResource;
  35 import java.io.BufferedReader;
  36 import java.io.ByteArrayOutputStream;
  37 import java.io.File;
  38 import java.io.FileInputStream;
  39 import java.io.FileNotFoundException;
  40 import java.io.FileOutputStream;
  41 import java.io.FileWriter;
  42 import java.io.IOException;
  43 import java.io.InputStream;
  44 import java.io.InputStreamReader;
  45 import java.io.OutputStream;
  46 import java.io.PrintStream;
  47 import java.io.Writer;
  48 import java.lang.reflect.Method;
  49 import java.net.MalformedURLException;
  50 import java.net.URL;
  51 import java.net.URLClassLoader;
  52 import java.nio.file.Files;
  53 import java.nio.file.StandardCopyOption;
  54 import java.security.CodeSigner;
  55 import java.security.InvalidKeyException;
  56 import java.security.KeyStore;
  57 import java.security.KeyStoreException;
  58 import java.security.NoSuchAlgorithmException;
  59 import java.security.PrivateKey;
  60 import java.security.SignatureException;
  61 import java.security.UnrecoverableKeyException;
  62 import java.security.cert.CertPath;
  63 import java.security.cert.Certificate;
  64 import java.security.cert.CertificateEncodingException;
  65 import java.security.cert.CertificateException;
  66 import java.security.cert.X509Certificate;
  67 import java.text.MessageFormat;
  68 import java.util.ArrayList;
  69 import java.util.Collection;
  70 import java.util.EnumMap;
  71 import java.util.Enumeration;
  72 import java.util.HashSet;
  73 import java.util.Iterator;
  74 import java.util.List;
  75 import java.util.Map;
  76 import java.util.Map.Entry;
  77 import java.util.ResourceBundle;
  78 import java.util.Set;
  79 import java.util.jar.Attributes;
  80 import java.util.jar.JarEntry;
  81 import java.util.jar.JarFile;
  82 import java.util.jar.JarOutputStream;
  83 import java.util.jar.Manifest;
  84 import java.util.regex.Matcher;
  85 import java.util.regex.Pattern;
  86 import java.util.zip.ZipEntry;
  87 import java.util.zip.ZipOutputStream;
  88 import sun.misc.BASE64Encoder;
  89 
  90 import static com.oracle.bundlers.StandardBundlerParam.*;
  91 
  92 
  93 public class PackagerLib {
  94     public static final String JAVAFX_VERSION = "2.2";
  95 
  96     private static final ResourceBundle bundle =
  97             ResourceBundle.getBundle("com/sun/javafx/tools/packager/Bundle");
  98 
  99     private static final String dtFX = "dtjava.js";
 100 
 101     private static final String webfilesDir = "web-files";
 102     //Note: leading "." is important for IE8
 103     private static final String EMBEDDED_DT = "./"+webfilesDir+"/"+dtFX;
 104 
 105     private static final String PUBLIC_DT = "http://java.com/js/dtjava.js";
 106 
 107     private CreateJarParams createJarParams;
 108     private DeployParams deployParams;
 109     private CreateBSSParams createBssParams;
 110     private File bssTmpDir;
 111     private boolean isSignedJNLP;
 112 
 113 
 114     private enum Filter {ALL, CLASSES_ONLY, RESOURCES};
 115 
 116     private ClassLoader classLoader;
 117 
 118     private ClassLoader getClassLoader() throws PackagerException {
 119         if (classLoader == null) {
 120             try {
 121                 URL[] urls = {new URL(getJfxrtPath())};
 122                 classLoader = URLClassLoader.newInstance(urls);
 123             } catch (MalformedURLException ex) {
 124                 throw new PackagerException(ex, "ERR_CantFindRuntime");
 125             }
 126         }
 127         return classLoader;
 128     }
 129 
 130     public static final String MANIFEST_JAVAFX_MAIN ="JavaFX-Application-Class";
 131 
 132     //  if set of input resources consist of SINGLE element and
 133     //   this element is jar file then we expect this to be request to
 134     //   "update" jar file
 135     //  Input jar file MUST be executable jar file
 136     //
 137     // Check if we are in "special case" scenario
 138     private File jarFileToUpdate(CreateJarParams params) {
 139         if (params.resources.size() == 1) {
 140             PackagerResource p = params.resources.get(0);
 141             File f = p.getFile();
 142             if (!f.isFile() || !f.getAbsolutePath().toLowerCase().endsWith(".jar")) {
 143                 return null;
 144             }
 145             JarFile jf = null;
 146             try {
 147                 jf = new JarFile(f);
 148                 Manifest m = jf.getManifest(); //try to read manifest to validate it is jar
 149                 return f;
 150             } catch (Exception e) {
 151                 //treat any excepion as "not a special case" scenario
 152                 Log.verbose(e);
 153             } finally {
 154                 if (jf != null) {
 155                     try {
 156                         jf.close();
 157                     } catch (IOException ex) {
 158                     }
 159                 }
 160             }
 161         }
 162         return null;
 163     }
 164 
 165     public void packageAsJar(CreateJarParams createJarParams) throws PackagerException {
 166         if (createJarParams == null) {
 167             throw new IllegalArgumentException("Parameters must not be null");
 168         }
 169 
 170         if (createJarParams.outfile == null) {
 171             throw new IllegalArgumentException("Output file is not specified");
 172         }
 173 
 174         this.createJarParams = createJarParams;
 175 
 176         //Special case: could be request for "update jar file"
 177         File jarToUpdate = jarFileToUpdate(createJarParams);
 178         Manifest m = null;
 179 
 180         if (jarToUpdate != null) {
 181             JarFile jf = null;
 182             try {
 183                 //extract data we want to preserve
 184                 Log.info(MessageFormat.format(bundle.getString("MSG_UpdatingJar"), jarToUpdate.getAbsolutePath()));
 185                 jf = new JarFile(jarToUpdate);
 186                 m = jf.getManifest();
 187                 if (m != null) {
 188                     Attributes attrs = m.getMainAttributes();
 189                     if (createJarParams.applicationClass == null) {
 190                         createJarParams.applicationClass =
 191                                 attrs.getValue(Attributes.Name.MAIN_CLASS);
 192                     }
 193                     if (createJarParams.classpath == null) {
 194                         createJarParams.classpath =
 195                                 attrs.getValue(Attributes.Name.CLASS_PATH);
 196                     }
 197                 }
 198             } catch (IOException ex) {
 199                 throw new PackagerException(
 200                         ex, "ERR_FileReadFailed", jarToUpdate.getAbsolutePath());
 201             } finally {
 202                 if (jf != null) {
 203                     try {
 204                         jf.close();
 205                     } catch (IOException ex) {
 206                     }
 207                 }
 208             }
 209         }
 210 
 211         if (createJarParams.applicationClass == null) {
 212             throw new IllegalArgumentException(
 213                     "Main application class is not specified");
 214         }
 215 
 216         //NOTE: This should be a save-to-temp file, then rename operation
 217         File applicationJar = new File(createJarParams.outdir,
 218                         createJarParams.outfile.endsWith(".jar")
 219                               ? createJarParams.outfile
 220                               : createJarParams.outfile + ".jar");
 221 
 222         if (jarToUpdate != null &&
 223                 applicationJar.getAbsoluteFile().equals(jarToUpdate.getAbsoluteFile())) {
 224             try {
 225                 File newInputJar = File.createTempFile("tempcopy", ".jar");
 226                 Files.move(jarToUpdate.toPath(), newInputJar.toPath(),
 227                            StandardCopyOption.REPLACE_EXISTING);
 228                 jarToUpdate = newInputJar;
 229             } catch (IOException ioe) {
 230                 throw new PackagerException(
 231                         ioe, "ERR_FileCopyFailed", jarToUpdate.getAbsolutePath());
 232             }
 233         }
 234 
 235         File parentFile = applicationJar.getParentFile();
 236         if (parentFile != null) {
 237             parentFile.mkdirs();
 238         }
 239 
 240         if (m == null) {
 241             m = new Manifest();
 242         }
 243         Attributes attr = m.getMainAttributes();
 244         attr.put(Attributes.Name.MANIFEST_VERSION, "1.0");
 245         attr.put(new Attributes.Name("Created-By"), "JavaFX Packager");
 246 
 247         if (createJarParams.manifestAttrs != null) {
 248             for (Entry<String, String> e: createJarParams.manifestAttrs.entrySet()) {
 249                 attr.put(new Attributes.Name(e.getKey()), e.getValue());
 250             }
 251         }
 252 
 253         attr.put(Attributes.Name.MAIN_CLASS, createJarParams.applicationClass);
 254         if (createJarParams.classpath != null) {
 255             // Allow comma or semicolon as delimeter (turn them into spaces)
 256             String cp = createJarParams.classpath;
 257             cp = cp.replace(';', ' ').replace(',', ' ');
 258             attr.put(new Attributes.Name("Class-Path"), cp);
 259         }
 260 
 261         attr.put(new Attributes.Name("JavaFX-Version"), createJarParams.fxVersion);
 262 
 263         if (createJarParams.preloader != null) {
 264             attr.put(new Attributes.Name("JavaFX-Preloader-Class"), createJarParams.preloader);
 265         }
 266 
 267 
 268         if (createJarParams.arguments != null) {
 269             int idx = 1;
 270             for (String arg: createJarParams.arguments) {
 271                 attr.put(new Attributes.Name("JavaFX-Argument-"+idx),
 272                         encodeAsBase64(arg.getBytes()));
 273                 idx++;
 274             }
 275         }
 276         if (createJarParams.params != null) {
 277             int idx = 1;
 278             for (Param p : createJarParams.params) {
 279                 if (p.name != null) { //otherwise it is something weird and we skip it
 280                     attr.put(new Attributes.Name("JavaFX-Parameter-Name-" + idx),
 281                             encodeAsBase64(p.name.getBytes()));
 282                     if (p.value != null) { //legal, means not value specified
 283                         attr.put(new Attributes.Name("JavaFX-Parameter-Value-" + idx),
 284                                 encodeAsBase64(p.value.getBytes()));
 285                     }
 286                     idx++;
 287                 }
 288             }
 289         }
 290 
 291 
 292         if (createJarParams.css2bin) {
 293             try {
 294                 bssTmpDir = File.createTempFile("bssfiles", "");
 295             } catch (IOException ex) {
 296                 throw new PackagerException(ex, "ERR_CreatingTempFileFailed");
 297             }
 298             bssTmpDir.delete();
 299         }
 300 
 301         if (applicationJar.exists() && !applicationJar.delete()) {
 302             throw new PackagerException(
 303                     "ERR_CantDeleteFile", createJarParams.outfile);
 304         }
 305         try {
 306             jar(m, createJarParams.resources, jarToUpdate,
 307                     new JarOutputStream(new FileOutputStream(applicationJar)),
 308                     Filter.ALL);
 309         } catch (IOException ex) {
 310             throw new PackagerException(
 311                     ex, "ERR_CreatingJarFailed", createJarParams.outfile);
 312         }
 313 
 314         // cleanup
 315         deleteDirectory(bssTmpDir);
 316         this.createJarParams = null;
 317     }
 318 
 319     private String readTextFile(File in) throws PackagerException {
 320         StringBuilder sb = new StringBuilder();
 321         InputStreamReader isr = null;
 322         try {
 323             char[] buf = new char[16384];
 324             int len;
 325             isr = new InputStreamReader(new FileInputStream(in));
 326             while ((len = isr.read(buf)) > 0) {
 327                 sb.append(buf, sb.length(), len);
 328             }
 329         } catch (IOException ex) {
 330             throw new PackagerException(ex, "ERR_FileReadFailed",
 331                     in.getAbsolutePath());
 332         } finally {
 333             if (isr != null) {
 334                 try {
 335                     isr.close();
 336                 } catch (IOException ex) {
 337                 }
 338             }
 339         }
 340         return sb.toString();
 341     }
 342 
 343     private String processTemplate(String inpText,
 344             Map<TemplatePlaceholders, String> templateStrings) {
 345         //Core pattern matches
 346         //   #DT.SCRIPT#
 347         //   #DT.EMBED.CODE.ONLOAD#
 348         //   #DT.EMBED.CODE.ONLOAD(App2)#
 349         String corePattern = "(#[\\w\\.\\(\\)]+#)";
 350         //This will match
 351         //   "/*", "//" or "<!--" with arbitrary number of spaces
 352         String prefixGeneric = "[\\/\\*-<\\!]*[ \\t]*";
 353         //This will match
 354         //   "/*", "//" or "<!--" with arbitrary number of spaces
 355         String suffixGeneric = "[ \\t]*[\\*\\/>-]*";
 356 
 357         //NB: result core match is group number 1
 358         Pattern mainPattern = Pattern.compile(
 359                 prefixGeneric + corePattern + suffixGeneric);
 360 
 361         Matcher m = mainPattern.matcher(inpText);
 362         StringBuffer result = new StringBuffer();
 363         while (m.find()) {
 364             String match = m.group();
 365             String coreMatch = m.group(1);
 366             //have match, not validate it is not false positive ...
 367             // e.g. if we matched just some spaces in prefix/suffix ...
 368             boolean inComment =
 369                     (match.startsWith("<!--") && match.endsWith("-->")) ||
 370                     (match.startsWith("//")) ||
 371                     (match.startsWith("/*") && match.endsWith(" */"));
 372 
 373             //try to find if we have match
 374             String coreReplacement = null;
 375             //map with rules have no template ids
 376             //int p = coreMatch.indexOf("\\(");
 377             //strip leading/trailing #, then split of id part
 378             String parts[] = coreMatch.substring(1, coreMatch.length()-1).split("[\\(\\)]");
 379             String rulePart = parts[0];
 380             String idPart = (parts.length == 1) ?
 381                     //strip trailing ')'
 382                     null : parts[1];
 383             if (templateStrings.containsKey(
 384                     TemplatePlaceholders.fromString(rulePart))
 385                     && (idPart == null /* it is ok for templeteId to be not null, e.g. DT.SCRIPT.CODE */
 386                         || idPart.equals(deployParams.appId))) {
 387                 coreReplacement = templateStrings.get(
 388                         TemplatePlaceholders.fromString(rulePart));
 389             }
 390 
 391             if (coreReplacement != null) {
 392                 if (inComment || coreMatch.length() == match.length()) {
 393                     m.appendReplacement(result, coreReplacement);
 394                 } else { // pattern matched something that is not comment
 395                          // Very unlikely but lets play it safe
 396                     int pp = match.indexOf(coreMatch);
 397                     String v = match.substring(0, pp) +
 398                             coreReplacement +
 399                             match.substring(pp + coreMatch.length());
 400                     m.appendReplacement(result, v);
 401                 }
 402             }
 403         }
 404         m.appendTail(result);
 405         return result.toString();
 406     }
 407 
 408     private static enum Mode {FX, APPLET, SwingAPP};
 409 
 410     public void generateDeploymentPackages(DeployParams deployParams) throws PackagerException {
 411         if (deployParams == null) {
 412             throw new IllegalArgumentException("Parameters must not be null.");
 413         }
 414         this.deployParams = deployParams;
 415         boolean templateOn = !deployParams.templates.isEmpty();
 416         Map<TemplatePlaceholders, String> templateStrings = null;
 417         if (templateOn) {
 418             templateStrings =
 419                new EnumMap<TemplatePlaceholders, String>(TemplatePlaceholders.class);
 420         }
 421         try {
 422             //In case of FX app we will have one JNLP and one HTML
 423             //In case of Swing with FX we will have 2 JNLP files and one HTML
 424             String jnlp_filename_webstart = deployParams.outfile + ".jnlp";
 425             String jnlp_filename_browser
 426                     = deployParams.isSwingApp ?
 427                         (deployParams.outfile + "_browser.jnlp") : jnlp_filename_webstart;
 428             String html_filename = deployParams.outfile + ".html";
 429 
 430             //create out dir
 431             File odir = deployParams.outdir;
 432             odir.mkdirs();
 433 
 434             if (deployParams.includeDT && !extractWebFiles()) {
 435                 throw new PackagerException("ERR_NoEmbeddedDT");
 436             }
 437 
 438             ByteArrayOutputStream jnlp_bos_webstart = new ByteArrayOutputStream();
 439             ByteArrayOutputStream jnlp_bos_browser = new ByteArrayOutputStream();
 440 
 441             //for swing case we need to generate 2 JNLP files
 442             if (deployParams.isSwingApp) {
 443                PrintStream jnlp_ps = new PrintStream(jnlp_bos_webstart);
 444                generateJNLP(jnlp_ps, jnlp_filename_webstart, Mode.SwingAPP);
 445                jnlp_ps.close();
 446                //save JNLP
 447                save(jnlp_filename_webstart, jnlp_bos_webstart.toByteArray());
 448 
 449                jnlp_ps = new PrintStream(jnlp_bos_browser);
 450                generateJNLP(jnlp_ps, jnlp_filename_browser, Mode.APPLET);
 451                jnlp_ps.close();
 452                //save JNLP
 453                save(jnlp_filename_browser, jnlp_bos_browser.toByteArray());
 454 
 455             } else {
 456                 PrintStream jnlp_ps = new PrintStream(jnlp_bos_browser);
 457                 generateJNLP(jnlp_ps, jnlp_filename_browser, Mode.FX);
 458                 jnlp_ps.close();
 459 
 460                 //save JNLP
 461                 save(jnlp_filename_browser, jnlp_bos_browser.toByteArray());
 462 
 463                 jnlp_bos_webstart = jnlp_bos_browser;
 464             }
 465 
 466             //we do not need html if this is component and not main app
 467             if (!deployParams.isExtension) {
 468                 ByteArrayOutputStream html_bos =
 469                         new ByteArrayOutputStream();
 470                 PrintStream html_ps = new PrintStream(html_bos);
 471                 generateHTML(html_ps,
 472                         jnlp_bos_browser.toByteArray(), jnlp_filename_browser,
 473                         jnlp_bos_webstart.toByteArray(), jnlp_filename_webstart,
 474                         templateStrings, deployParams.isSwingApp);
 475                 html_ps.close();
 476 
 477                 //process template file
 478                 if (templateOn) {
 479                     for (DeployParams.Template t: deployParams.templates) {
 480                         File out = t.out;
 481                         if (out == null) {
 482                             System.out.println(
 483                                     "Perform inplace substitution for " +
 484                                     t.in.getAbsolutePath());
 485                             out = t.in;
 486                         }
 487                         save(out, processTemplate(
 488                                 readTextFile(t.in), templateStrings).getBytes());
 489                     }
 490                 } else {
 491                     //save HTML
 492                     save(html_filename, html_bos.toByteArray());
 493                 }
 494             }
 495 
 496             //copy jar files
 497             for (DeployResource resource: deployParams.resources) {
 498                 copyFiles(resource, deployParams.outdir);
 499             }
 500         } catch (Exception ex) {
 501             throw new PackagerException(ex, "ERR_DeployFailed");
 502         }
 503 
 504         BundleParams bp = deployParams.getBundleParams();
 505         if (bp != null) {
 506            generateNativeBundles(deployParams.outdir, bp.getBundleParamsAsMap(), deployParams.getBundleType(), deployParams.getTargetFormat(), deployParams.verbose);
 507         }
 508 
 509         this.deployParams = null;
 510     }
 511 
 512     private void generateNativeBundles(File outdir, Map<String, ? super Object> params, BundleType bundleType, String bundleFormat, boolean verbose) {
 513         outdir = new File(outdir, "bundles");
 514 
 515         if (params.containsKey(RUNTIME.getID())) {
 516             RelativeFileSet runtime = RUNTIME.fetchFrom(params);
 517             if (runtime == null) {
 518                 Log.info(bundle.getString("MSG_NoJREPackaged"));
 519             } else {
 520                 Log.info(MessageFormat.format(bundle.getString("MSG_UserProvidedJRE"), runtime.getBaseDirectory().getAbsolutePath()));
 521                 if (Log.isDebug()) {
 522                     runtime.dump();
 523                 }
 524             }
 525         } else {
 526             Log.info(bundle.getString("MSG_UseSystemJRE"));
 527         }
 528 
 529         for (com.oracle.bundlers.Bundler bundler : Bundlers.createBundlersInstance().getBundlers(bundleType)) {
 530             // if they specify the bundle format, require we match the ID
 531             if (bundleFormat != null && !bundleFormat.equals(bundler.getID())) continue;
 532             
 533             try {
 534                 if (bundler.validate(params)) {
 535                     bundler.execute(params, outdir);
 536                 }
 537                 
 538             } catch (UnsupportedPlatformException e) {
 539                 Log.debug(MessageFormat.format(bundle.getString("MSG_BundlerPlatformException"), bundler.getName()));
 540             } catch (ConfigException e) {
 541                 Log.info(MessageFormat.format(bundle.getString("MSG_BundlerConfigException"), bundler.getName(), e.getMessage(), e.getAdvice()));
 542             } catch (RuntimeException re) {
 543                 Log.info(MessageFormat.format(bundle.getString("MSG_BundlerRuntimeException"), bundler.getName(), re.toString()));
 544                 Log.debug(re);
 545             }
 546         }
 547     }
 548 
 549     private static void copyFiles(DeployResource resource, File outdir) throws IOException, PackagerException {
 550 
 551         if (resource.getFile().isDirectory()) {
 552             final File baseDir = resource.getBaseDir();
 553             File[] children = resource.getFile().listFiles();
 554             if (children != null) {
 555                 for (File file : children) {
 556                     copyFiles(new DeployResource(baseDir, file), outdir);
 557                 }
 558             }
 559         } else {
 560             final File srcFile = resource.getFile();
 561             if (srcFile.exists() && srcFile.isFile()) {
 562                 //skip file copying if jar is in the same location
 563                 final File destFile =
 564                         new File(outdir, resource.getRelativePath());
 565 
 566                 if (!srcFile.getCanonicalFile().equals(
 567                         destFile.getCanonicalFile())) {
 568                     copyFileToOutDir(new FileInputStream(srcFile),
 569                                      destFile);
 570                 } else {
 571                     Log.verbose(MessageFormat.format(bundle.getString("MSG_JarNoSelfCopy"), resource.getRelativePath()));
 572                 }
 573             }
 574         }
 575     }
 576 
 577     public void generateBSS(CreateBSSParams params) throws PackagerException {
 578         if (params == null) {
 579             throw new IllegalArgumentException("Parameters must not be null.");
 580         }
 581         this.createBssParams = params;
 582         createBinaryCss(createBssParams.resources, createBssParams.outdir);
 583         this.createBssParams = null;
 584     }
 585 
 586     public void signJar(SignJarParams params) throws PackagerException {
 587         try {
 588             JarSignature signature = retrieveSignature(params);
 589 
 590             for (PackagerResource pr: params.resources) {
 591                 signFile(pr, signature, params.outdir, params.verbose);
 592             }
 593 
 594         } catch (Exception ex) {
 595             Log.verbose(ex);
 596             throw new PackagerException("ERR_SignFailed", ex);
 597         }
 598 
 599     }
 600 
 601 
 602     private JarSignature retrieveSignature(SignJarParams params) throws KeyStoreException,
 603             NoSuchAlgorithmException, UnrecoverableKeyException, IOException,
 604             CertificateException, InvalidKeyException {
 605         if (params.keyPass == null) {
 606             params.keyPass = params.storePass;
 607         }
 608 
 609         if (params.keyStore == null) {
 610             throw new IOException("No keystore specified");
 611         }
 612 
 613         if (params.storePass == null) {
 614             throw new IOException("No store password specified");
 615         }
 616 
 617         if (params.storeType == null) {
 618             throw new IOException("No store type is specified");
 619         }
 620 
 621         KeyStore store = KeyStore.getInstance(params.storeType);
 622         store.load(new FileInputStream(params.keyStore), params.storePass.toCharArray());
 623 
 624         Certificate[] chain = store.getCertificateChain(params.alias);
 625         X509Certificate certChain[] = new X509Certificate[chain.length];
 626         for (int i=0; i<chain.length; i++) {
 627             certChain[i] = (X509Certificate) chain[i];
 628         }
 629 
 630         PrivateKey privateKey = (PrivateKey)
 631                 store.getKey(params.alias, params.keyPass.toCharArray());
 632 
 633         return JarSignature.create(privateKey, certChain);
 634     }
 635 
 636     private void signFile(
 637             PackagerResource pr, JarSignature signature, File outdir, boolean verbose)
 638                throws NoSuchAlgorithmException, IOException, SignatureException {
 639         if (pr.getFile().isDirectory()) {
 640             File[] children = pr.getFile().listFiles();
 641             if (children != null) {
 642                 for (File innerFile : children) {
 643                     signFile(new PackagerResource(
 644                             pr.getBaseDir(), innerFile), signature, outdir, verbose);
 645                 }
 646             }
 647         } else {
 648             File jar = pr.getFile();
 649             File parent = jar.getParentFile();
 650             String name = "bsigned_" + jar.getName();
 651             File signedJar = new File(parent, name);
 652 
 653             System.out.println("Signing (BLOB) " + jar.getPath());
 654 
 655             signAsBLOB(jar, signedJar, signature);
 656 
 657             File destJar;
 658             if (outdir != null) {
 659                 destJar = new File(outdir, pr.getRelativePath());
 660             } else {
 661                 // in-place
 662                 jar.delete();
 663                 destJar = jar;
 664             }
 665             destJar.delete();
 666             destJar.getParentFile().mkdirs();
 667             signedJar.renameTo(destJar);
 668             if (verbose) {
 669                System.out.println("Signed as " + destJar.getPath());
 670             }
 671         }
 672     }
 673 
 674     private void signAsBLOB(final File jar, File signedJar, JarSignature signature)
 675             throws IOException, NoSuchAlgorithmException, SignatureException
 676     {
 677         if (signature == null) {
 678             throw new IllegalStateException("Should retrieve signature first");
 679         }
 680 
 681         InputStreamSource in = new InputStreamSource() {
 682             @Override
 683             public InputStream getInputStream() throws IOException {
 684                 return new FileInputStream(jar);
 685             }
 686         };
 687         if (!signedJar.isFile()) {
 688             signedJar.createNewFile();
 689         }
 690         FileOutputStream fos = new FileOutputStream(signedJar);
 691         signature.signJarAsBLOB(in, new ZipOutputStream(fos));
 692     }
 693 
 694 
 695 
 696     public void makeAll(MakeAllParams makeAllParams) throws PackagerException {
 697         final String exe =
 698                 System.getProperty("os.name").startsWith("Windows") ? ".exe" : "";
 699         String jHome = System.getenv("JAVA_HOME");
 700         if (jHome == null) {
 701             jHome = System.getProperty("java.home");
 702         }
 703         if (jHome == null) {
 704             throw new PackagerException("ERR_MissingJavaHome");
 705         }
 706 
 707         final File javac = new File(new File(jHome), "bin/javac" + exe);
 708 
 709         String jfxHome = System.getenv("JAVAFX_HOME");
 710         if (jfxHome == null) {
 711             jfxHome = System.getProperty("javafx.home");
 712         }
 713         if (jfxHome == null) {
 714             throw new PackagerException("ERR_MissingJavaFxHome");
 715         }
 716 
 717         final String srcDirName = "src";
 718         final String compiledDirName = "compiled";
 719         final String distDirName = "dist";
 720         final String outfileName = "dist";
 721         final String jarName = outfileName + ".jar";
 722 
 723         final File distDir = new File(distDirName);
 724 
 725         final File compiledDir = new File(compiledDirName);
 726         compiledDir.mkdir();
 727 
 728         try {
 729             final File tmpFile = File.createTempFile("javac", "sources", new File("."));
 730             tmpFile.deleteOnExit();
 731             final FileWriter sources = new FileWriter(tmpFile);
 732             try {
 733                 scanAndCopy(new PackagerResource(new File(srcDirName), "."), sources, compiledDir);
 734             } finally {
 735                 sources.close();
 736             }
 737             String classpath = jfxHome + "/../rt/lib/ext/jfxrt.jar";
 738             if (makeAllParams.classpath != null) {
 739                 classpath += File.pathSeparator + makeAllParams.classpath;
 740             }
 741             if (makeAllParams.verbose) {
 742                 System.out.println("Executing javac:");
 743                 System.out.printf("%s %s %s %s %s %s%n",
 744                         javac.getAbsolutePath(),
 745                         "-d", compiledDirName,
 746                         "-cp", classpath,
 747                         "@" + tmpFile.getAbsolutePath());
 748             }
 749             int ret = execute(
 750                     javac.getAbsolutePath(),
 751                     "-d", compiledDirName,
 752                     "-cp", classpath,
 753                     "@" + tmpFile.getAbsolutePath());
 754             if (ret != 0) {
 755                 throw new PackagerException("ERR_JavacFailed", Integer.toString(ret));
 756             }
 757         } catch (PackagerException e) {
 758             throw e;
 759         } catch (Exception e) {
 760             throw new PackagerException(e, "ERR_MakeAllJavacFailed");
 761         }
 762 
 763         CreateJarParams cjp = new CreateJarParams();
 764         cjp.applicationClass = makeAllParams.appClass;
 765         cjp.preloader = makeAllParams.preloader;
 766         cjp.classpath = makeAllParams.classpath;
 767         cjp.css2bin = false;
 768         cjp.outdir = distDir;
 769         cjp.outfile = jarName;
 770         cjp.addResource(compiledDir, ".");
 771 
 772         packageAsJar(cjp);
 773 
 774         DeployParams dp = new DeployParams();
 775         dp.applicationClass = makeAllParams.appClass;
 776         dp.appName = makeAllParams.appName;
 777         dp.description = "Application description";
 778         dp.height = makeAllParams.height;
 779         dp.width = makeAllParams.width;
 780         dp.vendor = "Application vendor";
 781         dp.outdir = distDir;
 782         dp.outfile = outfileName;
 783         dp.addResource(distDir, jarName);
 784         dp.setBundleType(BundleType.ALL);
 785 
 786         generateDeploymentPackages(dp);
 787 
 788         deleteDirectory(compiledDir);
 789     }
 790 
 791     private static int execute(Object ... args) throws IOException, InterruptedException {
 792         final ArrayList<String> argsList = new ArrayList();
 793         for (Object a : args) {
 794             if (a instanceof List) {
 795                 argsList.addAll((List)a);
 796             } else if (a instanceof String) {
 797                 argsList.add((String)a);
 798             }
 799         }
 800         final Process p = Runtime.getRuntime().exec(argsList.toArray(new String[argsList.size()]));
 801         final BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
 802         Thread t = new Thread(new Runnable() {
 803             @Override
 804             public void run() {
 805                 try {
 806                     String line;
 807                     while ((line = in.readLine()) != null) {
 808                         System.out.println(line);
 809                     }
 810                 } catch (IOException ioe) {
 811                     Log.verbose(ioe);
 812                 }
 813             }
 814         });
 815         t.setDaemon(true);
 816         t.start();
 817         final BufferedReader err = new BufferedReader(new InputStreamReader(p.getErrorStream()));
 818         t = new Thread(new Runnable() {
 819             @Override
 820             public void run() {
 821                 try {
 822                     String line;
 823                     while ((line = err.readLine()) != null) {
 824                         System.err.println(line);
 825                     }
 826                 } catch (IOException ioe) {
 827                     Log.verbose(ioe);
 828                 }
 829             }
 830         });
 831         t.setDaemon(true);
 832         t.start();
 833         return p.waitFor();
 834     }
 835 
 836     private static void scanAndCopy(PackagerResource dir, Writer out, File outdir) throws PackagerException {
 837         if (!dir.getFile().exists()) {
 838             throw new PackagerException("ERR_MissingDirectory", dir.getFile().getName());
 839         }
 840         if ((dir.getFile().listFiles() == null) || (dir.getFile().listFiles().length == 0)) {
 841             throw new PackagerException("ERR_EmptySourceDirectory", dir.getFile().getName());
 842         }
 843         try {
 844             for (File f : dir.getFile().listFiles()) {
 845                 if (f.isDirectory()) {
 846                     scanAndCopy(new PackagerResource(dir.getBaseDir(), f), out, outdir);
 847                 } else if (f.getName().endsWith(".java")) {
 848                     out.write('\'' + f.getAbsolutePath().replace('\\', '/') + "\'\n");
 849                 } else {
 850                     copyFileToOutDir(new FileInputStream(f),
 851                             new File(outdir.getPath() + File.separator
 852                             + dir.getRelativePath() + File.separator
 853                             + f.getName()));
 854                 }
 855             }
 856         } catch (IOException ex) {
 857             throw new PackagerException("ERR_FileCopyFailed", dir.getFile().getName());
 858         }
 859     }
 860 
 861     //return null if args are default
 862     private String getJvmArguments(boolean includeProperties) {
 863         StringBuilder sb = new StringBuilder();
 864         for(String v: deployParams.jvmargs) {
 865             sb.append(v);  //may need to escape if parameter has spaces
 866             sb.append(" ");
 867         }
 868         if (includeProperties) {
 869             for(String k: deployParams.properties.keySet()) {
 870                 sb.append("-D");
 871                 sb.append(k);
 872                 sb.append("=");
 873                 sb.append(deployParams.properties.get(k)); //may need to escape if value has spaces
 874                 sb.append(" ");
 875             }
 876         }
 877         if (sb.length() > 0) {
 878             return sb.toString();
 879         }
 880         return null;
 881     }
 882 
 883     private void generateJNLP(PrintStream out, String jnlp_filename, Mode m)
 884             throws IOException, CertificateEncodingException
 885     {
 886         out.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
 887         //have to use "old" spec version or old javaws will fail
 888         // with "unknown" version exception ...
 889         out.println("<jnlp spec=\"1.0\" xmlns:jfx=\"http://javafx.com\"" +
 890                 (deployParams.codebase != null ?
 891                      " codebase=\"" + deployParams.codebase + "\"" : "") +
 892                 " href=\""+jnlp_filename+"\">");
 893         out.println("  <information>");
 894         out.println("    <title>" +
 895                 ((deployParams.title != null)
 896                 ? deployParams.title : "Sample JavaFX Application") +
 897                 "</title>");
 898         out.println("    <vendor>" +
 899                 ((deployParams.vendor != null)
 900                 ? deployParams.vendor : "Unknown vendor") +
 901                 "</vendor>");
 902         out.println("    <description>" +
 903                 ((deployParams.description != null)
 904                 ? deployParams.description : "Sample JavaFX 2.0 application.") +
 905                 "</description>");
 906         for (Iterator<Icon> it = deployParams.icons.iterator(); it.hasNext();) {
 907             DeployParams.Icon i = it.next();
 908             if (i.mode == DeployParams.RunMode.WEBSTART ||
 909                     i.mode == DeployParams.RunMode.ALL) {
 910             out.println("    <icon href=\"" + i.href+"\" " +
 911                 ((i.kind != null) ? " kind=\"" + i.kind + "\"" : "") +
 912                 ((i.width != DeployParams.Icon.UNDEFINED) ?
 913                     " width=\"" + i.width + "\"" : "") +
 914                 ((i.height != DeployParams.Icon.UNDEFINED) ?
 915                     " height=\"" + i.height + "\"" : "") +
 916                 ((i.depth != DeployParams.Icon.UNDEFINED) ?
 917                     " depth=\"" + i.depth + "\"" : "") +
 918                 "/>");
 919             }
 920         }
 921 
 922         if (deployParams.offlineAllowed && !deployParams.isExtension) {
 923             out.println("    <offline-allowed/>");
 924         }
 925         out.println("  </information>");
 926 
 927         if (!deployParams.isExtension) {
 928             //FX is platfrom specific (soon will be available for Mac and Linux too)
 929             out.println("  <resources>");
 930             out.println("    <jfx:javafx-runtime version=\"" +
 931                     deployParams.fxPlatform +
 932                     "\" href=\"http://javadl.sun.com/webapps/download/GetFile/javafx-latest/windows-i586/javafx2.jnlp\"/>");
 933             out.println("  </resources>");
 934         }
 935 
 936         boolean needToCloseResourceTag = false;
 937         //jre is available for all platforms
 938         if (!deployParams.isExtension) {
 939             out.println("  <resources>");
 940             needToCloseResourceTag = true;
 941 
 942             String vmargs = getJvmArguments(false);
 943             vmargs = (vmargs == null) ? "" : " java-vm-args=\""+vmargs+"\" ";
 944             out.println("    <j2se version=\"" + deployParams.jrePlatform + "\"" +
 945                     vmargs + " href=\"http://java.sun.com/products/autodl/j2se\"/>");
 946             for (String k : deployParams.properties.keySet()) {
 947                 out.println("    <property name=\"" + k +
 948                         "\" value=\"" + deployParams.properties.get(k) + "\"/>");
 949             }
 950         }
 951         String currentOS = null, currentArch = null;
 952         //NOTE: This should sort the list by os+arch; it will reduce the number of resource tags
 953         String pendingPrint = null;
 954         for (DeployResource resource: deployParams.resources) {
 955             //if not same OS or arch then open new resources element
 956             if (!needToCloseResourceTag ||
 957                 ((currentOS == null && resource.getOs() != null) ||
 958                  currentOS != null && !currentOS.equals(resource.getOs())) ||
 959                 ((currentArch == null && resource.getArch() != null) ||
 960                  currentArch != null && !currentArch.equals(resource.getArch()))) {
 961 
 962                 //we do not print right a way as it may be empty block
 963                 // Not all resources make sense for JNLP (e.g. data or license)
 964                 if (needToCloseResourceTag) {
 965                    pendingPrint = "  </resources>\n";
 966                 } else {
 967                     pendingPrint = "";
 968                 }
 969                 currentOS = resource.getOs();
 970                 currentArch = resource.getArch();
 971                 pendingPrint += "  <resources" +
 972                         ((currentOS != null) ? " os=\"" + currentOS + "\"" : "") +
 973                         ((currentArch != null) ? " arch=\""+currentArch+"\"" : "") +
 974                         ">\n";
 975             }
 976             final File srcFile = resource.getFile();
 977             if (srcFile.exists() && srcFile.isFile()) {
 978                 final String relativePath = resource.getRelativePath();
 979                 DeployResource.Type type = resource.getType();
 980                 switch (type) {
 981                     case jar:
 982                         if (pendingPrint != null) {
 983                             out.print(pendingPrint);
 984                             pendingPrint = null;
 985                             needToCloseResourceTag = true;
 986                         }
 987                         out.print("    <jar href=\"" + relativePath + "\" size=\""
 988                                 + srcFile.length() + "\"");
 989                         out.print(" download=\"" + resource.getMode() + "\" ");
 990                         out.println("/>");
 991                         break;
 992                     case jnlp:
 993                         if (pendingPrint != null) {
 994                             out.print(pendingPrint);
 995                             pendingPrint = null;
 996                             needToCloseResourceTag = true;
 997                         }
 998                         out.println("    <extension href=\"" + relativePath + "\"/>");
 999                         break;
1000                     case nativelib:
1001                         if (pendingPrint != null) {
1002                             out.print(pendingPrint);
1003                             needToCloseResourceTag = true;
1004                             pendingPrint = null;
1005                         }
1006                         out.println("    <nativelib href=\"" + relativePath + "\"/>");
1007                         break;
1008                 }
1009             }
1010         }
1011         if (needToCloseResourceTag) {
1012             out.println("  </resources>");
1013         }
1014 
1015         if (deployParams.allPermissions) {
1016             out.println("<security>");
1017             out.println("  <all-permissions/>");
1018             processEmbeddedCertificates(out);
1019             out.println("</security>");
1020         }
1021 
1022         if (deployParams.needShortcut) {
1023             out.println("  <shortcut><desktop/></shortcut>");
1024 
1025 //            //TODO: Add support for a more sophisticated shortcut tag.
1026 //  <shortcut/> // install no shortcuts, and do not consider "installed"
1027 //  <shortcut installed="true"/> // install no shortcuts, but consider "installed"
1028 //  <shortcut installed="false"><desktop/></shortcut> // install desktop shortcut, but do not consider the app "installed"
1029 //  <shortcut installed="true"><menu/></shortcut> // install menu shortcut, and consider app "installed"
1030         }
1031 
1032         if (!deployParams.isExtension) {
1033             if (m == Mode.APPLET) {
1034                 out.print("  <applet-desc  width=\"" + deployParams.width
1035                         + "\" height=\"" + deployParams.height + "\"");
1036 
1037                 out.print(" main-class=\"" + deployParams.applicationClass + "\" ");
1038                 out.println(" name=\"" + deployParams.appName + "\" >");
1039                 if (deployParams.params != null) {
1040                     for (Param p : deployParams.params) {
1041                         out.println("    <param name=\"" + p.name + "\""
1042                                 + (p.value != null
1043                                 ? (" value=\"" + p.value + "\"") : "")
1044                                 + "/>");
1045                     }
1046                 }
1047                 out.println("  </applet-desc>");
1048             } else if (m == Mode.SwingAPP) {
1049                 out.print("  <application-desc main-class=\"" + deployParams.applicationClass + "\" ");
1050                 out.println(" name=\"" + deployParams.appName + "\" >");
1051                 if (deployParams.arguments != null) {
1052                     for (String a : deployParams.arguments) {
1053                         out.println("    <argument>" + a + "</argument>");
1054                     }
1055                 }
1056                 out.println("  </application-desc>");
1057             } else { //JavaFX application
1058                 //embed fallback application
1059                 if (deployParams.fallbackApp != null) {
1060                     out.print("  <applet-desc  width=\"" + deployParams.width
1061                             + "\" height=\"" + deployParams.height + "\"");
1062 
1063                     out.print(" main-class=\"" + deployParams.fallbackApp + "\" ");
1064                     out.println(" name=\"" + deployParams.appName + "\" >");
1065                     out.println("    <param name=\"requiredFXVersion\" value=\""
1066                             + deployParams.fxPlatform + "\"/>");
1067                     out.println("  </applet-desc>");
1068                 }
1069 
1070                 //javafx application descriptor
1071                 out.print("  <jfx:javafx-desc  width=\"" + deployParams.width
1072                         + "\" height=\"" + deployParams.height + "\"");
1073 
1074                 out.print(" main-class=\"" + deployParams.applicationClass + "\" ");
1075                 out.print(" name=\"" + deployParams.appName + "\" ");
1076                 if (deployParams.preloader != null) {
1077                     out.print(" preloader-class=\"" + deployParams.preloader + "\"");
1078                 }
1079                 if (((deployParams.params == null) || deployParams.params.isEmpty())
1080                         && (deployParams.arguments == null || deployParams.arguments.isEmpty())) {
1081                     out.println("/>");
1082                 } else {
1083                     out.println(">");
1084                     if (deployParams.params != null) {
1085                         for (Param p : deployParams.params) {
1086                             out.println("    <fx:param name=\"" + p.name + "\""
1087                                     + (p.value != null
1088                                     ? (" value=\"" + p.value + "\"") : "")
1089                                     + "/>");
1090                         }
1091                     }
1092                     if (deployParams.arguments != null) {
1093                         for (String a : deployParams.arguments) {
1094                             out.println("    <fx:argument>" + a + "</fx:argument>");
1095                         }
1096                     }
1097                     out.println("  </jfx:javafx-desc>");
1098                 }
1099             }
1100         } else {
1101             out.println("<component-desc/>");
1102         }
1103 
1104         out.println("  <update check=\"" + deployParams.updateMode + "\"/>");
1105         out.println("</jnlp>");
1106     }
1107 
1108 
1109 
1110     private void addToList(List<String> l, String name, String value, boolean isString) {
1111         String s = isString ? "'" : "";
1112         String v = name +" : " + s + value + s;
1113         l.add(v);
1114     }
1115 
1116     private String listToString(List<String> lst, String offset) {
1117         StringBuilder b = new StringBuilder();
1118         if (lst == null || lst.isEmpty()) {
1119             return offset + "{}";
1120         }
1121 
1122         b.append(offset).append("{\n");
1123         boolean first = true;
1124         for (String s : lst) {
1125             if (!first) {
1126                 b.append(",\n");
1127             }
1128             first = false;
1129             b.append(offset).append("    ");
1130             b.append(s);
1131         }
1132         b.append("\n");
1133         b.append(offset).append("}");
1134         return b.toString();
1135     }
1136 
1137     private String encodeAsBase64(byte inp[]) {
1138         BASE64Encoder encoder = new BASE64Encoder();
1139         return encoder.encode(inp);
1140     }
1141 
1142     private void generateHTML(PrintStream out,
1143             byte[] jnlp_bytes_browser, String jnlpfile_browser,
1144             byte[] jnlp_bytes_webstart, String jnlpfile_webstart,
1145             Map<TemplatePlaceholders, String> templateStrings,
1146             boolean swingMode) {
1147         String poff = "    ";
1148         String poff2 = poff + poff;
1149         String poff3 = poff2 + poff;
1150 
1151         StringBuilder out_embed_dynamic = new StringBuilder();
1152         StringBuilder out_embed_onload = new StringBuilder();
1153         StringBuilder out_launch_code = new StringBuilder();
1154 
1155         String appletParams = getAppletParameters();
1156         String jnlp_content_browser = null;
1157         String jnlp_content_webstart = null;
1158 
1159         if (deployParams.embedJNLP) {
1160             jnlp_content_browser =
1161                     encodeAsBase64(jnlp_bytes_browser).replaceAll("\\r|\\n", "");
1162             jnlp_content_webstart =
1163                     encodeAsBase64(jnlp_bytes_webstart).replaceAll("\\r|\\n", "");
1164         }
1165 
1166         out.println("<html><head>");
1167         String dtURL = deployParams.includeDT ? EMBEDDED_DT : PUBLIC_DT;
1168         String includeDtString = "<SCRIPT src=\"" + dtURL + "\"></SCRIPT>";
1169         if (templateStrings != null) {
1170             templateStrings.put(TemplatePlaceholders.SCRIPT_URL, dtURL);
1171             templateStrings.put(TemplatePlaceholders.SCRIPT_CODE, includeDtString);
1172         }
1173         out.println("  " + includeDtString);
1174 
1175         String webstartError = "System is not setup to launch JavaFX applications. " +
1176                 "Make sure that you have a recent Java runtime, then install JavaFX Runtime 2.0 "+
1177                 "and check that JavaFX is enabled in the Java Control Panel.";
1178 
1179         List w_app = new ArrayList();
1180         List w_platform = new ArrayList();
1181         List w_callback = new ArrayList();
1182 
1183         addToList(w_app, "url", jnlpfile_webstart, true);
1184         if (jnlp_content_webstart != null) {
1185             addToList(w_app, "jnlp_content", jnlp_content_webstart, true);
1186         }
1187 
1188         addToList(w_platform, "javafx", deployParams.fxPlatform, true);
1189         String vmargs = getJvmArguments(true);
1190         if (vmargs != null) {
1191             addToList(w_platform, "jvmargs", vmargs, true);
1192         }
1193 
1194         if (!"".equals(appletParams)) {
1195             addToList(w_app, "params", "{"+appletParams+"}", false);
1196         }
1197 
1198         if ((deployParams.callbacks != null) && !deployParams.callbacks.isEmpty()) {
1199             for (JSCallback cb: deployParams.callbacks) {
1200                 addToList(w_callback, cb.getName(), cb.getCmd(), false);
1201             }
1202         }
1203 
1204         //prepare content of launchApp function
1205         out_launch_code.append(poff2).append("dtjava.launch(");
1206         out_launch_code.append(listToString(w_app, poff3)).append(",\n");
1207         out_launch_code.append(listToString(w_platform, poff3)).append(",\n");
1208         out_launch_code.append(listToString(w_callback, poff3)).append("\n");
1209         out_launch_code.append(poff2).append(");\n");
1210 
1211         out.println("<script>");
1212         out.println(poff  + "function launchApplication(jnlpfile) {");
1213         out.print(out_launch_code.toString());
1214         out.println(poff2 + "return false;");
1215         out.println(poff + "}");
1216         out.println("</script>");
1217 
1218         if (templateStrings != null) {
1219             templateStrings.put(TemplatePlaceholders.LAUNCH_CODE,
1220                     out_launch_code.toString());
1221         }
1222 
1223         //applet deployment
1224         String appId = deployParams.appId; //if null then it will be autogenerated
1225         String placeholder = deployParams.placeholder;
1226         if (placeholder == null) { //placeholder can not be null
1227             placeholder = "'javafx-app-placeholder'";
1228         }
1229 
1230         //prepare content of embedApp()
1231         List p_app = new ArrayList();
1232         List p_platform = new ArrayList();
1233         List p_callback = new ArrayList();
1234 
1235         if (appId != null) {
1236             addToList(p_app, "id", appId, true);
1237         }
1238         if (deployParams.isSwingApp) {
1239           addToList(p_app, "toolkit", "swing", true);
1240         }
1241         addToList(p_app, "url", jnlpfile_browser, true);
1242         addToList(p_app, "placeholder", placeholder, false);
1243         if (deployParams.embeddedWidth != null && deployParams.embeddedHeight != null) {
1244           addToList(p_app, "width", ""+deployParams.embeddedWidth, true);
1245           addToList(p_app, "height", ""+deployParams.embeddedHeight, true);
1246         } else {
1247           addToList(p_app, "width", ""+deployParams.width, false);
1248           addToList(p_app, "height", ""+deployParams.height, false);
1249         }
1250         if (jnlp_content_browser != null) {
1251             addToList(p_app, "jnlp_content", jnlp_content_browser, true);
1252         }
1253 
1254         addToList(p_platform, "javafx", deployParams.fxPlatform, true);
1255         if (vmargs != null) {
1256             addToList(p_platform, "jvmargs", vmargs, true);
1257         }
1258 
1259         if ((deployParams.callbacks != null) && !deployParams.callbacks.isEmpty()) {
1260             for (JSCallback cb: deployParams.callbacks) {
1261                 addToList(p_callback, cb.getName(), cb.getCmd(), false);
1262             }
1263         }
1264 
1265         if (!"".equals(appletParams)) {
1266             addToList(p_app, "params", "{"+appletParams+"}", false);
1267         }
1268 
1269         if (swingMode) {
1270             //Splash will not work in SwingMode
1271             //Unless user overwrites onGetSplash handler (and that means he handles splash on his own)
1272             // we will reset splash function to be "none"
1273             boolean needOnGetSplashImpl = true;
1274             if (deployParams.callbacks != null) {
1275                 for (JSCallback c: deployParams.callbacks) {
1276                     if ("onGetSplash".equals(c.getName())) {
1277                         needOnGetSplashImpl = false;
1278                     }
1279                 }
1280             }
1281 
1282             if (needOnGetSplashImpl) {
1283                 addToList(p_callback, "onGetSplash", "function() {}", false);
1284             }
1285         }
1286 
1287         out_embed_dynamic.append("dtjava.embed(\n");
1288         out_embed_dynamic.append(listToString(p_app, poff3)).append(",\n");
1289         out_embed_dynamic.append(listToString(p_platform, poff3)).append(",\n");
1290         out_embed_dynamic.append(listToString(p_callback, poff3)).append("\n");
1291 
1292         out_embed_dynamic.append(poff2).append(");\n");
1293 
1294         //now wrap content with function
1295         String embedFuncName = "javafxEmbed" +
1296                 ((deployParams.appId != null) ?
1297                    "_"+deployParams.appId : "");
1298         out_embed_onload.append("\n<script>\n");
1299         out_embed_onload.append(poff).append("function ").append(embedFuncName).append("() {\n");
1300         out_embed_onload.append(poff2);
1301         out_embed_onload.append(out_embed_dynamic);
1302         out_embed_onload.append(poff).append("}\n");
1303 
1304         out_embed_onload.append(poff).append(
1305             "<!-- Embed FX application into web page once page is loaded -->\n");
1306         out_embed_onload.append(poff).append("dtjava.addOnloadCallback(").append(embedFuncName).append(
1307             ");\n");
1308         out_embed_onload.append("</script>\n");
1309 
1310         if (templateStrings != null) {
1311             templateStrings.put(
1312                     TemplatePlaceholders.EMBED_CODE_ONLOAD,
1313                     out_embed_onload.toString());
1314             templateStrings.put(
1315                     TemplatePlaceholders.EMBED_CODE_DYNAMIC,
1316                     out_embed_dynamic.toString());
1317         }
1318 
1319         out.println(out_embed_onload.toString());
1320 
1321         out.println("</head><body>");
1322         out.println("<h2>Test page for <b>"+deployParams.appName+"</b></h2>");
1323         String launchString = "return launchApplication('" + jnlpfile_webstart + "');";
1324         out.println("  <b>Webstart:</b> <a href='" + jnlpfile_webstart +
1325                 "' onclick=\"" + launchString + "\">"
1326                     + "click to launch this app as webstart</a><br><hr><br>");
1327         out.println("");
1328         out.println("  <!-- Applet will be inserted here -->");
1329         //placeholder is wrapped with single quotes already
1330         out.println("  <div id="+placeholder+"></div>");
1331         out.println("</body></html>");
1332     }
1333 
1334     private void save(String fname, byte[] content) throws IOException {
1335         File odir = deployParams.outdir;
1336         save(new File(odir, fname), content);
1337     }
1338 
1339     private void save(File f, byte[] content) throws IOException {
1340         if (f.exists()) {
1341             f.delete();
1342         }
1343         FileOutputStream fos = new FileOutputStream(f);
1344         fos.write(content);
1345         fos.close();
1346     }
1347 
1348     private static void copyFileToOutDir(
1349             InputStream is, File fout) throws PackagerException {
1350 
1351         OutputStream out = null;
1352         final File outDir = fout.getParentFile();
1353         try {
1354             if (!outDir.exists() && !outDir.mkdirs()) {
1355                 throw new PackagerException("ERR_CreatingDirFailed", outDir.getPath());
1356             }
1357 
1358             out = new FileOutputStream(fout);
1359             byte[] buf = new byte[16384];
1360             int len;
1361             while ((len = is.read(buf)) > 0) {
1362                 out.write(buf, 0, len);
1363             }
1364         } catch (IOException ex) {
1365             throw new PackagerException(ex, "ERR_FileCopyFailed", outDir.getPath());
1366         } finally {
1367             try {
1368                 is.close();
1369             } catch (IOException ex) {
1370             }
1371 
1372             if (out != null) {
1373                 try {
1374                     out.close();
1375                 } catch (IOException ex) {
1376                 }
1377             }
1378         }
1379     }
1380 
1381 
1382     private String getAppletParameters() {
1383         String result = "";
1384         if (deployParams.htmlParams != null) {
1385             for (HtmlParam p: deployParams.htmlParams) {
1386                 if (!result.isEmpty()) {
1387                     result += ", ";
1388                 }
1389                 String escape = p.needEscape ? "\"" : "";
1390                 result += "\""+p.name+"\": " + escape + p.value + escape;
1391             }
1392         }
1393         return result;
1394     }
1395 
1396     private void jar(
1397             Manifest manifest, List<PackagerResource> files,
1398             File importJarFile, JarOutputStream jar, Filter filter)
1399                 throws IOException, PackagerException {
1400         try {
1401             jar.putNextEntry(new ZipEntry("META-INF/"));
1402             jar.closeEntry();
1403             jar.putNextEntry(new ZipEntry(JarFile.MANIFEST_NAME));
1404             manifest.write(jar);
1405             jar.closeEntry();
1406 
1407             alreadyAddedEntries.add("META-INF/");
1408             if (importJarFile != null) { //updating jar file
1409                 copyFromOtherJar(jar, importJarFile);
1410             } else { //normal situation
1411                 for (PackagerResource pr : files) {
1412                     jar(pr.getFile(), jar, filter,
1413                             pr.getBaseDir().getAbsolutePath().length() + 1);
1414                 }
1415             }
1416         } finally {
1417             jar.close();
1418             alreadyAddedEntries.clear();
1419         }
1420     }
1421 
1422     private Set<String> alreadyAddedEntries = new HashSet<String>();
1423     private void createParentEntries(String relativePath, JarOutputStream jar) throws IOException {
1424         String[] pathComponents = relativePath.split("/");
1425         StringBuilder pathSB = new StringBuilder();
1426         // iterating over directories only, the last component is the file
1427         // or will be created next time.
1428         for (int i = 0; i < pathComponents.length - 1; i++) {
1429             pathSB.append(pathComponents[i]).append("/");
1430             if (!alreadyAddedEntries.contains(pathSB.toString())) {
1431                 jar.putNextEntry(new ZipEntry(pathSB.toString()));
1432                 jar.closeEntry();
1433             }
1434             alreadyAddedEntries.add(pathSB.toString());
1435         }
1436     }
1437 
1438     //add everything but manifest from given jar file
1439     private void copyFromOtherJar(JarOutputStream jar, File inputFile) throws IOException {
1440         JarFile inJar = new JarFile(inputFile);
1441 
1442         Enumeration<JarEntry> all = inJar.entries();
1443         while (all.hasMoreElements()) {
1444             JarEntry je = all.nextElement();
1445 
1446             //skip manifest or root manifest dir entry (can not add duplicate)
1447             if ("META-INF/MANIFEST.MF".equals(je.getName().toUpperCase())
1448                     || "META-INF/".equals(je.getName().toUpperCase())) {
1449                 continue;
1450             }
1451 
1452             InputStream in = inJar.getInputStream(je);
1453             jar.putNextEntry(new JarEntry(je.getName()));
1454 
1455             byte b[] = new byte[65000];
1456             int i;
1457             try {
1458                 while ((i = in.read(b)) > 0) {
1459                     jar.write(b, 0, i);
1460                 }
1461             } finally {
1462                 in.close();
1463             }
1464 
1465             jar.closeEntry();
1466         }
1467     }
1468 
1469     private void jar(File f, JarOutputStream jar, Filter filter, int cut)
1470             throws IOException, PackagerException {
1471         if (!f.exists()) {
1472             throw new FileNotFoundException("Input folder does not exist ["
1473                     +f.getAbsolutePath()+"]");
1474         }
1475 
1476         if (f.isDirectory()) {
1477             File[] children = f.listFiles();
1478             if (children != null) {
1479                 for (File innerFile : children) {
1480                     jar(innerFile, jar, filter, cut);
1481                 }
1482             }
1483         } else if (filter == Filter.ALL
1484                 || (filter == Filter.CLASSES_ONLY && f.getName().endsWith(".class"))
1485                 || (filter == Filter.RESOURCES && isResource(f.getAbsolutePath()))) {
1486             final String absPath = f.getAbsolutePath();
1487             if (absPath.endsWith("META-INF\\MANIFEST.MF")
1488              || absPath.endsWith("META-INF/MANIFEST.MF")) {
1489                 return;
1490             }
1491             createParentEntries(absPath.substring(cut).replace('\\', '/'), jar);
1492             if (createJarParams.css2bin && f.getName().endsWith(".css")) {
1493                 // generate bss file into temporary directory
1494                 int startOfExt = absPath.lastIndexOf(".") + 1;
1495                 String bssFileName = absPath
1496                                       .substring(cut, startOfExt)
1497                                       .concat("bss");
1498 
1499                 File bssFile = new File(bssTmpDir, bssFileName);
1500                 bssFile.getParentFile().mkdirs();
1501 
1502                 createBinaryCss(absPath, bssFile.getAbsolutePath());
1503                 jar.putNextEntry(new ZipEntry(bssFileName.replace('\\', '/')));
1504                 f = bssFile;
1505             } else {
1506                 jar.putNextEntry(new ZipEntry(absPath.substring(cut).replace('\\', '/')));
1507             }
1508 
1509             byte b[] = new byte[65000];
1510             int i;
1511             FileInputStream in = new FileInputStream(f);
1512             try {
1513                 while ((i = in.read(b)) > 0) {
1514                     jar.write(b, 0, i);
1515                 }
1516             } finally {
1517                 in.close();
1518             }
1519             jar.closeEntry();
1520         }
1521     }
1522 
1523 
1524     private void createBinaryCss(List<PackagerResource> cssResources, File outdir)
1525             throws PackagerException {
1526         for (PackagerResource cssRes: cssResources) {
1527             String relPath = cssRes.getRelativePath();
1528             createBinaryCss(cssRes.getFile(), outdir, relPath);
1529         }
1530     }
1531 
1532     private void createBinaryCss(File f, File outdir, String relPath)
1533             throws PackagerException {
1534         if (f.isDirectory()) {
1535             File[] children = f.listFiles();
1536             if (children != null) {
1537                 for (File innerFile : children) {
1538                     createBinaryCss(innerFile, outdir, relPath + '/' + innerFile.getName());
1539                 }
1540             }
1541         } else if (f.getName().endsWith(".css")) {
1542             String cssFileName = f.getAbsolutePath();
1543             String bssFileName = new File(outdir.getAbsolutePath(),
1544                                           replaceExtensionByBSS(relPath))
1545                                           .getAbsolutePath();
1546             createBinaryCss(cssFileName, bssFileName);
1547         }
1548     }
1549 
1550     // Returns path to jfxrt.jar relatively to jar containing PackagerLib.class
1551     private String getJfxrtPath() throws PackagerException {
1552         String theClassFile = "PackagerLib.class";
1553         Class theClass = PackagerLib.class;
1554         String classUrl = theClass.getResource(theClassFile).toString();
1555 
1556         if (!classUrl.startsWith("jar:file:") || classUrl.indexOf("!") == -1){
1557             throw new PackagerException("ERR_CantFindRuntime");
1558         }
1559 
1560         // Strip everything after and including the "!"
1561         classUrl = classUrl.substring(0, classUrl.lastIndexOf("!"));
1562         // Strip everything after the last "/" or "\" to get rid of the jar filename
1563         int lastIndexOfSlash = Math.max(classUrl.lastIndexOf("/"), classUrl.lastIndexOf("\\"));
1564         String jfxrtPath = classUrl.substring(0, lastIndexOfSlash)
1565                     + "/../rt/lib/ext/jfxrt.jar!/";
1566 
1567         return jfxrtPath;
1568     }
1569 
1570     private Class loadClassFromRuntime(String className) throws PackagerException {
1571         try {
1572             ClassLoader cl = getClassLoader();
1573             return cl.loadClass(className);
1574         } catch (ClassNotFoundException ex) {
1575             throw new PackagerException(ex, "ERR_CantFindRuntime");
1576         }
1577     }
1578 
1579     private void createBinaryCss(String cssFile, String binCssFile) throws PackagerException {
1580         String ifname = cssFile;
1581         String ofname = (binCssFile != null)
1582                             ? binCssFile
1583                             : replaceExtensionByBSS(ifname);
1584 
1585         // create parent directories
1586         File of = new File(ofname);
1587         File parentFile = of.getParentFile();
1588         if (parentFile != null) {
1589             parentFile.mkdirs();
1590         }
1591 
1592         // Using reflection because CSS parser is part of runtime
1593         // and we want to avoid dependency on jfxrt during build
1594         Class clazz;
1595         try {
1596             clazz = Class.forName("com.sun.javafx.css.parser.Css2Bin");
1597         } catch (ClassNotFoundException e) {
1598             // class was not found with default class loader, trying to
1599             // locate it by loading from jfxrt.jar
1600             clazz = loadClassFromRuntime("com.sun.javafx.css.parser.Css2Bin");
1601         }
1602 
1603         try {
1604             Method m = clazz.getMethod("convertToBinary", new Class[]{String.class, String.class});
1605             m.invoke(null, ifname, ofname);
1606         } catch (Exception ex) {
1607             Throwable causeEx = ex.getCause();
1608             String cause = (causeEx != null) ? causeEx.getMessage()
1609                                              : bundle.getString("ERR_UnknownReason");
1610 
1611             throw new PackagerException(ex, "ERR_BSSConversionFailed", cssFile, cause);
1612         }
1613     }
1614 
1615     private static String replaceExtensionByBSS(String cssName) {
1616         return cssName.substring(0, cssName.lastIndexOf(".") + 1).concat("bss");
1617     }
1618 
1619 
1620     private boolean isResource(String name) {
1621         if (name.endsWith(".class")) {
1622             return false;
1623         }
1624         if (name.endsWith(".java")) {
1625             return false;
1626         }
1627         if (name.endsWith(".fx")) {
1628             return false;
1629         }
1630         if (name.endsWith(".cvsignore")) {
1631             return false;
1632         }
1633         if (name.endsWith(".hgignore")) {
1634             return false;
1635         }
1636         if (name.endsWith("vssver.scc")) {
1637             return false;
1638         }
1639         if (name.endsWith(".DS_Store")) {
1640             return false;
1641         }
1642         if (name.endsWith("~")) {
1643             return false;
1644         }
1645         name = name.replace('\\', '/');
1646         if (name.indexOf("/CVS/") >= 0) {
1647             return false;
1648         }
1649         if (name.indexOf("/.svn/") >= 0) {
1650             return false;
1651         }
1652         if (name.indexOf("/.hg/") >= 0) {
1653             return false;
1654         }
1655         if (name.indexOf("/.#") >= 0) {
1656             return false;
1657         }
1658         if (name.indexOf("/._") >= 0) {
1659             return false;
1660         }
1661         if (name.endsWith("#") && name.indexOf("/#") >= 0) {
1662             return false;
1663         }
1664         if (name.endsWith("%") && name.indexOf("/%") >= 0) {
1665             return false;
1666         }
1667         if (name.endsWith("MANIFEST.MF")) {
1668             return false;
1669         }
1670         return true;
1671     }
1672 
1673     private static String[] webFiles = {
1674       "javafx-loading-100x100.gif",
1675       dtFX,
1676       "javafx-loading-25x25.gif",
1677       "error.png",
1678       "upgrade_java.png",
1679       "javafx-chrome.png",
1680       "get_java.png",
1681       "upgrade_javafx.png",
1682       "get_javafx.png"
1683     };
1684 
1685     private static String prefixWebFiles = "/resources/web-files/";
1686 
1687     private boolean extractWebFiles() throws PackagerException {
1688         return doExtractWebFiles(webFiles);
1689     }
1690 
1691     private boolean doExtractWebFiles(String lst[]) throws PackagerException {
1692         File f = new File(deployParams.outdir, webfilesDir);
1693         f.mkdirs();
1694 
1695         for (String s: lst) {
1696             InputStream is =
1697                     PackagerLib.class.getResourceAsStream(prefixWebFiles+s);
1698             if (is == null) {
1699                 System.err.println("Internal error. Missing resources [" +
1700                         (prefixWebFiles+s) + "]");
1701                 return false;
1702             } else {
1703                 copyFileToOutDir(is, new File(f, s));
1704             }
1705         }
1706         return true;
1707     }
1708 
1709     private static boolean deleteDirectory(File dir) {
1710         if (dir == null || !dir.exists()) {
1711             return false;
1712         }
1713 
1714         if (dir.isDirectory()) {
1715             for (String file : dir.list()) {
1716                 deleteDirectory(new File(dir, file));
1717             }
1718         }
1719         return dir.delete();
1720     }
1721 
1722     private void processEmbeddedCertificates(PrintStream out)
1723             throws CertificateEncodingException, IOException {
1724         if (deployParams.embedCertificates) {
1725             Set<CertPath> certPaths = collectCertPaths();
1726             String signed = isSignedJNLP ? " signedjnlp=\"true\">" : ">";
1727             if (certPaths != null && !certPaths.isEmpty()) {
1728                 out.println("  <jfx:details" + signed);
1729                 for (CertPath cp : certPaths) {
1730                     String base64 = Utils.getBase64Encoded(cp);
1731                     out.println("     <jfx:certificate-path>" + base64 +
1732                             "</jfx:certificate-path>");
1733                 }
1734                 out.println("  </jfx:details>");
1735             }
1736         }
1737     }
1738 
1739     private Set<CertPath> collectCertPaths() throws IOException {
1740         Set<CertPath> result = new HashSet<CertPath>();
1741         for (DeployResource resource: deployParams.resources) {
1742             final File srcFile = resource.getFile();
1743             if (srcFile.exists() && srcFile.isFile() &&
1744                 srcFile.getName().toLowerCase().endsWith("jar")) {
1745                 result.addAll(extractCertPaths(srcFile));
1746             }
1747         }
1748         return result;
1749     }
1750 
1751     private Set<CertPath> extractCertPaths(File jar) throws IOException {
1752         Set<CertPath> result = new HashSet<CertPath>();
1753         JarFile jf = new JarFile(jar);
1754 
1755         // need to fully read jar file to build up internal signer info map
1756         Utils.readAllFully(jf);
1757 
1758         boolean blobSigned = false;
1759         Enumeration<JarEntry> entries = jf.entries();
1760         while (entries.hasMoreElements()) {
1761             JarEntry je = entries.nextElement();
1762             String entryName = je.getName();
1763 
1764             CodeSigner[] signers = null;
1765             if (entryName.equalsIgnoreCase(JarSignature.BLOB_SIGNATURE)) {
1766                 byte[] raw = Utils.getBytes(jf.getInputStream(je));
1767                 try {
1768                     JarSignature js = JarSignature.load(raw);
1769                     blobSigned = true;
1770                     signers = js.getCodeSigners();
1771                 } catch(Exception ex) {
1772                     throw new IOException(ex);
1773                 }
1774             } else {
1775                 signers = je.getCodeSigners();
1776             }
1777             result.addAll(extractCertPaths(signers));
1778 
1779             if (entryName.equalsIgnoreCase("JNLP-INF/APPLICATION.JNLP")) {
1780                 isSignedJNLP = true;
1781             }
1782 
1783             // if blob and also know signed JNLP, no need to continue
1784             if (blobSigned && isSignedJNLP) {
1785                 break;
1786             }
1787 
1788         }
1789         return result;
1790     }
1791 
1792     private static Collection<CertPath> extractCertPaths(CodeSigner[] signers) {
1793         Collection<CertPath> result = new ArrayList<CertPath>();
1794         if (signers != null) {
1795             for (CodeSigner cs : signers) {
1796                 CertPath cp = cs.getSignerCertPath();
1797                 if (cp != null) {
1798                     result.add(cp);
1799                 }
1800             }
1801         }
1802         return result;
1803     }
1804 }