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