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