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