/*
 *  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.unpack200;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.harmony.pack200.Codec;
import org.apache.harmony.pack200.Pack200Exception;
import org.apache.harmony.unpack200.bytecode.Attribute;
import org.apache.harmony.unpack200.bytecode.CPClass;
import org.apache.harmony.unpack200.bytecode.CPNameAndType;
import org.apache.harmony.unpack200.bytecode.CPUTF8;
import org.apache.harmony.unpack200.bytecode.ClassFileEntry;
import org.apache.harmony.unpack200.bytecode.ConstantValueAttribute;
import org.apache.harmony.unpack200.bytecode.DeprecatedAttribute;
import org.apache.harmony.unpack200.bytecode.EnclosingMethodAttribute;
import org.apache.harmony.unpack200.bytecode.ExceptionsAttribute;
import org.apache.harmony.unpack200.bytecode.LineNumberTableAttribute;
import org.apache.harmony.unpack200.bytecode.LocalVariableTableAttribute;
import org.apache.harmony.unpack200.bytecode.LocalVariableTypeTableAttribute;
import org.apache.harmony.unpack200.bytecode.SignatureAttribute;
import org.apache.harmony.unpack200.bytecode.SourceFileAttribute;

/**
 * Class Bands
 */
public class ClassBands extends BandSet {

    private int[] classFieldCount;

    private long[] classFlags;

    private long[] classAccessFlags; // Access flags for writing to the class
    // file

    private int[][] classInterfacesInts;

    private int[] classMethodCount;

    private int[] classSuperInts;

    private String[] classThis;

    private int[] classThisInts;

    private ArrayList[] classAttributes;

    private int[] classVersionMajor;

    private int[] classVersionMinor;

    private IcTuple[][] icLocal;

    private List[] codeAttributes;

    private int[] codeHandlerCount;

    private int[] codeMaxNALocals;

    private int[] codeMaxStack;

    private ArrayList[][] fieldAttributes;

    private String[][] fieldDescr;

    private int[][] fieldDescrInts;

    private long[][] fieldFlags;

    private long[][] fieldAccessFlags;

    private ArrayList[][] methodAttributes;

    private String[][] methodDescr;

    private int[][] methodDescrInts;

    private long[][] methodFlags;

    private long[][] methodAccessFlags;

    private final AttributeLayoutMap attrMap;

    private final CpBands cpBands;

    private final SegmentOptions options;

    private final int classCount;

    private int[] methodAttrCalls;

    private int[][] codeHandlerStartP;

    private int[][] codeHandlerEndPO;

    private int[][] codeHandlerCatchPO;

    private int[][] codeHandlerClassRCN;

    private boolean [] codeHasAttributes;

    /**
     * @param segment
     */
    public ClassBands(Segment segment) {
        super(segment);
        this.attrMap = segment.getAttrDefinitionBands()
                .getAttributeDefinitionMap();
        this.cpBands = segment.getCpBands();
        this.classCount = header.getClassCount();
        this.options = header.getOptions();

    }

    /*
     * (non-Javadoc)
     *
     * @see org.apache.harmony.unpack200.BandSet#unpack(java.io.InputStream)
     */
    public void read(InputStream in) throws IOException, Pack200Exception {
        int classCount = header.getClassCount();
        classThisInts = decodeBandInt("class_this", in, Codec.DELTA5, classCount);
        classThis = getReferences(classThisInts, cpBands.getCpClass());
        classSuperInts = decodeBandInt("class_super", in, Codec.DELTA5,
                classCount);
        int[] classInterfaceLengths = decodeBandInt("class_interface_count",
                in, Codec.DELTA5, classCount);
        classInterfacesInts = decodeBandInt("class_interface", in, Codec.DELTA5,
                classInterfaceLengths);
        classFieldCount = decodeBandInt("class_field_count", in, Codec.DELTA5,
                classCount);
        classMethodCount = decodeBandInt("class_method_count", in,
                Codec.DELTA5, classCount);
        parseFieldBands(in);
        parseMethodBands(in);
        parseClassAttrBands(in);
        parseCodeBands(in);

    }

    public void unpack() {

    }

    private void parseFieldBands(InputStream in) throws IOException,
            Pack200Exception {
        fieldDescrInts = decodeBandInt("field_descr", in, Codec.DELTA5,
                classFieldCount);
        fieldDescr = getReferences(fieldDescrInts, cpBands.getCpDescriptor());
        parseFieldAttrBands(in);
    }

    private void parseFieldAttrBands(InputStream in) throws IOException,
            Pack200Exception {
        fieldFlags = parseFlags("field_flags", in, classFieldCount,
                Codec.UNSIGNED5, options.hasFieldFlagsHi());
        int fieldAttrCount = SegmentUtils.countBit16(fieldFlags);
        int[] fieldAttrCounts = decodeBandInt("field_attr_count", in,
                Codec.UNSIGNED5, fieldAttrCount);
        int[][] fieldAttrIndexes = decodeBandInt("field_attr_indexes", in,
                Codec.UNSIGNED5, fieldAttrCounts);
        int callCount = getCallCount(fieldAttrIndexes, fieldFlags,
                AttributeLayout.CONTEXT_FIELD);
        int[] fieldAttrCalls = decodeBandInt("field_attr_calls", in,
                Codec.UNSIGNED5, callCount);

        // Assign empty field attributes
        fieldAttributes = new ArrayList[classCount][];
        for (int i = 0; i < classCount; i++) {
            fieldAttributes[i] = new ArrayList[fieldFlags[i].length];
            for (int j = 0; j < fieldFlags[i].length; j++) {
                fieldAttributes[i][j] = new ArrayList();
            }
        }

        AttributeLayout constantValueLayout = attrMap.getAttributeLayout(
                "ConstantValue", AttributeLayout.CONTEXT_FIELD);
        int constantCount = SegmentUtils.countMatches(fieldFlags,
                constantValueLayout);
        int[] field_constantValue_KQ = decodeBandInt("field_ConstantValue_KQ",
                in, Codec.UNSIGNED5, constantCount);
        int constantValueIndex = 0;

        AttributeLayout signatureLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_SIGNATURE,
                AttributeLayout.CONTEXT_FIELD);
        int signatureCount = SegmentUtils.countMatches(fieldFlags,
                signatureLayout);
        int[] fieldSignatureRS = decodeBandInt("field_Signature_RS", in,
                Codec.UNSIGNED5, signatureCount);
        int signatureIndex = 0;

        AttributeLayout deprecatedLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_DEPRECATED,
                AttributeLayout.CONTEXT_FIELD);

        for (int i = 0; i < classCount; i++) {
            for (int j = 0; j < fieldFlags[i].length; j++) {
                long flag = fieldFlags[i][j];
                if (deprecatedLayout.matches(flag)) {
                    fieldAttributes[i][j].add(new DeprecatedAttribute());
                }
                if (constantValueLayout.matches(flag)) {
                    // we've got a value to read
                    long result = field_constantValue_KQ[constantValueIndex];
                    String desc = fieldDescr[i][j];
                    int colon = desc.indexOf(':');
                    String type = desc.substring(colon + 1);
                    if (type.equals("B") || type.equals("S")
                            || type.equals("C") || type.equals("Z"))
                        type = "I";
                    ClassFileEntry value = constantValueLayout.getValue(result,
                            type, cpBands.getConstantPool());
                    fieldAttributes[i][j]
                            .add(new ConstantValueAttribute(value));
                    constantValueIndex++;
                }
                if (signatureLayout.matches(flag)) {
                    // we've got a signature attribute
                    long result = fieldSignatureRS[signatureIndex];
                    String desc = fieldDescr[i][j];
                    int colon = desc.indexOf(':');
                    String type = desc.substring(colon + 1);
                    CPUTF8 value = (CPUTF8) signatureLayout.getValue(result,
                            type, cpBands.getConstantPool());
                    fieldAttributes[i][j].add(new SignatureAttribute(value));
                    signatureIndex++;
                }
            }
        }

        int backwardsCallsUsed = parseFieldMetadataBands(in, fieldAttrCalls);

        // Parse non-predefined attribute bands
        int backwardsCallIndex = backwardsCallsUsed;
        int limit = options.hasFieldFlagsHi() ? 62 : 31;
        AttributeLayout[] otherLayouts = new AttributeLayout[limit + 1];
        int[] counts = new int[limit + 1];
        List[] otherAttributes = new List[limit + 1];
        for (int i = 0; i < limit; i++) {
            AttributeLayout layout = attrMap.getAttributeLayout(i,
                    AttributeLayout.CONTEXT_FIELD);
            if (layout != null && !(layout.isDefaultLayout())) {
                otherLayouts[i] = layout;
                counts[i] = SegmentUtils.countMatches(fieldFlags, layout);
            }
        }
        for (int i = 0; i < counts.length; i++) {
            if (counts[i] > 0) {
                NewAttributeBands bands = attrMap
                        .getAttributeBands(otherLayouts[i]);
                otherAttributes[i] = bands.parseAttributes(in, counts[i]);
                int numBackwardsCallables = otherLayouts[i]
                        .numBackwardsCallables();
                if (numBackwardsCallables > 0) {
                    int[] backwardsCalls = new int[numBackwardsCallables];
                    System.arraycopy(fieldAttrCalls, backwardsCallIndex,
                            backwardsCalls, 0, numBackwardsCallables);
                    bands.setBackwardsCalls(backwardsCalls);
                    backwardsCallIndex += numBackwardsCallables;
                }
            }
        }

        // Non-predefined attributes
        for (int i = 0; i < classCount; i++) {
            for (int j = 0; j < fieldFlags[i].length; j++) {
                long flag = fieldFlags[i][j];
                int othersAddedAtStart = 0;
                for (int k = 0; k < otherLayouts.length; k++) {
                    if (otherLayouts[k] != null
                            && otherLayouts[k].matches(flag)) {
                        // Add the next attribute
                        if(otherLayouts[k].getIndex()<15) {
                            fieldAttributes[i][j].add(othersAddedAtStart++, otherAttributes[k].get(0));
                        } else {
                            fieldAttributes[i][j].add(otherAttributes[k].get(0));
                        }
                        otherAttributes[k].remove(0);
                    }
                }
            }
        }
    }

    private void parseMethodBands(InputStream in) throws IOException,
            Pack200Exception {
        methodDescrInts = decodeBandInt("method_descr", in, Codec.MDELTA5,
                classMethodCount);
        methodDescr = getReferences(methodDescrInts, cpBands.getCpDescriptor());
        parseMethodAttrBands(in);
    }

    private void parseMethodAttrBands(InputStream in) throws IOException,
            Pack200Exception {
        methodFlags = parseFlags("method_flags", in, classMethodCount,
                Codec.UNSIGNED5, options.hasMethodFlagsHi());
        int methodAttrCount = SegmentUtils.countBit16(methodFlags);
        int[] methodAttrCounts = decodeBandInt("method_attr_count", in,
                Codec.UNSIGNED5, methodAttrCount);
        int[][] methodAttrIndexes = decodeBandInt("method_attr_indexes", in,
                Codec.UNSIGNED5, methodAttrCounts);
        int callCount = getCallCount(methodAttrIndexes, methodFlags,
                AttributeLayout.CONTEXT_METHOD);
        methodAttrCalls = decodeBandInt("method_attr_calls", in, Codec.UNSIGNED5,
                callCount);

        // assign empty method attributes
        methodAttributes = new ArrayList[classCount][];
        for (int i = 0; i < classCount; i++) {
            methodAttributes[i] = new ArrayList[methodFlags[i].length];
            for (int j = 0; j < methodFlags[i].length; j++) {
                methodAttributes[i][j] = new ArrayList();
            }
        }

        // Parse method exceptions attributes
        AttributeLayout methodExceptionsLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_EXCEPTIONS,
                AttributeLayout.CONTEXT_METHOD);
        int count = SegmentUtils.countMatches(methodFlags,
                methodExceptionsLayout);
        int[] numExceptions = decodeBandInt("method_Exceptions_n", in,
                Codec.UNSIGNED5, count);
        int[][] methodExceptionsRS = decodeBandInt("method_Exceptions_RC",
                in, Codec.UNSIGNED5, numExceptions);

        // Parse method signature attributes
        AttributeLayout methodSignatureLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_SIGNATURE,
                AttributeLayout.CONTEXT_METHOD);
        int count1 = SegmentUtils.countMatches(methodFlags,
                methodSignatureLayout);
        int[] methodSignatureRS = decodeBandInt("method_signature_RS", in,
                Codec.UNSIGNED5, count1);

        AttributeLayout deprecatedLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_DEPRECATED,
                AttributeLayout.CONTEXT_METHOD);

        // Add attributes to the attribute arrays
        int methodExceptionsIndex = 0;
        int methodSignatureIndex = 0;
        for (int i = 0; i < methodAttributes.length; i++) {
            for (int j = 0; j < methodAttributes[i].length; j++) {
                long flag = methodFlags[i][j];
                if (methodExceptionsLayout.matches(flag)) {
                    int n = numExceptions[methodExceptionsIndex];
                    int[] exceptions = methodExceptionsRS[methodExceptionsIndex];
                    CPClass[] exceptionClasses = new CPClass[n];
                    for (int k = 0; k < n; k++) {
                        exceptionClasses[k] = cpBands
                                .cpClassValue(exceptions[k]);
                    }
                    methodAttributes[i][j].add(new ExceptionsAttribute(
                            exceptionClasses));
                    methodExceptionsIndex++;
                }
                if (methodSignatureLayout.matches(flag)) {
                    // We've got a signature attribute
                    long result = methodSignatureRS[methodSignatureIndex];
                    String desc = methodDescr[i][j];
                    int colon = desc.indexOf(':');
                    String type = desc.substring(colon + 1);
                    // TODO Got to get better at this ... in any case, it should
                    // be e.g. KIB or KIH
                    if (type.equals("B") || type.equals("H"))
                        type = "I";
                    CPUTF8 value = (CPUTF8) methodSignatureLayout.getValue(
                            result, type, cpBands.getConstantPool());
                    methodAttributes[i][j]
                            .add(new SignatureAttribute(value));
                    methodSignatureIndex++;
                }
                if (deprecatedLayout.matches(flag)) {
                    methodAttributes[i][j].add(new DeprecatedAttribute());
                }
            }
        }

        // Parse method metadata bands
        int backwardsCallsUsed = parseMethodMetadataBands(in, methodAttrCalls);

        // Parse non-predefined attribute bands
        int backwardsCallIndex = backwardsCallsUsed;
        int limit = options.hasMethodFlagsHi() ? 62 : 31;
        AttributeLayout[] otherLayouts = new AttributeLayout[limit + 1];
        int[] counts = new int[limit + 1];
        List[] otherAttributes = new List[limit + 1];
        for (int i = 0; i < limit; i++) {
            AttributeLayout layout = attrMap.getAttributeLayout(i,
                    AttributeLayout.CONTEXT_METHOD);
            if (layout != null && !(layout.isDefaultLayout())) {
                otherLayouts[i] = layout;
                counts[i] = SegmentUtils.countMatches(methodFlags, layout);
            }
        }
        for (int i = 0; i < counts.length; i++) {
            if (counts[i] > 0) {
                NewAttributeBands bands = attrMap
                        .getAttributeBands(otherLayouts[i]);
                otherAttributes[i] = bands.parseAttributes(in, counts[i]);
                int numBackwardsCallables = otherLayouts[i]
                        .numBackwardsCallables();
                if (numBackwardsCallables > 0) {
                    int[] backwardsCalls = new int[numBackwardsCallables];
                    System.arraycopy(methodAttrCalls, backwardsCallIndex,
                            backwardsCalls, 0, numBackwardsCallables);
                    bands.setBackwardsCalls(backwardsCalls);
                    backwardsCallIndex += numBackwardsCallables;
                }
            }
        }

        // Non-predefined attributes
        for (int i = 0; i < methodAttributes.length; i++) {
            for (int j = 0; j < methodAttributes[i].length; j++) {
                long flag = methodFlags[i][j];
                int othersAddedAtStart = 0;
                for (int k = 0; k < otherLayouts.length; k++) {
                    if (otherLayouts[k] != null
                            && otherLayouts[k].matches(flag)) {
                        // Add the next attribute
                        if(otherLayouts[k].getIndex() < 15) {
                            methodAttributes[i][j].add(othersAddedAtStart++, otherAttributes[k].get(0));
                        } else {
                            methodAttributes[i][j].add(otherAttributes[k].get(0));
                        }
                        otherAttributes[k].remove(0);
                    }
                }
            }
        }
    }

    private int getCallCount(int[][] methodAttrIndexes, long[][] flags,
            int context) throws Pack200Exception {
        int callCount = 0;
        for (int i = 0; i < methodAttrIndexes.length; i++) {
            for (int j = 0; j < methodAttrIndexes[i].length; j++) {
                int index = methodAttrIndexes[i][j];
                AttributeLayout layout = attrMap.getAttributeLayout(index,
                        context);
                callCount += layout.numBackwardsCallables();
            }
        }
        int layoutsUsed = 0;
        for (int i = 0; i < flags.length; i++) {
            for (int j = 0; j < flags[i].length; j++) {
                layoutsUsed |= flags[i][j];
            }
        }
        for (int i = 0; i < 26; i++) {
            if ((layoutsUsed & 1 << i) != 0) {
                AttributeLayout layout = attrMap.getAttributeLayout(i, context);
                callCount += layout.numBackwardsCallables();
            }
        }
        return callCount;
    }

    private void parseClassAttrBands(InputStream in) throws IOException,
            Pack200Exception {
        String[] cpUTF8 = cpBands.getCpUTF8();
        String[] cpClass = cpBands.getCpClass();

        // Prepare empty attribute lists
        classAttributes = new ArrayList[classCount];
        for (int i = 0; i < classCount; i++) {
            classAttributes[i] = new ArrayList();
        }

        classFlags = parseFlags("class_flags", in, classCount, Codec.UNSIGNED5,
                options.hasClassFlagsHi());
        int classAttrCount = SegmentUtils.countBit16(classFlags);
        int[] classAttrCounts = decodeBandInt("class_attr_count", in,
                Codec.UNSIGNED5, classAttrCount);
        int[][] classAttrIndexes = decodeBandInt("class_attr_indexes", in,
                Codec.UNSIGNED5, classAttrCounts);
        int callCount = getCallCount(classAttrIndexes,
                new long[][] { classFlags }, AttributeLayout.CONTEXT_CLASS);
        int[] classAttrCalls = decodeBandInt("class_attr_calls", in,
                Codec.UNSIGNED5, callCount);

        AttributeLayout deprecatedLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_DEPRECATED,
                AttributeLayout.CONTEXT_CLASS);

        AttributeLayout sourceFileLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_SOURCE_FILE,
                AttributeLayout.CONTEXT_CLASS);
        int sourceFileCount = SegmentUtils.countMatches(classFlags,
                sourceFileLayout);
        int[] classSourceFile = decodeBandInt("class_SourceFile_RUN", in,
                Codec.UNSIGNED5, sourceFileCount);

        AttributeLayout enclosingMethodLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_ENCLOSING_METHOD,
                AttributeLayout.CONTEXT_CLASS);
        int enclosingMethodCount = SegmentUtils.countMatches(classFlags,
                enclosingMethodLayout);
        int[] enclosingMethodRC = decodeBandInt(
                "class_EnclosingMethod_RC", in, Codec.UNSIGNED5,
                enclosingMethodCount);
        int[] enclosingMethodRDN = decodeBandInt(
                "class_EnclosingMethod_RDN", in, Codec.UNSIGNED5,
                enclosingMethodCount);

        AttributeLayout signatureLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_SIGNATURE,
                AttributeLayout.CONTEXT_CLASS);
        int signatureCount = SegmentUtils.countMatches(classFlags,
                signatureLayout);
        int[] classSignature = decodeBandInt("class_Signature_RS", in,
                Codec.UNSIGNED5, signatureCount);

        int backwardsCallsUsed = parseClassMetadataBands(in, classAttrCalls);

        AttributeLayout innerClassLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_INNER_CLASSES,
                AttributeLayout.CONTEXT_CLASS);
        int innerClassCount = SegmentUtils.countMatches(classFlags,
                innerClassLayout);
        int[] classInnerClassesN = decodeBandInt("class_InnerClasses_N", in,
                Codec.UNSIGNED5, innerClassCount);
        int[][] classInnerClassesRC = decodeBandInt("class_InnerClasses_RC",
                in, Codec.UNSIGNED5, classInnerClassesN);
        int[][] classInnerClassesF = decodeBandInt("class_InnerClasses_F", in,
                Codec.UNSIGNED5, classInnerClassesN);
        int flagsCount = 0;
        for (int i = 0; i < classInnerClassesF.length; i++) {
            for (int j = 0; j < classInnerClassesF[i].length; j++) {
                if (classInnerClassesF[i][j] != 0) {
                    flagsCount++;
                }
            }
        }
        int[] classInnerClassesOuterRCN = decodeBandInt(
                "class_InnerClasses_outer_RCN", in, Codec.UNSIGNED5, flagsCount);
        int[] classInnerClassesNameRUN = decodeBandInt(
                "class_InnerClasses_name_RUN", in, Codec.UNSIGNED5, flagsCount);

        AttributeLayout versionLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_CLASS_FILE_VERSION,
                AttributeLayout.CONTEXT_CLASS);
        int versionCount = SegmentUtils.countMatches(classFlags, versionLayout);
        int[] classFileVersionMinorH = decodeBandInt(
                "class_file_version_minor_H", in, Codec.UNSIGNED5, versionCount);
        int[] classFileVersionMajorH = decodeBandInt(
                "class_file_version_major_H", in, Codec.UNSIGNED5, versionCount);
        if (versionCount > 0) {
            classVersionMajor = new int[classCount];
            classVersionMinor = new int[classCount];
        }
        int defaultVersionMajor = header.getDefaultClassMajorVersion();
        int defaultVersionMinor = header.getDefaultClassMinorVersion();

        // Parse non-predefined attribute bands
        int backwardsCallIndex = backwardsCallsUsed;
        int limit = options.hasClassFlagsHi() ? 62 : 31;
        AttributeLayout[] otherLayouts = new AttributeLayout[limit + 1];
        int[] counts = new int[limit + 1];
        List[] otherAttributes = new List[limit + 1];
        for (int i = 0; i < limit; i++) {
            AttributeLayout layout = attrMap.getAttributeLayout(i,
                    AttributeLayout.CONTEXT_CLASS);
            if (layout != null && !(layout.isDefaultLayout())) {
                otherLayouts[i] = layout;
                counts[i] = SegmentUtils.countMatches(classFlags, layout);
            }
        }
        for (int i = 0; i < counts.length; i++) {
            if (counts[i] > 0) {
                NewAttributeBands bands = attrMap
                        .getAttributeBands(otherLayouts[i]);
                otherAttributes[i] = bands.parseAttributes(in, counts[i]);
                int numBackwardsCallables = otherLayouts[i]
                        .numBackwardsCallables();
                if (numBackwardsCallables > 0) {
                    int[] backwardsCalls = new int[numBackwardsCallables];
                    System.arraycopy(classAttrCalls, backwardsCallIndex,
                            backwardsCalls, 0, numBackwardsCallables);
                    bands.setBackwardsCalls(backwardsCalls);
                    backwardsCallIndex += numBackwardsCallables;
                }
            }
        }

        // Now process the attribute bands we have parsed
        int sourceFileIndex = 0;
        int enclosingMethodIndex = 0;
        int signatureIndex = 0;
        int innerClassIndex = 0;
        int innerClassC2NIndex = 0;
        int versionIndex = 0;
        icLocal = new IcTuple[classCount][];
        for (int i = 0; i < classCount; i++) {
            long flag = classFlags[i];
            if (deprecatedLayout.matches(classFlags[i])) {
                classAttributes[i].add(new DeprecatedAttribute());
            }
            if (sourceFileLayout.matches(flag)) {
                long result = classSourceFile[sourceFileIndex];
                ClassFileEntry value = sourceFileLayout.getValue(result,
                        cpBands.getConstantPool());
                if (value == null) {
                    // Remove package prefix
                    String className = classThis[i].substring(classThis[i]
                            .lastIndexOf('/') + 1);
                    className = className
                            .substring(className.lastIndexOf('.') + 1);

                    // Remove mangled nested class names
                    char[] chars = className.toCharArray();
                    int index = -1;
                    for (int j = 0; j < chars.length; j++) {
                        if (chars[j] <= 0x2D) {
                            index = j;
                            break;
                        }
                    }
                    if (index > -1) {
                        className = className.substring(0, index);
                    }
                    // Add .java to the end
                    value = cpBands.cpUTF8Value(className + ".java", true);
                }
                classAttributes[i].add(new SourceFileAttribute((CPUTF8)value));
                sourceFileIndex++;
            }
            if (enclosingMethodLayout.matches(flag)) {
                CPClass theClass = cpBands
                        .cpClassValue(enclosingMethodRC[enclosingMethodIndex]);
                CPNameAndType theMethod = null;
                if(enclosingMethodRDN[enclosingMethodIndex] != 0) {
                    theMethod = cpBands
                            .cpNameAndTypeValue(enclosingMethodRDN[enclosingMethodIndex] - 1);
                }
                classAttributes[i].add(new EnclosingMethodAttribute(theClass,
                        theMethod));
                enclosingMethodIndex++;
            }
            if (signatureLayout.matches(flag)) {
                long result = classSignature[signatureIndex];
                CPUTF8 value = (CPUTF8) signatureLayout.getValue(result, cpBands
                        .getConstantPool());
                classAttributes[i].add(new SignatureAttribute(value));
                signatureIndex++;
            }
            if (innerClassLayout.matches(flag)) {
                // Just create the tuples for now because the attributes are
                // decided at the end when creating class constant pools
                icLocal[i] = new IcTuple[classInnerClassesN[innerClassIndex]];
                for (int j = 0; j < icLocal[i].length; j++) {
                    int icTupleCIndex = classInnerClassesRC[innerClassIndex][j];
                    int icTupleC2Index = -1;
                    int icTupleNIndex = -1;

                    String icTupleC = cpClass[icTupleCIndex];
                    int icTupleF = classInnerClassesF[innerClassIndex][j];
                    String icTupleC2 = null;
                    String icTupleN = null;

                    if (icTupleF != 0) {
                        icTupleC2Index = classInnerClassesOuterRCN[innerClassC2NIndex];
                        icTupleNIndex = classInnerClassesNameRUN[innerClassC2NIndex];
                        icTupleC2 = cpClass[icTupleC2Index];
                        icTupleN = cpUTF8[icTupleNIndex];
                        innerClassC2NIndex++;
                    } else {
                        // Get from icBands
                        IcBands icBands = segment.getIcBands();
                        IcTuple[] icAll = icBands.getIcTuples();
                        for (int k = 0; k < icAll.length; k++) {
                            if (icAll[k].getC().equals(icTupleC)) {
                                icTupleF = icAll[k].getF();
                                icTupleC2 = icAll[k].getC2();
                                icTupleN = icAll[k].getN();
                                break;
                            }
                        }
                    }

                    IcTuple icTuple = new IcTuple(icTupleC, icTupleF,
                            icTupleC2, icTupleN, icTupleCIndex, icTupleC2Index, icTupleNIndex, j);
                    icLocal[i][j] = icTuple;
                }
                innerClassIndex++;
            }
            if (versionLayout.matches(flag)) {
                classVersionMajor[i] = classFileVersionMajorH[versionIndex];
                classVersionMinor[i] = classFileVersionMinorH[versionIndex];
                versionIndex++;
            } else if (classVersionMajor != null) {
                // Fill in with defaults
                classVersionMajor[i] = defaultVersionMajor;
                classVersionMinor[i] = defaultVersionMinor;
            }
            // Non-predefined attributes
            for (int j = 0; j < otherLayouts.length; j++) {
                if (otherLayouts[j] != null && otherLayouts[j].matches(flag)) {
                    // Add the next attribute
                    classAttributes[i].add(otherAttributes[j].get(0));
                    otherAttributes[j].remove(0);
                }
            }
        }
    }

    private void parseCodeBands(InputStream in) throws Pack200Exception,
            IOException {
        AttributeLayout layout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_CODE, AttributeLayout.CONTEXT_METHOD);

        int codeCount = SegmentUtils.countMatches(methodFlags, layout);
        int[] codeHeaders = decodeBandInt("code_headers", in, Codec.BYTE1,
                codeCount);

        boolean allCodeHasFlags = segment.getSegmentHeader().getOptions().hasAllCodeFlags();
        if(!allCodeHasFlags) {
            codeHasAttributes = new boolean[codeCount];
        }
        int codeSpecialHeader = 0;
        for (int i = 0; i < codeCount; i++) {
            if (codeHeaders[i] == 0) {
                codeSpecialHeader++;
                if(!allCodeHasFlags) {
                    codeHasAttributes[i] = true;
                }
            }
        }
        int[] codeMaxStackSpecials = decodeBandInt("code_max_stack", in,
                Codec.UNSIGNED5, codeSpecialHeader);
        int[] codeMaxNALocalsSpecials = decodeBandInt("code_max_na_locals", in,
                Codec.UNSIGNED5, codeSpecialHeader);
        int[] codeHandlerCountSpecials = decodeBandInt("code_handler_count",
                in, Codec.UNSIGNED5, codeSpecialHeader);

        codeMaxStack = new int[codeCount];
        codeMaxNALocals = new int[codeCount];
        codeHandlerCount = new int[codeCount];
        int special = 0;
        for (int i = 0; i < codeCount; i++) {
            int header = 0xff & codeHeaders[i];
            if (header < 0) {
                throw new IllegalStateException("Shouldn't get here");
            } else if (header == 0) {
                codeMaxStack[i] = codeMaxStackSpecials[special];
                codeMaxNALocals[i] = codeMaxNALocalsSpecials[special];
                codeHandlerCount[i] = codeHandlerCountSpecials[special];
                special++;
            } else if (header <= 144) {
                codeMaxStack[i] = (header - 1) % 12;
                codeMaxNALocals[i] = (header - 1) / 12;
                codeHandlerCount[i] = 0;
            } else if (header <= 208) {
                codeMaxStack[i] = (header - 145) % 8;
                codeMaxNALocals[i] = (header - 145) / 8;
                codeHandlerCount[i] = 1;
            } else if (header <= 255) {
                codeMaxStack[i] = (header - 209) % 7;
                codeMaxNALocals[i] = (header - 209) / 7;
                codeHandlerCount[i] = 2;
            } else {
                throw new IllegalStateException("Shouldn't get here either");
            }
        }
        codeHandlerStartP = decodeBandInt("code_handler_start_P", in,
                Codec.BCI5, codeHandlerCount);
        codeHandlerEndPO = decodeBandInt("code_handler_end_PO", in,
                Codec.BRANCH5, codeHandlerCount);
        codeHandlerCatchPO = decodeBandInt("code_handler_catch_PO", in,
                Codec.BRANCH5, codeHandlerCount);
        codeHandlerClassRCN = decodeBandInt(
                "code_handler_class_RCN", in, Codec.UNSIGNED5, codeHandlerCount);

        int codeFlagsCount = allCodeHasFlags ? codeCount : codeSpecialHeader;

        codeAttributes = new List[codeFlagsCount];
        for (int i = 0; i < codeAttributes.length; i++) {
            codeAttributes[i] = new ArrayList();
        }
        parseCodeAttrBands(in, codeFlagsCount);
    }

    private void parseCodeAttrBands(InputStream in, int codeFlagsCount)
            throws IOException, Pack200Exception {
        long[] codeFlags = parseFlags("code_flags", in, codeFlagsCount,
                Codec.UNSIGNED5, segment.getSegmentHeader().getOptions()
                        .hasCodeFlagsHi());
        int codeAttrCount = SegmentUtils.countBit16(codeFlags);
        int[] codeAttrCounts = decodeBandInt("code_attr_count", in,
                Codec.UNSIGNED5, codeAttrCount);
        int[][] codeAttrIndexes = decodeBandInt("code_attr_indexes", in,
                Codec.UNSIGNED5, codeAttrCounts);
        int callCount = 0;
        for (int i = 0; i < codeAttrIndexes.length; i++) {
            for (int j = 0; j < codeAttrIndexes[i].length; j++) {
                int index = codeAttrIndexes[i][j];
                AttributeLayout layout = attrMap.getAttributeLayout(index,
                        AttributeLayout.CONTEXT_CODE);
                callCount += layout.numBackwardsCallables();
            }
        }
        int[] codeAttrCalls = decodeBandInt("code_attr_calls", in,
                Codec.UNSIGNED5, callCount);

        AttributeLayout lineNumberTableLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_LINE_NUMBER_TABLE,
                AttributeLayout.CONTEXT_CODE);
        int lineNumberTableCount = SegmentUtils.countMatches(codeFlags,
                lineNumberTableLayout);
        int[] lineNumberTableN = decodeBandInt("code_LineNumberTable_N", in,
                Codec.UNSIGNED5, lineNumberTableCount);
        int[][] lineNumberTableBciP = decodeBandInt(
                "code_LineNumberTable_bci_P", in, Codec.BCI5, lineNumberTableN);
        int[][] lineNumberTableLine = decodeBandInt(
                "code_LineNumberTable_line", in, Codec.UNSIGNED5,
                lineNumberTableN);

        AttributeLayout localVariableTableLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_LOCAL_VARIABLE_TABLE,
                AttributeLayout.CONTEXT_CODE);
        AttributeLayout localVariableTypeTableLayout = attrMap
                .getAttributeLayout(
                        AttributeLayout.ATTRIBUTE_LOCAL_VARIABLE_TYPE_TABLE,
                        AttributeLayout.CONTEXT_CODE);

        int lengthLocalVariableNBand = SegmentUtils.countMatches(codeFlags,
                localVariableTableLayout);
        int[] localVariableTableN = decodeBandInt("code_LocalVariableTable_N",
                in, Codec.UNSIGNED5, lengthLocalVariableNBand);
        int[][] localVariableTableBciP = decodeBandInt(
                "code_LocalVariableTable_bci_P", in, Codec.BCI5,
                localVariableTableN);
        int[][] localVariableTableSpanO = decodeBandInt(
                "code_LocalVariableTable_span_O", in, Codec.BRANCH5,
                localVariableTableN);
        CPUTF8[][] localVariableTableNameRU = parseCPUTF8References(
                "code_LocalVariableTable_name_RU", in, Codec.UNSIGNED5,
                localVariableTableN);
        CPUTF8[][] localVariableTableTypeRS = parseCPSignatureReferences(
                "code_LocalVariableTable_type_RS", in, Codec.UNSIGNED5,
                localVariableTableN);
        int[][] localVariableTableSlot = decodeBandInt(
                "code_LocalVariableTable_slot", in, Codec.UNSIGNED5,
                localVariableTableN);

        int lengthLocalVariableTypeTableNBand = SegmentUtils.countMatches(
                codeFlags, localVariableTypeTableLayout);
        int[] localVariableTypeTableN = decodeBandInt(
                "code_LocalVariableTypeTable_N", in, Codec.UNSIGNED5,
                lengthLocalVariableTypeTableNBand);
        int[][] localVariableTypeTableBciP = decodeBandInt(
                "code_LocalVariableTypeTable_bci_P", in, Codec.BCI5,
                localVariableTypeTableN);
        int[][] localVariableTypeTableSpanO = decodeBandInt(
                "code_LocalVariableTypeTable_span_O", in, Codec.BRANCH5,
                localVariableTypeTableN);
        CPUTF8[][] localVariableTypeTableNameRU = parseCPUTF8References(
                "code_LocalVariableTypeTable_name_RU", in, Codec.UNSIGNED5,
                localVariableTypeTableN);
        CPUTF8[][] localVariableTypeTableTypeRS = parseCPSignatureReferences(
                "code_LocalVariableTypeTable_type_RS", in, Codec.UNSIGNED5,
                localVariableTypeTableN);
        int[][] localVariableTypeTableSlot = decodeBandInt(
                "code_LocalVariableTypeTable_slot", in, Codec.UNSIGNED5,
                localVariableTypeTableN);

        // Parse non-predefined attribute bands
        int backwardsCallIndex = 0;
        int limit = options.hasCodeFlagsHi() ? 62 : 31;
        AttributeLayout[] otherLayouts = new AttributeLayout[limit + 1];
        int[] counts = new int[limit + 1];
        List[] otherAttributes = new List[limit + 1];
        for (int i = 0; i < limit; i++) {
            AttributeLayout layout = attrMap.getAttributeLayout(i,
                    AttributeLayout.CONTEXT_CODE);
            if (layout != null && !(layout.isDefaultLayout())) {
                otherLayouts[i] = layout;
                counts[i] = SegmentUtils.countMatches(codeFlags, layout);
            }
        }
        for (int i = 0; i < counts.length; i++) {
            if (counts[i] > 0) {
                NewAttributeBands bands = attrMap
                        .getAttributeBands(otherLayouts[i]);
                otherAttributes[i] = bands.parseAttributes(in, counts[i]);
                int numBackwardsCallables = otherLayouts[i]
                        .numBackwardsCallables();
                if (numBackwardsCallables > 0) {
                    int[] backwardsCalls = new int[numBackwardsCallables];
                    System.arraycopy(codeAttrCalls, backwardsCallIndex,
                            backwardsCalls, 0, numBackwardsCallables);
                    bands.setBackwardsCalls(backwardsCalls);
                    backwardsCallIndex += numBackwardsCallables;
                }
            }
        }

        int lineNumberIndex = 0;
        int lvtIndex = 0;
        int lvttIndex = 0;
        for (int i = 0; i < codeFlagsCount; i++) {
            if (lineNumberTableLayout.matches(codeFlags[i])) {
                LineNumberTableAttribute lnta = new LineNumberTableAttribute(
                        lineNumberTableN[lineNumberIndex],
                        lineNumberTableBciP[lineNumberIndex],
                        lineNumberTableLine[lineNumberIndex]);
                lineNumberIndex++;
                codeAttributes[i].add(lnta);
            }
            if (localVariableTableLayout.matches(codeFlags[i])) {
                LocalVariableTableAttribute lvta = new LocalVariableTableAttribute(
                        localVariableTableN[lvtIndex],
                        localVariableTableBciP[lvtIndex],
                        localVariableTableSpanO[lvtIndex],
                        localVariableTableNameRU[lvtIndex],
                        localVariableTableTypeRS[lvtIndex],
                        localVariableTableSlot[lvtIndex]);
                lvtIndex++;
                codeAttributes[i].add(lvta);
            }
            if (localVariableTypeTableLayout.matches(codeFlags[i])) {
                LocalVariableTypeTableAttribute lvtta = new LocalVariableTypeTableAttribute(
                        localVariableTypeTableN[lvttIndex],
                        localVariableTypeTableBciP[lvttIndex],
                        localVariableTypeTableSpanO[lvttIndex],
                        localVariableTypeTableNameRU[lvttIndex],
                        localVariableTypeTableTypeRS[lvttIndex],
                        localVariableTypeTableSlot[lvttIndex]);
                lvttIndex++;
                codeAttributes[i].add(lvtta);
            }
            // Non-predefined attributes
            for (int j = 0; j < otherLayouts.length; j++) {
                if (otherLayouts[j] != null
                        && otherLayouts[j].matches(codeFlags[i])) {
                    // Add the next attribute
                    codeAttributes[i].add(otherAttributes[j].get(0));
                    otherAttributes[j].remove(0);
                }
            }
        }

    }

    private int parseFieldMetadataBands(InputStream in, int[] fieldAttrCalls)
            throws Pack200Exception, IOException {
        int backwardsCallsUsed = 0;
        String[] RxA = new String[] { "RVA", "RIA" };

        AttributeLayout rvaLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS,
                AttributeLayout.CONTEXT_FIELD);
        AttributeLayout riaLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS,
                AttributeLayout.CONTEXT_FIELD);

        int rvaCount = SegmentUtils.countMatches(fieldFlags, rvaLayout);
        int riaCount = SegmentUtils.countMatches(fieldFlags, riaLayout);
        int[] RxACount = new int[] { rvaCount, riaCount };
        int[] backwardsCalls = new int[] { 0, 0 };
        if (rvaCount > 0) {
            backwardsCalls[0] = fieldAttrCalls[0];
            backwardsCallsUsed++;
            if (riaCount > 0) {
                backwardsCalls[1] = fieldAttrCalls[1];
                backwardsCallsUsed++;
            }
        } else if (riaCount > 0) {
            backwardsCalls[1] = fieldAttrCalls[0];
            backwardsCallsUsed++;
        }
        MetadataBandGroup[] mb = parseMetadata(in, RxA, RxACount,
                backwardsCalls, "field");
        List rvaAttributes = mb[0].getAttributes();
        List riaAttributes = mb[1].getAttributes();
        int rvaAttributesIndex = 0;
        int riaAttributesIndex = 0;
        for (int i = 0; i < fieldFlags.length; i++) {
            for (int j = 0; j < fieldFlags[i].length; j++) {
                if (rvaLayout.matches(fieldFlags[i][j])) {
                    fieldAttributes[i][j].add(rvaAttributes.get(rvaAttributesIndex++));
                }
                if (riaLayout.matches(fieldFlags[i][j])) {
                    fieldAttributes[i][j].add(riaAttributes.get(riaAttributesIndex++));
                }
            }
        }
        return backwardsCallsUsed;
    }

    private MetadataBandGroup[] parseMetadata(InputStream in, String[] RxA,
            int[] RxACount, int[] backwardsCallCounts, String contextName)
            throws IOException, Pack200Exception {
        MetadataBandGroup[] mbg = new MetadataBandGroup[RxA.length];
        for (int i = 0; i < RxA.length; i++) {
            mbg[i] = new MetadataBandGroup(RxA[i], cpBands);
            String rxa = RxA[i];
            if (rxa.indexOf('P') >= 0) {
                mbg[i].param_NB = decodeBandInt(contextName + "_" + rxa
                        + "_param_NB", in, Codec.BYTE1, RxACount[i]);
            }
            int pairCount = 0;
            if (!rxa.equals("AD")) {
                mbg[i].anno_N = decodeBandInt(contextName + "_" + rxa
                        + "_anno_N", in, Codec.UNSIGNED5, RxACount[i]);
                mbg[i].type_RS = parseCPSignatureReferences(contextName
                        + "_" + rxa + "_type_RS", in, Codec.UNSIGNED5,
                        mbg[i].anno_N);
                mbg[i].pair_N = decodeBandInt(contextName + "_" + rxa
                        + "_pair_N", in, Codec.UNSIGNED5, mbg[i].anno_N);
                for (int j = 0; j < mbg[i].pair_N.length; j++) {
                    for (int k = 0; k < mbg[i].pair_N[j].length; k++) {
                        pairCount += mbg[i].pair_N[j][k];
                    }
                }

                mbg[i].name_RU = parseCPUTF8References(contextName
                        + "_" + rxa + "_name_RU", in, Codec.UNSIGNED5,
                        pairCount);
            } else {
                pairCount = RxACount[i];
            }
            mbg[i].T = decodeBandInt(contextName + "_" + rxa + "_T", in,
                    Codec.BYTE1, pairCount + backwardsCallCounts[i]);
            int ICount = 0, DCount = 0, FCount = 0, JCount = 0, cCount = 0, eCount = 0, sCount = 0, arrayCount = 0, atCount = 0;
            for (int j = 0; j < mbg[i].T.length; j++) {
                char c = (char) mbg[i].T[j];
                switch (c) {
                case 'B':
                case 'C':
                case 'I':
                case 'S':
                case 'Z':
                    ICount++;
                    break;
                case 'D':
                    DCount++;
                    break;
                case 'F':
                    FCount++;
                    break;
                case 'J':
                    JCount++;
                    break;
                case 'c':
                    cCount++;
                    break;
                case 'e':
                    eCount++;
                    break;
                case 's':
                    sCount++;
                    break;
                case '[':
                    arrayCount++;
                    break;
                case '@':
                    atCount++;
                    break;
                }
            }
            mbg[i].caseI_KI = parseCPIntReferences(contextName + "_" + rxa
                    + "_caseI_KI", in, Codec.UNSIGNED5, ICount);
            mbg[i].caseD_KD = parseCPDoubleReferences(contextName + "_" + rxa
                    + "_caseD_KD", in, Codec.UNSIGNED5, DCount);
            mbg[i].caseF_KF = parseCPFloatReferences(contextName + "_" + rxa
                    + "_caseF_KF", in, Codec.UNSIGNED5, FCount);
            mbg[i].caseJ_KJ = parseCPLongReferences(contextName + "_" + rxa
                    + "_caseJ_KJ", in, Codec.UNSIGNED5, JCount);
            mbg[i].casec_RS = parseCPSignatureReferences(contextName + "_" + rxa
                    + "_casec_RS", in, Codec.UNSIGNED5, cCount);
            mbg[i].caseet_RS = parseReferences(contextName + "_" + rxa
                    + "_caseet_RS", in, Codec.UNSIGNED5, eCount, cpBands
                    .getCpSignature());
            mbg[i].caseec_RU = parseReferences(contextName + "_" + rxa
                    + "_caseec_RU", in, Codec.UNSIGNED5, eCount, cpBands
                    .getCpUTF8());
            mbg[i].cases_RU = parseCPUTF8References(contextName + "_" + rxa
                    + "_cases_RU", in, Codec.UNSIGNED5, sCount);
            mbg[i].casearray_N = decodeBandInt(contextName + "_" + rxa
                    + "_casearray_N", in, Codec.UNSIGNED5, arrayCount);
            mbg[i].nesttype_RS = parseCPUTF8References(contextName + "_" + rxa
                    + "_nesttype_RS", in, Codec.UNSIGNED5, atCount);
            mbg[i].nestpair_N = decodeBandInt(contextName + "_" + rxa
                    + "_nestpair_N", in, Codec.UNSIGNED5, atCount);
            int nestPairCount = 0;
            for (int j = 0; j < mbg[i].nestpair_N.length; j++) {
                nestPairCount += mbg[i].nestpair_N[j];
            }
            mbg[i].nestname_RU = parseCPUTF8References(contextName + "_" + rxa
                    + "_nestname_RU", in, Codec.UNSIGNED5, nestPairCount);
        }
        return mbg;
    }

    private int parseMethodMetadataBands(InputStream in, int[] methodAttrCalls)
            throws Pack200Exception, IOException {
        int backwardsCallsUsed = 0;
        String[] RxA = new String[] { "RVA", "RIA", "RVPA", "RIPA", "AD" };
        int[] rxaCounts = new int[] { 0, 0, 0, 0, 0 };
        
        AttributeLayout rvaLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS,
                AttributeLayout.CONTEXT_METHOD);
        AttributeLayout riaLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS,
                AttributeLayout.CONTEXT_METHOD);
        AttributeLayout rvpaLayout = attrMap
                .getAttributeLayout(
                        AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS,
                        AttributeLayout.CONTEXT_METHOD);
        AttributeLayout ripaLayout = attrMap
                .getAttributeLayout(
                        AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS,
                        AttributeLayout.CONTEXT_METHOD);
        AttributeLayout adLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_ANNOTATION_DEFAULT,
                AttributeLayout.CONTEXT_METHOD);
        AttributeLayout[] rxaLayouts = new AttributeLayout[] { rvaLayout,
                riaLayout, rvpaLayout, ripaLayout, adLayout };

        for (int i = 0; i < rxaLayouts.length; i++) {
            rxaCounts[i] = SegmentUtils
                    .countMatches(methodFlags, rxaLayouts[i]);
        }
        int[] backwardsCalls = new int[5];
        int methodAttrIndex = 0;
        for (int i = 0; i < backwardsCalls.length; i++) {
            if (rxaCounts[i] > 0) {
                backwardsCallsUsed++;
                backwardsCalls[i] = methodAttrCalls[methodAttrIndex];
                methodAttrIndex++;
            } else {
                backwardsCalls[i] = 0;
            }
        }
        MetadataBandGroup[] mbgs = parseMetadata(in, RxA, rxaCounts,
                backwardsCalls, "method");
        List[] attributeLists = new List[RxA.length];
        int[] attributeListIndexes = new int[RxA.length];
        for (int i = 0; i < mbgs.length; i++) {
            attributeLists[i] = mbgs[i].getAttributes();
            attributeListIndexes[i] = 0;
        }
        for (int i = 0; i < methodFlags.length; i++) {
            for (int j = 0; j < methodFlags[i].length; j++) {
                for (int k = 0; k < rxaLayouts.length; k++) {
                    if (rxaLayouts[k].matches(methodFlags[i][j])) {
                        methodAttributes[i][j]
                                .add(attributeLists[k].get(attributeListIndexes[k]++));
                    }
                }
            }
        }
        return backwardsCallsUsed;
    }

    /**
     * Parse the class metadata bands and return the number of backwards
     * callables
     *
     * @param in
     * @param classAttrCalls
     * @return
     * @throws Pack200Exception
     * @throws IOException
     */
    private int parseClassMetadataBands(InputStream in, int[] classAttrCalls)
            throws Pack200Exception, IOException {
        int numBackwardsCalls = 0;
        String[] RxA = new String[] { "RVA", "RIA" };

        AttributeLayout rvaLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS,
                AttributeLayout.CONTEXT_CLASS);
        AttributeLayout riaLayout = attrMap.getAttributeLayout(
                AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS,
                AttributeLayout.CONTEXT_CLASS);
        int rvaCount = SegmentUtils.countMatches(classFlags, rvaLayout);
        int riaCount = SegmentUtils.countMatches(classFlags, riaLayout);
        int[] RxACount = new int[] { rvaCount, riaCount };
        int[] backwardsCalls = new int[] { 0, 0 };
        if (rvaCount > 0) {
            numBackwardsCalls++;
            backwardsCalls[0] = classAttrCalls[0];
            if (riaCount > 0) {
                numBackwardsCalls++;
                backwardsCalls[1] = classAttrCalls[1];
            }
        } else if (riaCount > 0) {
            numBackwardsCalls++;
            backwardsCalls[1] = classAttrCalls[0];
        }
        MetadataBandGroup[] mbgs = parseMetadata(in, RxA, RxACount,
                backwardsCalls, "class");
        List rvaAttributes = mbgs[0].getAttributes();
        List riaAttributes = mbgs[1].getAttributes();
        int rvaAttributesIndex = 0;
        int riaAttributesIndex = 0;
        for (int i = 0; i < classFlags.length; i++) {
            if (rvaLayout.matches(classFlags[i])) {
                classAttributes[i].add(rvaAttributes.get(rvaAttributesIndex++));
            }
            if (riaLayout.matches(classFlags[i])) {
                classAttributes[i].add(riaAttributes.get(riaAttributesIndex++));
            }
        }
        return numBackwardsCalls;
    }

    public ArrayList[] getClassAttributes() {
        return classAttributes;
    }

    public int[] getClassFieldCount() {
        return classFieldCount;
    }

    public long[] getRawClassFlags() {
        return classFlags;
    }

    public long[] getClassFlags() throws Pack200Exception {
        if (classAccessFlags == null) {
            long mask = 0x7FFF;
            for (int i = 0; i < 16; i++) {
                AttributeLayout layout = attrMap.getAttributeLayout(i,
                        AttributeLayout.CONTEXT_CLASS);
                if (layout != null && !layout.isDefaultLayout()) {
                    mask &= ~(1 << i);
                }
            }
            classAccessFlags = new long[classFlags.length];
            for (int i = 0; i < classFlags.length; i++) {
                classAccessFlags[i] = classFlags[i] & mask;
            }
        }
        return classAccessFlags;
    }

    public int[][] getClassInterfacesInts() {
        return classInterfacesInts;
    }

    public int[] getClassMethodCount() {
        return classMethodCount;
    }

    public int[] getClassSuperInts() {
        return classSuperInts;
    }

    public int[] getClassThisInts() {
        return classThisInts;
    }

    public int[] getCodeMaxNALocals() {
        return codeMaxNALocals;
    }

    public int[] getCodeMaxStack() {
        return codeMaxStack;
    }

    public ArrayList[][] getFieldAttributes() {
        return fieldAttributes;
    }

    public int[][] getFieldDescrInts() {
        return fieldDescrInts;
    }

    public int[][] getMethodDescrInts() {
        return methodDescrInts;
    }

    public long[][] getFieldFlags() throws Pack200Exception {
        if (fieldAccessFlags == null) {
            long mask = 0x7FFF;
            for (int i = 0; i < 16; i++) {
                AttributeLayout layout = attrMap.getAttributeLayout(i,
                        AttributeLayout.CONTEXT_FIELD);
                if (layout != null && !layout.isDefaultLayout()) {
                    mask &= ~(1 << i);
                }
            }
            fieldAccessFlags = new long[fieldFlags.length][];
            for (int i = 0; i < fieldFlags.length; i++) {
                fieldAccessFlags[i] = new long[fieldFlags[i].length];
                for (int j = 0; j < fieldFlags[i].length; j++) {
                    fieldAccessFlags[i][j] = fieldFlags[i][j] & mask;
                }
            }
        }
        return fieldAccessFlags;
    }

    /**
     * Answer an ArrayList of ArrayLists which hold the code attributes
     * corresponding to all classes in order.
     *
     * If a class doesn't have any attributes, the corresponding element in this
     * list will be an empty ArrayList.
     *
     * @return ArrayList
     */
    public ArrayList getOrderedCodeAttributes() {
        ArrayList orderedAttributeList = new ArrayList(codeAttributes.length);
        for (int classIndex = 0; classIndex < codeAttributes.length; classIndex++) {
            ArrayList currentAttributes = new ArrayList(codeAttributes[classIndex].size());
            for (int attributeIndex = 0; attributeIndex < codeAttributes[classIndex]
                    .size(); attributeIndex++) {
                Attribute attribute = (Attribute) codeAttributes[classIndex]
                        .get(attributeIndex);
                currentAttributes.add(attribute);
            }
            orderedAttributeList.add(currentAttributes);
        }
        return orderedAttributeList;
    }

    public ArrayList[][] getMethodAttributes() {
        return methodAttributes;
    }

    public String[][] getMethodDescr() {
        return methodDescr;
    }

    public long[][] getMethodFlags() throws Pack200Exception {
        if (methodAccessFlags == null) {
            long mask = 0x7FFF;
            for (int i = 0; i < 16; i++) {
                AttributeLayout layout = attrMap.getAttributeLayout(i,
                        AttributeLayout.CONTEXT_METHOD);
                if (layout != null && !layout.isDefaultLayout()) {
                    mask &= ~(1 << i);
                }
            }
            methodAccessFlags = new long[methodFlags.length][];
            for (int i = 0; i < methodFlags.length; i++) {
                methodAccessFlags[i] = new long[methodFlags[i].length];
                for (int j = 0; j < methodFlags[i].length; j++) {
                    methodAccessFlags[i][j] = methodFlags[i][j] & mask;
                }
            }
        }
        return methodAccessFlags;
    }

    /**
     * Returns null if all classes should use the default major and minor
     * version or an array of integers containing the major version numberss to
     * use for each class in the segment
     *
     * @return Class file major version numbers, or null if none specified
     */
    public int[] getClassVersionMajor() {
        return classVersionMajor;
    }

    /**
     * Returns null if all classes should use the default major and minor
     * version or an array of integers containing the minor version numberss to
     * use for each class in the segment
     *
     * @return Class file minor version numbers, or null if none specified
     */
    public int[] getClassVersionMinor() {
        return classVersionMinor;
    }

    public int[] getCodeHandlerCount() {
        return codeHandlerCount;
    }

    public int[][] getCodeHandlerCatchPO() {
        return codeHandlerCatchPO;
    }

    public int[][] getCodeHandlerClassRCN() {
        return codeHandlerClassRCN;
    }

    public int[][] getCodeHandlerEndPO() {
        return codeHandlerEndPO;
    }

    public int[][] getCodeHandlerStartP() {
        return codeHandlerStartP;
    }

    public IcTuple[][] getIcLocal() {
        return icLocal;
    }

    public boolean[] getCodeHasAttributes() {
        return codeHasAttributes;
    }

}
