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

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

import org.apache.commons.compress.harmony.pack200.Codec;
import org.apache.commons.compress.harmony.pack200.Pack200Exception;
import org.apache.commons.compress.harmony.unpack200.bytecode.Attribute;
import org.apache.commons.compress.harmony.unpack200.bytecode.CPClass;
import org.apache.commons.compress.harmony.unpack200.bytecode.CPNameAndType;
import org.apache.commons.compress.harmony.unpack200.bytecode.CPUTF8;
import org.apache.commons.compress.harmony.unpack200.bytecode.ClassFileEntry;
import org.apache.commons.compress.harmony.unpack200.bytecode.ConstantValueAttribute;
import org.apache.commons.compress.harmony.unpack200.bytecode.DeprecatedAttribute;
import org.apache.commons.compress.harmony.unpack200.bytecode.EnclosingMethodAttribute;
import org.apache.commons.compress.harmony.unpack200.bytecode.ExceptionsAttribute;
import org.apache.commons.compress.harmony.unpack200.bytecode.LineNumberTableAttribute;
import org.apache.commons.compress.harmony.unpack200.bytecode.LocalVariableTableAttribute;
import org.apache.commons.compress.harmony.unpack200.bytecode.LocalVariableTypeTableAttribute;
import org.apache.commons.compress.harmony.unpack200.bytecode.SignatureAttribute;
import org.apache.commons.compress.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<Attribute>[] classAttributes;

    private int[] classVersionMajor;

    private int[] classVersionMinor;

    private IcTuple[][] icLocal;

    private List<Attribute>[] codeAttributes;

    private int[] codeHandlerCount;

    private int[] codeMaxNALocals;

    private int[] codeMaxStack;

    private ArrayList<Attribute>[][] fieldAttributes;

    private String[][] fieldDescr;

    private int[][] fieldDescrInts;

    private long[][] fieldFlags;

    private long[][] fieldAccessFlags;

    private ArrayList<Attribute>[][] 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 TODO
     */
    public ClassBands(final 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.commons.compress.harmony.unpack200.BandSet#unpack(java.io.InputStream)
     */
    @Override
    public void read(final InputStream in) throws IOException, Pack200Exception {
        final 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);
        final 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);

    }

    @Override
    public void unpack() {

    }

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

    private void parseFieldAttrBands(final InputStream in) throws IOException, Pack200Exception {
        fieldFlags = parseFlags("field_flags", in, classFieldCount, Codec.UNSIGNED5, options.hasFieldFlagsHi());
        final int fieldAttrCount = SegmentUtils.countBit16(fieldFlags);
        final int[] fieldAttrCounts = decodeBandInt("field_attr_count", in, Codec.UNSIGNED5, fieldAttrCount);
        final int[][] fieldAttrIndexes = decodeBandInt("field_attr_indexes", in, Codec.UNSIGNED5, fieldAttrCounts);
        final int callCount = getCallCount(fieldAttrIndexes, fieldFlags, AttributeLayout.CONTEXT_FIELD);
        final 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<>();
            }
        }

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

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

        final 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++) {
                final 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
                    final long result = field_constantValue_KQ[constantValueIndex];
                    final String desc = fieldDescr[i][j];
                    final 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";
                    }
                    final 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
                    final long result = fieldSignatureRS[signatureIndex];
                    final String desc = fieldDescr[i][j];
                    final int colon = desc.indexOf(':');
                    final String type = desc.substring(colon + 1);
                    final CPUTF8 value = (CPUTF8) signatureLayout.getValue(result, type, cpBands.getConstantPool());
                    fieldAttributes[i][j].add(new SignatureAttribute(value));
                    signatureIndex++;
                }
            }
        }

        // Parse non-predefined attribute bands
        int backwardsCallIndex = parseFieldMetadataBands(in, fieldAttrCalls);
        final int limit = options.hasFieldFlagsHi() ? 62 : 31;
        final AttributeLayout[] otherLayouts = new AttributeLayout[limit + 1];
        final int[] counts = new int[limit + 1];
        final List<Attribute>[] otherAttributes = new List[limit + 1];
        for (int i = 0; i < limit; i++) {
            final 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) {
                final NewAttributeBands bands = attrMap.getAttributeBands(otherLayouts[i]);
                otherAttributes[i] = bands.parseAttributes(in, counts[i]);
                final int numBackwardsCallables = otherLayouts[i].numBackwardsCallables();
                if (numBackwardsCallables > 0) {
                    final 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++) {
                final 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(final InputStream in) throws IOException, Pack200Exception {
        methodDescrInts = decodeBandInt("method_descr", in, Codec.MDELTA5, classMethodCount);
        methodDescr = getReferences(methodDescrInts, cpBands.getCpDescriptor());
        parseMethodAttrBands(in);
    }

    private void parseMethodAttrBands(final InputStream in) throws IOException, Pack200Exception {
        methodFlags = parseFlags("method_flags", in, classMethodCount, Codec.UNSIGNED5, options.hasMethodFlagsHi());
        final int methodAttrCount = SegmentUtils.countBit16(methodFlags);
        final int[] methodAttrCounts = decodeBandInt("method_attr_count", in, Codec.UNSIGNED5, methodAttrCount);
        final int[][] methodAttrIndexes = decodeBandInt("method_attr_indexes", in, Codec.UNSIGNED5, methodAttrCounts);
        final 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
        final AttributeLayout methodExceptionsLayout = attrMap.getAttributeLayout(AttributeLayout.ATTRIBUTE_EXCEPTIONS,
            AttributeLayout.CONTEXT_METHOD);
        final int count = SegmentUtils.countMatches(methodFlags, methodExceptionsLayout);
        final int[] numExceptions = decodeBandInt("method_Exceptions_n", in, Codec.UNSIGNED5, count);
        final int[][] methodExceptionsRS = decodeBandInt("method_Exceptions_RC", in, Codec.UNSIGNED5, numExceptions);

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

        final 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++) {
                final long flag = methodFlags[i][j];
                if (methodExceptionsLayout.matches(flag)) {
                    final int n = numExceptions[methodExceptionsIndex];
                    final int[] exceptions = methodExceptionsRS[methodExceptionsIndex];
                    final 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
                    final long result = methodSignatureRS[methodSignatureIndex];
                    final String desc = methodDescr[i][j];
                    final 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";
                    }
                    final 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 non-predefined attribute bands
        int backwardsCallIndex = parseMethodMetadataBands(in, methodAttrCalls);
        final int limit = options.hasMethodFlagsHi() ? 62 : 31;
        final AttributeLayout[] otherLayouts = new AttributeLayout[limit + 1];
        final int[] counts = new int[limit + 1];
        for (int i = 0; i < limit; i++) {
            final AttributeLayout layout = attrMap.getAttributeLayout(i, AttributeLayout.CONTEXT_METHOD);
            if (layout != null && !(layout.isDefaultLayout())) {
                otherLayouts[i] = layout;
                counts[i] = SegmentUtils.countMatches(methodFlags, layout);
            }
        }
        final List<Attribute>[] otherAttributes = new List[limit + 1];
        for (int i = 0; i < counts.length; i++) {
            if (counts[i] > 0) {
                final NewAttributeBands bands = attrMap.getAttributeBands(otherLayouts[i]);
                otherAttributes[i] = bands.parseAttributes(in, counts[i]);
                final int numBackwardsCallables = otherLayouts[i].numBackwardsCallables();
                if (numBackwardsCallables > 0) {
                    final 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++) {
                final 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(final int[][] methodAttrIndexes, final long[][] flags, final int context) {
        int callCount = 0;
        for (int i = 0; i < methodAttrIndexes.length; i++) {
            for (int j = 0; j < methodAttrIndexes[i].length; j++) {
                final int index = methodAttrIndexes[i][j];
                final 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) {
                final AttributeLayout layout = attrMap.getAttributeLayout(i, context);
                callCount += layout.numBackwardsCallables();
            }
        }
        return callCount;
    }

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

        // Prepare empty attribute lists
        classAttributes = new ArrayList[classCount];
        Arrays.setAll(classAttributes, i -> new ArrayList<>());

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

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

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

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

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

        final int backwardsCallsUsed = parseClassMetadataBands(in, classAttrCalls);

        final AttributeLayout innerClassLayout = attrMap.getAttributeLayout(AttributeLayout.ATTRIBUTE_INNER_CLASSES,
            AttributeLayout.CONTEXT_CLASS);
        final int innerClassCount = SegmentUtils.countMatches(classFlags, innerClassLayout);
        final int[] classInnerClassesN = decodeBandInt("class_InnerClasses_N", in, Codec.UNSIGNED5, innerClassCount);
        final int[][] classInnerClassesRC = decodeBandInt("class_InnerClasses_RC", in, Codec.UNSIGNED5,
            classInnerClassesN);
        final 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++;
                }
            }
        }
        final int[] classInnerClassesOuterRCN = decodeBandInt("class_InnerClasses_outer_RCN", in, Codec.UNSIGNED5,
            flagsCount);
        final int[] classInnerClassesNameRUN = decodeBandInt("class_InnerClasses_name_RUN", in, Codec.UNSIGNED5,
            flagsCount);

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

        // Parse non-predefined attribute bands
        int backwardsCallIndex = backwardsCallsUsed;
        final int limit = options.hasClassFlagsHi() ? 62 : 31;
        final AttributeLayout[] otherLayouts = new AttributeLayout[limit + 1];
        final int[] counts = new int[limit + 1];
        final List<Attribute>[] otherAttributes = new List[limit + 1];
        for (int i = 0; i < limit; i++) {
            final 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) {
                final NewAttributeBands bands = attrMap.getAttributeBands(otherLayouts[i]);
                otherAttributes[i] = bands.parseAttributes(in, counts[i]);
                final int numBackwardsCallables = otherLayouts[i].numBackwardsCallables();
                if (numBackwardsCallables > 0) {
                    final 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++) {
            final long flag = classFlags[i];
            if (deprecatedLayout.matches(classFlags[i])) {
                classAttributes[i].add(new DeprecatedAttribute());
            }
            if (sourceFileLayout.matches(flag)) {
                final 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
                    final 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)) {
                final 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)) {
                final long result = classSignature[signatureIndex];
                final 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++) {
                    final int icTupleCIndex = classInnerClassesRC[innerClassIndex][j];
                    int icTupleC2Index = -1;
                    int icTupleNIndex = -1;

                    final 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
                        final IcBands icBands = segment.getIcBands();
                        final 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;
                            }
                        }
                    }

                    final 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(final InputStream in) throws Pack200Exception, IOException {
        final AttributeLayout layout = attrMap.getAttributeLayout(AttributeLayout.ATTRIBUTE_CODE,
            AttributeLayout.CONTEXT_METHOD);

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

        final 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;
                }
            }
        }
        final int[] codeMaxStackSpecials = decodeBandInt("code_max_stack", in, Codec.UNSIGNED5, codeSpecialHeader);
        final int[] codeMaxNALocalsSpecials = decodeBandInt("code_max_na_locals", in, Codec.UNSIGNED5,
            codeSpecialHeader);
        final 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++) {
            final int header = 0xff & codeHeaders[i];
            if (header < 0) {
                throw new IllegalStateException("Shouldn't get here");
            }
            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);

        final int codeFlagsCount = allCodeHasFlags ? codeCount : codeSpecialHeader;

        codeAttributes = new List[codeFlagsCount];
        Arrays.setAll(codeAttributes, i -> new ArrayList<>());
        parseCodeAttrBands(in, codeFlagsCount);
    }

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

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

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

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

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

        // Parse non-predefined attribute bands
        int backwardsCallIndex = 0;
        final int limit = options.hasCodeFlagsHi() ? 62 : 31;
        final AttributeLayout[] otherLayouts = new AttributeLayout[limit + 1];
        final int[] counts = new int[limit + 1];
        final List<Attribute>[] otherAttributes = new List[limit + 1];
        for (int i = 0; i < limit; i++) {
            final 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) {
                final NewAttributeBands bands = attrMap.getAttributeBands(otherLayouts[i]);
                otherAttributes[i] = bands.parseAttributes(in, counts[i]);
                final int numBackwardsCallables = otherLayouts[i].numBackwardsCallables();
                if (numBackwardsCallables > 0) {
                    final 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])) {
                final LineNumberTableAttribute lnta = new LineNumberTableAttribute(lineNumberTableN[lineNumberIndex],
                    lineNumberTableBciP[lineNumberIndex], lineNumberTableLine[lineNumberIndex]);
                lineNumberIndex++;
                codeAttributes[i].add(lnta);
            }
            if (localVariableTableLayout.matches(codeFlags[i])) {
                final 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])) {
                final 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(final InputStream in, final int[] fieldAttrCalls)
        throws Pack200Exception, IOException {
        int backwardsCallsUsed = 0;
        final String[] RxA = {"RVA", "RIA"};

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

        final int rvaCount = SegmentUtils.countMatches(fieldFlags, rvaLayout);
        final int riaCount = SegmentUtils.countMatches(fieldFlags, riaLayout);
        final int[] RxACount = {rvaCount, riaCount};
        final int[] backwardsCalls = {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++;
        }
        final MetadataBandGroup[] mb = parseMetadata(in, RxA, RxACount, backwardsCalls, "field");
        final List<Attribute> rvaAttributes = mb[0].getAttributes();
        final List<Attribute> 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(final InputStream in, final String[] RxA, final int[] RxACount,
        final int[] backwardsCallCounts, final String contextName) throws IOException, Pack200Exception {
        final MetadataBandGroup[] mbg = new MetadataBandGroup[RxA.length];
        for (int i = 0; i < RxA.length; i++) {
            mbg[i] = new MetadataBandGroup(RxA[i], cpBands);
            final 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++) {
                final 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(final InputStream in, final int[] methodAttrCalls)
        throws Pack200Exception, IOException {
        int backwardsCallsUsed = 0;
        final String[] RxA = {"RVA", "RIA", "RVPA", "RIPA", "AD"};
        final int[] rxaCounts = {0, 0, 0, 0, 0};

        final AttributeLayout rvaLayout = attrMap
            .getAttributeLayout(AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, AttributeLayout.CONTEXT_METHOD);
        final AttributeLayout riaLayout = attrMap.getAttributeLayout(
            AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS, AttributeLayout.CONTEXT_METHOD);
        final AttributeLayout rvpaLayout = attrMap.getAttributeLayout(
            AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, AttributeLayout.CONTEXT_METHOD);
        final AttributeLayout ripaLayout = attrMap.getAttributeLayout(
            AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS, AttributeLayout.CONTEXT_METHOD);
        final AttributeLayout adLayout = attrMap.getAttributeLayout(AttributeLayout.ATTRIBUTE_ANNOTATION_DEFAULT,
            AttributeLayout.CONTEXT_METHOD);
        final AttributeLayout[] rxaLayouts = {rvaLayout, riaLayout, rvpaLayout, ripaLayout, adLayout};

        Arrays.setAll(rxaCounts, i -> SegmentUtils.countMatches(methodFlags, rxaLayouts[i]));
        final 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;
            }
        }
        final MetadataBandGroup[] mbgs = parseMetadata(in, RxA, rxaCounts, backwardsCalls, "method");
        final List<Attribute>[] attributeLists = new List[RxA.length];
        final 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 TODO
     * @param classAttrCalls TODO
     * @return the number of backwards callables.
     * @throws Pack200Exception TODO
     * @throws IOException If an I/O error occurs.
     */
    private int parseClassMetadataBands(final InputStream in, final int[] classAttrCalls)
        throws Pack200Exception, IOException {
        int numBackwardsCalls = 0;
        final String[] RxA = {"RVA", "RIA"};

        final AttributeLayout rvaLayout = attrMap
            .getAttributeLayout(AttributeLayout.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, AttributeLayout.CONTEXT_CLASS);
        final AttributeLayout riaLayout = attrMap
            .getAttributeLayout(AttributeLayout.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS, AttributeLayout.CONTEXT_CLASS);
        final int rvaCount = SegmentUtils.countMatches(classFlags, rvaLayout);
        final int riaCount = SegmentUtils.countMatches(classFlags, riaLayout);
        final int[] RxACount = {rvaCount, riaCount};
        final int[] backwardsCalls = {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];
        }
        final MetadataBandGroup[] mbgs = parseMetadata(in, RxA, RxACount, backwardsCalls, "class");
        final List<Attribute> rvaAttributes = mbgs[0].getAttributes();
        final List<Attribute> 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<Attribute>[] getClassAttributes() {
        return classAttributes;
    }

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

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

    public long[] getClassFlags() {
        if (classAccessFlags == null) {
            long mask = 0x7FFF;
            for (int i = 0; i < 16; i++) {
                final 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<Attribute>[][] getFieldAttributes() {
        return fieldAttributes;
    }

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

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

    public long[][] getFieldFlags() {
        if (fieldAccessFlags == null) {
            long mask = 0x7FFF;
            for (int i = 0; i < 16; i++) {
                final 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<List<Attribute>> getOrderedCodeAttributes() {
        final ArrayList<List<Attribute>> orderedAttributeList = new ArrayList<>(codeAttributes.length);
        for (int classIndex = 0; classIndex < codeAttributes.length; classIndex++) {
            final List<Attribute> currentAttributes = new ArrayList<>(codeAttributes[classIndex].size());
            for (int attributeIndex = 0; attributeIndex < codeAttributes[classIndex].size(); attributeIndex++) {
                currentAttributes.add(codeAttributes[classIndex].get(attributeIndex));
            }
            orderedAttributeList.add(currentAttributes);
        }
        return orderedAttributeList;
    }

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

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

    public long[][] getMethodFlags() {
        if (methodAccessFlags == null) {
            long mask = 0x7FFF;
            for (int i = 0; i < 16; i++) {
                final 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;
    }

}
