1 /* 2 * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.java.util.jar.pack; 27 28 import java.util.jar.Pack200; 29 import com.sun.java.util.jar.pack.Attribute.Layout; 30 import com.sun.java.util.jar.pack.ConstantPool.ClassEntry; 31 import com.sun.java.util.jar.pack.ConstantPool.DescriptorEntry; 32 import com.sun.java.util.jar.pack.ConstantPool.BootstrapMethodEntry; 33 import com.sun.java.util.jar.pack.ConstantPool.Index; 34 import com.sun.java.util.jar.pack.ConstantPool.LiteralEntry; 35 import com.sun.java.util.jar.pack.ConstantPool.Utf8Entry; 36 import com.sun.java.util.jar.pack.ConstantPool.Entry; 37 import java.io.ByteArrayInputStream; 38 import java.io.ByteArrayOutputStream; 39 import java.io.IOException; 40 import java.io.InputStream; 41 import java.io.OutputStream; 42 import java.io.SequenceInputStream; 43 import java.lang.reflect.Modifier; 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.Collection; 47 import java.util.Collections; 48 import java.util.Comparator; 49 import java.util.HashMap; 50 import java.util.HashSet; 51 import java.util.Iterator; 52 import java.util.List; 53 import java.util.ListIterator; 54 import java.util.Map; 55 import java.util.Set; 56 import java.util.jar.JarFile; 57 import static com.sun.java.util.jar.pack.Constants.*; 58 59 /** 60 * Define the main data structure transmitted by pack/unpack. 61 * @author John Rose 62 */ 63 class Package { 64 int verbose; 65 { 66 PropMap pmap = Utils.currentPropMap(); 67 if (pmap != null) 68 verbose = pmap.getInteger(Utils.DEBUG_VERBOSE); 69 } 70 71 final int magic = JAVA_PACKAGE_MAGIC; 72 73 int default_modtime = NO_MODTIME; 74 int default_options = 0; // FO_DEFLATE_HINT 75 76 Version defaultClassVersion = null; 77 78 // These fields can be adjusted by driver properties. 79 final Version minClassVersion; 80 final Version maxClassVersion; 81 // null, indicates that consensus rules during package write 82 final Version packageVersion; 83 84 Version observedHighestClassVersion = null; 85 86 87 // What constants are used in this unit? 88 ConstantPool.IndexGroup cp = new ConstantPool.IndexGroup(); 89 90 /* 91 * typically used by the PackageReader to set the defaults, in which 92 * case we take the defaults. 93 */ 94 public Package() { 95 minClassVersion = JAVA_MIN_CLASS_VERSION; 96 maxClassVersion = JAVA_MAX_CLASS_VERSION; 97 packageVersion = null; 98 } 99 100 101 /* 102 * Typically used by the PackerImpl during before packing, the defaults are 103 * overridden by the users preferences. 104 */ 105 public Package(Version minClassVersion, Version maxClassVersion, Version packageVersion) { 106 // Fill in permitted range of major/minor version numbers. 107 this.minClassVersion = minClassVersion == null 108 ? JAVA_MIN_CLASS_VERSION 109 : minClassVersion; 110 this.maxClassVersion = maxClassVersion == null 111 ? JAVA_MAX_CLASS_VERSION 112 : maxClassVersion; 113 this.packageVersion = packageVersion; 114 } 115 116 117 public void reset() { 118 cp = new ConstantPool.IndexGroup(); 119 classes.clear(); 120 files.clear(); 121 BandStructure.nextSeqForDebug = 0; 122 observedHighestClassVersion = null; 123 } 124 125 // Special empty versions of Code and InnerClasses, used for markers. 126 public static final Attribute.Layout attrCodeEmpty; 127 public static final Attribute.Layout attrBootstrapMethodsEmpty; 128 public static final Attribute.Layout attrInnerClassesEmpty; 129 public static final Attribute.Layout attrSourceFileSpecial; 130 public static final Map<Attribute.Layout, Attribute> attrDefs; 131 static { 132 Map<Layout, Attribute> ad = new HashMap<>(3); 133 attrCodeEmpty = Attribute.define(ad, ATTR_CONTEXT_METHOD, 134 "Code", "").layout(); 135 attrBootstrapMethodsEmpty = Attribute.define(ad, ATTR_CONTEXT_CLASS, 136 "BootstrapMethods", "").layout(); 137 attrInnerClassesEmpty = Attribute.define(ad, ATTR_CONTEXT_CLASS, 138 "InnerClasses", "").layout(); 139 attrSourceFileSpecial = Attribute.define(ad, ATTR_CONTEXT_CLASS, 140 "SourceFile", "RUNH").layout(); 141 attrDefs = Collections.unmodifiableMap(ad); 142 } 143 144 Version getDefaultClassVersion() { 145 return defaultClassVersion; 146 } 147 148 /** Return the highest version number of all classes, 149 * or 0 if there are no classes. 150 */ 151 private void setHighestClassVersion() { 152 if (observedHighestClassVersion != null) 153 return; 154 Version res = JAVA_MIN_CLASS_VERSION; // initial low value 155 for (Class cls : classes) { 156 Version ver = cls.getVersion(); 157 if (res.lessThan(ver)) res = ver; 158 } 159 observedHighestClassVersion = res; 160 } 161 162 Version getHighestClassVersion() { 163 setHighestClassVersion(); 164 return observedHighestClassVersion; 165 } 166 167 // What Java classes are in this unit? 168 169 ArrayList<Package.Class> classes = new ArrayList<>(); 170 171 public List<Package.Class> getClasses() { 172 return classes; 173 } 174 175 public final 176 class Class extends Attribute.Holder implements Comparable<Class> { 177 public Package getPackage() { return Package.this; } 178 179 // Optional file characteristics and data source (a "class stub") 180 File file; 181 182 // File header 183 int magic; 184 Version version; 185 186 // Local constant pool (one-way mapping of index => package cp). 187 Entry[] cpMap; 188 189 // Class header 190 //int flags; // in Attribute.Holder.this.flags 191 ClassEntry thisClass; 192 ClassEntry superClass; 193 ClassEntry[] interfaces; 194 195 // Class parts 196 ArrayList<Field> fields; 197 ArrayList<Method> methods; 198 //ArrayList attributes; // in Attribute.Holder.this.attributes 199 // Note that InnerClasses may be collected at the package level. 200 ArrayList<InnerClass> innerClasses; 201 ArrayList<BootstrapMethodEntry> bootstrapMethods; 202 203 Class(int flags, ClassEntry thisClass, ClassEntry superClass, ClassEntry[] interfaces) { 204 this.magic = JAVA_MAGIC; 205 this.version = defaultClassVersion; 206 this.flags = flags; 207 this.thisClass = thisClass; 208 this.superClass = superClass; 209 this.interfaces = interfaces; 210 211 boolean added = classes.add(this); 212 assert(added); 213 } 214 215 Class(String classFile) { 216 // A blank class; must be read with a ClassReader, etc. 217 initFile(newStub(classFile)); 218 } 219 220 List<Field> getFields() { return fields == null ? noFields : fields; } 221 List<Method> getMethods() { return methods == null ? noMethods : methods; } 222 223 public String getName() { 224 return thisClass.stringValue(); 225 } 226 227 Version getVersion() { 228 return this.version; 229 } 230 231 // Note: equals and hashCode are identity-based. 232 public int compareTo(Class that) { 233 String n0 = this.getName(); 234 String n1 = that.getName(); 235 return n0.compareTo(n1); 236 } 237 238 String getObviousSourceFile() { 239 return Package.getObviousSourceFile(getName()); 240 } 241 242 private void transformSourceFile(boolean minimize) { 243 // Replace "obvious" SourceFile by null. 244 Attribute olda = getAttribute(attrSourceFileSpecial); 245 if (olda == null) 246 return; // no SourceFile attr. 247 String obvious = getObviousSourceFile(); 248 List<Entry> ref = new ArrayList<>(1); 249 olda.visitRefs(this, VRM_PACKAGE, ref); 250 Utf8Entry sfName = (Utf8Entry) ref.get(0); 251 Attribute a = olda; 252 if (sfName == null) { 253 if (minimize) { 254 // A pair of zero bytes. Cannot use predef. layout. 255 a = Attribute.find(ATTR_CONTEXT_CLASS, "SourceFile", "H"); 256 a = a.addContent(new byte[2]); 257 } else { 258 // Expand null attribute to the obvious string. 259 byte[] bytes = new byte[2]; 260 sfName = getRefString(obvious); 261 Object f = null; 262 f = Fixups.addRefWithBytes(f, bytes, sfName); 263 a = attrSourceFileSpecial.addContent(bytes, f); 264 } 265 } else if (obvious.equals(sfName.stringValue())) { 266 if (minimize) { 267 // Replace by an all-zero attribute. 268 a = attrSourceFileSpecial.addContent(new byte[2]); 269 } else { 270 assert(false); 271 } 272 } 273 if (a != olda) { 274 if (verbose > 2) 275 Utils.log.fine("recoding obvious SourceFile="+obvious); 276 List<Attribute> newAttrs = new ArrayList<>(getAttributes()); 277 int where = newAttrs.indexOf(olda); 278 newAttrs.set(where, a); 279 setAttributes(newAttrs); 280 } 281 } 282 283 void minimizeSourceFile() { 284 transformSourceFile(true); 285 } 286 void expandSourceFile() { 287 transformSourceFile(false); 288 } 289 290 protected Entry[] getCPMap() { 291 return cpMap; 292 } 293 294 protected void setCPMap(Entry[] cpMap) { 295 this.cpMap = cpMap; 296 } 297 298 boolean hasBootstrapMethods() { 299 return bootstrapMethods != null && !bootstrapMethods.isEmpty(); 300 } 301 302 List<BootstrapMethodEntry> getBootstrapMethods() { 303 return bootstrapMethods; 304 } 305 306 BootstrapMethodEntry[] getBootstrapMethodMap() { 307 return (hasBootstrapMethods()) 308 ? bootstrapMethods.toArray(new BootstrapMethodEntry[bootstrapMethods.size()]) 309 : null; 310 } 311 312 void setBootstrapMethods(Collection<BootstrapMethodEntry> bsms) { 313 assert(bootstrapMethods == null); // do not do this twice 314 bootstrapMethods = new ArrayList<>(bsms); 315 } 316 317 boolean hasInnerClasses() { 318 return innerClasses != null; 319 } 320 List<InnerClass> getInnerClasses() { 321 return innerClasses; 322 } 323 324 public void setInnerClasses(Collection<InnerClass> ics) { 325 innerClasses = (ics == null) ? null : new ArrayList<>(ics); 326 // Edit the attribute list, if necessary. 327 Attribute a = getAttribute(attrInnerClassesEmpty); 328 if (innerClasses != null && a == null) 329 addAttribute(attrInnerClassesEmpty.canonicalInstance()); 330 else if (innerClasses == null && a != null) 331 removeAttribute(a); 332 } 333 334 /** Given a global map of ICs (keyed by thisClass), 335 * compute the subset of its Map.values which are 336 * required to be present in the local InnerClasses 337 * attribute. Perform this calculation without 338 * reference to any actual InnerClasses attribute. 339 * <p> 340 * The order of the resulting list is consistent 341 * with that of Package.this.allInnerClasses. 342 */ 343 public List<InnerClass> computeGloballyImpliedICs() { 344 Set<Entry> cpRefs = new HashSet<>(); 345 { // This block temporarily displaces this.innerClasses. 346 ArrayList<InnerClass> innerClassesSaved = innerClasses; 347 innerClasses = null; // ignore for the moment 348 visitRefs(VRM_CLASSIC, cpRefs); 349 innerClasses = innerClassesSaved; 350 } 351 ConstantPool.completeReferencesIn(cpRefs, true); 352 353 Set<Entry> icRefs = new HashSet<>(); 354 for (Entry e : cpRefs) { 355 // Restrict cpRefs to InnerClasses entries only. 356 if (!(e instanceof ClassEntry)) continue; 357 // For every IC reference, add its outers also. 358 while (e != null) { 359 InnerClass ic = getGlobalInnerClass(e); 360 if (ic == null) break; 361 if (!icRefs.add(e)) break; 362 e = ic.outerClass; 363 // If we add A$B$C to the mix, we must also add A$B. 364 } 365 } 366 // This loop is structured this way so as to accumulate 367 // entries into impliedICs in an order which reflects 368 // the order of allInnerClasses. 369 ArrayList<InnerClass> impliedICs = new ArrayList<>(); 370 for (InnerClass ic : allInnerClasses) { 371 // This one is locally relevant if it describes 372 // a member of the current class, or if the current 373 // class uses it somehow. In the particular case 374 // where thisClass is an inner class, it will already 375 // be a member of icRefs. 376 if (icRefs.contains(ic.thisClass) 377 || ic.outerClass == this.thisClass) { 378 // Add every relevant class to the IC attribute: 379 if (verbose > 1) 380 Utils.log.fine("Relevant IC: "+ic); 381 impliedICs.add(ic); 382 } 383 } 384 return impliedICs; 385 } 386 387 // Helper for both minimizing and expanding. 388 // Computes a symmetric difference. 389 private List<InnerClass> computeICdiff() { 390 List<InnerClass> impliedICs = computeGloballyImpliedICs(); 391 List<InnerClass> actualICs = getInnerClasses(); 392 if (actualICs == null) 393 actualICs = Collections.emptyList(); 394 395 // Symmetric difference is calculated from I, A like this: 396 // diff = (I+A) - (I*A) 397 // Note that the center C is unordered, but the result 398 // preserves the original ordering of I and A. 399 // 400 // Class file rules require that outers precede inners. 401 // So, add I before A, in case A$B$Z is local, but A$B 402 // is implicit. The reverse is never the case. 403 if (actualICs.isEmpty()) { 404 return impliedICs; 405 // Diff is I since A is empty. 406 } 407 if (impliedICs.isEmpty()) { 408 return actualICs; 409 // Diff is A since I is empty. 410 } 411 // (I*A) is non-trivial 412 Set<InnerClass> center = new HashSet<>(actualICs); 413 center.retainAll(new HashSet<>(impliedICs)); 414 impliedICs.addAll(actualICs); 415 impliedICs.removeAll(center); 416 // Diff is now I^A = (I+A)-(I*A). 417 return impliedICs; 418 } 419 420 /** When packing, anticipate the effect of expandLocalICs. 421 * Replace the local ICs by their symmetric difference 422 * with the globally implied ICs for this class; if this 423 * difference is empty, remove the local ICs altogether. 424 * <p> 425 * An empty local IC attribute is reserved to signal 426 * the unpacker to delete the attribute altogether, 427 * so a missing local IC attribute signals the unpacker 428 * to use the globally implied ICs changed. 429 */ 430 void minimizeLocalICs() { 431 List<InnerClass> diff = computeICdiff(); 432 List<InnerClass> actualICs = innerClasses; 433 List<InnerClass> localICs; // will be the diff, modulo edge cases 434 if (diff.isEmpty()) { 435 // No diff, so transmit no attribute. 436 localICs = null; 437 if (actualICs != null && actualICs.isEmpty()) { 438 // Odd case: No implied ICs, and a zero length attr. 439 // Do not support it directly. 440 if (verbose > 0) 441 Utils.log.info("Warning: Dropping empty InnerClasses attribute from "+this); 442 } 443 } else if (actualICs == null) { 444 // No local IC attribute, even though some are implied. 445 // Signal with trivial attribute. 446 localICs = Collections.emptyList(); 447 } else { 448 // Transmit a non-empty diff, which will create 449 // a local ICs attribute. 450 localICs = diff; 451 } 452 // Reduce the set to the symmetric difference. 453 setInnerClasses(localICs); 454 if (verbose > 1 && localICs != null) 455 Utils.log.fine("keeping local ICs in "+this+": "+localICs); 456 } 457 458 /** When unpacking, undo the effect of minimizeLocalICs. 459 * Must return negative if any IC tuples may have been deleted. 460 * Otherwise, return positive if any IC tuples were added. 461 */ 462 int expandLocalICs() { 463 List<InnerClass> localICs = innerClasses; 464 List<InnerClass> actualICs; 465 int changed; 466 if (localICs == null) { 467 // Diff was empty. (Common case.) 468 List<InnerClass> impliedICs = computeGloballyImpliedICs(); 469 if (impliedICs.isEmpty()) { 470 actualICs = null; 471 changed = 0; 472 } else { 473 actualICs = impliedICs; 474 changed = 1; // added more tuples 475 } 476 } else if (localICs.isEmpty()) { 477 // It was a non-empty diff, but the local ICs were absent. 478 actualICs = null; 479 // [] => null, no tuple change, but attribute deletion. 480 changed = -1; 481 } else { 482 // Non-trivial diff was transmitted. 483 actualICs = computeICdiff(); 484 // If we only added more ICs, return +1. 485 changed = actualICs.containsAll(localICs)? +1: -1; 486 } 487 setInnerClasses(actualICs); 488 return changed; 489 } 490 491 public abstract 492 class Member extends Attribute.Holder implements Comparable<Member> { 493 DescriptorEntry descriptor; 494 495 protected Member(int flags, DescriptorEntry descriptor) { 496 this.flags = flags; 497 this.descriptor = descriptor; 498 } 499 500 public Class thisClass() { return Class.this; } 501 502 public DescriptorEntry getDescriptor() { 503 return descriptor; 504 } 505 public String getName() { 506 return descriptor.nameRef.stringValue(); 507 } 508 public String getType() { 509 return descriptor.typeRef.stringValue(); 510 } 511 512 protected Entry[] getCPMap() { 513 return cpMap; 514 } 515 protected void visitRefs(int mode, Collection<Entry> refs) { 516 if (verbose > 2) Utils.log.fine("visitRefs "+this); 517 // Careful: The descriptor is used by the package, 518 // but the classfile breaks it into component refs. 519 if (mode == VRM_CLASSIC) { 520 refs.add(descriptor.nameRef); 521 refs.add(descriptor.typeRef); 522 } else { 523 refs.add(descriptor); 524 } 525 // Handle attribute list: 526 super.visitRefs(mode, refs); 527 } 528 529 public String toString() { 530 return Class.this + "." + descriptor.prettyString(); 531 } 532 } 533 534 public 535 class Field extends Member { 536 // Order is significant for fields: It is visible to reflection. 537 int order; 538 539 public Field(int flags, DescriptorEntry descriptor) { 540 super(flags, descriptor); 541 assert(!descriptor.isMethod()); 542 if (fields == null) 543 fields = new ArrayList<>(); 544 boolean added = fields.add(this); 545 assert(added); 546 order = fields.size(); 547 } 548 549 public byte getLiteralTag() { 550 return descriptor.getLiteralTag(); 551 } 552 553 public int compareTo(Member o) { 554 Field that = (Field)o; 555 return this.order - that.order; 556 } 557 } 558 559 public 560 class Method extends Member { 561 // Code attribute is specially hardwired. 562 Code code; 563 564 public Method(int flags, DescriptorEntry descriptor) { 565 super(flags, descriptor); 566 assert(descriptor.isMethod()); 567 if (methods == null) 568 methods = new ArrayList<>(); 569 boolean added = methods.add(this); 570 assert(added); 571 } 572 573 public void trimToSize() { 574 super.trimToSize(); 575 if (code != null) 576 code.trimToSize(); 577 } 578 579 public int getArgumentSize() { 580 int argSize = descriptor.typeRef.computeSize(true); 581 int thisSize = Modifier.isStatic(flags) ? 0 : 1; 582 return thisSize + argSize; 583 } 584 585 // Sort methods in a canonical order (by type, then by name). 586 public int compareTo(Member o) { 587 Method that = (Method)o; 588 return this.getDescriptor().compareTo(that.getDescriptor()); 589 } 590 591 public void strip(String attrName) { 592 if ("Code".equals(attrName)) 593 code = null; 594 if (code != null) 595 code.strip(attrName); 596 super.strip(attrName); 597 } 598 protected void visitRefs(int mode, Collection<Entry> refs) { 599 super.visitRefs(mode, refs); 600 if (code != null) { 601 if (mode == VRM_CLASSIC) { 602 refs.add(getRefString("Code")); 603 } 604 code.visitRefs(mode, refs); 605 } 606 } 607 } 608 609 public void trimToSize() { 610 super.trimToSize(); 611 for (int isM = 0; isM <= 1; isM++) { 612 ArrayList<? extends Member> members = (isM == 0) ? fields : methods; 613 if (members == null) continue; 614 members.trimToSize(); 615 for (Member m : members) { 616 m.trimToSize(); 617 } 618 } 619 if (innerClasses != null) { 620 innerClasses.trimToSize(); 621 } 622 } 623 624 public void strip(String attrName) { 625 if ("InnerClass".equals(attrName)) 626 innerClasses = null; 627 for (int isM = 0; isM <= 1; isM++) { 628 ArrayList<? extends Member> members = (isM == 0) ? fields : methods; 629 if (members == null) continue; 630 for (Member m : members) { 631 m.strip(attrName); 632 } 633 } 634 super.strip(attrName); 635 } 636 637 protected void visitRefs(int mode, Collection<Entry> refs) { 638 if (verbose > 2) Utils.log.fine("visitRefs "+this); 639 refs.add(thisClass); 640 refs.add(superClass); 641 refs.addAll(Arrays.asList(interfaces)); 642 for (int isM = 0; isM <= 1; isM++) { 643 ArrayList<? extends Member> members = (isM == 0) ? fields : methods; 644 if (members == null) continue; 645 for (Member m : members) { 646 boolean ok = false; 647 try { 648 m.visitRefs(mode, refs); 649 ok = true; 650 } finally { 651 if (!ok) 652 Utils.log.warning("Error scanning "+m); 653 } 654 } 655 } 656 visitInnerClassRefs(mode, refs); 657 // Handle attribute list: 658 super.visitRefs(mode, refs); 659 } 660 661 protected void visitInnerClassRefs(int mode, Collection<Entry> refs) { 662 Package.visitInnerClassRefs(innerClasses, mode, refs); 663 } 664 665 // Hook called by ClassReader when it's done. 666 void finishReading() { 667 trimToSize(); 668 maybeChooseFileName(); 669 } 670 671 public void initFile(File file) { 672 assert(this.file == null); // set-once 673 if (file == null) { 674 // Build a trivial stub. 675 file = newStub(canonicalFileName()); 676 } 677 this.file = file; 678 assert(file.isClassStub()); 679 file.stubClass = this; 680 maybeChooseFileName(); 681 } 682 683 public void maybeChooseFileName() { 684 if (thisClass == null) { 685 return; // do not choose yet 686 } 687 String canonName = canonicalFileName(); 688 if (file.nameString.equals("")) { 689 file.nameString = canonName; 690 } 691 if (file.nameString.equals(canonName)) { 692 // The file name is predictable. Transmit "". 693 file.name = getRefString(""); 694 return; 695 } 696 // If name has not yet been looked up, find it now. 697 if (file.name == null) { 698 file.name = getRefString(file.nameString); 699 } 700 } 701 702 public String canonicalFileName() { 703 if (thisClass == null) return null; 704 return thisClass.stringValue() + ".class"; 705 } 706 707 public java.io.File getFileName(java.io.File parent) { 708 String name = file.name.stringValue(); 709 if (name.equals("")) 710 name = canonicalFileName(); 711 String fname = name.replace('/', java.io.File.separatorChar); 712 return new java.io.File(parent, fname); 713 } 714 public java.io.File getFileName() { 715 return getFileName(null); 716 } 717 718 public String toString() { 719 return thisClass.stringValue(); 720 } 721 } 722 723 void addClass(Class c) { 724 assert(c.getPackage() == this); 725 boolean added = classes.add(c); 726 assert(added); 727 // Make sure the class is represented in the total file order: 728 if (c.file == null) c.initFile(null); 729 addFile(c.file); 730 } 731 732 // What non-class files are in this unit? 733 ArrayList<File> files = new ArrayList<>(); 734 735 public List<File> getFiles() { 736 return files; 737 } 738 739 public List<File> getClassStubs() { 740 List<File> classStubs = new ArrayList<>(classes.size()); 741 for (Class cls : classes) { 742 assert(cls.file.isClassStub()); 743 classStubs.add(cls.file); 744 } 745 return classStubs; 746 } 747 748 public final class File implements Comparable<File> { 749 String nameString; // true name of this file 750 Utf8Entry name; 751 int modtime = NO_MODTIME; 752 int options = 0; // random flag bits, such as deflate_hint 753 Class stubClass; // if this is a stub, here's the class 754 ArrayList<byte[]> prepend = new ArrayList<>(); // list of byte[] 755 java.io.ByteArrayOutputStream append = new ByteArrayOutputStream(); 756 757 File(Utf8Entry name) { 758 this.name = name; 759 this.nameString = name.stringValue(); 760 // caller must fill in contents 761 } 762 File(String nameString) { 763 nameString = fixupFileName(nameString); 764 this.name = getRefString(nameString); 765 this.nameString = name.stringValue(); 766 } 767 768 public boolean isDirectory() { 769 // JAR directory. Useless. 770 return nameString.endsWith("/"); 771 } 772 public boolean isClassStub() { 773 return (options & FO_IS_CLASS_STUB) != 0; 774 } 775 public Class getStubClass() { 776 assert(isClassStub()); 777 assert(stubClass != null); 778 return stubClass; 779 } 780 public boolean isTrivialClassStub() { 781 return isClassStub() 782 && name.stringValue().equals("") 783 && (modtime == NO_MODTIME || modtime == default_modtime) 784 && (options &~ FO_IS_CLASS_STUB) == 0; 785 } 786 787 // The nameString is the key. Ignore other things. 788 // (Note: The name might be "", in the case of a trivial class stub.) 789 public boolean equals(Object o) { 790 if (o == null || (o.getClass() != File.class)) 791 return false; 792 File that = (File)o; 793 return that.nameString.equals(this.nameString); 794 } 795 public int hashCode() { 796 return nameString.hashCode(); 797 } 798 // Simple alphabetic sort. PackageWriter uses a better comparator. 799 public int compareTo(File that) { 800 return this.nameString.compareTo(that.nameString); 801 } 802 public String toString() { 803 return nameString+"{" 804 +(isClassStub()?"*":"") 805 +(BandStructure.testBit(options,FO_DEFLATE_HINT)?"@":"") 806 +(modtime==NO_MODTIME?"":"M"+modtime) 807 +(getFileLength()==0?"":"["+getFileLength()+"]") 808 +"}"; 809 } 810 811 public java.io.File getFileName() { 812 return getFileName(null); 813 } 814 public java.io.File getFileName(java.io.File parent) { 815 String lname = this.nameString; 816 //if (name.startsWith("./")) name = name.substring(2); 817 String fname = lname.replace('/', java.io.File.separatorChar); 818 return new java.io.File(parent, fname); 819 } 820 821 public void addBytes(byte[] bytes) { 822 addBytes(bytes, 0, bytes.length); 823 } 824 public void addBytes(byte[] bytes, int off, int len) { 825 if (((append.size() | len) << 2) < 0) { 826 prepend.add(append.toByteArray()); 827 append.reset(); 828 } 829 append.write(bytes, off, len); 830 } 831 public long getFileLength() { 832 long len = 0; 833 if (prepend == null || append == null) return 0; 834 for (byte[] block : prepend) { 835 len += block.length; 836 } 837 len += append.size(); 838 return len; 839 } 840 public void writeTo(OutputStream out) throws IOException { 841 if (prepend == null || append == null) return; 842 for (byte[] block : prepend) { 843 out.write(block); 844 } 845 append.writeTo(out); 846 } 847 public void readFrom(InputStream in) throws IOException { 848 byte[] buf = new byte[1 << 16]; 849 int nr; 850 while ((nr = in.read(buf)) > 0) { 851 addBytes(buf, 0, nr); 852 } 853 } 854 public InputStream getInputStream() { 855 InputStream in = new ByteArrayInputStream(append.toByteArray()); 856 if (prepend.isEmpty()) return in; 857 List<InputStream> isa = new ArrayList<>(prepend.size()+1); 858 for (byte[] bytes : prepend) { 859 isa.add(new ByteArrayInputStream(bytes)); 860 } 861 isa.add(in); 862 return new SequenceInputStream(Collections.enumeration(isa)); 863 } 864 865 protected void visitRefs(int mode, Collection<Entry> refs) { 866 assert(name != null); 867 refs.add(name); 868 } 869 } 870 871 File newStub(String classFileNameString) { 872 File stub = new File(classFileNameString); 873 stub.options |= FO_IS_CLASS_STUB; 874 stub.prepend = null; 875 stub.append = null; // do not collect data 876 return stub; 877 } 878 879 private static String fixupFileName(String name) { 880 String fname = name.replace(java.io.File.separatorChar, '/'); 881 if (fname.startsWith("/")) { 882 throw new IllegalArgumentException("absolute file name "+fname); 883 } 884 return fname; 885 } 886 887 void addFile(File file) { 888 boolean added = files.add(file); 889 assert(added); 890 } 891 892 // Is there a globally declared table of inner classes? 893 List<InnerClass> allInnerClasses = new ArrayList<>(); 894 Map<ClassEntry, InnerClass> allInnerClassesByThis; 895 896 public 897 List<InnerClass> getAllInnerClasses() { 898 return allInnerClasses; 899 } 900 901 public 902 void setAllInnerClasses(Collection<InnerClass> ics) { 903 assert(ics != allInnerClasses); 904 allInnerClasses.clear(); 905 allInnerClasses.addAll(ics); 906 907 // Make an index: 908 allInnerClassesByThis = new HashMap<>(allInnerClasses.size()); 909 for (InnerClass ic : allInnerClasses) { 910 Object pic = allInnerClassesByThis.put(ic.thisClass, ic); 911 assert(pic == null); // caller must ensure key uniqueness! 912 } 913 } 914 915 /** Return a global inner class record for the given thisClass. */ 916 public 917 InnerClass getGlobalInnerClass(Entry thisClass) { 918 assert(thisClass instanceof ClassEntry); 919 return allInnerClassesByThis.get(thisClass); 920 } 921 922 static 923 class InnerClass implements Comparable<InnerClass> { 924 final ClassEntry thisClass; 925 final ClassEntry outerClass; 926 final Utf8Entry name; 927 final int flags; 928 929 // Can name and outerClass be derived from thisClass? 930 final boolean predictable; 931 932 // About 30% of inner classes are anonymous (in rt.jar). 933 // About 60% are class members; the rest are named locals. 934 // Nearly all have predictable outers and names. 935 936 InnerClass(ClassEntry thisClass, ClassEntry outerClass, 937 Utf8Entry name, int flags) { 938 this.thisClass = thisClass; 939 this.outerClass = outerClass; 940 this.name = name; 941 this.flags = flags; 942 this.predictable = computePredictable(); 943 } 944 945 private boolean computePredictable() { 946 //System.out.println("computePredictable "+outerClass+" "+this.name); 947 String[] parse = parseInnerClassName(thisClass.stringValue()); 948 if (parse == null) return false; 949 String pkgOuter = parse[0]; 950 //String number = parse[1]; 951 String lname = parse[2]; 952 String haveName = (this.name == null) ? null : this.name.stringValue(); 953 String haveOuter = (outerClass == null) ? null : outerClass.stringValue(); 954 boolean lpredictable = (lname == haveName && pkgOuter == haveOuter); 955 //System.out.println("computePredictable => "+predictable); 956 return lpredictable; 957 } 958 959 public boolean equals(Object o) { 960 if (o == null || o.getClass() != InnerClass.class) 961 return false; 962 InnerClass that = (InnerClass)o; 963 return eq(this.thisClass, that.thisClass) 964 && eq(this.outerClass, that.outerClass) 965 && eq(this.name, that.name) 966 && this.flags == that.flags; 967 } 968 private static boolean eq(Object x, Object y) { 969 return (x == null)? y == null: x.equals(y); 970 } 971 public int hashCode() { 972 return thisClass.hashCode(); 973 } 974 public int compareTo(InnerClass that) { 975 return this.thisClass.compareTo(that.thisClass); 976 } 977 978 protected void visitRefs(int mode, Collection<Entry> refs) { 979 refs.add(thisClass); 980 if (mode == VRM_CLASSIC || !predictable) { 981 // If the name can be demangled, the package omits 982 // the products of demangling. Otherwise, include them. 983 refs.add(outerClass); 984 refs.add(name); 985 } 986 } 987 988 public String toString() { 989 return thisClass.stringValue(); 990 } 991 } 992 993 // Helper for building InnerClasses attributes. 994 private static 995 void visitInnerClassRefs(Collection<InnerClass> innerClasses, int mode, Collection<Entry> refs) { 996 if (innerClasses == null) { 997 return; // no attribute; nothing to do 998 } 999 if (mode == VRM_CLASSIC) { 1000 refs.add(getRefString("InnerClasses")); 1001 } 1002 if (innerClasses.size() > 0) { 1003 // Count the entries themselves: 1004 for (InnerClass c : innerClasses) { 1005 c.visitRefs(mode, refs); 1006 } 1007 } 1008 } 1009 1010 static String[] parseInnerClassName(String n) { 1011 //System.out.println("parseInnerClassName "+n); 1012 String pkgOuter, number, name; 1013 int dollar1, dollar2; // pointers to $ in the pattern 1014 // parse n = (<pkg>/)*<outer>($<number>)?($<name>)? 1015 int nlen = n.length(); 1016 int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, n.length()) + 1; 1017 dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, n.length()); 1018 if (dollar2 < pkglen) return null; 1019 if (isDigitString(n, dollar2+1, nlen)) { 1020 // n = (<pkg>/)*<outer>$<number> 1021 number = n.substring(dollar2+1, nlen); 1022 name = null; 1023 dollar1 = dollar2; 1024 } else if ((dollar1 1025 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, dollar2-1)) 1026 > pkglen 1027 && isDigitString(n, dollar1+1, dollar2)) { 1028 // n = (<pkg>/)*<outer>$<number>$<name> 1029 number = n.substring(dollar1+1, dollar2); 1030 name = n.substring(dollar2+1, nlen).intern(); 1031 } else { 1032 // n = (<pkg>/)*<outer>$<name> 1033 dollar1 = dollar2; 1034 number = null; 1035 name = n.substring(dollar2+1, nlen).intern(); 1036 } 1037 if (number == null) 1038 pkgOuter = n.substring(0, dollar1).intern(); 1039 else 1040 pkgOuter = null; 1041 //System.out.println("parseInnerClassName parses "+pkgOuter+" "+number+" "+name); 1042 return new String[] { pkgOuter, number, name }; 1043 } 1044 1045 private static final int SLASH_MIN = '.'; 1046 private static final int SLASH_MAX = '/'; 1047 private static final int DOLLAR_MIN = 0; 1048 private static final int DOLLAR_MAX = '-'; 1049 static { 1050 assert(lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, "x$$y$", 4) == 2); 1051 assert(lastIndexOf(SLASH_MIN, SLASH_MAX, "x//y/", 4) == 2); 1052 } 1053 1054 private static int lastIndexOf(int chMin, int chMax, String str, int pos) { 1055 for (int i = pos; --i >= 0; ) { 1056 int ch = str.charAt(i); 1057 if (ch >= chMin && ch <= chMax) { 1058 return i; 1059 } 1060 } 1061 return -1; 1062 } 1063 1064 private static boolean isDigitString(String x, int beg, int end) { 1065 if (beg == end) return false; // null string 1066 for (int i = beg; i < end; i++) { 1067 char ch = x.charAt(i); 1068 if (!(ch >= '0' && ch <= '9')) return false; 1069 } 1070 return true; 1071 } 1072 1073 static String getObviousSourceFile(String className) { 1074 String n = className; 1075 int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, n.length()) + 1; 1076 n = n.substring(pkglen); 1077 int cutoff = n.length(); 1078 for (;;) { 1079 // Work backwards, finding all '$', '#', etc. 1080 int dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, cutoff-1); 1081 if (dollar2 < 0) 1082 break; 1083 cutoff = dollar2; 1084 if (cutoff == 0) 1085 break; 1086 } 1087 String obvious = n.substring(0, cutoff)+".java"; 1088 return obvious; 1089 } 1090 /* 1091 static { 1092 assert(getObviousSourceFile("foo").equals("foo.java")); 1093 assert(getObviousSourceFile("foo/bar").equals("bar.java")); 1094 assert(getObviousSourceFile("foo/bar$baz").equals("bar.java")); 1095 assert(getObviousSourceFile("foo/bar#baz#1").equals("bar.java")); 1096 assert(getObviousSourceFile("foo.bar.baz#1").equals("baz.java")); 1097 } 1098 */ 1099 1100 static Utf8Entry getRefString(String s) { 1101 return ConstantPool.getUtf8Entry(s); 1102 } 1103 1104 static LiteralEntry getRefLiteral(Comparable<?> s) { 1105 return ConstantPool.getLiteralEntry(s); 1106 } 1107 1108 void stripAttributeKind(String what) { 1109 // what is one of { Debug, Compile, Constant, Exceptions, InnerClasses } 1110 if (verbose > 0) 1111 Utils.log.info("Stripping "+what.toLowerCase()+" data and attributes..."); 1112 switch (what) { 1113 case "Debug": 1114 strip("SourceFile"); 1115 strip("LineNumberTable"); 1116 strip("LocalVariableTable"); 1117 strip("LocalVariableTypeTable"); 1118 break; 1119 case "Compile": 1120 // Keep the inner classes normally. 1121 // Although they have no effect on execution, 1122 // the Reflection API exposes them, and JCK checks them. 1123 // NO: // strip("InnerClasses"); 1124 strip("Deprecated"); 1125 strip("Synthetic"); 1126 break; 1127 case "Exceptions": 1128 // Keep the exceptions normally. 1129 // Although they have no effect on execution, 1130 // the Reflection API exposes them, and JCK checks them. 1131 strip("Exceptions"); 1132 break; 1133 case "Constant": 1134 stripConstantFields(); 1135 break; 1136 } 1137 } 1138 1139 public void trimToSize() { 1140 classes.trimToSize(); 1141 for (Class c : classes) { 1142 c.trimToSize(); 1143 } 1144 files.trimToSize(); 1145 } 1146 1147 public void strip(String attrName) { 1148 for (Class c : classes) { 1149 c.strip(attrName); 1150 } 1151 } 1152 1153 public void stripConstantFields() { 1154 for (Class c : classes) { 1155 for (Iterator<Class.Field> j = c.fields.iterator(); j.hasNext(); ) { 1156 Class.Field f = j.next(); 1157 if (Modifier.isFinal(f.flags) 1158 // do not strip non-static finals: 1159 && Modifier.isStatic(f.flags) 1160 && f.getAttribute("ConstantValue") != null 1161 && !f.getName().startsWith("serial")) { 1162 if (verbose > 2) { 1163 Utils.log.fine(">> Strip "+this+" ConstantValue"); 1164 j.remove(); 1165 } 1166 } 1167 } 1168 } 1169 } 1170 1171 protected void visitRefs(int mode, Collection<Entry> refs) { 1172 for ( Class c : classes) { 1173 c.visitRefs(mode, refs); 1174 } 1175 if (mode != VRM_CLASSIC) { 1176 for (File f : files) { 1177 f.visitRefs(mode, refs); 1178 } 1179 visitInnerClassRefs(allInnerClasses, mode, refs); 1180 } 1181 } 1182 1183 // Use this before writing the package file. 1184 // It sorts files into a new order which seems likely to 1185 // compress better. It also moves classes to the end of the 1186 // file order. It also removes JAR directory entries, which 1187 // are useless. 1188 void reorderFiles(boolean keepClassOrder, boolean stripDirectories) { 1189 // First reorder the classes, if that is allowed. 1190 if (!keepClassOrder) { 1191 // In one test with rt.jar, this trick gained 0.7% 1192 Collections.sort(classes); 1193 } 1194 1195 // Remove stubs from resources; maybe we'll add them on at the end, 1196 // if there are some non-trivial ones. The best case is that 1197 // modtimes and options are not transmitted, and the stub files 1198 // for class files do not need to be transmitted at all. 1199 // Also 1200 List<File> stubs = getClassStubs(); 1201 for (Iterator<File> i = files.iterator(); i.hasNext(); ) { 1202 File file = i.next(); 1203 if (file.isClassStub() || 1204 (stripDirectories && file.isDirectory())) { 1205 i.remove(); 1206 } 1207 } 1208 1209 // Sort the remaining non-class files. 1210 // We sort them by file type. 1211 // This keeps files of similar format near each other. 1212 // Put class files at the end, keeping their fixed order. 1213 // Be sure the JAR file's required manifest stays at the front. (4893051) 1214 Collections.sort(files, new Comparator<>() { 1215 public int compare(File r0, File r1) { 1216 // Get the file name. 1217 String f0 = r0.nameString; 1218 String f1 = r1.nameString; 1219 if (f0.equals(f1)) return 0; 1220 if (JarFile.MANIFEST_NAME.equals(f0)) return 0-1; 1221 if (JarFile.MANIFEST_NAME.equals(f1)) return 1-0; 1222 // Extract file basename. 1223 String n0 = f0.substring(1+f0.lastIndexOf('/')); 1224 String n1 = f1.substring(1+f1.lastIndexOf('/')); 1225 // Extract basename extension. 1226 String x0 = n0.substring(1+n0.lastIndexOf('.')); 1227 String x1 = n1.substring(1+n1.lastIndexOf('.')); 1228 int r; 1229 // Primary sort key is file extension. 1230 r = x0.compareTo(x1); 1231 if (r != 0) return r; 1232 r = f0.compareTo(f1); 1233 return r; 1234 } 1235 }); 1236 1237 // Add back the class stubs after sorting, before trimStubs. 1238 files.addAll(stubs); 1239 } 1240 1241 void trimStubs() { 1242 // Restore enough non-trivial stubs to carry the needed class modtimes. 1243 for (ListIterator<File> i = files.listIterator(files.size()); i.hasPrevious(); ) { 1244 File file = i.previous(); 1245 if (!file.isTrivialClassStub()) { 1246 if (verbose > 1) 1247 Utils.log.fine("Keeping last non-trivial "+file); 1248 break; 1249 } 1250 if (verbose > 2) 1251 Utils.log.fine("Removing trivial "+file); 1252 i.remove(); 1253 } 1254 1255 if (verbose > 0) { 1256 Utils.log.info("Transmitting "+files.size()+" files, including per-file data for "+getClassStubs().size()+" classes out of "+classes.size()); 1257 } 1258 } 1259 1260 // Use this before writing the package file. 1261 void buildGlobalConstantPool(Set<Entry> requiredEntries) { 1262 if (verbose > 1) 1263 Utils.log.fine("Checking for unused CP entries"); 1264 requiredEntries.add(getRefString("")); // uconditionally present 1265 visitRefs(VRM_PACKAGE, requiredEntries); 1266 ConstantPool.completeReferencesIn(requiredEntries, false); 1267 if (verbose > 1) 1268 Utils.log.fine("Sorting CP entries"); 1269 Index cpAllU = ConstantPool.makeIndex("unsorted", requiredEntries); 1270 Index[] byTagU = ConstantPool.partitionByTag(cpAllU); 1271 for (int i = 0; i < ConstantPool.TAGS_IN_ORDER.length; i++) { 1272 byte tag = ConstantPool.TAGS_IN_ORDER[i]; 1273 // Work on all entries of a given kind. 1274 Index ix = byTagU[tag]; 1275 if (ix == null) continue; 1276 ConstantPool.sort(ix); 1277 cp.initIndexByTag(tag, ix); 1278 byTagU[tag] = null; // done with it 1279 } 1280 for (int i = 0; i < byTagU.length; i++) { 1281 Index ix = byTagU[i]; 1282 assert(ix == null); // all consumed 1283 } 1284 for (int i = 0; i < ConstantPool.TAGS_IN_ORDER.length; i++) { 1285 byte tag = ConstantPool.TAGS_IN_ORDER[i]; 1286 Index ix = cp.getIndexByTag(tag); 1287 assert(ix.assertIsSorted()); 1288 if (verbose > 2) Utils.log.fine(ix.dumpString()); 1289 } 1290 } 1291 1292 // Use this before writing the class files. 1293 void ensureAllClassFiles() { 1294 Set<File> fileSet = new HashSet<>(files); 1295 for (Class cls : classes) { 1296 // Add to the end of ths list: 1297 if (!fileSet.contains(cls.file)) 1298 files.add(cls.file); 1299 } 1300 } 1301 1302 static final List<Object> noObjects = Arrays.asList(new Object[0]); 1303 static final List<Class.Field> noFields = Arrays.asList(new Class.Field[0]); 1304 static final List<Class.Method> noMethods = Arrays.asList(new Class.Method[0]); 1305 static final List<InnerClass> noInnerClasses = Arrays.asList(new InnerClass[0]); 1306 1307 protected static final class Version { 1308 1309 public final short major; 1310 public final short minor; 1311 1312 private Version(short major, short minor) { 1313 this.major = major; 1314 this.minor = minor; 1315 } 1316 1317 public String toString() { 1318 return major + "." + minor; 1319 } 1320 1321 public boolean equals(Object that) { 1322 return that instanceof Version 1323 && major == ((Version)that).major 1324 && minor == ((Version)that).minor; 1325 } 1326 1327 public int intValue() { 1328 return (major << 16) + minor; 1329 } 1330 1331 public int hashCode() { 1332 return (major << 16) + 7 + minor; 1333 } 1334 1335 public static Version of(int major, int minor) { 1336 return new Version((short)major, (short)minor); 1337 } 1338 1339 public static Version of(byte[] bytes) { 1340 int minor = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF); 1341 int major = ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF); 1342 return new Version((short)major, (short)minor); 1343 } 1344 1345 public static Version of(int major_minor) { 1346 short minor = (short)major_minor; 1347 short major = (short)(major_minor >>> 16); 1348 return new Version(major, minor); 1349 } 1350 1351 public static Version makeVersion(PropMap props, String partialKey) { 1352 int min = props.getInteger(Utils.COM_PREFIX 1353 + partialKey + ".minver", -1); 1354 int maj = props.getInteger(Utils.COM_PREFIX 1355 + partialKey + ".majver", -1); 1356 return min >= 0 && maj >= 0 ? Version.of(maj, min) : null; 1357 } 1358 public byte[] asBytes() { 1359 byte[] bytes = { 1360 (byte) (minor >> 8), (byte) minor, 1361 (byte) (major >> 8), (byte) major 1362 }; 1363 return bytes; 1364 } 1365 public int compareTo(Version that) { 1366 return this.intValue() - that.intValue(); 1367 } 1368 1369 public boolean lessThan(Version that) { 1370 return compareTo(that) < 0 ; 1371 } 1372 1373 public boolean greaterThan(Version that) { 1374 return compareTo(that) > 0 ; 1375 } 1376 } 1377 }