NewAttributeBands.java

/*
 * 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
 *
 *   https://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.Collections;
import java.util.List;

import org.apache.commons.compress.harmony.internal.AttributeLayoutParser;
import org.apache.commons.compress.harmony.internal.AttributeLayoutParser.UnionCaseData;
import org.apache.commons.compress.harmony.internal.AttributeLayoutUtils;
import org.apache.commons.compress.harmony.pack200.BHSDCodec;
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.CPDouble;
import org.apache.commons.compress.harmony.unpack200.bytecode.CPFieldRef;
import org.apache.commons.compress.harmony.unpack200.bytecode.CPFloat;
import org.apache.commons.compress.harmony.unpack200.bytecode.CPInteger;
import org.apache.commons.compress.harmony.unpack200.bytecode.CPInterfaceMethodRef;
import org.apache.commons.compress.harmony.unpack200.bytecode.CPLong;
import org.apache.commons.compress.harmony.unpack200.bytecode.CPMethodRef;
import org.apache.commons.compress.harmony.unpack200.bytecode.CPNameAndType;
import org.apache.commons.compress.harmony.unpack200.bytecode.CPString;
import org.apache.commons.compress.harmony.unpack200.bytecode.CPUTF8;
import org.apache.commons.compress.harmony.unpack200.bytecode.NewAttribute;
import org.apache.commons.lang3.IntegerRange;

/**
 * Sets of bands relating to a non-predefined attribute
 *
 * @see <a href="https://docs.oracle.com/en/java/javase/13/docs/specs/pack-spec.html">Pack200: A Packed Class Deployment Format For Java Applications</a>
 */
public class NewAttributeBands extends BandSet {

    /**
     * An AttributeLayoutElement is a part of an attribute layout and has one or more bands associated with it, which transmit the AttributeElement data for
     * successive Attributes of this type.
     */
    private interface AttributeLayoutElement {

        /**
         * Adds the band data for this element at the given index to the attribute.
         *
         * @param index     Index position to add the attribute.
         * @param attribute The attribute to add.
         */
        void addToAttribute(int index, NewAttribute attribute);

        /**
         * Reads the bands associated with this part of the layout.
         *
         * @param in    the input stream to read from.
         * @param count the number of elements to read.
         * @throws Pack200Exception Bad archive.
         * @throws IOException      If an I/O error occurs.
         */
        void readBands(InputStream in, int count) throws IOException, Pack200Exception;

    }

    private class AttributeLayoutFactory implements AttributeLayoutParser.Factory<LayoutElement> {

        @Override
        public LayoutElement createCall(int callableIndex) {
            return new Call(callableIndex);
        }

        @Override
        public LayoutElement createCallable(List<LayoutElement> body) throws Pack200Exception {
            return new Callable(body);
        }

        @Override
        public LayoutElement createIntegral(String tag) {
            return new Integral(tag);
        }

        @Override
        public LayoutElement createReference(String tag) {
            return new Reference(tag);
        }

        @Override
        public LayoutElement createReplication(String unsignedInt, List<LayoutElement> body) throws Pack200Exception {
            return new Replication(unsignedInt, body);
        }

        @Override
        public LayoutElement createUnion(String anyInt, List<UnionCaseData<LayoutElement>> cases, List<LayoutElement> body) throws Pack200Exception {
            final List<UnionCase> unionCases = new ArrayList<>();
            for (final UnionCaseData<LayoutElement> unionCaseData : cases) {
                unionCases.add(new UnionCase(unionCaseData.tagRanges, unionCaseData.body, false));
            }
            return new Union(anyInt, unionCases, body);
        }
    }

    /**
     * Represents a call to a callable layout element.
     */
    public class Call extends LayoutElement {

        private final int callableIndex;
        private Callable callable;

        /**
         * Constructs a new Call.
         *
         * @param callableIndex the callable index.
         */
        public Call(final int callableIndex) {
            this.callableIndex = callableIndex;
        }

        @Override
        public void addToAttribute(final int n, final NewAttribute attribute) {
            callable.addNextToAttribute(attribute);
        }

        /**
         * Gets the callable.
         *
         * @return the callable.
         */
        public Callable getCallable() {
            return callable;
        }

        /**
         * Gets the callable index.
         *
         * @return the callable index.
         */
        public int getCallableIndex() {
            return callableIndex;
        }

        @Override
        public void readBands(final InputStream in, final int count) {
            /*
             * We don't read anything here, but we need to pass the extra count to the callable if it's a forwards call. For backwards callables the count is
             * transmitted directly in the attribute bands and so it is added later.
             */
            if (callableIndex > 0) {
                callable.addCount(count);
            }
        }

        /**
         * Sets the callable.
         *
         * @param callable the callable.
         */
        public void setCallable(final Callable callable) {
            this.callable = callable;
            if (callableIndex < 1) {
                callable.setBackwardsCallable();
            }
        }
    }

    /**
     * Callable layout element that can be referenced and reused.
     */
    public static class Callable extends LayoutElement {

        private final List<LayoutElement> body;

        private boolean isBackwardsCallable;

        private boolean isFirstCallable;

        private int count;

        private int index;

        /**
         * Constructs a new Callable layout element with the given body.
         *
         * @param body the body of the callable.
         * @throws Pack200Exception If the body is empty.
         */
        public Callable(final List<LayoutElement> body) throws Pack200Exception {
            if (body.isEmpty()) {
                throw new Pack200Exception("Corrupted Pack200 archive: Callable body is empty");
            }
            this.body = body;
        }

        /**
         * Adds the count of a call to this callable (ie the number of calls)
         *
         * @param count the count to add.
         */
        public void addCount(final int count) {
            this.count += count;
        }

        /**
         * Used by calls when adding band contents to attributes, so they don't have to keep track of the internal index of the callable.
         *
         * @param attribute the attribute to add to.
         */
        public void addNextToAttribute(final NewAttribute attribute) {
            for (final LayoutElement element : body) {
                element.addToAttribute(index, attribute);
            }
            index++;
        }

        @Override
        public void addToAttribute(final int n, final NewAttribute attribute) {
            if (isFirstCallable) {
                // Ignore n because bands also contain element parts from calls
                for (final LayoutElement element : body) {
                    element.addToAttribute(index, attribute);
                }
                index++;
            }
        }

        /**
         * Gets the body of this callable.
         *
         * @return the body elements.
         */
        public List<LayoutElement> getBody() {
            return body;
        }

        /**
         * Tests whether this is a backwards callable.
         *
         * @return true if backwards callable.
         */
        public boolean isBackwardsCallable() {
            return isBackwardsCallable;
        }

        @Override
        public void readBands(final InputStream in, int count) throws IOException, Pack200Exception {
            if (isFirstCallable) {
                count += this.count;
            } else {
                count = this.count;
            }
            for (final LayoutElement element : body) {
                element.readBands(in, count);
            }
        }

        /**
         * Tells this Callable that it is a backwards callable.
         */
        public void setBackwardsCallable() {
            this.isBackwardsCallable = true;
        }

        /**
         * Sets whether this is the first callable.
         *
         * @param isFirstCallable true if first callable.
         */
        public void setFirstCallable(final boolean isFirstCallable) {
            this.isFirstCallable = isFirstCallable;
        }
    }

    /**
     * Integral layout element for integer values.
     */
    public class Integral extends LayoutElement {

        private final String tag;

        private int[] band;

        /**
         * Constructs a new Integral layout element with the given tag.
         *
         * @param tag The tag.
         * @throws IllegalArgumentException If the tag is invalid.
         */
        public Integral(final String tag) {
            this.tag = AttributeLayoutUtils.checkIntegralTag(tag);
        }

        @Override
        public void addToAttribute(final int n, final NewAttribute attribute) {
            int value = band[n];
            switch (tag) {
            case "B":
            case "FB":
                attribute.addInteger(1, value);
                break;
            case "SB":
                attribute.addInteger(1, (byte) value);
                break;
            case "H":
            case "FH":
                attribute.addInteger(2, value);
                break;
            case "SH":
                attribute.addInteger(2, (short) value);
                break;
            case "I":
            case "FI":
            case "SI":
                attribute.addInteger(4, value);
                break;
            case "V":
            case "FV":
            case "SV":
                break;
            default:
                if (tag.startsWith("PO")) {
                    final char uintType = tag.substring(2).toCharArray()[0];
                    final int length = getLength(uintType);
                    attribute.addBCOffset(length, value);
                } else if (tag.startsWith("P")) {
                    final char uintType = tag.substring(1).toCharArray()[0];
                    final int length = getLength(uintType);
                    attribute.addBCIndex(length, value);
                } else if (tag.startsWith("OS")) {
                    final char uintType = tag.substring(2).toCharArray()[0];
                    final int length = getLength(uintType);
                    switch (length) {
                    case 1:
                        value = (byte) value;
                        break;
                    case 2:
                        value = (short) value;
                        break;
                    case 4:
                        value = value;
                        break;
                    default:
                        break;
                    }
                    attribute.addBCLength(length, value);
                } else if (tag.startsWith("O")) {
                    final char uintType = tag.substring(1).toCharArray()[0];
                    final int length = getLength(uintType);
                    attribute.addBCLength(length, value);
                }
                break;
            }
        }

        /**
         * Gets the tag of this integral.
         *
         * @return the tag.
         */
        public String getTag() {
            return tag;
        }

        /**
         * Gets the value at the specified index.
         *
         * @param index the index.
         * @return the value.
         */
        int getValue(final int index) {
            return band[index];
        }

        @Override
        public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
            band = decodeBandInt(attributeLayout.getName() + "_" + tag, in, getCodec(tag), count);
        }

    }

    /**
     * Abstract base class for layout elements.
     */
    private abstract static class LayoutElement implements AttributeLayoutElement {

        /**
         * Gets the length for the given unsigned int type.
         *
         * @param uintType the unsigned int type character.
         * @return the length in bytes.
         */
        protected int getLength(final char uintType) {
            int length = 0;
            switch (uintType) {
            case 'B':
                length = 1;
                break;
            case 'H':
                length = 2;
                break;
            case 'I':
                length = 4;
                break;
            case 'V':
                length = 0;
                break;
            }
            return length;
        }
    }

    /**
     * Constant Pool Reference
     */
    public class Reference extends LayoutElement {

        private final String tag;

        private Object band;

        private final int length;

        /**
         * Constructs a new Reference layout element with the given tag.
         *
         * @param tag The tag.
         * @throws IllegalArgumentException If the tag is invalid.
         */
        public Reference(final String tag) {
            this.tag = AttributeLayoutUtils.checkReferenceTag(tag);
            length = getLength(tag.charAt(tag.length() - 1));
        }

        @Override
        public void addToAttribute(final int n, final NewAttribute attribute) {
            if (tag.startsWith("KI")) { // Integer
                attribute.addToBody(length, ((CPInteger[]) band)[n]);
            } else if (tag.startsWith("KJ")) { // Long
                attribute.addToBody(length, ((CPLong[]) band)[n]);
            } else if (tag.startsWith("KF")) { // Float
                attribute.addToBody(length, ((CPFloat[]) band)[n]);
            } else if (tag.startsWith("KD")) { // Double
                attribute.addToBody(length, ((CPDouble[]) band)[n]);
            } else if (tag.startsWith("KS")) { // String
                attribute.addToBody(length, ((CPString[]) band)[n]);
            } else if (tag.startsWith("RC")) { // Class
                attribute.addToBody(length, ((CPClass[]) band)[n]);
            } else if (tag.startsWith("RS")) { // Signature
                attribute.addToBody(length, ((CPUTF8[]) band)[n]);
            } else if (tag.startsWith("RD")) { // Descriptor
                attribute.addToBody(length, ((CPNameAndType[]) band)[n]);
            } else if (tag.startsWith("RF")) { // Field Reference
                attribute.addToBody(length, ((CPFieldRef[]) band)[n]);
            } else if (tag.startsWith("RM")) { // Method Reference
                attribute.addToBody(length, ((CPMethodRef[]) band)[n]);
            } else if (tag.startsWith("RI")) { // Interface Method Reference
                attribute.addToBody(length, ((CPInterfaceMethodRef[]) band)[n]);
            } else if (tag.startsWith("RU")) { // UTF8 String
                attribute.addToBody(length, ((CPUTF8[]) band)[n]);
            }
        }

        /**
         * Gets the tag.
         *
         * @return the tag.
         */
        public String getTag() {
            return tag;
        }

        @Override
        public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
            if (tag.startsWith("KI")) { // Integer
                band = parseCPIntReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
            } else if (tag.startsWith("KJ")) { // Long
                band = parseCPLongReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
            } else if (tag.startsWith("KF")) { // Float
                band = parseCPFloatReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
            } else if (tag.startsWith("KD")) { // Double
                band = parseCPDoubleReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
            } else if (tag.startsWith("KS")) { // String
                band = parseCPStringReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
            } else if (tag.startsWith("RC")) { // Class
                band = parseCPClassReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
            } else if (tag.startsWith("RS")) { // Signature
                band = parseCPSignatureReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
            } else if (tag.startsWith("RD")) { // Descriptor
                band = parseCPDescriptorReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
            } else if (tag.startsWith("RF")) { // Field Reference
                band = parseCPFieldRefReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
            } else if (tag.startsWith("RM")) { // Method Reference
                band = parseCPMethodRefReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
            } else if (tag.startsWith("RI")) { // Interface Method Reference
                band = parseCPInterfaceMethodRefReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
            } else if (tag.startsWith("RU")) { // UTF8 String
                band = parseCPUTF8References(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
            }
        }

    }

    /**
     * A replication is an array of layout elements, with an associated count
     */
    public class Replication extends LayoutElement {

        private final Integral countElement;

        private final List<LayoutElement> layoutElements;

        private Replication(final String tag, final List<LayoutElement> layoutElements) throws Pack200Exception {
            this.countElement = new Integral(tag);
            this.layoutElements = layoutElements;
            if (layoutElements.isEmpty()) {
                throw new Pack200Exception("Corrupted Pack200 archive: Replication body is empty");
            }
        }

        /**
         * Constructs a new Replication layout element.
         *
         * @param tag the tag of the Integral element.
         * @param contents the contents of the replication.
         * @throws IllegalArgumentException If the tag is invalid or the contents are empty.
         * @throws Pack200Exception If the contents are invalid.
         */
        public Replication(final String tag, final String contents) throws Pack200Exception {
            this(tag, AttributeLayoutUtils.readBody(contents, attributeLayoutFactory));
        }

        @Override
        public void addToAttribute(final int index, final NewAttribute attribute) {
            // Add the count value
            countElement.addToAttribute(index, attribute);

            // Add the corresponding array values
            int offset = 0;
            for (int i = 0; i < index; i++) {
                offset += countElement.getValue(i);
            }
            final long numElements = countElement.getValue(index);
            for (int i = offset; i < offset + numElements; i++) {
                for (final LayoutElement layoutElement : layoutElements) {
                    layoutElement.addToAttribute(i, attribute);
                }
            }
        }

        /**
         * Gets the count element.
         *
         * @return the count element.
         */
        public Integral getCountElement() {
            return countElement;
        }

        /**
         * Gets the layout elements.
         *
         * @return the layout elements.
         */
        public List<LayoutElement> getLayoutElements() {
            return layoutElements;
        }

        @Override
        public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
            countElement.readBands(in, count);
            int arrayCount = 0;
            for (int i = 0; i < count; i++) {
                arrayCount += countElement.getValue(i);
            }
            for (final LayoutElement layoutElement : layoutElements) {
                layoutElement.readBands(in, arrayCount);
            }
        }
    }

    /**
     * A Union is a type of layout element where the tag value acts as a selector for one of the union cases
     */
    public class Union extends LayoutElement {

        private final Integral unionTag;
        private final List<UnionCase> unionCases;
        private final List<LayoutElement> defaultCaseBody;
        private int[] caseCounts;
        private int defaultCount;

        /**
         * Constructs a new Union layout element.
         *
         * @param tag the tag of the Integral element.
         * @param unionCases the union cases.
         * @param body the default case body.
         * @throws IllegalArgumentException If the tag is invalid.
         */
        public Union(final String tag, final List<UnionCase> unionCases, final List<LayoutElement> body) {
            this.unionTag = new Integral(tag);
            this.unionCases = unionCases;
            this.defaultCaseBody = body;
        }

        @Override
        public void addToAttribute(final int n, final NewAttribute attribute) {
            unionTag.addToAttribute(n, attribute);
            int offset = 0;
            final int[] tagBand = unionTag.band;
            final int tag = unionTag.getValue(n);
            boolean defaultCase = true;
            for (final UnionCase unionCase : unionCases) {
                if (unionCase.hasTag(tag)) {
                    defaultCase = false;
                    for (int j = 0; j < n; j++) {
                        if (unionCase.hasTag(tagBand[j])) {
                            offset++;
                        }
                    }
                    unionCase.addToAttribute(offset, attribute);
                }
            }
            if (defaultCase) {
                // default case
                int defaultOffset = 0;
                for (int j = 0; j < n; j++) {
                    boolean found = false;
                    for (final UnionCase unionCase : unionCases) {
                        if (unionCase.hasTag(tagBand[j])) {
                            found = true;
                        }
                    }
                    if (!found) {
                        defaultOffset++;
                    }
                }
                if (defaultCaseBody != null) {
                    for (final LayoutElement element : defaultCaseBody) {
                        element.addToAttribute(defaultOffset, attribute);
                    }
                }
            }
        }

        /**
         * Gets the default case body.
         *
         * @return the default case body.
         */
        public List<LayoutElement> getDefaultCaseBody() {
            return defaultCaseBody;
        }

        /**
         * Gets the union cases.
         *
         * @return the union cases.
         */
        public List<UnionCase> getUnionCases() {
            return unionCases;
        }

        /**
         * Gets the union tag.
         *
         * @return the union tag.
         */
        public Integral getUnionTag() {
            return unionTag;
        }

        @Override
        public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
            unionTag.readBands(in, count);
            final int[] values = unionTag.band;
            // Count the band size for each union case then read the bands
            caseCounts = new int[unionCases.size()];
            for (int i = 0; i < caseCounts.length; i++) {
                final UnionCase unionCase = unionCases.get(i);
                for (final int value : values) {
                    if (unionCase.hasTag(value)) {
                        caseCounts[i]++;
                    }
                }
                unionCase.readBands(in, caseCounts[i]);
            }
            // Count number of default cases then read the default bands
            for (final int value : values) {
                boolean found = false;
                for (final UnionCase unionCase : unionCases) {
                    if (unionCase.hasTag(value)) {
                        found = true;
                    }
                }
                if (!found) {
                    defaultCount++;
                }
            }
            if (defaultCaseBody != null) {
                for (final LayoutElement element : defaultCaseBody) {
                    element.readBands(in, defaultCount);
                }
            }
        }

    }

    /**
     * A Union case
     */
    public class UnionCase extends LayoutElement {

        private final List<IntegerRange> tagRanges;
        private final List<LayoutElement> body;

        /**
         * Constructs a new UnionCase with the given tags.
         *
         * @param tags the tags.
         */
        public UnionCase(final List<Integer> tags) {
            this(tags, Collections.emptyList());
        }

        /**
         * Constructs a new UnionCase.
         *
         * @param tags the tags.
         * @param body the body elements.
         */
        public UnionCase(final List<Integer> tags, final List<LayoutElement> body) {
            this(AttributeLayoutUtils.toRanges(tags), body, false);
        }

        private UnionCase(final List<IntegerRange> tagRanges, final List<LayoutElement> body, final boolean ignored) {
            this.tagRanges = tagRanges;
            this.body = body;
        }

        @Override
        public void addToAttribute(final int index, final NewAttribute attribute) {
            for (final LayoutElement element : body) {
                element.addToAttribute(index, attribute);
            }
        }

        /**
         * Gets the body of this union case.
         *
         * @return the body elements.
         */
        public List<LayoutElement> getBody() {
            return body;
        }

        /**
         * Tests whether this union case has the given tag.
         *
         * @param i the tag value.
         * @return true if this case matches the tag.
         */
        public boolean hasTag(final int i) {
            return AttributeLayoutUtils.unionCaseMatches(tagRanges, i);
        }

        /**
         * Tests whether this union case has the given tag.
         *
         * @param l the tag value.
         * @return true if this case matches the tag.
         */
        public boolean hasTag(final long l) {
            return hasTag((int) l);
        }

        @Override
        public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
            for (final LayoutElement element : body) {
                element.readBands(in, count);
            }
        }
    }


    private final AttributeLayout attributeLayout;

    private int backwardsCallCount;

    /**
     * The attribute layout elements.
     */
    protected List<LayoutElement> attributeLayoutElements;

    /**
     * The attribute layout factory.
     */
    private final AttributeLayoutFactory attributeLayoutFactory = new AttributeLayoutFactory();

    /**
     * Constructs new attribute bands for the given segment and layout.
     *
     * @param segment the segment.
     * @param attributeLayout the attribute layout.
     * @throws IOException if an I/O error occurs.
     */
    public NewAttributeBands(final Segment segment, final AttributeLayout attributeLayout) throws IOException {
        super(segment);
        this.attributeLayout = attributeLayout;
        parseLayout();
        attributeLayout.setBackwardsCallCount(backwardsCallCount);
    }

    /**
     * Gets the backwards call count.
     *
     * @return the backwards call count.
     */
    public int getBackwardsCallCount() {
        return backwardsCallCount;
    }

    /**
     * Returns the {@link BHSDCodec} that should be used for the given layout element.
     *
     * @param layoutElement the layout element string.
     * @return the {@link BHSDCodec} that should be used for the given layout element.
     */
    public BHSDCodec getCodec(final String layoutElement) {
        if (layoutElement.indexOf('O') >= 0) {
            return Codec.BRANCH5;
        }
        if (layoutElement.indexOf('P') >= 0) {
            return Codec.BCI5;
        }
        if (layoutElement.indexOf('S') >= 0 && !layoutElement.contains("KS") //$NON-NLS-1$
                && !layoutElement.contains("RS")) { //$NON-NLS-1$
            return Codec.SIGNED5;
        }
        if (layoutElement.indexOf('B') >= 0) {
            return Codec.BYTE1;
        }
        return Codec.UNSIGNED5;
    }

    /**
     * Gets one attribute at the given index from the various bands. The correct bands must have already been read in.
     *
     * @param index    the index of the attribute.
     * @param elements the layout elements.
     * @return attribute at the given index.
     */
    private Attribute getOneAttribute(final int index, final List<LayoutElement> elements) {
        final NewAttribute attribute = new NewAttribute(segment.getCpBands().cpUTF8Value(attributeLayout.getName()), attributeLayout.getIndex());
        for (final AttributeLayoutElement element : elements) {
            element.addToAttribute(index, attribute);
        }
        return attribute;
    }

    /**
     * Parse the bands relating to this AttributeLayout and return the correct class file attributes as a List of {@link Attribute}.
     *
     * @param in              parse source.
     * @param occurrenceCount the number of occurrences.
     * @return Class file attributes as a List of {@link Attribute}.
     * @throws IOException      If an I/O error occurs.
     * @throws Pack200Exception If a Pack200 semantic error occurs.
     */
    public List<Attribute> parseAttributes(final InputStream in, final int occurrenceCount) throws IOException, Pack200Exception {
        for (final AttributeLayoutElement element : attributeLayoutElements) {
            element.readBands(in, occurrenceCount);
        }

        final List<Attribute> attributes = new ArrayList<>(occurrenceCount);
        for (int i = 0; i < occurrenceCount; i++) {
            attributes.add(getOneAttribute(i, attributeLayoutElements));
        }
        return attributes;
    }

    /**
     * Tokenize the layout into AttributeElements
     *
     * @throws IOException If an I/O error occurs.
     */
    private void parseLayout() throws IOException {
        if (attributeLayoutElements == null) {
            attributeLayoutElements = AttributeLayoutUtils.readAttributeLayout(attributeLayout.getLayout(), attributeLayoutFactory);
            resolveCalls();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.apache.commons.compress.harmony.unpack200.BandSet#unpack(java.io.InputStream)
     */
    @Override
    public void read(final InputStream in) throws IOException, Pack200Exception {
        // does nothing - use parseAttributes instead
    }

    /**
     * Resolve calls in the attribute layout and returns the number of backwards calls
     */
    private void resolveCalls() {
        int backwardsCalls = 0;
        for (int i = 0; i < attributeLayoutElements.size(); i++) {
            final AttributeLayoutElement element = attributeLayoutElements.get(i);
            if (element instanceof Callable) {
                final Callable callable = (Callable) element;
                if (i == 0) {
                    callable.setFirstCallable(true);
                }
                // Look for calls in the body
                for (final LayoutElement layoutElement : callable.body) {
                    // Set the callable for each call
                    backwardsCalls += resolveCallsForElement(i, callable, layoutElement);
                }
            }
        }
        backwardsCallCount = backwardsCalls;
    }

    private int resolveCallsForElement(final int i, final Callable currentCallable, final LayoutElement layoutElement) {
        int backwardsCalls = 0;
        if (layoutElement instanceof Call) {
            final Call call = (Call) layoutElement;
            int index = call.callableIndex;
            if (index == 0) { // Calls the parent callable
                backwardsCalls++;
                call.setCallable(currentCallable);
            } else if (index > 0) { // Forwards call
                for (int k = i + 1; k < attributeLayoutElements.size(); k++) {
                    final AttributeLayoutElement el = attributeLayoutElements.get(k);
                    if (el instanceof Callable) {
                        index--;
                        if (index == 0) {
                            call.setCallable((Callable) el);
                            break;
                        }
                    }
                }
            } else { // Backwards call
                backwardsCalls++;
                for (int k = i - 1; k >= 0; k--) {
                    final AttributeLayoutElement el = attributeLayoutElements.get(k);
                    if (el instanceof Callable) {
                        index++;
                        if (index == 0) {
                            call.setCallable((Callable) el);
                            break;
                        }
                    }
                }
            }
        } else if (layoutElement instanceof Replication) {
            final List<LayoutElement> children = ((Replication) layoutElement).layoutElements;
            for (final LayoutElement child : children) {
                backwardsCalls += resolveCallsForElement(i, currentCallable, child);
            }
        }
        return backwardsCalls;
    }

    /**
     * Once the attribute bands have been read the callables can be informed about the number of times each is subject to a backwards call. This method is used
     * to set this information.
     *
     * @param backwardsCalls one int for each backwards callable, which contains the number of times that callable is subject to a backwards call.
     * @throws IOException If an I/O error occurs.
     */
    public void setBackwardsCalls(final int[] backwardsCalls) throws IOException {
        int index = 0;
        parseLayout();
        for (final AttributeLayoutElement element : attributeLayoutElements) {
            if (element instanceof Callable && ((Callable) element).isBackwardsCallable()) {
                ((Callable) element).addCount(backwardsCalls[index]);
                index++;
            }
        }
    }

    @Override
    public void unpack() throws IOException, Pack200Exception {

    }

}