Package.java revision 15559:fc1be68dffc8
1/*
2 * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
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                // [] => null, no tuple change, but attribute deletion.
480                changed = -1;
481            } else {
482                // Non-trivial diff was transmitted.
483                actualICs = computeICdiff();
484                // If we only added more ICs, return +1.
485                changed = actualICs.containsAll(localICs)? +1: -1;
486            }
487            setInnerClasses(actualICs);
488            return changed;
489        }
490
491        public abstract
492        class Member extends Attribute.Holder implements Comparable<Member> {
493            DescriptorEntry descriptor;
494
495            protected Member(int flags, DescriptorEntry descriptor) {
496                this.flags = flags;
497                this.descriptor = descriptor;
498            }
499
500            public Class thisClass() { return Class.this; }
501
502            public DescriptorEntry getDescriptor() {
503                return descriptor;
504            }
505            public String getName() {
506                return descriptor.nameRef.stringValue();
507            }
508            public String getType() {
509                return descriptor.typeRef.stringValue();
510            }
511
512            protected Entry[] getCPMap() {
513                return cpMap;
514            }
515            protected void visitRefs(int mode, Collection<Entry> refs) {
516                if (verbose > 2)  Utils.log.fine("visitRefs "+this);
517                // Careful:  The descriptor is used by the package,
518                // but the classfile breaks it into component refs.
519                if (mode == VRM_CLASSIC) {
520                    refs.add(descriptor.nameRef);
521                    refs.add(descriptor.typeRef);
522                } else {
523                    refs.add(descriptor);
524                }
525                // Handle attribute list:
526                super.visitRefs(mode, refs);
527            }
528
529            public String toString() {
530                return Class.this + "." + descriptor.prettyString();
531            }
532        }
533
534        public
535        class Field extends Member {
536            // Order is significant for fields:  It is visible to reflection.
537            int order;
538
539            public Field(int flags, DescriptorEntry descriptor) {
540                super(flags, descriptor);
541                assert(!descriptor.isMethod());
542                if (fields == null)
543                    fields = new ArrayList<>();
544                boolean added = fields.add(this);
545                assert(added);
546                order = fields.size();
547            }
548
549            public byte getLiteralTag() {
550                return descriptor.getLiteralTag();
551            }
552
553            public int compareTo(Member o) {
554                Field that = (Field)o;
555                return this.order - that.order;
556            }
557        }
558
559        public
560        class Method extends Member {
561            // Code attribute is specially hardwired.
562            Code code;
563
564            public Method(int flags, DescriptorEntry descriptor) {
565                super(flags, descriptor);
566                assert(descriptor.isMethod());
567                if (methods == null)
568                    methods = new ArrayList<>();
569                boolean added = methods.add(this);
570                assert(added);
571            }
572
573            public void trimToSize() {
574                super.trimToSize();
575                if (code != null)
576                    code.trimToSize();
577            }
578
579            public int getArgumentSize() {
580                int argSize  = descriptor.typeRef.computeSize(true);
581                int thisSize = Modifier.isStatic(flags) ? 0 : 1;
582                return thisSize + argSize;
583            }
584
585            // Sort methods in a canonical order (by type, then by name).
586            public int compareTo(Member o) {
587                Method that = (Method)o;
588                return this.getDescriptor().compareTo(that.getDescriptor());
589            }
590
591            public void strip(String attrName) {
592                if ("Code".equals(attrName))
593                    code = null;
594                if (code != null)
595                    code.strip(attrName);
596                super.strip(attrName);
597            }
598            protected void visitRefs(int mode, Collection<Entry> refs) {
599                super.visitRefs(mode, refs);
600                if (code != null) {
601                    if (mode == VRM_CLASSIC) {
602                        refs.add(getRefString("Code"));
603                    }
604                    code.visitRefs(mode, refs);
605                }
606            }
607        }
608
609        public void trimToSize() {
610            super.trimToSize();
611            for (int isM = 0; isM <= 1; isM++) {
612                ArrayList<? extends Member> members = (isM == 0) ? fields : methods;
613                if (members == null)  continue;
614                members.trimToSize();
615                for (Member m : members) {
616                    m.trimToSize();
617                }
618            }
619            if (innerClasses != null) {
620                innerClasses.trimToSize();
621            }
622        }
623
624        public void strip(String attrName) {
625            if ("InnerClass".equals(attrName))
626                innerClasses = null;
627            for (int isM = 0; isM <= 1; isM++) {
628                ArrayList<? extends Member> members = (isM == 0) ? fields : methods;
629                if (members == null)  continue;
630                for (Member m : members) {
631                    m.strip(attrName);
632                }
633            }
634            super.strip(attrName);
635        }
636
637        protected void visitRefs(int mode, Collection<Entry> refs) {
638            if (verbose > 2)  Utils.log.fine("visitRefs "+this);
639            refs.add(thisClass);
640            refs.add(superClass);
641            refs.addAll(Arrays.asList(interfaces));
642            for (int isM = 0; isM <= 1; isM++) {
643                ArrayList<? extends Member> members = (isM == 0) ? fields : methods;
644                if (members == null)  continue;
645                for (Member m : members) {
646                    boolean ok = false;
647                    try {
648                        m.visitRefs(mode, refs);
649                        ok = true;
650                    } finally {
651                        if (!ok)
652                            Utils.log.warning("Error scanning "+m);
653                    }
654                }
655            }
656            visitInnerClassRefs(mode, refs);
657            // Handle attribute list:
658            super.visitRefs(mode, refs);
659        }
660
661        protected void visitInnerClassRefs(int mode, Collection<Entry> refs) {
662            Package.visitInnerClassRefs(innerClasses, mode, refs);
663        }
664
665        // Hook called by ClassReader when it's done.
666        void finishReading() {
667            trimToSize();
668            maybeChooseFileName();
669        }
670
671        public void initFile(File file) {
672            assert(this.file == null);  // set-once
673            if (file == null) {
674                // Build a trivial stub.
675                file = newStub(canonicalFileName());
676            }
677            this.file = file;
678            assert(file.isClassStub());
679            file.stubClass = this;
680            maybeChooseFileName();
681        }
682
683        public void maybeChooseFileName() {
684            if (thisClass == null) {
685                return;  // do not choose yet
686            }
687            String canonName = canonicalFileName();
688            if (file.nameString.equals("")) {
689                file.nameString = canonName;
690            }
691            if (file.nameString.equals(canonName)) {
692                // The file name is predictable.  Transmit "".
693                file.name = getRefString("");
694                return;
695            }
696            // If name has not yet been looked up, find it now.
697            if (file.name == null) {
698                file.name = getRefString(file.nameString);
699            }
700        }
701
702        public String canonicalFileName() {
703            if (thisClass == null)  return null;
704            return thisClass.stringValue() + ".class";
705        }
706
707        public java.io.File getFileName(java.io.File parent) {
708            String name = file.name.stringValue();
709            if (name.equals(""))
710                name = canonicalFileName();
711            String fname = name.replace('/', java.io.File.separatorChar);
712            return new java.io.File(parent, fname);
713        }
714        public java.io.File getFileName() {
715            return getFileName(null);
716        }
717
718        public String toString() {
719            return thisClass.stringValue();
720        }
721    }
722
723    void addClass(Class c) {
724        assert(c.getPackage() == this);
725        boolean added = classes.add(c);
726        assert(added);
727        // Make sure the class is represented in the total file order:
728        if (c.file == null)  c.initFile(null);
729        addFile(c.file);
730    }
731
732    // What non-class files are in this unit?
733    ArrayList<File> files = new ArrayList<>();
734
735    public List<File> getFiles() {
736        return files;
737    }
738
739    public List<File> getClassStubs() {
740        List<File> classStubs = new ArrayList<>(classes.size());
741        for (Class cls : classes) {
742            assert(cls.file.isClassStub());
743            classStubs.add(cls.file);
744        }
745        return classStubs;
746    }
747
748    public final class File implements Comparable<File> {
749        String nameString;  // true name of this file
750        Utf8Entry name;
751        int modtime = NO_MODTIME;
752        int options = 0;  // random flag bits, such as deflate_hint
753        Class stubClass;  // if this is a stub, here's the class
754        ArrayList<byte[]> prepend = new ArrayList<>();  // list of byte[]
755        java.io.ByteArrayOutputStream append = new ByteArrayOutputStream();
756
757        File(Utf8Entry name) {
758            this.name = name;
759            this.nameString = name.stringValue();
760            // caller must fill in contents
761        }
762        File(String nameString) {
763            nameString = fixupFileName(nameString);
764            this.name = getRefString(nameString);
765            this.nameString = name.stringValue();
766        }
767
768        public boolean isDirectory() {
769            // JAR directory.  Useless.
770            return nameString.endsWith("/");
771        }
772        public boolean isClassStub() {
773            return (options & FO_IS_CLASS_STUB) != 0;
774        }
775        public Class getStubClass() {
776            assert(isClassStub());
777            assert(stubClass != null);
778            return stubClass;
779        }
780        public boolean isTrivialClassStub() {
781            return isClassStub()
782                && name.stringValue().equals("")
783                && (modtime == NO_MODTIME || modtime == default_modtime)
784                && (options &~ FO_IS_CLASS_STUB) == 0;
785        }
786
787        // The nameString is the key.  Ignore other things.
788        // (Note:  The name might be "", in the case of a trivial class stub.)
789        public boolean equals(Object o) {
790            if (o == null || (o.getClass() != File.class))
791                return false;
792            File that = (File)o;
793            return that.nameString.equals(this.nameString);
794        }
795        public int hashCode() {
796            return nameString.hashCode();
797        }
798        // Simple alphabetic sort.  PackageWriter uses a better comparator.
799        public int compareTo(File that) {
800            return this.nameString.compareTo(that.nameString);
801        }
802        public String toString() {
803            return nameString+"{"
804                +(isClassStub()?"*":"")
805                +(BandStructure.testBit(options,FO_DEFLATE_HINT)?"@":"")
806                +(modtime==NO_MODTIME?"":"M"+modtime)
807                +(getFileLength()==0?"":"["+getFileLength()+"]")
808                +"}";
809        }
810
811        public java.io.File getFileName() {
812            return getFileName(null);
813        }
814        public java.io.File getFileName(java.io.File parent) {
815            String lname = this.nameString;
816            //if (name.startsWith("./"))  name = name.substring(2);
817            String fname = lname.replace('/', java.io.File.separatorChar);
818            return new java.io.File(parent, fname);
819        }
820
821        public void addBytes(byte[] bytes) {
822            addBytes(bytes, 0, bytes.length);
823        }
824        public void addBytes(byte[] bytes, int off, int len) {
825            if (((append.size() | len) << 2) < 0) {
826                prepend.add(append.toByteArray());
827                append.reset();
828            }
829            append.write(bytes, off, len);
830        }
831        public long getFileLength() {
832            long len = 0;
833            if (prepend == null || append == null)  return 0;
834            for (byte[] block : prepend) {
835                len += block.length;
836            }
837            len += append.size();
838            return len;
839        }
840        public void writeTo(OutputStream out) throws IOException {
841            if (prepend == null || append == null)  return;
842            for (byte[] block : prepend) {
843                out.write(block);
844            }
845            append.writeTo(out);
846        }
847        public void readFrom(InputStream in) throws IOException {
848            byte[] buf = new byte[1 << 16];
849            int nr;
850            while ((nr = in.read(buf)) > 0) {
851                addBytes(buf, 0, nr);
852            }
853        }
854        public InputStream getInputStream() {
855            InputStream in = new ByteArrayInputStream(append.toByteArray());
856            if (prepend.isEmpty())  return in;
857            List<InputStream> isa = new ArrayList<>(prepend.size()+1);
858            for (byte[] bytes : prepend) {
859                isa.add(new ByteArrayInputStream(bytes));
860            }
861            isa.add(in);
862            return new SequenceInputStream(Collections.enumeration(isa));
863        }
864
865        protected void visitRefs(int mode, Collection<Entry> refs) {
866            assert(name != null);
867            refs.add(name);
868        }
869    }
870
871    File newStub(String classFileNameString) {
872        File stub = new File(classFileNameString);
873        stub.options |= FO_IS_CLASS_STUB;
874        stub.prepend = null;
875        stub.append = null;  // do not collect data
876        return stub;
877    }
878
879    private static String fixupFileName(String name) {
880        String fname = name.replace(java.io.File.separatorChar, '/');
881        if (fname.startsWith("/")) {
882            throw new IllegalArgumentException("absolute file name "+fname);
883        }
884        return fname;
885    }
886
887    void addFile(File file) {
888        boolean added = files.add(file);
889        assert(added);
890    }
891
892    // Is there a globally declared table of inner classes?
893    List<InnerClass> allInnerClasses = new ArrayList<>();
894    Map<ClassEntry, InnerClass>   allInnerClassesByThis;
895
896    public
897    List<InnerClass> getAllInnerClasses() {
898        return allInnerClasses;
899    }
900
901    public
902    void setAllInnerClasses(Collection<InnerClass> ics) {
903        assert(ics != allInnerClasses);
904        allInnerClasses.clear();
905        allInnerClasses.addAll(ics);
906
907        // Make an index:
908        allInnerClassesByThis = new HashMap<>(allInnerClasses.size());
909        for (InnerClass ic : allInnerClasses) {
910            Object pic = allInnerClassesByThis.put(ic.thisClass, ic);
911            assert(pic == null);  // caller must ensure key uniqueness!
912        }
913    }
914
915    /** Return a global inner class record for the given thisClass. */
916    public
917    InnerClass getGlobalInnerClass(Entry thisClass) {
918        assert(thisClass instanceof ClassEntry);
919        return allInnerClassesByThis.get(thisClass);
920    }
921
922    static
923    class InnerClass implements Comparable<InnerClass> {
924        final ClassEntry thisClass;
925        final ClassEntry outerClass;
926        final Utf8Entry name;
927        final int flags;
928
929        // Can name and outerClass be derived from thisClass?
930        final boolean predictable;
931
932        // About 30% of inner classes are anonymous (in rt.jar).
933        // About 60% are class members; the rest are named locals.
934        // Nearly all have predictable outers and names.
935
936        InnerClass(ClassEntry thisClass, ClassEntry outerClass,
937                   Utf8Entry name, int flags) {
938            this.thisClass = thisClass;
939            this.outerClass = outerClass;
940            this.name = name;
941            this.flags = flags;
942            this.predictable = computePredictable();
943        }
944
945        private boolean computePredictable() {
946            //System.out.println("computePredictable "+outerClass+" "+this.name);
947            String[] parse = parseInnerClassName(thisClass.stringValue());
948            if (parse == null)  return false;
949            String pkgOuter = parse[0];
950            //String number = parse[1];
951            String lname     = parse[2];
952            String haveName  = (this.name == null)  ? null : this.name.stringValue();
953            String haveOuter = (outerClass == null) ? null : outerClass.stringValue();
954            boolean lpredictable = (lname == haveName && pkgOuter == haveOuter);
955            //System.out.println("computePredictable => "+predictable);
956            return lpredictable;
957        }
958
959        public boolean equals(Object o) {
960            if (o == null || o.getClass() != InnerClass.class)
961                return false;
962            InnerClass that = (InnerClass)o;
963            return eq(this.thisClass, that.thisClass)
964                && eq(this.outerClass, that.outerClass)
965                && eq(this.name, that.name)
966                && this.flags == that.flags;
967        }
968        private static boolean eq(Object x, Object y) {
969            return (x == null)? y == null: x.equals(y);
970        }
971        public int hashCode() {
972            return thisClass.hashCode();
973        }
974        public int compareTo(InnerClass that) {
975            return this.thisClass.compareTo(that.thisClass);
976        }
977
978        protected void visitRefs(int mode, Collection<Entry> refs) {
979            refs.add(thisClass);
980            if (mode == VRM_CLASSIC || !predictable) {
981                // If the name can be demangled, the package omits
982                // the products of demangling.  Otherwise, include them.
983                refs.add(outerClass);
984                refs.add(name);
985            }
986        }
987
988        public String toString() {
989            return thisClass.stringValue();
990        }
991    }
992
993    // Helper for building InnerClasses attributes.
994    private static
995    void visitInnerClassRefs(Collection<InnerClass> innerClasses, int mode, Collection<Entry> refs) {
996        if (innerClasses == null) {
997            return;  // no attribute; nothing to do
998        }
999        if (mode == VRM_CLASSIC) {
1000            refs.add(getRefString("InnerClasses"));
1001        }
1002        if (innerClasses.size() > 0) {
1003            // Count the entries themselves:
1004            for (InnerClass c : innerClasses) {
1005                c.visitRefs(mode, refs);
1006            }
1007        }
1008    }
1009
1010    static String[] parseInnerClassName(String n) {
1011        //System.out.println("parseInnerClassName "+n);
1012        String pkgOuter, number, name;
1013        int dollar1, dollar2;  // pointers to $ in the pattern
1014        // parse n = (<pkg>/)*<outer>($<number>)?($<name>)?
1015        int nlen = n.length();
1016        int pkglen = lastIndexOf(SLASH_MIN,  SLASH_MAX,  n, n.length()) + 1;
1017        dollar2    = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, n.length());
1018        if (dollar2 < pkglen)  return null;
1019        if (isDigitString(n, dollar2+1, nlen)) {
1020            // n = (<pkg>/)*<outer>$<number>
1021            number = n.substring(dollar2+1, nlen);
1022            name = null;
1023            dollar1 = dollar2;
1024        } else if ((dollar1
1025                    = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, dollar2-1))
1026                   > pkglen
1027                   && isDigitString(n, dollar1+1, dollar2)) {
1028            // n = (<pkg>/)*<outer>$<number>$<name>
1029            number = n.substring(dollar1+1, dollar2);
1030            name = n.substring(dollar2+1, nlen).intern();
1031        } else {
1032            // n = (<pkg>/)*<outer>$<name>
1033            dollar1 = dollar2;
1034            number = null;
1035            name = n.substring(dollar2+1, nlen).intern();
1036        }
1037        if (number == null)
1038            pkgOuter = n.substring(0, dollar1).intern();
1039        else
1040            pkgOuter = null;
1041        //System.out.println("parseInnerClassName parses "+pkgOuter+" "+number+" "+name);
1042        return new String[] { pkgOuter, number, name };
1043    }
1044
1045    private static final int SLASH_MIN = '.';
1046    private static final int SLASH_MAX = '/';
1047    private static final int DOLLAR_MIN = 0;
1048    private static final int DOLLAR_MAX = '-';
1049    static {
1050        assert(lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, "x$$y$", 4) == 2);
1051        assert(lastIndexOf(SLASH_MIN,  SLASH_MAX,  "x//y/", 4) == 2);
1052    }
1053
1054    private static int lastIndexOf(int chMin, int chMax, String str, int pos) {
1055        for (int i = pos; --i >= 0; ) {
1056            int ch = str.charAt(i);
1057            if (ch >= chMin && ch <= chMax) {
1058                return i;
1059            }
1060        }
1061        return -1;
1062    }
1063
1064    private static boolean isDigitString(String x, int beg, int end) {
1065        if (beg == end)  return false;  // null string
1066        for (int i = beg; i < end; i++) {
1067            char ch = x.charAt(i);
1068            if (!(ch >= '0' && ch <= '9'))  return false;
1069        }
1070        return true;
1071    }
1072
1073    static String getObviousSourceFile(String className) {
1074        String n = className;
1075        int pkglen = lastIndexOf(SLASH_MIN,  SLASH_MAX,  n, n.length()) + 1;
1076        n = n.substring(pkglen);
1077        int cutoff = n.length();
1078        for (;;) {
1079            // Work backwards, finding all '$', '#', etc.
1080            int dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, cutoff-1);
1081            if (dollar2 < 0)
1082                break;
1083            cutoff = dollar2;
1084            if (cutoff == 0)
1085                break;
1086        }
1087        String obvious = n.substring(0, cutoff)+".java";
1088        return obvious;
1089    }
1090/*
1091    static {
1092        assert(getObviousSourceFile("foo").equals("foo.java"));
1093        assert(getObviousSourceFile("foo/bar").equals("bar.java"));
1094        assert(getObviousSourceFile("foo/bar$baz").equals("bar.java"));
1095        assert(getObviousSourceFile("foo/bar#baz#1").equals("bar.java"));
1096        assert(getObviousSourceFile("foo.bar.baz#1").equals("baz.java"));
1097    }
1098*/
1099
1100    static Utf8Entry getRefString(String s) {
1101        return ConstantPool.getUtf8Entry(s);
1102    }
1103
1104    static LiteralEntry getRefLiteral(Comparable<?> s) {
1105        return ConstantPool.getLiteralEntry(s);
1106    }
1107
1108    void stripAttributeKind(String what) {
1109        // what is one of { Debug, Compile, Constant, Exceptions, InnerClasses }
1110        if (verbose > 0)
1111            Utils.log.info("Stripping "+what.toLowerCase()+" data and attributes...");
1112        switch (what) {
1113            case "Debug":
1114                strip("SourceFile");
1115                strip("LineNumberTable");
1116                strip("LocalVariableTable");
1117                strip("LocalVariableTypeTable");
1118                break;
1119            case "Compile":
1120                // Keep the inner classes normally.
1121                // Although they have no effect on execution,
1122                // the Reflection API exposes them, and JCK checks them.
1123                // NO: // strip("InnerClasses");
1124                strip("Deprecated");
1125                strip("Synthetic");
1126                break;
1127            case "Exceptions":
1128                // Keep the exceptions normally.
1129                // Although they have no effect on execution,
1130                // the Reflection API exposes them, and JCK checks them.
1131                strip("Exceptions");
1132                break;
1133            case "Constant":
1134                stripConstantFields();
1135                break;
1136        }
1137    }
1138
1139    public void trimToSize() {
1140        classes.trimToSize();
1141        for (Class c : classes) {
1142            c.trimToSize();
1143        }
1144        files.trimToSize();
1145    }
1146
1147    public void strip(String attrName) {
1148        for (Class c : classes) {
1149            c.strip(attrName);
1150        }
1151    }
1152
1153    public void stripConstantFields() {
1154        for (Class c : classes) {
1155            for (Iterator<Class.Field> j = c.fields.iterator(); j.hasNext(); ) {
1156                Class.Field f = j.next();
1157                if (Modifier.isFinal(f.flags)
1158                    // do not strip non-static finals:
1159                    && Modifier.isStatic(f.flags)
1160                    && f.getAttribute("ConstantValue") != null
1161                    && !f.getName().startsWith("serial")) {
1162                    if (verbose > 2) {
1163                        Utils.log.fine(">> Strip "+this+" ConstantValue");
1164                        j.remove();
1165                    }
1166                }
1167            }
1168        }
1169    }
1170
1171    protected void visitRefs(int mode, Collection<Entry> refs) {
1172        for ( Class c : classes) {
1173            c.visitRefs(mode, refs);
1174        }
1175        if (mode != VRM_CLASSIC) {
1176            for (File f : files) {
1177                f.visitRefs(mode, refs);
1178            }
1179            visitInnerClassRefs(allInnerClasses, mode, refs);
1180        }
1181    }
1182
1183    // Use this before writing the package file.
1184    // It sorts files into a new order which seems likely to
1185    // compress better.  It also moves classes to the end of the
1186    // file order.  It also removes JAR directory entries, which
1187    // are useless.
1188    void reorderFiles(boolean keepClassOrder, boolean stripDirectories) {
1189        // First reorder the classes, if that is allowed.
1190        if (!keepClassOrder) {
1191            // In one test with rt.jar, this trick gained 0.7%
1192            Collections.sort(classes);
1193        }
1194
1195        // Remove stubs from resources; maybe we'll add them on at the end,
1196        // if there are some non-trivial ones.  The best case is that
1197        // modtimes and options are not transmitted, and the stub files
1198        // for class files do not need to be transmitted at all.
1199        // Also
1200        List<File> stubs = getClassStubs();
1201        for (Iterator<File> i = files.iterator(); i.hasNext(); ) {
1202            File file = i.next();
1203            if (file.isClassStub() ||
1204                (stripDirectories && file.isDirectory())) {
1205                i.remove();
1206            }
1207        }
1208
1209        // Sort the remaining non-class files.
1210        // We sort them by file type.
1211        // This keeps files of similar format near each other.
1212        // Put class files at the end, keeping their fixed order.
1213        // Be sure the JAR file's required manifest stays at the front. (4893051)
1214        Collections.sort(files, new Comparator<>() {
1215                public int compare(File r0, File r1) {
1216                    // Get the file name.
1217                    String f0 = r0.nameString;
1218                    String f1 = r1.nameString;
1219                    if (f0.equals(f1)) return 0;
1220                    if (JarFile.MANIFEST_NAME.equals(f0))  return 0-1;
1221                    if (JarFile.MANIFEST_NAME.equals(f1))  return 1-0;
1222                    // Extract file basename.
1223                    String n0 = f0.substring(1+f0.lastIndexOf('/'));
1224                    String n1 = f1.substring(1+f1.lastIndexOf('/'));
1225                    // Extract basename extension.
1226                    String x0 = n0.substring(1+n0.lastIndexOf('.'));
1227                    String x1 = n1.substring(1+n1.lastIndexOf('.'));
1228                    int r;
1229                    // Primary sort key is file extension.
1230                    r = x0.compareTo(x1);
1231                    if (r != 0)  return r;
1232                    r = f0.compareTo(f1);
1233                    return r;
1234                }
1235            });
1236
1237        // Add back the class stubs after sorting, before trimStubs.
1238        files.addAll(stubs);
1239    }
1240
1241    void trimStubs() {
1242        // Restore enough non-trivial stubs to carry the needed class modtimes.
1243        for (ListIterator<File> i = files.listIterator(files.size()); i.hasPrevious(); ) {
1244            File file = i.previous();
1245            if (!file.isTrivialClassStub()) {
1246                if (verbose > 1)
1247                    Utils.log.fine("Keeping last non-trivial "+file);
1248                break;
1249            }
1250            if (verbose > 2)
1251                Utils.log.fine("Removing trivial "+file);
1252            i.remove();
1253        }
1254
1255        if (verbose > 0) {
1256            Utils.log.info("Transmitting "+files.size()+" files, including per-file data for "+getClassStubs().size()+" classes out of "+classes.size());
1257        }
1258    }
1259
1260    // Use this before writing the package file.
1261    void buildGlobalConstantPool(Set<Entry> requiredEntries) {
1262        if (verbose > 1)
1263            Utils.log.fine("Checking for unused CP entries");
1264        requiredEntries.add(getRefString(""));  // uconditionally present
1265        visitRefs(VRM_PACKAGE, requiredEntries);
1266        ConstantPool.completeReferencesIn(requiredEntries, false);
1267        if (verbose > 1)
1268            Utils.log.fine("Sorting CP entries");
1269        Index   cpAllU = ConstantPool.makeIndex("unsorted", requiredEntries);
1270        Index[] byTagU = ConstantPool.partitionByTag(cpAllU);
1271        for (int i = 0; i < ConstantPool.TAGS_IN_ORDER.length; i++) {
1272            byte tag = ConstantPool.TAGS_IN_ORDER[i];
1273            // Work on all entries of a given kind.
1274            Index ix = byTagU[tag];
1275            if (ix == null)  continue;
1276            ConstantPool.sort(ix);
1277            cp.initIndexByTag(tag, ix);
1278            byTagU[tag] = null;  // done with it
1279        }
1280        for (int i = 0; i < byTagU.length; i++) {
1281            Index ix = byTagU[i];
1282            assert(ix == null);  // all consumed
1283        }
1284        for (int i = 0; i < ConstantPool.TAGS_IN_ORDER.length; i++) {
1285            byte tag = ConstantPool.TAGS_IN_ORDER[i];
1286            Index ix = cp.getIndexByTag(tag);
1287            assert(ix.assertIsSorted());
1288            if (verbose > 2)  Utils.log.fine(ix.dumpString());
1289        }
1290    }
1291
1292    // Use this before writing the class files.
1293    void ensureAllClassFiles() {
1294        Set<File> fileSet = new HashSet<>(files);
1295        for (Class cls : classes) {
1296            // Add to the end of ths list:
1297            if (!fileSet.contains(cls.file))
1298                files.add(cls.file);
1299        }
1300    }
1301
1302    static final List<Object> noObjects = Arrays.asList(new Object[0]);
1303    static final List<Class.Field> noFields = Arrays.asList(new Class.Field[0]);
1304    static final List<Class.Method> noMethods = Arrays.asList(new Class.Method[0]);
1305    static final List<InnerClass> noInnerClasses = Arrays.asList(new InnerClass[0]);
1306
1307    protected static final class Version {
1308
1309        public final short major;
1310        public final short minor;
1311
1312        private Version(short major, short minor) {
1313            this.major = major;
1314            this.minor = minor;
1315        }
1316
1317        public String toString() {
1318            return major + "." + minor;
1319        }
1320
1321        public boolean equals(Object that) {
1322            return that instanceof Version
1323                    && major == ((Version)that).major
1324                    && minor == ((Version)that).minor;
1325        }
1326
1327        public int intValue() {
1328            return (major << 16) + minor;
1329        }
1330
1331        public int hashCode() {
1332            return (major << 16) + 7 + minor;
1333        }
1334
1335        public static Version of(int major, int minor) {
1336            return new Version((short)major, (short)minor);
1337        }
1338
1339        public static Version of(byte[] bytes) {
1340           int minor = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF);
1341           int major = ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF);
1342           return new Version((short)major, (short)minor);
1343        }
1344
1345        public static Version of(int major_minor) {
1346            short minor = (short)major_minor;
1347            short major = (short)(major_minor >>> 16);
1348            return new Version(major, minor);
1349        }
1350
1351        public static Version makeVersion(PropMap props, String partialKey) {
1352            int min = props.getInteger(Utils.COM_PREFIX
1353                    + partialKey + ".minver", -1);
1354            int maj = props.getInteger(Utils.COM_PREFIX
1355                    + partialKey + ".majver", -1);
1356            return min >= 0 && maj >= 0 ? Version.of(maj, min) : null;
1357        }
1358        public byte[] asBytes() {
1359            byte[] bytes = {
1360                (byte) (minor >> 8), (byte) minor,
1361                (byte) (major >> 8), (byte) major
1362            };
1363            return bytes;
1364        }
1365        public int compareTo(Version that) {
1366            return this.intValue() - that.intValue();
1367        }
1368
1369        public boolean lessThan(Version that) {
1370            return compareTo(that) < 0 ;
1371        }
1372
1373        public boolean greaterThan(Version that) {
1374            return compareTo(that) > 0 ;
1375        }
1376    }
1377}
1378