1 /*
   2  * Copyright (c) 2012, 2017, 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.tools.packager.Bundlers;
  29 import com.oracle.tools.packager.ConfigException;
  30 import com.oracle.tools.packager.Log;
  31 import com.oracle.tools.packager.Platform;
  32 import com.oracle.tools.packager.UnsupportedPlatformException;
  33 import com.sun.javafx.tools.packager.JarSignature.InputStreamSource;
  34 import com.sun.javafx.tools.packager.bundlers.BundleParams;
  35 import com.sun.javafx.tools.packager.bundlers.Bundler.BundleType;
  36 import com.sun.javafx.tools.resource.PackagerResource;
  37 import com.sun.javafx.css.parser.Css2Bin;
  38 
  39 import java.io.BufferedReader;
  40 import java.io.File;
  41 import java.io.FileInputStream;
  42 import java.io.FileNotFoundException;
  43 import java.io.FileOutputStream;
  44 import java.io.FileWriter;
  45 import java.io.IOException;
  46 import java.io.InputStream;
  47 import java.io.InputStreamReader;
  48 import java.io.OutputStream;
  49 import java.io.Writer;
  50 import java.nio.file.Files;
  51 import java.nio.file.StandardCopyOption;
  52 import java.security.InvalidKeyException;
  53 import java.security.KeyStore;
  54 import java.security.KeyStoreException;
  55 import java.security.NoSuchAlgorithmException;
  56 import java.security.PrivateKey;
  57 import java.security.SignatureException;
  58 import java.security.UnrecoverableKeyException;
  59 import java.security.cert.Certificate;
  60 import java.security.cert.CertificateException;
  61 import java.security.cert.X509Certificate;
  62 import java.text.MessageFormat;
  63 import java.util.ArrayList;
  64 import java.util.Base64;
  65 import java.util.Enumeration;
  66 import java.util.HashMap;
  67 import java.util.HashSet;
  68 import java.util.List;
  69 import java.util.Map;
  70 import java.util.Map.Entry;
  71 import java.util.ResourceBundle;
  72 import java.util.Set;
  73 import java.util.jar.Attributes;
  74 import java.util.jar.JarEntry;
  75 import java.util.jar.JarFile;
  76 import java.util.jar.JarOutputStream;
  77 import java.util.jar.Manifest;
  78 import java.util.zip.ZipEntry;
  79 import java.util.zip.ZipOutputStream;
  80 
  81 /**
  82  * @deprecated use {@link ToolProvider} to locate the {@code "javapackager"} tool instead.
  83  */
  84 @Deprecated(since="10", forRemoval=true)
  85 public class PackagerLib {
  86     public static final String JAVAFX_VERSION = System.getProperty("java.version");
  87 
  88     private static final ResourceBundle bundle =
  89             ResourceBundle.getBundle("com/sun/javafx/tools/packager/Bundle");
  90 
  91     private CreateJarParams createJarParams;
  92     private CreateBSSParams createBssParams;
  93     private File bssTmpDir;
  94 
  95 
  96     private enum Filter {ALL, CLASSES_ONLY, RESOURCES}
  97 
  98     //  if set of input resources consist of SINGLE element and
  99     //   this element is jar file then we expect this to be request to
 100     //   "update" jar file
 101     //  Input jar file MUST be executable jar file
 102     //
 103     // Check if we are in "special case" scenario
 104     private File jarFileToUpdate(CreateJarParams params) {
 105         if (params.resources.size() == 1) {
 106             PackagerResource p = params.resources.get(0);
 107             File f = p.getFile();
 108             if (!f.isFile() || !f.getAbsolutePath().toLowerCase().endsWith(".jar")) {
 109                 return null;
 110             }
 111             try (JarFile jf = new JarFile(f)) {
 112                 jf.getManifest(); //try to read manifest to validate it is jar
 113                 return f;
 114             } catch (Exception e) {
 115                 //treat any exception as "not a special case" scenario
 116                 com.oracle.tools.packager.Log.verbose(e);
 117             }
 118         }
 119         return null;
 120     }
 121 
 122     public void packageAsJar(CreateJarParams createJarParams) throws PackagerException {
 123         if (createJarParams == null) {
 124             throw new IllegalArgumentException("Parameters must not be null");
 125         }
 126 
 127         if (createJarParams.outfile == null) {
 128             throw new IllegalArgumentException("Output file is not specified");
 129         }
 130 
 131         this.createJarParams = createJarParams;
 132 
 133         //Special case: could be request for "update jar file"
 134         File jarToUpdate = jarFileToUpdate(createJarParams);
 135         Manifest m = null;
 136 
 137         if (jarToUpdate != null) {
 138             com.oracle.tools.packager.Log.info(MessageFormat.format(bundle.getString("MSG_UpdatingJar"), jarToUpdate.getAbsolutePath()));
 139             try (JarFile jf = new JarFile(jarToUpdate)) {
 140                 //extract data we want to preserve
 141                 m = jf.getManifest();
 142                 if (m != null) {
 143                     Attributes attrs = m.getMainAttributes();
 144                     if (createJarParams.applicationClass == null) {
 145                         createJarParams.applicationClass =
 146                                 attrs.getValue(Attributes.Name.MAIN_CLASS);
 147                     }
 148                     if (createJarParams.classpath == null) {
 149                         createJarParams.classpath =
 150                                 attrs.getValue(Attributes.Name.CLASS_PATH);
 151                     }
 152                     if (createJarParams.codebase == null) {
 153                         createJarParams.codebase =
 154                                 attrs.getValue(new Attributes.Name("Codebase"));
 155                     }
 156                     if (createJarParams.allPermissions == null) {
 157                         String value  =
 158                                 attrs.getValue(new Attributes.Name("Permissions"));
 159                         if (value != null) {
 160                             createJarParams.allPermissions = Boolean.valueOf(value);
 161                         }
 162                     }
 163                 }
 164             } catch (IOException ex) {
 165                 throw new PackagerException(
 166                         ex, "ERR_FileReadFailed", jarToUpdate.getAbsolutePath());
 167             }
 168         }
 169 
 170         if (createJarParams.applicationClass == null) {
 171             throw new IllegalArgumentException(
 172                     "Main application class is not specified");
 173         }
 174 
 175         //NOTE: This should be a save-to-temp file, then rename operation
 176         File applicationJar = new File(createJarParams.outdir,
 177                 createJarParams.outfile.endsWith(".jar")
 178                         ? createJarParams.outfile
 179                         : createJarParams.outfile + ".jar");
 180 
 181         if (jarToUpdate != null &&
 182                 applicationJar.getAbsoluteFile().equals(jarToUpdate.getAbsoluteFile())) {
 183             try {
 184                 File newInputJar = File.createTempFile("tempcopy", ".jar");
 185                 Files.move(jarToUpdate.toPath(), newInputJar.toPath(),
 186                         StandardCopyOption.REPLACE_EXISTING);
 187                 jarToUpdate = newInputJar;
 188             } catch (IOException ioe) {
 189                 throw new PackagerException(
 190                         ioe, "ERR_FileCopyFailed", jarToUpdate.getAbsolutePath());
 191             }
 192         }
 193 
 194         File parentFile = applicationJar.getParentFile();
 195         if (parentFile != null) {
 196             parentFile.mkdirs();
 197         }
 198 
 199         if (m == null) {
 200             m = new Manifest();
 201         }
 202         Attributes attr = m.getMainAttributes();
 203         attr.put(Attributes.Name.MANIFEST_VERSION, "1.0");
 204         attr.put(new Attributes.Name("Created-By"), "JavaFX Packager");
 205 
 206         if (createJarParams.manifestAttrs != null) {
 207             for (Entry<String, String> e: createJarParams.manifestAttrs.entrySet()) {
 208                 attr.put(new Attributes.Name(e.getKey()), e.getValue());
 209             }
 210         }
 211 
 212         attr.put(Attributes.Name.MAIN_CLASS, createJarParams.applicationClass);
 213         if (createJarParams.classpath != null) {
 214             // Allow comma or semicolon as delimeter (turn them into spaces)
 215             String cp = createJarParams.classpath;
 216             cp = cp.replace(';', ' ').replace(',', ' ');
 217             attr.put(Attributes.Name.CLASS_PATH, cp);
 218         }
 219 
 220         String existingSetting = attr.getValue("Permissions");
 221         if (existingSetting == null) {
 222             attr.put(new Attributes.Name("Permissions"),
 223                     Boolean.TRUE.equals(createJarParams.allPermissions) ? "all-permissions" : "sandbox");
 224         } else if (createJarParams.allPermissions != null && !Boolean.valueOf(existingSetting).equals(createJarParams.allPermissions)) {
 225             throw new PackagerException(
 226                 "ERR_ContradictorySetting", "Permissions");
 227         }
 228 
 229         existingSetting = attr.getValue("Codebase");
 230         if (existingSetting == null) {
 231             if (createJarParams.codebase != null) {
 232                 attr.put(new Attributes.Name("Codebase"), createJarParams.codebase);
 233             }
 234         } else if (createJarParams.codebase != null && !existingSetting.equals(createJarParams.codebase)) {
 235             throw new PackagerException(
 236                     "ERR_ContradictorySetting", "Codebase");
 237         }
 238 
 239         attr.put(new Attributes.Name("JavaFX-Version"), createJarParams.fxVersion);
 240 
 241         if (createJarParams.preloader != null) {
 242             attr.put(new Attributes.Name("JavaFX-Preloader-Class"), createJarParams.preloader);
 243         }
 244 
 245 
 246         if (createJarParams.arguments != null) {
 247             int idx = 1;
 248             for (String arg: createJarParams.arguments) {
 249                 attr.put(new Attributes.Name("JavaFX-Argument-"+idx),
 250                         encodeAsBase64(arg.getBytes()));
 251                 idx++;
 252             }
 253         }
 254         if (createJarParams.params != null) {
 255             int idx = 1;
 256             for (Param p : createJarParams.params) {
 257                 if (p.name != null) { //otherwise it is something weird and we skip it
 258                     attr.put(new Attributes.Name("JavaFX-Parameter-Name-" + idx),
 259                             encodeAsBase64(p.name.getBytes()));
 260                     if (p.value != null) { //legal, means not value specified
 261                         attr.put(new Attributes.Name("JavaFX-Parameter-Value-" + idx),
 262                                 encodeAsBase64(p.value.getBytes()));
 263                     }
 264                     idx++;
 265                 }
 266             }
 267         }
 268 
 269 
 270         if (createJarParams.css2bin) {
 271             try {
 272                 bssTmpDir = File.createTempFile("bssfiles", "");
 273             } catch (IOException ex) {
 274                 throw new PackagerException(ex, "ERR_CreatingTempFileFailed");
 275             }
 276             bssTmpDir.delete();
 277         }
 278 
 279         if (applicationJar.exists() && !applicationJar.delete()) {
 280             throw new PackagerException(
 281                     "ERR_CantDeleteFile", createJarParams.outfile);
 282         }
 283         try {
 284             jar(m, createJarParams.resources, jarToUpdate,
 285                     new JarOutputStream(new FileOutputStream(applicationJar)),
 286                     Filter.ALL);
 287         } catch (IOException ex) {
 288             throw new PackagerException(
 289                     ex, "ERR_CreatingJarFailed", createJarParams.outfile);
 290         }
 291 
 292         // cleanup
 293         deleteDirectory(bssTmpDir);
 294         this.createJarParams = null;
 295     }
 296 
 297     public void generateDeploymentPackages(DeployParams deployParams) throws PackagerException {
 298         if (deployParams == null) {
 299             throw new IllegalArgumentException("Parameters must not be null.");
 300         }
 301 
 302         try {
 303             BundleParams bp = deployParams.getBundleParams();
 304 
 305             if (bp != null) {
 306                 switch(deployParams.getBundleType()) {
 307                     case NATIVE: {
 308                         // Generate disk images.
 309                         generateNativeBundles(deployParams.outdir,
 310                                               bp.getBundleParamsAsMap(),
 311                                               BundleType.IMAGE.toString(),
 312                                               deployParams.getTargetFormat());
 313 
 314                         // Generate installers.
 315                         generateNativeBundles(deployParams.outdir,
 316                                               bp.getBundleParamsAsMap(),
 317                                               BundleType.INSTALLER.toString(),
 318                                               deployParams.getTargetFormat());
 319                         break;
 320                     }
 321 
 322                     case JNLP:
 323                     case NONE: {
 324                         // Old school default. Just generate JNLP.
 325                         generateNativeBundles(deployParams.outdir,
 326                                               bp.getBundleParamsAsMap(),
 327                                               BundleType.JNLP.toString(),
 328                                               "jnlp");
 329                         break;
 330                     }
 331 
 332                     default: {
 333                         // A specefic output format, just generate that.
 334                         generateNativeBundles(deployParams.outdir,
 335                                               bp.getBundleParamsAsMap(),
 336                                               deployParams.getBundleType().toString(),
 337                                               deployParams.getTargetFormat());
 338                     }
 339                 }
 340             }
 341         } catch (PackagerException ex) {
 342             throw ex;
 343         } catch (Exception ex) {
 344             throw new PackagerException(ex, "ERR_DeployFailed", ex.getMessage());
 345         }
 346 
 347     }
 348 
 349     private void generateNativeBundles(File outdir, Map<String, ? super Object> params, String bundleType, String bundleFormat) throws PackagerException {
 350         for (com.oracle.tools.packager.Bundler bundler : Bundlers.createBundlersInstance().getBundlers(bundleType)) {
 351             // if they specify the bundle format, require we match the ID
 352             if (bundleFormat != null && !bundleFormat.equalsIgnoreCase(bundler.getID())) continue;
 353 
 354             Map<String, ? super Object> localParams = new HashMap<>(params);
 355             try {
 356                 if (bundler.validate(localParams)) {
 357                     File result = bundler.execute(localParams, outdir);
 358                     bundler.cleanup(localParams);
 359                     if (result == null) {
 360                         throw new PackagerException("MSG_BundlerFailed", bundler.getID(), bundler.getName());
 361                     }
 362                 }
 363             } catch (UnsupportedPlatformException e) {
 364                 com.oracle.tools.packager.Log.debug(MessageFormat.format(bundle.getString("MSG_BundlerPlatformException"), bundler.getName()));
 365             } catch (ConfigException e) {
 366                 com.oracle.tools.packager.Log.debug(e);
 367                 if (e.getAdvice() != null) {
 368                     com.oracle.tools.packager.Log.info(MessageFormat.format(bundle.getString("MSG_BundlerConfigException"), bundler.getName(), e.getMessage(), e.getAdvice()));
 369                 } else {
 370                     com.oracle.tools.packager.Log.info(MessageFormat.format(bundle.getString("MSG_BundlerConfigExceptionNoAdvice"), bundler.getName(), e.getMessage()));
 371                 }
 372             } catch (RuntimeException re) {
 373                 com.oracle.tools.packager.Log.info(MessageFormat.format(bundle.getString("MSG_BundlerRuntimeException"), bundler.getName(), re.toString()));
 374                 com.oracle.tools.packager.Log.debug(re);
 375             }
 376         }
 377     }
 378 
 379     public void generateBSS(CreateBSSParams params) throws PackagerException {
 380         if (params == null) {
 381             throw new IllegalArgumentException("Parameters must not be null.");
 382         }
 383         this.createBssParams = params;
 384         createBinaryCss(createBssParams.resources, createBssParams.outdir);
 385         this.createBssParams = null;
 386     }
 387 
 388     public void signJar(SignJarParams params) throws PackagerException {
 389         try {
 390             JarSignature signature = retrieveSignature(params);
 391 
 392             for (PackagerResource pr: params.resources) {
 393                 signFile(pr, signature, params.outdir, params.verbose);
 394             }
 395 
 396         } catch (Exception ex) {
 397             com.oracle.tools.packager.Log.verbose(ex);
 398             throw new PackagerException("ERR_SignFailed", ex);
 399         }
 400 
 401     }
 402 
 403 
 404     private JarSignature retrieveSignature(SignJarParams params) throws KeyStoreException,
 405             NoSuchAlgorithmException, UnrecoverableKeyException, IOException,
 406             CertificateException, InvalidKeyException {
 407         if (params.keyPass == null) {
 408             params.keyPass = params.storePass;
 409         }
 410 
 411         if (params.keyStore == null) {
 412             throw new IOException("No keystore specified");
 413         }
 414 
 415         if (params.storePass == null) {
 416             throw new IOException("No store password specified");
 417         }
 418 
 419         if (params.storeType == null) {
 420             throw new IOException("No store type is specified");
 421         }
 422 
 423         KeyStore store = KeyStore.getInstance(params.storeType);
 424         store.load(new FileInputStream(params.keyStore), params.storePass.toCharArray());
 425 
 426         Certificate[] chain = store.getCertificateChain(params.alias);
 427         X509Certificate certChain[] = new X509Certificate[chain.length];
 428         for (int i=0; i<chain.length; i++) {
 429             certChain[i] = (X509Certificate) chain[i];
 430         }
 431 
 432         PrivateKey privateKey = (PrivateKey)
 433                 store.getKey(params.alias, params.keyPass.toCharArray());
 434 
 435         return JarSignature.create(privateKey, certChain);
 436     }
 437 
 438     private void signFile(
 439             PackagerResource pr, JarSignature signature, File outdir, boolean verbose)
 440             throws NoSuchAlgorithmException, IOException, SignatureException {
 441         if (pr.getFile().isDirectory()) {
 442             File[] children = pr.getFile().listFiles();
 443             if (children != null) {
 444                 for (File innerFile : children) {
 445                     signFile(new PackagerResource(
 446                             pr.getBaseDir(), innerFile), signature, outdir, verbose);
 447                 }
 448             }
 449         } else {
 450             File jar = pr.getFile();
 451             File parent = jar.getParentFile();
 452             String name = "bsigned_" + jar.getName();
 453             File signedJar = new File(parent, name);
 454 
 455             Log.info("Signing (BLOB) " + jar.getPath());
 456 
 457             signAsBLOB(jar, signedJar, signature);
 458 
 459             File destJar;
 460             if (outdir != null) {
 461                 destJar = new File(outdir, pr.getRelativePath());
 462             } else {
 463                 // in-place
 464                 jar.delete();
 465                 destJar = jar;
 466             }
 467             destJar.delete();
 468             destJar.getParentFile().mkdirs();
 469             signedJar.renameTo(destJar);
 470             if (verbose) {
 471                 Log.info("Signed as " + destJar.getPath());
 472             }
 473         }
 474     }
 475 
 476     private void signAsBLOB(final File jar, File signedJar, JarSignature signature)
 477             throws IOException, NoSuchAlgorithmException, SignatureException
 478     {
 479         if (signature == null) {
 480             throw new IllegalStateException("Should retrieve signature first");
 481         }
 482 
 483         InputStreamSource in = () -> new FileInputStream(jar);
 484         if (!signedJar.isFile()) {
 485             signedJar.createNewFile();
 486         }
 487         FileOutputStream fos = new FileOutputStream(signedJar);
 488         signature.signJarAsBLOB(in, new ZipOutputStream(fos));
 489     }
 490 
 491     public void makeAll(MakeAllParams makeAllParams) throws PackagerException {
 492         final String exe = (Platform.getPlatform() == Platform.WINDOWS) ? ".exe" : "";
 493         String jHome = System.getenv("JAVA_HOME");
 494         if (jHome == null) {
 495             jHome = System.getProperty("java.home");
 496         }
 497         if (jHome == null) {
 498             throw new PackagerException("ERR_MissingJavaHome");
 499         }
 500 
 501         final File javac = new File(new File(jHome), "bin/javac" + exe);
 502 
 503         final String srcDirName = "src";
 504         final String compiledDirName = "compiled";
 505         final String distDirName = "dist";
 506         final String outfileName = "dist";
 507         final String jarName = outfileName + ".jar";
 508 
 509         final File distDir = new File(distDirName);
 510 
 511         final File compiledDir = new File(compiledDirName);
 512         compiledDir.mkdir();
 513 
 514         try {
 515             final File tmpFile = File.createTempFile("javac", "sources", new File("."));
 516             tmpFile.deleteOnExit();
 517             try (FileWriter sources = new FileWriter(tmpFile)) {
 518                 scanAndCopy(new PackagerResource(new File(srcDirName), "."), sources, compiledDir);
 519             }
 520 
 521             if (makeAllParams.verbose) {
 522                 Log.info("Executing javac:");
 523                 Log.infof("%s %s %s %s %s %s%n",
 524                         javac.getAbsolutePath(),
 525                         "-d", compiledDirName,
 526                         "-cp", makeAllParams.classpath != null ?
 527                                 makeAllParams.classpath : "<null>",
 528                         "@" + tmpFile.getAbsolutePath());
 529             }
 530            
 531             int ret = -1;
 532             if (makeAllParams.classpath != null) {
 533                 ret = execute(
 534                         javac.getAbsolutePath(),
 535                         "-d", compiledDirName,
 536                         "-cp", makeAllParams.classpath,
 537                         "@" + tmpFile.getAbsolutePath());
 538             } else {
 539                 ret = execute(
 540                         javac.getAbsolutePath(),
 541                         "-d", compiledDirName,
 542                         "@" + tmpFile.getAbsolutePath());
 543             }
 544 
 545             if (ret != 0) {
 546                 throw new PackagerException("ERR_JavacFailed", Integer.toString(ret));
 547             }
 548         } catch (PackagerException e) {
 549             throw e;
 550         } catch (Exception e) {
 551             throw new PackagerException(e, "ERR_MakeAllJavacFailed");
 552         }
 553 
 554         CreateJarParams cjp = new CreateJarParams();
 555         cjp.applicationClass = makeAllParams.appClass;
 556         cjp.preloader = makeAllParams.preloader;
 557         cjp.classpath = makeAllParams.classpath;
 558         cjp.css2bin = false;
 559         cjp.outdir = distDir;
 560         cjp.outfile = jarName;
 561         cjp.addResource(compiledDir, ".");
 562 
 563         packageAsJar(cjp);
 564 
 565         DeployParams dp = new DeployParams();
 566         dp.applicationClass = makeAllParams.appClass;
 567         dp.appName = makeAllParams.appName;
 568         dp.description = "Application description";
 569         dp.height = makeAllParams.height;
 570         dp.width = makeAllParams.width;
 571         dp.vendor = "Application vendor";
 572         dp.outdir = distDir;
 573         dp.outfile = outfileName;
 574         dp.addResource(distDir, jarName);
 575         //noinspection deprecation
 576         dp.setBundleType(BundleType.ALL);
 577 
 578         generateDeploymentPackages(dp);
 579 
 580         deleteDirectory(compiledDir);
 581     }
 582 
 583     @SuppressWarnings("unchecked")
 584     private static int execute(Object ... args) throws IOException, InterruptedException {
 585         final ArrayList<String> argsList = new ArrayList<>();
 586         for (Object a : args) {
 587             if (a instanceof List) {
 588                 argsList.addAll((List)a);
 589             } else if (a instanceof String) {
 590                 argsList.add((String)a);
 591             }
 592         }
 593         final Process p = Runtime.getRuntime().exec(argsList.toArray(new String[argsList.size()]));
 594         final BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
 595         Thread t = new Thread(() -> {
 596             try {
 597                 String line;
 598                 while ((line = in.readLine()) != null) {
 599                     Log.info(line);
 600                 }
 601             } catch (IOException ioe) {
 602                 com.oracle.tools.packager.Log.verbose(ioe);
 603             }
 604         });
 605         t.setDaemon(true);
 606         t.start();
 607         final BufferedReader err = new BufferedReader(new InputStreamReader(p.getErrorStream()));
 608         t = new Thread(() -> {
 609             try {
 610                 String line;
 611                 while ((line = err.readLine()) != null) {
 612                     Log.error(line);
 613                 }
 614             } catch (IOException ioe) {
 615                 Log.verbose(ioe);
 616             }
 617         });
 618         t.setDaemon(true);
 619         t.start();
 620         return p.waitFor();
 621     }
 622 
 623     private static void scanAndCopy(PackagerResource dir, Writer out, File outdir) throws PackagerException {
 624         if (!dir.getFile().exists()) {
 625             throw new PackagerException("ERR_MissingDirectory", dir.getFile().getName());
 626         }
 627         File[] dirFilesList = dir.getFile().listFiles();
 628         if ((dirFilesList == null) || (dirFilesList.length == 0)) {
 629             throw new PackagerException("ERR_EmptySourceDirectory", dir.getFile().getName());
 630         }
 631         try {
 632             for (File f : dirFilesList) {
 633                 if (f.isDirectory()) {
 634                     scanAndCopy(new PackagerResource(dir.getBaseDir(), f), out, outdir);
 635                 } else if (f.getName().endsWith(".java")) {
 636                     out.write('\'' + f.getAbsolutePath().replace('\\', '/') + "\'\n");
 637                 } else {
 638                     copyFileToOutDir(new FileInputStream(f),
 639                             new File(outdir.getPath() + File.separator
 640                                     + dir.getRelativePath() + File.separator
 641                                     + f.getName()));
 642                 }
 643             }
 644         } catch (IOException ex) {
 645             throw new PackagerException("ERR_FileCopyFailed", dir.getFile().getName());
 646         }
 647     }
 648 
 649     private String encodeAsBase64(byte inp[]) {
 650         return Base64.getEncoder().encodeToString(inp);
 651     }
 652 
 653     private static void copyFileToOutDir(
 654             InputStream isa, File fout) throws PackagerException {
 655 
 656         final File outDir = fout.getParentFile();
 657         if (!outDir.exists() && !outDir.mkdirs()) {
 658             throw new PackagerException("ERR_CreatingDirFailed", outDir.getPath());
 659         }
 660         try (InputStream is = isa; OutputStream out = new FileOutputStream(fout)) {
 661             byte[] buf = new byte[16384];
 662             int len;
 663             while ((len = is.read(buf)) > 0) {
 664                 out.write(buf, 0, len);
 665             }
 666         } catch (IOException ex) {
 667             throw new PackagerException(ex, "ERR_FileCopyFailed", outDir.getPath());
 668         }
 669     }
 670 
 671     private void jar(
 672             Manifest manifest, List<PackagerResource> files,
 673             File importJarFile, JarOutputStream jar, Filter filter)
 674             throws IOException, PackagerException {
 675         try {
 676             jar.putNextEntry(new ZipEntry("META-INF/"));
 677             jar.closeEntry();
 678             jar.putNextEntry(new ZipEntry(JarFile.MANIFEST_NAME));
 679             manifest.write(jar);
 680             jar.closeEntry();
 681 
 682             alreadyAddedEntries.add("META-INF/");
 683             if (importJarFile != null) { //updating jar file
 684                 copyFromOtherJar(jar, importJarFile);
 685             } else { //normal situation
 686                 for (PackagerResource pr : files) {
 687                     jar(pr.getFile(), jar, filter,
 688                             pr.getBaseDir().getAbsolutePath().length() + 1);
 689                 }
 690             }
 691         } finally {
 692             jar.close();
 693             alreadyAddedEntries.clear();
 694         }
 695     }
 696 
 697     private Set<String> alreadyAddedEntries = new HashSet<>();
 698     private void createParentEntries(String relativePath, JarOutputStream jar) throws IOException {
 699         String[] pathComponents = relativePath.split("/");
 700         StringBuilder pathSB = new StringBuilder();
 701         // iterating over directories only, the last component is the file
 702         // or will be created next time.
 703         for (int i = 0; i < pathComponents.length - 1; i++) {
 704             pathSB.append(pathComponents[i]).append("/");
 705             if (!alreadyAddedEntries.contains(pathSB.toString())) {
 706                 jar.putNextEntry(new ZipEntry(pathSB.toString()));
 707                 jar.closeEntry();
 708             }
 709             alreadyAddedEntries.add(pathSB.toString());
 710         }
 711     }
 712 
 713     //add everything but manifest from given jar file
 714     private void copyFromOtherJar(JarOutputStream jar, File inputFile) throws IOException {
 715         JarFile inJar = new JarFile(inputFile);
 716 
 717         Enumeration<JarEntry> all = inJar.entries();
 718         while (all.hasMoreElements()) {
 719             JarEntry je = all.nextElement();
 720 
 721             //skip manifest or root manifest dir entry (can not add duplicate)
 722             if ("META-INF/MANIFEST.MF".equals(je.getName().toUpperCase())
 723                     || "META-INF/".equals(je.getName().toUpperCase())) {
 724                 continue;
 725             }
 726 
 727             jar.putNextEntry(new JarEntry(je.getName()));
 728 
 729             byte b[] = new byte[65000];
 730             int i;
 731             try (InputStream in = inJar.getInputStream(je)) {
 732                 while ((i = in.read(b)) > 0) {
 733                     jar.write(b, 0, i);
 734                 }
 735             }
 736 
 737             jar.closeEntry();
 738         }
 739     }
 740 
 741     private void jar(File f, JarOutputStream jar, Filter filter, int cut)
 742             throws IOException, PackagerException {
 743         if (!f.exists()) {
 744             throw new FileNotFoundException("Input folder does not exist ["
 745                     +f.getAbsolutePath()+"]");
 746         }
 747 
 748         if (f.isDirectory()) {
 749             File[] children = f.listFiles();
 750             if (children != null) {
 751                 for (File innerFile : children) {
 752                     jar(innerFile, jar, filter, cut);
 753                 }
 754             }
 755         } else if (filter == Filter.ALL
 756                 || (filter == Filter.CLASSES_ONLY && f.getName().endsWith(".class"))
 757                 || (filter == Filter.RESOURCES && isResource(f.getAbsolutePath()))) {
 758             final String absPath = f.getAbsolutePath();
 759             if (absPath.endsWith("META-INF\\MANIFEST.MF")
 760                     || absPath.endsWith("META-INF/MANIFEST.MF")) {
 761                 return;
 762             }
 763             createParentEntries(absPath.substring(cut).replace('\\', '/'), jar);
 764             if (createJarParams.css2bin && f.getName().endsWith(".css")) {
 765                 // generate bss file into temporary directory
 766                 int startOfExt = absPath.lastIndexOf(".") + 1;
 767                 String bssFileName = absPath
 768                         .substring(cut, startOfExt)
 769                         .concat("bss");
 770 
 771                 File bssFile = new File(bssTmpDir, bssFileName);
 772                 bssFile.getParentFile().mkdirs();
 773 
 774                 createBinaryCss(absPath, bssFile.getAbsolutePath());
 775                 jar.putNextEntry(new ZipEntry(bssFileName.replace('\\', '/')));
 776                 f = bssFile;
 777             } else {
 778                 jar.putNextEntry(new ZipEntry(absPath.substring(cut).replace('\\', '/')));
 779             }
 780 
 781             byte b[] = new byte[65000];
 782             int i;
 783 
 784             try (FileInputStream in = new FileInputStream(f)) {
 785                 while ((i = in.read(b)) > 0) {
 786                     jar.write(b, 0, i);
 787                 }
 788             }
 789             jar.closeEntry();
 790         }
 791     }
 792 
 793     private void createBinaryCss(List<PackagerResource> cssResources, File outdir)
 794             throws PackagerException {
 795         for (PackagerResource cssRes: cssResources) {
 796             String relPath = cssRes.getRelativePath();
 797             createBinaryCss(cssRes.getFile(), outdir, relPath);
 798         }
 799     }
 800 
 801     private void createBinaryCss(File f, File outdir, String relPath)
 802             throws PackagerException {
 803         if (f.isDirectory()) {
 804             File[] children = f.listFiles();
 805             if (children != null) {
 806                 for (File innerFile : children) {
 807                     createBinaryCss(innerFile, outdir, relPath + '/' + innerFile.getName());
 808                 }
 809             }
 810         } else if (f.getName().endsWith(".css")) {
 811             String cssFileName = f.getAbsolutePath();
 812             String bssFileName = new File(outdir.getAbsolutePath(),
 813                     replaceExtensionByBSS(relPath))
 814                     .getAbsolutePath();
 815             createBinaryCss(cssFileName, bssFileName);
 816         }
 817     }
 818 
 819     private void createBinaryCss(String cssFile, String binCssFile) throws PackagerException {
 820         String ofname = (binCssFile != null)
 821                 ? binCssFile
 822                 : replaceExtensionByBSS(cssFile);
 823 
 824         // create parent directories
 825         File of = new File(ofname);
 826         File parentFile = of.getParentFile();
 827         if (parentFile != null) {
 828             parentFile.mkdirs();
 829         }
 830 
 831         try {
 832             Css2Bin.convertToBinary(cssFile, ofname);
 833         } catch (IOException ex) {
 834             Throwable causeEx = ex.getCause();
 835             String cause = (causeEx != null) ? causeEx.getMessage()
 836                     : bundle.getString("ERR_UnknownReason");
 837 
 838             throw new PackagerException(ex, "ERR_BSSConversionFailed", cssFile, cause);
 839         }
 840     }
 841 
 842     private static String replaceExtensionByBSS(String cssName) {
 843         return cssName.substring(0, cssName.lastIndexOf(".") + 1).concat("bss");
 844     }
 845 
 846 
 847     private boolean isResource(String name) {
 848         if (name.endsWith(".class")) {
 849             return false;
 850         }
 851         if (name.endsWith(".java")) {
 852             return false;
 853         }
 854         if (name.endsWith(".fx")) {
 855             return false;
 856         }
 857         if (name.endsWith(".cvsignore")) {
 858             return false;
 859         }
 860         if (name.endsWith(".hgignore")) {
 861             return false;
 862         }
 863         if (name.endsWith("vssver.scc")) {
 864             return false;
 865         }
 866         if (name.endsWith(".DS_Store")) {
 867             return false;
 868         }
 869         if (name.endsWith("~")) {
 870             return false;
 871         }
 872         name = name.replace('\\', '/');
 873         if (name.contains("/CVS/")) {
 874             return false;
 875         }
 876         if (name.contains("/.svn/")) {
 877             return false;
 878         }
 879         if (name.contains("/.hg/")) {
 880             return false;
 881         }
 882         if (name.contains("/.#")) {
 883             return false;
 884         }
 885         if (name.contains("/._")) {
 886             return false;
 887         }
 888         if (name.endsWith("#") && name.contains("/#")) {
 889             return false;
 890         }
 891         if (name.endsWith("%") && name.contains("/%")) {
 892             return false;
 893         }
 894         if (name.endsWith("MANIFEST.MF")) {
 895             return false;
 896         }
 897         return true;
 898     }
 899 
 900     private static boolean deleteDirectory(File dir) {
 901         if (dir == null || !dir.exists()) {
 902             return false;
 903         }
 904 
 905         if (dir.isDirectory()) {
 906             for (String file : dir.list()) {
 907                 deleteDirectory(new File(dir, file));
 908             }
 909         }
 910         return dir.delete();
 911     }
 912 }