/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.apache.harmony.pack200;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.harmony.pack200.AttributeDefinitionBands.AttributeDefinition;
import org.apache.harmony.pack200.IcBands.IcTuple;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;

/**
 * Class bands (corresponds to the <code>class_bands</code> set of bands in the
 * pack200 specification)
 */
public class ClassBands extends BandSet {

    private final CpBands cpBands;
    private final AttributeDefinitionBands attrBands;

    private final CPClass[] class_this;
    private final CPClass[] class_super;
    private final CPClass[][] class_interface;
    private final int[] class_interface_count;

    private final int[] major_versions;

    private final long[] class_flags;
    private int[] class_attr_calls;
    private final List classSourceFile = new ArrayList();
    private final List classEnclosingMethodClass = new ArrayList();
    private final List classEnclosingMethodDesc = new ArrayList();
    private final List classSignature = new ArrayList();

    private final IntList classFileVersionMinor = new IntList();
    private final IntList classFileVersionMajor = new IntList();

    private final int[] class_field_count;
    private final CPNameAndType[][] field_descr;
    private final long[][] field_flags;
    private int[] field_attr_calls;
    private final List fieldConstantValueKQ = new ArrayList();
    private final List fieldSignature = new ArrayList();

    private final int[] class_method_count;
    private final CPNameAndType[][] method_descr;
    private final long[][] method_flags;
    private int[] method_attr_calls;
    private final List methodSignature = new ArrayList();
    private final IntList methodExceptionNumber = new IntList();
    private final List methodExceptionClasses = new ArrayList();

    private int[] codeHeaders;
    private final IntList codeMaxStack = new IntList();
    private final IntList codeMaxLocals = new IntList();
    private final IntList codeHandlerCount = new IntList();
    private final List codeHandlerStartP = new ArrayList();
    private final List codeHandlerEndPO = new ArrayList();
    private final List codeHandlerCatchPO = new ArrayList();
    private final List codeHandlerClass = new ArrayList();
    private final List codeFlags = new ArrayList();
    private int[] code_attr_calls;
    private final IntList codeLineNumberTableN = new IntList();
    private final List codeLineNumberTableBciP = new ArrayList();
    private final IntList codeLineNumberTableLine = new IntList();
    private final IntList codeLocalVariableTableN = new IntList();
    private final List codeLocalVariableTableBciP = new ArrayList();
    private final List codeLocalVariableTableSpanO = new ArrayList();
    private final List codeLocalVariableTableNameRU = new ArrayList();
    private final List codeLocalVariableTableTypeRS = new ArrayList();
    private final IntList codeLocalVariableTableSlot = new IntList();
    private final IntList codeLocalVariableTypeTableN = new IntList();
    private final List codeLocalVariableTypeTableBciP = new ArrayList();
    private final List codeLocalVariableTypeTableSpanO = new ArrayList();
    private final List codeLocalVariableTypeTableNameRU = new ArrayList();
    private final List codeLocalVariableTypeTableTypeRS = new ArrayList();
    private final IntList codeLocalVariableTypeTableSlot = new IntList();

    private final MetadataBandGroup class_RVA_bands;
    private final MetadataBandGroup class_RIA_bands;
    private final MetadataBandGroup field_RVA_bands;
    private final MetadataBandGroup field_RIA_bands;
    private final MetadataBandGroup method_RVA_bands;
    private final MetadataBandGroup method_RIA_bands;
    private final MetadataBandGroup method_RVPA_bands;
    private final MetadataBandGroup method_RIPA_bands;
    private final MetadataBandGroup method_AD_bands;

    private final List classAttributeBands = new ArrayList();
    private final List methodAttributeBands = new ArrayList();
    private final List fieldAttributeBands = new ArrayList();
    private final List codeAttributeBands = new ArrayList();

    private final List tempFieldFlags = new ArrayList();
    private final List tempFieldDesc = new ArrayList();
    private final List tempMethodFlags = new ArrayList();
    private final List tempMethodDesc = new ArrayList();
    private TempParamAnnotation tempMethodRVPA;
    private TempParamAnnotation tempMethodRIPA;

    private boolean anySyntheticClasses = false;
    private boolean anySyntheticFields = false;
    private boolean anySyntheticMethods = false;
    private final Segment segment;

    private final Map classReferencesInnerClass = new HashMap();
    private final boolean stripDebug;

    private int index = 0;

    private int numMethodArgs = 0;
    private int[] class_InnerClasses_N;
    private CPClass[] class_InnerClasses_RC;
    private int[] class_InnerClasses_F;
    private List classInnerClassesOuterRCN;
    private List classInnerClassesNameRUN;

    public ClassBands(Segment segment, int numClasses, int effort, boolean stripDebug) throws IOException {
        super(effort, segment.getSegmentHeader());
        this.stripDebug = stripDebug;
        this.segment = segment;
        this.cpBands = segment.getCpBands();
        this.attrBands = segment.getAttrBands();
        class_this = new CPClass[numClasses];
        class_super = new CPClass[numClasses];
        class_interface_count = new int[numClasses];
        class_interface = new CPClass[numClasses][];
        class_field_count = new int[numClasses];
        class_method_count = new int[numClasses];
        field_descr = new CPNameAndType[numClasses][];
        field_flags = new long[numClasses][];
        method_descr = new CPNameAndType[numClasses][];
        method_flags = new long[numClasses][];
        for (int i = 0; i < numClasses; i++) {
            field_flags[i] = new long[0];
            method_flags[i] = new long[0];
        }
        // minor_versions = new int[numClasses];
        major_versions = new int[numClasses];
        class_flags = new long[numClasses];

        class_RVA_bands = new MetadataBandGroup("RVA", MetadataBandGroup.CONTEXT_CLASS, cpBands, segmentHeader, effort);
        class_RIA_bands = new MetadataBandGroup("RIA", MetadataBandGroup.CONTEXT_CLASS, cpBands, segmentHeader, effort);
        field_RVA_bands = new MetadataBandGroup("RVA", MetadataBandGroup.CONTEXT_FIELD, cpBands, segmentHeader, effort);
        field_RIA_bands = new MetadataBandGroup("RIA", MetadataBandGroup.CONTEXT_FIELD, cpBands, segmentHeader, effort);
        method_RVA_bands = new MetadataBandGroup("RVA", MetadataBandGroup.CONTEXT_METHOD, cpBands, segmentHeader, effort);
        method_RIA_bands = new MetadataBandGroup("RIA", MetadataBandGroup.CONTEXT_METHOD, cpBands, segmentHeader, effort);
        method_RVPA_bands = new MetadataBandGroup("RVPA", MetadataBandGroup.CONTEXT_METHOD, cpBands, segmentHeader, effort);
        method_RIPA_bands = new MetadataBandGroup("RIPA", MetadataBandGroup.CONTEXT_METHOD, cpBands, segmentHeader, effort);
        method_AD_bands = new MetadataBandGroup("AD", MetadataBandGroup.CONTEXT_METHOD, cpBands, segmentHeader, effort);

        createNewAttributeBands();
    }

    private void createNewAttributeBands() throws IOException {
        List classAttributeLayouts = attrBands.getClassAttributeLayouts();
        for (Iterator iterator = classAttributeLayouts.iterator(); iterator.hasNext();) {
            AttributeDefinition def = (AttributeDefinition) iterator.next();
            classAttributeBands.add(new NewAttributeBands(effort, cpBands, segment.getSegmentHeader(), def));
        }
        List methodAttributeLayouts = attrBands.getMethodAttributeLayouts();
        for (Iterator iterator = methodAttributeLayouts.iterator(); iterator.hasNext();) {
            AttributeDefinition def = (AttributeDefinition) iterator.next();
            methodAttributeBands.add(new NewAttributeBands(effort, cpBands, segment.getSegmentHeader(), def));
        }
        List fieldAttributeLayouts = attrBands.getFieldAttributeLayouts();
        for (Iterator iterator = fieldAttributeLayouts.iterator(); iterator.hasNext();) {
            AttributeDefinition def = (AttributeDefinition) iterator.next();
            fieldAttributeBands.add(new NewAttributeBands(effort, cpBands, segment.getSegmentHeader(), def));
        }
        List codeAttributeLayouts = attrBands.getCodeAttributeLayouts();
        for (Iterator iterator = codeAttributeLayouts.iterator(); iterator.hasNext();) {
            AttributeDefinition def = (AttributeDefinition) iterator.next();
            codeAttributeBands.add(new NewAttributeBands(effort, cpBands, segment.getSegmentHeader(), def));
        }
    }

    public void addClass(int major, int flags, String className,
            String signature, String superName, String[] interfaces) {
        class_this[index] = cpBands.getCPClass(className);
        class_super[index] = cpBands.getCPClass(superName);
        class_interface_count[index] = interfaces.length;
        class_interface[index] = new CPClass[interfaces.length];
        for (int i = 0; i < interfaces.length; i++) {
            class_interface[index][i] = cpBands.getCPClass(interfaces[i]);
        }
        major_versions[index] = major;
        class_flags[index] = flags;
        if(!anySyntheticClasses && ((flags & (1 << 12)) != 0) && segment.getCurrentClassReader().hasSyntheticAttributes()) {
            cpBands.addCPUtf8("Synthetic");
            anySyntheticClasses = true;
        }
        if((flags & Opcodes.ACC_DEPRECATED) != 0) { // ASM uses (1<<17) flag for deprecated
            flags = flags & ~Opcodes.ACC_DEPRECATED;
            flags = flags | (1<<20);
        }
        if(signature != null) {
            class_flags[index] |= (1 << 19);
            classSignature.add(cpBands.getCPSignature(signature));
        }
    }

    public void currentClassReferencesInnerClass(CPClass inner) {
        if(!(index >= class_this.length)) {
            CPClass currentClass = class_this[index];
            if(currentClass != null && !currentClass.equals(inner) && !isInnerClassOf(currentClass.toString(), inner)) {
                Set referencedInnerClasses = (Set)classReferencesInnerClass.get(currentClass);
                if(referencedInnerClasses == null) {
                    referencedInnerClasses = new HashSet();
                    classReferencesInnerClass.put(currentClass, referencedInnerClasses);
                }
                referencedInnerClasses.add(inner);
            }
        }
    }

    private boolean isInnerClassOf(String possibleInner, CPClass possibleOuter) {
        if(isInnerClass(possibleInner)) {
            String superClassName = possibleInner.substring(0, possibleInner.lastIndexOf('$'));
            if(superClassName.equals(possibleOuter.toString())) {
                return true;
            } else { // do this recursively
                return isInnerClassOf(superClassName, possibleOuter);
            }
        }
        return false;
    }

    private boolean isInnerClass(String possibleInner) {
        return possibleInner.indexOf('$') != -1;
    }

    public void addField(int flags, String name, String desc, String signature,
            Object value) {
        flags = flags & 0xFFFF;
        tempFieldDesc.add(cpBands.getCPNameAndType(name, desc));
        if (signature != null) {
            fieldSignature.add(cpBands.getCPSignature(signature));
            flags |= (1 << 19);
        }
        if((flags & Opcodes.ACC_DEPRECATED) != 0) { // ASM uses (1<<17) flag for deprecated
            flags = flags & ~Opcodes.ACC_DEPRECATED;
            flags = flags | (1<<20);
        }
        if (value != null) {
            fieldConstantValueKQ.add(cpBands.getConstant(value));
            flags |= (1 << 17);
        }
        if(!anySyntheticFields && ((flags & (1 << 12)) != 0) && segment.getCurrentClassReader().hasSyntheticAttributes()) {
            cpBands.addCPUtf8("Synthetic");
            anySyntheticFields = true;
        }
        tempFieldFlags.add(new Long(flags));
    }

    /**
     * All input classes for the segment have now been read in, so this method
     * is called so that this class can calculate/complete anything it could not
     * do while classes were being read.
     */
    public void finaliseBands() {
        int defaultMajorVersion = segmentHeader.getDefaultMajorVersion();
        for (int i = 0; i < class_flags.length; i++) {
            int major = major_versions[i];
            if (major != defaultMajorVersion) {
                class_flags[i] |= 1 << 24;
                classFileVersionMajor.add(major);
                classFileVersionMinor.add(0);
            }
        }
        // Calculate code headers
        codeHeaders = new int[codeHandlerCount.size()];
        int removed = 0;
        for (int i = 0; i < codeHeaders.length; i++) {
            int numHandlers = codeHandlerCount.get(i - removed);
            int maxLocals = codeMaxLocals.get(i - removed);
            int maxStack = codeMaxStack.get(i - removed);
            if (numHandlers == 0) {
                int header = maxLocals * 12 + maxStack + 1;
                if (header < 145 && maxStack < 12) {
                    codeHeaders[i] = header;
                }
            } else if (numHandlers == 1) {
                int header = maxLocals * 8 + maxStack + 145;
                if (header < 209 && maxStack < 8) {
                    codeHeaders[i] = header;
                }
            } else if (numHandlers == 2) {
                int header = maxLocals * 7 + maxStack + 209;
                if (header < 256 && maxStack < 7) {
                    codeHeaders[i] = header;
                }
            }
            if (codeHeaders[i] != 0) { // Remove the redundant values from
                                        // codeHandlerCount, codeMaxLocals and
                                        // codeMaxStack
                codeHandlerCount.remove(i - removed);
                codeMaxLocals.remove(i - removed);
                codeMaxStack.remove(i - removed);
                removed++;
            } else if (!segment.getSegmentHeader().have_all_code_flags()) {
                codeFlags.add(new Long(0));
            }
        }

        // Compute any required IcLocals
        IntList innerClassesN = new IntList();
        List icLocal = new ArrayList();
        for (int i = 0; i < class_this.length; i++) {
            CPClass cpClass = class_this[i];
            Set referencedInnerClasses = (Set) classReferencesInnerClass.get(cpClass);
            if(referencedInnerClasses != null) {
                int innerN = 0;
                List innerClasses = segment.getIcBands().getInnerClassesForOuter(cpClass.toString());
                if(innerClasses != null) {
                    for (Iterator iterator2 = innerClasses.iterator(); iterator2
                            .hasNext();) {
                        referencedInnerClasses.remove(((IcTuple)iterator2.next()).C);
                    }
                }
                for (Iterator iterator2 = referencedInnerClasses.iterator(); iterator2
                        .hasNext();) {
                    CPClass inner = (CPClass) iterator2.next();
                    IcTuple icTuple = segment.getIcBands().getIcTuple(inner);
                    if(icTuple != null && ! icTuple.isAnonymous()) {
                        // should transmit an icLocal entry
                        icLocal.add(icTuple);
                        innerN++;
                    }
                }
                if(innerN != 0) {
                    innerClassesN.add(innerN);
                    class_flags[i] |= (1 << 23);
                }
            }
        }
        class_InnerClasses_N = innerClassesN.toArray();
        class_InnerClasses_RC = new CPClass[icLocal.size()];
        class_InnerClasses_F = new int[icLocal.size()];
        classInnerClassesOuterRCN = new ArrayList();
        classInnerClassesNameRUN = new ArrayList();
        for (int i = 0; i < class_InnerClasses_RC.length; i++) {
            IcTuple icTuple = (IcTuple) icLocal.get(i);
            class_InnerClasses_RC[i] = (icTuple.C);
            if(icTuple.C2 == null && icTuple.N == null) {
                class_InnerClasses_F[i] = 0;
            } else {
                if (icTuple.F == 0) {
                    class_InnerClasses_F[i] = 0x00010000;
                } else {
                    class_InnerClasses_F[i] = icTuple.F;
                }
                classInnerClassesOuterRCN.add(icTuple.C2);
                classInnerClassesNameRUN.add(icTuple.N);
            }
        }
        // Calculate any backwards calls from metadata bands
        IntList classAttrCalls = new IntList();
        IntList fieldAttrCalls = new IntList();
        IntList methodAttrCalls = new IntList();
        IntList codeAttrCalls = new IntList();

        if(class_RVA_bands.hasContent()) {
            classAttrCalls.add(class_RVA_bands.numBackwardsCalls());
        }
        if(class_RIA_bands.hasContent()) {
            classAttrCalls.add(class_RIA_bands.numBackwardsCalls());
        }
        if(field_RVA_bands.hasContent()) {
            fieldAttrCalls.add(field_RVA_bands.numBackwardsCalls());
        }
        if(field_RIA_bands.hasContent()) {
            fieldAttrCalls.add(field_RIA_bands.numBackwardsCalls());
        }
        if(method_RVA_bands.hasContent()) {
            methodAttrCalls.add(method_RVA_bands.numBackwardsCalls());
        }
        if(method_RIA_bands.hasContent()) {
            methodAttrCalls.add(method_RIA_bands.numBackwardsCalls());
        }
        if(method_RVPA_bands.hasContent()) {
            methodAttrCalls.add(method_RVPA_bands.numBackwardsCalls());
        }
        if(method_RIPA_bands.hasContent()) {
            methodAttrCalls.add(method_RIPA_bands.numBackwardsCalls());
        }
        if(method_AD_bands.hasContent()) {
            methodAttrCalls.add(method_AD_bands.numBackwardsCalls());
        }

        // Sort non-predefined attribute bands
        Comparator comparator = new Comparator() {
            public int compare(Object arg0, Object arg1) {
                NewAttributeBands bands0 = (NewAttributeBands)arg0;
                NewAttributeBands bands1 = (NewAttributeBands)arg1;
                return bands0.getFlagIndex() - bands1.getFlagIndex();
            }
        };
        Collections.sort(classAttributeBands, comparator);
        Collections.sort(methodAttributeBands, comparator);
        Collections.sort(fieldAttributeBands, comparator);
        Collections.sort(codeAttributeBands, comparator);

        for (Iterator iterator = classAttributeBands.iterator(); iterator.hasNext();) {
            NewAttributeBands bands = (NewAttributeBands) iterator.next();
            if(bands.isUsedAtLeastOnce()) {
                int[] backwardsCallCounts = bands.numBackwardsCalls();
                for (int i = 0; i < backwardsCallCounts.length; i++) {
                    classAttrCalls.add(backwardsCallCounts[i]);
                }
            }
        }
        for (Iterator iterator = methodAttributeBands.iterator(); iterator.hasNext();) {
            NewAttributeBands bands = (NewAttributeBands) iterator.next();
            if(bands.isUsedAtLeastOnce()) {
                int[] backwardsCallCounts = bands.numBackwardsCalls();
                for (int i = 0; i < backwardsCallCounts.length; i++) {
                    methodAttrCalls.add(backwardsCallCounts[i]);
                }
            }
        }
        for (Iterator iterator = fieldAttributeBands.iterator(); iterator.hasNext();) {
            NewAttributeBands bands = (NewAttributeBands) iterator.next();
            if(bands.isUsedAtLeastOnce()) {
                int[] backwardsCallCounts = bands.numBackwardsCalls();
                for (int i = 0; i < backwardsCallCounts.length; i++) {
                    fieldAttrCalls.add(backwardsCallCounts[i]);
                }
            }
        }
        for (Iterator iterator = codeAttributeBands.iterator(); iterator.hasNext();) {
            NewAttributeBands bands = (NewAttributeBands) iterator.next();
            if(bands.isUsedAtLeastOnce()) {
                int[] backwardsCallCounts = bands.numBackwardsCalls();
                for (int i = 0; i < backwardsCallCounts.length; i++) {
                    codeAttrCalls.add(backwardsCallCounts[i]);
                }
            }
        }

        class_attr_calls = classAttrCalls.toArray();
        field_attr_calls = fieldAttrCalls.toArray();
        method_attr_calls = methodAttrCalls.toArray();
        code_attr_calls = codeAttrCalls.toArray();
    }

    public void pack(OutputStream out) throws IOException, Pack200Exception {
        PackingUtils.log("Writing class bands...");

        byte[] encodedBand = encodeBandInt("class_this", getInts(class_this),
                Codec.DELTA5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from class_this[" + class_this.length + "]");

        encodedBand = encodeBandInt("class_super", getInts(class_super),
                Codec.DELTA5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from class_super[" + class_super.length + "]");

        encodedBand = encodeBandInt("class_interface_count",
                class_interface_count, Codec.DELTA5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from class_interface_count["
                + class_interface_count.length + "]");

        int totalInterfaces = sum(class_interface_count);
        int[] classInterface = new int[totalInterfaces];
        int k = 0;
        for (int i = 0; i < class_interface.length; i++) {
            if (class_interface[i] != null) {
                for (int j = 0; j < class_interface[i].length; j++) {
                    CPClass cpClass = class_interface[i][j];
                    classInterface[k] = cpClass.getIndex();
                    k++;
                }
            }
        }

        encodedBand = encodeBandInt("class_interface", classInterface,
                Codec.DELTA5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from class_interface[" + classInterface.length + "]");

        encodedBand = encodeBandInt("class_field_count", class_field_count,
                Codec.DELTA5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from class_field_count[" + class_field_count.length
                + "]");

        encodedBand = encodeBandInt("class_method_count", class_method_count,
                Codec.DELTA5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from class_method_count[" + class_method_count.length
                + "]");

        int totalFields = sum(class_field_count);
        int[] fieldDescr = new int[totalFields];
        k = 0;
        for (int i = 0; i < index; i++) {
            for (int j = 0; j < field_descr[i].length; j++) {
                CPNameAndType descr = field_descr[i][j];
                fieldDescr[k] = descr.getIndex();
                k++;
            }
        }

        encodedBand = encodeBandInt("field_descr", fieldDescr, Codec.DELTA5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from field_descr[" + fieldDescr.length + "]");

        writeFieldAttributeBands(out);

        int totalMethods = sum(class_method_count);
        int[] methodDescr = new int[totalMethods];
        k = 0;
        for (int i = 0; i < index; i++) {
            for (int j = 0; j < method_descr[i].length; j++) {
                CPNameAndType descr = method_descr[i][j];
                methodDescr[k] = descr.getIndex();
                k++;
            }
        }

        encodedBand = encodeBandInt("method_descr", methodDescr, Codec.MDELTA5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from method_descr[" + methodDescr.length + "]");

        writeMethodAttributeBands(out);
        writeClassAttributeBands(out);
        writeCodeBands(out);
    }

    private int sum(int[] ints) {
        int sum = 0;
        for (int i = 0; i < ints.length; i++) {
            sum += ints[i];
        }
        return sum;
    }

    private void writeFieldAttributeBands(OutputStream out) throws IOException,
            Pack200Exception {
        byte[] encodedBand = encodeFlags("field_flags", field_flags,
                Codec.UNSIGNED5, Codec.UNSIGNED5, segmentHeader
                        .have_field_flags_hi());
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from field_flags[" + field_flags.length + "]");

        // *field_attr_count :UNSIGNED5 [COUNT(1<<16,...)]
        // *field_attr_indexes :UNSIGNED5 [SUM(*field_attr_count)]
        encodedBand = encodeBandInt("field_attr_calls", field_attr_calls,
                Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from field_attr_calls[" + field_attr_calls.length
                + "]");

        encodedBand = encodeBandInt("fieldConstantValueKQ",
                cpEntryListToArray(fieldConstantValueKQ), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from fieldConstantValueKQ["
                + fieldConstantValueKQ.size() + "]");

        encodedBand = encodeBandInt("fieldSignature",
                cpEntryListToArray(fieldSignature), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from fieldSignature[" + fieldSignature.size() + "]");

        field_RVA_bands.pack(out);
        field_RIA_bands.pack(out);
        for (Iterator iterator = fieldAttributeBands.iterator(); iterator
                .hasNext();) {
            NewAttributeBands bands = (NewAttributeBands) iterator.next();
            bands.pack(out);
        }
    }

    private void writeMethodAttributeBands(OutputStream out)
            throws IOException, Pack200Exception {
        byte[] encodedBand = encodeFlags("method_flags", method_flags,
                Codec.UNSIGNED5, Codec.UNSIGNED5, segmentHeader
                        .have_method_flags_hi());
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from method_flags[" + method_flags.length + "]");

        // *method_attr_count :UNSIGNED5 [COUNT(1<<16,...)]
        // *method_attr_indexes :UNSIGNED5 [SUM(*method_attr_count)]
        encodedBand = encodeBandInt("method_attr_calls", method_attr_calls,
                Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from method_attr_calls[" + method_attr_calls.length
                + "]");

        encodedBand = encodeBandInt("methodExceptionNumber",
                methodExceptionNumber.toArray(), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from methodExceptionNumber["
                + methodExceptionNumber.size() + "]");

        encodedBand = encodeBandInt("methodExceptionClasses",
                cpEntryListToArray(methodExceptionClasses), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from methodExceptionClasses["
                + methodExceptionClasses.size() + "]");

        encodedBand = encodeBandInt("methodSignature",
                cpEntryListToArray(methodSignature), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils
                .log("Wrote " + encodedBand.length
                        + " bytes from methodSignature["
                        + methodSignature.size() + "]");

        method_RVA_bands.pack(out);
        method_RIA_bands.pack(out);
        method_RVPA_bands.pack(out);
        method_RIPA_bands.pack(out);
        method_AD_bands.pack(out);
        for (Iterator iterator = methodAttributeBands.iterator(); iterator
                .hasNext();) {
            NewAttributeBands bands = (NewAttributeBands) iterator.next();
            bands.pack(out);
        }
    }

    private void writeClassAttributeBands(OutputStream out) throws IOException,
            Pack200Exception {
        byte[] encodedBand = encodeFlags("class_flags", class_flags,
                Codec.UNSIGNED5, Codec.UNSIGNED5, segmentHeader
                        .have_class_flags_hi());
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from class_flags[" + class_flags.length + "]");

        // These bands are not needed, but could be used to reduce the size of
        // the archive if there are enough different non-standard attributes
        // defined that segmentHeader.have_class_flags_hi() is true. The same
        // applies to method_attr_count, field_attr_count, code_attr_count etc.

        // *class_attr_count :UNSIGNED5 [COUNT(1<<16,...)]
        // *class_attr_indexes :UNSIGNED5 [SUM(*class_attr_count)]

        encodedBand = encodeBandInt("class_attr_calls", class_attr_calls,
                Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from class_attr_calls[" + class_attr_calls.length
                + "]");

        encodedBand = encodeBandInt("classSourceFile",
                cpEntryOrNullListToArray(classSourceFile), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils
                .log("Wrote " + encodedBand.length
                        + " bytes from classSourceFile["
                        + classSourceFile.size() + "]");

        encodedBand = encodeBandInt("class_enclosing_method_RC",
                cpEntryListToArray(classEnclosingMethodClass), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from class_enclosing_method_RC["
                + classEnclosingMethodClass.size() + "]");

        encodedBand = encodeBandInt("class_EnclosingMethod_RDN",
                cpEntryOrNullListToArray(classEnclosingMethodDesc),
                Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from class_EnclosingMethod_RDN["
                + classEnclosingMethodDesc.size() + "]");

        encodedBand = encodeBandInt("class_Signature_RS",
                cpEntryListToArray(classSignature), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from class_Signature_RS[" + classSignature.size()
                + "]");

        class_RVA_bands.pack(out);
        class_RIA_bands.pack(out);

        encodedBand = encodeBandInt("class_InnerClasses_N",
                class_InnerClasses_N, Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from class_InnerClasses_N["
                + class_InnerClasses_N.length + "]");

        encodedBand = encodeBandInt("class_InnerClasses_RC",
                getInts(class_InnerClasses_RC), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from class_InnerClasses_RC["
                + class_InnerClasses_RC.length + "]");

        encodedBand = encodeBandInt("class_InnerClasses_F",
                class_InnerClasses_F, Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from class_InnerClasses_F["
                + class_InnerClasses_F.length + "]");

        encodedBand = encodeBandInt("class_InnerClasses_outer_RCN",
                cpEntryOrNullListToArray(classInnerClassesOuterRCN),
                Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from class_InnerClasses_outer_RCN["
                + classInnerClassesOuterRCN.size() + "]");

        encodedBand = encodeBandInt("class_InnerClasses_name_RUN",
                cpEntryOrNullListToArray(classInnerClassesNameRUN),
                Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from class_InnerClasses_name_RUN["
                + classInnerClassesNameRUN.size() + "]");

        encodedBand = encodeBandInt("classFileVersionMinor",
                classFileVersionMinor.toArray(), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from classFileVersionMinor["
                + classFileVersionMinor.size() + "]");

        encodedBand = encodeBandInt("classFileVersionMajor",
                classFileVersionMajor.toArray(), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from classFileVersionMajor["
                + classFileVersionMajor.size() + "]");

        for (Iterator iterator = classAttributeBands.iterator(); iterator
                .hasNext();) {
            NewAttributeBands bands = (NewAttributeBands) iterator.next();
            bands.pack(out);
        }
    }

    private int[] getInts(CPClass[] cpClasses) {
        int[] ints = new int[cpClasses.length];
        for (int i = 0; i < ints.length; i++) {
            if(cpClasses[i] != null) {
                ints[i] = cpClasses[i].getIndex();
            }
        }
        return ints;
    }

    private void writeCodeBands(OutputStream out) throws IOException,
            Pack200Exception {
        byte[] encodedBand = encodeBandInt("codeHeaders", codeHeaders,
                Codec.BYTE1);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from codeHeaders[" + codeHeaders.length + "]");

        encodedBand = encodeBandInt("codeMaxStack", codeMaxStack.toArray(),
                Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from codeMaxStack[" + codeMaxStack.size() + "]");

        encodedBand = encodeBandInt("codeMaxLocals", codeMaxLocals.toArray(),
                Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from codeMaxLocals[" + codeMaxLocals.size() + "]");

        encodedBand = encodeBandInt("codeHandlerCount", codeHandlerCount
                .toArray(), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from codeHandlerCount[" + codeHandlerCount.size()
                + "]");

        encodedBand = encodeBandInt("codeHandlerStartP",
                integerListToArray(codeHandlerStartP), Codec.BCI5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from codeHandlerStartP[" + codeHandlerStartP.size()
                + "]");

        encodedBand = encodeBandInt("codeHandlerEndPO",
                integerListToArray(codeHandlerEndPO), Codec.BRANCH5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from codeHandlerEndPO[" + codeHandlerEndPO.size()
                + "]");

        encodedBand = encodeBandInt("codeHandlerCatchPO",
                integerListToArray(codeHandlerCatchPO), Codec.BRANCH5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from codeHandlerCatchPO[" + codeHandlerCatchPO.size()
                + "]");

        encodedBand = encodeBandInt("codeHandlerClass",
                cpEntryOrNullListToArray(codeHandlerClass), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from codeHandlerClass[" + codeHandlerClass.size()
                + "]");

        writeCodeAttributeBands(out);
    }

    private void writeCodeAttributeBands(OutputStream out) throws IOException,
            Pack200Exception {
        byte[] encodedBand = encodeFlags("codeFlags",
                longListToArray(codeFlags), Codec.UNSIGNED5, Codec.UNSIGNED5,
                segmentHeader.have_code_flags_hi());
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from codeFlags[" + codeFlags.size() + "]");

        // *code_attr_count :UNSIGNED5 [COUNT(1<<16,...)]
        // *code_attr_indexes :UNSIGNED5 [SUM(*code_attr_count)]
        encodedBand = encodeBandInt("code_attr_calls", code_attr_calls,
                Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils
                .log("Wrote " + encodedBand.length
                        + " bytes from code_attr_calls["
                        + code_attr_calls.length + "]");

        encodedBand = encodeBandInt("code_LineNumberTable_N",
                codeLineNumberTableN.toArray(), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from code_LineNumberTable_N["
                + codeLineNumberTableN.size() + "]");

        encodedBand = encodeBandInt("code_LineNumberTable_bci_P",
                integerListToArray(codeLineNumberTableBciP), Codec.BCI5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from code_LineNumberTable_bci_P["
                + codeLineNumberTableBciP.size() + "]");

        encodedBand = encodeBandInt("code_LineNumberTable_line",
                codeLineNumberTableLine.toArray(), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from code_LineNumberTable_line["
                + codeLineNumberTableLine.size() + "]");

        encodedBand = encodeBandInt("code_LocalVariableTable_N",
                codeLocalVariableTableN.toArray(), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from code_LocalVariableTable_N["
                + codeLocalVariableTableN.size() + "]");

        encodedBand = encodeBandInt("code_LocalVariableTable_bci_P",
                integerListToArray(codeLocalVariableTableBciP), Codec.BCI5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from code_LocalVariableTable_bci_P["
                + codeLocalVariableTableBciP.size() + "]");

        encodedBand = encodeBandInt("code_LocalVariableTable_span_O",
                integerListToArray(codeLocalVariableTableSpanO), Codec.BRANCH5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from code_LocalVariableTable_span_O["
                + codeLocalVariableTableSpanO.size() + "]");

        encodedBand = encodeBandInt("code_LocalVariableTable_name_RU",
                cpEntryListToArray(codeLocalVariableTableNameRU),
                Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from code_LocalVariableTable_name_RU["
                + codeLocalVariableTableNameRU.size() + "]");

        encodedBand = encodeBandInt("code_LocalVariableTable_type_RS",
                cpEntryListToArray(codeLocalVariableTableTypeRS),
                Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from code_LocalVariableTable_type_RS["
                + codeLocalVariableTableTypeRS.size() + "]");

        encodedBand = encodeBandInt("code_LocalVariableTable_slot",
                codeLocalVariableTableSlot.toArray(), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from code_LocalVariableTable_slot["
                + codeLocalVariableTableSlot.size() + "]");

        encodedBand = encodeBandInt("code_LocalVariableTypeTable_N",
                codeLocalVariableTypeTableN.toArray(), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from code_LocalVariableTypeTable_N["
                + codeLocalVariableTypeTableN.size() + "]");

        encodedBand = encodeBandInt("code_LocalVariableTypeTable_bci_P",
                integerListToArray(codeLocalVariableTypeTableBciP), Codec.BCI5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from code_LocalVariableTypeTable_bci_P["
                + codeLocalVariableTypeTableBciP.size() + "]");

        encodedBand = encodeBandInt("code_LocalVariableTypeTable_span_O",
                integerListToArray(codeLocalVariableTypeTableSpanO), Codec.BRANCH5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from code_LocalVariableTypeTable_span_O["
                + codeLocalVariableTypeTableSpanO.size() + "]");

        encodedBand = encodeBandInt("code_LocalVariableTypeTable_name_RU",
                cpEntryListToArray(codeLocalVariableTypeTableNameRU),
                Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from code_LocalVariableTypeTable_name_RU["
                + codeLocalVariableTypeTableNameRU.size() + "]");

        encodedBand = encodeBandInt("code_LocalVariableTypeTable_type_RS",
                cpEntryListToArray(codeLocalVariableTypeTableTypeRS),
                Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from code_LocalVariableTypeTable_type_RS["
                + codeLocalVariableTypeTableTypeRS.size() + "]");

        encodedBand = encodeBandInt("code_LocalVariableTypeTable_slot",
                codeLocalVariableTypeTableSlot.toArray(), Codec.UNSIGNED5);
        out.write(encodedBand);
        PackingUtils.log("Wrote " + encodedBand.length
                + " bytes from code_LocalVariableTypeTable_slot["
                + codeLocalVariableTypeTableSlot.size() + "]");

        for (Iterator iterator = codeAttributeBands.iterator(); iterator.hasNext();) {
            NewAttributeBands bands = (NewAttributeBands) iterator.next();
            bands.pack(out);
        }
    }

    public void addMethod(int flags, String name, String desc,
            String signature, String[] exceptions) {
        CPNameAndType nt = cpBands.getCPNameAndType(name, desc);
        tempMethodDesc.add(nt);
        if (signature != null) {
            methodSignature.add(cpBands.getCPSignature(signature));
            flags |= (1 << 19);
        }
        if (exceptions != null) {
            methodExceptionNumber.add(exceptions.length);
            for (int i = 0; i < exceptions.length; i++) {
                methodExceptionClasses.add(cpBands.getCPClass(exceptions[i]));
            }
            flags |= (1 << 18);
        }
        if((flags & Opcodes.ACC_DEPRECATED) != 0) { // ASM uses (1<<17) flag for deprecated
            flags = flags & ~Opcodes.ACC_DEPRECATED;
            flags = flags | (1<<20);
        }
        tempMethodFlags.add(new Long(flags));
        numMethodArgs = countArgs(desc);
        if(!anySyntheticMethods && ((flags & (1 << 12)) != 0) && segment.getCurrentClassReader().hasSyntheticAttributes()) {
            cpBands.addCPUtf8("Synthetic");
            anySyntheticMethods = true;
        }
    }

    public void endOfMethod() {
        if (tempMethodRVPA != null) {
            method_RVPA_bands.addParameterAnnotation(tempMethodRVPA.numParams,
                    tempMethodRVPA.annoN, tempMethodRVPA.pairN,
                    tempMethodRVPA.typeRS, tempMethodRVPA.nameRU,
                    tempMethodRVPA.t, tempMethodRVPA.values,
                    tempMethodRVPA.caseArrayN, tempMethodRVPA.nestTypeRS,
                    tempMethodRVPA.nestNameRU, tempMethodRVPA.nestPairN);
            tempMethodRVPA = null;
        }
        if (tempMethodRIPA != null) {
            method_RIPA_bands.addParameterAnnotation(tempMethodRIPA.numParams,
                    tempMethodRIPA.annoN, tempMethodRIPA.pairN,
                    tempMethodRIPA.typeRS, tempMethodRIPA.nameRU,
                    tempMethodRIPA.t, tempMethodRIPA.values,
                    tempMethodRIPA.caseArrayN, tempMethodRIPA.nestTypeRS,
                    tempMethodRIPA.nestNameRU, tempMethodRIPA.nestPairN);
            tempMethodRIPA = null;
        }
        if(codeFlags.size() > 0) {
            long latestCodeFlag = ((Long)codeFlags.get(codeFlags.size() - 1)).longValue();
            if (latestCodeFlag == (1 << 2)) {
                int latestLocalVariableTableN = codeLocalVariableTableN.get(codeLocalVariableTableN.size() - 1);
                if (latestLocalVariableTableN == 0) {
                    codeLocalVariableTableN.remove(codeLocalVariableTableN.size() - 1);
                    codeFlags.remove(codeFlags.size() - 1);
                    codeFlags.add(new Long(0));
                }
            }
        }
    }

    protected static int countArgs(String descriptor) {
        int bra = descriptor.indexOf('(');
        int ket = descriptor.indexOf(')');
        if (bra == -1 || ket == -1 || ket < bra)
            throw new IllegalArgumentException("No arguments");

        boolean inType = false;
        boolean consumingNextType = false;
        int count = 0;
        for (int i = bra + 1; i < ket; i++) {
            char charAt = descriptor.charAt(i);
            if (inType && charAt == ';') {
                inType = false;
                consumingNextType = false;
            } else if (!inType && charAt == 'L') {
                inType = true;
                count++;
            } else if (charAt == '[') {
                consumingNextType = true;
            } else if (inType) {
                // NOP
            } else {
                if (consumingNextType) {
                    count++;
                    consumingNextType = false;
                } else {
                    if (charAt == 'D' || charAt == 'J') {
                        count += 2;
                    } else {
                        count++;
                    }
                }
            }
        }
        return count;
    }

    public void endOfClass() { // All the data for the current class has been
                                // read
        int numFields = tempFieldDesc.size();
        class_field_count[index] = numFields;
        field_descr[index] = new CPNameAndType[numFields];
        field_flags[index] = new long[numFields];
        for (int i = 0; i < numFields; i++) {
            field_descr[index][i] = (CPNameAndType) tempFieldDesc.get(i);
            field_flags[index][i] = ((Long) tempFieldFlags.get(i)).longValue();
        }
        int numMethods = tempMethodDesc.size();
        class_method_count[index] = numMethods;
        method_descr[index] = new CPNameAndType[numMethods];
        method_flags[index] = new long[numMethods];
        for (int i = 0; i < numMethods; i++) {
            method_descr[index][i] = (CPNameAndType) tempMethodDesc.get(i);
            method_flags[index][i] = ((Long) tempMethodFlags.get(i))
                    .longValue();
        }
        tempFieldDesc.clear();
        tempFieldFlags.clear();
        tempMethodDesc.clear();
        tempMethodFlags.clear();
        index++;
    }

    public void addSourceFile(String source) {
        String implicitSourceFileName = class_this[index].toString();
        if(implicitSourceFileName.indexOf('$') != -1) {
            implicitSourceFileName = implicitSourceFileName.substring(0, implicitSourceFileName.indexOf('$'));
        }
        implicitSourceFileName = implicitSourceFileName
                .substring(implicitSourceFileName.lastIndexOf('/') + 1)
                + ".java";
        if (source.equals(implicitSourceFileName)) {
            classSourceFile.add(null);
        } else {
            classSourceFile.add(cpBands.getCPUtf8(source));
        }
        class_flags[index] |= (1 << 17);
    }

    public void addEnclosingMethod(String owner, String name, String desc) {
        class_flags[index] |= (1 << 18);
        classEnclosingMethodClass.add(cpBands.getCPClass(owner));
        classEnclosingMethodDesc.add(name == null ? null : cpBands
                .getCPNameAndType(name, desc));
    }

    public void addClassAttribute(NewAttribute attribute) {
        // TODO: backwards calls
        String attributeName = attribute.type;
        for (Iterator iterator = classAttributeBands.iterator(); iterator.hasNext();) {
            NewAttributeBands bands = (NewAttributeBands) iterator.next();
            if(bands.getAttributeName().equals(attributeName)) {
                bands.addAttribute(attribute);
                int flagIndex = bands.getFlagIndex();
                class_flags[index] |= (1 << flagIndex);
                return;
            }
        }
        throw new RuntimeException("No suitable definition for " + attributeName);
    }

    public void addFieldAttribute(NewAttribute attribute) {
        String attributeName = attribute.type;
        for (Iterator iterator = fieldAttributeBands.iterator(); iterator.hasNext();) {
            NewAttributeBands bands = (NewAttributeBands) iterator.next();
            if(bands.getAttributeName().equals(attributeName)) {
                bands.addAttribute(attribute);
                int flagIndex = bands.getFlagIndex();
                Long flags = (Long)tempFieldFlags.remove(tempFieldFlags.size() - 1);
                tempFieldFlags.add(new Long(flags.longValue() | (1 << flagIndex)));
                return;
            }
        }
        throw new RuntimeException("No suitable definition for " + attributeName);
    }

    public void addMethodAttribute(NewAttribute attribute) {
        String attributeName = attribute.type;
        for (Iterator iterator = methodAttributeBands.iterator(); iterator.hasNext();) {
            NewAttributeBands bands = (NewAttributeBands) iterator.next();
            if(bands.getAttributeName().equals(attributeName)) {
                bands.addAttribute(attribute);
                int flagIndex = bands.getFlagIndex();
                Long flags = (Long)tempMethodFlags.remove(tempMethodFlags.size() - 1);
                tempMethodFlags.add(new Long(flags.longValue() | (1 << flagIndex)));
                return;
            }
        }
        throw new RuntimeException("No suitable definition for " + attributeName);
    }

    public void addCodeAttribute(NewAttribute attribute) {
        String attributeName = attribute.type;
        for (Iterator iterator = codeAttributeBands.iterator(); iterator.hasNext();) {
            NewAttributeBands bands = (NewAttributeBands) iterator.next();
            if(bands.getAttributeName().equals(attributeName)) {
                bands.addAttribute(attribute);
                int flagIndex = bands.getFlagIndex();
                Long flags = (Long)codeFlags.remove(codeFlags.size() - 1);
                codeFlags.add(new Long(flags.longValue() | (1 << flagIndex)));
                return;
            }
        }
        throw new RuntimeException("No suitable definition for " + attributeName);
    }

    public void addMaxStack(int maxStack, int maxLocals) {
        Long latestFlag = (Long) tempMethodFlags
                .remove(tempMethodFlags.size() - 1);
        Long newFlag = new Long(latestFlag.intValue() | (1 << 17));
        tempMethodFlags.add(newFlag);
        codeMaxStack.add(maxStack);
        if ((newFlag.longValue() & (1 << 3)) == 0) { // not static
            maxLocals--; // minus 'this' local
        }
        maxLocals -= numMethodArgs;
        codeMaxLocals.add(maxLocals);
    }

    public void addCode() {
        codeHandlerCount.add(0);
        if(!stripDebug) {
            codeFlags.add(new Long((1 << 2)));
            codeLocalVariableTableN.add(0);
        }
    }

    public void addHandler(Label start, Label end, Label handler, String type) {
        int handlers = codeHandlerCount.remove(codeHandlerCount
                .size() - 1);
        codeHandlerCount.add(handlers + 1);
        codeHandlerStartP.add(start);
        codeHandlerEndPO.add(end);
        codeHandlerCatchPO.add(handler);
        codeHandlerClass.add(type == null ? null : cpBands.getCPClass(type));
    }

    public void addLineNumber(int line, Label start) {
        Long latestCodeFlag = (Long) codeFlags.get(codeFlags.size() - 1);
        if ((latestCodeFlag.intValue() & (1 << 1)) == 0) {
            codeFlags.remove(codeFlags.size() - 1);
            codeFlags.add(new Long(latestCodeFlag.intValue() | (1 << 1)));
            codeLineNumberTableN.add(1);
        } else {
            codeLineNumberTableN
                    .increment(codeLineNumberTableN.size() - 1);
        }
        codeLineNumberTableLine.add(line);
        codeLineNumberTableBciP.add(start);
    }

    public void addLocalVariable(String name, String desc, String signature,
            Label start, Label end, int indx) {
        if (signature != null) { // LocalVariableTypeTable attribute
            Long latestCodeFlag = (Long) codeFlags.get(codeFlags.size() - 1);
            if ((latestCodeFlag.intValue() & (1 << 3)) == 0) {
                codeFlags.remove(codeFlags.size() - 1);
                codeFlags.add(new Long(latestCodeFlag.intValue() | (1 << 3)));
                codeLocalVariableTypeTableN.add(1);
            } else {
                codeLocalVariableTypeTableN
                        .increment(codeLocalVariableTypeTableN.size() - 1);
            }
            codeLocalVariableTypeTableBciP.add(start);
            codeLocalVariableTypeTableSpanO.add(end);
            codeLocalVariableTypeTableNameRU.add(cpBands.getCPUtf8(name));
            codeLocalVariableTypeTableTypeRS.add(cpBands
                    .getCPSignature(signature));
            codeLocalVariableTypeTableSlot.add(indx);
        }
        // LocalVariableTable attribute
        codeLocalVariableTableN
                .increment(codeLocalVariableTableN.size() - 1);
        codeLocalVariableTableBciP.add(start);
        codeLocalVariableTableSpanO.add(end);
        codeLocalVariableTableNameRU.add(cpBands.getCPUtf8(name));
        codeLocalVariableTableTypeRS.add(cpBands.getCPSignature(desc));
        codeLocalVariableTableSlot.add(indx);
    }

    public void doBciRenumbering(IntList bciRenumbering, Map labelsToOffsets) {
        renumberBci(codeLineNumberTableBciP, bciRenumbering, labelsToOffsets);
        renumberBci(codeLocalVariableTableBciP, bciRenumbering, labelsToOffsets);
        renumberOffsetBci(codeLocalVariableTableBciP,
                codeLocalVariableTableSpanO, bciRenumbering, labelsToOffsets);
        renumberBci(codeLocalVariableTypeTableBciP, bciRenumbering,
                labelsToOffsets);
        renumberOffsetBci(codeLocalVariableTypeTableBciP,
                codeLocalVariableTypeTableSpanO, bciRenumbering,
                labelsToOffsets);
        renumberBci(codeHandlerStartP, bciRenumbering, labelsToOffsets);
        renumberOffsetBci(codeHandlerStartP, codeHandlerEndPO,
                bciRenumbering, labelsToOffsets);
        renumberDoubleOffsetBci(codeHandlerStartP, codeHandlerEndPO, codeHandlerCatchPO,
                bciRenumbering, labelsToOffsets);

        for (Iterator iterator = classAttributeBands.iterator(); iterator.hasNext();) {
            NewAttributeBands newAttributeBandSet = (NewAttributeBands) iterator.next();
            newAttributeBandSet.renumberBci(bciRenumbering, labelsToOffsets);
        }
        for (Iterator iterator = methodAttributeBands.iterator(); iterator.hasNext();) {
            NewAttributeBands newAttributeBandSet = (NewAttributeBands) iterator.next();
            newAttributeBandSet.renumberBci(bciRenumbering, labelsToOffsets);
        }
        for (Iterator iterator = fieldAttributeBands.iterator(); iterator.hasNext();) {
            NewAttributeBands newAttributeBandSet = (NewAttributeBands) iterator.next();
            newAttributeBandSet.renumberBci(bciRenumbering, labelsToOffsets);
        }
        for (Iterator iterator = codeAttributeBands.iterator(); iterator.hasNext();) {
            NewAttributeBands newAttributeBandSet = (NewAttributeBands) iterator.next();
            newAttributeBandSet.renumberBci(bciRenumbering, labelsToOffsets);
        }
    }

    private void renumberBci(List list, IntList bciRenumbering, Map labelsToOffsets) {
        for (int i = list.size() - 1; i >= 0; i--) {
            Object label = list.get(i);
            if (label instanceof Integer) {
                break;
            } else if (label instanceof Label) {
                list.remove(i);
                Integer bytecodeIndex = (Integer) labelsToOffsets.get(label);
                list.add(i, new Integer(bciRenumbering.get(bytecodeIndex.intValue())));
            }
        }
    }

    private void renumberOffsetBci(List relative, List list,
            IntList bciRenumbering, Map labelsToOffsets) {
        for (int i = list.size() - 1; i >= 0; i--) {
            Object label = list.get(i);
            if (label instanceof Integer) {
                break;
            } else if (label instanceof Label) {
                list.remove(i);
                Integer bytecodeIndex = (Integer) labelsToOffsets.get(label);
                Integer renumberedOffset = new Integer(bciRenumbering
                        .get(bytecodeIndex.intValue())
                        - ((Integer) relative.get(i)).intValue());
                list.add(i, renumberedOffset);
            }
        }
    }

    private void renumberDoubleOffsetBci(List relative, List firstOffset, List list,
            IntList bciRenumbering, Map labelsToOffsets) {
        // TODO: There's probably a nicer way of doing this...
        for (int i = list.size() - 1; i >= 0; i--) {
            Object label = list.get(i);
            if (label instanceof Integer) {
                break;
            } else if (label instanceof Label) {
                list.remove(i);
                Integer bytecodeIndex = (Integer) labelsToOffsets.get(label);
                Integer renumberedOffset = new Integer(bciRenumbering
                        .get(bytecodeIndex.intValue())
                        - ((Integer) relative.get(i)).intValue() - ((Integer) firstOffset.get(i)).intValue());
                list.add(i, renumberedOffset);
            }
        }
    }

    public boolean isAnySyntheticClasses() {
        return anySyntheticClasses;
    }

    public boolean isAnySyntheticFields() {
        return anySyntheticFields;
    }

    public boolean isAnySyntheticMethods() {
        return anySyntheticMethods;
    }

    public void addParameterAnnotation(int parameter, String desc,
            boolean visible, List nameRU, List t, List values, List caseArrayN, List nestTypeRS, List nestNameRU, List nestPairN) {
        if(visible) {
            if(tempMethodRVPA == null) {
                tempMethodRVPA = new TempParamAnnotation(numMethodArgs);
                tempMethodRVPA.addParameterAnnotation(parameter, desc, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN);
            }
            Long flag = (Long) tempMethodFlags.remove(tempMethodFlags.size() - 1);
            tempMethodFlags.add(new Long(flag.longValue() | (1<<23)));
        } else {
            if(tempMethodRIPA == null) {
                tempMethodRIPA = new TempParamAnnotation(numMethodArgs);
                tempMethodRIPA.addParameterAnnotation(parameter, desc, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN);
            }
            Long flag = (Long) tempMethodFlags.remove(tempMethodFlags.size() - 1);
            tempMethodFlags.add(new Long(flag.longValue() | (1<<24)));
        }
    }

    private static class TempParamAnnotation {

        int numParams;
        int[] annoN;
        IntList pairN = new IntList();
        List typeRS = new ArrayList();
        List nameRU = new ArrayList();
        List t = new ArrayList();
        List values = new ArrayList();
        List caseArrayN = new ArrayList();
        List nestTypeRS = new ArrayList();
        List nestNameRU = new ArrayList();
        List nestPairN = new ArrayList();

        public TempParamAnnotation (int numParams) {
            this.numParams = numParams;
            annoN = new int[numParams];
        }

        public void addParameterAnnotation(int parameter, String desc,
                List nameRU, List t, List values, List caseArrayN,
                List nestTypeRS, List nestNameRU, List nestPairN) {
            annoN[parameter]++;
            typeRS.add(desc);
            pairN.add(nameRU.size());
            this.nameRU.addAll(nameRU);
            this.t.addAll(t);
            this.values.addAll(values);
            this.caseArrayN.addAll(caseArrayN);
            this.nestTypeRS.addAll(nestTypeRS);
            this.nestNameRU.addAll(nestNameRU);
            this.nestPairN.addAll(nestPairN);
        }
    }

    public void addAnnotation(int context, String desc, boolean visible, List nameRU, List t, List values, List caseArrayN, List nestTypeRS, List nestNameRU, List nestPairN) {
        switch (context) {
        case MetadataBandGroup.CONTEXT_CLASS:
            if(visible) {
                class_RVA_bands.addAnnotation(desc, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN);
                if((class_flags[index] & (1<<21)) != 0) {
                    class_RVA_bands.incrementAnnoN();
                } else {
                    class_RVA_bands.newEntryInAnnoN();
                    class_flags[index] = class_flags[index] | (1<<21);
                }
            } else {
                class_RIA_bands.addAnnotation(desc, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN);
                if((class_flags[index] & (1<<22)) != 0) {
                    class_RIA_bands.incrementAnnoN();
                } else {
                    class_RIA_bands.newEntryInAnnoN();
                    class_flags[index] = class_flags[index] | (1<<22);
                }
            }
            break;
        case MetadataBandGroup.CONTEXT_FIELD:
            if(visible) {
                field_RVA_bands.addAnnotation(desc, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN);
                Long flag = (Long) tempFieldFlags.remove(tempFieldFlags.size() - 1);
                if((flag.intValue() & (1<<21)) != 0) {
                    field_RVA_bands.incrementAnnoN();
                } else {
                    field_RVA_bands.newEntryInAnnoN();
                }
                tempFieldFlags.add(new Long(flag.intValue() | (1<<21)));
            } else {
                field_RIA_bands.addAnnotation(desc, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN);
                Long flag = (Long) tempFieldFlags.remove(tempFieldFlags.size() - 1);
                if((flag.intValue() & (1<<22)) != 0) {
                    field_RIA_bands.incrementAnnoN();
                } else {
                    field_RIA_bands.newEntryInAnnoN();
                }
                tempFieldFlags.add(new Long(flag.intValue() | (1<<22)));
            }
            break;
        case MetadataBandGroup.CONTEXT_METHOD:
            if(visible) {
                method_RVA_bands.addAnnotation(desc, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN);
                Long flag = (Long) tempMethodFlags.remove(tempMethodFlags.size() - 1);
                if((flag.intValue() & (1<<21)) != 0) {
                    method_RVA_bands.incrementAnnoN();
                } else {
                    method_RVA_bands.newEntryInAnnoN();
                }
                tempMethodFlags.add(new Long(flag.intValue() | (1<<21)));
            } else {
                method_RIA_bands.addAnnotation(desc, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN);
                Long flag = (Long) tempMethodFlags.remove(tempMethodFlags.size() - 1);
                if((flag.intValue() & (1<<22)) != 0) {
                    method_RIA_bands.incrementAnnoN();
                } else {
                    method_RIA_bands.newEntryInAnnoN();
                }
                tempMethodFlags.add(new Long(flag.intValue() | (1<<22)));
            }
            break;
        }
    }

    public void addAnnotationDefault(List nameRU, List t, List values, List caseArrayN, List nestTypeRS, List nestNameRU, List nestPairN) {
        method_AD_bands.addAnnotation(null, nameRU, t, values, caseArrayN, nestTypeRS, nestNameRU, nestPairN);
        Long flag = (Long) tempMethodFlags.remove(tempMethodFlags.size() - 1);
        tempMethodFlags.add(new Long(flag.longValue() | (1<<25)));
    }

    /**
     * Remove all entries for the current class
     */
    public void removeCurrentClass() {
        // Note - this doesn't remove any entries added to the constant pool but
        // that shouldn't be a problem
        if ((class_flags[index] & (1 << 17)) != 0) {
            classSourceFile.remove(classSourceFile.size() - 1);
        }
        if ((class_flags[index] & (1 << 18)) != 0) {
            classEnclosingMethodClass
                    .remove(classEnclosingMethodClass.size() - 1);
            classEnclosingMethodDesc
                    .remove(classEnclosingMethodDesc.size() - 1);
        }
        if ((class_flags[index] & (1 << 19)) != 0) {
            classSignature.remove(classSignature.size() - 1);
        }
        if ((class_flags[index] & (1 << 21)) != 0) {
            class_RVA_bands.removeLatest();
        }
        if ((class_flags[index] & (1 << 22)) != 0) {
            class_RIA_bands.removeLatest();
        }
        for (Iterator iterator = tempFieldFlags.iterator(); iterator.hasNext();) {
            Long flagsL = (Long) iterator.next();
            long flags = flagsL.longValue();
            if ((flags & (1 << 19)) != 0) {
                fieldSignature.remove(fieldSignature.size() - 1);
            }
            if ((flags & (1 << 17)) != 0) {
                fieldConstantValueKQ.remove(fieldConstantValueKQ.size() - 1);
            }
            if ((flags & (1 << 21)) != 0) {
                field_RVA_bands.removeLatest();
            }
            if ((flags & (1 << 22)) != 0) {
                field_RIA_bands.removeLatest();
            }
        }
        for (Iterator iterator = tempMethodFlags.iterator(); iterator.hasNext();) {
            Long flagsL = (Long) iterator.next();
            long flags = flagsL.longValue();
            if ((flags & (1 << 19)) != 0) {
                methodSignature.remove(methodSignature.size() - 1);
            }
            if ((flags & (1 << 18)) != 0) {
                int exceptions = methodExceptionNumber
                        .remove(methodExceptionNumber.size() - 1);
                for (int i = 0; i < exceptions; i++) {
                    methodExceptionClasses
                            .remove(methodExceptionClasses.size() - 1);
                }
            }
            if ((flags & (1 << 17)) != 0) { // has code attribute
                codeMaxLocals.remove(codeMaxLocals.size() - 1);
                codeMaxStack.remove(codeMaxStack.size() - 1);
                int handlers = codeHandlerCount
                        .remove(codeHandlerCount.size() - 1);
                for (int i = 0; i < handlers; i++) {
                    int index = codeHandlerStartP.size() - 1;
                    codeHandlerStartP.remove(index);
                    codeHandlerEndPO.remove(index);
                    codeHandlerCatchPO.remove(index);
                    codeHandlerClass.remove(index);
                }
                if (!stripDebug) {
                    long cdeFlags = ((Long) codeFlags
                            .remove(codeFlags.size() - 1)).longValue();
                    int numLocalVariables = codeLocalVariableTableN
                            .remove(codeLocalVariableTableN.size() - 1);
                    for (int i = 0; i < numLocalVariables; i++) {
                        int location = codeLocalVariableTableBciP.size() - 1;
                        codeLocalVariableTableBciP.remove(location);
                        codeLocalVariableTableSpanO.remove(location);
                        codeLocalVariableTableNameRU.remove(location);
                        codeLocalVariableTableTypeRS.remove(location);
                        codeLocalVariableTableSlot.remove(location);
                    }
                    if ((cdeFlags & (1 << 3)) != 0) {
                        int numLocalVariablesInTypeTable = codeLocalVariableTypeTableN
                                .remove(codeLocalVariableTypeTableN.size() - 1);
                        for (int i = 0; i < numLocalVariablesInTypeTable; i++) {
                            int location = codeLocalVariableTypeTableBciP
                                    .size() - 1;
                            codeLocalVariableTypeTableBciP.remove(location);
                            codeLocalVariableTypeTableSpanO.remove(location);
                            codeLocalVariableTypeTableNameRU.remove(location);
                            codeLocalVariableTypeTableTypeRS.remove(location);
                            codeLocalVariableTypeTableSlot.remove(location);
                        }
                    }
                    if ((cdeFlags & (1 << 1)) != 0) {
                        int numLineNumbers = codeLineNumberTableN
                                .remove(codeLineNumberTableN.size() - 1);
                        for (int i = 0; i < numLineNumbers; i++) {
                            int location = codeLineNumberTableBciP.size() - 1;
                            codeLineNumberTableBciP.remove(location);
                            codeLineNumberTableLine.remove(location);
                        }
                    }
                }
            }
            if ((flags & (1 << 21)) != 0) {
                method_RVA_bands.removeLatest();
            }
            if ((flags & (1 << 22)) != 0) {
                method_RIA_bands.removeLatest();
            }
            if ((flags & (1 << 23)) != 0) {
                method_RVPA_bands.removeLatest();
            }
            if ((flags & (1 << 24)) != 0) {
                method_RIPA_bands.removeLatest();
            }
            if ((flags & (1 << 25)) != 0) {
                method_AD_bands.removeLatest();
            }
        }
        class_this[index] = null;
        class_super[index] = null;
        class_interface_count[index] = 0;
        class_interface[index] = null;
        major_versions[index] = 0;
        class_flags[index] = 0;
        tempFieldDesc.clear();
        tempFieldFlags.clear();
        tempMethodDesc.clear();
        tempMethodFlags.clear();
        if(index > 0) {
            index--;
        }
    }

    public int numClassesProcessed() {
        return index;
    }
}
