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

Java example source code file (PackageWriter.java)

This example Java source code file (PackageWriter.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

attr_context_limit, class, classentry, cprefband, entry, index, innerclass, intband, ioexception, long_code_header, memberentry, numberentry, printstream, string, util

The PackageWriter.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.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.sun.java.util.jar.pack.Constants.*;

/**
 * Writer for a package file.
 * @author John Rose
 */
class PackageWriter extends BandStructure {
    Package pkg;
    OutputStream finalOut;
    Package.Version packageVersion;

    PackageWriter(Package pkg, OutputStream out) throws IOException {
        this.pkg = pkg;
        this.finalOut = out;
        // Caller has specified maximum class file version in the package:
        initHighestClassVersion(pkg.getHighestClassVersion());
    }

    void write() throws IOException {
        boolean ok = false;
        try {
            if (verbose > 0) {
                Utils.log.info("Setting up constant pool...");
            }
            setup();

            if (verbose > 0) {
                Utils.log.info("Packing...");
            }

            // writeFileHeader() is done last, since it has ultimate counts
            // writeBandHeaders() is called after all other bands are done
            writeConstantPool();
            writeFiles();
            writeAttrDefs();
            writeInnerClasses();
            writeClassesAndByteCodes();
            writeAttrCounts();

            if (verbose > 1)  printCodeHist();

            // choose codings (fill band_headers if needed)
            if (verbose > 0) {
                Utils.log.info("Coding...");
            }
            all_bands.chooseBandCodings();

            // now we can write the headers:
            writeFileHeader();

            writeAllBandsTo(finalOut);

            ok = true;
        } catch (Exception ee) {
            Utils.log.warning("Error on output: "+ee, ee);
            //if (verbose > 0)  ee.printStackTrace();
            // Write partial output only if we are verbose.
            if (verbose > 0)  finalOut.close();
            if (ee instanceof IOException)  throw (IOException)ee;
            if (ee instanceof RuntimeException)  throw (RuntimeException)ee;
            throw new Error("error packing", ee);
        }
    }

    Set<Entry>                       requiredEntries;  // for the CP
    Map<Attribute.Layout, int[]>     backCountTable;   // for layout callables
    int[][]     attrCounts;       // count attr. occurrences

    void setup() {
        requiredEntries = new HashSet<>();
        setArchiveOptions();
        trimClassAttributes();
        collectAttributeLayouts();
        pkg.buildGlobalConstantPool(requiredEntries);
        setBandIndexes();
        makeNewAttributeBands();
        collectInnerClasses();
    }

    /*
     * Convenience function to choose an archive version based
     * on the class file versions observed within the archive
     * or set the user defined version preset via properties.
     */
    void chooseDefaultPackageVersion() throws IOException {
        if (pkg.packageVersion != null) {
            packageVersion = pkg.packageVersion;
            if (verbose > 0) {
                Utils.log.info("package version overridden with: "
                                + packageVersion);
            }
            return;
        }

        Package.Version highV = getHighestClassVersion();
        // set the package version now
        if (highV.lessThan(JAVA6_MAX_CLASS_VERSION)) {
            // There are only old classfiles in this segment or resources
            packageVersion = JAVA5_PACKAGE_VERSION;
        } else if (highV.equals(JAVA6_MAX_CLASS_VERSION) ||
                (highV.equals(JAVA7_MAX_CLASS_VERSION) && !pkg.cp.haveExtraTags())) {
            // force down the package version if we have jdk7 classes without
            // any Indy references, this is because jdk7 class file (51.0) without
            // Indy is identical to jdk6 class file (50.0).
            packageVersion = JAVA6_PACKAGE_VERSION;
        } else if (highV.equals(JAVA7_MAX_CLASS_VERSION)) {
            packageVersion = JAVA7_PACKAGE_VERSION;
        } else {
            // Normal case.  Use the newest archive format, when available
            packageVersion = JAVA8_PACKAGE_VERSION;
        }

        if (verbose > 0) {
            Utils.log.info("Highest version class file: " + highV
                    + " package version: " + packageVersion);
        }
    }

    void checkVersion() throws IOException {
        assert(packageVersion != null);

        if (packageVersion.lessThan(JAVA7_PACKAGE_VERSION)) {
            // this bit was reserved for future use in previous versions
            if (testBit(archiveOptions, AO_HAVE_CP_EXTRAS)) {
                throw new IOException("Format bits for Java 7 must be zero in previous releases");
            }
        }
        if (testBit(archiveOptions, AO_UNUSED_MBZ)) {
            throw new IOException("High archive option bits are reserved and must be zero: " + Integer.toHexString(archiveOptions));
        }
    }

    void setArchiveOptions() {
        // Decide on some archive options early.
        // Does not decide on: AO_HAVE_SPECIAL_FORMATS,
        // AO_HAVE_CP_NUMBERS, AO_HAVE_FILE_HEADERS.
        // Also, AO_HAVE_FILE_OPTIONS may be forced on later.
        int minModtime = pkg.default_modtime;
        int maxModtime = pkg.default_modtime;
        int minOptions = -1;
        int maxOptions = 0;

        // Import defaults from package (deflate hint, etc.).
        archiveOptions |= pkg.default_options;

        for (File file : pkg.files) {
            int modtime = file.modtime;
            int options = file.options;

            if (minModtime == NO_MODTIME) {
                minModtime = maxModtime = modtime;
            } else {
                if (minModtime > modtime)  minModtime = modtime;
                if (maxModtime < modtime)  maxModtime = modtime;
            }
            minOptions &= options;
            maxOptions |= options;
        }
        if (pkg.default_modtime == NO_MODTIME) {
            // Make everything else be a positive offset from here.
            pkg.default_modtime = minModtime;
        }
        if (minModtime != NO_MODTIME && minModtime != maxModtime) {
            // Put them into a band.
            archiveOptions |= AO_HAVE_FILE_MODTIME;
        }
        // If the archive deflation is set do not bother with each file.
        if (!testBit(archiveOptions,AO_DEFLATE_HINT) && minOptions != -1) {
            if (testBit(minOptions, FO_DEFLATE_HINT)) {
                // Every file has the deflate_hint set.
                // Set it for the whole archive, and omit options.
                archiveOptions |= AO_DEFLATE_HINT;
                minOptions -= FO_DEFLATE_HINT;
                maxOptions -= FO_DEFLATE_HINT;
            }
            pkg.default_options |= minOptions;
            if (minOptions != maxOptions
                || minOptions != pkg.default_options) {
                archiveOptions |= AO_HAVE_FILE_OPTIONS;
            }
        }
        // Decide on default version number (majority rule).
        Map<Package.Version, int[]> verCounts = new HashMap<>();
        int bestCount = 0;
        Package.Version bestVersion = null;
        for (Class cls : pkg.classes) {
            Package.Version version = cls.getVersion();
            int[] var = verCounts.get(version);
            if (var == null) {
                var = new int[1];
                verCounts.put(version, var);
            }
            int count = (var[0] += 1);
            //System.out.println("version="+version+" count="+count);
            if (bestCount < count) {
                bestCount = count;
                bestVersion = version;
            }
        }
        verCounts.clear();
        if (bestVersion == null)  bestVersion = JAVA_MIN_CLASS_VERSION;  // degenerate case
        pkg.defaultClassVersion = bestVersion;
        if (verbose > 0)
           Utils.log.info("Consensus version number in segment is " + bestVersion);
        if (verbose > 0)
            Utils.log.info("Highest version number in segment is "
                            + pkg.getHighestClassVersion());

        // Now add explicit pseudo-attrs. to classes with odd versions.
        for (Class cls : pkg.classes) {
            if (!cls.getVersion().equals(bestVersion)) {
                Attribute a = makeClassFileVersionAttr(cls.getVersion());
                if (verbose > 1) {
                    Utils.log.fine("Version "+cls.getVersion() + " of " + cls
                                     + " doesn't match package version "
                                     + bestVersion);
                }
                // Note:  Does not add in "natural" order.  (Who cares?)
                cls.addAttribute(a);
            }
        }

        // Decide if we are transmitting a huge resource file:
        for (File file : pkg.files) {
            long len = file.getFileLength();
            if (len != (int)len) {
                archiveOptions |= AO_HAVE_FILE_SIZE_HI;
                if (verbose > 0)
                   Utils.log.info("Note: Huge resource file "+file.getFileName()+" forces 64-bit sizing");
                break;
            }
        }

        // Decide if code attributes typically have sub-attributes.
        // In that case, to preserve compact 1-byte code headers,
        // we must declare unconditional presence of code flags.
        int cost0 = 0;
        int cost1 = 0;
        for (Class cls : pkg.classes) {
            for (Class.Method m : cls.getMethods()) {
                if (m.code != null) {
                    if (m.code.attributeSize() == 0) {
                        // cost of a useless unconditional flags byte
                        cost1 += 1;
                    } else if (shortCodeHeader(m.code) != LONG_CODE_HEADER) {
                        // cost of inflating a short header
                        cost0 += 3;
                    }
                }
            }
        }
        if (cost0 > cost1) {
            archiveOptions |= AO_HAVE_ALL_CODE_FLAGS;
        }
        if (verbose > 0)
            Utils.log.info("archiveOptions = "
                             +"0b"+Integer.toBinaryString(archiveOptions));
    }

    void writeFileHeader() throws IOException {
        chooseDefaultPackageVersion();
        writeArchiveMagic();
        writeArchiveHeader();
    }

    // Local routine used to format fixed-format scalars
    // in the file_header:
    private void putMagicInt32(int val) throws IOException {
        int res = val;
        for (int i = 0; i < 4; i++) {
            archive_magic.putByte(0xFF & (res >>> 24));
            res <<= 8;
        }
    }

    void writeArchiveMagic() throws IOException {
        putMagicInt32(pkg.magic);
    }

    void writeArchiveHeader() throws IOException {
        // for debug only:  number of words optimized away
        int headerSizeForDebug = AH_LENGTH_MIN;

        // AO_HAVE_SPECIAL_FORMATS is set if non-default
        // coding techniques are used, or if there are
        // compressor-defined attributes transmitted.
        boolean haveSpecial = testBit(archiveOptions, AO_HAVE_SPECIAL_FORMATS);
        if (!haveSpecial) {
            haveSpecial |= (band_headers.length() != 0);
            haveSpecial |= (attrDefsWritten.length != 0);
            if (haveSpecial)
                archiveOptions |= AO_HAVE_SPECIAL_FORMATS;
        }
        if (haveSpecial)
            headerSizeForDebug += AH_SPECIAL_FORMAT_LEN;

        // AO_HAVE_FILE_HEADERS is set if there is any
        // file or segment envelope information present.
        boolean haveFiles = testBit(archiveOptions, AO_HAVE_FILE_HEADERS);
        if (!haveFiles) {
            haveFiles |= (archiveNextCount > 0);
            haveFiles |= (pkg.default_modtime != NO_MODTIME);
            if (haveFiles)
                archiveOptions |= AO_HAVE_FILE_HEADERS;
        }
        if (haveFiles)
            headerSizeForDebug += AH_FILE_HEADER_LEN;

        // AO_HAVE_CP_NUMBERS is set if there are any numbers
        // in the global constant pool.  (Numbers are in 15% of classes.)
        boolean haveNumbers = testBit(archiveOptions, AO_HAVE_CP_NUMBERS);
        if (!haveNumbers) {
            haveNumbers |= pkg.cp.haveNumbers();
            if (haveNumbers)
                archiveOptions |= AO_HAVE_CP_NUMBERS;
        }
        if (haveNumbers)
            headerSizeForDebug += AH_CP_NUMBER_LEN;

        // AO_HAVE_CP_EXTRAS is set if there are constant pool entries
        // beyond the Java 6 version of the class file format.
        boolean haveCPExtra = testBit(archiveOptions, AO_HAVE_CP_EXTRAS);
        if (!haveCPExtra) {
            haveCPExtra |= pkg.cp.haveExtraTags();
            if (haveCPExtra)
                archiveOptions |= AO_HAVE_CP_EXTRAS;
        }
        if (haveCPExtra)
            headerSizeForDebug += AH_CP_EXTRA_LEN;

        // the archiveOptions are all initialized, sanity check now!.
        checkVersion();

        archive_header_0.putInt(packageVersion.minor);
        archive_header_0.putInt(packageVersion.major);
        if (verbose > 0)
            Utils.log.info("Package Version for this segment:" + packageVersion);
        archive_header_0.putInt(archiveOptions); // controls header format
        assert(archive_header_0.length() == AH_LENGTH_0);

        final int DUMMY = 0;
        if (haveFiles) {
            assert(archive_header_S.length() == AH_ARCHIVE_SIZE_HI);
            archive_header_S.putInt(DUMMY); // (archiveSize1 >>> 32)
            assert(archive_header_S.length() == AH_ARCHIVE_SIZE_LO);
            archive_header_S.putInt(DUMMY); // (archiveSize1 >>> 0)
            assert(archive_header_S.length() == AH_LENGTH_S);
        }

        // Done with unsized part of header....

        if (haveFiles) {
            archive_header_1.putInt(archiveNextCount);  // usually zero
            archive_header_1.putInt(pkg.default_modtime);
            archive_header_1.putInt(pkg.files.size());
        } else {
            assert(pkg.files.isEmpty());
        }

        if (haveSpecial) {
            archive_header_1.putInt(band_headers.length());
            archive_header_1.putInt(attrDefsWritten.length);
        } else {
            assert(band_headers.length() == 0);
            assert(attrDefsWritten.length == 0);
        }

        writeConstantPoolCounts(haveNumbers, haveCPExtra);

        archive_header_1.putInt(pkg.getAllInnerClasses().size());
        archive_header_1.putInt(pkg.defaultClassVersion.minor);
        archive_header_1.putInt(pkg.defaultClassVersion.major);
        archive_header_1.putInt(pkg.classes.size());

        // Sanity:  Make sure we came out to 29 (less optional fields):
        assert(archive_header_0.length() +
               archive_header_S.length() +
               archive_header_1.length()
               == headerSizeForDebug);

        // Figure out all the sizes now, first cut:
        archiveSize0 = 0;
        archiveSize1 = all_bands.outputSize();
        // Second cut:
        archiveSize0 += archive_magic.outputSize();
        archiveSize0 += archive_header_0.outputSize();
        archiveSize0 += archive_header_S.outputSize();
        // Make the adjustments:
        archiveSize1 -= archiveSize0;

        // Patch the header:
        if (haveFiles) {
            int archiveSizeHi = (int)(archiveSize1 >>> 32);
            int archiveSizeLo = (int)(archiveSize1 >>> 0);
            archive_header_S.patchValue(AH_ARCHIVE_SIZE_HI, archiveSizeHi);
            archive_header_S.patchValue(AH_ARCHIVE_SIZE_LO, archiveSizeLo);
            int zeroLen = UNSIGNED5.getLength(DUMMY);
            archiveSize0 += UNSIGNED5.getLength(archiveSizeHi) - zeroLen;
            archiveSize0 += UNSIGNED5.getLength(archiveSizeLo) - zeroLen;
        }
        if (verbose > 1)
            Utils.log.fine("archive sizes: "+
                             archiveSize0+"+"+archiveSize1);
        assert(all_bands.outputSize() == archiveSize0+archiveSize1);
    }

    void writeConstantPoolCounts(boolean haveNumbers, boolean haveCPExtra) throws IOException {
        for (byte tag : ConstantPool.TAGS_IN_ORDER) {
            int count = pkg.cp.getIndexByTag(tag).size();
            switch (tag) {
            case CONSTANT_Utf8:
                // The null string is always first.
                if (count > 0)
                    assert(pkg.cp.getIndexByTag(tag).get(0)
                           == ConstantPool.getUtf8Entry(""));
                break;

            case CONSTANT_Integer:
            case CONSTANT_Float:
            case CONSTANT_Long:
            case CONSTANT_Double:
                // Omit counts for numbers if possible.
                if (!haveNumbers) {
                    assert(count == 0);
                    continue;
                }
                break;

            case CONSTANT_MethodHandle:
            case CONSTANT_MethodType:
            case CONSTANT_InvokeDynamic:
            case CONSTANT_BootstrapMethod:
                // Omit counts for newer entities if possible.
                if (!haveCPExtra) {
                    assert(count == 0);
                    continue;
                }
                break;
            }
            archive_header_1.putInt(count);
        }
    }

    protected Index getCPIndex(byte tag) {
        return pkg.cp.getIndexByTag(tag);
    }

// (The following observations are out of date; they apply only to
// "banding" the constant pool itself.  Later revisions of this algorithm
// applied the banding technique to every part of the package file,
// applying the benefits more broadly.)

// Note:  Keeping the data separate in passes (or "bands") allows the
// compressor to issue significantly shorter indexes for repeated data.
// The difference in zipped size is 4%, which is remarkable since the
// unzipped sizes are the same (only the byte order differs).

// After moving similar data into bands, it becomes natural to delta-encode
// each band.  (This is especially useful if we sort the constant pool first.)
// Delta encoding saves an extra 5% in the output size (13% of the CP itself).
// Because a typical delta usees much less data than a byte, the savings after
// zipping is even better:  A zipped delta-encoded package is 8% smaller than
// a zipped non-delta-encoded package.  Thus, in the zipped file, a banded,
// delta-encoded constant pool saves over 11% (of the total file size) compared
// with a zipped unbanded file.

    void writeConstantPool() throws IOException {
        IndexGroup cp = pkg.cp;

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

        for (byte tag : ConstantPool.TAGS_IN_ORDER) {
            Index index = cp.getIndexByTag(tag);

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

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

            switch (tag) {
            case CONSTANT_Utf8:
                writeUtf8Bands(cpMap);
                break;
            case CONSTANT_Integer:
                for (int i = 0; i < cpMap.length; i++) {
                    NumberEntry e = (NumberEntry) cpMap[i];
                    int x = ((Integer)e.numberValue()).intValue();
                    cp_Int.putInt(x);
                }
                break;
            case CONSTANT_Float:
                for (int i = 0; i < cpMap.length; i++) {
                    NumberEntry e = (NumberEntry) cpMap[i];
                    float fx = ((Float)e.numberValue()).floatValue();
                    int x = Float.floatToIntBits(fx);
                    cp_Float.putInt(x);
                }
                break;
            case CONSTANT_Long:
                for (int i = 0; i < cpMap.length; i++) {
                    NumberEntry e = (NumberEntry) cpMap[i];
                    long x = ((Long)e.numberValue()).longValue();
                    cp_Long_hi.putInt((int)(x >>> 32));
                    cp_Long_lo.putInt((int)(x >>> 0));
                }
                break;
            case CONSTANT_Double:
                for (int i = 0; i < cpMap.length; i++) {
                    NumberEntry e = (NumberEntry) cpMap[i];
                    double dx = ((Double)e.numberValue()).doubleValue();
                    long x = Double.doubleToLongBits(dx);
                    cp_Double_hi.putInt((int)(x >>> 32));
                    cp_Double_lo.putInt((int)(x >>> 0));
                }
                break;
            case CONSTANT_String:
                for (int i = 0; i < cpMap.length; i++) {
                    StringEntry e = (StringEntry) cpMap[i];
                    cp_String.putRef(e.ref);
                }
                break;
            case CONSTANT_Class:
                for (int i = 0; i < cpMap.length; i++) {
                    ClassEntry e = (ClassEntry) cpMap[i];
                    cp_Class.putRef(e.ref);
                }
                break;
            case CONSTANT_Signature:
                writeSignatureBands(cpMap);
                break;
            case CONSTANT_NameandType:
                for (int i = 0; i < cpMap.length; i++) {
                    DescriptorEntry e = (DescriptorEntry) cpMap[i];
                    cp_Descr_name.putRef(e.nameRef);
                    cp_Descr_type.putRef(e.typeRef);
                }
                break;
            case CONSTANT_Fieldref:
                writeMemberRefs(tag, cpMap, cp_Field_class, cp_Field_desc);
                break;
            case CONSTANT_Methodref:
                writeMemberRefs(tag, cpMap, cp_Method_class, cp_Method_desc);
                break;
            case CONSTANT_InterfaceMethodref:
                writeMemberRefs(tag, cpMap, cp_Imethod_class, cp_Imethod_desc);
                break;
            case CONSTANT_MethodHandle:
                for (int i = 0; i < cpMap.length; i++) {
                    MethodHandleEntry e = (MethodHandleEntry) cpMap[i];
                    cp_MethodHandle_refkind.putInt(e.refKind);
                    cp_MethodHandle_member.putRef(e.memRef);
                }
                break;
            case CONSTANT_MethodType:
                for (int i = 0; i < cpMap.length; i++) {
                    MethodTypeEntry e = (MethodTypeEntry) cpMap[i];
                    cp_MethodType.putRef(e.typeRef);
                }
                break;
            case CONSTANT_InvokeDynamic:
                for (int i = 0; i < cpMap.length; i++) {
                    InvokeDynamicEntry e = (InvokeDynamicEntry) cpMap[i];
                    cp_InvokeDynamic_spec.putRef(e.bssRef);
                    cp_InvokeDynamic_desc.putRef(e.descRef);
                }
                break;
            case CONSTANT_BootstrapMethod:
                for (int i = 0; i < cpMap.length; i++) {
                    BootstrapMethodEntry e = (BootstrapMethodEntry) cpMap[i];
                    cp_BootstrapMethod_ref.putRef(e.bsmRef);
                    cp_BootstrapMethod_arg_count.putInt(e.argRefs.length);
                    for (Entry argRef : e.argRefs) {
                        cp_BootstrapMethod_arg.putRef(argRef);
                    }
                }
                break;
            default:
                throw new AssertionError("unexpected CP tag in package");
            }
        }
        if (optDumpBands || verbose > 1) {
            for (byte tag = CONSTANT_GroupFirst; tag < CONSTANT_GroupLimit; tag++) {
                Index index = 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);
                    }
                }
            }
        }
    }

    void writeUtf8Bands(Entry[] cpMap) throws IOException {
        if (cpMap.length == 0)
            return;  // nothing to write

        // The first element must always be the empty string.
        assert(cpMap[0].stringValue().equals(""));
        final int SUFFIX_SKIP_1 = 1;
        final int PREFIX_SKIP_2 = 2;

        // Fetch the char arrays, first of all.
        char[][] chars = new char[cpMap.length][];
        for (int i = 0; i < chars.length; i++) {
            chars[i] = cpMap[i].stringValue().toCharArray();
        }

        // First band:  Write lengths of shared prefixes.
        int[] prefixes = new int[cpMap.length];  // includes 2 skipped zeroes
        char[] prevChars = {};
        for (int i = 0; i < chars.length; i++) {
            int prefix = 0;
            char[] curChars = chars[i];
            int limit = Math.min(curChars.length, prevChars.length);
            while (prefix < limit && curChars[prefix] == prevChars[prefix])
                prefix++;
            prefixes[i] = prefix;
            if (i >= PREFIX_SKIP_2)
                cp_Utf8_prefix.putInt(prefix);
            else
                assert(prefix == 0);
            prevChars = curChars;
        }

        // Second band:  Write lengths of unshared suffixes.
        // Third band:  Write the char values in the unshared suffixes.
        for (int i = 0; i < chars.length; i++) {
            char[] str = chars[i];
            int prefix = prefixes[i];
            int suffix = str.length - prefixes[i];
            boolean isPacked = false;
            if (suffix == 0) {
                // Zero suffix length is special flag to indicate
                // separate treatment in cp_Utf8_big bands.
                // This suffix length never occurs naturally,
                // except in the one case of a zero-length string.
                // (If it occurs, it is the first, due to sorting.)
                // The zero length string must, paradoxically, be
                // encoded as a zero-length cp_Utf8_big band.
                // This wastes exactly (& tolerably) one null byte.
                isPacked = (i >= SUFFIX_SKIP_1);
                // Do not bother to add an empty "(Utf8_big_0)" band.
                // Also, the initial empty string does not require a band.
            } else if (optBigStrings && effort > 1 && suffix > 100) {
                int numWide = 0;
                for (int n = 0; n < suffix; n++) {
                    if (str[prefix+n] > 127) {
                        numWide++;
                    }
                }
                if (numWide > 100) {
                    // Try packing the chars with an alternate encoding.
                    isPacked = tryAlternateEncoding(i, numWide, str, prefix);
                }
            }
            if (i < SUFFIX_SKIP_1) {
                // No output.
                assert(!isPacked);
                assert(suffix == 0);
            } else if (isPacked) {
                // Mark packed string with zero-length suffix count.
                // This tells the unpacker to go elsewhere for the suffix bits.
                // Fourth band:  Write unshared suffix with alternate coding.
                cp_Utf8_suffix.putInt(0);
                cp_Utf8_big_suffix.putInt(suffix);
            } else {
                assert(suffix != 0);  // would be ambiguous
                // Normal string.  Save suffix in third and fourth bands.
                cp_Utf8_suffix.putInt(suffix);
                for (int n = 0; n < suffix; n++) {
                    int ch = str[prefix+n];
                    cp_Utf8_chars.putInt(ch);
                }
            }
        }
        if (verbose > 0) {
            int normCharCount = cp_Utf8_chars.length();
            int packCharCount = cp_Utf8_big_chars.length();
            int charCount = normCharCount + packCharCount;
            Utils.log.info("Utf8string #CHARS="+charCount+" #PACKEDCHARS="+packCharCount);
        }
    }

    private boolean tryAlternateEncoding(int i, int numWide,
                                         char[] str, int prefix) {
        int suffix = str.length - prefix;
        int[] cvals = new int[suffix];
        for (int n = 0; n < suffix; n++) {
            cvals[n] = str[prefix+n];
        }
        CodingChooser cc = getCodingChooser();
        Coding bigRegular = cp_Utf8_big_chars.regularCoding;
        String bandName = "(Utf8_big_"+i+")";
        int[] sizes = { 0, 0 };
        final int BYTE_SIZE = CodingChooser.BYTE_SIZE;
        final int ZIP_SIZE = CodingChooser.ZIP_SIZE;
        if (verbose > 1 || cc.verbose > 1) {
            Utils.log.fine("--- chooseCoding "+bandName);
        }
        CodingMethod special = cc.choose(cvals, bigRegular, sizes);
        Coding charRegular = cp_Utf8_chars.regularCoding;
        if (verbose > 1)
            Utils.log.fine("big string["+i+"] len="+suffix+" #wide="+numWide+" size="+sizes[BYTE_SIZE]+"/z="+sizes[ZIP_SIZE]+" coding "+special);
        if (special != charRegular) {
            int specialZipSize = sizes[ZIP_SIZE];
            int[] normalSizes = cc.computeSize(charRegular, cvals);
            int normalZipSize = normalSizes[ZIP_SIZE];
            int minWin = Math.max(5, normalZipSize/1000);
            if (verbose > 1)
                Utils.log.fine("big string["+i+"] normalSize="+normalSizes[BYTE_SIZE]+"/z="+normalSizes[ZIP_SIZE]+" win="+(specialZipSize<normalZipSize-minWin));
            if (specialZipSize < normalZipSize-minWin) {
                IntBand big = cp_Utf8_big_chars.newIntBand(bandName);
                big.initializeValues(cvals);
                return true;
            }
        }
        return false;
    }

    void writeSignatureBands(Entry[] cpMap) throws IOException {
        for (int i = 0; i < cpMap.length; i++) {
            SignatureEntry e = (SignatureEntry) cpMap[i];
            cp_Signature_form.putRef(e.formRef);
            for (int j = 0; j < e.classRefs.length; j++) {
                cp_Signature_classes.putRef(e.classRefs[j]);
            }
        }
    }

    void writeMemberRefs(byte tag, Entry[] cpMap, CPRefBand cp_class, CPRefBand cp_desc) throws IOException {
        for (int i = 0; i < cpMap.length; i++) {
            MemberEntry e = (MemberEntry) cpMap[i];
            cp_class.putRef(e.classRef);
            cp_desc.putRef(e.descRef);
        }
    }

    void writeFiles() throws IOException {
        int numFiles = pkg.files.size();
        if (numFiles == 0)  return;
        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 (!haveOptions) {
            for (File file : pkg.files) {
                if (file.isClassStub()) {
                    haveOptions = true;
                    options |= AO_HAVE_FILE_OPTIONS;
                    archiveOptions = options;
                    break;
                }
            }
        }
        if (haveSizeHi || haveModtime || haveOptions || !pkg.files.isEmpty()) {
            options |= AO_HAVE_FILE_HEADERS;
            archiveOptions = options;
        }
        for (File file : pkg.files) {
            file_name.putRef(file.name);
            long len = file.getFileLength();
            file_size_lo.putInt((int)len);
            if (haveSizeHi)
                file_size_hi.putInt((int)(len >>> 32));
            if (haveModtime)
                file_modtime.putInt(file.modtime - pkg.default_modtime);
            if (haveOptions)
                file_options.putInt(file.options);
            file.writeTo(file_bits.collectorStream());
            if (verbose > 1)
                Utils.log.fine("Wrote "+len+" bytes of "+file.name.stringValue());
        }
        if (verbose > 0)
            Utils.log.info("Wrote "+numFiles+" resource files");
    }

    void collectAttributeLayouts() {
        maxFlags = new int[ATTR_CONTEXT_LIMIT];
        allLayouts = new FixedList<>(ATTR_CONTEXT_LIMIT);
        for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) {
            allLayouts.set(i, new HashMap<Attribute.Layout, int[]>());
        }
        // Collect maxFlags and allLayouts.
        for (Class cls : pkg.classes) {
            visitAttributeLayoutsIn(ATTR_CONTEXT_CLASS, cls);
            for (Class.Field f : cls.getFields()) {
                visitAttributeLayoutsIn(ATTR_CONTEXT_FIELD, f);
            }
            for (Class.Method m : cls.getMethods()) {
                visitAttributeLayoutsIn(ATTR_CONTEXT_METHOD, m);
                if (m.code != null) {
                    visitAttributeLayoutsIn(ATTR_CONTEXT_CODE, m.code);
                }
            }
        }
        // If there are many species of attributes, use 63-bit flags.
        for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) {
            int nl = allLayouts.get(i).size();
            boolean haveLongFlags = haveFlagsHi(i);
            final int TOO_MANY_ATTRS = 32 /*int flag size*/
                - 12 /*typical flag bits in use*/
                + 4  /*typical number of OK overflows*/;
            if (nl >= TOO_MANY_ATTRS) {  // heuristic
                int mask = 1<<(LG_AO_HAVE_XXX_FLAGS_HI+i);
                archiveOptions |= mask;
                haveLongFlags = true;
                if (verbose > 0)
                   Utils.log.info("Note: Many "+Attribute.contextName(i)+" attributes forces 63-bit flags");
            }
            if (verbose > 1) {
                Utils.log.fine(Attribute.contextName(i)+".maxFlags = 0x"+Integer.toHexString(maxFlags[i]));
                Utils.log.fine(Attribute.contextName(i)+".#layouts = "+nl);
            }
            assert(haveFlagsHi(i) == haveLongFlags);
        }
        initAttrIndexLimit();

        // Standard indexes can never conflict with flag bits.  Assert it.
        for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) {
            assert((attrFlagMask[i] & maxFlags[i]) == 0);
        }
        // Collect counts for both predefs. and custom defs.
        // Decide on custom, local attribute definitions.
        backCountTable = new HashMap<>();
        attrCounts = new int[ATTR_CONTEXT_LIMIT][];
        for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) {
            // Now the remaining defs in allLayouts[i] need attr. indexes.
            // Fill up unused flag bits with new defs.
            // Unused bits are those which are not used by predefined attrs,
            // and which are always clear in the classfiles.
            long avHiBits = ~(maxFlags[i] | attrFlagMask[i]);
            assert(attrIndexLimit[i] > 0);
            assert(attrIndexLimit[i] < 64);  // all bits fit into a Java long
            avHiBits &= (1L<() {
                public int compare(Map.Entry<Attribute.Layout, int[]> e0,
                                   Map.Entry<Attribute.Layout, int[]> e1) {
                    // Primary sort key is count, reversed.
                    int r = -(e0.getValue()[0] - e1.getValue()[0]);
                    if (r != 0)  return r;
                    return e0.getKey().compareTo(e1.getKey());
                }
            });
            attrCounts[i] = new int[attrIndexLimit[i]+layoutsAndCounts.length];
            for (int j = 0; j < layoutsAndCounts.length; j++) {
                Map.Entry<Attribute.Layout, int[]> e = layoutsAndCounts[j];
                Attribute.Layout def = e.getKey();
                int count = e.getValue()[0];
                int index;
                Integer predefIndex = attrIndexTable.get(def);
                if (predefIndex != null) {
                    // The index is already set.
                    index = predefIndex.intValue();
                } else if (avHiBits != 0) {
                    while ((avHiBits & 1) == 0) {
                        avHiBits >>>= 1;
                        nextLoBit += 1;
                    }
                    avHiBits -= 1;  // clear low bit; we are using it now
                    // Update attrIndexTable:
                    index = setAttributeLayoutIndex(def, nextLoBit);
                } else {
                    // Update attrIndexTable:
                    index = setAttributeLayoutIndex(def, ATTR_INDEX_OVERFLOW);
                }

                // Now that we know the index, record the count of this def.
                attrCounts[i][index] = count;

                // For all callables in the def, keep a tally of back-calls.
                Attribute.Layout.Element[] cbles = def.getCallables();
                final int[] bc = new int[cbles.length];
                for (int k = 0; k < cbles.length; k++) {
                    assert(cbles[k].kind == Attribute.EK_CBLE);
                    if (!cbles[k].flagTest(Attribute.EF_BACK)) {
                        bc[k] = -1;  // no count to accumulate here
                    }
                }
                backCountTable.put(def, bc);

                if (predefIndex == null) {
                    // Make sure the package CP can name the local attribute.
                    Entry ne = ConstantPool.getUtf8Entry(def.name());
                    String layout = def.layoutForClassVersion(getHighestClassVersion());
                    Entry le = ConstantPool.getUtf8Entry(layout);
                    requiredEntries.add(ne);
                    requiredEntries.add(le);
                    if (verbose > 0) {
                        if (index < attrIndexLimit[i])
                           Utils.log.info("Using free flag bit 1<<"+index+" for "+count+" occurrences of "+def);
                        else
                            Utils.log.info("Using overflow index "+index+" for "+count+" occurrences of "+def);
                    }
                }
            }
        }
        // Later, when emitting attr_definition_bands, we will look at
        // attrDefSeen and attrDefs at position 32/63 and beyond.
        // The attrIndexTable will provide elements of xxx_attr_indexes bands.

        // Done with scratch variables:
        maxFlags = null;
        allLayouts = null;
    }

    // Scratch variables for processing attributes and flags.
    int[] maxFlags;
    List<Map allLayouts;

    void visitAttributeLayoutsIn(int ctype, Attribute.Holder h) {
        // Make note of which flags appear in the class file.
        // Set them in maxFlags.
        maxFlags[ctype] |= h.flags;
        for (Attribute a : h.getAttributes()) {
            Attribute.Layout def = a.layout();
            Map<Attribute.Layout, int[]> defMap = allLayouts.get(ctype);
            int[] count = defMap.get(def);
            if (count == null) {
                defMap.put(def, count = new int[1]);
            }
            if (count[0] < Integer.MAX_VALUE) {
                count[0] += 1;
            }
        }
    }

    Attribute.Layout[] attrDefsWritten;

    void writeAttrDefs() throws IOException {
        List<Object[]> defList = new ArrayList<>();
        for (int i = 0; i < ATTR_CONTEXT_LIMIT; i++) {
            int limit = attrDefs.get(i).size();
            for (int j = 0; j < limit; j++) {
                int header = i;  // ctype
                if (j < attrIndexLimit[i]) {
                    header |= ((j + ADH_BIT_IS_LSB) << ADH_BIT_SHIFT);
                    assert(header < 0x100);  // must fit into a byte
                    // (...else header is simply ctype, with zero high bits.)
                    if (!testBit(attrDefSeen[i], 1L< ADH_BIT_SHIFT) - ADH_BIT_IS_LSB;
                    if (hdrIndex < 0)  hdrIndex = indexForDebug[def.ctype()]++;
                    int realIndex = (attrIndexTable.get(def)).intValue();
                    assert(hdrIndex == realIndex);
                }
                if (dump != null) {
                    int index = (header >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB;
                    dump.println(index+" "+def);
                }
            }
        }
    }

    void writeAttrCounts() throws IOException {
        // Write the four xxx_attr_calls bands.
        for (int ctype = 0; ctype < ATTR_CONTEXT_LIMIT; ctype++) {
            MultiBand xxx_attr_bands = attrBands[ctype];
            IntBand xxx_attr_calls = getAttrBand(xxx_attr_bands, AB_ATTR_CALLS);
            Attribute.Layout[] defs = new Attribute.Layout[attrDefs.get(ctype).size()];
            attrDefs.get(ctype).toArray(defs);
            for (boolean predef = true; ; predef = false) {
                for (int ai = 0; ai < defs.length; ai++) {
                    Attribute.Layout def = defs[ai];
                    if (def == null)  continue;  // unused index
                    if (predef != isPredefinedAttr(ctype, ai))
                        continue;  // wrong pass
                    int totalCount = attrCounts[ctype][ai];
                    if (totalCount == 0)
                        continue;  // irrelevant
                    int[] bc = backCountTable.get(def);
                    for (int j = 0; j < bc.length; j++) {
                        if (bc[j] >= 0) {
                            int backCount = bc[j];
                            bc[j] = -1;  // close out; do not collect further counts
                            xxx_attr_calls.putInt(backCount);
                            assert(def.getCallables()[j].flagTest(Attribute.EF_BACK));
                        } else {
                            assert(!def.getCallables()[j].flagTest(Attribute.EF_BACK));
                        }
                    }
                }
                if (!predef)  break;
            }
        }
    }

    void trimClassAttributes() {
        for (Class cls : pkg.classes) {
            // Replace "obvious" SourceFile attrs by null.
            cls.minimizeSourceFile();
            // BootstrapMethods should never have been inserted.
            assert(cls.getAttribute(Package.attrBootstrapMethodsEmpty) == null);
        }
    }

    void collectInnerClasses() {
        // Capture inner classes, removing them from individual classes.
        // Irregular inner classes must stay local, though.
        Map<ClassEntry, InnerClass> allICMap = new HashMap<>();
        // First, collect a consistent global set.
        for (Class cls : pkg.classes) {
            if (!cls.hasInnerClasses())  continue;
            for (InnerClass ic : cls.getInnerClasses()) {
                InnerClass pic = allICMap.put(ic.thisClass, ic);
                if (pic != null && !pic.equals(ic) && pic.predictable) {
                    // Different ICs.  Choose the better to make global.
                    allICMap.put(pic.thisClass, pic);
                }
            }
        }

        InnerClass[] allICs = new InnerClass[allICMap.size()];
        allICMap.values().toArray(allICs);
        allICMap = null;  // done with it

        // Note: The InnerClasses attribute must be in a valid order,
        // so that A$B always occurs earlier than A$B$C.  This is an
        // important side-effect of sorting lexically by class name.
        Arrays.sort(allICs);  // put in canonical order
        pkg.setAllInnerClasses(Arrays.asList(allICs));

        // Next, empty out of every local set the consistent entries.
        // Calculate whether there is any remaining need to have a local
        // set, and whether it needs to be locked.
        for (Class cls : pkg.classes) {
            cls.minimizeLocalICs();
        }
    }

    void writeInnerClasses() throws IOException {
        for (InnerClass ic : pkg.getAllInnerClasses()) {
            int flags = ic.flags;
            assert((flags & ACC_IC_LONG_FORM) == 0);
            if (!ic.predictable) {
                flags |= ACC_IC_LONG_FORM;
            }
            ic_this_class.putRef(ic.thisClass);
            ic_flags.putInt(flags);
            if (!ic.predictable) {
                ic_outer_class.putRef(ic.outerClass);
                ic_name.putRef(ic.name);
            }
        }
    }

    /** If there are any extra InnerClasses entries to write which are
     *  not already implied by the global table, put them into a
     *  local attribute.  This is expected to be rare.
     */
    void writeLocalInnerClasses(Class cls) throws IOException {
        List<InnerClass> localICs = cls.getInnerClasses();
        class_InnerClasses_N.putInt(localICs.size());
        for(InnerClass ic : localICs) {
            class_InnerClasses_RC.putRef(ic.thisClass);
            // Is it redundant with the global version?
            if (ic.equals(pkg.getGlobalInnerClass(ic.thisClass))) {
                // A zero flag means copy a global IC here.
                class_InnerClasses_F.putInt(0);
            } else {
                int flags = ic.flags;
                if (flags == 0)
                    flags = ACC_IC_LONG_FORM;  // force it to be non-zero
                class_InnerClasses_F.putInt(flags);
                class_InnerClasses_outer_RCN.putRef(ic.outerClass);
                class_InnerClasses_name_RUN.putRef(ic.name);
            }
        }
    }

    void writeClassesAndByteCodes() throws IOException {
        Class[] classes = new Class[pkg.classes.size()];
        pkg.classes.toArray(classes);
        // Note:  This code respects the order in which caller put classes.
        if (verbose > 0)
            Utils.log.info("  ...scanning "+classes.length+" classes...");

        int nwritten = 0;
        for (int i = 0; i < classes.length; i++) {
            // Collect the class body, sans bytecodes.
            Class cls = classes[i];
            if (verbose > 1)
                Utils.log.fine("Scanning "+cls);

            ClassEntry   thisClass  = cls.thisClass;
            ClassEntry   superClass = cls.superClass;
            ClassEntry[] interfaces = cls.interfaces;
            // Encode rare case of null superClass as thisClass:
            assert(superClass != thisClass);  // bad class file!?
            if (superClass == null)  superClass = thisClass;
            class_this.putRef(thisClass);
            class_super.putRef(superClass);
            class_interface_count.putInt(cls.interfaces.length);
            for (int j = 0; j < interfaces.length; j++) {
                class_interface.putRef(interfaces[j]);
            }

            writeMembers(cls);
            writeAttrs(ATTR_CONTEXT_CLASS, cls, cls);

            nwritten++;
            if (verbose > 0 && (nwritten % 1000) == 0)
                Utils.log.info("Have scanned "+nwritten+" classes...");
        }
    }

    void writeMembers(Class cls) throws IOException {
        List<Class.Field> fields = cls.getFields();
        class_field_count.putInt(fields.size());
        for (Class.Field f : fields) {
            field_descr.putRef(f.getDescriptor());
            writeAttrs(ATTR_CONTEXT_FIELD, f, cls);
        }

        List<Class.Method> methods = cls.getMethods();
        class_method_count.putInt(methods.size());
        for (Class.Method m : methods) {
            method_descr.putRef(m.getDescriptor());
            writeAttrs(ATTR_CONTEXT_METHOD, m, cls);
            assert((m.code != null) == (m.getAttribute(attrCodeEmpty) != null));
            if (m.code != null) {
                writeCodeHeader(m.code);
                writeByteCodes(m.code);
            }
        }
    }

    void writeCodeHeader(Code c) throws IOException {
        boolean attrsOK = testBit(archiveOptions, AO_HAVE_ALL_CODE_FLAGS);
        int na = c.attributeSize();
        int sc = shortCodeHeader(c);
        if (!attrsOK && na > 0)
            // We must write flags, and can only do so for long headers.
            sc = LONG_CODE_HEADER;
        if (verbose > 2) {
            int siglen = c.getMethod().getArgumentSize();
            Utils.log.fine("Code sizes info "+c.max_stack+" "+c.max_locals+" "+c.getHandlerCount()+" "+siglen+" "+na+(sc > 0 ? " SHORT="+sc : ""));
        }
        code_headers.putByte(sc);
        if (sc == LONG_CODE_HEADER) {
            code_max_stack.putInt(c.getMaxStack());
            code_max_na_locals.putInt(c.getMaxNALocals());
            code_handler_count.putInt(c.getHandlerCount());
        } else {
            assert(attrsOK || na == 0);
            assert(c.getHandlerCount() < shortCodeHeader_h_limit);
        }
        writeCodeHandlers(c);
        if (sc == LONG_CODE_HEADER || attrsOK)
            writeAttrs(ATTR_CONTEXT_CODE, c, c.thisClass());
    }

    void writeCodeHandlers(Code c) throws IOException {
        int sum, del;
        for (int j = 0, jmax = c.getHandlerCount(); j < jmax; j++) {
            code_handler_class_RCN.putRef(c.handler_class[j]); // null OK
            // Encode end as offset from start, and catch as offset from end,
            // because they are strongly correlated.
            sum = c.encodeBCI(c.handler_start[j]);
            code_handler_start_P.putInt(sum);
            del = c.encodeBCI(c.handler_end[j]) - sum;
            code_handler_end_PO.putInt(del);
            sum += del;
            del = c.encodeBCI(c.handler_catch[j]) - sum;
            code_handler_catch_PO.putInt(del);
        }
    }

    // Generic routines for writing attributes and flags of
    // classes, fields, methods, and codes.
    void writeAttrs(int ctype,
                    final Attribute.Holder h,
                    Class cls) throws IOException {
        MultiBand xxx_attr_bands = attrBands[ctype];
        IntBand xxx_flags_hi = getAttrBand(xxx_attr_bands, AB_FLAGS_HI);
        IntBand xxx_flags_lo = getAttrBand(xxx_attr_bands, AB_FLAGS_LO);
        boolean haveLongFlags = haveFlagsHi(ctype);
        assert(attrIndexLimit[ctype] == (haveLongFlags? 63: 32));
        if (h.attributes == null) {
            xxx_flags_lo.putInt(h.flags);  // no extra bits to set here
            if (haveLongFlags)
                xxx_flags_hi.putInt(0);
            return;
        }
        if (verbose > 3)
            Utils.log.fine("Transmitting attrs for "+h+" flags="+Integer.toHexString(h.flags));

        long flagMask = attrFlagMask[ctype];  // which flags are attr bits?
        long flagsToAdd = 0;
        int overflowCount = 0;
        for (Attribute a : h.attributes) {
            Attribute.Layout def = a.layout();
            int index = (attrIndexTable.get(def)).intValue();
            assert(attrDefs.get(ctype).get(index) == def);
            if (verbose > 3)
                Utils.log.fine("add attr @"+index+" "+a+" in "+h);
            if (index < attrIndexLimit[ctype] && testBit(flagMask, 1L<> 32));
        else
            assert((flagsToAdd >>> 32) == 0);
        assert((h.flags & flagsToAdd) == 0)
            : (h+".flags="
                +Integer.toHexString(h.flags)+"^"
                +Long.toHexString(flagsToAdd));
    }

    // temporary scratch variables for processing code blocks
    private Code                 curCode;
    private Class                curClass;
    private Entry[] curCPMap;
    private void beginCode(Code c) {
        assert(curCode == null);
        curCode = c;
        curClass = c.m.thisClass();
        curCPMap = c.getCPMap();
    }
    private void endCode() {
        curCode = null;
        curClass = null;
        curCPMap = null;
    }

    // Return an _invokeinit_op variant, if the instruction matches one,
    // else -1.
    private int initOpVariant(Instruction i, Entry newClass) {
        if (i.getBC() != _invokespecial)  return -1;
        MemberEntry ref = (MemberEntry) i.getCPRef(curCPMap);
        if ("<init>".equals(ref.descRef.nameRef.stringValue()) == false)
            return -1;
        ClassEntry refClass = ref.classRef;
        if (refClass == curClass.thisClass)
            return _invokeinit_op+_invokeinit_self_option;
        if (refClass == curClass.superClass)
            return _invokeinit_op+_invokeinit_super_option;
        if (refClass == newClass)
            return _invokeinit_op+_invokeinit_new_option;
        return -1;
    }

    // Return a _self_linker_op variant, if the instruction matches one,
    // else -1.
    private int selfOpVariant(Instruction i) {
        int bc = i.getBC();
        if (!(bc >= _first_linker_op && bc <= _last_linker_op))  return -1;
        MemberEntry ref = (MemberEntry) i.getCPRef(curCPMap);
        // do not optimize this case, simply fall back to regular coding
        if ((bc == _invokespecial || bc == _invokestatic) &&
                ref.tagEquals(CONSTANT_InterfaceMethodref))
            return -1;
        ClassEntry refClass = ref.classRef;
        int self_bc = _self_linker_op + (bc - _first_linker_op);
        if (refClass == curClass.thisClass)
            return self_bc;
        if (refClass == curClass.superClass)
            return self_bc + _self_linker_super_flag;
        return -1;
    }

    void writeByteCodes(Code code) throws IOException {
        beginCode(code);
        IndexGroup cp = pkg.cp;

        // true if the previous instruction is an aload to absorb
        boolean prevAload = false;

        // class of most recent new; helps compress <init> calls
        Entry newClass = null;

        for (Instruction i = code.instructionAt(0); i != null; i = i.next()) {
            // %%% Add a stress mode which issues _ref/_byte_escape.
            if (verbose > 3)  Utils.log.fine(i.toString());

            if (i.isNonstandard()) {
                // Crash and burn with a complaint if there are funny
                // bytecodes in this class file.
                String complaint = code.getMethod()
                    +" contains an unrecognized bytecode "+i
                    +"; please use the pass-file option on this class.";
                Utils.log.warning(complaint);
                throw new IOException(complaint);
            }

            if (i.isWide()) {
                if (verbose > 1) {
                    Utils.log.fine("_wide opcode in "+code);
                    Utils.log.fine(i.toString());
                }
                bc_codes.putByte(_wide);
                codeHist[_wide]++;
            }

            int bc = i.getBC();

            // Begin "bc_linker" compression.
            if (bc == _aload_0) {
                // Try to group aload_0 with a following operation.
                Instruction ni = code.instructionAt(i.getNextPC());
                if (selfOpVariant(ni) >= 0) {
                    prevAload = true;
                    continue;
                }
            }

            // Test for <init> invocations:
            int init_bc = initOpVariant(i, newClass);
            if (init_bc >= 0) {
                if (prevAload) {
                    // get rid of it
                    bc_codes.putByte(_aload_0);
                    codeHist[_aload_0]++;
                    prevAload = false;  //used up
                }
                // Write special bytecode.
                bc_codes.putByte(init_bc);
                codeHist[init_bc]++;
                MemberEntry ref = (MemberEntry) i.getCPRef(curCPMap);
                // Write operand to a separate band.
                int coding = cp.getOverloadingIndex(ref);
                bc_initref.putInt(coding);
                continue;
            }

            int self_bc = selfOpVariant(i);
            if (self_bc >= 0) {
                boolean isField = Instruction.isFieldOp(bc);
                boolean isSuper = (self_bc >= _self_linker_op+_self_linker_super_flag);
                boolean isAload = prevAload;
                prevAload = false;  //used up
                if (isAload)
                    self_bc += _self_linker_aload_flag;
                // Write special bytecode.
                bc_codes.putByte(self_bc);
                codeHist[self_bc]++;
                // Write field or method ref to a separate band.
                MemberEntry ref = (MemberEntry) i.getCPRef(curCPMap);
                CPRefBand bc_which = selfOpRefBand(self_bc);
                Index which_ix = cp.getMemberIndex(ref.tag, ref.classRef);
                bc_which.putRef(ref, which_ix);
                continue;
            }
            assert(!prevAload);
            // End "bc_linker" compression.

            // Normal bytecode.
            codeHist[bc]++;
            switch (bc) {
            case _tableswitch: // apc:  (df, lo, hi, (hi-lo+1)*(label))
            case _lookupswitch: // apc:  (df, nc, nc*(case, label))
                bc_codes.putByte(bc);
                Instruction.Switch isw = (Instruction.Switch) i;
                // Note that we do not write the alignment bytes.
                int apc = isw.getAlignedPC();
                int npc = isw.getNextPC();
                // write a length specification into the bytecode stream
                int caseCount = isw.getCaseCount();
                bc_case_count.putInt(caseCount);
                putLabel(bc_label, code, i.getPC(), isw.getDefaultLabel());
                for (int j = 0; j < caseCount; j++) {
                    putLabel(bc_label, code, i.getPC(), isw.getCaseLabel(j));
                }
                // Transmit case values in their own band.
                if (bc == _tableswitch) {
                    bc_case_value.putInt(isw.getCaseValue(0));
                } else {
                    for (int j = 0; j < caseCount; j++) {
                        bc_case_value.putInt(isw.getCaseValue(j));
                    }
                }
                // Done with the switch.
                continue;
            }

            int branch = i.getBranchLabel();
            if (branch >= 0) {
                bc_codes.putByte(bc);
                putLabel(bc_label, code, i.getPC(), branch);
                continue;
            }
            Entry ref = i.getCPRef(curCPMap);
            if (ref != null) {
                if (bc == _new)  newClass = ref;
                if (bc == _ldc)  ldcHist[ref.tag]++;
                CPRefBand bc_which;
                int vbc = bc;
                switch (i.getCPTag()) {
                case CONSTANT_LoadableValue:
                    switch (ref.tag) {
                    case CONSTANT_Integer:
                        bc_which = bc_intref;
                        switch (bc) {
                        case _ldc:    vbc = _ildc; break;
                        case _ldc_w:  vbc = _ildc_w; break;
                        default:      assert(false);
                        }
                        break;
                    case CONSTANT_Float:
                        bc_which = bc_floatref;
                        switch (bc) {
                        case _ldc:    vbc = _fldc; break;
                        case _ldc_w:  vbc = _fldc_w; break;
                        default:      assert(false);
                        }
                        break;
                    case CONSTANT_Long:
                        bc_which = bc_longref;
                        assert(bc == _ldc2_w);
                        vbc = _lldc2_w;
                        break;
                    case CONSTANT_Double:
                        bc_which = bc_doubleref;
                        assert(bc == _ldc2_w);
                        vbc = _dldc2_w;
                        break;
                    case CONSTANT_String:
                        bc_which = bc_stringref;
                        switch (bc) {
                        case _ldc:    vbc = _sldc; break;
                        case _ldc_w:  vbc = _sldc_w; break;
                        default:      assert(false);
                        }
                        break;
                    case CONSTANT_Class:
                        bc_which = bc_classref;
                        switch (bc) {
                        case _ldc:    vbc = _cldc; break;
                        case _ldc_w:  vbc = _cldc_w; break;
                        default:      assert(false);
                        }
                        break;
                    default:
                        // CONSTANT_MethodHandle, etc.
                        if (getHighestClassVersion().lessThan(JAVA7_MAX_CLASS_VERSION)) {
                            throw new IOException("bad class file major version for Java 7 ldc");
                        }
                        bc_which = bc_loadablevalueref;
                        switch (bc) {
                        case _ldc:    vbc = _qldc; break;
                        case _ldc_w:  vbc = _qldc_w; break;
                        default:      assert(false);
                        }
                    }
                    break;
                case CONSTANT_Class:
                    // Use a special shorthand for the current class:
                    if (ref == curClass.thisClass)  ref = null;
                    bc_which = bc_classref; break;
                case CONSTANT_Fieldref:
                    bc_which = bc_fieldref; break;
                case CONSTANT_Methodref:
                    if (ref.tagEquals(CONSTANT_InterfaceMethodref)) {
                        if (bc == _invokespecial)
                            vbc = _invokespecial_int;
                        if (bc == _invokestatic)
                            vbc = _invokestatic_int;
                        bc_which = bc_imethodref;
                    } else {
                        bc_which = bc_methodref;
                    }
                    break;
                case CONSTANT_InterfaceMethodref:
                    bc_which = bc_imethodref; break;
                case CONSTANT_InvokeDynamic:
                    bc_which = bc_indyref; break;
                default:
                    bc_which = null;
                    assert(false);
                }
                if (ref != null && bc_which.index != null && !bc_which.index.contains(ref)) {
                    // Crash and burn with a complaint if there are funny
                    // references for this bytecode instruction.
                    // Example:  invokestatic of a CONSTANT_InterfaceMethodref.
                    String complaint = code.getMethod() +
                        " contains a bytecode " + i +
                        " with an unsupported constant reference; please use the pass-file option on this class.";
                    Utils.log.warning(complaint);
                    throw new IOException(complaint);
                }
                bc_codes.putByte(vbc);
                bc_which.putRef(ref);
                // handle trailing junk
                if (bc == _multianewarray) {
                    assert(i.getConstant() == code.getByte(i.getPC()+3));
                    // Just dump the byte into the bipush pile
                    bc_byte.putByte(0xFF & i.getConstant());
                } else if (bc == _invokeinterface) {
                    assert(i.getLength() == 5);
                    // Make sure the discarded bytes are sane:
                    assert(i.getConstant() == (1+((MemberEntry)ref).descRef.typeRef.computeSize(true)) << 8);
                } else if (bc == _invokedynamic) {
                    if (getHighestClassVersion().lessThan(JAVA7_MAX_CLASS_VERSION)) {
                        throw new IOException("bad class major version for Java 7 invokedynamic");
                    }
                    assert(i.getLength() == 5);
                    assert(i.getConstant() == 0);  // last 2 bytes MBZ
                } else {
                    // Make sure there is nothing else to write.
                    assert(i.getLength() == ((bc == _ldc)?2:3));
                }
                continue;
            }
            int slot = i.getLocalSlot();
            if (slot >= 0) {
                bc_codes.putByte(bc);
                bc_local.putInt(slot);
                int con = i.getConstant();
                if (bc == _iinc) {
                    if (!i.isWide()) {
                        bc_byte.putByte(0xFF & con);
                    } else {
                        bc_short.putInt(0xFFFF & con);
                    }
                } else {
                    assert(con == 0);
                }
                continue;
            }
            // Generic instruction.  Copy the body.
            bc_codes.putByte(bc);
            int pc = i.getPC()+1;
            int npc = i.getNextPC();
            if (pc < npc) {
                // Do a few remaining multi-byte instructions.
                switch (bc) {
                case _sipush:
                    bc_short.putInt(0xFFFF & i.getConstant());
                    break;
                case _bipush:
                    bc_byte.putByte(0xFF & i.getConstant());
                    break;
                case _newarray:
                    bc_byte.putByte(0xFF & i.getConstant());
                    break;
                default:
                    assert(false);  // that's it
                }
            }
        }
        bc_codes.putByte(_end_marker);
        bc_codes.elementCountForDebug++;
        codeHist[_end_marker]++;
        endCode();
    }

    int[] codeHist = new int[1<<8];
    int[] ldcHist  = new int[20];
    void printCodeHist() {
        assert(verbose > 0);
        String[] hist = new String[codeHist.length];
        int totalBytes = 0;
        for (int bc = 0; bc < codeHist.length; bc++) {
            totalBytes += codeHist[bc];
        }
        for (int bc = 0; bc < codeHist.length; bc++) {
            if (codeHist[bc] == 0) { hist[bc] = ""; continue; }
            String iname = Instruction.byteName(bc);
            String count = "" + codeHist[bc];
            count = "         ".substring(count.length()) + count;
            String pct = "" + (codeHist[bc] * 10000 / totalBytes);
            while (pct.length() < 4) {
                pct = "0" + pct;
            }
            pct = pct.substring(0, pct.length()-2) + "." + pct.substring(pct.length()-2);
            hist[bc] = count + "  " + pct + "%  " + iname;
        }
        Arrays.sort(hist);
        System.out.println("Bytecode histogram ["+totalBytes+"]");
        for (int i = hist.length; --i >= 0; ) {
            if ("".equals(hist[i]))  continue;
            System.out.println(hist[i]);
        }
        for (int tag = 0; tag < ldcHist.length; tag++) {
            int count = ldcHist[tag];
            if (count == 0)  continue;
            System.out.println("ldc "+ConstantPool.tagName(tag)+" "+count);
        }
    }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java PackageWriter.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.