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