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