1 /* 2 * Copyright (c) 2001, 2010, 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 com.sun.java.util.jar.pack.Attribute.Layout; 29 import com.sun.java.util.jar.pack.ConstantPool.ClassEntry; 30 import com.sun.java.util.jar.pack.ConstantPool.DescriptorEntry; 31 import com.sun.java.util.jar.pack.ConstantPool.Index; 32 import com.sun.java.util.jar.pack.ConstantPool.LiteralEntry; 33 import com.sun.java.util.jar.pack.ConstantPool.Utf8Entry; 34 import com.sun.java.util.jar.pack.ConstantPool.Entry; 35 import java.io.ByteArrayInputStream; 36 import java.io.ByteArrayOutputStream; 37 import java.io.IOException; 38 import java.io.InputStream; 39 import java.io.OutputStream; 40 import java.io.SequenceInputStream; 41 import java.lang.reflect.Modifier; 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.Collection; 45 import java.util.Collections; 46 import java.util.Comparator; 47 import java.util.HashMap; 48 import java.util.HashSet; 49 import java.util.Iterator; 50 import java.util.List; 51 import java.util.ListIterator; 52 import java.util.Map; 53 import java.util.Set; 54 import java.util.jar.JarFile; 55 import static com.sun.java.util.jar.pack.Constants.*; 56 57 /** 58 * Define the main data structure transmitted by pack/unpack. 59 * @author John Rose 60 */ 61 class Package { 62 int verbose; 63 { 64 PropMap pmap = Utils.currentPropMap(); 65 if (pmap != null) 66 verbose = pmap.getInteger(Utils.DEBUG_VERBOSE); 67 } 68 69 int magic; 70 int package_minver; 71 int package_majver; 72 73 int default_modtime = NO_MODTIME; 74 int default_options = 0; // FO_DEFLATE_HINT 75 76 short default_class_majver = -1; // fill in later 77 short default_class_minver = 0; // fill in later 78 79 // These fields can be adjusted by driver properties. 80 short min_class_majver = JAVA_MIN_CLASS_MAJOR_VERSION; 81 short min_class_minver = JAVA_MIN_CLASS_MINOR_VERSION; 82 short max_class_majver = JAVA7_MAX_CLASS_MAJOR_VERSION; 83 short max_class_minver = JAVA7_MAX_CLASS_MINOR_VERSION; 84 85 short observed_max_class_majver = min_class_majver; 86 short observed_max_class_minver = min_class_minver; 87 88 // What constants are used in this unit? 89 ConstantPool.IndexGroup cp = new ConstantPool.IndexGroup(); 90 91 Package() { 92 magic = JAVA_PACKAGE_MAGIC; 93 package_minver = -1; // fill in later 94 package_majver = 0; // fill in later 95 } 96 97 public 98 void reset() { 99 cp = new ConstantPool.IndexGroup(); 100 classes.clear(); 101 files.clear(); 102 BandStructure.nextSeqForDebug = 0; 103 } 104 105 int getPackageVersion() { 106 return (package_majver << 16) + package_minver; 107 } 108 109 // Special empty versions of Code and InnerClasses, used for markers. 110 public static final Attribute.Layout attrCodeEmpty; 111 public static final Attribute.Layout attrInnerClassesEmpty; 112 public static final Attribute.Layout attrSourceFileSpecial; 113 public static final Map<Attribute.Layout, Attribute> attrDefs; 114 static { 115 Map<Layout, Attribute> ad = new HashMap<>(3); 116 attrCodeEmpty = Attribute.define(ad, ATTR_CONTEXT_METHOD, 117 "Code", "").layout(); 118 attrInnerClassesEmpty = Attribute.define(ad, ATTR_CONTEXT_CLASS, 119 "InnerClasses", "").layout(); 120 attrSourceFileSpecial = Attribute.define(ad, ATTR_CONTEXT_CLASS, 121 "SourceFile", "RUNH").layout(); 122 attrDefs = Collections.unmodifiableMap(ad); 123 } 124 125 int getDefaultClassVersion() { 126 return (default_class_majver << 16) + (char)default_class_minver; 127 } 128 129 /** Return the highest version number of all classes, 130 * or 0 if there are no classes. 131 */ 132 int getHighestClassVersion() { 133 int res = 0; // initial low value 134 for (Class cls : classes) { 135 int ver = cls.getVersion(); 136 if (res < ver) res = ver; 137 } 138 return res; 139 } 140 141 /** Convenience function to choose an archive version based 142 * on the class file versions observed within the archive. 143 */ 144 void choosePackageVersion() { 145 assert(package_majver <= 0); // do not call this twice 146 int classver = getHighestClassVersion(); 147 if (classver == 0 || (classver >>> 16) < JAVA6_MAX_CLASS_MAJOR_VERSION) { 148 // There are only old classfiles in this segment or resources 149 package_majver = JAVA5_PACKAGE_MAJOR_VERSION; 150 package_minver = JAVA5_PACKAGE_MINOR_VERSION; 151 } else if ((classver >>> 16) == JAVA6_MAX_CLASS_MAJOR_VERSION) { 152 package_majver = JAVA6_PACKAGE_MAJOR_VERSION; 153 package_minver = JAVA6_PACKAGE_MINOR_VERSION; 154 } else { 155 // Normal case. Use the newest archive format, when available 156 // TODO: replace the following with JAVA7* when the need arises 157 package_majver = JAVA6_PACKAGE_MAJOR_VERSION; 158 package_minver = JAVA6_PACKAGE_MINOR_VERSION; 159 } 160 } 161 162 // What Java classes are in this unit? 163 164 // Fixed 6211177, converted to throw IOException 165 void checkVersion() throws IOException { 166 if (magic != JAVA_PACKAGE_MAGIC) { 167 String gotMag = Integer.toHexString(magic); 168 String expMag = Integer.toHexString(JAVA_PACKAGE_MAGIC); 169 throw new IOException("Unexpected package magic number: got "+gotMag+"; expected "+expMag); 170 } 171 if ((package_majver != JAVA6_PACKAGE_MAJOR_VERSION && 172 package_majver != JAVA5_PACKAGE_MAJOR_VERSION) || 173 (package_minver != JAVA6_PACKAGE_MINOR_VERSION && 174 package_minver != JAVA5_PACKAGE_MINOR_VERSION)) { 175 176 String gotVer = package_majver+"."+package_minver; 177 String expVer = JAVA6_PACKAGE_MAJOR_VERSION+"."+JAVA6_PACKAGE_MINOR_VERSION+ 178 " OR "+ 179 JAVA5_PACKAGE_MAJOR_VERSION+"."+JAVA5_PACKAGE_MINOR_VERSION; 180 throw new IOException("Unexpected package minor version: got "+gotVer+"; expected "+expVer); 181 } 182 } 183 184 ArrayList<Package.Class> classes = new ArrayList<>(); 185 186 public List<Package.Class> getClasses() { 187 return classes; 188 } 189 190 public final 191 class Class extends Attribute.Holder implements Comparable { 192 public Package getPackage() { return Package.this; } 193 194 // Optional file characteristics and data source (a "class stub") 195 File file; 196 197 // File header 198 int magic; 199 short minver, majver; 200 201 // Local constant pool (one-way mapping of index => package cp). 202 Entry[] cpMap; 203 204 // Class header 205 //int flags; // in Attribute.Holder.this.flags 206 ClassEntry thisClass; 207 ClassEntry superClass; 208 ClassEntry[] interfaces; 209 210 // Class parts 211 ArrayList<Field> fields; 212 ArrayList<Method> methods; 213 //ArrayList attributes; // in Attribute.Holder.this.attributes 214 // Note that InnerClasses may be collected at the package level. 215 ArrayList<InnerClass> innerClasses; 216 217 Class(int flags, ClassEntry thisClass, ClassEntry superClass, ClassEntry[] interfaces) { 218 this.magic = JAVA_MAGIC; 219 this.minver = default_class_minver; 220 this.majver = default_class_majver; 221 this.flags = flags; 222 this.thisClass = thisClass; 223 this.superClass = superClass; 224 this.interfaces = interfaces; 225 226 boolean added = classes.add(this); 227 assert(added); 228 } 229 230 Class(String classFile) { 231 // A blank class; must be read with a ClassReader, etc. 232 initFile(newStub(classFile)); 233 } 234 235 List<Field> getFields() { return fields == null ? noFields : fields; } 236 List<Method> getMethods() { return methods == null ? noMethods : methods; } 237 238 public String getName() { 239 return thisClass.stringValue(); 240 } 241 242 int getVersion() { 243 return (majver << 16) + (char)minver; 244 } 245 String getVersionString() { 246 return versionStringOf(majver, minver); 247 } 248 249 // Note: equals and hashCode are identity-based. 250 public int compareTo(Object o) { 251 Class that = (Class)o; 252 String n0 = this.getName(); 253 String n1 = that.getName(); 254 return n0.compareTo(n1); 255 } 256 257 String getObviousSourceFile() { 258 return Package.getObviousSourceFile(getName()); 259 } 260 261 private void transformSourceFile(boolean minimize) { 262 // Replace "obvious" SourceFile by null. 263 Attribute olda = getAttribute(attrSourceFileSpecial); 264 if (olda == null) 265 return; // no SourceFile attr. 266 String obvious = getObviousSourceFile(); 267 List<Entry> ref = new ArrayList<>(1); 268 olda.visitRefs(this, VRM_PACKAGE, ref); 269 Utf8Entry sfName = (Utf8Entry) ref.get(0); 270 Attribute a = olda; 271 if (sfName == null) { 272 if (minimize) { 273 // A pair of zero bytes. Cannot use predef. layout. 274 a = Attribute.find(ATTR_CONTEXT_CLASS, "SourceFile", "H"); 275 a = a.addContent(new byte[2]); 276 } else { 277 // Expand null attribute to the obvious string. 278 byte[] bytes = new byte[2]; 279 sfName = getRefString(obvious); 280 Object f = null; 281 f = Fixups.add(f, bytes, 0, Fixups.U2_FORMAT, sfName); 282 a = attrSourceFileSpecial.addContent(bytes, f); 283 } 284 } else if (obvious.equals(sfName.stringValue())) { 285 if (minimize) { 286 // Replace by an all-zero attribute. 287 a = attrSourceFileSpecial.addContent(new byte[2]); 288 } else { 289 assert(false); 290 } 291 } 292 if (a != olda) { 293 if (verbose > 2) 294 Utils.log.fine("recoding obvious SourceFile="+obvious); 295 List<Attribute> newAttrs = new ArrayList<>(getAttributes()); 296 int where = newAttrs.indexOf(olda); 297 newAttrs.set(where, a); 298 setAttributes(newAttrs); 299 } 300 } 301 302 void minimizeSourceFile() { 303 transformSourceFile(true); 304 } 305 void expandSourceFile() { 306 transformSourceFile(false); 307 } 308 309 protected Entry[] getCPMap() { 310 return cpMap; 311 } 312 313 protected void setCPMap(Entry[] cpMap) { 314 this.cpMap = cpMap; 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<InnerClass>(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 changed = 0; // [] => null, no tuple change 480 } else { 481 // Non-trivial diff was transmitted. 482 actualICs = computeICdiff(); 483 // If we only added more ICs, return +1. 484 changed = actualICs.containsAll(localICs)? +1: -1; 485 } 486 setInnerClasses(actualICs); 487 return changed; 488 } 489 490 public abstract 491 class Member extends Attribute.Holder implements Comparable { 492 DescriptorEntry descriptor; 493 494 protected Member(int flags, DescriptorEntry descriptor) { 495 this.flags = flags; 496 this.descriptor = descriptor; 497 } 498 499 public Class thisClass() { return Class.this; } 500 501 public DescriptorEntry getDescriptor() { 502 return descriptor; 503 } 504 public String getName() { 505 return descriptor.nameRef.stringValue(); 506 } 507 public String getType() { 508 return descriptor.typeRef.stringValue(); 509 } 510 511 protected Entry[] getCPMap() { 512 return cpMap; 513 } 514 protected void visitRefs(int mode, Collection<Entry> refs) { 515 if (verbose > 2) Utils.log.fine("visitRefs "+this); 516 // Careful: The descriptor is used by the package, 517 // but the classfile breaks it into component refs. 518 if (mode == VRM_CLASSIC) { 519 refs.add(descriptor.nameRef); 520 refs.add(descriptor.typeRef); 521 } else { 522 refs.add(descriptor); 523 } 524 // Handle attribute list: 525 super.visitRefs(mode, refs); 526 } 527 528 public String toString() { 529 return Class.this + "." + descriptor.prettyString(); 530 } 531 } 532 533 public 534 class Field extends Member { 535 // Order is significant for fields: It is visible to reflection. 536 int order; 537 538 public Field(int flags, DescriptorEntry descriptor) { 539 super(flags, descriptor); 540 assert(!descriptor.isMethod()); 541 if (fields == null) 542 fields = new ArrayList<>(); 543 boolean added = fields.add(this); 544 assert(added); 545 order = fields.size(); 546 } 547 548 public byte getLiteralTag() { 549 return descriptor.getLiteralTag(); 550 } 551 552 public int compareTo(Object o) { 553 Field that = (Field)o; 554 return this.order - that.order; 555 } 556 } 557 558 public 559 class Method extends Member { 560 // Code attribute is specially hardwired. 561 Code code; 562 563 public Method(int flags, DescriptorEntry descriptor) { 564 super(flags, descriptor); 565 assert(descriptor.isMethod()); 566 if (methods == null) 567 methods = new ArrayList<>(); 568 boolean added = methods.add(this); 569 assert(added); 570 } 571 572 public void trimToSize() { 573 super.trimToSize(); 574 if (code != null) 575 code.trimToSize(); 576 } 577 578 public int getArgumentSize() { 579 int argSize = descriptor.typeRef.computeSize(true); 580 int thisSize = Modifier.isStatic(flags) ? 0 : 1; 581 return thisSize + argSize; 582 } 583 584 // Sort methods in a canonical order (by type, then by name). 585 public int compareTo(Object o) { 586 Method that = (Method)o; 587 return this.getDescriptor().compareTo(that.getDescriptor()); 588 } 589 590 public void strip(String attrName) { 591 if ("Code".equals(attrName)) 592 code = null; 593 if (code != null) 594 code.strip(attrName); 595 super.strip(attrName); 596 } 597 protected void visitRefs(int mode, Collection<Entry> refs) { 598 super.visitRefs(mode, refs); 599 if (code != null) { 600 if (mode == VRM_CLASSIC) { 601 refs.add(getRefString("Code")); 602 } 603 code.visitRefs(mode, refs); 604 } 605 } 606 } 607 608 public void trimToSize() { 609 super.trimToSize(); 610 for (int isM = 0; isM <= 1; isM++) { 611 ArrayList members = (isM == 0) ? fields : methods; 612 if (members == null) continue; 613 members.trimToSize(); 614 for (Iterator i = members.iterator(); i.hasNext(); ) { 615 Member m = (Member)i.next(); 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 members = (isM == 0) ? fields : methods; 629 if (members == null) continue; 630 for (Iterator i = members.iterator(); i.hasNext(); ) { 631 Member m = (Member)i.next(); 632 m.strip(attrName); 633 } 634 } 635 super.strip(attrName); 636 } 637 638 protected void visitRefs(int mode, Collection<Entry> refs) { 639 if (verbose > 2) Utils.log.fine("visitRefs "+this); 640 refs.add(thisClass); 641 refs.add(superClass); 642 refs.addAll(Arrays.asList(interfaces)); 643 for (int isM = 0; isM <= 1; isM++) { 644 ArrayList members = (isM == 0) ? fields : methods; 645 if (members == null) continue; 646 for (Iterator i = members.iterator(); i.hasNext(); ) { 647 Member m = (Member)i.next(); 648 boolean ok = false; 649 try { 650 m.visitRefs(mode, refs); 651 ok = true; 652 } finally { 653 if (!ok) 654 Utils.log.warning("Error scanning "+m); 655 } 656 } 657 } 658 visitInnerClassRefs(mode, refs); 659 // Handle attribute list: 660 super.visitRefs(mode, refs); 661 } 662 663 protected void visitInnerClassRefs(int mode, Collection<Entry> refs) { 664 Package.visitInnerClassRefs(innerClasses, mode, refs); 665 } 666 667 // Hook called by ClassReader when it's done. 668 void finishReading() { 669 trimToSize(); 670 maybeChooseFileName(); 671 } 672 673 public void initFile(File file) { 674 assert(this.file == null); // set-once 675 if (file == null) { 676 // Build a trivial stub. 677 file = newStub(canonicalFileName()); 678 } 679 this.file = file; 680 assert(file.isClassStub()); 681 file.stubClass = this; 682 maybeChooseFileName(); 683 } 684 685 public void maybeChooseFileName() { 686 if (thisClass == null) { 687 return; // do not choose yet 688 } 689 String canonName = canonicalFileName(); 690 if (file.nameString.equals("")) { 691 file.nameString = canonName; 692 } 693 if (file.nameString.equals(canonName)) { 694 // The file name is predictable. Transmit "". 695 file.name = getRefString(""); 696 return; 697 } 698 // If name has not yet been looked up, find it now. 699 if (file.name == null) { 700 file.name = getRefString(file.nameString); 701 } 702 } 703 704 public String canonicalFileName() { 705 if (thisClass == null) return null; 706 return thisClass.stringValue() + ".class"; 707 } 708 709 public java.io.File getFileName(java.io.File parent) { 710 String name = file.name.stringValue(); 711 if (name.equals("")) 712 name = canonicalFileName(); 713 String fname = name.replace('/', java.io.File.separatorChar); 714 return new java.io.File(parent, fname); 715 } 716 public java.io.File getFileName() { 717 return getFileName(null); 718 } 719 720 public String toString() { 721 return thisClass.stringValue(); 722 } 723 } 724 725 void addClass(Class c) { 726 assert(c.getPackage() == this); 727 boolean added = classes.add(c); 728 assert(added); 729 // Make sure the class is represented in the total file order: 730 if (c.file == null) c.initFile(null); 731 addFile(c.file); 732 } 733 734 // What non-class files are in this unit? 735 ArrayList<File> files = new ArrayList<>(); 736 737 public List<File> getFiles() { 738 return files; 739 } 740 741 public List<File> getClassStubs() { 742 List<File> classStubs = new ArrayList<>(classes.size()); 743 for (Class cls : classes) { 744 assert(cls.file.isClassStub()); 745 classStubs.add(cls.file); 746 } 747 return classStubs; 748 } 749 750 public final class File implements Comparable { 751 String nameString; // true name of this file 752 Utf8Entry name; 753 int modtime = NO_MODTIME; 754 int options = 0; // random flag bits, such as deflate_hint 755 Class stubClass; // if this is a stub, here's the class 756 ArrayList prepend = new ArrayList(); // list of byte[] 757 java.io.ByteArrayOutputStream append = new ByteArrayOutputStream(); 758 759 File(Utf8Entry name) { 760 this.name = name; 761 this.nameString = name.stringValue(); 762 // caller must fill in contents 763 } 764 File(String nameString) { 765 nameString = fixupFileName(nameString); 766 this.name = getRefString(nameString); 767 this.nameString = name.stringValue(); 768 } 769 770 public boolean isDirectory() { 771 // JAR directory. Useless. 772 return nameString.endsWith("/"); 773 } 774 public boolean isClassStub() { 775 return (options & FO_IS_CLASS_STUB) != 0; 776 } 777 public Class getStubClass() { 778 assert(isClassStub()); 779 assert(stubClass != null); 780 return stubClass; 781 } 782 public boolean isTrivialClassStub() { 783 return isClassStub() 784 && name.stringValue().equals("") 785 && (modtime == NO_MODTIME || modtime == default_modtime) 786 && (options &~ FO_IS_CLASS_STUB) == 0; 787 } 788 789 // The nameString is the key. Ignore other things. 790 // (Note: The name might be "", in the case of a trivial class stub.) 791 public boolean equals(Object o) { 792 if (o == null || (o.getClass() != File.class)) 793 return false; 794 File that = (File)o; 795 return that.nameString.equals(this.nameString); 796 } 797 public int hashCode() { 798 return nameString.hashCode(); 799 } 800 // Simple alphabetic sort. PackageWriter uses a better comparator. 801 public int compareTo(Object o) { 802 File that = (File)o; 803 return this.nameString.compareTo(that.nameString); 804 } 805 public String toString() { 806 return nameString+"{" 807 +(isClassStub()?"*":"") 808 +(BandStructure.testBit(options,FO_DEFLATE_HINT)?"@":"") 809 +(modtime==NO_MODTIME?"":"M"+modtime) 810 +(getFileLength()==0?"":"["+getFileLength()+"]") 811 +"}"; 812 } 813 814 public java.io.File getFileName() { 815 return getFileName(null); 816 } 817 public java.io.File getFileName(java.io.File parent) { 818 String lname = this.nameString; 819 //if (name.startsWith("./")) name = name.substring(2); 820 String fname = lname.replace('/', java.io.File.separatorChar); 821 return new java.io.File(parent, fname); 822 } 823 824 public void addBytes(byte[] bytes) { 825 addBytes(bytes, 0, bytes.length); 826 } 827 public void addBytes(byte[] bytes, int off, int len) { 828 if (((append.size() | len) << 2) < 0) { 829 prepend.add(append.toByteArray()); 830 append.reset(); 831 } 832 append.write(bytes, off, len); 833 } 834 public long getFileLength() { 835 long len = 0; 836 if (prepend == null || append == null) return 0; 837 for (Iterator i = prepend.iterator(); i.hasNext(); ) { 838 byte[] block = (byte[]) i.next(); 839 len += block.length; 840 } 841 len += append.size(); 842 return len; 843 } 844 public void writeTo(OutputStream out) throws IOException { 845 if (prepend == null || append == null) return; 846 for (Iterator i = prepend.iterator(); i.hasNext(); ) { 847 byte[] block = (byte[]) i.next(); 848 out.write(block); 849 } 850 append.writeTo(out); 851 } 852 public void readFrom(InputStream in) throws IOException { 853 byte[] buf = new byte[1 << 16]; 854 int nr; 855 while ((nr = in.read(buf)) > 0) { 856 addBytes(buf, 0, nr); 857 } 858 } 859 public InputStream getInputStream() { 860 InputStream in = new ByteArrayInputStream(append.toByteArray()); 861 if (prepend.isEmpty()) return in; 862 List<InputStream> isa = new ArrayList<>(prepend.size()+1); 863 for (Iterator i = prepend.iterator(); i.hasNext(); ) { 864 byte[] bytes = (byte[]) i.next(); 865 isa.add(new ByteArrayInputStream(bytes)); 866 } 867 isa.add(in); 868 return new SequenceInputStream(Collections.enumeration(isa)); 869 } 870 871 protected void visitRefs(int mode, Collection<Entry> refs) { 872 assert(name != null); 873 refs.add(name); 874 } 875 } 876 877 File newStub(String classFileNameString) { 878 File stub = new File(classFileNameString); 879 stub.options |= FO_IS_CLASS_STUB; 880 stub.prepend = null; 881 stub.append = null; // do not collect data 882 return stub; 883 } 884 885 private static String fixupFileName(String name) { 886 String fname = name.replace(java.io.File.separatorChar, '/'); 887 if (fname.startsWith("/")) { 888 throw new IllegalArgumentException("absolute file name "+fname); 889 } 890 return fname; 891 } 892 893 void addFile(File file) { 894 boolean added = files.add(file); 895 assert(added); 896 } 897 898 // Is there a globally declared table of inner classes? 899 List<InnerClass> allInnerClasses = new ArrayList<>(); 900 Map<ClassEntry, InnerClass> allInnerClassesByThis; 901 902 public 903 List<InnerClass> getAllInnerClasses() { 904 return allInnerClasses; 905 } 906 907 public 908 void setAllInnerClasses(Collection<InnerClass> ics) { 909 assert(ics != allInnerClasses); 910 allInnerClasses.clear(); 911 allInnerClasses.addAll(ics); 912 913 // Make an index: 914 allInnerClassesByThis = new HashMap<>(allInnerClasses.size()); 915 for (InnerClass ic : allInnerClasses) { 916 Object pic = allInnerClassesByThis.put(ic.thisClass, ic); 917 assert(pic == null); // caller must ensure key uniqueness! 918 } 919 } 920 921 /** Return a global inner class record for the given thisClass. */ 922 public 923 InnerClass getGlobalInnerClass(Entry thisClass) { 924 assert(thisClass instanceof ClassEntry); 925 return allInnerClassesByThis.get(thisClass); 926 } 927 928 static 929 class InnerClass implements Comparable { 930 final ClassEntry thisClass; 931 final ClassEntry outerClass; 932 final Utf8Entry name; 933 final int flags; 934 935 // Can name and outerClass be derived from thisClass? 936 final boolean predictable; 937 938 // About 30% of inner classes are anonymous (in rt.jar). 939 // About 60% are class members; the rest are named locals. 940 // Nearly all have predictable outers and names. 941 942 InnerClass(ClassEntry thisClass, ClassEntry outerClass, 943 Utf8Entry name, int flags) { 944 this.thisClass = thisClass; 945 this.outerClass = outerClass; 946 this.name = name; 947 this.flags = flags; 948 this.predictable = computePredictable(); 949 } 950 951 private boolean computePredictable() { 952 //System.out.println("computePredictable "+outerClass+" "+this.name); 953 String[] parse = parseInnerClassName(thisClass.stringValue()); 954 if (parse == null) return false; 955 String pkgOuter = parse[0]; 956 //String number = parse[1]; 957 String lname = parse[2]; 958 String haveName = (this.name == null) ? null : this.name.stringValue(); 959 String haveOuter = (outerClass == null) ? null : outerClass.stringValue(); 960 boolean lpredictable = (lname == haveName && pkgOuter == haveOuter); 961 //System.out.println("computePredictable => "+predictable); 962 return lpredictable; 963 } 964 965 public boolean equals(Object o) { 966 if (o == null || o.getClass() != InnerClass.class) 967 return false; 968 InnerClass that = (InnerClass)o; 969 return eq(this.thisClass, that.thisClass) 970 && eq(this.outerClass, that.outerClass) 971 && eq(this.name, that.name) 972 && this.flags == that.flags; 973 } 974 private static boolean eq(Object x, Object y) { 975 return (x == null)? y == null: x.equals(y); 976 } 977 public int hashCode() { 978 return thisClass.hashCode(); 979 } 980 public int compareTo(Object o) { 981 InnerClass that = (InnerClass)o; 982 return this.thisClass.compareTo(that.thisClass); 983 } 984 985 protected void visitRefs(int mode, Collection<Entry> refs) { 986 refs.add(thisClass); 987 if (mode == VRM_CLASSIC || !predictable) { 988 // If the name can be demangled, the package omits 989 // the products of demangling. Otherwise, include them. 990 refs.add(outerClass); 991 refs.add(name); 992 } 993 } 994 995 public String toString() { 996 return thisClass.stringValue(); 997 } 998 } 999 1000 // Helper for building InnerClasses attributes. 1001 static private 1002 void visitInnerClassRefs(Collection<InnerClass> innerClasses, int mode, Collection<Entry> refs) { 1003 if (innerClasses == null) { 1004 return; // no attribute; nothing to do 1005 } 1006 if (mode == VRM_CLASSIC) { 1007 refs.add(getRefString("InnerClasses")); 1008 } 1009 if (innerClasses.size() > 0) { 1010 // Count the entries themselves: 1011 for (InnerClass c : innerClasses) { 1012 c.visitRefs(mode, refs); 1013 } 1014 } 1015 } 1016 1017 static String[] parseInnerClassName(String n) { 1018 //System.out.println("parseInnerClassName "+n); 1019 String pkgOuter, number, name; 1020 int dollar1, dollar2; // pointers to $ in the pattern 1021 // parse n = (<pkg>/)*<outer>($<number>)?($<name>)? 1022 int nlen = n.length(); 1023 int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, n.length()) + 1; 1024 dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, n.length()); 1025 if (dollar2 < pkglen) return null; 1026 if (isDigitString(n, dollar2+1, nlen)) { 1027 // n = (<pkg>/)*<outer>$<number> 1028 number = n.substring(dollar2+1, nlen); 1029 name = null; 1030 dollar1 = dollar2; 1031 } else if ((dollar1 1032 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, dollar2-1)) 1033 > pkglen 1034 && isDigitString(n, dollar1+1, dollar2)) { 1035 // n = (<pkg>/)*<outer>$<number>$<name> 1036 number = n.substring(dollar1+1, dollar2); 1037 name = n.substring(dollar2+1, nlen).intern(); 1038 } else { 1039 // n = (<pkg>/)*<outer>$<name> 1040 dollar1 = dollar2; 1041 number = null; 1042 name = n.substring(dollar2+1, nlen).intern(); 1043 } 1044 if (number == null) 1045 pkgOuter = n.substring(0, dollar1).intern(); 1046 else 1047 pkgOuter = null; 1048 //System.out.println("parseInnerClassName parses "+pkgOuter+" "+number+" "+name); 1049 return new String[] { pkgOuter, number, name }; 1050 } 1051 1052 private static final int SLASH_MIN = '.'; 1053 private static final int SLASH_MAX = '/'; 1054 private static final int DOLLAR_MIN = 0; 1055 private static final int DOLLAR_MAX = '-'; 1056 static { 1057 assert(lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, "x$$y$", 4) == 2); 1058 assert(lastIndexOf(SLASH_MIN, SLASH_MAX, "x//y/", 4) == 2); 1059 } 1060 1061 private static int lastIndexOf(int chMin, int chMax, String str, int pos) { 1062 for (int i = pos; --i >= 0; ) { 1063 int ch = str.charAt(i); 1064 if (ch >= chMin && ch <= chMax) { 1065 return i; 1066 } 1067 } 1068 return -1; 1069 } 1070 1071 private static boolean isDigitString(String x, int beg, int end) { 1072 if (beg == end) return false; // null string 1073 for (int i = beg; i < end; i++) { 1074 char ch = x.charAt(i); 1075 if (!(ch >= '0' && ch <= '9')) return false; 1076 } 1077 return true; 1078 } 1079 1080 static String getObviousSourceFile(String className) { 1081 String n = className; 1082 int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, n.length()) + 1; 1083 n = n.substring(pkglen); 1084 int cutoff = n.length(); 1085 for (;;) { 1086 // Work backwards, finding all '$', '#', etc. 1087 int dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, cutoff-1); 1088 if (dollar2 < 0) 1089 break; 1090 cutoff = dollar2; 1091 if (cutoff == 0) 1092 break; 1093 } 1094 String obvious = n.substring(0, cutoff)+".java"; 1095 return obvious; 1096 } 1097 /* 1098 static { 1099 assert(getObviousSourceFile("foo").equals("foo.java")); 1100 assert(getObviousSourceFile("foo/bar").equals("bar.java")); 1101 assert(getObviousSourceFile("foo/bar$baz").equals("bar.java")); 1102 assert(getObviousSourceFile("foo/bar#baz#1").equals("bar.java")); 1103 assert(getObviousSourceFile("foo.bar.baz#1").equals("baz.java")); 1104 } 1105 */ 1106 1107 static Utf8Entry getRefString(String s) { 1108 return ConstantPool.getUtf8Entry(s); 1109 } 1110 1111 static LiteralEntry getRefLiteral(Comparable s) { 1112 return ConstantPool.getLiteralEntry(s); 1113 } 1114 1115 void stripAttributeKind(String what) { 1116 // what is one of { Debug, Compile, Constant, Exceptions, InnerClasses } 1117 if (verbose > 0) 1118 Utils.log.info("Stripping "+what.toLowerCase()+" data and attributes..."); 1119 switch (what) { 1120 case "Debug": 1121 strip("SourceFile"); 1122 strip("LineNumberTable"); 1123 strip("LocalVariableTable"); 1124 strip("LocalVariableTypeTable"); 1125 break; 1126 case "Compile": 1127 // Keep the inner classes normally. 1128 // Although they have no effect on execution, 1129 // the Reflection API exposes them, and JCK checks them. 1130 // NO: // strip("InnerClasses"); 1131 strip("Deprecated"); 1132 strip("Synthetic"); 1133 break; 1134 case "Exceptions": 1135 // Keep the exceptions normally. 1136 // Although they have no effect on execution, 1137 // the Reflection API exposes them, and JCK checks them. 1138 strip("Exceptions"); 1139 break; 1140 case "Constant": 1141 stripConstantFields(); 1142 break; 1143 } 1144 } 1145 1146 public void trimToSize() { 1147 classes.trimToSize(); 1148 for (Class c : classes) { 1149 c.trimToSize(); 1150 } 1151 files.trimToSize(); 1152 } 1153 1154 public void strip(String attrName) { 1155 for (Class c : classes) { 1156 c.strip(attrName); 1157 } 1158 } 1159 1160 public static String versionStringOf(int majver, int minver) { 1161 return majver+"."+minver; 1162 } 1163 public static String versionStringOf(int version) { 1164 return versionStringOf(version >>> 16, (char)version); 1165 } 1166 1167 public void stripConstantFields() { 1168 for (Class c : classes) { 1169 for (Iterator<Class.Field> j = c.fields.iterator(); j.hasNext(); ) { 1170 Class.Field f = j.next(); 1171 if (Modifier.isFinal(f.flags) 1172 // do not strip non-static finals: 1173 && Modifier.isStatic(f.flags) 1174 && f.getAttribute("ConstantValue") != null 1175 && !f.getName().startsWith("serial")) { 1176 if (verbose > 2) { 1177 Utils.log.fine(">> Strip "+this+" ConstantValue"); 1178 j.remove(); 1179 } 1180 } 1181 } 1182 } 1183 } 1184 1185 protected void visitRefs(int mode, Collection<Entry> refs) { 1186 for ( Class c : classes) { 1187 c.visitRefs(mode, refs); 1188 } 1189 if (mode != VRM_CLASSIC) { 1190 for (File f : files) { 1191 f.visitRefs(mode, refs); 1192 } 1193 visitInnerClassRefs(allInnerClasses, mode, refs); 1194 } 1195 } 1196 1197 // Use this before writing the package file. 1198 // It sorts files into a new order which seems likely to 1199 // compress better. It also moves classes to the end of the 1200 // file order. It also removes JAR directory entries, which 1201 // are useless. 1202 @SuppressWarnings("unchecked") 1203 void reorderFiles(boolean keepClassOrder, boolean stripDirectories) { 1204 // First reorder the classes, if that is allowed. 1205 if (!keepClassOrder) { 1206 // In one test with rt.jar, this trick gained 0.7% 1207 Collections.sort(classes); 1208 } 1209 1210 // Remove stubs from resources; maybe we'll add them on at the end, 1211 // if there are some non-trivial ones. The best case is that 1212 // modtimes and options are not transmitted, and the stub files 1213 // for class files do not need to be transmitted at all. 1214 // Also 1215 List<File> stubs = getClassStubs(); 1216 for (Iterator<File> i = files.iterator(); i.hasNext(); ) { 1217 File file = i.next(); 1218 if (file.isClassStub() || 1219 (stripDirectories && file.isDirectory())) { 1220 i.remove(); 1221 } 1222 } 1223 1224 // Sort the remaining non-class files. 1225 // We sort them by file type. 1226 // This keeps files of similar format near each other. 1227 // Put class files at the end, keeping their fixed order. 1228 // Be sure the JAR file's required manifest stays at the front. (4893051) 1229 Collections.sort(files, new Comparator() { 1230 public int compare(Object o0, Object o1) { 1231 File r0 = (File) o0; 1232 File r1 = (File) o1; 1233 // Get the file name. 1234 String f0 = r0.nameString; 1235 String f1 = r1.nameString; 1236 if (f0.equals(f1)) return 0; 1237 if (JarFile.MANIFEST_NAME.equals(f0)) return 0-1; 1238 if (JarFile.MANIFEST_NAME.equals(f1)) return 1-0; 1239 // Extract file basename. 1240 String n0 = f0.substring(1+f0.lastIndexOf('/')); 1241 String n1 = f1.substring(1+f1.lastIndexOf('/')); 1242 // Extract basename extension. 1243 String x0 = n0.substring(1+n0.lastIndexOf('.')); 1244 String x1 = n1.substring(1+n1.lastIndexOf('.')); 1245 int r; 1246 // Primary sort key is file extension. 1247 r = x0.compareTo(x1); 1248 if (r != 0) return r; 1249 r = f0.compareTo(f1); 1250 return r; 1251 } 1252 }); 1253 1254 // Add back the class stubs after sorting, before trimStubs. 1255 files.addAll(stubs); 1256 } 1257 1258 void trimStubs() { 1259 // Restore enough non-trivial stubs to carry the needed class modtimes. 1260 for (ListIterator<File> i = files.listIterator(files.size()); i.hasPrevious(); ) { 1261 File file = i.previous(); 1262 if (!file.isTrivialClassStub()) { 1263 if (verbose > 1) 1264 Utils.log.fine("Keeping last non-trivial "+file); 1265 break; 1266 } 1267 if (verbose > 2) 1268 Utils.log.fine("Removing trivial "+file); 1269 i.remove(); 1270 } 1271 1272 if (verbose > 0) { 1273 Utils.log.info("Transmitting "+files.size()+" files, including per-file data for "+getClassStubs().size()+" classes out of "+classes.size()); 1274 } 1275 } 1276 1277 // Use this before writing the package file. 1278 void buildGlobalConstantPool(Set<Entry> requiredEntries) { 1279 if (verbose > 1) 1280 Utils.log.fine("Checking for unused CP entries"); 1281 requiredEntries.add(getRefString("")); // uconditionally present 1282 visitRefs(VRM_PACKAGE, requiredEntries); 1283 ConstantPool.completeReferencesIn(requiredEntries, false); 1284 if (verbose > 1) 1285 Utils.log.fine("Sorting CP entries"); 1286 Index cpAllU = ConstantPool.makeIndex("unsorted", requiredEntries); 1287 Index[] byTagU = ConstantPool.partitionByTag(cpAllU); 1288 for (int i = 0; i < ConstantPool.TAGS_IN_ORDER.length; i++) { 1289 byte tag = ConstantPool.TAGS_IN_ORDER[i]; 1290 // Work on all entries of a given kind. 1291 Index ix = byTagU[tag]; 1292 if (ix == null) continue; 1293 ConstantPool.sort(ix); 1294 cp.initIndexByTag(tag, ix); 1295 byTagU[tag] = null; // done with it 1296 } 1297 for (int i = 0; i < byTagU.length; i++) { 1298 assert(byTagU[i] == null); // all consumed 1299 } 1300 for (int i = 0; i < ConstantPool.TAGS_IN_ORDER.length; i++) { 1301 byte tag = ConstantPool.TAGS_IN_ORDER[i]; 1302 Index ix = cp.getIndexByTag(tag); 1303 assert(ix.assertIsSorted()); 1304 if (verbose > 2) Utils.log.fine(ix.dumpString()); 1305 } 1306 } 1307 1308 // Use this before writing the class files. 1309 void ensureAllClassFiles() { 1310 Set<File> fileSet = new HashSet<>(files); 1311 for (Class cls : classes) { 1312 // Add to the end of ths list: 1313 if (!fileSet.contains(cls.file)) 1314 files.add(cls.file); 1315 } 1316 } 1317 1318 static final List<Object> noObjects = Arrays.asList(new Object[0]); 1319 static final List<Class.Field> noFields = Arrays.asList(new Class.Field[0]); 1320 static final List<Class.Method> noMethods = Arrays.asList(new Class.Method[0]); 1321 static final List<InnerClass> noInnerClasses = Arrays.asList(new InnerClass[0]); 1322 }