Package.java revision 10444:f08705540498
1/*
2 * Copyright (c) 2001, 2013, 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
26package com.sun.java.util.jar.pack;
27
28import java.util.jar.Pack200;
29import com.sun.java.util.jar.pack.Attribute.Layout;
30import com.sun.java.util.jar.pack.ConstantPool.ClassEntry;
31import com.sun.java.util.jar.pack.ConstantPool.DescriptorEntry;
32import com.sun.java.util.jar.pack.ConstantPool.BootstrapMethodEntry;
33import com.sun.java.util.jar.pack.ConstantPool.Index;
34import com.sun.java.util.jar.pack.ConstantPool.LiteralEntry;
35import com.sun.java.util.jar.pack.ConstantPool.Utf8Entry;
36import com.sun.java.util.jar.pack.ConstantPool.Entry;
37import java.io.ByteArrayInputStream;
38import java.io.ByteArrayOutputStream;
39import java.io.IOException;
40import java.io.InputStream;
41import java.io.OutputStream;
42import java.io.SequenceInputStream;
43import java.lang.reflect.Modifier;
44import java.util.ArrayList;
45import java.util.Arrays;
46import java.util.Collection;
47import java.util.Collections;
48import java.util.Comparator;
49import java.util.HashMap;
50import java.util.HashSet;
51import java.util.Iterator;
52import java.util.List;
53import java.util.ListIterator;
54import java.util.Map;
55import java.util.Set;
56import java.util.jar.JarFile;
57import 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 */
63class 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 = 0;  // [] => null, no tuple change
480            } else {
481                // Non-trivial diff was transmitted.
482                actualICs = computeICdiff();
483                // If we only added more ICs, return +1.
484                changed = actualICs.containsAll(localICs)? +1: -1;
485            }
486            setInnerClasses(actualICs);
487            return changed;
488        }
489
490        public abstract
491        class Member extends Attribute.Holder implements Comparable<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    static private
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<File>() {
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}
1377