alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (PackageReader.java)

This example Java source code file (PackageReader.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

arraylist, classentry, code, cprefband, descriptorentry, entry, index, intband, ioexception, memberentry, runtimeexception, string, suffix_skip_1, utf8entry, util

The PackageReader.java Java example source code

/*
 * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.java.util.jar.pack;

import com.sun.java.util.jar.pack.ConstantPool.*;
import com.sun.java.util.jar.pack.Package.Class;
import com.sun.java.util.jar.pack.Package.File;
import com.sun.java.util.jar.pack.Package.InnerClass;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.PrintStream;
import java.io.FilterInputStream;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import static com.sun.java.util.jar.pack.Constants.*;

/**
 * Reader for a package file.
 *
 * @see PackageWriter
 * @author John Rose
 */
class PackageReader extends BandStructure {
    Package pkg;
    byte[] bytes;
    LimitedBuffer in;
    Package.Version packageVersion;

    PackageReader(Package pkg, InputStream in) throws IOException {
        this.pkg = pkg;
        this.in = new LimitedBuffer(in);
    }

    /** A buffered input stream which is careful not to
     *  read its underlying stream ahead of a given mark,
     *  called the 'readLimit'.  This property declares
     *  the maximum number of characters that future reads
     *  can consume from the underlying stream.
     */
    static
    class LimitedBuffer extends BufferedInputStream {
        long served;     // total number of charburgers served
        int  servedPos;  // ...as of this value of super.pos
        long limit;      // current declared limit
        long buffered;
        public boolean atLimit() {
            boolean z = (getBytesServed() == limit);
            assert(!z || limit == buffered);
            return z;
        }
        public long getBytesServed() {
            return served + (pos - servedPos);
        }
        public void setReadLimit(long newLimit) {
            if (newLimit == -1)
                limit = -1;
            else
                limit = getBytesServed() + newLimit;
        }
        public long getReadLimit() {
            if (limit == -1)
                return limit;
            else
                return limit - getBytesServed();
        }
        public int read() throws IOException {
            if (pos < count) {
                // fast path
                return buf[pos++] & 0xFF;
            }
            served += (pos - servedPos);
            int ch = super.read();
            servedPos = pos;
            if (ch >= 0)  served += 1;
            assert(served <= limit || limit == -1);
            return ch;
        }
        public int read(byte b[], int off, int len) throws IOException {
            served += (pos - servedPos);
            int nr = super.read(b, off, len);
            servedPos = pos;
            if (nr >= 0)  served += nr;
            //assert(served <= limit || limit == -1);
            return nr;
        }
        public long skip(long n) throws IOException {
            throw new RuntimeException("no skipping");
        }
        LimitedBuffer(InputStream originalIn) {
            super(null, 1<<14);
            servedPos = pos;
            super.in = new FilterInputStream(originalIn) {
                public int read() throws IOException {
                    if (buffered == limit)
                        return -1;
                    ++buffered;
                    return super.read();
                }
                public int read(byte b[], int off, int len) throws IOException {
                    if (buffered == limit)
                        return -1;
                    if (limit != -1) {
                        long remaining = limit - buffered;
                        if (len > remaining)
                            len = (int)remaining;
                    }
                    int nr = super.read(b, off, len);
                    if (nr >= 0)  buffered += nr;
                    return nr;
                }
            };
        }
    }

    void read() throws IOException {
        boolean ok = false;
        try {
            //  pack200_archive:
            //        file_header
            //        *band_headers :BYTE1
            //        cp_bands
            //        attr_definition_bands
            //        ic_bands
            //        class_bands
            //        bc_bands
            //        file_bands
            readFileHeader();
            readBandHeaders();
            readConstantPool();  // cp_bands
            readAttrDefs();
            readInnerClasses();
            Class[] classes = readClasses();
            readByteCodes();
            readFiles();     // file_bands
            assert(archiveSize1 == 0 || in.atLimit());
            assert(archiveSize1 == 0 ||
                   in.getBytesServed() == archiveSize0+archiveSize1);
            all_bands.doneDisbursing();

            // As a post-pass, build constant pools and inner classes.
            for (int i = 0; i < classes.length; i++) {
                reconstructClass(classes[i]);
            }

            ok = true;
        } catch (Exception ee) {
            Utils.log.warning("Error on input: "+ee, ee);
            if (verbose > 0)
                Utils.log.info("Stream offsets:"+
                                 " served="+in.getBytesServed()+
                                 " buffered="+in.buffered+
                                 " limit="+in.limit);
            //if (verbose > 0)  ee.printStackTrace();
            if (ee instanceof IOException)  throw (IOException)ee;
            if (ee instanceof RuntimeException)  throw (RuntimeException)ee;
            throw new Error("error unpacking", ee);
        }
    }

    // Temporary count values, until band decoding gets rolling.
    int[] tagCount = new int[CONSTANT_Limit];
    int numFiles;
    int numAttrDefs;
    int numInnerClasses;
    int numClasses;

    void readFileHeader() throws IOException {
        //  file_header:
        //        archive_magic archive_header
        readArchiveMagic();
        readArchiveHeader();
    }

    // Local routine used to parse fixed-format scalars
    // in the file_header:
    private int getMagicInt32() throws IOException {
        int res = 0;
        for (int i = 0; i < 4; i++) {
            res <<= 8;
            res |= (archive_magic.getByte() & 0xFF);
        }
        return res;
    }

    final static int MAGIC_BYTES = 4;

    void readArchiveMagic() throws IOException {
        // Read a minimum of bytes in the first gulp.
        in.setReadLimit(MAGIC_BYTES + AH_LENGTH_MIN);

        //  archive_magic:
        //        #archive_magic_word :BYTE1[4]
        archive_magic.expectLength(MAGIC_BYTES);
        archive_magic.readFrom(in);

        // read and check magic numbers:
        int magic = getMagicInt32();
        if (pkg.magic != magic) {
            throw new IOException("Unexpected package magic number: got "
                    + magic + "; expected " + pkg.magic);
        }
        archive_magic.doneDisbursing();
    }

     // Fixed 6211177, converted to throw IOException
    void checkArchiveVersion() throws IOException {
        Package.Version versionFound = null;
        for (Package.Version v : new Package.Version[] {
                JAVA8_PACKAGE_VERSION,
                JAVA7_PACKAGE_VERSION,
                JAVA6_PACKAGE_VERSION,
                JAVA5_PACKAGE_VERSION
            }) {
            if (packageVersion.equals(v)) {
                versionFound = v;
                break;
            }
        }
        if (versionFound == null) {
            String expVer =   JAVA8_PACKAGE_VERSION.toString()
                            + "OR"
                            + JAVA7_PACKAGE_VERSION.toString()
                            + " OR "
                            + JAVA6_PACKAGE_VERSION.toString()
                            + " OR "
                            + JAVA5_PACKAGE_VERSION.toString();
            throw new IOException("Unexpected package minor version: got "
                    +  packageVersion.toString() + "; expected " + expVer);
        }
    }

    void readArchiveHeader() throws IOException {
        //  archive_header:
        //        #archive_minver :UNSIGNED5[1]
        //        #archive_majver :UNSIGNED5[1]
        //        #archive_options :UNSIGNED5[1]
        //        (archive_file_counts) ** (#have_file_headers)
        //        (archive_special_counts) ** (#have_special_formats)
        //        cp_counts
        //        class_counts
        //
        //  archive_file_counts:
        //        #archive_size_hi :UNSIGNED5[1]
        //        #archive_size_lo :UNSIGNED5[1]
        //        #archive_next_count :UNSIGNED5[1]
        //        #archive_modtime :UNSIGNED5[1]
        //        #file_count :UNSIGNED5[1]
        //
        //  class_counts:
        //        #ic_count :UNSIGNED5[1]
        //        #default_class_minver :UNSIGNED5[1]
        //        #default_class_majver :UNSIGNED5[1]
        //        #class_count :UNSIGNED5[1]
        //
        //  archive_special_counts:
        //        #band_headers_size :UNSIGNED5[1]
        //        #attr_definition_count :UNSIGNED5[1]
        //
        archive_header_0.expectLength(AH_LENGTH_0);
        archive_header_0.readFrom(in);

        int minver = archive_header_0.getInt();
        int majver = archive_header_0.getInt();
        packageVersion = Package.Version.of(majver, minver);
        checkArchiveVersion();
        this.initHighestClassVersion(JAVA7_MAX_CLASS_VERSION);

        archiveOptions = archive_header_0.getInt();
        archive_header_0.doneDisbursing();

        // detect archive optional fields in archive header
        boolean haveSpecial = testBit(archiveOptions, AO_HAVE_SPECIAL_FORMATS);
        boolean haveFiles   = testBit(archiveOptions, AO_HAVE_FILE_HEADERS);
        boolean haveNumbers = testBit(archiveOptions, AO_HAVE_CP_NUMBERS);
        boolean haveCPExtra = testBit(archiveOptions, AO_HAVE_CP_EXTRAS);
        initAttrIndexLimit();

        // now we are ready to use the data:
        archive_header_S.expectLength(haveFiles? AH_LENGTH_S: 0);
        archive_header_S.readFrom(in);
        if (haveFiles) {
            long sizeHi = archive_header_S.getInt();
            long sizeLo = archive_header_S.getInt();
            archiveSize1 = (sizeHi << 32) + ((sizeLo << 32) >>> 32);
            // Set the limit, now, up to the file_bits.
            in.setReadLimit(archiveSize1);  // for debug only
        } else {
            archiveSize1 = 0;
            in.setReadLimit(-1);  // remove limitation
        }
        archive_header_S.doneDisbursing();
        archiveSize0 = in.getBytesServed();

        int remainingHeaders = AH_LENGTH_MIN - AH_LENGTH_0 - AH_LENGTH_S;
        if (haveFiles)    remainingHeaders += AH_FILE_HEADER_LEN;
        if (haveSpecial)  remainingHeaders += AH_SPECIAL_FORMAT_LEN;
        if (haveNumbers)  remainingHeaders += AH_CP_NUMBER_LEN;
        if (haveCPExtra)  remainingHeaders += AH_CP_EXTRA_LEN;
        archive_header_1.expectLength(remainingHeaders);
        archive_header_1.readFrom(in);

        if (haveFiles) {
            archiveNextCount = archive_header_1.getInt();
            pkg.default_modtime = archive_header_1.getInt();
            numFiles = archive_header_1.getInt();
        } else {
            archiveNextCount = 0;
            numFiles = 0;
        }

        if (haveSpecial) {
            band_headers.expectLength(archive_header_1.getInt());
            numAttrDefs = archive_header_1.getInt();
        } else {
            band_headers.expectLength(0);
            numAttrDefs = 0;
        }

        readConstantPoolCounts(haveNumbers, haveCPExtra);

        numInnerClasses = archive_header_1.getInt();

        minver = (short) archive_header_1.getInt();
        majver = (short) archive_header_1.getInt();
        pkg.defaultClassVersion = Package.Version.of(majver, minver);
        numClasses = archive_header_1.getInt();

        archive_header_1.doneDisbursing();

        // set some derived archive bits
        if (testBit(archiveOptions, AO_DEFLATE_HINT)) {
            pkg.default_options |= FO_DEFLATE_HINT;
        }
    }

    void readBandHeaders() throws IOException {
        band_headers.readFrom(in);
        bandHeaderBytePos = 1;  // Leave room to pushback the initial XB byte.
        bandHeaderBytes = new byte[bandHeaderBytePos + band_headers.length()];
        for (int i = bandHeaderBytePos; i < bandHeaderBytes.length; i++) {
            bandHeaderBytes[i] = (byte) band_headers.getByte();
        }
        band_headers.doneDisbursing();
    }

    void readConstantPoolCounts(boolean haveNumbers, boolean haveCPExtra) throws IOException {
        // size the constant pools:
        for (int k = 0; k < ConstantPool.TAGS_IN_ORDER.length; k++) {
            //  cp_counts:
            //        #cp_Utf8_count :UNSIGNED5[1]
            //        (cp_number_counts) ** (#have_cp_numbers)
            //        #cp_String_count :UNSIGNED5[1]
            //        #cp_Class_count :UNSIGNED5[1]
            //        #cp_Signature_count :UNSIGNED5[1]
            //        #cp_Descr_count :UNSIGNED5[1]
            //        #cp_Field_count :UNSIGNED5[1]
            //        #cp_Method_count :UNSIGNED5[1]
            //        #cp_Imethod_count :UNSIGNED5[1]
            //        (cp_attr_counts) ** (#have_cp_attr_counts)
            //
            //  cp_number_counts:
            //        #cp_Int_count :UNSIGNED5[1]
            //        #cp_Float_count :UNSIGNED5[1]
            //        #cp_Long_count :UNSIGNED5[1]
            //        #cp_Double_count :UNSIGNED5[1]
            //
            //  cp_extra_counts:
            //        #cp_MethodHandle_count :UNSIGNED5[1]
            //        #cp_MethodType_count :UNSIGNED5[1]
            //        #cp_InvokeDynamic_count :UNSIGNED5[1]
            //        #cp_BootstrapMethod_count :UNSIGNED5[1]
            //
            byte tag = ConstantPool.TAGS_IN_ORDER[k];
            if (!haveNumbers) {
                // These four counts are optional.
                switch (tag) {
                case CONSTANT_Integer:
                case CONSTANT_Float:
                case CONSTANT_Long:
                case CONSTANT_Double:
                    continue;
                }
            }
            if (!haveCPExtra) {
                // These four counts are optional.
                switch (tag) {
                case CONSTANT_MethodHandle:
                case CONSTANT_MethodType:
                case CONSTANT_InvokeDynamic:
                case CONSTANT_BootstrapMethod:
                    continue;
                }
            }
            tagCount[tag] = archive_header_1.getInt();
        }
    }

    protected Index getCPIndex(byte tag) {
        return pkg.cp.getIndexByTag(tag);
    }
    Index initCPIndex(byte tag, Entry[] cpMap) {
        if (verbose > 3) {
            for (int i = 0; i < cpMap.length; i++) {
                Utils.log.fine("cp.add "+cpMap[i]);
            }
        }
        Index index = ConstantPool.makeIndex(ConstantPool.tagName(tag), cpMap);
        if (verbose > 1)  Utils.log.fine("Read "+index);
        pkg.cp.initIndexByTag(tag, index);
        return index;
    }

    void checkLegacy(String bandname) {
        if (packageVersion.lessThan(JAVA7_PACKAGE_VERSION)) {
            throw new RuntimeException("unexpected band " + bandname);
        }
    }
    void readConstantPool() throws IOException {
        //  cp_bands:
        //        cp_Utf8
        //        *cp_Int :UDELTA5
        //        *cp_Float :UDELTA5
        //        cp_Long
        //        cp_Double
        //        *cp_String :UDELTA5  (cp_Utf8)
        //        *cp_Class :UDELTA5  (cp_Utf8)
        //        cp_Signature
        //        cp_Descr
        //        cp_Field
        //        cp_Method
        //        cp_Imethod

        if (verbose > 0)  Utils.log.info("Reading CP");

        for (int k = 0; k < ConstantPool.TAGS_IN_ORDER.length; k++) {
            byte tag = ConstantPool.TAGS_IN_ORDER[k];
            int  len = tagCount[tag];

            Entry[] cpMap = new Entry[len];
            if (verbose > 0)
                Utils.log.info("Reading "+cpMap.length+" "+ConstantPool.tagName(tag)+" entries...");

            switch (tag) {
            case CONSTANT_Utf8:
                readUtf8Bands(cpMap);
                break;
            case CONSTANT_Integer:
                cp_Int.expectLength(cpMap.length);
                cp_Int.readFrom(in);
                for (int i = 0; i < cpMap.length; i++) {
                    int x = cp_Int.getInt();  // coding handles signs OK
                    cpMap[i] = ConstantPool.getLiteralEntry(x);
                }
                cp_Int.doneDisbursing();
                break;
            case CONSTANT_Float:
                cp_Float.expectLength(cpMap.length);
                cp_Float.readFrom(in);
                for (int i = 0; i < cpMap.length; i++) {
                    int x = cp_Float.getInt();
                    float fx = Float.intBitsToFloat(x);
                    cpMap[i] = ConstantPool.getLiteralEntry(fx);
                }
                cp_Float.doneDisbursing();
                break;
            case CONSTANT_Long:
                //  cp_Long:
                //        *cp_Long_hi :UDELTA5
                //        *cp_Long_lo :DELTA5
                cp_Long_hi.expectLength(cpMap.length);
                cp_Long_hi.readFrom(in);
                cp_Long_lo.expectLength(cpMap.length);
                cp_Long_lo.readFrom(in);
                for (int i = 0; i < cpMap.length; i++) {
                    long hi = cp_Long_hi.getInt();
                    long lo = cp_Long_lo.getInt();
                    long x = (hi << 32) + ((lo << 32) >>> 32);
                    cpMap[i] = ConstantPool.getLiteralEntry(x);
                }
                cp_Long_hi.doneDisbursing();
                cp_Long_lo.doneDisbursing();
                break;
            case CONSTANT_Double:
                //  cp_Double:
                //        *cp_Double_hi :UDELTA5
                //        *cp_Double_lo :DELTA5
                cp_Double_hi.expectLength(cpMap.length);
                cp_Double_hi.readFrom(in);
                cp_Double_lo.expectLength(cpMap.length);
                cp_Double_lo.readFrom(in);
                for (int i = 0; i < cpMap.length; i++) {
                    long hi = cp_Double_hi.getInt();
                    long lo = cp_Double_lo.getInt();
                    long x = (hi << 32) + ((lo << 32) >>> 32);
                    double dx = Double.longBitsToDouble(x);
                    cpMap[i] = ConstantPool.getLiteralEntry(dx);
                }
                cp_Double_hi.doneDisbursing();
                cp_Double_lo.doneDisbursing();
                break;
            case CONSTANT_String:
                cp_String.expectLength(cpMap.length);
                cp_String.readFrom(in);
                cp_String.setIndex(getCPIndex(CONSTANT_Utf8));
                for (int i = 0; i < cpMap.length; i++) {
                    cpMap[i] = ConstantPool.getLiteralEntry(cp_String.getRef().stringValue());
                }
                cp_String.doneDisbursing();
                break;
            case CONSTANT_Class:
                cp_Class.expectLength(cpMap.length);
                cp_Class.readFrom(in);
                cp_Class.setIndex(getCPIndex(CONSTANT_Utf8));
                for (int i = 0; i < cpMap.length; i++) {
                    cpMap[i] = ConstantPool.getClassEntry(cp_Class.getRef().stringValue());
                }
                cp_Class.doneDisbursing();
                break;
            case CONSTANT_Signature:
                readSignatureBands(cpMap);
                break;
            case CONSTANT_NameandType:
                //  cp_Descr:
                //        *cp_Descr_type :DELTA5  (cp_Signature)
                //        *cp_Descr_name :UDELTA5  (cp_Utf8)
                cp_Descr_name.expectLength(cpMap.length);
                cp_Descr_name.readFrom(in);
                cp_Descr_name.setIndex(getCPIndex(CONSTANT_Utf8));
                cp_Descr_type.expectLength(cpMap.length);
                cp_Descr_type.readFrom(in);
                cp_Descr_type.setIndex(getCPIndex(CONSTANT_Signature));
                for (int i = 0; i < cpMap.length; i++) {
                    Entry ref  = cp_Descr_name.getRef();
                    Entry ref2 = cp_Descr_type.getRef();
                    cpMap[i] = ConstantPool.getDescriptorEntry((Utf8Entry)ref,
                                                        (SignatureEntry)ref2);
                }
                cp_Descr_name.doneDisbursing();
                cp_Descr_type.doneDisbursing();
                break;
            case CONSTANT_Fieldref:
                readMemberRefs(tag, cpMap, cp_Field_class, cp_Field_desc);
                break;
            case CONSTANT_Methodref:
                readMemberRefs(tag, cpMap, cp_Method_class, cp_Method_desc);
                break;
            case CONSTANT_InterfaceMethodref:
                readMemberRefs(tag, cpMap, cp_Imethod_class, cp_Imethod_desc);
                break;
            case CONSTANT_MethodHandle:
                if (cpMap.length > 0) {
                    checkLegacy(cp_MethodHandle_refkind.name());
                }
                cp_MethodHandle_refkind.expectLength(cpMap.length);
                cp_MethodHandle_refkind.readFrom(in);
                cp_MethodHandle_member.expectLength(cpMap.length);
                cp_MethodHandle_member.readFrom(in);
                cp_MethodHandle_member.setIndex(getCPIndex(CONSTANT_AnyMember));
                for (int i = 0; i < cpMap.length; i++) {
                    byte        refKind = (byte)        cp_MethodHandle_refkind.getInt();
                    MemberEntry memRef  = (MemberEntry) cp_MethodHandle_member.getRef();
                    cpMap[i] = ConstantPool.getMethodHandleEntry(refKind, memRef);
                }
                cp_MethodHandle_refkind.doneDisbursing();
                cp_MethodHandle_member.doneDisbursing();
                break;
            case CONSTANT_MethodType:
                if (cpMap.length > 0) {
                    checkLegacy(cp_MethodType.name());
                }
                cp_MethodType.expectLength(cpMap.length);
                cp_MethodType.readFrom(in);
                cp_MethodType.setIndex(getCPIndex(CONSTANT_Signature));
                for (int i = 0; i < cpMap.length; i++) {
                    SignatureEntry typeRef  = (SignatureEntry) cp_MethodType.getRef();
                    cpMap[i] = ConstantPool.getMethodTypeEntry(typeRef);
                }
                cp_MethodType.doneDisbursing();
                break;
            case CONSTANT_InvokeDynamic:
                if (cpMap.length > 0) {
                    checkLegacy(cp_InvokeDynamic_spec.name());
                }
                cp_InvokeDynamic_spec.expectLength(cpMap.length);
                cp_InvokeDynamic_spec.readFrom(in);
                cp_InvokeDynamic_spec.setIndex(getCPIndex(CONSTANT_BootstrapMethod));
                cp_InvokeDynamic_desc.expectLength(cpMap.length);
                cp_InvokeDynamic_desc.readFrom(in);
                cp_InvokeDynamic_desc.setIndex(getCPIndex(CONSTANT_NameandType));
                for (int i = 0; i < cpMap.length; i++) {
                    BootstrapMethodEntry bss   = (BootstrapMethodEntry) cp_InvokeDynamic_spec.getRef();
                    DescriptorEntry      descr = (DescriptorEntry)      cp_InvokeDynamic_desc.getRef();
                    cpMap[i] = ConstantPool.getInvokeDynamicEntry(bss, descr);
                }
                cp_InvokeDynamic_spec.doneDisbursing();
                cp_InvokeDynamic_desc.doneDisbursing();
                break;
            case CONSTANT_BootstrapMethod:
                if (cpMap.length > 0) {
                    checkLegacy(cp_BootstrapMethod_ref.name());
                }
                cp_BootstrapMethod_ref.expectLength(cpMap.length);
                cp_BootstrapMethod_ref.readFrom(in);
                cp_BootstrapMethod_ref.setIndex(getCPIndex(CONSTANT_MethodHandle));
                cp_BootstrapMethod_arg_count.expectLength(cpMap.length);
                cp_BootstrapMethod_arg_count.readFrom(in);
                int totalArgCount = cp_BootstrapMethod_arg_count.getIntTotal();
                cp_BootstrapMethod_arg.expectLength(totalArgCount);
                cp_BootstrapMethod_arg.readFrom(in);
                cp_BootstrapMethod_arg.setIndex(getCPIndex(CONSTANT_LoadableValue));
                for (int i = 0; i < cpMap.length; i++) {
                    MethodHandleEntry bsm = (MethodHandleEntry) cp_BootstrapMethod_ref.getRef();
                    int argc = cp_BootstrapMethod_arg_count.getInt();
                    Entry[] argRefs = new Entry[argc];
                    for (int j = 0; j < argc; j++) {
                        argRefs[j] = cp_BootstrapMethod_arg.getRef();
                    }
                    cpMap[i] = ConstantPool.getBootstrapMethodEntry(bsm, argRefs);
                }
                cp_BootstrapMethod_ref.doneDisbursing();
                cp_BootstrapMethod_arg_count.doneDisbursing();
                cp_BootstrapMethod_arg.doneDisbursing();
                break;
            default:
                throw new AssertionError("unexpected CP tag in package");
            }

            Index index = initCPIndex(tag, cpMap);

            if (optDumpBands) {
                try (PrintStream ps = new PrintStream(getDumpStream(index, ".idx"))) {
                    printArrayTo(ps, index.cpMap, 0, index.cpMap.length);
                }
            }
        }

        cp_bands.doneDisbursing();

        if (optDumpBands || verbose > 1) {
            for (byte tag = CONSTANT_GroupFirst; tag < CONSTANT_GroupLimit; tag++) {
                Index index = pkg.cp.getIndexByTag(tag);
                if (index == null || index.isEmpty())  continue;
                Entry[] cpMap = index.cpMap;
                if (verbose > 1)
                    Utils.log.info("Index group "+ConstantPool.tagName(tag)+" contains "+cpMap.length+" entries.");
                if (optDumpBands) {
                    try (PrintStream ps = new PrintStream(getDumpStream(index.debugName, tag, ".gidx", index))) {
                        printArrayTo(ps, cpMap, 0, cpMap.length, true);
                    }
                }
            }
        }

        setBandIndexes();
    }

    void readUtf8Bands(Entry[] cpMap) throws IOException {
        //  cp_Utf8:
        //        *cp_Utf8_prefix :DELTA5
        //        *cp_Utf8_suffix :UNSIGNED5
        //        *cp_Utf8_chars :CHAR3
        //        *cp_Utf8_big_suffix :DELTA5
        //        (*cp_Utf8_big_chars :DELTA5)
        //          ** length(cp_Utf8_big_suffix)
        int len = cpMap.length;
        if (len == 0)
            return;  // nothing to read

        // Bands have implicit leading zeroes, for the empty string:
        final int SUFFIX_SKIP_1 = 1;
        final int PREFIX_SKIP_2 = 2;

        // First band:  Read lengths of shared prefixes.
        cp_Utf8_prefix.expectLength(Math.max(0, len - PREFIX_SKIP_2));
        cp_Utf8_prefix.readFrom(in);

        // Second band:  Read lengths of unshared suffixes:
        cp_Utf8_suffix.expectLength(Math.max(0, len - SUFFIX_SKIP_1));
        cp_Utf8_suffix.readFrom(in);

        char[][] suffixChars = new char[len][];
        int bigSuffixCount = 0;

        // Third band:  Read the char values in the unshared suffixes:
        cp_Utf8_chars.expectLength(cp_Utf8_suffix.getIntTotal());
        cp_Utf8_chars.readFrom(in);
        for (int i = 0; i < len; i++) {
            int suffix = (i < SUFFIX_SKIP_1)? 0: cp_Utf8_suffix.getInt();
            if (suffix == 0 && i >= SUFFIX_SKIP_1) {
                // chars are packed in cp_Utf8_big_chars
                bigSuffixCount += 1;
                continue;
            }
            suffixChars[i] = new char[suffix];
            for (int j = 0; j < suffix; j++) {
                int ch = cp_Utf8_chars.getInt();
                assert(ch == (char)ch);
                suffixChars[i][j] = (char)ch;
            }
        }
        cp_Utf8_chars.doneDisbursing();

        // Fourth band:  Go back and size the specially packed strings.
        int maxChars = 0;
        cp_Utf8_big_suffix.expectLength(bigSuffixCount);
        cp_Utf8_big_suffix.readFrom(in);
        cp_Utf8_suffix.resetForSecondPass();
        for (int i = 0; i < len; i++) {
            int suffix = (i < SUFFIX_SKIP_1)? 0: cp_Utf8_suffix.getInt();
            int prefix = (i < PREFIX_SKIP_2)? 0: cp_Utf8_prefix.getInt();
            if (suffix == 0 && i >= SUFFIX_SKIP_1) {
                assert(suffixChars[i] == null);
                suffix = cp_Utf8_big_suffix.getInt();
            } else {
                assert(suffixChars[i] != null);
            }
            if (maxChars < prefix + suffix)
                maxChars = prefix + suffix;
        }
        char[] buf = new char[maxChars];

        // Fifth band(s):  Get the specially packed characters.
        cp_Utf8_suffix.resetForSecondPass();
        cp_Utf8_big_suffix.resetForSecondPass();
        for (int i = 0; i < len; i++) {
            if (i < SUFFIX_SKIP_1)  continue;
            int suffix = cp_Utf8_suffix.getInt();
            if (suffix != 0)  continue;  // already input
            suffix = cp_Utf8_big_suffix.getInt();
            suffixChars[i] = new char[suffix];
            if (suffix == 0) {
                // Do not bother to add an empty "(Utf8_big_0)" band.
                continue;
            }
            IntBand packed = cp_Utf8_big_chars.newIntBand("(Utf8_big_"+i+")");
            packed.expectLength(suffix);
            packed.readFrom(in);
            for (int j = 0; j < suffix; j++) {
                int ch = packed.getInt();
                assert(ch == (char)ch);
                suffixChars[i][j] = (char)ch;
            }
            packed.doneDisbursing();
        }
        cp_Utf8_big_chars.doneDisbursing();

        // Finally, sew together all the prefixes and suffixes.
        cp_Utf8_prefix.resetForSecondPass();
        cp_Utf8_suffix.resetForSecondPass();
        cp_Utf8_big_suffix.resetForSecondPass();
        for (int i = 0; i < len; i++) {
            int prefix = (i < PREFIX_SKIP_2)? 0: cp_Utf8_prefix.getInt();
            int suffix = (i < SUFFIX_SKIP_1)? 0: cp_Utf8_suffix.getInt();
            if (suffix == 0 && i >= SUFFIX_SKIP_1)
                suffix = cp_Utf8_big_suffix.getInt();

            // by induction, the buffer is already filled with the prefix
            System.arraycopy(suffixChars[i], 0, buf, prefix, suffix);

            cpMap[i] = ConstantPool.getUtf8Entry(new String(buf, 0, prefix+suffix));
        }

        cp_Utf8_prefix.doneDisbursing();
        cp_Utf8_suffix.doneDisbursing();
        cp_Utf8_big_suffix.doneDisbursing();
    }

    Map<Utf8Entry, SignatureEntry> utf8Signatures;

    void readSignatureBands(Entry[] cpMap) throws IOException {
        //  cp_Signature:
        //        *cp_Signature_form :DELTA5  (cp_Utf8)
        //        *cp_Signature_classes :UDELTA5  (cp_Class)
        cp_Signature_form.expectLength(cpMap.length);
        cp_Signature_form.readFrom(in);
        cp_Signature_form.setIndex(getCPIndex(CONSTANT_Utf8));
        int[] numSigClasses = new int[cpMap.length];
        for (int i = 0; i < cpMap.length; i++) {
            Utf8Entry formRef = (Utf8Entry) cp_Signature_form.getRef();
            numSigClasses[i] = ConstantPool.countClassParts(formRef);
        }
        cp_Signature_form.resetForSecondPass();
        cp_Signature_classes.expectLength(getIntTotal(numSigClasses));
        cp_Signature_classes.readFrom(in);
        cp_Signature_classes.setIndex(getCPIndex(CONSTANT_Class));
        utf8Signatures = new HashMap<>();
        for (int i = 0; i < cpMap.length; i++) {
            Utf8Entry formRef = (Utf8Entry) cp_Signature_form.getRef();
            ClassEntry[] classRefs = new ClassEntry[numSigClasses[i]];
            for (int j = 0; j < classRefs.length; j++) {
                classRefs[j] = (ClassEntry) cp_Signature_classes.getRef();
            }
            SignatureEntry se = ConstantPool.getSignatureEntry(formRef, classRefs);
            cpMap[i] = se;
            utf8Signatures.put(se.asUtf8Entry(), se);
        }
        cp_Signature_form.doneDisbursing();
        cp_Signature_classes.doneDisbursing();
    }

    void readMemberRefs(byte tag, Entry[] cpMap, CPRefBand cp_class, CPRefBand cp_desc) throws IOException {
        //  cp_Field:
        //        *cp_Field_class :DELTA5  (cp_Class)
        //        *cp_Field_desc :UDELTA5  (cp_Descr)
        //  cp_Method:
        //        *cp_Method_class :DELTA5  (cp_Class)
        //        *cp_Method_desc :UDELTA5  (cp_Descr)
        //  cp_Imethod:
        //        *cp_Imethod_class :DELTA5  (cp_Class)
        //        *cp_Imethod_desc :UDELTA5  (cp_Descr)
        cp_class.expectLength(cpMap.length);
        cp_class.readFrom(in);
        cp_class.setIndex(getCPIndex(CONSTANT_Class));
        cp_desc.expectLength(cpMap.length);
        cp_desc.readFrom(in);
        cp_desc.setIndex(getCPIndex(CONSTANT_NameandType));
        for (int i = 0; i < cpMap.length; i++) {
            ClassEntry      mclass = (ClassEntry     ) cp_class.getRef();
            DescriptorEntry mdescr = (DescriptorEntry) cp_desc.getRef();
            cpMap[i] = ConstantPool.getMemberEntry(tag, mclass, mdescr);
        }
        cp_class.doneDisbursing();
        cp_desc.doneDisbursing();
    }

    void readFiles() throws IOException {
        //  file_bands:
        //        *file_name :UNSIGNED5  (cp_Utf8)
        //        *file_size_hi :UNSIGNED5
        //        *file_size_lo :UNSIGNED5
        //        *file_modtime :DELTA5
        //        *file_options :UNSIGNED5
        //        *file_bits :BYTE1
        if (verbose > 0)
            Utils.log.info("  ...building "+numFiles+" files...");
        file_name.expectLength(numFiles);
        file_size_lo.expectLength(numFiles);
        int options = archiveOptions;
        boolean haveSizeHi  = testBit(options, AO_HAVE_FILE_SIZE_HI);
        boolean haveModtime = testBit(options, AO_HAVE_FILE_MODTIME);
        boolean haveOptions = testBit(options, AO_HAVE_FILE_OPTIONS);
        if (haveSizeHi)
            file_size_hi.expectLength(numFiles);
        if (haveModtime)
            file_modtime.expectLength(numFiles);
        if (haveOptions)
            file_options.expectLength(numFiles);

        file_name.readFrom(in);
        file_size_hi.readFrom(in);
        file_size_lo.readFrom(in);
        file_modtime.readFrom(in);
        file_options.readFrom(in);
        file_bits.setInputStreamFrom(in);

        Iterator<Class> nextClass = pkg.getClasses().iterator();

        // Compute file lengths before reading any file bits.
        long totalFileLength = 0;
        long[] fileLengths = new long[numFiles];
        for (int i = 0; i < numFiles; i++) {
            long size = ((long)file_size_lo.getInt() << 32) >>> 32;
            if (haveSizeHi)
                size += (long)file_size_hi.getInt() << 32;
            fileLengths[i] = size;
            totalFileLength += size;
        }
        assert(in.getReadLimit() == -1 || in.getReadLimit() == totalFileLength);

        byte[] buf = new byte[1<<16];
        for (int i = 0; i < numFiles; i++) {
            // %%% Use a big temp file for file bits?
            Utf8Entry name = (Utf8Entry) file_name.getRef();
            long size = fileLengths[i];
            File file = pkg.new File(name);
            file.modtime = pkg.default_modtime;
            file.options = pkg.default_options;
            if (haveModtime)
                file.modtime += file_modtime.getInt();
            if (haveOptions)
                file.options |= file_options.getInt();
            if (verbose > 1)
                Utils.log.fine("Reading "+size+" bytes of "+name.stringValue());
            long toRead = size;
            while (toRead > 0) {
                int nr = buf.length;
                if (nr > toRead)  nr = (int) toRead;
                nr = file_bits.getInputStream().read(buf, 0, nr);
                if (nr < 0)  throw new EOFException();
                file.addBytes(buf, 0, nr);
                toRead -= nr;
            }
            pkg.addFile(file);
            if (file.isClassStub()) {
                assert(file.getFileLength() == 0);
                Class cls = nextClass.next();
                cls.initFile(file);
            }
        }

        // Do the rest of the classes.
        while (nextClass.hasNext()) {
            Class cls = nextClass.next();
            cls.initFile(null);  // implicitly initialize to a trivial one
            cls.file.modtime = pkg.default_modtime;
        }

        file_name.doneDisbursing();
        file_size_hi.doneDisbursing();
        file_size_lo.doneDisbursing();
        file_modtime.doneDisbursing();
        file_options.doneDisbursing();
        file_bits.doneDisbursing();
        file_bands.doneDisbursing();

        if (archiveSize1 != 0 && !in.atLimit()) {
            throw new RuntimeException("Predicted archive_size "+
                                       archiveSize1+" != "+
                                       (in.getBytesServed()-archiveSize0));
        }
    }

    void readAttrDefs() throws IOException {
        //  attr_definition_bands:
        //        *attr_definition_headers :BYTE1
        //        *attr_definition_name :UNSIGNED5  (cp_Utf8)
        //        *attr_definition_layout :UNSIGNED5  (cp_Utf8)
        attr_definition_headers.expectLength(numAttrDefs);
        attr_definition_name.expectLength(numAttrDefs);
        attr_definition_layout.expectLength(numAttrDefs);
        attr_definition_headers.readFrom(in);
        attr_definition_name.readFrom(in);
        attr_definition_layout.readFrom(in);
        try (PrintStream dump = !optDumpBands ? null
                 : new PrintStream(getDumpStream(attr_definition_headers, ".def")))
        {
            for (int i = 0; i < numAttrDefs; i++) {
                int       header = attr_definition_headers.getByte();
                Utf8Entry name   = (Utf8Entry) attr_definition_name.getRef();
                Utf8Entry layout = (Utf8Entry) attr_definition_layout.getRef();
                int       ctype  = (header &  ADH_CONTEXT_MASK);
                int       index  = (header >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB;
                Attribute.Layout def = new Attribute.Layout(ctype,
                                                            name.stringValue(),
                                                            layout.stringValue());
                // Check layout string for Java 6 extensions.
                String pvLayout = def.layoutForClassVersion(getHighestClassVersion());
                if (!pvLayout.equals(def.layout())) {
                    throw new IOException("Bad attribute layout in archive: "+def.layout());
                }
                this.setAttributeLayoutIndex(def, index);
                if (dump != null)  dump.println(index+" "+def);
            }
        }
        attr_definition_headers.doneDisbursing();
        attr_definition_name.doneDisbursing();
        attr_definition_layout.doneDisbursing();
        // Attribute layouts define bands, one per layout element.
        // Create them now, all at once.
        makeNewAttributeBands();
        attr_definition_bands.doneDisbursing();
    }

    void readInnerClasses() throws IOException {
        //  ic_bands:
        //        *ic_this_class :UDELTA5  (cp_Class)
        //        *ic_flags :UNSIGNED5
        //        *ic_outer_class :DELTA5  (null or cp_Class)
        //        *ic_name :DELTA5  (null or cp_Utf8)
        ic_this_class.expectLength(numInnerClasses);
        ic_this_class.readFrom(in);
        ic_flags.expectLength(numInnerClasses);
        ic_flags.readFrom(in);
        int longICCount = 0;
        for (int i = 0; i < numInnerClasses; i++) {
            int flags = ic_flags.getInt();
            boolean longForm = (flags & ACC_IC_LONG_FORM) != 0;
            if (longForm) {
                longICCount += 1;
            }
        }
        ic_outer_class.expectLength(longICCount);
        ic_outer_class.readFrom(in);
        ic_name.expectLength(longICCount);
        ic_name.readFrom(in);
        ic_flags.resetForSecondPass();
        List<InnerClass> icList = new ArrayList<>(numInnerClasses);
        for (int i = 0; i < numInnerClasses; i++) {
            int flags = ic_flags.getInt();
            boolean longForm = (flags & ACC_IC_LONG_FORM) != 0;
            flags &= ~ACC_IC_LONG_FORM;
            ClassEntry thisClass = (ClassEntry) ic_this_class.getRef();
            ClassEntry outerClass;
            Utf8Entry  thisName;
            if (longForm) {
                outerClass = (ClassEntry) ic_outer_class.getRef();
                thisName   = (Utf8Entry)  ic_name.getRef();
            } else {
                String n = thisClass.stringValue();
                String[] parse = Package.parseInnerClassName(n);
                assert(parse != null);
                String pkgOuter = parse[0];
                //String number = parse[1];
                String name     = parse[2];
                if (pkgOuter == null)
                    outerClass = null;
                else
                    outerClass = ConstantPool.getClassEntry(pkgOuter);
                if (name == null)
                    thisName   = null;
                else
                    thisName   = ConstantPool.getUtf8Entry(name);
            }
            InnerClass ic =
                new InnerClass(thisClass, outerClass, thisName, flags);
            assert(longForm || ic.predictable);
            icList.add(ic);
        }
        ic_flags.doneDisbursing();
        ic_this_class.doneDisbursing();
        ic_outer_class.doneDisbursing();
        ic_name.doneDisbursing();
        pkg.setAllInnerClasses(icList);
        ic_bands.doneDisbursing();
    }

    void readLocalInnerClasses(Class cls) throws IOException {
        int nc = class_InnerClasses_N.getInt();
        List<InnerClass> localICs = new ArrayList<>(nc);
        for (int i = 0; i < nc; i++) {
            ClassEntry thisClass = (ClassEntry) class_InnerClasses_RC.getRef();
            int        flags     =              class_InnerClasses_F.getInt();
            if (flags == 0) {
                // A zero flag means copy a global IC here.
                InnerClass ic = pkg.getGlobalInnerClass(thisClass);
                assert(ic != null);  // must be a valid global IC reference
                localICs.add(ic);
            } else {
                if (flags == ACC_IC_LONG_FORM)
                    flags = 0;  // clear the marker bit
                ClassEntry outer = (ClassEntry) class_InnerClasses_outer_RCN.getRef();
                Utf8Entry name   = (Utf8Entry)  class_InnerClasses_name_RUN.getRef();
                localICs.add(new InnerClass(thisClass, outer, name, flags));
            }
        }
        cls.setInnerClasses(localICs);
        // cls.expandLocalICs may add more tuples to ics also,
        // or may even delete tuples.
        // We cannot do that now, because we do not know the
        // full contents of the local constant pool yet.
    }

    static final int NO_FLAGS_YET = 0;  // placeholder for later flag read-in

    Class[] readClasses() throws IOException {
        //  class_bands:
        //        *class_this :DELTA5  (cp_Class)
        //        *class_super :DELTA5  (cp_Class)
        //        *class_interface_count :DELTA5
        //        *class_interface :DELTA5  (cp_Class)
        //        ...(member bands)...
        //        class_attr_bands
        //        code_bands
        Class[] classes = new Class[numClasses];
        if (verbose > 0)
            Utils.log.info("  ...building "+classes.length+" classes...");

        class_this.expectLength(numClasses);
        class_super.expectLength(numClasses);
        class_interface_count.expectLength(numClasses);

        class_this.readFrom(in);
        class_super.readFrom(in);
        class_interface_count.readFrom(in);
        class_interface.expectLength(class_interface_count.getIntTotal());
        class_interface.readFrom(in);
        for (int i = 0; i < classes.length; i++) {
            ClassEntry   thisClass  = (ClassEntry) class_this.getRef();
            ClassEntry   superClass = (ClassEntry) class_super.getRef();
            ClassEntry[] interfaces = new ClassEntry[class_interface_count.getInt()];
            for (int j = 0; j < interfaces.length; j++) {
                interfaces[j] = (ClassEntry) class_interface.getRef();
            }
            // Packer encoded rare case of null superClass as thisClass:
            if (superClass == thisClass)  superClass = null;
            Class cls = pkg.new Class(NO_FLAGS_YET,
                                      thisClass, superClass, interfaces);
            classes[i] = cls;
        }
        class_this.doneDisbursing();
        class_super.doneDisbursing();
        class_interface_count.doneDisbursing();
        class_interface.doneDisbursing();
        readMembers(classes);
        countAndReadAttrs(ATTR_CONTEXT_CLASS, Arrays.asList(classes));
        pkg.trimToSize();
        readCodeHeaders();
        //code_bands.doneDisbursing(); // still need to read code attrs
        //class_bands.doneDisbursing(); // still need to read code attrs
        return classes;
    }

    private int getOutputIndex(Entry e) {
        // Output CPs do not contain signatures.
        assert(e.tag != CONSTANT_Signature);
        int k = pkg.cp.untypedIndexOf(e);
        // In the output ordering, input signatures can serve
        // in place of Utf8s.
        if (k >= 0)
            return k;
        if (e.tag == CONSTANT_Utf8) {
            Entry se = utf8Signatures.get(e);
            return pkg.cp.untypedIndexOf(se);
        }
        return -1;
    }

    Comparator<Entry> entryOutputOrder = new Comparator() {
        public int compare(Entry e0, Entry e1) {
            int k0 = getOutputIndex(e0);
            int k1 = getOutputIndex(e1);
            if (k0 >= 0 && k1 >= 0)
                // If both have keys, use the keys.
                return k0 - k1;
            if (k0 == k1)
                // If neither have keys, use their native tags & spellings.
                return e0.compareTo(e1);
            // Otherwise, the guy with the key comes first.
            return (k0 >= 0)? 0-1: 1-0;
        }
    };

    void reconstructClass(Class cls) {
        if (verbose > 1)  Utils.log.fine("reconstruct "+cls);

        // check for local .ClassFile.version
        Attribute retroVersion = cls.getAttribute(attrClassFileVersion);
        if (retroVersion != null) {
            cls.removeAttribute(retroVersion);
            cls.version = parseClassFileVersionAttr(retroVersion);
        } else {
            cls.version = pkg.defaultClassVersion;
        }

        // Replace null SourceFile by "obvious" string.
        cls.expandSourceFile();

        // record the local cp:
        cls.setCPMap(reconstructLocalCPMap(cls));
    }

    Entry[] reconstructLocalCPMap(Class cls) {
        Set<Entry> ldcRefs = ldcRefMap.get(cls);
        Set<Entry> cpRefs = new HashSet<>();

        // look for constant pool entries:
        cls.visitRefs(VRM_CLASSIC, cpRefs);

        ArrayList<BootstrapMethodEntry> bsms = new ArrayList<>();
        /*
         * BootstrapMethod(BSMs) are added here before InnerClasses(ICs),
         * so as to ensure the order. Noting that the BSMs  may be
         * removed if they are not found in the CP, after the ICs expansion.
         */
        cls.addAttribute(Package.attrBootstrapMethodsEmpty.canonicalInstance());

        // flesh out the local constant pool
        ConstantPool.completeReferencesIn(cpRefs, true, bsms);

        // Now that we know all our local class references,
        // compute the InnerClasses attribute.
        int changed = cls.expandLocalICs();

        if (changed != 0) {
            if (changed > 0) {
                // Just visit the expanded InnerClasses attr.
                cls.visitInnerClassRefs(VRM_CLASSIC, cpRefs);
            } else {
                // Have to recompute from scratch, because of deletions.
                cpRefs.clear();
                cls.visitRefs(VRM_CLASSIC, cpRefs);
            }

            // flesh out the local constant pool, again
            ConstantPool.completeReferencesIn(cpRefs, true, bsms);
        }

        // remove the attr previously set, otherwise add the bsm and
        // references as required
        if (bsms.isEmpty()) {
            cls.attributes.remove(Package.attrBootstrapMethodsEmpty.canonicalInstance());
        } else {
            cpRefs.add(Package.getRefString("BootstrapMethods"));
            Collections.sort(bsms);
            cls.setBootstrapMethods(bsms);
        }

        // construct a local constant pool
        int numDoubles = 0;
        for (Entry e : cpRefs) {
            if (e.isDoubleWord())  numDoubles++;
        }
        Entry[] cpMap = new Entry[1+numDoubles+cpRefs.size()];
        int fillp = 1;

        // Add all ldc operands first.
        if (ldcRefs != null) {
            assert(cpRefs.containsAll(ldcRefs));
            for (Entry e : ldcRefs) {
                cpMap[fillp++] = e;
            }
            assert(fillp == 1+ldcRefs.size());
            cpRefs.removeAll(ldcRefs);
            ldcRefs = null;  // done with it
        }

        // Next add all the two-byte references.
        Set<Entry> wideRefs = cpRefs;
        cpRefs = null;  // do not use!
        int narrowLimit = fillp;
        for (Entry e : wideRefs) {
            cpMap[fillp++] = e;
        }
        assert(fillp == narrowLimit+wideRefs.size());
        Arrays.sort(cpMap, 1, narrowLimit, entryOutputOrder);
        Arrays.sort(cpMap, narrowLimit, fillp, entryOutputOrder);

        if (verbose > 3) {
            Utils.log.fine("CP of "+this+" {");
            for (int i = 0; i < fillp; i++) {
                Entry e = cpMap[i];
                Utils.log.fine("  "+((e==null)?-1:getOutputIndex(e))
                                   +" : "+e);
            }
            Utils.log.fine("}");
        }

        // Now repack backwards, introducing null elements.
        int revp = cpMap.length;
        for (int i = fillp; --i >= 1; ) {
            Entry e = cpMap[i];
            if (e.isDoubleWord())
                cpMap[--revp] = null;
            cpMap[--revp] = e;
        }
        assert(revp == 1);  // do not process the initial null

        return cpMap;
    }

    void readMembers(Class[] classes) throws IOException {
        //  class_bands:
        //        ...
        //        *class_field_count :DELTA5
        //        *class_method_count :DELTA5
        //
        //        *field_descr :DELTA5  (cp_Descr)
        //        field_attr_bands
        //
        //        *method_descr :MDELTA5  (cp_Descr)
        //        method_attr_bands
        //        ...
        assert(classes.length == numClasses);
        class_field_count.expectLength(numClasses);
        class_method_count.expectLength(numClasses);
        class_field_count.readFrom(in);
        class_method_count.readFrom(in);

        // Make a pre-pass over field and method counts to size the descrs:
        int totalNF = class_field_count.getIntTotal();
        int totalNM = class_method_count.getIntTotal();
        field_descr.expectLength(totalNF);
        method_descr.expectLength(totalNM);
        if (verbose > 1)  Utils.log.fine("expecting #fields="+totalNF+
                " and #methods="+totalNM+" in #classes="+numClasses);

        List<Class.Field> fields = new ArrayList<>(totalNF);
        field_descr.readFrom(in);
        for (int i = 0; i < classes.length; i++) {
            Class c = classes[i];
            int nf = class_field_count.getInt();
            for (int j = 0; j < nf; j++) {
                Class.Field f = c.new Field(NO_FLAGS_YET, (DescriptorEntry)
                                            field_descr.getRef());
                fields.add(f);
            }
        }
        class_field_count.doneDisbursing();
        field_descr.doneDisbursing();
        countAndReadAttrs(ATTR_CONTEXT_FIELD, fields);
        fields = null;  // release to GC

        List<Class.Method> methods = new ArrayList<>(totalNM);
        method_descr.readFrom(in);
        for (int i = 0; i < classes.length; i++) {
            Class c = classes[i];
            int nm = class_method_count.getInt();
            for (int j = 0; j < nm; j++) {
                Class.Method m = c.new Method(NO_FLAGS_YET, (DescriptorEntry)
                                              method_descr.getRef());
                methods.add(m);
            }
        }
        class_method_count.doneDisbursing();
        method_descr.doneDisbursing();
        countAndReadAttrs(ATTR_CONTEXT_METHOD, methods);

        // Up to this point, Code attributes look like empty attributes.
        // Now we start to special-case them.  The empty canonical Code
        // attributes stay in the method attribute lists, however.
        allCodes = buildCodeAttrs(methods);
    }

    Code[] allCodes;
    List<Code> codesWithFlags;
    Map<Class, Set ldcRefMap = new HashMap<>();

    Code[] buildCodeAttrs(List<Class.Method> methods) {
        List<Code> codes = new ArrayList<>(methods.size());
        for (Class.Method m : methods) {
            if (m.getAttribute(attrCodeEmpty) != null) {
                m.code = new Code(m);
                codes.add(m.code);
            }
        }
        Code[] a = new Code[codes.size()];
        codes.toArray(a);
        return a;
    }

    void readCodeHeaders() throws IOException {
        //  code_bands:
        //        *code_headers :BYTE1
        //
        //        *code_max_stack :UNSIGNED5
        //        *code_max_na_locals :UNSIGNED5
        //        *code_handler_count :UNSIGNED5
        //        ...
        //        code_attr_bands
        boolean attrsOK = testBit(archiveOptions, AO_HAVE_ALL_CODE_FLAGS);
        code_headers.expectLength(allCodes.length);
        code_headers.readFrom(in);
        List<Code> longCodes = new ArrayList<>(allCodes.length / 10);
        for (int i = 0; i < allCodes.length; i++) {
            Code c = allCodes[i];
            int sc = code_headers.getByte();
            assert(sc == (sc & 0xFF));
            if (verbose > 2)
                Utils.log.fine("codeHeader "+c+" = "+sc);
            if (sc == LONG_CODE_HEADER) {
                // We will read ms/ml/nh/flags from bands shortly.
                longCodes.add(c);
                continue;
            }
            // Short code header is the usual case:
            c.setMaxStack(     shortCodeHeader_max_stack(sc) );
            c.setMaxNALocals(  shortCodeHeader_max_na_locals(sc) );
            c.setHandlerCount( shortCodeHeader_handler_count(sc) );
            assert(shortCodeHeader(c) == sc);
        }
        code_headers.doneDisbursing();
        code_max_stack.expectLength(longCodes.size());
        code_max_na_locals.expectLength(longCodes.size());
        code_handler_count.expectLength(longCodes.size());

        // Do the long headers now.
        code_max_stack.readFrom(in);
        code_max_na_locals.readFrom(in);
        code_handler_count.readFrom(in);
        for (Code c : longCodes) {
            c.setMaxStack(     code_max_stack.getInt() );
            c.setMaxNALocals(  code_max_na_locals.getInt() );
            c.setHandlerCount( code_handler_count.getInt() );
        }
        code_max_stack.doneDisbursing();
        code_max_na_locals.doneDisbursing();
        code_handler_count.doneDisbursing();

        readCodeHandlers();

        if (attrsOK) {
            // Code attributes are common (debug info not stripped).
            codesWithFlags = Arrays.asList(allCodes);
        } else {
            // Code attributes are very sparse (debug info is stripped).
            codesWithFlags = longCodes;
        }
        countAttrs(ATTR_CONTEXT_CODE, codesWithFlags);
        // do readAttrs later, after BCs are scanned
    }

    void readCodeHandlers() throws IOException {
        //  code_bands:
        //        ...
        //        *code_handler_start_P :BCI5
        //        *code_handler_end_PO :BRANCH5
        //        *code_handler_catch_PO :BRANCH5
        //        *code_handler_class_RCN :UNSIGNED5  (null or cp_Class)
        //        ...
        int nh = 0;
        for (int i = 0; i < allCodes.length; i++) {
            Code c = allCodes[i];
            nh += c.getHandlerCount();
        }

        ValueBand[] code_handler_bands = {
            code_handler_start_P,
            code_handler_end_PO,
            code_handler_catch_PO,
            code_handler_class_RCN
        };

        for (int i = 0; i < code_handler_bands.length; i++) {
            code_handler_bands[i].expectLength(nh);
            code_handler_bands[i].readFrom(in);
        }

        for (int i = 0; i < allCodes.length; i++) {
            Code c = allCodes[i];
            for (int j = 0, jmax = c.getHandlerCount(); j < jmax; j++) {
                c.handler_class[j] = code_handler_class_RCN.getRef();
                // For now, just record the raw BCI codes.
                // We must wait until we have instruction boundaries.
                c.handler_start[j] = code_handler_start_P.getInt();
                c.handler_end[j]   = code_handler_end_PO.getInt();
                c.handler_catch[j] = code_handler_catch_PO.getInt();
            }
        }
        for (int i = 0; i < code_handler_bands.length; i++) {
            code_handler_bands[i].doneDisbursing();
        }
    }

    void fixupCodeHandlers() {
        // Actually decode (renumber) the BCIs now.
        for (int i = 0; i < allCodes.length; i++) {
            Code c = allCodes[i];
            for (int j = 0, jmax = c.getHandlerCount(); j < jmax; j++) {
                int sum = c.handler_start[j];
                c.handler_start[j] = c.decodeBCI(sum);
                sum += c.handler_end[j];
                c.handler_end[j]   = c.decodeBCI(sum);
                sum += c.handler_catch[j];
                c.handler_catch[j] = c.decodeBCI(sum);
            }
        }
    }

    // Generic routines for reading attributes of
    // classes, fields, methods, and codes.
    // The holders is a global list, already collected,
    // of attribute "customers".
    void countAndReadAttrs(int ctype, Collection<? extends Attribute.Holder> holders)
            throws IOException {
        //  class_attr_bands:
        //        *class_flags :UNSIGNED5
        //        *class_attr_count :UNSIGNED5
        //        *class_attr_indexes :UNSIGNED5
        //        *class_attr_calls :UNSIGNED5
        //        *class_Signature_RS :UNSIGNED5 (cp_Signature)
        //        class_metadata_bands
        //        *class_SourceFile_RU :UNSIGNED5 (cp_Utf8)
        //        *class_EnclosingMethod_RM :UNSIGNED5 (cp_Method)
        //        ic_local_bands
        //        *class_ClassFile_version_minor_H :UNSIGNED5
        //        *class_ClassFile_version_major_H :UNSIGNED5
        //        class_type_metadata_bands
        //
        //  field_attr_bands:
        //        *field_flags :UNSIGNED5
        //        *field_attr_count :UNSIGNED5
        //        *field_attr_indexes :UNSIGNED5
        //        *field_attr_calls :UNSIGNED5
        //        *field_Signature_RS :UNSIGNED5 (cp_Signature)
        //        field_metadata_bands
        //        *field_ConstantValue_KQ :UNSIGNED5 (cp_Int, etc.; see note)
        //        field_type_metadata_bands
        //
        //  method_attr_bands:
        //        *method_flags :UNSIGNED5
        //        *method_attr_count :UNSIGNED5
        //        *method_attr_indexes :UNSIGNED5
        //        *method_attr_calls :UNSIGNED5
        //        *method_Signature_RS :UNSIGNED5 (cp_Signature)
        //        method_metadata_bands
        //        *method_Exceptions_N :UNSIGNED5
        //        *method_Exceptions_RC :UNSIGNED5  (cp_Class)
        //        *method_MethodParameters_NB: BYTE1
        //        *method_MethodParameters_RUN: UNSIGNED5 (cp_Utf8)
        //        *method_MethodParameters_FH:  UNSIGNED5 (flag)
        //        method_type_metadata_bands
        //
        //  code_attr_bands:
        //        *code_flags :UNSIGNED5
        //        *code_attr_count :UNSIGNED5
        //        *code_attr_indexes :UNSIGNED5
        //        *code_attr_calls :UNSIGNED5
        //        *code_LineNumberTable_N :UNSIGNED5
        //        *code_LineNumberTable_bci_P :BCI5
        //        *code_LineNumberTable_line :UNSIGNED5
        //        *code_LocalVariableTable_N :UNSIGNED5
        //        *code_LocalVariableTable_bci_P :BCI5
        //        *code_LocalVariableTable_span_O :BRANCH5
        //        *code_LocalVariableTable_name_RU :UNSIGNED5 (cp_Utf8)
        //        *code_LocalVariableTable_type_RS :UNSIGNED5 (cp_Signature)
        //        *code_LocalVariableTable_slot :UNSIGNED5
        //        code_type_metadata_bands

        countAttrs(ctype, holders);
        readAttrs(ctype, holders);
    }

    // Read flags and count the attributes that are to be placed
    // on the given holders.
    void countAttrs(int ctype, Collection<? extends Attribute.Holder> holders)
            throws IOException {
        // Here, xxx stands for one of class, field, method, code.
        MultiBand xxx_attr_bands = attrBands[ctype];
        long flagMask = attrFlagMask[ctype];
        if (verbose > 1) {
            Utils.log.fine("scanning flags and attrs for "+
                    Attribute.contextName(ctype)+"["+holders.size()+"]");
        }

        // Fetch the attribute layout definitions which govern the bands
        // we are about to read.
        List<Attribute.Layout> defList = attrDefs.get(ctype);
        Attribute.Layout[] defs = new Attribute.Layout[defList.size()];
        defList.toArray(defs);
        IntBand xxx_flags_hi = getAttrBand(xxx_attr_bands, AB_FLAGS_HI);
        IntBand xxx_flags_lo = getAttrBand(xxx_attr_bands, AB_FLAGS_LO);
        IntBand xxx_attr_count = getAttrBand(xxx_attr_bands, AB_ATTR_COUNT);
        IntBand xxx_attr_indexes = getAttrBand(xxx_attr_bands, AB_ATTR_INDEXES);
        IntBand xxx_attr_calls = getAttrBand(xxx_attr_bands, AB_ATTR_CALLS);

        // Count up the number of holders which have overflow attrs.
        int overflowMask = attrOverflowMask[ctype];
        int overflowHolderCount = 0;
        boolean haveLongFlags = haveFlagsHi(ctype);
        xxx_flags_hi.expectLength(haveLongFlags? holders.size(): 0);
        xxx_flags_hi.readFrom(in);
        xxx_flags_lo.expectLength(holders.size());
        xxx_flags_lo.readFrom(in);
        assert((flagMask & overflowMask) == overflowMask);
        for (Attribute.Holder h : holders) {
            int flags = xxx_flags_lo.getInt();
            h.flags = flags;
            if ((flags & overflowMask) != 0)
                overflowHolderCount += 1;
        }

        // For each holder with overflow attrs, read a count.
        xxx_attr_count.expectLength(overflowHolderCount);
        xxx_attr_count.readFrom(in);
        xxx_attr_indexes.expectLength(xxx_attr_count.getIntTotal());
        xxx_attr_indexes.readFrom(in);

        // Now it's time to check flag bits that indicate attributes.
        // We accumulate (a) a list of attribute types for each holder
        // (class/field/method/code), and also we accumulate (b) a total
        // count for each attribute type.
        int[] totalCounts = new int[defs.length];
        for (Attribute.Holder h : holders) {
            assert(h.attributes == null);
            // System.out.println("flags="+h.flags+" using fm="+flagMask);
            long attrBits = ((h.flags & flagMask) << 32) >>> 32;
            // Clean up the flags now.
            h.flags -= (int)attrBits;   // strip attr bits
            assert(h.flags == (char)h.flags);  // 16 bits only now
            assert((ctype != ATTR_CONTEXT_CODE) || h.flags == 0);
            if (haveLongFlags)
                attrBits += (long)xxx_flags_hi.getInt() << 32;
            if (attrBits == 0)  continue;  // no attrs on this guy

            int noa = 0;  // number of overflow attrs
            long overflowBit = (attrBits & overflowMask);
            assert(overflowBit >= 0);
            attrBits -= overflowBit;
            if (overflowBit != 0) {
                noa = xxx_attr_count.getInt();
            }

            int nfa = 0;  // number of flag attrs
            long bits = attrBits;
            for (int ai = 0; bits != 0; ai++) {
                if ((bits & (1L<(nfa + noa);
            h.attributes = ha;
            bits = attrBits;  // iterate again
            for (int ai = 0; bits != 0; ai++) {
                if ((bits & (1L<();
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        for (final Attribute.Holder h : holders) {
            if (h.attributes == null)  continue;
            for (ListIterator<Attribute> j = h.attributes.listIterator(); j.hasNext(); ) {
                Attribute a = j.next();
                Attribute.Layout def = a.layout();
                if (def.bandCount == 0) {
                    if (def == attrInnerClassesEmpty) {
                        // Special logic to read this attr.
                        readLocalInnerClasses((Class) h);
                        continue;
                    }
                    // Canonical empty attr works fine (e.g., Synthetic).
                    continue;
                }
                sawDefs.add(def);
                boolean isCV = (ctype == ATTR_CONTEXT_FIELD && def == attrConstantValue);
                if (isCV)  setConstantValueIndex((Class.Field)h);
                if (verbose > 2)
                    Utils.log.fine("read "+a+" in "+h);
                final Band[] ab = attrBandTable.get(def);
                // Read one attribute of type def from ab into a byte array.
                buf.reset();
                Object fixups = a.unparse(new Attribute.ValueStream() {
                    public int getInt(int bandIndex) {
                        return ((IntBand) ab[bandIndex]).getInt();
                    }
                    public Entry getRef(int bandIndex) {
                        return ((CPRefBand) ab[bandIndex]).getRef();
                    }
                    public int decodeBCI(int bciCode) {
                        Code code = (Code) h;
                        return code.decodeBCI(bciCode);
                    }
                }, buf);
                // Replace the canonical attr with the one just read.
                j.set(a.addContent(buf.toByteArray(), fixups));
                if (isCV)  setConstantValueIndex(null);  // clean up
            }
        }

        // Mark the bands we just used as done disbursing.
        for (Attribute.Layout def : sawDefs) {
            if (def == null)  continue;  // unused index
            Band[] ab = attrBandTable.get(def);
            for (int j = 0; j < ab.length; j++) {
                ab[j].doneDisbursing();
            }
        }

        if (ctype == ATTR_CONTEXT_CLASS) {
            class_InnerClasses_N.doneDisbursing();
            class_InnerClasses_RC.doneDisbursing();
            class_InnerClasses_F.doneDisbursing();
            class_InnerClasses_outer_RCN.doneDisbursing();
            class_InnerClasses_name_RUN.doneDisbursing();
        }

        MultiBand xxx_attr_bands = attrBands[ctype];
        for (int i = 0; i < xxx_attr_bands.size(); i++) {
            Band b = xxx_attr_bands.get(i);
            if (b instanceof MultiBand)
                b.doneDisbursing();
        }
        xxx_attr_bands.doneDisbursing();
    }

    private
    void readAttrBands(Attribute.Layout.Element[] elems,
                       int count, int[] forwardCounts,
                       Band[] ab)
            throws IOException {
        for (int i = 0; i < elems.length; i++) {
            Attribute.Layout.Element e = elems[i];
            Band eBand = null;
            if (e.hasBand()) {
                eBand = ab[e.bandIndex];
                eBand.expectLength(count);
                eBand.readFrom(in);
            }
            switch (e.kind) {
            case Attribute.EK_REPL:
                // Recursive call.
                int repCount = ((IntBand)eBand).getIntTotal();
                // Note:  getIntTotal makes an extra pass over this band.
                readAttrBands(e.body, repCount, forwardCounts, ab);
                break;
            case Attribute.EK_UN:
                int remainingCount = count;
                for (int j = 0; j < e.body.length; j++) {
                    int caseCount;
                    if (j == e.body.length-1) {
                        caseCount = remainingCount;
                    } else {
                        caseCount = 0;
                        for (int j0 = j;
                             (j == j0)
                             || (j < e.body.length
                                 && e.body[j].flagTest(Attribute.EF_BACK));
                             j++) {
                            caseCount += ((IntBand)eBand).getIntCount(e.body[j].value);
                        }
                        --j;  // back up to last occurrence of this body
                    }
                    remainingCount -= caseCount;
                    readAttrBands(e.body[j].body, caseCount, forwardCounts, ab);
                }
                assert(remainingCount == 0);
                break;
            case Attribute.EK_CALL:
                assert(e.body.length == 1);
                assert(e.body[0].kind == Attribute.EK_CBLE);
                if (!e.flagTest(Attribute.EF_BACK)) {
                    // Backward calls are pre-counted, but forwards are not.
                    // Push the present count forward.
                    assert(forwardCounts[e.value] >= 0);
                    forwardCounts[e.value] += count;
                }
                break;
            case Attribute.EK_CBLE:
                assert(false);
                break;
            }
        }
    }

    void readByteCodes() throws IOException {
        //  bc_bands:
        //        *bc_codes :BYTE1
        //        *bc_case_count :UNSIGNED5
        //        *bc_case_value :DELTA5
        //        *bc_byte :BYTE1
        //        *bc_short :DELTA5
        //        *bc_local :UNSIGNED5
        //        *bc_label :BRANCH5
        //        *bc_intref :DELTA5  (cp_Int)
        //        *bc_floatref :DELTA5  (cp_Float)
        //        *bc_longref :DELTA5  (cp_Long)
        //        *bc_doubleref :DELTA5  (cp_Double)
        //        *bc_stringref :DELTA5  (cp_String)
        //        *bc_classref :UNSIGNED5  (current class or cp_Class)
        //        *bc_fieldref :DELTA5  (cp_Field)
        //        *bc_methodref :UNSIGNED5  (cp_Method)
        //        *bc_imethodref :DELTA5  (cp_Imethod)
        //        *bc_thisfield :UNSIGNED5 (cp_Field, only for current class)
        //        *bc_superfield :UNSIGNED5 (cp_Field, only for current super)
        //        *bc_thismethod :UNSIGNED5 (cp_Method, only for current class)
        //        *bc_supermethod :UNSIGNED5 (cp_Method, only for current super)
        //        *bc_initref :UNSIGNED5 (cp_Field, only for most recent new)
        //        *bc_escref :UNSIGNED5 (cp_All)
        //        *bc_escrefsize :UNSIGNED5
        //        *bc_escsize :UNSIGNED5
        //        *bc_escbyte :BYTE1
        bc_codes.elementCountForDebug = allCodes.length;
        bc_codes.setInputStreamFrom(in);
        readByteCodeOps();  // reads from bc_codes and bc_case_count
        bc_codes.doneDisbursing();

        // All the operand bands have now been sized.  Read them all in turn.
        Band[] operand_bands = {
            bc_case_value,
            bc_byte, bc_short,
            bc_local, bc_label,
            bc_intref, bc_floatref,
            bc_longref, bc_doubleref, bc_stringref,
            bc_loadablevalueref,
            bc_classref, bc_fieldref,
            bc_methodref, bc_imethodref,
            bc_indyref,
            bc_thisfield, bc_superfield,
            bc_thismethod, bc_supermethod,
            bc_initref,
            bc_escref, bc_escrefsize, bc_escsize
        };
        for (int i = 0; i < operand_bands.length; i++) {
            operand_bands[i].readFrom(in);
        }
        bc_escbyte.expectLength(bc_escsize.getIntTotal());
        bc_escbyte.readFrom(in);

        expandByteCodeOps();

        // Done fetching values from operand bands:
        bc_case_count.doneDisbursing();
        for (int i = 0; i < operand_bands.length; i++) {
            operand_bands[i].doneDisbursing();
        }
        bc_escbyte.doneDisbursing();
        bc_bands.doneDisbursing();

        // We must delay the parsing of Code attributes until we
        // have a complete model of bytecodes, for BCI encodings.
        readAttrs(ATTR_CONTEXT_CODE, codesWithFlags);
        // Ditto for exception handlers in codes.
        fixupCodeHandlers();
        // Now we can finish with class_bands; cf. readClasses().
        code_bands.doneDisbursing();
        class_bands.doneDisbursing();
    }

    private void readByteCodeOps() throws IOException {
        // scratch buffer for collecting code::
        byte[] buf = new byte[1<<12];
        // record of all switch opcodes (these are variable-length)
        List<Integer> allSwitchOps = new ArrayList<>();
        for (int k = 0; k < allCodes.length; k++) {
            Code c = allCodes[k];
        scanOneMethod:
            for (int i = 0; ; i++) {
                int bc = bc_codes.getByte();
                if (i + 10 > buf.length)  buf = realloc(buf);
                buf[i] = (byte)bc;
                boolean isWide = false;
                if (bc == _wide) {
                    bc = bc_codes.getByte();
                    buf[++i] = (byte)bc;
                    isWide = true;
                }
                assert(bc == (0xFF & bc));
                // Adjust expectations of various band sizes.
                switch (bc) {
                case _tableswitch:
                case _lookupswitch:
                    bc_case_count.expectMoreLength(1);
                    allSwitchOps.add(bc);
                    break;
                case _iinc:
                    bc_local.expectMoreLength(1);
                    if (isWide)
                        bc_short.expectMoreLength(1);
                    else
                        bc_byte.expectMoreLength(1);
                    break;
                case _sipush:
                    bc_short.expectMoreLength(1);
                    break;
                case _bipush:
                    bc_byte.expectMoreLength(1);
                    break;
                case _newarray:
                    bc_byte.expectMoreLength(1);
                    break;
                case _multianewarray:
                    assert(getCPRefOpBand(bc) == bc_classref);
                    bc_classref.expectMoreLength(1);
                    bc_byte.expectMoreLength(1);
                    break;
                case _ref_escape:
                    bc_escrefsize.expectMoreLength(1);
                    bc_escref.expectMoreLength(1);
                    break;
                case _byte_escape:
                    bc_escsize.expectMoreLength(1);
                    // bc_escbyte will have to be counted too
                    break;
                default:
                    if (Instruction.isInvokeInitOp(bc)) {
                        bc_initref.expectMoreLength(1);
                        break;
                    }
                    if (Instruction.isSelfLinkerOp(bc)) {
                        CPRefBand bc_which = selfOpRefBand(bc);
                        bc_which.expectMoreLength(1);
                        break;
                    }
                    if (Instruction.isBranchOp(bc)) {
                        bc_label.expectMoreLength(1);
                        break;
                    }
                    if (Instruction.isCPRefOp(bc)) {
                        CPRefBand bc_which = getCPRefOpBand(bc);
                        bc_which.expectMoreLength(1);
                        assert(bc != _multianewarray);  // handled elsewhere
                        break;
                    }
                    if (Instruction.isLocalSlotOp(bc)) {
                        bc_local.expectMoreLength(1);
                        break;
                    }
                    break;
                case _end_marker:
                    {
                        // Transfer from buf to a more permanent place:
                        c.bytes = realloc(buf, i);
                        break scanOneMethod;
                    }
                }
            }
        }

        // To size instruction bands correctly, we need info on switches:
        bc_case_count.readFrom(in);
        for (Integer i : allSwitchOps) {
            int bc = i.intValue();
            int caseCount = bc_case_count.getInt();
            bc_label.expectMoreLength(1+caseCount); // default label + cases
            bc_case_value.expectMoreLength(bc == _tableswitch ? 1 : caseCount);
        }
        bc_case_count.resetForSecondPass();
    }

    private void expandByteCodeOps() throws IOException {
        // scratch buffer for collecting code:
        byte[] buf = new byte[1<<12];
        // scratch buffer for collecting instruction boundaries:
        int[] insnMap = new int[1<<12];
        // list of label carriers, for label decoding post-pass:
        int[] labels = new int[1<<10];
        // scratch buffer for registering CP refs:
        Fixups fixupBuf = new Fixups();

        for (int k = 0; k < allCodes.length; k++) {
            Code code = allCodes[k];
            byte[] codeOps = code.bytes;
            code.bytes = null;  // just for now, while we accumulate bits

            Class curClass = code.thisClass();

            Set<Entry> ldcRefSet = ldcRefMap.get(curClass);
            if (ldcRefSet == null)
                ldcRefMap.put(curClass, ldcRefSet = new HashSet<>());

            ClassEntry thisClass  = curClass.thisClass;
            ClassEntry superClass = curClass.superClass;
            ClassEntry newClass   = null;  // class of last _new opcode

            int pc = 0;  // fill pointer in buf; actual bytecode PC
            int numInsns = 0;
            int numLabels = 0;
            boolean hasEscs = false;
            fixupBuf.clear();
            for (int i = 0; i < codeOps.length; i++) {
                int bc = Instruction.getByte(codeOps, i);
                int curPC = pc;
                insnMap[numInsns++] = curPC;
                if (pc + 10 > buf.length)  buf = realloc(buf);
                if (numInsns+10 > insnMap.length)  insnMap = realloc(insnMap);
                if (numLabels+10 > labels.length)  labels = realloc(labels);
                boolean isWide = false;
                if (bc == _wide) {
                    buf[pc++] = (byte) bc;
                    bc = Instruction.getByte(codeOps, ++i);
                    isWide = true;
                }
                switch (bc) {
                case _tableswitch: // apc:  (df, lo, hi, (hi-lo+1)*(label))
                case _lookupswitch: // apc:  (df, nc, nc*(case, label))
                    {
                        int caseCount = bc_case_count.getInt();
                        while ((pc + 30 + caseCount*8) > buf.length)
                            buf = realloc(buf);
                        buf[pc++] = (byte) bc;
                        //initialize apc, df, lo, hi bytes to reasonable bits:
                        Arrays.fill(buf, pc, pc+30, (byte)0);
                        Instruction.Switch isw = (Instruction.Switch)
                            Instruction.at(buf, curPC);
                        //isw.setDefaultLabel(getLabel(bc_label, code, curPC));
                        isw.setCaseCount(caseCount);
                        if (bc == _tableswitch) {
                            isw.setCaseValue(0, bc_case_value.getInt());
                        } else {
                            for (int j = 0; j < caseCount; j++) {
                                isw.setCaseValue(j, bc_case_value.getInt());
                            }
                        }
                        // Make our getLabel calls later.
                        labels[numLabels++] = curPC;
                        pc = isw.getNextPC();
                        continue;
                    }
                case _iinc:
                    {
                        buf[pc++] = (byte) bc;
                        int local = bc_local.getInt();
                        int delta;
                        if (isWide) {
                            delta = bc_short.getInt();
                            Instruction.setShort(buf, pc, local); pc += 2;
                            Instruction.setShort(buf, pc, delta); pc += 2;
                        } else {
                            delta = (byte) bc_byte.getByte();
                            buf[pc++] = (byte)local;
                            buf[pc++] = (byte)delta;
                        }
                        continue;
                    }
                case _sipush:
                    {
                        int val = bc_short.getInt();
                        buf[pc++] = (byte) bc;
                        Instruction.setShort(buf, pc, val); pc += 2;
                        continue;
                    }
                case _bipush:
                case _newarray:
                    {
                        int val = bc_byte.getByte();
                        buf[pc++] = (byte) bc;
                        buf[pc++] = (byte) val;
                        continue;
                    }
                case _ref_escape:
                    {
                        // Note that insnMap has one entry for this.
                        hasEscs = true;
                        int size = bc_escrefsize.getInt();
                        Entry ref = bc_escref.getRef();
                        if (size == 1)  ldcRefSet.add(ref);
                        int fmt;
                        switch (size) {
                        case 1: fixupBuf.addU1(pc, ref); break;
                        case 2: fixupBuf.addU2(pc, ref); break;
                        default: assert(false); fmt = 0;
                        }
                        buf[pc+0] = buf[pc+1] = 0;
                        pc += size;
                    }
                    continue;
                case _byte_escape:
                    {
                        // Note that insnMap has one entry for all these bytes.
                        hasEscs = true;
                        int size = bc_escsize.getInt();
                        while ((pc + size) > buf.length)
                            buf = realloc(buf);
                        while (size-- > 0) {
                            buf[pc++] = (byte) bc_escbyte.getByte();
                        }
                    }
                    continue;
                default:
                    if (Instruction.isInvokeInitOp(bc)) {
                        int idx = (bc - _invokeinit_op);
                        int origBC = _invokespecial;
                        ClassEntry classRef;
                        switch (idx) {
                        case _invokeinit_self_option:
                            classRef = thisClass; break;
                        case _invokeinit_super_option:
                            classRef = superClass; break;
                        default:
                            assert(idx == _invokeinit_new_option);
                            classRef = newClass; break;
                        }
                        buf[pc++] = (byte) origBC;
                        int coding = bc_initref.getInt();
                        // Find the nth overloading of <init> in classRef.
                        MemberEntry ref = pkg.cp.getOverloadingForIndex(CONSTANT_Methodref, classRef, "<init>", coding);
                        fixupBuf.addU2(pc, ref);
                        buf[pc+0] = buf[pc+1] = 0;
                        pc += 2;
                        assert(Instruction.opLength(origBC) == (pc - curPC));
                        continue;
                    }
                    if (Instruction.isSelfLinkerOp(bc)) {
                        int idx = (bc - _self_linker_op);
                        boolean isSuper = (idx >= _self_linker_super_flag);
                        if (isSuper)  idx -= _self_linker_super_flag;
                        boolean isAload = (idx >= _self_linker_aload_flag);
                        if (isAload)  idx -= _self_linker_aload_flag;
                        int origBC = _first_linker_op + idx;
                        boolean isField = Instruction.isFieldOp(origBC);
                        CPRefBand bc_which;
                        ClassEntry which_cls  = isSuper ? superClass : thisClass;
                        Index which_ix;
                        if (isField) {
                            bc_which = isSuper ? bc_superfield  : bc_thisfield;
                            which_ix = pkg.cp.getMemberIndex(CONSTANT_Fieldref, which_cls);
                        } else {
                            bc_which = isSuper ? bc_supermethod : bc_thismethod;
                            which_ix = pkg.cp.getMemberIndex(CONSTANT_Methodref, which_cls);
                        }
                        assert(bc_which == selfOpRefBand(bc));
                        MemberEntry ref = (MemberEntry) bc_which.getRef(which_ix);
                        if (isAload) {
                            buf[pc++] = (byte) _aload_0;
                            curPC = pc;
                            // Note: insnMap keeps the _aload_0 separate.
                            insnMap[numInsns++] = curPC;
                        }
                        buf[pc++] = (byte) origBC;
                        fixupBuf.addU2(pc, ref);
                        buf[pc+0] = buf[pc+1] = 0;
                        pc += 2;
                        assert(Instruction.opLength(origBC) == (pc - curPC));
                        continue;
                    }
                    if (Instruction.isBranchOp(bc)) {
                        buf[pc++] = (byte) bc;
                        assert(!isWide);  // no wide prefix for branches
                        int nextPC = curPC + Instruction.opLength(bc);
                        // Make our getLabel calls later.
                        labels[numLabels++] = curPC;
                        //Instruction.at(buf, curPC).setBranchLabel(getLabel(bc_label, code, curPC));
                        while (pc < nextPC)  buf[pc++] = 0;
                        continue;
                    }
                    if (Instruction.isCPRefOp(bc)) {
                        CPRefBand bc_which = getCPRefOpBand(bc);
                        Entry ref = bc_which.getRef();
                        if (ref == null) {
                            if (bc_which == bc_classref) {
                                // Shorthand for class self-references.
                                ref = thisClass;
                            } else {
                                assert(false);
                            }
                        }
                        int origBC = bc;
                        int size = 2;
                        switch (bc) {
                        case _invokestatic_int:
                            origBC = _invokestatic;
                            break;
                        case _invokespecial_int:
                            origBC = _invokespecial;
                            break;
                        case _ildc:
                        case _cldc:
                        case _fldc:
                        case _sldc:
                        case _qldc:
                            origBC = _ldc;
                            size = 1;
                            ldcRefSet.add(ref);
                            break;
                        case _ildc_w:
                        case _cldc_w:
                        case _fldc_w:
                        case _sldc_w:
                        case _qldc_w:
                            origBC = _ldc_w;
                            break;
                        case _lldc2_w:
                        case _dldc2_w:
                            origBC = _ldc2_w;
                            break;
                        case _new:
                            newClass = (ClassEntry) ref;
                            break;
                        }
                        buf[pc++] = (byte) origBC;
                        int fmt;
                        switch (size) {
                        case 1: fixupBuf.addU1(pc, ref); break;
                        case 2: fixupBuf.addU2(pc, ref); break;
                        default: assert(false); fmt = 0;
                        }
                        buf[pc+0] = buf[pc+1] = 0;
                        pc += size;
                        if (origBC == _multianewarray) {
                            // Copy the trailing byte also.
                            int val = bc_byte.getByte();
                            buf[pc++] = (byte) val;
                        } else if (origBC == _invokeinterface) {
                            int argSize = ((MemberEntry)ref).descRef.typeRef.computeSize(true);
                            buf[pc++] = (byte)( 1 + argSize );
                            buf[pc++] = 0;
                        } else if (origBC == _invokedynamic) {
                            buf[pc++] = 0;
                            buf[pc++] = 0;
                        }
                        assert(Instruction.opLength(origBC) == (pc - curPC));
                        continue;
                    }
                    if (Instruction.isLocalSlotOp(bc)) {
                        buf[pc++] = (byte) bc;
                        int local = bc_local.getInt();
                        if (isWide) {
                            Instruction.setShort(buf, pc, local);
                            pc += 2;
                            if (bc == _iinc) {
                                int iVal = bc_short.getInt();
                                Instruction.setShort(buf, pc, iVal);
                                pc += 2;
                            }
                        } else {
                            Instruction.setByte(buf, pc, local);
                            pc += 1;
                            if (bc == _iinc) {
                                int iVal = bc_byte.getByte();
                                Instruction.setByte(buf, pc, iVal);
                                pc += 1;
                            }
                        }
                        assert(Instruction.opLength(bc) == (pc - curPC));
                        continue;
                    }
                    // Random bytecode.  Just copy it.
                    if (bc >= _bytecode_limit)
                        Utils.log.warning("unrecognized bytescode "+bc
                                            +" "+Instruction.byteName(bc));
                    assert(bc < _bytecode_limit);
                    buf[pc++] = (byte) bc;
                    assert(Instruction.opLength(bc) == (pc - curPC));
                    continue;
                }
            }
            // now make a permanent copy of the bytecodes
            code.setBytes(realloc(buf, pc));
            code.setInstructionMap(insnMap, numInsns);
            // fix up labels, now that code has its insnMap
            Instruction ibr = null;  // temporary branch instruction
            for (int i = 0; i < numLabels; i++) {
                int curPC = labels[i];
                // (Note:  Passing ibr in allows reuse, a speed hack.)
                ibr = Instruction.at(code.bytes, curPC, ibr);
                if (ibr instanceof Instruction.Switch) {
                    Instruction.Switch isw = (Instruction.Switch) ibr;
                    isw.setDefaultLabel(getLabel(bc_label, code, curPC));
                    int caseCount = isw.getCaseCount();
                    for (int j = 0; j < caseCount; j++) {
                        isw.setCaseLabel(j, getLabel(bc_label, code, curPC));
                    }
                } else {
                    ibr.setBranchLabel(getLabel(bc_label, code, curPC));
                }
            }
            if (fixupBuf.size() > 0) {
                if (verbose > 2)
                    Utils.log.fine("Fixups in code: "+fixupBuf);
                code.addFixups(fixupBuf);
            }
        }
    }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java PackageReader.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.