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