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