0001: /*
0002: * Copyright 2001-2005 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025:
0026: package com.sun.java.util.jar.pack;
0027:
0028: import java.lang.reflect.Modifier;
0029: import java.util.*;
0030: import java.util.zip.*;
0031: import java.util.jar.*;
0032: import java.io.*;
0033: import com.sun.java.util.jar.pack.ConstantPool.*;
0034:
0035: /**
0036: * Define the main data structure transmitted by pack/unpack.
0037: * @author John Rose
0038: * @version 1.34, 05/05/07
0039: */
0040: class Package implements Constants {
0041: int verbose;
0042: {
0043: PropMap pmap = Utils.currentPropMap();
0044: if (pmap != null)
0045: verbose = pmap.getInteger(Utils.DEBUG_VERBOSE);
0046: }
0047:
0048: int magic;
0049: int package_minver;
0050: int package_majver;
0051:
0052: int default_modtime = NO_MODTIME;
0053: int default_options = 0; // FO_DEFLATE_HINT
0054:
0055: short default_class_majver = -1; // fill in later
0056: short default_class_minver = 0; // fill in later
0057:
0058: // These fields can be adjusted by driver properties.
0059: short min_class_majver = JAVA_MIN_CLASS_MAJOR_VERSION;
0060: short min_class_minver = JAVA_MIN_CLASS_MINOR_VERSION;
0061: short max_class_majver = JAVA6_MAX_CLASS_MAJOR_VERSION;
0062: short max_class_minver = JAVA6_MAX_CLASS_MINOR_VERSION;
0063:
0064: short observed_max_class_majver = min_class_majver;
0065: short observed_max_class_minver = min_class_minver;
0066:
0067: // What constants are used in this unit?
0068: ConstantPool.IndexGroup cp = new ConstantPool.IndexGroup();
0069:
0070: Package() {
0071: magic = JAVA_PACKAGE_MAGIC;
0072: package_minver = -1; // fill in later
0073: package_majver = 0; // fill in later
0074: }
0075:
0076: public void reset() {
0077: cp = new ConstantPool.IndexGroup();
0078: classes.clear();
0079: files.clear();
0080: }
0081:
0082: int getPackageVersion() {
0083: return (package_majver << 16) + (int) package_minver;
0084: }
0085:
0086: // Special empty versions of Code and InnerClasses, used for markers.
0087: public static final Attribute.Layout attrCodeEmpty;
0088: public static final Attribute.Layout attrInnerClassesEmpty;
0089: public static final Attribute.Layout attrSourceFileSpecial;
0090: public static final Map attrDefs;
0091: static {
0092: HashMap ad = new HashMap(2);
0093: attrCodeEmpty = Attribute.define(ad, ATTR_CONTEXT_METHOD,
0094: "Code", "").layout();
0095: attrInnerClassesEmpty = Attribute.define(ad,
0096: ATTR_CONTEXT_CLASS, "InnerClasses", "").layout();
0097: attrSourceFileSpecial = Attribute.define(ad,
0098: ATTR_CONTEXT_CLASS, "SourceFile", "RUNH").layout();
0099: attrDefs = Collections.unmodifiableMap(ad);
0100: }
0101:
0102: int getDefaultClassVersion() {
0103: return (default_class_majver << 16)
0104: + (char) default_class_minver;
0105: }
0106:
0107: /** Return the highest version number of all classes,
0108: * or 0 if there are no classes.
0109: */
0110: int getHighestClassVersion() {
0111: int res = 0; // initial low value
0112: for (Iterator i = classes.iterator(); i.hasNext();) {
0113: Class cls = (Class) i.next();
0114: int ver = cls.getVersion();
0115: if (res < ver)
0116: res = ver;
0117: }
0118: return res;
0119: }
0120:
0121: /** Convenience function to choose an archive version based
0122: * on the class file versions observed within the archive.
0123: */
0124: void choosePackageVersion() {
0125: assert (package_majver <= 0); // do not call this twice
0126: int classver = getHighestClassVersion();
0127: if (classver != 0
0128: && (classver >>> 16) < JAVA6_MAX_CLASS_MAJOR_VERSION) {
0129: // There are only old classfiles in this segment.
0130: package_majver = JAVA5_PACKAGE_MAJOR_VERSION;
0131: package_minver = JAVA5_PACKAGE_MINOR_VERSION;
0132: } else {
0133: // Normal case. Use the newest archive format.
0134: package_majver = JAVA6_PACKAGE_MAJOR_VERSION;
0135: package_minver = JAVA6_PACKAGE_MINOR_VERSION;
0136: }
0137: }
0138:
0139: // What Java classes are in this unit?
0140:
0141: // Fixed 6211177, converted to throw IOException
0142: void checkVersion() throws IOException {
0143: if (magic != JAVA_PACKAGE_MAGIC) {
0144: String gotMag = Integer.toHexString(magic);
0145: String expMag = Integer.toHexString(JAVA_PACKAGE_MAGIC);
0146: throw new IOException(
0147: "Unexpected package magic number: got " + gotMag
0148: + "; expected " + expMag);
0149: }
0150: if ((package_majver != JAVA6_PACKAGE_MAJOR_VERSION && package_majver != JAVA5_PACKAGE_MAJOR_VERSION)
0151: || (package_minver != JAVA6_PACKAGE_MINOR_VERSION && package_minver != JAVA5_PACKAGE_MINOR_VERSION)) {
0152:
0153: String gotVer = package_majver + "." + package_minver;
0154: String expVer = JAVA6_PACKAGE_MAJOR_VERSION + "."
0155: + JAVA6_PACKAGE_MINOR_VERSION + " OR "
0156: + JAVA5_PACKAGE_MAJOR_VERSION + "."
0157: + JAVA5_PACKAGE_MINOR_VERSION;
0158: throw new IOException(
0159: "Unexpected package minor version: got " + gotVer
0160: + "; expected " + expVer);
0161: }
0162: }
0163:
0164: ArrayList classes = new ArrayList();
0165:
0166: public List getClasses() {
0167: return classes;
0168: }
0169:
0170: public class Class extends Attribute.Holder implements Comparable {
0171: public Package getPackage() {
0172: return Package.this ;
0173: }
0174:
0175: // Optional file characteristics and data source (a "class stub")
0176: File file;
0177:
0178: // File header
0179: int magic;
0180: short minver, majver;
0181:
0182: // Local constant pool (one-way mapping of index => package cp).
0183: Entry[] cpMap;
0184:
0185: // Class header
0186: //int flags; // in Attribute.Holder.this.flags
0187: ClassEntry this Class;
0188: ClassEntry super Class;
0189: ClassEntry[] interfaces;
0190:
0191: // Class parts
0192: ArrayList fields;
0193: ArrayList methods;
0194: //ArrayList attributes; // in Attribute.Holder.this.attributes
0195: // Note that InnerClasses may be collected at the package level.
0196: ArrayList innerClasses;
0197:
0198: Class(int flags, ClassEntry this Class, ClassEntry super Class,
0199: ClassEntry[] interfaces) {
0200: this .magic = JAVA_MAGIC;
0201: this .minver = default_class_minver;
0202: this .majver = default_class_majver;
0203: this .flags = flags;
0204: this .this Class = this Class;
0205: this .super Class = super Class;
0206: this .interfaces = interfaces;
0207:
0208: boolean added = classes.add(this );
0209: assert (added);
0210: }
0211:
0212: Class(String classFile) {
0213: // A blank class; must be read with a ClassReader, etc.
0214: initFile(newStub(classFile));
0215: }
0216:
0217: List getFields() {
0218: return fields == null ? noFields : fields;
0219: }
0220:
0221: List getMethods() {
0222: return methods == null ? noMethods : methods;
0223: }
0224:
0225: public String getName() {
0226: return this Class.stringValue();
0227: }
0228:
0229: int getVersion() {
0230: return (majver << 16) + (char) minver;
0231: }
0232:
0233: String getVersionString() {
0234: return versionStringOf(majver, minver);
0235: }
0236:
0237: // Note: equals and hashCode are identity-based.
0238: public int compareTo(Object o) {
0239: Class that = (Class) o;
0240: String n0 = this .getName();
0241: String n1 = that.getName();
0242: return n0.compareTo(n1);
0243: }
0244:
0245: String getObviousSourceFile() {
0246: return Package.getObviousSourceFile(getName());
0247: }
0248:
0249: private void transformSourceFile(boolean minimize) {
0250: // Replace "obvious" SourceFile by null.
0251: Attribute olda = getAttribute(attrSourceFileSpecial);
0252: if (olda == null)
0253: return; // no SourceFile attr.
0254: String obvious = getObviousSourceFile();
0255: ArrayList ref = new ArrayList(1);
0256: olda.visitRefs(this , VRM_PACKAGE, ref);
0257: Utf8Entry sfName = (Utf8Entry) ref.get(0);
0258: Attribute a = olda;
0259: if (sfName == null) {
0260: if (minimize) {
0261: // A pair of zero bytes. Cannot use predef. layout.
0262: a = Attribute.find(ATTR_CONTEXT_CLASS,
0263: "SourceFile", "H");
0264: a = a.addContent(new byte[2]);
0265: } else {
0266: // Expand null attribute to the obvious string.
0267: byte[] bytes = new byte[2];
0268: sfName = getRefString(obvious);
0269: Object f = null;
0270: f = Fixups.add(f, bytes, 0, Fixups.U2_FORMAT,
0271: sfName);
0272: a = attrSourceFileSpecial.addContent(bytes, f);
0273: }
0274: } else if (obvious.equals(sfName.stringValue())) {
0275: if (minimize) {
0276: // Replace by an all-zero attribute.
0277: a = attrSourceFileSpecial.addContent(new byte[2]);
0278: } else {
0279: assert (false);
0280: }
0281: }
0282: if (a != olda) {
0283: if (verbose > 2)
0284: Utils.log.fine("recoding obvious SourceFile="
0285: + obvious);
0286: List newAttrs = new ArrayList(getAttributes());
0287: int where = newAttrs.indexOf(olda);
0288: newAttrs.set(where, a);
0289: setAttributes(newAttrs);
0290: }
0291: }
0292:
0293: void minimizeSourceFile() {
0294: transformSourceFile(true);
0295: }
0296:
0297: void expandSourceFile() {
0298: transformSourceFile(false);
0299: }
0300:
0301: protected Entry[] getCPMap() {
0302: return cpMap;
0303: }
0304:
0305: protected void setCPMap(Entry[] cpMap) {
0306: this .cpMap = cpMap;
0307: }
0308:
0309: boolean hasInnerClasses() {
0310: return innerClasses != null;
0311: }
0312:
0313: List getInnerClasses() {
0314: return innerClasses;
0315: }
0316:
0317: public void setInnerClasses(Collection ics) {
0318: innerClasses = (ics == null) ? null : new ArrayList(ics);
0319: // Edit the attribute list, if necessary.
0320: Attribute a = getAttribute(attrInnerClassesEmpty);
0321: if (innerClasses != null && a == null)
0322: addAttribute(attrInnerClassesEmpty.canonicalInstance());
0323: else if (innerClasses == null && a != null)
0324: removeAttribute(a);
0325: }
0326:
0327: /** Given a global map of ICs (keyed by thisClass),
0328: * compute the subset of its Map.values which are
0329: * required to be present in the local InnerClasses
0330: * attribute. Perform this calculation without
0331: * reference to any actual InnerClasses attribute.
0332: * <p>
0333: * The order of the resulting list is consistent
0334: * with that of Package.this.allInnerClasses.
0335: */
0336: public List computeGloballyImpliedICs() {
0337: HashSet cpRefs = new HashSet();
0338: { // This block temporarily displaces this.innerClasses.
0339: ArrayList innerClassesSaved = innerClasses;
0340: innerClasses = null; // ignore for the moment
0341: visitRefs(VRM_CLASSIC, cpRefs);
0342: innerClasses = innerClassesSaved;
0343: }
0344: ConstantPool.completeReferencesIn(cpRefs, true);
0345:
0346: HashSet icRefs = new HashSet();
0347: for (Iterator i = cpRefs.iterator(); i.hasNext();) {
0348: Entry e = (Entry) i.next();
0349: // Restrict cpRefs to InnerClasses entries only.
0350: if (!(e instanceof ClassEntry))
0351: continue;
0352: // For every IC reference, add its outers also.
0353: while (e != null) {
0354: InnerClass ic = getGlobalInnerClass(e);
0355: if (ic == null)
0356: break;
0357: if (!icRefs.add(e))
0358: break;
0359: e = ic.outerClass;
0360: // If we add A$B$C to the mix, we must also add A$B.
0361: }
0362: }
0363: // This loop is structured this way so as to accumulate
0364: // entries into impliedICs in an order which reflects
0365: // the order of allInnerClasses.
0366: ArrayList impliedICs = new ArrayList();
0367: for (Iterator i = allInnerClasses.iterator(); i.hasNext();) {
0368: InnerClass ic = (InnerClass) i.next();
0369: // This one is locally relevant if it describes
0370: // a member of the current class, or if the current
0371: // class uses it somehow. In the particular case
0372: // where thisClass is an inner class, it will already
0373: // be a member of icRefs.
0374: if (icRefs.contains(ic.this Class)
0375: || ic.outerClass == this .this Class) {
0376: // Add every relevant class to the IC attribute:
0377: if (verbose > 1)
0378: Utils.log.fine("Relevant IC: " + ic);
0379: impliedICs.add(ic);
0380: }
0381: }
0382: return impliedICs;
0383: }
0384:
0385: // Helper for both minimizing and expanding.
0386: // Computes a symmetric difference.
0387: private List computeICdiff() {
0388: List impliedICs = computeGloballyImpliedICs();
0389: List actualICs = getInnerClasses();
0390: if (actualICs == null)
0391: actualICs = Collections.EMPTY_LIST;
0392:
0393: // Symmetric difference is calculated from I, A like this:
0394: // diff = (I+A) - (I*A)
0395: // Note that the center C is unordered, but the result
0396: // preserves the original ordering of I and A.
0397: //
0398: // Class file rules require that outers precede inners.
0399: // So, add I before A, in case A$B$Z is local, but A$B
0400: // is implicit. The reverse is never the case.
0401: if (actualICs.isEmpty()) {
0402: return impliedICs;
0403: // Diff is I since A is empty.
0404: }
0405: if (impliedICs.isEmpty()) {
0406: return actualICs;
0407: // Diff is A since I is empty.
0408: }
0409: // (I*A) is non-trivial
0410: HashSet center = new HashSet(actualICs);
0411: center.retainAll(new HashSet(impliedICs));
0412: impliedICs.addAll(actualICs);
0413: impliedICs.removeAll(center);
0414: // Diff is now I^A = (I+A)-(I*A).
0415: return impliedICs;
0416: }
0417:
0418: /** When packing, anticipate the effect of expandLocalICs.
0419: * Replace the local ICs by their symmetric difference
0420: * with the globally implied ICs for this class; if this
0421: * difference is empty, remove the local ICs altogether.
0422: * <p>
0423: * An empty local IC attribute is reserved to signal
0424: * the unpacker to delete the attribute altogether,
0425: * so a missing local IC attribute signals the unpacker
0426: * to use the globally implied ICs changed.
0427: */
0428: void minimizeLocalICs() {
0429: List diff = computeICdiff();
0430: List actualICs = innerClasses;
0431: List localICs; // will be the diff, modulo edge cases
0432: if (diff.isEmpty()) {
0433: // No diff, so transmit no attribute.
0434: localICs = null;
0435: if (actualICs != null && actualICs.isEmpty()) {
0436: // Odd case: No implied ICs, and a zero length attr.
0437: // Do not support it directly.
0438: if (verbose > 0)
0439: Utils.log
0440: .info("Warning: Dropping empty InnerClasses attribute from "
0441: + this );
0442: }
0443: } else if (actualICs == null) {
0444: // No local IC attribute, even though some are implied.
0445: // Signal with trivial attribute.
0446: localICs = Collections.EMPTY_LIST;
0447: } else {
0448: // Transmit a non-empty diff, which will create
0449: // a local ICs attribute.
0450: localICs = diff;
0451: }
0452: // Reduce the set to the symmetric difference.
0453: setInnerClasses(localICs);
0454: if (verbose > 1 && localICs != null)
0455: Utils.log.fine("keeping local ICs in " + this + ": "
0456: + localICs);
0457: }
0458:
0459: /** When unpacking, undo the effect of minimizeLocalICs.
0460: * Must return negative if any IC tuples may have been deleted.
0461: * Otherwise, return positive if any IC tuples were added.
0462: */
0463: int expandLocalICs() {
0464: List localICs = innerClasses;
0465: List actualICs;
0466: int changed;
0467: if (localICs == null) {
0468: // Diff was empty. (Common case.)
0469: List impliedICs = computeGloballyImpliedICs();
0470: if (impliedICs.isEmpty()) {
0471: actualICs = null;
0472: changed = 0;
0473: } else {
0474: actualICs = impliedICs;
0475: changed = 1; // added more tuples
0476: }
0477: } else if (localICs.isEmpty()) {
0478: // It was a non-empty diff, but the local ICs were absent.
0479: actualICs = null;
0480: changed = 0; // [] => null, no tuple change
0481: } else {
0482: // Non-trivial diff was transmitted.
0483: actualICs = computeICdiff();
0484: // If we only added more ICs, return +1.
0485: changed = actualICs.containsAll(localICs) ? +1 : -1;
0486: }
0487: setInnerClasses(actualICs);
0488: return changed;
0489: }
0490:
0491: public abstract class Member extends Attribute.Holder implements
0492: Comparable {
0493: DescriptorEntry descriptor;
0494:
0495: protected Member(int flags, DescriptorEntry descriptor) {
0496: this .flags = flags;
0497: this .descriptor = descriptor;
0498: }
0499:
0500: public Class this Class() {
0501: return Class.this ;
0502: }
0503:
0504: public DescriptorEntry getDescriptor() {
0505: return descriptor;
0506: }
0507:
0508: public String getName() {
0509: return descriptor.nameRef.stringValue();
0510: }
0511:
0512: public String getType() {
0513: return descriptor.typeRef.stringValue();
0514: }
0515:
0516: protected Entry[] getCPMap() {
0517: return cpMap;
0518: }
0519:
0520: protected void visitRefs(int mode, Collection refs) {
0521: if (verbose > 2)
0522: Utils.log.fine("visitRefs " + this );
0523: // Careful: The descriptor is used by the package,
0524: // but the classfile breaks it into component refs.
0525: if (mode == VRM_CLASSIC) {
0526: refs.add(descriptor.nameRef);
0527: refs.add(descriptor.typeRef);
0528: } else {
0529: refs.add(descriptor);
0530: }
0531: // Handle attribute list:
0532: super .visitRefs(mode, refs);
0533: }
0534:
0535: public String toString() {
0536: return Class.this + "." + descriptor.prettyString();
0537: }
0538: }
0539:
0540: public class Field extends Member {
0541: // Order is significant for fields: It is visible to reflection.
0542: int order;
0543:
0544: public Field(int flags, DescriptorEntry descriptor) {
0545: super (flags, descriptor);
0546: assert (!descriptor.isMethod());
0547: if (fields == null)
0548: fields = new ArrayList();
0549: boolean added = fields.add(this );
0550: assert (added);
0551: order = fields.size();
0552: }
0553:
0554: public byte getLiteralTag() {
0555: return descriptor.getLiteralTag();
0556: }
0557:
0558: public int compareTo(Object o) {
0559: Field that = (Field) o;
0560: return this .order - that.order;
0561: }
0562: }
0563:
0564: public class Method extends Member {
0565: // Code attribute is specially hardwired.
0566: Code code;
0567:
0568: public Method(int flags, DescriptorEntry descriptor) {
0569: super (flags, descriptor);
0570: assert (descriptor.isMethod());
0571: if (methods == null)
0572: methods = new ArrayList();
0573: boolean added = methods.add(this );
0574: assert (added);
0575: }
0576:
0577: public void trimToSize() {
0578: super .trimToSize();
0579: if (code != null)
0580: code.trimToSize();
0581: }
0582:
0583: public int getArgumentSize() {
0584: int argSize = descriptor.typeRef.computeSize(true);
0585: int this Size = Modifier.isStatic(flags) ? 0 : 1;
0586: return this Size + argSize;
0587: }
0588:
0589: // Sort methods in a canonical order (by type, then by name).
0590: public int compareTo(Object o) {
0591: Method that = (Method) o;
0592: return this .getDescriptor().compareTo(
0593: that.getDescriptor());
0594: }
0595:
0596: public void strip(String attrName) {
0597: if (attrName == "Code")
0598: code = null;
0599: if (code != null)
0600: code.strip(attrName);
0601: super .strip(attrName);
0602: }
0603:
0604: protected void visitRefs(int mode, Collection refs) {
0605: super .visitRefs(mode, refs);
0606: if (code != null) {
0607: if (mode == VRM_CLASSIC) {
0608: refs.add(getRefString("Code"));
0609: }
0610: code.visitRefs(mode, refs);
0611: }
0612: }
0613: }
0614:
0615: public void trimToSize() {
0616: super .trimToSize();
0617: for (int isM = 0; isM <= 1; isM++) {
0618: ArrayList members = (isM == 0) ? fields : methods;
0619: if (members == null)
0620: continue;
0621: members.trimToSize();
0622: for (Iterator i = members.iterator(); i.hasNext();) {
0623: Member m = (Member) i.next();
0624: m.trimToSize();
0625: }
0626: }
0627: if (innerClasses != null) {
0628: innerClasses.trimToSize();
0629: }
0630: }
0631:
0632: public void strip(String attrName) {
0633: if (attrName == "InnerClass")
0634: innerClasses = null;
0635: for (int isM = 0; isM <= 1; isM++) {
0636: ArrayList members = (isM == 0) ? fields : methods;
0637: if (members == null)
0638: continue;
0639: for (Iterator i = members.iterator(); i.hasNext();) {
0640: Member m = (Member) i.next();
0641: m.strip(attrName);
0642: }
0643: }
0644: super .strip(attrName);
0645: }
0646:
0647: protected void visitRefs(int mode, Collection refs) {
0648: if (verbose > 2)
0649: Utils.log.fine("visitRefs " + this );
0650: refs.add(this Class);
0651: refs.add(super Class);
0652: for (int i = 0; i < interfaces.length; i++) {
0653: refs.add(interfaces[i]);
0654: }
0655: for (int isM = 0; isM <= 1; isM++) {
0656: ArrayList members = (isM == 0) ? fields : methods;
0657: if (members == null)
0658: continue;
0659: for (Iterator i = members.iterator(); i.hasNext();) {
0660: Member m = (Member) i.next();
0661: boolean ok = false;
0662: try {
0663: m.visitRefs(mode, refs);
0664: ok = true;
0665: } finally {
0666: if (!ok)
0667: Utils.log.warning("Error scanning " + m);
0668: }
0669: }
0670: }
0671: visitInnerClassRefs(mode, refs);
0672: // Handle attribute list:
0673: super .visitRefs(mode, refs);
0674: }
0675:
0676: protected void visitInnerClassRefs(int mode, Collection refs) {
0677: Package.visitInnerClassRefs(innerClasses, mode, refs);
0678: }
0679:
0680: // Hook called by ClassReader when it's done.
0681: void finishReading() {
0682: trimToSize();
0683: maybeChooseFileName();
0684: }
0685:
0686: public void initFile(File file) {
0687: assert (this .file == null); // set-once
0688: if (file == null) {
0689: // Build a trivial stub.
0690: file = newStub(canonicalFileName());
0691: }
0692: this .file = file;
0693: assert (file.isClassStub());
0694: file.stubClass = this ;
0695: maybeChooseFileName();
0696: }
0697:
0698: public void maybeChooseFileName() {
0699: if (this Class == null) {
0700: return; // do not choose yet
0701: }
0702: String canonName = canonicalFileName();
0703: if (file.nameString.equals("")) {
0704: file.nameString = canonName;
0705: }
0706: if (file.nameString.equals(canonName)) {
0707: // The file name is predictable. Transmit "".
0708: file.name = getRefString("");
0709: return;
0710: }
0711: // If name has not yet been looked up, find it now.
0712: if (file.name == null) {
0713: file.name = getRefString(file.nameString);
0714: }
0715: }
0716:
0717: public String canonicalFileName() {
0718: if (this Class == null)
0719: return null;
0720: return this Class.stringValue() + ".class";
0721: }
0722:
0723: public java.io.File getFileName(java.io.File parent) {
0724: String name = file.name.stringValue();
0725: if (name.equals(""))
0726: name = canonicalFileName();
0727: String fname = name
0728: .replace('/', java.io.File.separatorChar);
0729: return new java.io.File(parent, fname);
0730: }
0731:
0732: public java.io.File getFileName() {
0733: return getFileName(null);
0734: }
0735:
0736: public String toString() {
0737: return this Class.stringValue();
0738: }
0739: }
0740:
0741: void addClass(Class c) {
0742: assert (c.getPackage() == this );
0743: boolean added = classes.add(c);
0744: assert (added);
0745: // Make sure the class is represented in the total file order:
0746: if (c.file == null)
0747: c.initFile(null);
0748: addFile(c.file);
0749: }
0750:
0751: // What non-class files are in this unit?
0752: ArrayList files = new ArrayList();
0753:
0754: public List getFiles() {
0755: return files;
0756: }
0757:
0758: public List getClassStubs() {
0759: ArrayList classStubs = new ArrayList(classes.size());
0760: for (Iterator i = classes.iterator(); i.hasNext();) {
0761: Class cls = (Class) i.next();
0762: assert (cls.file.isClassStub());
0763: classStubs.add(cls.file);
0764: }
0765: return classStubs;
0766: }
0767:
0768: public class File implements Comparable {
0769: String nameString; // true name of this file
0770: Utf8Entry name;
0771: int modtime = NO_MODTIME;
0772: int options = 0; // random flag bits, such as deflate_hint
0773: Class stubClass; // if this is a stub, here's the class
0774: ArrayList prepend = new ArrayList(); // list of byte[]
0775: java.io.ByteArrayOutputStream append = new ByteArrayOutputStream();
0776:
0777: File(Utf8Entry name) {
0778: this .name = name;
0779: this .nameString = name.stringValue();
0780: // caller must fill in contents
0781: }
0782:
0783: File(String nameString) {
0784: nameString = fixupFileName(nameString);
0785: this .name = getRefString(nameString);
0786: this .nameString = name.stringValue();
0787: }
0788:
0789: public boolean isDirectory() {
0790: // JAR directory. Useless.
0791: return nameString.endsWith("/");
0792: }
0793:
0794: public boolean isClassStub() {
0795: return (options & FO_IS_CLASS_STUB) != 0;
0796: }
0797:
0798: public Class getStubClass() {
0799: assert (isClassStub());
0800: assert (stubClass != null);
0801: return stubClass;
0802: }
0803:
0804: public boolean isTrivialClassStub() {
0805: return isClassStub()
0806: && name.stringValue().equals("")
0807: && (modtime == NO_MODTIME || modtime == default_modtime)
0808: && (options & ~FO_IS_CLASS_STUB) == 0;
0809: }
0810:
0811: // The nameString is the key. Ignore other things.
0812: // (Note: The name might be "", in the case of a trivial class stub.)
0813: public boolean equals(Object o) {
0814: File that = (File) o;
0815: return that.nameString == this .nameString;
0816: }
0817:
0818: public int hashCode() {
0819: return nameString.hashCode();
0820: }
0821:
0822: // Simple alphabetic sort. PackageWriter uses a better comparator.
0823: public int compareTo(Object o) {
0824: File that = (File) o;
0825: return this .nameString.compareTo(that.nameString);
0826: }
0827:
0828: public String toString() {
0829: return nameString
0830: + "{"
0831: + (isClassStub() ? "*" : "")
0832: + (BandStructure.testBit(options, FO_DEFLATE_HINT) ? "@"
0833: : "")
0834: + (modtime == NO_MODTIME ? "" : "M" + modtime)
0835: + (getFileLength() == 0 ? "" : "["
0836: + getFileLength() + "]") + "}";
0837: }
0838:
0839: public java.io.File getFileName() {
0840: return getFileName(null);
0841: }
0842:
0843: public java.io.File getFileName(java.io.File parent) {
0844: String name = this .nameString;
0845: //if (name.startsWith("./")) name = name.substring(2);
0846: String fname = name
0847: .replace('/', java.io.File.separatorChar);
0848: return new java.io.File(parent, fname);
0849: }
0850:
0851: public void addBytes(byte[] bytes) {
0852: addBytes(bytes, 0, bytes.length);
0853: }
0854:
0855: public void addBytes(byte[] bytes, int off, int len) {
0856: if (((append.size() | len) << 2) < 0) {
0857: prepend.add(append.toByteArray());
0858: append.reset();
0859: }
0860: append.write(bytes, off, len);
0861: }
0862:
0863: public long getFileLength() {
0864: long len = 0;
0865: if (prepend == null && append == null)
0866: return 0;
0867: for (Iterator i = prepend.iterator(); i.hasNext();) {
0868: byte[] block = (byte[]) i.next();
0869: len += block.length;
0870: }
0871: len += append.size();
0872: return len;
0873: }
0874:
0875: public void writeTo(OutputStream out) throws IOException {
0876: if (prepend == null && append == null)
0877: return;
0878: for (Iterator i = prepend.iterator(); i.hasNext();) {
0879: byte[] block = (byte[]) i.next();
0880: out.write(block);
0881: }
0882: append.writeTo(out);
0883: }
0884:
0885: public void readFrom(InputStream in) throws IOException {
0886: byte[] buf = new byte[1 << 16];
0887: int nr;
0888: while ((nr = in.read(buf)) > 0) {
0889: addBytes(buf, 0, nr);
0890: }
0891: }
0892:
0893: public InputStream getInputStream() {
0894: InputStream in = new ByteArrayInputStream(append
0895: .toByteArray());
0896: if (prepend.size() == 0)
0897: return in;
0898: ArrayList isa = new ArrayList(prepend.size() + 1);
0899: for (Iterator i = prepend.iterator(); i.hasNext();) {
0900: byte[] bytes = (byte[]) i.next();
0901: isa.add(new ByteArrayInputStream(bytes));
0902: }
0903: isa.add(in);
0904: return new SequenceInputStream(Collections.enumeration(isa));
0905: }
0906:
0907: protected void visitRefs(int mode, Collection refs) {
0908: assert (name != null);
0909: refs.add(name);
0910: }
0911: }
0912:
0913: File newStub(String classFileNameString) {
0914: File stub = new File(classFileNameString);
0915: stub.options |= FO_IS_CLASS_STUB;
0916: stub.prepend = null;
0917: stub.append = null; // do not collect data
0918: return stub;
0919: }
0920:
0921: private static String fixupFileName(String name) {
0922: String fname = name.replace(java.io.File.separatorChar, '/');
0923: if (fname.startsWith("/")) {
0924: throw new IllegalArgumentException("absolute file name "
0925: + fname);
0926: }
0927: return fname;
0928: }
0929:
0930: void addFile(File file) {
0931: boolean added = files.add(file);
0932: assert (added);
0933: }
0934:
0935: // Is there a globally declared table of inner classes?
0936: ArrayList allInnerClasses = new ArrayList();
0937: HashMap allInnerClassesByThis;
0938:
0939: public List getAllInnerClasses() {
0940: return allInnerClasses;
0941: }
0942:
0943: public void setAllInnerClasses(Collection ics) {
0944: assert (ics != allInnerClasses);
0945: allInnerClasses.clear();
0946: allInnerClasses.addAll(ics);
0947:
0948: // Make an index:
0949: allInnerClassesByThis = new HashMap(allInnerClasses.size());
0950: for (Iterator i = allInnerClasses.iterator(); i.hasNext();) {
0951: InnerClass ic = (InnerClass) i.next();
0952: Object pic = allInnerClassesByThis.put(ic.this Class, ic);
0953: assert (pic == null); // caller must ensure key uniqueness!
0954: }
0955: }
0956:
0957: /** Return a global inner class record for the given thisClass. */
0958: public InnerClass getGlobalInnerClass(Entry this Class) {
0959: assert (this Class instanceof ClassEntry);
0960: return (InnerClass) allInnerClassesByThis.get(this Class);
0961: }
0962:
0963: static class InnerClass implements Comparable {
0964: final ClassEntry this Class;
0965: final ClassEntry outerClass;
0966: final Utf8Entry name;
0967: final int flags;
0968:
0969: // Can name and outerClass be derived from thisClass?
0970: final boolean predictable;
0971:
0972: // About 30% of inner classes are anonymous (in rt.jar).
0973: // About 60% are class members; the rest are named locals.
0974: // Nearly all have predictable outers and names.
0975:
0976: InnerClass(ClassEntry this Class, ClassEntry outerClass,
0977: Utf8Entry name, int flags) {
0978: this .this Class = this Class;
0979: this .outerClass = outerClass;
0980: this .name = name;
0981: this .flags = flags;
0982: this .predictable = computePredictable();
0983: }
0984:
0985: private boolean computePredictable() {
0986: //System.out.println("computePredictable "+outerClass+" "+this.name);
0987: String[] parse = parseInnerClassName(this Class
0988: .stringValue());
0989: if (parse == null)
0990: return false;
0991: String pkgOuter = parse[0];
0992: //String number = parse[1];
0993: String name = parse[2];
0994: String haveName = (this .name == null) ? null : this .name
0995: .stringValue();
0996: String haveOuter = (outerClass == null) ? null : outerClass
0997: .stringValue();
0998: boolean predictable = (name == haveName && pkgOuter == haveOuter);
0999: //System.out.println("computePredictable => "+predictable);
1000: return predictable;
1001: }
1002:
1003: public boolean equals(Object o) {
1004: if (o == null)
1005: return false;
1006: InnerClass that = (InnerClass) o;
1007: return eq(this .this Class, that.this Class)
1008: && eq(this .outerClass, that.outerClass)
1009: && eq(this .name, that.name)
1010: && this .flags == that.flags;
1011: }
1012:
1013: private static boolean eq(Object x, Object y) {
1014: return (x == null) ? y == null : x.equals(y);
1015: }
1016:
1017: public int hashCode() {
1018: return this Class.hashCode();
1019: }
1020:
1021: public int compareTo(Object o) {
1022: InnerClass that = (InnerClass) o;
1023: return this .this Class.compareTo(that.this Class);
1024: }
1025:
1026: protected void visitRefs(int mode, Collection refs) {
1027: refs.add(this Class);
1028: if (mode == VRM_CLASSIC || !predictable) {
1029: // If the name can be demangled, the package omits
1030: // the products of demangling. Otherwise, include them.
1031: refs.add(outerClass);
1032: refs.add(name);
1033: }
1034: }
1035:
1036: public String toString() {
1037: return this Class.stringValue();
1038: }
1039: }
1040:
1041: // Helper for building InnerClasses attributes.
1042: static private void visitInnerClassRefs(Collection innerClasses,
1043: int mode, Collection refs) {
1044: if (innerClasses == null) {
1045: return; // no attribute; nothing to do
1046: }
1047: if (mode == VRM_CLASSIC) {
1048: refs.add(getRefString("InnerClasses"));
1049: }
1050: if (innerClasses.size() > 0) {
1051: // Count the entries themselves:
1052: for (Iterator i = innerClasses.iterator(); i.hasNext();) {
1053: InnerClass c = (InnerClass) i.next();
1054: c.visitRefs(mode, refs);
1055: }
1056: }
1057: }
1058:
1059: static String[] parseInnerClassName(String n) {
1060: //System.out.println("parseInnerClassName "+n);
1061: String pkgOuter, number, name;
1062: int dollar1, dollar2; // pointers to $ in the pattern
1063: // parse n = (<pkg>/)*<outer>($<number>)?($<name>)?
1064: int nlen = n.length();
1065: int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, n.length()) + 1;
1066: dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, n.length());
1067: if (dollar2 < pkglen)
1068: return null;
1069: if (isDigitString(n, dollar2 + 1, nlen)) {
1070: // n = (<pkg>/)*<outer>$<number>
1071: number = n.substring(dollar2 + 1, nlen);
1072: name = null;
1073: dollar1 = dollar2;
1074: } else if ((dollar1 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n,
1075: dollar2 - 1)) > pkglen
1076: && isDigitString(n, dollar1 + 1, dollar2)) {
1077: // n = (<pkg>/)*<outer>$<number>$<name>
1078: number = n.substring(dollar1 + 1, dollar2);
1079: name = n.substring(dollar2 + 1, nlen).intern();
1080: } else {
1081: // n = (<pkg>/)*<outer>$<name>
1082: dollar1 = dollar2;
1083: number = null;
1084: name = n.substring(dollar2 + 1, nlen).intern();
1085: }
1086: if (number == null)
1087: pkgOuter = n.substring(0, dollar1).intern();
1088: else
1089: pkgOuter = null;
1090: //System.out.println("parseInnerClassName parses "+pkgOuter+" "+number+" "+name);
1091: return new String[] { pkgOuter, number, name };
1092: }
1093:
1094: private static final int SLASH_MIN = '.';
1095: private static final int SLASH_MAX = '/';
1096: private static final int DOLLAR_MIN = 0;
1097: private static final int DOLLAR_MAX = '-';
1098: static {
1099: assert (lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, "x$$y$", 4) == 2);
1100: assert (lastIndexOf(SLASH_MIN, SLASH_MAX, "x//y/", 4) == 2);
1101: }
1102:
1103: private static int lastIndexOf(int chMin, int chMax, String str,
1104: int pos) {
1105: for (int i = pos; --i >= 0;) {
1106: int ch = str.charAt(i);
1107: if (ch >= chMin && ch <= chMax) {
1108: return i;
1109: }
1110: }
1111: return -1;
1112: }
1113:
1114: private static boolean isDigitString(String x, int beg, int end) {
1115: if (beg == end)
1116: return false; // null string
1117: for (int i = beg; i < end; i++) {
1118: char ch = x.charAt(i);
1119: if (!(ch >= '0' && ch <= '9'))
1120: return false;
1121: }
1122: return true;
1123: }
1124:
1125: static String getObviousSourceFile(String className) {
1126: String n = className;
1127: int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, n.length()) + 1;
1128: n = n.substring(pkglen);
1129: int cutoff = n.length();
1130: for (;;) {
1131: // Work backwards, finding all '$', '#', etc.
1132: int dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n,
1133: cutoff - 1);
1134: if (dollar2 < 0)
1135: break;
1136: cutoff = dollar2;
1137: if (cutoff == 0)
1138: break;
1139: }
1140: String obvious = n.substring(0, cutoff) + ".java";
1141: return obvious;
1142: }
1143:
1144: /*
1145: static {
1146: assert(getObviousSourceFile("foo").equals("foo.java"));
1147: assert(getObviousSourceFile("foo/bar").equals("bar.java"));
1148: assert(getObviousSourceFile("foo/bar$baz").equals("bar.java"));
1149: assert(getObviousSourceFile("foo/bar#baz#1").equals("bar.java"));
1150: assert(getObviousSourceFile("foo.bar.baz#1").equals("baz.java"));
1151: }
1152: */
1153:
1154: static Utf8Entry getRefString(String s) {
1155: return ConstantPool.getUtf8Entry(s);
1156: }
1157:
1158: static LiteralEntry getRefLiteral(Comparable s) {
1159: return ConstantPool.getLiteralEntry(s);
1160: }
1161:
1162: void stripAttributeKind(String what) {
1163: // what is one of { Debug, Compile, Constant, Exceptions, InnerClasses }
1164: if (verbose > 0)
1165: Utils.log.info("Stripping " + what.toLowerCase()
1166: + " data and attributes...");
1167: if (what == "Debug") {
1168: strip("SourceFile");
1169: strip("LineNumberTable");
1170: strip("LocalVariableTable");
1171: strip("LocalVariableTypeTable");
1172: }
1173: if (what == "Compile") {
1174: // Keep the inner classes normally.
1175: // Although they have no effect on execution,
1176: // the Reflection API exposes them, and JCK checks them.
1177: // NO: // strip("InnerClasses");
1178: strip("Deprecated");
1179: strip("Synthetic");
1180: }
1181: if (what == "Exceptions") {
1182: // Keep the exceptions normally.
1183: // Although they have no effect on execution,
1184: // the Reflection API exposes them, and JCK checks them.
1185: strip("Exceptions");
1186: }
1187: if (what == "Constant") {
1188: stripConstantFields();
1189: }
1190: }
1191:
1192: public void trimToSize() {
1193: classes.trimToSize();
1194: for (Iterator i = classes.iterator(); i.hasNext();) {
1195: Class c = (Class) i.next();
1196: c.trimToSize();
1197: }
1198: files.trimToSize();
1199: }
1200:
1201: public void strip(String attrName) {
1202: for (Iterator i = classes.iterator(); i.hasNext();) {
1203: Class c = (Class) i.next();
1204: c.strip(attrName);
1205: }
1206: }
1207:
1208: public static String versionStringOf(int majver, int minver) {
1209: return majver + "." + minver;
1210: }
1211:
1212: public static String versionStringOf(int version) {
1213: return versionStringOf(version >>> 16, (char) version);
1214: }
1215:
1216: public void stripConstantFields() {
1217: for (Iterator i = classes.iterator(); i.hasNext();) {
1218: Class c = (Class) i.next();
1219: for (Iterator j = c.fields.iterator(); j.hasNext();) {
1220: Class.Field f = (Class.Field) j.next();
1221: if (Modifier.isFinal(f.flags)
1222: // do not strip non-static finals:
1223: && Modifier.isStatic(f.flags)
1224: && f.getAttribute("ConstantValue") != null
1225: && !f.getName().startsWith("serial")) {
1226: if (verbose > 2) {
1227: Utils.log.fine(">> Strip " + this
1228: + " ConstantValue");
1229: j.remove();
1230: }
1231: }
1232: }
1233: }
1234: }
1235:
1236: protected void visitRefs(int mode, Collection refs) {
1237: for (Iterator i = classes.iterator(); i.hasNext();) {
1238: Class c = (Class) i.next();
1239: c.visitRefs(mode, refs);
1240: }
1241: if (mode != VRM_CLASSIC) {
1242: for (Iterator i = files.iterator(); i.hasNext();) {
1243: File f = (File) i.next();
1244: f.visitRefs(mode, refs);
1245: }
1246: visitInnerClassRefs(allInnerClasses, mode, refs);
1247: }
1248: }
1249:
1250: // Use this before writing the package file.
1251: // It sorts files into a new order which seems likely to
1252: // compress better. It also moves classes to the end of the
1253: // file order. It also removes JAR directory entries, which
1254: // are useless.
1255: void reorderFiles(boolean keepClassOrder, boolean stripDirectories) {
1256: // First reorder the classes, if that is allowed.
1257: if (!keepClassOrder) {
1258: // In one test with rt.jar, this trick gained 0.7%
1259: Collections.sort(classes);
1260: }
1261:
1262: // Remove stubs from resources; maybe we'll add them on at the end,
1263: // if there are some non-trivial ones. The best case is that
1264: // modtimes and options are not transmitted, and the stub files
1265: // for class files do not need to be transmitted at all.
1266: // Also
1267: List stubs = getClassStubs();
1268: for (Iterator i = files.iterator(); i.hasNext();) {
1269: File file = (File) i.next();
1270: if (file.isClassStub()
1271: || (stripDirectories && file.isDirectory())) {
1272: i.remove();
1273: }
1274: }
1275:
1276: // Sort the remaining non-class files.
1277: // We sort them by file type.
1278: // This keeps files of similar format near each other.
1279: // Put class files at the end, keeping their fixed order.
1280: // Be sure the JAR file's required manifest stays at the front. (4893051)
1281: Collections.sort(files, new Comparator() {
1282: public int compare(Object o0, Object o1) {
1283: File r0 = (File) o0;
1284: File r1 = (File) o1;
1285: // Get the file name.
1286: String f0 = r0.nameString;
1287: String f1 = r1.nameString;
1288: if (f0.equals(f1))
1289: return 0;
1290: if (JarFile.MANIFEST_NAME.equals(f0))
1291: return 0 - 1;
1292: if (JarFile.MANIFEST_NAME.equals(f1))
1293: return 1 - 0;
1294: // Extract file basename.
1295: String n0 = f0.substring(1 + f0.lastIndexOf('/'));
1296: String n1 = f1.substring(1 + f1.lastIndexOf('/'));
1297: // Extract basename extension.
1298: String x0 = n0.substring(1 + n0.lastIndexOf('.'));
1299: String x1 = n1.substring(1 + n1.lastIndexOf('.'));
1300: int r;
1301: // Primary sort key is file extension.
1302: r = x0.compareTo(x1);
1303: if (r != 0)
1304: return r;
1305: r = f0.compareTo(f1);
1306: return r;
1307: }
1308: });
1309:
1310: // Add back the class stubs after sorting, before trimStubs.
1311: files.addAll(stubs);
1312: }
1313:
1314: void trimStubs() {
1315: // Restore enough non-trivial stubs to carry the needed class modtimes.
1316: for (ListIterator i = files.listIterator(files.size()); i
1317: .hasPrevious();) {
1318: File file = (File) i.previous();
1319: if (!file.isTrivialClassStub()) {
1320: if (verbose > 1)
1321: Utils.log.fine("Keeping last non-trivial " + file);
1322: break;
1323: }
1324: if (verbose > 2)
1325: Utils.log.fine("Removing trivial " + file);
1326: i.remove();
1327: }
1328:
1329: if (verbose > 0) {
1330: Utils.log.info("Transmitting " + files.size()
1331: + " files, including per-file data for "
1332: + getClassStubs().size() + " classes out of "
1333: + classes.size());
1334: }
1335: }
1336:
1337: // Use this before writing the package file.
1338: void buildGlobalConstantPool(Set requiredEntries) {
1339: if (verbose > 1)
1340: Utils.log.fine("Checking for unused CP entries");
1341: requiredEntries.add(getRefString("")); // uconditionally present
1342: visitRefs(VRM_PACKAGE, requiredEntries);
1343: ConstantPool.completeReferencesIn(requiredEntries, false);
1344: if (verbose > 1)
1345: Utils.log.fine("Sorting CP entries");
1346: Index cpAllU = ConstantPool.makeIndex("unsorted",
1347: requiredEntries);
1348: Index[] byTagU = ConstantPool.partitionByTag(cpAllU);
1349: for (int i = 0; i < ConstantPool.TAGS_IN_ORDER.length; i++) {
1350: byte tag = ConstantPool.TAGS_IN_ORDER[i];
1351: // Work on all entries of a given kind.
1352: Index ix = byTagU[tag];
1353: if (ix == null)
1354: continue;
1355: ConstantPool.sort(ix);
1356: cp.initIndexByTag(tag, ix);
1357: byTagU[tag] = null; // done with it
1358: }
1359: for (int i = 0; i < byTagU.length; i++) {
1360: assert (byTagU[i] == null); // all consumed
1361: }
1362: for (int i = 0; i < ConstantPool.TAGS_IN_ORDER.length; i++) {
1363: byte tag = ConstantPool.TAGS_IN_ORDER[i];
1364: Index ix = cp.getIndexByTag(tag);
1365: assert (ix.assertIsSorted());
1366: if (verbose > 2)
1367: Utils.log.fine(ix.dumpString());
1368: }
1369: }
1370:
1371: // Use this before writing the class files.
1372: void ensureAllClassFiles() {
1373: HashSet fileSet = new HashSet(files);
1374: for (Iterator i = classes.iterator(); i.hasNext();) {
1375: Class cls = (Class) i.next();
1376: // Add to the end of ths list:
1377: if (!fileSet.contains(cls.file))
1378: files.add(cls.file);
1379: }
1380: }
1381:
1382: static final List noObjects = Arrays.asList(new Object[0]);
1383: static final List noFields = Arrays.asList(new Class.Field[0]);
1384: static final List noMethods = Arrays.asList(new Class.Method[0]);
1385: static final List noInnerClasses = Arrays.asList(new InnerClass[0]);
1386: }
|