1 /* 2 * Copyright (c) 1996, 2018, 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 sun.tools.jar; 27 28 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; 29 import static java.util.jar.JarFile.MANIFEST_NAME; 30 import static java.util.stream.Collectors.joining; 31 import static jdk.internal.util.jar.JarIndex.INDEX_NAME; 32 33 import java.io.*; 34 import java.lang.module.Configuration; 35 import java.lang.module.FindException; 36 import java.lang.module.InvalidModuleDescriptorException; 37 import java.lang.module.ModuleDescriptor; 38 import java.lang.module.ModuleDescriptor.Exports; 39 import java.lang.module.ModuleDescriptor.Opens; 40 import java.lang.module.ModuleDescriptor.Provides; 41 import java.lang.module.ModuleDescriptor.Version; 42 import java.lang.module.ModuleFinder; 43 import java.lang.module.ModuleReader; 44 import java.lang.module.ModuleReference; 45 import java.lang.module.ResolvedModule; 46 import java.net.URI; 47 import java.nio.ByteBuffer; 48 import java.nio.file.Files; 49 import java.nio.file.Path; 50 import java.nio.file.Paths; 51 import java.nio.file.StandardCopyOption; 52 import java.nio.file.attribute.PosixFilePermission; 53 import java.text.MessageFormat; 54 import java.util.*; 55 import java.util.function.Consumer; 56 import java.util.jar.Attributes; 57 import java.util.jar.JarFile; 58 import java.util.jar.JarOutputStream; 59 import java.util.jar.Manifest; 60 import java.util.regex.Pattern; 61 import java.util.stream.Collectors; 62 import java.util.stream.Stream; 63 import java.util.zip.CRC32; 64 import java.util.zip.ZipEntry; 65 import java.util.zip.ZipFile; 66 import java.util.zip.ZipInputStream; 67 import java.util.zip.ZipOutputStream; 68 69 import jdk.internal.module.Checks; 70 import jdk.internal.module.ModuleHashes; 71 import jdk.internal.module.ModuleHashesBuilder; 72 import jdk.internal.module.ModuleInfo; 73 import jdk.internal.module.ModuleInfoExtender; 74 import jdk.internal.module.ModuleResolution; 75 import jdk.internal.module.ModuleTarget; 76 import jdk.internal.util.jar.JarIndex; 77 78 /** 79 * This class implements a simple utility for creating files in the JAR 80 * (Java Archive) file format. The JAR format is based on the ZIP file 81 * format, with optional meta-information stored in a MANIFEST entry. 82 */ 83 public class Main { 84 String program; 85 PrintWriter out, err; 86 String fname, mname, ename; 87 String zname = ""; 88 String rootjar = null; 89 90 private static final int BASE_VERSION = 0; 91 92 private static class Entry { 93 final String name; 94 final File file; 95 final boolean isDir; 96 97 Entry(File file, String name, boolean isDir) { 98 this.file = file; 99 this.isDir = isDir; 100 this.name = name; 101 } 102 103 @Override 104 public boolean equals(Object o) { 105 if (this == o) return true; 106 if (!(o instanceof Entry)) return false; 107 return this.file.equals(((Entry)o).file); 108 } 109 110 @Override 111 public int hashCode() { 112 return file.hashCode(); 113 } 114 } 115 116 // An entryName(path)->Entry map generated during "expand", it helps to 117 // decide whether or not an existing entry in a jar file needs to be 118 // replaced, during the "update" operation. 119 Map<String, Entry> entryMap = new HashMap<>(); 120 121 // All entries need to be added/updated. 122 Set<Entry> entries = new LinkedHashSet<>(); 123 124 // module-info.class entries need to be added/updated. 125 Map<String,byte[]> moduleInfos = new HashMap<>(); 126 127 // A paths Set for each version, where each Set contains directories 128 // specified by the "-C" operation. 129 Map<Integer,Set<String>> pathsMap = new HashMap<>(); 130 131 // There's also a files array per version 132 Map<Integer,String[]> filesMap = new HashMap<>(); 133 134 // Do we think this is a multi-release jar? Set to true 135 // if --release option found followed by at least file 136 boolean isMultiRelease; 137 138 // The last parsed --release value, if any. Used in conjunction with 139 // "-d,--describe-module" to select the operative module descriptor. 140 int releaseValue = -1; 141 142 /* 143 * cflag: create 144 * uflag: update 145 * xflag: xtract 146 * tflag: table 147 * vflag: verbose 148 * flag0: no zip compression (store only) 149 * Mflag: DO NOT generate a manifest file (just ZIP) 150 * iflag: generate jar index 151 * nflag: Perform jar normalization at the end 152 * pflag: preserve/don't strip leading slash and .. component from file name 153 * dflag: print module descriptor 154 * oflag: preserve Posix file attributes 155 */ 156 boolean cflag, uflag, xflag, tflag, vflag, flag0, Mflag, iflag, nflag, pflag, dflag, oflag; 157 158 boolean suppressDeprecateMsg = false; 159 160 /* To support additional GNU Style informational options */ 161 Consumer<PrintWriter> info; 162 163 /* Modular jar related options */ 164 Version moduleVersion; 165 Pattern modulesToHash; 166 ModuleResolution moduleResolution = ModuleResolution.empty(); 167 ModuleFinder moduleFinder = ModuleFinder.of(); 168 169 static final String MODULE_INFO = "module-info.class"; 170 static final String MANIFEST_DIR = "META-INF/"; 171 static final String VERSIONS_DIR = MANIFEST_DIR + "versions/"; 172 static final String VERSION = "1.0"; 173 static final int VERSIONS_DIR_LENGTH = VERSIONS_DIR.length(); 174 private static ResourceBundle rsrc; 175 176 /** 177 * If true, maintain compatibility with JDK releases prior to 6.0 by 178 * timestamping extracted files with the time at which they are extracted. 179 * Default is to use the time given in the archive. 180 */ 181 private static final boolean useExtractionTime = 182 Boolean.getBoolean("sun.tools.jar.useExtractionTime"); 183 184 /** 185 * Initialize ResourceBundle 186 */ 187 static { 188 try { 189 rsrc = ResourceBundle.getBundle("sun.tools.jar.resources.jar"); 190 } catch (MissingResourceException e) { 191 throw new Error("Fatal: Resource for jar is missing"); 192 } 193 } 194 195 static String getMsg(String key) { 196 try { 197 return (rsrc.getString(key)); 198 } catch (MissingResourceException e) { 199 throw new Error("Error in message file"); 200 } 201 } 202 203 static String formatMsg(String key, String arg) { 204 String msg = getMsg(key); 205 String[] args = new String[1]; 206 args[0] = arg; 207 return MessageFormat.format(msg, (Object[]) args); 208 } 209 210 static String formatMsg2(String key, String arg, String arg1) { 211 String msg = getMsg(key); 212 String[] args = new String[2]; 213 args[0] = arg; 214 args[1] = arg1; 215 return MessageFormat.format(msg, (Object[]) args); 216 } 217 218 public Main(PrintStream out, PrintStream err, String program) { 219 this.out = new PrintWriter(out, true); 220 this.err = new PrintWriter(err, true); 221 this.program = program; 222 } 223 224 public Main(PrintWriter out, PrintWriter err, String program) { 225 this.out = out; 226 this.err = err; 227 this.program = program; 228 } 229 230 /** 231 * Creates a new empty temporary file in the same directory as the 232 * specified file. A variant of File.createTempFile. 233 */ 234 private static File createTempFileInSameDirectoryAs(File file) 235 throws IOException { 236 File dir = file.getParentFile(); 237 if (dir == null) 238 dir = new File("."); 239 return File.createTempFile("jartmp", null, dir); 240 } 241 242 private boolean ok; 243 244 /** 245 * Starts main program with the specified arguments. 246 */ 247 @SuppressWarnings({"removal"}) 248 public synchronized boolean run(String args[]) { 249 ok = true; 250 if (!parseArgs(args)) { 251 return false; 252 } 253 File tmpFile = null; 254 try { 255 if (cflag || uflag) { 256 if (fname != null) { 257 // The name of the zip file as it would appear as its own 258 // zip file entry. We use this to make sure that we don't 259 // add the zip file to itself. 260 zname = fname.replace(File.separatorChar, '/'); 261 if (zname.startsWith("./")) { 262 zname = zname.substring(2); 263 } 264 } 265 } 266 if (cflag) { 267 Manifest manifest = null; 268 if (!Mflag) { 269 if (mname != null) { 270 try (InputStream in = new FileInputStream(mname)) { 271 manifest = new Manifest(new BufferedInputStream(in)); 272 } 273 } else { 274 manifest = new Manifest(); 275 } 276 addVersion(manifest); 277 addCreatedBy(manifest); 278 if (isAmbiguousMainClass(manifest)) { 279 return false; 280 } 281 if (ename != null) { 282 addMainClass(manifest, ename); 283 } 284 if (isMultiRelease) { 285 addMultiRelease(manifest); 286 } 287 } 288 expand(); 289 if (!moduleInfos.isEmpty()) { 290 // All actual file entries (excl manifest and module-info.class) 291 Set<String> jentries = new HashSet<>(); 292 // all packages if it's a class or resource 293 Set<String> packages = new HashSet<>(); 294 entries.stream() 295 .filter(e -> !e.isDir) 296 .forEach( e -> { 297 addPackageIfNamed(packages, e.name); 298 jentries.add(e.name); 299 }); 300 addExtendedModuleAttributes(moduleInfos, packages); 301 302 // Basic consistency checks for modular jars. 303 if (!checkModuleInfo(moduleInfos.get(MODULE_INFO), jentries)) 304 return false; 305 306 } else if (moduleVersion != null || modulesToHash != null) { 307 error(getMsg("error.module.options.without.info")); 308 return false; 309 } 310 if (vflag && fname == null) { 311 // Disable verbose output so that it does not appear 312 // on stdout along with file data 313 // error("Warning: -v option ignored"); 314 vflag = false; 315 } 316 final String tmpbase = (fname == null) 317 ? "tmpjar" 318 : fname.substring(fname.indexOf(File.separatorChar) + 1); 319 320 tmpFile = createTemporaryFile(tmpbase, ".jar"); 321 try (OutputStream out = new FileOutputStream(tmpFile)) { 322 create(new BufferedOutputStream(out, 4096), manifest); 323 } 324 if (nflag) { 325 if (!suppressDeprecateMsg) { 326 warn(formatMsg("warn.flag.is.deprecated", "-n")); 327 } 328 File packFile = createTemporaryFile(tmpbase, ".pack"); 329 try { 330 java.util.jar.Pack200.Packer packer = java.util.jar.Pack200.newPacker(); 331 Map<String, String> p = packer.properties(); 332 p.put(java.util.jar.Pack200.Packer.EFFORT, "1"); // Minimal effort to conserve CPU 333 try (JarFile jarFile = new JarFile(tmpFile.getCanonicalPath()); 334 OutputStream pack = new FileOutputStream(packFile)) 335 { 336 packer.pack(jarFile, pack); 337 } 338 if (tmpFile.exists()) { 339 tmpFile.delete(); 340 } 341 tmpFile = createTemporaryFile(tmpbase, ".jar"); 342 try (OutputStream out = new FileOutputStream(tmpFile); 343 JarOutputStream jos = new JarOutputStream(out)) 344 { 345 java.util.jar.Pack200.Unpacker unpacker = java.util.jar.Pack200.newUnpacker(); 346 unpacker.unpack(packFile, jos); 347 } 348 } finally { 349 Files.deleteIfExists(packFile.toPath()); 350 } 351 } 352 validateAndClose(tmpFile); 353 } else if (uflag) { 354 File inputFile = null; 355 if (fname != null) { 356 inputFile = new File(fname); 357 tmpFile = createTempFileInSameDirectoryAs(inputFile); 358 } else { 359 vflag = false; 360 tmpFile = createTemporaryFile("tmpjar", ".jar"); 361 } 362 expand(); 363 try (FileInputStream in = (fname != null) ? new FileInputStream(inputFile) 364 : new FileInputStream(FileDescriptor.in); 365 FileOutputStream out = new FileOutputStream(tmpFile); 366 InputStream manifest = (!Mflag && (mname != null)) ? 367 (new FileInputStream(mname)) : null; 368 ) { 369 boolean updateOk = update(in, new BufferedOutputStream(out), 370 manifest, moduleInfos, null); 371 if (ok) { 372 ok = updateOk; 373 } 374 } 375 validateAndClose(tmpFile); 376 } else if (tflag) { 377 replaceFSC(filesMap); 378 // For the "list table contents" action, access using the 379 // ZipFile class is always most efficient since only a 380 // "one-finger" scan through the central directory is required. 381 String[] files = filesMapToFiles(filesMap); 382 if (fname != null) { 383 list(fname, files); 384 } else { 385 InputStream in = new FileInputStream(FileDescriptor.in); 386 try { 387 list(new BufferedInputStream(in), files); 388 } finally { 389 in.close(); 390 } 391 } 392 } else if (xflag) { 393 replaceFSC(filesMap); 394 // For the extract action, when extracting all the entries, 395 // access using the ZipInputStream class is most efficient, 396 // since only a single sequential scan through the zip file is 397 // required. When using the ZipFile class, a "two-finger" scan 398 // is required, but this is likely to be more efficient when a 399 // partial extract is requested. In case the zip file has 400 // "leading garbage", we fall back from the ZipInputStream 401 // implementation to the ZipFile implementation, since only the 402 // latter can handle it. 403 404 String[] files = filesMapToFiles(filesMap); 405 // if we need to restore posix permissions (-o flag), we need to use 406 // the ZipFile approach because permissions are stored in the CEN 407 // which is not read when using a ZipInputStream. 408 if (fname != null && (files != null || oflag)) { 409 extract(fname, files); 410 } else { 411 InputStream in = (fname == null) 412 ? new FileInputStream(FileDescriptor.in) 413 : new FileInputStream(fname); 414 try { 415 if (!extract(new BufferedInputStream(in), files) && fname != null) { 416 extract(fname, files); 417 } 418 } finally { 419 in.close(); 420 } 421 } 422 } else if (iflag) { 423 String[] files = filesMap.get(BASE_VERSION); // base entries only, can be null 424 genIndex(rootjar, files); 425 } else if (dflag) { 426 boolean found; 427 if (fname != null) { 428 try (ZipFile zf = new ZipFile(fname)) { 429 found = describeModule(zf); 430 } 431 } else { 432 try (FileInputStream fin = new FileInputStream(FileDescriptor.in)) { 433 found = describeModuleFromStream(fin); 434 } 435 } 436 if (!found) 437 error(getMsg("error.module.descriptor.not.found")); 438 } 439 } catch (IOException e) { 440 fatalError(e); 441 ok = false; 442 } catch (Error ee) { 443 ee.printStackTrace(); 444 ok = false; 445 } catch (Throwable t) { 446 t.printStackTrace(); 447 ok = false; 448 } finally { 449 if (tmpFile != null && tmpFile.exists()) 450 tmpFile.delete(); 451 } 452 out.flush(); 453 err.flush(); 454 return ok; 455 } 456 457 private void validateAndClose(File tmpfile) throws IOException { 458 if (ok && isMultiRelease) { 459 try (ZipFile zf = new ZipFile(tmpfile)) { 460 ok = Validator.validate(this, zf); 461 if (!ok) { 462 error(formatMsg("error.validator.jarfile.invalid", fname)); 463 } 464 } catch (IOException e) { 465 error(formatMsg2("error.validator.jarfile.exception", fname, e.getMessage())); 466 } 467 } 468 Path path = tmpfile.toPath(); 469 try { 470 if (ok) { 471 if (fname != null) { 472 Files.move(path, Paths.get(fname), StandardCopyOption.REPLACE_EXISTING); 473 } else { 474 Files.copy(path, new FileOutputStream(FileDescriptor.out)); 475 } 476 } 477 } finally { 478 Files.deleteIfExists(path); 479 } 480 } 481 482 private String[] filesMapToFiles(Map<Integer,String[]> filesMap) { 483 if (filesMap.isEmpty()) return null; 484 return filesMap.entrySet() 485 .stream() 486 .flatMap(this::filesToEntryNames) 487 .toArray(String[]::new); 488 } 489 490 Stream<String> filesToEntryNames(Map.Entry<Integer,String[]> fileEntries) { 491 int version = fileEntries.getKey(); 492 Set<String> cpaths = pathsMap.get(version); 493 return Stream.of(fileEntries.getValue()) 494 .map(f -> toVersionedName(toEntryName(f, cpaths, false), version)); 495 } 496 497 /** 498 * Parses command line arguments. 499 */ 500 boolean parseArgs(String args[]) { 501 /* Preprocess and expand @file arguments */ 502 try { 503 args = CommandLine.parse(args); 504 } catch (FileNotFoundException e) { 505 fatalError(formatMsg("error.cant.open", e.getMessage())); 506 return false; 507 } catch (IOException e) { 508 fatalError(e); 509 return false; 510 } 511 /* parse flags */ 512 int count = 1; 513 try { 514 String flags = args[0]; 515 516 // Note: flags.length == 2 can be treated as the short version of 517 // the GNU option since the there cannot be any other options, 518 // excluding -C, as per the old way. 519 if (flags.startsWith("--") || 520 (flags.startsWith("-") && flags.length() == 2)) { 521 try { 522 count = GNUStyleOptions.parseOptions(this, args); 523 } catch (GNUStyleOptions.BadArgs x) { 524 if (info == null) { 525 if (x.showUsage) { 526 usageError(x.getMessage()); 527 } else { 528 error(x.getMessage()); 529 } 530 return false; 531 } 532 } 533 if (info != null) { 534 info.accept(out); 535 return true; 536 } 537 } else { 538 // Legacy/compatibility options 539 if (flags.startsWith("-")) { 540 flags = flags.substring(1); 541 } 542 for (int i = 0; i < flags.length(); i++) { 543 switch (flags.charAt(i)) { 544 case 'c': 545 if (xflag || tflag || uflag || iflag) { 546 usageError(getMsg("error.multiple.main.operations")); 547 return false; 548 } 549 cflag = true; 550 break; 551 case 'u': 552 if (cflag || xflag || tflag || iflag) { 553 usageError(getMsg("error.multiple.main.operations")); 554 return false; 555 } 556 uflag = true; 557 break; 558 case 'x': 559 if (cflag || uflag || tflag || iflag) { 560 usageError(getMsg("error.multiple.main.operations")); 561 return false; 562 } 563 xflag = true; 564 break; 565 case 't': 566 if (cflag || uflag || xflag || iflag) { 567 usageError(getMsg("error.multiple.main.operations")); 568 return false; 569 } 570 tflag = true; 571 break; 572 case 'M': 573 Mflag = true; 574 break; 575 case 'v': 576 vflag = true; 577 break; 578 case 'f': 579 fname = args[count++]; 580 break; 581 case 'm': 582 mname = args[count++]; 583 break; 584 case '0': 585 flag0 = true; 586 break; 587 case 'i': 588 if (cflag || uflag || xflag || tflag) { 589 usageError(getMsg("error.multiple.main.operations")); 590 return false; 591 } 592 // do not increase the counter, files will contain rootjar 593 rootjar = args[count++]; 594 iflag = true; 595 break; 596 case 'n': 597 nflag = true; 598 break; 599 case 'e': 600 ename = args[count++]; 601 break; 602 case 'P': 603 pflag = true; 604 break; 605 default: 606 usageError(formatMsg("error.illegal.option", 607 String.valueOf(flags.charAt(i)))); 608 return false; 609 } 610 } 611 } 612 } catch (ArrayIndexOutOfBoundsException e) { 613 usageError(getMsg("main.usage.summary")); 614 return false; 615 } 616 if (!cflag && !tflag && !xflag && !uflag && !iflag && !dflag) { 617 usageError(getMsg("error.bad.option")); 618 return false; 619 } 620 621 /* parse file arguments */ 622 int n = args.length - count; 623 if (n > 0) { 624 int version = BASE_VERSION; 625 int k = 0; 626 String[] nameBuf = new String[n]; 627 pathsMap.put(version, new HashSet<>()); 628 try { 629 for (int i = count; i < args.length; i++) { 630 if (args[i].equals("-C")) { 631 if (dflag) { 632 // "--describe-module/-d" does not require file argument(s), 633 // but does accept --release 634 usageError(getMsg("error.bad.dflag")); 635 return false; 636 } 637 /* change the directory */ 638 String dir = args[++i]; 639 dir = (dir.endsWith(File.separator) ? 640 dir : (dir + File.separator)); 641 dir = dir.replace(File.separatorChar, '/'); 642 643 boolean hasUNC = (File.separatorChar == '\\'&& dir.startsWith("//")); 644 while (dir.indexOf("//") > -1) { 645 dir = dir.replace("//", "/"); 646 } 647 if (hasUNC) { // Restore Windows UNC path. 648 dir = "/" + dir; 649 } 650 pathsMap.get(version).add(dir); 651 nameBuf[k++] = dir + args[++i]; 652 } else if (args[i].startsWith("--release")) { 653 int v = BASE_VERSION; 654 try { 655 v = Integer.valueOf(args[++i]); 656 } catch (NumberFormatException x) { 657 error(formatMsg("error.release.value.notnumber", args[i])); 658 // this will fall into the next error, thus returning false 659 } 660 if (v < 9) { 661 usageError(formatMsg("error.release.value.toosmall", String.valueOf(v))); 662 return false; 663 } 664 // associate the files, if any, with the previous version number 665 if (k > 0) { 666 String[] files = new String[k]; 667 System.arraycopy(nameBuf, 0, files, 0, k); 668 filesMap.put(version, files); 669 isMultiRelease = version > BASE_VERSION; 670 } 671 // reset the counters and start with the new version number 672 k = 0; 673 nameBuf = new String[n]; 674 version = v; 675 releaseValue = version; 676 pathsMap.put(version, new HashSet<>()); 677 } else { 678 if (dflag) { 679 // "--describe-module/-d" does not require file argument(s), 680 // but does accept --release 681 usageError(getMsg("error.bad.dflag")); 682 return false; 683 } 684 nameBuf[k++] = args[i]; 685 } 686 } 687 } catch (ArrayIndexOutOfBoundsException e) { 688 usageError(getMsg("error.bad.file.arg")); 689 return false; 690 } 691 // associate remaining files, if any, with a version 692 if (k > 0) { 693 String[] files = new String[k]; 694 System.arraycopy(nameBuf, 0, files, 0, k); 695 filesMap.put(version, files); 696 isMultiRelease = version > BASE_VERSION; 697 } 698 } else if (cflag && (mname == null)) { 699 usageError(getMsg("error.bad.cflag")); 700 return false; 701 } else if (uflag) { 702 if ((mname != null) || (ename != null)) { 703 /* just want to update the manifest */ 704 return true; 705 } else { 706 usageError(getMsg("error.bad.uflag")); 707 return false; 708 } 709 } 710 return true; 711 } 712 713 /* 714 * Add the package of the given resource name if it's a .class 715 * or a resource in a named package. 716 */ 717 void addPackageIfNamed(Set<String> packages, String name) { 718 if (name.startsWith(VERSIONS_DIR)) { 719 // trim the version dir prefix 720 int i0 = VERSIONS_DIR_LENGTH; 721 int i = name.indexOf('/', i0); 722 if (i <= 0) { 723 warn(formatMsg("warn.release.unexpected.versioned.entry", name)); 724 return; 725 } 726 while (i0 < i) { 727 char c = name.charAt(i0); 728 if (c < '0' || c > '9') { 729 warn(formatMsg("warn.release.unexpected.versioned.entry", name)); 730 return; 731 } 732 i0++; 733 } 734 name = name.substring(i + 1, name.length()); 735 } 736 String pn = toPackageName(name); 737 // add if this is a class or resource in a package 738 if (Checks.isPackageName(pn)) { 739 packages.add(pn); 740 } 741 } 742 743 private String toEntryName(String name, Set<String> cpaths, boolean isDir) { 744 name = name.replace(File.separatorChar, '/'); 745 if (isDir) { 746 name = name.endsWith("/") ? name : name + "/"; 747 } 748 String matchPath = ""; 749 for (String path : cpaths) { 750 if (name.startsWith(path) && path.length() > matchPath.length()) { 751 matchPath = path; 752 } 753 } 754 name = safeName(name.substring(matchPath.length())); 755 // the old implementaton doesn't remove 756 // "./" if it was led by "/" (?) 757 if (name.startsWith("./")) { 758 name = name.substring(2); 759 } 760 return name; 761 } 762 763 private static String toVersionedName(String name, int version) { 764 return version > BASE_VERSION 765 ? VERSIONS_DIR + version + "/" + name : name; 766 } 767 768 private static String toPackageName(String path) { 769 int index = path.lastIndexOf('/'); 770 if (index != -1) { 771 return path.substring(0, index).replace('/', '.'); 772 } else { 773 return ""; 774 } 775 } 776 777 private void expand() throws IOException { 778 for (int version : filesMap.keySet()) { 779 String[] files = filesMap.get(version); 780 expand(null, files, pathsMap.get(version), version); 781 } 782 } 783 784 /** 785 * Expands list of files to process into full list of all files that 786 * can be found by recursively descending directories. 787 * 788 * @param dir parent directory 789 * @param files list of files to expand 790 * @param cpaths set of directories specified by -C option for the files 791 * @throws IOException if an I/O error occurs 792 */ 793 private void expand(File dir, String[] files, Set<String> cpaths, int version) 794 throws IOException 795 { 796 if (files == null) 797 return; 798 799 for (int i = 0; i < files.length; i++) { 800 File f; 801 if (dir == null) 802 f = new File(files[i]); 803 else 804 f = new File(dir, files[i]); 805 806 boolean isDir = f.isDirectory(); 807 String name = toEntryName(f.getPath(), cpaths, isDir); 808 809 if (version != BASE_VERSION) { 810 if (name.startsWith(VERSIONS_DIR)) { 811 // the entry starts with VERSIONS_DIR and version != BASE_VERSION, 812 // which means the "[dirs|files]" in --release v [dirs|files] 813 // includes VERSIONS_DIR-ed entries --> warning and skip (?) 814 error(formatMsg2("error.release.unexpected.versioned.entry", 815 name, String.valueOf(version))); 816 ok = false; 817 return; 818 } 819 name = toVersionedName(name, version); 820 } 821 822 if (f.isFile()) { 823 Entry e = new Entry(f, name, false); 824 if (isModuleInfoEntry(name)) { 825 moduleInfos.putIfAbsent(name, Files.readAllBytes(f.toPath())); 826 if (uflag) 827 entryMap.put(name, e); 828 } else if (entries.add(e)) { 829 if (uflag) 830 entryMap.put(name, e); 831 } 832 } else if (isDir) { 833 Entry e = new Entry(f, name, true); 834 if (entries.add(e)) { 835 // utilize entryMap for the duplicate dir check even in 836 // case of cflag == true. 837 // dir name confilict/duplicate could happen with -C option. 838 // just remove the last "e" from the "entries" (zos will fail 839 // with "duplicated" entries), but continue expanding the 840 // sub tree 841 if (entryMap.containsKey(name)) { 842 entries.remove(e); 843 } else { 844 entryMap.put(name, e); 845 } 846 expand(f, f.list(), cpaths, version); 847 } 848 } else { 849 error(formatMsg("error.nosuch.fileordir", String.valueOf(f))); 850 ok = false; 851 } 852 } 853 } 854 855 /** 856 * Creates a new JAR file. 857 */ 858 void create(OutputStream out, Manifest manifest) throws IOException 859 { 860 try (ZipOutputStream zos = new JarOutputStream(out)) { 861 if (flag0) { 862 zos.setMethod(ZipOutputStream.STORED); 863 } 864 // TODO: check module-info attributes against manifest ?? 865 if (manifest != null) { 866 if (vflag) { 867 output(getMsg("out.added.manifest")); 868 } 869 ZipEntry e = new ZipEntry(MANIFEST_DIR); 870 e.setTime(System.currentTimeMillis()); 871 e.setSize(0); 872 e.setCrc(0); 873 zos.putNextEntry(e); 874 e = new ZipEntry(MANIFEST_NAME); 875 e.setTime(System.currentTimeMillis()); 876 if (flag0) { 877 crc32Manifest(e, manifest); 878 } 879 zos.putNextEntry(e); 880 manifest.write(zos); 881 zos.closeEntry(); 882 } 883 updateModuleInfo(moduleInfos, zos); 884 for (Entry entry : entries) { 885 addFile(zos, entry); 886 } 887 } 888 } 889 890 private char toUpperCaseASCII(char c) { 891 return (c < 'a' || c > 'z') ? c : (char) (c + 'A' - 'a'); 892 } 893 894 /** 895 * Compares two strings for equality, ignoring case. The second 896 * argument must contain only upper-case ASCII characters. 897 * We don't want case comparison to be locale-dependent (else we 898 * have the notorious "turkish i bug"). 899 */ 900 private boolean equalsIgnoreCase(String s, String upper) { 901 assert upper.toUpperCase(java.util.Locale.ENGLISH).equals(upper); 902 int len; 903 if ((len = s.length()) != upper.length()) 904 return false; 905 for (int i = 0; i < len; i++) { 906 char c1 = s.charAt(i); 907 char c2 = upper.charAt(i); 908 if (c1 != c2 && toUpperCaseASCII(c1) != c2) 909 return false; 910 } 911 return true; 912 } 913 914 /** 915 * Updates an existing jar file. 916 */ 917 boolean update(InputStream in, OutputStream out, 918 InputStream newManifest, 919 Map<String,byte[]> moduleInfos, 920 JarIndex jarIndex) throws IOException 921 { 922 ZipInputStream zis = new ZipInputStream(in); 923 ZipOutputStream zos = new JarOutputStream(out); 924 ZipEntry e = null; 925 boolean foundManifest = false; 926 boolean updateOk = true; 927 928 // All actual entries added/updated/existing, in the jar file (excl manifest 929 // and module-info.class ). 930 Set<String> jentries = new HashSet<>(); 931 932 if (jarIndex != null) { 933 addIndex(jarIndex, zos); 934 } 935 936 // put the old entries first, replace if necessary 937 while ((e = zis.getNextEntry()) != null) { 938 String name = e.getName(); 939 940 boolean isManifestEntry = equalsIgnoreCase(name, MANIFEST_NAME); 941 boolean isModuleInfoEntry = isModuleInfoEntry(name); 942 943 if ((jarIndex != null && equalsIgnoreCase(name, INDEX_NAME)) 944 || (Mflag && isManifestEntry)) { 945 continue; 946 } else if (isManifestEntry && ((newManifest != null) || 947 (ename != null) || isMultiRelease)) { 948 foundManifest = true; 949 if (newManifest != null) { 950 // Don't read from the newManifest InputStream, as we 951 // might need it below, and we can't re-read the same data 952 // twice. 953 FileInputStream fis = new FileInputStream(mname); 954 boolean ambiguous = isAmbiguousMainClass(new Manifest(fis)); 955 fis.close(); 956 if (ambiguous) { 957 return false; 958 } 959 } 960 // Update the manifest. 961 Manifest old = new Manifest(zis); 962 if (newManifest != null) { 963 old.read(newManifest); 964 } 965 if (!updateManifest(old, zos)) { 966 return false; 967 } 968 } else if (moduleInfos != null && isModuleInfoEntry) { 969 moduleInfos.putIfAbsent(name, zis.readAllBytes()); 970 } else { 971 boolean isDir = e.isDirectory(); 972 if (!entryMap.containsKey(name)) { // copy the old stuff 973 // do our own compression 974 ZipEntry e2 = new ZipEntry(name); 975 e2.setMethod(e.getMethod()); 976 e2.setTime(e.getTime()); 977 e2.setComment(e.getComment()); 978 e2.setExtra(e.getExtra()); 979 if (e.getMethod() == ZipEntry.STORED) { 980 e2.setSize(e.getSize()); 981 e2.setCrc(e.getCrc()); 982 } 983 zos.putNextEntry(e2); 984 copy(zis, zos); 985 } else { // replace with the new files 986 Entry ent = entryMap.get(name); 987 addFile(zos, ent); 988 entryMap.remove(name); 989 entries.remove(ent); 990 isDir = ent.isDir; 991 } 992 if (!isDir) { 993 jentries.add(name); 994 } 995 } 996 } 997 998 // add the remaining new files 999 for (Entry entry : entries) { 1000 addFile(zos, entry); 1001 if (!entry.isDir) { 1002 jentries.add(entry.name); 1003 } 1004 } 1005 if (!foundManifest) { 1006 if (newManifest != null) { 1007 Manifest m = new Manifest(newManifest); 1008 updateOk = !isAmbiguousMainClass(m); 1009 if (updateOk) { 1010 if (!updateManifest(m, zos)) { 1011 updateOk = false; 1012 } 1013 } 1014 } else if (ename != null) { 1015 if (!updateManifest(new Manifest(), zos)) { 1016 updateOk = false; 1017 } 1018 } 1019 } 1020 if (updateOk) { 1021 if (moduleInfos != null && !moduleInfos.isEmpty()) { 1022 Set<String> pkgs = new HashSet<>(); 1023 jentries.forEach( je -> addPackageIfNamed(pkgs, je)); 1024 addExtendedModuleAttributes(moduleInfos, pkgs); 1025 updateOk = checkModuleInfo(moduleInfos.get(MODULE_INFO), jentries); 1026 updateModuleInfo(moduleInfos, zos); 1027 // TODO: check manifest main classes, etc 1028 } else if (moduleVersion != null || modulesToHash != null) { 1029 error(getMsg("error.module.options.without.info")); 1030 updateOk = false; 1031 } 1032 } 1033 zis.close(); 1034 zos.close(); 1035 return updateOk; 1036 } 1037 1038 private void addIndex(JarIndex index, ZipOutputStream zos) 1039 throws IOException 1040 { 1041 ZipEntry e = new ZipEntry(INDEX_NAME); 1042 e.setTime(System.currentTimeMillis()); 1043 if (flag0) { 1044 CRC32OutputStream os = new CRC32OutputStream(); 1045 index.write(os); 1046 os.updateEntry(e); 1047 } 1048 zos.putNextEntry(e); 1049 index.write(zos); 1050 zos.closeEntry(); 1051 } 1052 1053 private void updateModuleInfo(Map<String,byte[]> moduleInfos, ZipOutputStream zos) 1054 throws IOException 1055 { 1056 String fmt = uflag ? "out.update.module-info": "out.added.module-info"; 1057 for (Map.Entry<String,byte[]> mi : moduleInfos.entrySet()) { 1058 String name = mi.getKey(); 1059 byte[] bytes = mi.getValue(); 1060 ZipEntry e = new ZipEntry(name); 1061 e.setTime(System.currentTimeMillis()); 1062 if (flag0) { 1063 crc32ModuleInfo(e, bytes); 1064 } 1065 zos.putNextEntry(e); 1066 zos.write(bytes); 1067 zos.closeEntry(); 1068 if (vflag) { 1069 output(formatMsg(fmt, name)); 1070 } 1071 } 1072 } 1073 1074 private boolean updateManifest(Manifest m, ZipOutputStream zos) 1075 throws IOException 1076 { 1077 addVersion(m); 1078 addCreatedBy(m); 1079 if (ename != null) { 1080 addMainClass(m, ename); 1081 } 1082 if (isMultiRelease) { 1083 addMultiRelease(m); 1084 } 1085 ZipEntry e = new ZipEntry(MANIFEST_NAME); 1086 e.setTime(System.currentTimeMillis()); 1087 if (flag0) { 1088 crc32Manifest(e, m); 1089 } 1090 zos.putNextEntry(e); 1091 m.write(zos); 1092 if (vflag) { 1093 output(getMsg("out.update.manifest")); 1094 } 1095 return true; 1096 } 1097 1098 private static final boolean isWinDriveLetter(char c) { 1099 return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')); 1100 } 1101 1102 private String safeName(String name) { 1103 if (!pflag) { 1104 int len = name.length(); 1105 int i = name.lastIndexOf("../"); 1106 if (i == -1) { 1107 i = 0; 1108 } else { 1109 i += 3; // strip any dot-dot components 1110 } 1111 if (File.separatorChar == '\\') { 1112 // the spec requests no drive letter. skip if 1113 // the entry name has one. 1114 while (i < len) { 1115 int off = i; 1116 if (i + 1 < len && 1117 name.charAt(i + 1) == ':' && 1118 isWinDriveLetter(name.charAt(i))) { 1119 i += 2; 1120 } 1121 while (i < len && name.charAt(i) == '/') { 1122 i++; 1123 } 1124 if (i == off) { 1125 break; 1126 } 1127 } 1128 } else { 1129 while (i < len && name.charAt(i) == '/') { 1130 i++; 1131 } 1132 } 1133 if (i != 0) { 1134 name = name.substring(i); 1135 } 1136 } 1137 return name; 1138 } 1139 1140 private void addVersion(Manifest m) { 1141 Attributes global = m.getMainAttributes(); 1142 if (global.getValue(Attributes.Name.MANIFEST_VERSION) == null) { 1143 global.put(Attributes.Name.MANIFEST_VERSION, VERSION); 1144 } 1145 } 1146 1147 private void addCreatedBy(Manifest m) { 1148 Attributes global = m.getMainAttributes(); 1149 if (global.getValue(new Attributes.Name("Created-By")) == null) { 1150 String javaVendor = System.getProperty("java.vendor"); 1151 String jdkVersion = System.getProperty("java.version"); 1152 global.put(new Attributes.Name("Created-By"), jdkVersion + " (" + 1153 javaVendor + ")"); 1154 } 1155 } 1156 1157 private void addMainClass(Manifest m, String mainApp) { 1158 Attributes global = m.getMainAttributes(); 1159 1160 // overrides any existing Main-Class attribute 1161 global.put(Attributes.Name.MAIN_CLASS, mainApp); 1162 } 1163 1164 private void addMultiRelease(Manifest m) { 1165 Attributes global = m.getMainAttributes(); 1166 global.put(Attributes.Name.MULTI_RELEASE, "true"); 1167 } 1168 1169 private boolean isAmbiguousMainClass(Manifest m) { 1170 if (ename != null) { 1171 Attributes global = m.getMainAttributes(); 1172 if ((global.get(Attributes.Name.MAIN_CLASS) != null)) { 1173 usageError(getMsg("error.bad.eflag")); 1174 return true; 1175 } 1176 } 1177 return false; 1178 } 1179 1180 /** 1181 * Adds a new file entry to the ZIP output stream. 1182 */ 1183 void addFile(ZipOutputStream zos, Entry entry) throws IOException { 1184 1185 File file = entry.file; 1186 String name = entry.name; 1187 boolean isDir = entry.isDir; 1188 1189 if (name.equals("") || name.equals(".") || name.equals(zname)) { 1190 return; 1191 } else if ((name.equals(MANIFEST_DIR) || name.equals(MANIFEST_NAME)) 1192 && !Mflag) { 1193 if (vflag) { 1194 output(formatMsg("out.ignore.entry", name)); 1195 } 1196 return; 1197 } else if (name.equals(MODULE_INFO)) { 1198 throw new Error("Unexpected module info: " + name); 1199 } 1200 1201 long size = isDir ? 0 : file.length(); 1202 1203 if (vflag) { 1204 out.print(formatMsg("out.adding", name)); 1205 } 1206 ZipEntry e = new ZipEntry(name); 1207 e.setTime(file.lastModified()); 1208 if (size == 0) { 1209 e.setMethod(ZipEntry.STORED); 1210 e.setSize(0); 1211 e.setCrc(0); 1212 } else if (flag0) { 1213 crc32File(e, file); 1214 } 1215 if (oflag) { 1216 e.setPosixPermissions(Files.getPosixFilePermissions(file.toPath())); 1217 } 1218 zos.putNextEntry(e); 1219 if (!isDir) { 1220 copy(file, zos); 1221 } 1222 zos.closeEntry(); 1223 /* report how much compression occurred. */ 1224 if (vflag) { 1225 size = e.getSize(); 1226 long csize = e.getCompressedSize(); 1227 out.print(formatMsg2("out.size", String.valueOf(size), 1228 String.valueOf(csize))); 1229 if (e.getMethod() == ZipEntry.DEFLATED) { 1230 long ratio = 0; 1231 if (size != 0) { 1232 ratio = ((size - csize) * 100) / size; 1233 } 1234 output(formatMsg("out.deflated", String.valueOf(ratio))); 1235 } else { 1236 output(getMsg("out.stored")); 1237 } 1238 } 1239 } 1240 1241 /** 1242 * A buffer for use only by copy(InputStream, OutputStream). 1243 * Not as clean as allocating a new buffer as needed by copy, 1244 * but significantly more efficient. 1245 */ 1246 private byte[] copyBuf = new byte[8192]; 1247 1248 /** 1249 * Copies all bytes from the input stream to the output stream. 1250 * Does not close or flush either stream. 1251 * 1252 * @param from the input stream to read from 1253 * @param to the output stream to write to 1254 * @throws IOException if an I/O error occurs 1255 */ 1256 private void copy(InputStream from, OutputStream to) throws IOException { 1257 int n; 1258 while ((n = from.read(copyBuf)) != -1) 1259 to.write(copyBuf, 0, n); 1260 } 1261 1262 /** 1263 * Copies all bytes from the input file to the output stream. 1264 * Does not close or flush the output stream. 1265 * 1266 * @param from the input file to read from 1267 * @param to the output stream to write to 1268 * @throws IOException if an I/O error occurs 1269 */ 1270 private void copy(File from, OutputStream to) throws IOException { 1271 try (InputStream in = new FileInputStream(from)) { 1272 copy(in, to); 1273 } 1274 } 1275 1276 /** 1277 * Copies all bytes from the input stream to the output file. 1278 * Does not close the input stream. 1279 * 1280 * @param from the input stream to read from 1281 * @param to the output file to write to 1282 * @throws IOException if an I/O error occurs 1283 */ 1284 private void copy(InputStream from, File to) throws IOException { 1285 try (OutputStream out = new FileOutputStream(to)) { 1286 copy(from, out); 1287 } 1288 } 1289 1290 /** 1291 * Computes the crc32 of a module-info.class. This is necessary when the 1292 * ZipOutputStream is in STORED mode. 1293 */ 1294 private void crc32ModuleInfo(ZipEntry e, byte[] bytes) throws IOException { 1295 CRC32OutputStream os = new CRC32OutputStream(); 1296 ByteArrayInputStream in = new ByteArrayInputStream(bytes); 1297 in.transferTo(os); 1298 os.updateEntry(e); 1299 } 1300 1301 /** 1302 * Computes the crc32 of a Manifest. This is necessary when the 1303 * ZipOutputStream is in STORED mode. 1304 */ 1305 private void crc32Manifest(ZipEntry e, Manifest m) throws IOException { 1306 CRC32OutputStream os = new CRC32OutputStream(); 1307 m.write(os); 1308 os.updateEntry(e); 1309 } 1310 1311 /** 1312 * Computes the crc32 of a File. This is necessary when the 1313 * ZipOutputStream is in STORED mode. 1314 */ 1315 private void crc32File(ZipEntry e, File f) throws IOException { 1316 CRC32OutputStream os = new CRC32OutputStream(); 1317 copy(f, os); 1318 if (os.n != f.length()) { 1319 throw new JarException(formatMsg( 1320 "error.incorrect.length", f.getPath())); 1321 } 1322 os.updateEntry(e); 1323 } 1324 1325 void replaceFSC(Map<Integer, String []> filesMap) { 1326 filesMap.keySet().forEach(version -> { 1327 String[] files = filesMap.get(version); 1328 if (files != null) { 1329 for (int i = 0; i < files.length; i++) { 1330 files[i] = files[i].replace(File.separatorChar, '/'); 1331 } 1332 } 1333 }); 1334 } 1335 1336 @SuppressWarnings("serial") 1337 Set<ZipEntry> newDirSet() { 1338 return new HashSet<ZipEntry>() { 1339 public boolean add(ZipEntry e) { 1340 return ((e == null || useExtractionTime) ? false : super.add(e)); 1341 }}; 1342 } 1343 1344 void updateLastModifiedTime(Set<ZipEntry> zes) throws IOException { 1345 for (ZipEntry ze : zes) { 1346 long lastModified = ze.getTime(); 1347 if (lastModified != -1) { 1348 String name = safeName(ze.getName().replace(File.separatorChar, '/')); 1349 if (name.length() != 0) { 1350 File f = new File(name.replace('/', File.separatorChar)); 1351 f.setLastModified(lastModified); 1352 } 1353 } 1354 } 1355 } 1356 1357 /** 1358 * Extracts specified entries from JAR file. 1359 * 1360 * @return whether entries were found and successfully extracted 1361 * (indicating this was a zip file without "leading garbage") 1362 */ 1363 boolean extract(InputStream in, String files[]) throws IOException { 1364 ZipInputStream zis = new ZipInputStream(in); 1365 ZipEntry e; 1366 // Set of all directory entries specified in archive. Disallows 1367 // null entries. Disallows all entries if using pre-6.0 behavior. 1368 boolean entriesFound = false; 1369 Set<ZipEntry> dirs = newDirSet(); 1370 while ((e = zis.getNextEntry()) != null) { 1371 entriesFound = true; 1372 if (files == null) { 1373 dirs.add(extractFile(zis, e)); 1374 } else { 1375 String name = e.getName(); 1376 for (String file : files) { 1377 if (name.startsWith(file)) { 1378 dirs.add(extractFile(zis, e)); 1379 break; 1380 } 1381 } 1382 } 1383 } 1384 1385 // Update timestamps of directories specified in archive with their 1386 // timestamps as given in the archive. We do this after extraction, 1387 // instead of during, because creating a file in a directory changes 1388 // that directory's timestamp. 1389 updateLastModifiedTime(dirs); 1390 1391 return entriesFound; 1392 } 1393 1394 /** 1395 * Extracts specified entries from JAR file, via ZipFile. 1396 */ 1397 void extract(String fname, String files[]) throws IOException { 1398 ZipFile zf = new ZipFile(fname); 1399 Set<ZipEntry> dirs = newDirSet(); 1400 Enumeration<? extends ZipEntry> zes = zf.entries(); 1401 while (zes.hasMoreElements()) { 1402 ZipEntry e = zes.nextElement(); 1403 if (files == null) { 1404 dirs.add(extractFile(zf.getInputStream(e), e)); 1405 } else { 1406 String name = e.getName(); 1407 for (String file : files) { 1408 if (name.startsWith(file)) { 1409 dirs.add(extractFile(zf.getInputStream(e), e)); 1410 break; 1411 } 1412 } 1413 } 1414 } 1415 zf.close(); 1416 updateLastModifiedTime(dirs); 1417 } 1418 1419 /** 1420 * Extracts next entry from JAR file, creating directories as needed. If 1421 * the entry is for a directory which doesn't exist prior to this 1422 * invocation, returns that entry, otherwise returns null. 1423 */ 1424 ZipEntry extractFile(InputStream is, ZipEntry e) throws IOException { 1425 ZipEntry rc = null; 1426 // The spec requres all slashes MUST be forward '/', it is possible 1427 // an offending zip/jar entry may uses the backwards slash in its 1428 // name. It might cause problem on Windows platform as it skips 1429 // our "safe" check for leading slahs and dot-dot. So replace them 1430 // with '/'. 1431 String name = safeName(e.getName().replace(File.separatorChar, '/')); 1432 if (name.length() == 0) { 1433 return rc; // leading '/' or 'dot-dot' only path 1434 } 1435 File f = new File(name.replace('/', File.separatorChar)); 1436 if (e.isDirectory()) { 1437 if (f.exists()) { 1438 if (!f.isDirectory()) { 1439 throw new IOException(formatMsg("error.create.dir", 1440 f.getPath())); 1441 } 1442 } else { 1443 if (!f.mkdirs()) { 1444 throw new IOException(formatMsg("error.create.dir", 1445 f.getPath())); 1446 } else { 1447 rc = e; 1448 } 1449 } 1450 1451 if (vflag) { 1452 output(formatMsg("out.create", name)); 1453 } 1454 } else { 1455 if (f.getParent() != null) { 1456 File d = new File(f.getParent()); 1457 if (!d.exists() && !d.mkdirs() || !d.isDirectory()) { 1458 throw new IOException(formatMsg( 1459 "error.create.dir", d.getPath())); 1460 } 1461 } 1462 try { 1463 copy(is, f); 1464 } finally { 1465 if (is instanceof ZipInputStream) 1466 ((ZipInputStream)is).closeEntry(); 1467 else 1468 is.close(); 1469 } 1470 if (vflag) { 1471 if (e.getMethod() == ZipEntry.DEFLATED) { 1472 output(formatMsg("out.inflated", name)); 1473 } else { 1474 output(formatMsg("out.extracted", name)); 1475 } 1476 } 1477 } 1478 if (!useExtractionTime) { 1479 long lastModified = e.getTime(); 1480 if (lastModified != -1) { 1481 f.setLastModified(lastModified); 1482 } 1483 } 1484 if (oflag) { 1485 Optional<Set<PosixFilePermission>> permissions = e.getPosixPermissions(); 1486 if (permissions.isPresent()) { 1487 try { 1488 Files.setPosixFilePermissions(f.toPath(), permissions.get()); 1489 } catch (UnsupportedOperationException exc) { 1490 // Ignore the exception 1491 } 1492 } 1493 } 1494 return rc; 1495 } 1496 1497 /** 1498 * Lists contents of JAR file. 1499 */ 1500 void list(InputStream in, String files[]) throws IOException { 1501 ZipInputStream zis = new ZipInputStream(in); 1502 ZipEntry e; 1503 while ((e = zis.getNextEntry()) != null) { 1504 /* 1505 * In the case of a compressed (deflated) entry, the entry size 1506 * is stored immediately following the entry data and cannot be 1507 * determined until the entry is fully read. Therefore, we close 1508 * the entry first before printing out its attributes. 1509 */ 1510 zis.closeEntry(); 1511 printEntry(e, files); 1512 } 1513 } 1514 1515 /** 1516 * Lists contents of JAR file, via ZipFile. 1517 */ 1518 void list(String fname, String files[]) throws IOException { 1519 ZipFile zf = new ZipFile(fname); 1520 Enumeration<? extends ZipEntry> zes = zf.entries(); 1521 while (zes.hasMoreElements()) { 1522 printEntry(zes.nextElement(), files); 1523 } 1524 zf.close(); 1525 } 1526 1527 /** 1528 * Outputs the class index table to the INDEX.LIST file of the 1529 * root jar file. 1530 */ 1531 void dumpIndex(String rootjar, JarIndex index) throws IOException { 1532 File jarFile = new File(rootjar); 1533 Path jarPath = jarFile.toPath(); 1534 Path tmpPath = createTempFileInSameDirectoryAs(jarFile).toPath(); 1535 try { 1536 if (update(Files.newInputStream(jarPath), 1537 Files.newOutputStream(tmpPath), 1538 null, null, index)) { 1539 try { 1540 Files.move(tmpPath, jarPath, REPLACE_EXISTING); 1541 } catch (IOException e) { 1542 throw new IOException(getMsg("error.write.file"), e); 1543 } 1544 } 1545 } finally { 1546 Files.deleteIfExists(tmpPath); 1547 } 1548 } 1549 1550 private HashSet<String> jarPaths = new HashSet<String>(); 1551 1552 /** 1553 * Generates the transitive closure of the Class-Path attribute for 1554 * the specified jar file. 1555 */ 1556 List<String> getJarPath(String jar) throws IOException { 1557 List<String> files = new ArrayList<String>(); 1558 files.add(jar); 1559 jarPaths.add(jar); 1560 1561 // take out the current path 1562 String path = jar.substring(0, Math.max(0, jar.lastIndexOf('/') + 1)); 1563 1564 // class path attribute will give us jar file name with 1565 // '/' as separators, so we need to change them to the 1566 // appropriate one before we open the jar file. 1567 JarFile rf = new JarFile(jar.replace('/', File.separatorChar)); 1568 1569 if (rf != null) { 1570 Manifest man = rf.getManifest(); 1571 if (man != null) { 1572 Attributes attr = man.getMainAttributes(); 1573 if (attr != null) { 1574 String value = attr.getValue(Attributes.Name.CLASS_PATH); 1575 if (value != null) { 1576 StringTokenizer st = new StringTokenizer(value); 1577 while (st.hasMoreTokens()) { 1578 String ajar = st.nextToken(); 1579 if (!ajar.endsWith("/")) { // it is a jar file 1580 ajar = path.concat(ajar); 1581 /* check on cyclic dependency */ 1582 if (! jarPaths.contains(ajar)) { 1583 files.addAll(getJarPath(ajar)); 1584 } 1585 } 1586 } 1587 } 1588 } 1589 } 1590 } 1591 rf.close(); 1592 return files; 1593 } 1594 1595 /** 1596 * Generates class index file for the specified root jar file. 1597 */ 1598 void genIndex(String rootjar, String[] files) throws IOException { 1599 List<String> jars = getJarPath(rootjar); 1600 int njars = jars.size(); 1601 String[] jarfiles; 1602 1603 if (njars == 1 && files != null) { 1604 // no class-path attribute defined in rootjar, will 1605 // use command line specified list of jars 1606 for (int i = 0; i < files.length; i++) { 1607 jars.addAll(getJarPath(files[i])); 1608 } 1609 njars = jars.size(); 1610 } 1611 jarfiles = jars.toArray(new String[njars]); 1612 JarIndex index = new JarIndex(jarfiles); 1613 dumpIndex(rootjar, index); 1614 } 1615 1616 /** 1617 * Prints entry information, if requested. 1618 */ 1619 void printEntry(ZipEntry e, String[] files) throws IOException { 1620 if (files == null) { 1621 printEntry(e); 1622 } else { 1623 String name = e.getName(); 1624 for (String file : files) { 1625 if (name.startsWith(file)) { 1626 printEntry(e); 1627 return; 1628 } 1629 } 1630 } 1631 } 1632 1633 /** 1634 * Prints entry information. 1635 */ 1636 void printEntry(ZipEntry e) throws IOException { 1637 if (vflag) { 1638 StringBuilder sb = new StringBuilder(); 1639 String s = Long.toString(e.getSize()); 1640 for (int i = 6 - s.length(); i > 0; --i) { 1641 sb.append(' '); 1642 } 1643 sb.append(s).append(' ').append(new Date(e.getTime()).toString()); 1644 sb.append(' ').append(e.getName()); 1645 output(sb.toString()); 1646 } else { 1647 output(e.getName()); 1648 } 1649 } 1650 1651 /** 1652 * Prints usage message. 1653 */ 1654 void usageError(String s) { 1655 err.println(s); 1656 err.println(getMsg("main.usage.summary.try")); 1657 } 1658 1659 /** 1660 * A fatal exception has been caught. No recovery possible 1661 */ 1662 void fatalError(Exception e) { 1663 e.printStackTrace(); 1664 } 1665 1666 /** 1667 * A fatal condition has been detected; message is "s". 1668 * No recovery possible 1669 */ 1670 void fatalError(String s) { 1671 error(program + ": " + s); 1672 } 1673 1674 /** 1675 * Print an output message; like verbose output and the like 1676 */ 1677 protected void output(String s) { 1678 out.println(s); 1679 } 1680 1681 /** 1682 * Print an error message; like something is broken 1683 */ 1684 void error(String s) { 1685 err.println(s); 1686 } 1687 1688 /** 1689 * Print a warning message 1690 */ 1691 void warn(String s) { 1692 err.println(s); 1693 } 1694 1695 /** 1696 * Main routine to start program. 1697 */ 1698 public static void main(String args[]) { 1699 Main jartool = new Main(System.out, System.err, "jar"); 1700 System.exit(jartool.run(args) ? 0 : 1); 1701 } 1702 1703 /** 1704 * An OutputStream that doesn't send its output anywhere, (but could). 1705 * It's here to find the CRC32 of an input file, necessary for STORED 1706 * mode in ZIP. 1707 */ 1708 private static class CRC32OutputStream extends java.io.OutputStream { 1709 final CRC32 crc = new CRC32(); 1710 long n = 0; 1711 1712 CRC32OutputStream() {} 1713 1714 public void write(int r) throws IOException { 1715 crc.update(r); 1716 n++; 1717 } 1718 1719 public void write(byte[] b, int off, int len) throws IOException { 1720 crc.update(b, off, len); 1721 n += len; 1722 } 1723 1724 /** 1725 * Updates a ZipEntry which describes the data read by this 1726 * output stream, in STORED mode. 1727 */ 1728 public void updateEntry(ZipEntry e) { 1729 e.setMethod(ZipEntry.STORED); 1730 e.setSize(n); 1731 e.setCrc(crc.getValue()); 1732 } 1733 } 1734 1735 /** 1736 * Attempt to create temporary file in the system-provided temporary folder, if failed attempts 1737 * to create it in the same folder as the file in parameter (if any) 1738 */ 1739 private File createTemporaryFile(String tmpbase, String suffix) { 1740 File tmpfile = null; 1741 1742 try { 1743 tmpfile = File.createTempFile(tmpbase, suffix); 1744 } catch (IOException | SecurityException e) { 1745 // Unable to create file due to permission violation or security exception 1746 } 1747 if (tmpfile == null) { 1748 // Were unable to create temporary file, fall back to temporary file in the same folder 1749 if (fname != null) { 1750 try { 1751 File tmpfolder = new File(fname).getAbsoluteFile().getParentFile(); 1752 tmpfile = File.createTempFile(fname, ".tmp" + suffix, tmpfolder); 1753 } catch (IOException ioe) { 1754 // Last option failed - fall gracefully 1755 fatalError(ioe); 1756 } 1757 } else { 1758 // No options left - we can not compress to stdout without access to the temporary folder 1759 fatalError(new IOException(getMsg("error.create.tempfile"))); 1760 } 1761 } 1762 return tmpfile; 1763 } 1764 1765 // Modular jar support 1766 1767 /** 1768 * Associates a module descriptor's zip entry name along with its 1769 * bytes and an optional URI. Used when describing modules. 1770 */ 1771 interface ModuleInfoEntry { 1772 String name(); 1773 Optional<String> uriString(); 1774 InputStream bytes() throws IOException; 1775 } 1776 1777 static class ZipFileModuleInfoEntry implements ModuleInfoEntry { 1778 private final ZipFile zipFile; 1779 private final ZipEntry entry; 1780 ZipFileModuleInfoEntry(ZipFile zipFile, ZipEntry entry) { 1781 this.zipFile = zipFile; 1782 this.entry = entry; 1783 } 1784 @Override public String name() { return entry.getName(); } 1785 @Override public InputStream bytes() throws IOException { 1786 return zipFile.getInputStream(entry); 1787 } 1788 /** Returns an optional containing the effective URI. */ 1789 @Override public Optional<String> uriString() { 1790 String uri = (Paths.get(zipFile.getName())).toUri().toString(); 1791 uri = "jar:" + uri + "/!" + entry.getName(); 1792 return Optional.of(uri); 1793 } 1794 } 1795 1796 static class StreamedModuleInfoEntry implements ModuleInfoEntry { 1797 private final String name; 1798 private final byte[] bytes; 1799 StreamedModuleInfoEntry(String name, byte[] bytes) { 1800 this.name = name; 1801 this.bytes = bytes; 1802 } 1803 @Override public String name() { return name; } 1804 @Override public InputStream bytes() throws IOException { 1805 return new ByteArrayInputStream(bytes); 1806 } 1807 /** Returns an empty optional. */ 1808 @Override public Optional<String> uriString() { 1809 return Optional.empty(); // no URI can be derived 1810 } 1811 } 1812 1813 /** Describes a module from a given zip file. */ 1814 private boolean describeModule(ZipFile zipFile) throws IOException { 1815 ZipFileModuleInfoEntry[] infos = zipFile.stream() 1816 .filter(e -> isModuleInfoEntry(e.getName())) 1817 .sorted(ENTRY_COMPARATOR) 1818 .map(e -> new ZipFileModuleInfoEntry(zipFile, e)) 1819 .toArray(ZipFileModuleInfoEntry[]::new); 1820 1821 if (infos.length == 0) { 1822 // No module descriptor found, derive and describe the automatic module 1823 String fn = zipFile.getName(); 1824 ModuleFinder mf = ModuleFinder.of(Paths.get(fn)); 1825 try { 1826 Set<ModuleReference> mref = mf.findAll(); 1827 if (mref.isEmpty()) { 1828 output(formatMsg("error.unable.derive.automodule", fn)); 1829 return true; 1830 } 1831 ModuleDescriptor md = mref.iterator().next().descriptor(); 1832 output(getMsg("out.automodule") + "\n"); 1833 describeModule(md, null, null, ""); 1834 } catch (FindException e) { 1835 String msg = formatMsg("error.unable.derive.automodule", fn); 1836 Throwable t = e.getCause(); 1837 if (t != null) 1838 msg = msg + "\n" + t.getMessage(); 1839 output(msg); 1840 } 1841 } else { 1842 return describeModuleFromEntries(infos); 1843 } 1844 return true; 1845 } 1846 1847 private boolean describeModuleFromStream(FileInputStream fis) 1848 throws IOException 1849 { 1850 List<ModuleInfoEntry> infos = new LinkedList<>(); 1851 1852 try (BufferedInputStream bis = new BufferedInputStream(fis); 1853 ZipInputStream zis = new ZipInputStream(bis)) { 1854 ZipEntry e; 1855 while ((e = zis.getNextEntry()) != null) { 1856 String ename = e.getName(); 1857 if (isModuleInfoEntry(ename)) { 1858 infos.add(new StreamedModuleInfoEntry(ename, zis.readAllBytes())); 1859 } 1860 } 1861 } 1862 1863 if (infos.size() == 0) 1864 return false; 1865 1866 ModuleInfoEntry[] sorted = infos.stream() 1867 .sorted(Comparator.comparing(ModuleInfoEntry::name, ENTRYNAME_COMPARATOR)) 1868 .toArray(ModuleInfoEntry[]::new); 1869 1870 return describeModuleFromEntries(sorted); 1871 } 1872 1873 private boolean lessThanEqualReleaseValue(ModuleInfoEntry entry) { 1874 return intVersionFromEntry(entry) <= releaseValue ? true : false; 1875 } 1876 1877 private static String versionFromEntryName(String name) { 1878 String s = name.substring(VERSIONS_DIR_LENGTH); 1879 return s.substring(0, s.indexOf("/")); 1880 } 1881 1882 private static int intVersionFromEntry(ModuleInfoEntry entry) { 1883 String name = entry.name(); 1884 if (!name.startsWith(VERSIONS_DIR)) 1885 return BASE_VERSION; 1886 1887 String s = name.substring(VERSIONS_DIR_LENGTH); 1888 s = s.substring(0, s.indexOf('/')); 1889 return Integer.valueOf(s); 1890 } 1891 1892 /** 1893 * Describes a single module descriptor, determined by the specified 1894 * --release, if any, from the given ordered entries. 1895 * The given infos must be ordered as per ENTRY_COMPARATOR. 1896 */ 1897 private boolean describeModuleFromEntries(ModuleInfoEntry[] infos) 1898 throws IOException 1899 { 1900 assert infos.length > 0; 1901 1902 // Informative: output all non-root descriptors, if any 1903 String releases = Arrays.stream(infos) 1904 .filter(e -> !e.name().equals(MODULE_INFO)) 1905 .map(ModuleInfoEntry::name) 1906 .map(Main::versionFromEntryName) 1907 .collect(joining(" ")); 1908 if (!releases.equals("")) 1909 output("releases: " + releases + "\n"); 1910 1911 // Describe the operative descriptor for the specified --release, if any 1912 if (releaseValue != -1) { 1913 ModuleInfoEntry entry = null; 1914 int i = 0; 1915 while (i < infos.length && lessThanEqualReleaseValue(infos[i])) { 1916 entry = infos[i]; 1917 i++; 1918 } 1919 1920 if (entry == null) { 1921 output(formatMsg("error.no.operative.descriptor", 1922 String.valueOf(releaseValue))); 1923 return false; 1924 } 1925 1926 String uriString = entry.uriString().orElse(""); 1927 try (InputStream is = entry.bytes()) { 1928 describeModule(is, uriString); 1929 } 1930 } else { 1931 // no specific --release specified, output the root, if any 1932 if (infos[0].name().equals(MODULE_INFO)) { 1933 String uriString = infos[0].uriString().orElse(""); 1934 try (InputStream is = infos[0].bytes()) { 1935 describeModule(is, uriString); 1936 } 1937 } else { 1938 // no root, output message to specify --release 1939 output(getMsg("error.no.root.descriptor")); 1940 } 1941 } 1942 return true; 1943 } 1944 1945 static <T> String toLowerCaseString(Collection<T> set) { 1946 if (set.isEmpty()) { return ""; } 1947 return " " + set.stream().map(e -> e.toString().toLowerCase(Locale.ROOT)) 1948 .sorted().collect(joining(" ")); 1949 } 1950 1951 static <T> String toString(Collection<T> set) { 1952 if (set.isEmpty()) { return ""; } 1953 return " " + set.stream().map(e -> e.toString()).sorted().collect(joining(" ")); 1954 } 1955 1956 private void describeModule(InputStream entryInputStream, String uriString) 1957 throws IOException 1958 { 1959 ModuleInfo.Attributes attrs = ModuleInfo.read(entryInputStream, null); 1960 ModuleDescriptor md = attrs.descriptor(); 1961 ModuleTarget target = attrs.target(); 1962 ModuleHashes hashes = attrs.recordedHashes(); 1963 1964 describeModule(md, target, hashes, uriString); 1965 } 1966 1967 private void describeModule(ModuleDescriptor md, 1968 ModuleTarget target, 1969 ModuleHashes hashes, 1970 String uriString) 1971 throws IOException 1972 { 1973 StringBuilder sb = new StringBuilder(); 1974 1975 sb.append(md.toNameAndVersion()); 1976 1977 if (!uriString.equals("")) 1978 sb.append(" ").append(uriString); 1979 if (md.isOpen()) 1980 sb.append(" open"); 1981 if (md.isAutomatic()) 1982 sb.append(" automatic"); 1983 sb.append("\n"); 1984 1985 // unqualified exports (sorted by package) 1986 md.exports().stream() 1987 .sorted(Comparator.comparing(Exports::source)) 1988 .filter(e -> !e.isQualified()) 1989 .forEach(e -> sb.append("exports ").append(e.source()) 1990 .append(toLowerCaseString(e.modifiers())) 1991 .append("\n")); 1992 1993 // dependences 1994 md.requires().stream().sorted() 1995 .forEach(r -> sb.append("requires ").append(r.name()) 1996 .append(toLowerCaseString(r.modifiers())) 1997 .append("\n")); 1998 1999 // service use and provides 2000 md.uses().stream().sorted() 2001 .forEach(s -> sb.append("uses ").append(s).append("\n")); 2002 2003 md.provides().stream() 2004 .sorted(Comparator.comparing(Provides::service)) 2005 .forEach(p -> sb.append("provides ").append(p.service()) 2006 .append(" with") 2007 .append(toString(p.providers())) 2008 .append("\n")); 2009 2010 // qualified exports 2011 md.exports().stream() 2012 .sorted(Comparator.comparing(Exports::source)) 2013 .filter(Exports::isQualified) 2014 .forEach(e -> sb.append("qualified exports ").append(e.source()) 2015 .append(" to").append(toLowerCaseString(e.targets())) 2016 .append("\n")); 2017 2018 // open packages 2019 md.opens().stream() 2020 .sorted(Comparator.comparing(Opens::source)) 2021 .filter(o -> !o.isQualified()) 2022 .forEach(o -> sb.append("opens ").append(o.source()) 2023 .append(toLowerCaseString(o.modifiers())) 2024 .append("\n")); 2025 2026 md.opens().stream() 2027 .sorted(Comparator.comparing(Opens::source)) 2028 .filter(Opens::isQualified) 2029 .forEach(o -> sb.append("qualified opens ").append(o.source()) 2030 .append(toLowerCaseString(o.modifiers())) 2031 .append(" to").append(toLowerCaseString(o.targets())) 2032 .append("\n")); 2033 2034 // non-exported/non-open packages 2035 Set<String> concealed = new TreeSet<>(md.packages()); 2036 md.exports().stream().map(Exports::source).forEach(concealed::remove); 2037 md.opens().stream().map(Opens::source).forEach(concealed::remove); 2038 concealed.forEach(p -> sb.append("contains ").append(p).append("\n")); 2039 2040 md.mainClass().ifPresent(v -> sb.append("main-class ").append(v).append("\n")); 2041 2042 if (target != null) { 2043 String targetPlatform = target.targetPlatform(); 2044 if (!targetPlatform.isEmpty()) 2045 sb.append("platform ").append(targetPlatform).append("\n"); 2046 } 2047 2048 if (hashes != null) { 2049 hashes.names().stream().sorted().forEach( 2050 mod -> sb.append("hashes ").append(mod).append(" ") 2051 .append(hashes.algorithm()).append(" ") 2052 .append(toHex(hashes.hashFor(mod))) 2053 .append("\n")); 2054 } 2055 2056 output(sb.toString()); 2057 } 2058 2059 private static String toHex(byte[] ba) { 2060 StringBuilder sb = new StringBuilder(ba.length << 1); 2061 for (byte b: ba) { 2062 sb.append(String.format("%02x", b & 0xff)); 2063 } 2064 return sb.toString(); 2065 } 2066 2067 static String toBinaryName(String classname) { 2068 return (classname.replace('.', '/')) + ".class"; 2069 } 2070 2071 private boolean checkModuleInfo(byte[] moduleInfoBytes, Set<String> entries) 2072 throws IOException 2073 { 2074 boolean ok = true; 2075 if (moduleInfoBytes != null) { // no root module-info.class if null 2076 try { 2077 // ModuleDescriptor.read() checks open/exported pkgs vs packages 2078 ModuleDescriptor md = ModuleDescriptor.read(ByteBuffer.wrap(moduleInfoBytes)); 2079 // A module must have the implementation class of the services it 'provides'. 2080 if (md.provides().stream().map(Provides::providers).flatMap(List::stream) 2081 .filter(p -> !entries.contains(toBinaryName(p))) 2082 .peek(p -> fatalError(formatMsg("error.missing.provider", p))) 2083 .count() != 0) { 2084 ok = false; 2085 } 2086 } catch (InvalidModuleDescriptorException x) { 2087 fatalError(x.getMessage()); 2088 ok = false; 2089 } 2090 } 2091 return ok; 2092 } 2093 2094 /** 2095 * Adds extended modules attributes to the given module-info's. The given 2096 * Map values are updated in-place. Returns false if an error occurs. 2097 */ 2098 private void addExtendedModuleAttributes(Map<String,byte[]> moduleInfos, 2099 Set<String> packages) 2100 throws IOException 2101 { 2102 for (Map.Entry<String,byte[]> e: moduleInfos.entrySet()) { 2103 ModuleDescriptor md = ModuleDescriptor.read(ByteBuffer.wrap(e.getValue())); 2104 e.setValue(extendedInfoBytes(md, e.getValue(), packages)); 2105 } 2106 } 2107 2108 static boolean isModuleInfoEntry(String name) { 2109 // root or versioned module-info.class 2110 if (name.endsWith(MODULE_INFO)) { 2111 int end = name.length() - MODULE_INFO.length(); 2112 if (end == 0) 2113 return true; 2114 if (name.startsWith(VERSIONS_DIR)) { 2115 int off = VERSIONS_DIR_LENGTH; 2116 if (off == end) // meta-inf/versions/module-info.class 2117 return false; 2118 while (off < end - 1) { 2119 char c = name.charAt(off++); 2120 if (c < '0' || c > '9') 2121 return false; 2122 } 2123 return name.charAt(off) == '/'; 2124 } 2125 } 2126 return false; 2127 } 2128 2129 /** 2130 * Returns a byte array containing the given module-info.class plus any 2131 * extended attributes. 2132 * 2133 * If --module-version, --main-class, or other options were provided 2134 * then the corresponding class file attributes are added to the 2135 * module-info here. 2136 */ 2137 private byte[] extendedInfoBytes(ModuleDescriptor md, 2138 byte[] miBytes, 2139 Set<String> packages) 2140 throws IOException 2141 { 2142 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 2143 InputStream is = new ByteArrayInputStream(miBytes); 2144 ModuleInfoExtender extender = ModuleInfoExtender.newExtender(is); 2145 2146 // Add (or replace) the Packages attribute 2147 extender.packages(packages); 2148 2149 // --main-class 2150 if (ename != null) 2151 extender.mainClass(ename); 2152 2153 // --module-version 2154 if (moduleVersion != null) 2155 extender.version(moduleVersion); 2156 2157 // --hash-modules 2158 if (modulesToHash != null) { 2159 String mn = md.name(); 2160 Hasher hasher = new Hasher(md, fname); 2161 ModuleHashes moduleHashes = hasher.computeHashes(mn); 2162 if (moduleHashes != null) { 2163 extender.hashes(moduleHashes); 2164 } else { 2165 warn("warning: no module is recorded in hash in " + mn); 2166 } 2167 } 2168 2169 if (moduleResolution.value() != 0) { 2170 extender.moduleResolution(moduleResolution); 2171 } 2172 2173 extender.write(baos); 2174 return baos.toByteArray(); 2175 } 2176 2177 /** 2178 * Compute and record hashes 2179 */ 2180 private class Hasher { 2181 final ModuleHashesBuilder hashesBuilder; 2182 final ModuleFinder finder; 2183 final Set<String> modules; 2184 Hasher(ModuleDescriptor descriptor, String fname) throws IOException { 2185 // Create a module finder that finds the modular JAR 2186 // being created/updated 2187 URI uri = Paths.get(fname).toUri(); 2188 ModuleReference mref = new ModuleReference(descriptor, uri) { 2189 @Override 2190 public ModuleReader open() { 2191 throw new UnsupportedOperationException("should not reach here"); 2192 } 2193 }; 2194 2195 // Compose a module finder with the module path and 2196 // the modular JAR being created or updated 2197 this.finder = ModuleFinder.compose(moduleFinder, 2198 new ModuleFinder() { 2199 @Override 2200 public Optional<ModuleReference> find(String name) { 2201 if (descriptor.name().equals(name)) 2202 return Optional.of(mref); 2203 else 2204 return Optional.empty(); 2205 } 2206 2207 @Override 2208 public Set<ModuleReference> findAll() { 2209 return Collections.singleton(mref); 2210 } 2211 }); 2212 2213 // Determine the modules that matches the pattern {@code modulesToHash} 2214 Set<String> roots = finder.findAll().stream() 2215 .map(ref -> ref.descriptor().name()) 2216 .filter(mn -> modulesToHash.matcher(mn).find()) 2217 .collect(Collectors.toSet()); 2218 2219 // use system module path unless it creates a modular JAR for 2220 // a module that is present in the system image e.g. upgradeable 2221 // module 2222 ModuleFinder system; 2223 String name = descriptor.name(); 2224 if (name != null && ModuleFinder.ofSystem().find(name).isPresent()) { 2225 system = ModuleFinder.of(); 2226 } else { 2227 system = ModuleFinder.ofSystem(); 2228 } 2229 // get a resolved module graph 2230 Configuration config = 2231 Configuration.empty().resolve(system, finder, roots); 2232 2233 // filter modules resolved from the system module finder 2234 this.modules = config.modules().stream() 2235 .map(ResolvedModule::name) 2236 .filter(mn -> roots.contains(mn) && !system.find(mn).isPresent()) 2237 .collect(Collectors.toSet()); 2238 2239 this.hashesBuilder = new ModuleHashesBuilder(config, modules); 2240 } 2241 2242 /** 2243 * Compute hashes of the specified module. 2244 * 2245 * It records the hashing modules that depend upon the specified 2246 * module directly or indirectly. 2247 */ 2248 ModuleHashes computeHashes(String name) { 2249 if (hashesBuilder == null) 2250 return null; 2251 2252 return hashesBuilder.computeHashes(Set.of(name)).get(name); 2253 } 2254 } 2255 2256 // sort base entries before versioned entries, and sort entry classes with 2257 // nested classes so that the outter class appears before the associated 2258 // nested class 2259 static Comparator<String> ENTRYNAME_COMPARATOR = (s1, s2) -> { 2260 2261 if (s1.equals(s2)) return 0; 2262 boolean b1 = s1.startsWith(VERSIONS_DIR); 2263 boolean b2 = s2.startsWith(VERSIONS_DIR); 2264 if (b1 && !b2) return 1; 2265 if (!b1 && b2) return -1; 2266 int n = 0; // starting char for String compare 2267 if (b1 && b2) { 2268 // normally strings would be sorted so "10" goes before "9", but 2269 // version number strings need to be sorted numerically 2270 n = VERSIONS_DIR.length(); // skip the common prefix 2271 int i1 = s1.indexOf('/', n); 2272 int i2 = s2.indexOf('/', n); 2273 if (i1 == -1) throw new Validator.InvalidJarException(s1); 2274 if (i2 == -1) throw new Validator.InvalidJarException(s2); 2275 // shorter version numbers go first 2276 if (i1 != i2) return i1 - i2; 2277 // otherwise, handle equal length numbers below 2278 } 2279 int l1 = s1.length(); 2280 int l2 = s2.length(); 2281 int lim = Math.min(l1, l2); 2282 for (int k = n; k < lim; k++) { 2283 char c1 = s1.charAt(k); 2284 char c2 = s2.charAt(k); 2285 if (c1 != c2) { 2286 // change natural ordering so '.' comes before '$' 2287 // i.e. outer classes come before nested classes 2288 if (c1 == '$' && c2 == '.') return 1; 2289 if (c1 == '.' && c2 == '$') return -1; 2290 return c1 - c2; 2291 } 2292 } 2293 return l1 - l2; 2294 }; 2295 2296 static Comparator<ZipEntry> ENTRY_COMPARATOR = 2297 Comparator.comparing(ZipEntry::getName, ENTRYNAME_COMPARATOR); 2298 2299 }