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 }