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