    001/*
    002 *  Licensed to the Apache Software Foundation (ASF) under one or more
    003 *  contributor license agreements.  See the NOTICE file distributed with
    004 *  this work for additional information regarding copyright ownership.
    005 *  The ASF licenses this file to You under the Apache License, Version 2.0
    006 *  (the "License"); you may not use this file except in compliance with
    007 *  the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 *  Unless required by applicable law or agreed to in writing, software
    012 *  distributed under the License is distributed on an "AS IS" BASIS,
    013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 *  See the License for the specific language governing permissions and
    015 *  limitations under the License.
    016 */
    017package org.apache.commons.compress.harmony.unpack200;
    018
    019import java.io.IOException;
    020import java.io.InputStream;
    021import java.io.StringReader;
    022import java.util.ArrayList;
    023import java.util.Collections;
    024import java.util.Iterator;
    025import java.util.List;
    026
    027import org.apache.commons.compress.harmony.pack200.BHSDCodec;
    028import org.apache.commons.compress.harmony.pack200.Codec;
    029import org.apache.commons.compress.harmony.pack200.Pack200Exception;
    030import org.apache.commons.compress.harmony.unpack200.bytecode.Attribute;
    031import org.apache.commons.compress.harmony.unpack200.bytecode.CPClass;
    032import org.apache.commons.compress.harmony.unpack200.bytecode.CPDouble;
    033import org.apache.commons.compress.harmony.unpack200.bytecode.CPFieldRef;
    034import org.apache.commons.compress.harmony.unpack200.bytecode.CPFloat;
    035import org.apache.commons.compress.harmony.unpack200.bytecode.CPInteger;
    036import org.apache.commons.compress.harmony.unpack200.bytecode.CPInterfaceMethodRef;
    037import org.apache.commons.compress.harmony.unpack200.bytecode.CPLong;
    038import org.apache.commons.compress.harmony.unpack200.bytecode.CPMethodRef;
    039import org.apache.commons.compress.harmony.unpack200.bytecode.CPNameAndType;
    040import org.apache.commons.compress.harmony.unpack200.bytecode.CPString;
    041import org.apache.commons.compress.harmony.unpack200.bytecode.CPUTF8;
    042import org.apache.commons.compress.harmony.unpack200.bytecode.NewAttribute;
    043
    044/**
    045 * Set of bands relating to a non-predefined attribute
    046 */
    047public class NewAttributeBands extends BandSet {
    048
    049    private final AttributeLayout attributeLayout;
    050
    051    private int backwardsCallCount;
    052
    053    protected List attributeLayoutElements;
    054
    055    public NewAttributeBands(final Segment segment, final AttributeLayout attributeLayout) throws IOException {
    056        super(segment);
    057        this.attributeLayout = attributeLayout;
    058        parseLayout();
    059        attributeLayout.setBackwardsCallCount(backwardsCallCount);
    060    }
    061
    062    /*
    063     * (non-Javadoc)
    064     *
    065     * @see org.apache.commons.compress.harmony.unpack200.BandSet#unpack(java.io.InputStream)
    066     */
    067    @Override
    068    public void read(final InputStream in) throws IOException, Pack200Exception {
    069        // does nothing - use parseAttributes instead
    070    }
    071
    072    /**
    073     * Parse the bands relating to this AttributeLayout and return the correct class file attributes as a List of
    074     * {@link Attribute}.
    075     *
    076     * @param in parse source.
    077     * @param occurrenceCount TODO
    078     * @return Class file attributes as a List of {@link Attribute}.
    079     * @throws IOException If an I/O error occurs.
    080     * @throws Pack200Exception TODO
    081     */
    082    public List parseAttributes(final InputStream in, final int occurrenceCount) throws IOException, Pack200Exception {
    083        for (int i = 0; i < attributeLayoutElements.size(); i++) {
    084            final AttributeLayoutElement element = (AttributeLayoutElement) attributeLayoutElements.get(i);
    085            element.readBands(in, occurrenceCount);
    086        }
    087
    088        final List attributes = new ArrayList(occurrenceCount);
    089        for (int i = 0; i < occurrenceCount; i++) {
    090            attributes.add(getOneAttribute(i, attributeLayoutElements));
    091        }
    092        return attributes;
    093    }
    094
    095    /**
    096     * Get one attribute at the given index from the various bands. The correct bands must have already been read in.
    097     *
    098     * @param index TODO
    099     * @param elements TODO
    100     * @return attribute at the given index.
    101     */
    102    private Attribute getOneAttribute(final int index, final List elements) {
    103        final NewAttribute attribute = new NewAttribute(segment.getCpBands().cpUTF8Value(attributeLayout.getName()),
    104            attributeLayout.getIndex());
    105        for (int i = 0; i < elements.size(); i++) {
    106            final AttributeLayoutElement element = (AttributeLayoutElement) elements.get(i);
    107            element.addToAttribute(index, attribute);
    108        }
    109        return attribute;
    110    }
    111
    112    /**
    113     * Tokenise the layout into AttributeElements
    114     *
    115     * @throws IOException If an I/O error occurs.
    116     */
    117    private void parseLayout() throws IOException {
    118        if (attributeLayoutElements == null) {
    119            attributeLayoutElements = new ArrayList();
    120            final StringReader stream = new StringReader(attributeLayout.getLayout());
    121            AttributeLayoutElement e;
    122            while ((e = readNextAttributeElement(stream)) != null) {
    123                attributeLayoutElements.add(e);
    124            }
    125            resolveCalls();
    126        }
    127    }
    128
    129    /**
    130     * Resolve calls in the attribute layout and returns the number of backwards calls
    131     */
    132    private void resolveCalls() {
    133        int backwardsCalls = 0;
    134        for (int i = 0; i < attributeLayoutElements.size(); i++) {
    135            final AttributeLayoutElement element = (AttributeLayoutElement) attributeLayoutElements.get(i);
    136            if (element instanceof Callable) {
    137                final Callable callable = (Callable) element;
    138                if (i == 0) {
    139                    callable.setFirstCallable(true);
    140                }
    141                final List body = callable.body; // Look for calls in the body
    142                for (int iIndex = 0; iIndex < body.size(); iIndex++) {
    143                    final LayoutElement layoutElement = (LayoutElement) body.get(iIndex);
    144                    // Set the callable for each call
    145                    backwardsCalls += resolveCallsForElement(i, callable, layoutElement);
    146                }
    147            }
    148        }
    149        backwardsCallCount = backwardsCalls;
    150    }
    151
    152    private int resolveCallsForElement(final int i, final Callable currentCallable, final LayoutElement layoutElement) {
    153        int backwardsCalls = 0;
    154        if (layoutElement instanceof Call) {
    155            final Call call = (Call) layoutElement;
    156            int index = call.callableIndex;
    157            if (index == 0) { // Calls the parent callable
    158                backwardsCalls++;
    159                call.setCallable(currentCallable);
    160            } else if (index > 0) { // Forwards call
    161                for (int k = i + 1; k < attributeLayoutElements.size(); k++) {
    162                    final AttributeLayoutElement el = (AttributeLayoutElement) attributeLayoutElements.get(k);
    163                    if (el instanceof Callable) {
    164                        index--;
    165                        if (index == 0) {
    166                            call.setCallable((Callable) el);
    167                            break;
    168                        }
    169                    }
    170                }
    171            } else { // Backwards call
    172                backwardsCalls++;
    173                for (int k = i - 1; k >= 0; k--) {
    174                    final AttributeLayoutElement el = (AttributeLayoutElement) attributeLayoutElements.get(k);
    175                    if (el instanceof Callable) {
    176                        index++;
    177                        if (index == 0) {
    178                            call.setCallable((Callable) el);
    179                            break;
    180                        }
    181                    }
    182                }
    183            }
    184        } else if (layoutElement instanceof Replication) {
    185            final List children = ((Replication) layoutElement).layoutElements;
    186            for (final Iterator iterator = children.iterator(); iterator.hasNext();) {
    187                final LayoutElement object = (LayoutElement) iterator.next();
    188                backwardsCalls += resolveCallsForElement(i, currentCallable, object);
    189            }
    190        }
    191        return backwardsCalls;
    192    }
    193
    194    private AttributeLayoutElement readNextAttributeElement(final StringReader stream) throws IOException {
    195        stream.mark(1);
    196        final int nextChar = stream.read();
    197        if (nextChar == -1) {
    198            return null;
    199        }
    200        if (nextChar == '[') {
    201            final List body = readBody(getStreamUpToMatchingBracket(stream));
    202            return new Callable(body);
    203        }
    204        stream.reset();
    205        return readNextLayoutElement(stream);
    206    }
    207
    208    private LayoutElement readNextLayoutElement(final StringReader stream) throws IOException {
    209        final int nextChar = stream.read();
    210        if (nextChar == -1) {
    211            return null;
    212        }
    213        switch (nextChar) {
    214        // Integrals
    215        case 'B':
    216        case 'H':
    217        case 'I':
    218        case 'V':
    219            return new Integral(new String(new char[] {(char) nextChar}));
    220        case 'S':
    221        case 'F':
    222            return new Integral(new String(new char[] {(char) nextChar, (char) stream.read()}));
    223        case 'P':
    224            stream.mark(1);
    225            if (stream.read() != 'O') {
    226                stream.reset();
    227                return new Integral("P" + (char) stream.read());
    228            } else {
    229                return new Integral("PO" + (char) stream.read());
    230            }
    231        case 'O':
    232            stream.mark(1);
    233            if (stream.read() != 'S') {
    234                stream.reset();
    235                return new Integral("O" + (char) stream.read());
    236            } else {
    237                return new Integral("OS" + (char) stream.read());
    238            }
    239
    240            // Replication
    241        case 'N':
    242            final char uint_type = (char) stream.read();
    243            stream.read(); // '['
    244            final String str = readUpToMatchingBracket(stream);
    245            return new Replication("" + uint_type, str);
    246
    247        // Union
    248        case 'T':
    249            String int_type = "" + (char) stream.read();
    250            if (int_type.equals("S")) {
    251                int_type += (char) stream.read();
    252            }
    253            final List unionCases = new ArrayList();
    254            UnionCase c;
    255            while ((c = readNextUnionCase(stream)) != null) {
    256                unionCases.add(c);
    257            }
    258            stream.read(); // '('
    259            stream.read(); // ')'
    260            stream.read(); // '['
    261            List body = null;
    262            stream.mark(1);
    263            final char next = (char) stream.read();
    264            if (next != ']') {
    265                stream.reset();
    266                body = readBody(getStreamUpToMatchingBracket(stream));
    267            }
    268            return new Union(int_type, unionCases, body);
    269
    270        // Call
    271        case '(':
    272            final int number = readNumber(stream).intValue();
    273            stream.read(); // ')'
    274            return new Call(number);
    275        // Reference
    276        case 'K':
    277        case 'R':
    278            final StringBuilder string = new StringBuilder("").append((char) nextChar).append((char) stream.read());
    279            final char nxt = (char) stream.read();
    280            string.append(nxt);
    281            if (nxt == 'N') {
    282                string.append((char) stream.read());
    283            }
    284            return new Reference(string.toString());
    285        }
    286        return null;
    287    }
    288
    289    /**
    290     * Read a UnionCase from the stream.
    291     *
    292     * @param stream source stream.
    293     * @return A UnionCase from the stream.
    294     * @throws IOException If an I/O error occurs.
    295     */
    296    private UnionCase readNextUnionCase(final StringReader stream) throws IOException {
    297        stream.mark(2);
    298        stream.read(); // '('
    299        char next = (char) stream.read();
    300        if (next == ')') {
    301            stream.reset();
    302            return null;
    303        }
    304        stream.reset();
    305        stream.read(); // '('
    306        final List tags = new ArrayList();
    307        Integer nextTag;
    308        do {
    309            nextTag = readNumber(stream);
    310            if (nextTag != null) {
    311                tags.add(nextTag);
    312                stream.read(); // ',' or ')'
    313            }
    314        } while (nextTag != null);
    315        stream.read(); // '['
    316        stream.mark(1);
    317        next = (char) stream.read();
    318        if (next == ']') {
    319            return new UnionCase(tags);
    320        }
    321        stream.reset();
    322        return new UnionCase(tags, readBody(getStreamUpToMatchingBracket(stream)));
    323    }
    324
    325    /**
    326     * An AttributeLayoutElement is a part of an attribute layout and has one or more bands associated with it, which
    327     * transmit the AttributeElement data for successive Attributes of this type.
    328     */
    329    private interface AttributeLayoutElement {
    330
    331        /**
    332         * Read the bands associated with this part of the layout.
    333         *
    334         * @param in TODO
    335         * @param count TODO
    336         * @throws Pack200Exception Bad archive.
    337         * @throws IOException If an I/O error occurs.
    338         */
    339        void readBands(InputStream in, int count) throws IOException, Pack200Exception;
    340
    341        /**
    342         * Adds the band data for this element at the given index to the attribute.
    343         *
    344         * @param index Index position to add the attribute.
    345         * @param attribute The attribute to add.
    346         */
    347        void addToAttribute(int index, NewAttribute attribute);
    348
    349    }
    350
    351    private abstract class LayoutElement implements AttributeLayoutElement {
    352
    353        protected int getLength(final char uint_type) {
    354            int length = 0;
    355            switch (uint_type) {
    356            case 'B':
    357                length = 1;
    358                break;
    359            case 'H':
    360                length = 2;
    361                break;
    362            case 'I':
    363                length = 4;
    364                break;
    365            case 'V':
    366                length = 0;
    367                break;
    368            }
    369            return length;
    370        }
    371    }
    372
    373    public class Integral extends LayoutElement {
    374
    375        private final String tag;
    376
    377        private int[] band;
    378
    379        public Integral(final String tag) {
    380            this.tag = tag;
    381        }
    382
    383        @Override
    384        public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
    385            band = decodeBandInt(attributeLayout.getName() + "_" + tag, in, getCodec(tag), count);
    386        }
    387
    388        @Override
    389        public void addToAttribute(final int n, final NewAttribute attribute) {
    390            long value = band[n];
    391            if (tag.equals("B") || tag.equals("FB")) {
    392                attribute.addInteger(1, value);
    393            } else if (tag.equals("SB")) {
    394                attribute.addInteger(1, (byte) value);
    395            } else if (tag.equals("H") || tag.equals("FH")) {
    396                attribute.addInteger(2, value);
    397            } else if (tag.equals("SH")) {
    398                attribute.addInteger(2, (short) value);
    399            } else if (tag.equals("I") || tag.equals("FI")) {
    400                attribute.addInteger(4, value);
    401            } else if (tag.equals("SI")) {
    402                attribute.addInteger(4, (int) value);
    403            } else if (tag.equals("V") || tag.equals("FV") || tag.equals("SV")) {
    404                // Don't add V's - they shouldn't be written out to the class
    405                // file
    406            } else if (tag.startsWith("PO")) {
    407                final char uint_type = tag.substring(2).toCharArray()[0];
    408                final int length = getLength(uint_type);
    409                attribute.addBCOffset(length, (int) value);
    410            } else if (tag.startsWith("P")) {
    411                final char uint_type = tag.substring(1).toCharArray()[0];
    412                final int length = getLength(uint_type);
    413                attribute.addBCIndex(length, (int) value);
    414            } else if (tag.startsWith("OS")) {
    415                final char uint_type = tag.substring(2).toCharArray()[0];
    416                final int length = getLength(uint_type);
    417                if (length == 1) {
    418                    value = (byte) value;
    419                } else if (length == 2) {
    420                    value = (short) value;
    421                } else if (length == 4) {
    422                    value = (int) value;
    423                }
    424                attribute.addBCLength(length, (int) value);
    425            } else if (tag.startsWith("O")) {
    426                final char uint_type = tag.substring(1).toCharArray()[0];
    427                final int length = getLength(uint_type);
    428                attribute.addBCLength(length, (int) value);
    429            }
    430        }
    431
    432        long getValue(final int index) {
    433            return band[index];
    434        }
    435
    436        public String getTag() {
    437            return tag;
    438        }
    439
    440    }
    441
    442    /**
    443     * A replication is an array of layout elements, with an associated count
    444     */
    445    public class Replication extends LayoutElement {
    446
    447        private final Integral countElement;
    448
    449        private final List layoutElements = new ArrayList();
    450
    451        public Replication(final String tag, final String contents) throws IOException {
    452            this.countElement = new Integral(tag);
    453            final StringReader stream = new StringReader(contents);
    454            LayoutElement e;
    455            while ((e = readNextLayoutElement(stream)) != null) {
    456                layoutElements.add(e);
    457            }
    458        }
    459
    460        @Override
    461        public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
    462            countElement.readBands(in, count);
    463            int arrayCount = 0;
    464            for (int i = 0; i < count; i++) {
    465                arrayCount += countElement.getValue(i);
    466            }
    467            for (int i = 0; i < layoutElements.size(); i++) {
    468                final LayoutElement element = (LayoutElement) layoutElements.get(i);
    469                element.readBands(in, arrayCount);
    470            }
    471        }
    472
    473        @Override
    474        public void addToAttribute(final int index, final NewAttribute attribute) {
    475            // Add the count value
    476            countElement.addToAttribute(index, attribute);
    477
    478            // Add the corresponding array values
    479            int offset = 0;
    480            for (int i = 0; i < index; i++) {
    481                offset += countElement.getValue(i);
    482            }
    483            final long numElements = countElement.getValue(index);
    484            for (int i = offset; i < offset + numElements; i++) {
    485                for (int it = 0; it < layoutElements.size(); it++) {
    486                    final LayoutElement element = (LayoutElement) layoutElements.get(it);
    487                    element.addToAttribute(i, attribute);
    488                }
    489            }
    490        }
    491
    492        public Integral getCountElement() {
    493            return countElement;
    494        }
    495
    496        public List getLayoutElements() {
    497            return layoutElements;
    498        }
    499    }
    500
    501    /**
    502     * A Union is a type of layout element where the tag value acts as a selector for one of the union cases
    503     */
    504    public class Union extends LayoutElement {
    505
    506        private final Integral unionTag;
    507        private final List unionCases;
    508        private final List defaultCaseBody;
    509        private int[] caseCounts;
    510        private int defaultCount;
    511
    512        public Union(final String tag, final List unionCases, final List body) {
    513            this.unionTag = new Integral(tag);
    514            this.unionCases = unionCases;
    515            this.defaultCaseBody = body;
    516        }
    517
    518        @Override
    519        public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
    520            unionTag.readBands(in, count);
    521            final int[] values = unionTag.band;
    522            // Count the band size for each union case then read the bands
    523            caseCounts = new int[unionCases.size()];
    524            for (int i = 0; i < caseCounts.length; i++) {
    525                final UnionCase unionCase = (UnionCase) unionCases.get(i);
    526                for (int j = 0; j < values.length; j++) {
    527                    if (unionCase.hasTag(values[j])) {
    528                        caseCounts[i]++;
    529                    }
    530                }
    531                unionCase.readBands(in, caseCounts[i]);
    532            }
    533            // Count number of default cases then read the default bands
    534            for (int i = 0; i < values.length; i++) {
    535                boolean found = false;
    536                for (int it = 0; it < unionCases.size(); it++) {
    537                    final UnionCase unionCase = (UnionCase) unionCases.get(it);
    538                    if (unionCase.hasTag(values[i])) {
    539                        found = true;
    540                    }
    541                }
    542                if (!found) {
    543                    defaultCount++;
    544                }
    545            }
    546            if (defaultCaseBody != null) {
    547                for (int i = 0; i < defaultCaseBody.size(); i++) {
    548                    final LayoutElement element = (LayoutElement) defaultCaseBody.get(i);
    549                    element.readBands(in, defaultCount);
    550                }
    551            }
    552        }
    553
    554        @Override
    555        public void addToAttribute(final int n, final NewAttribute attribute) {
    556            unionTag.addToAttribute(n, attribute);
    557            int offset = 0;
    558            final int[] tagBand = unionTag.band;
    559            final long tag = unionTag.getValue(n);
    560            boolean defaultCase = true;
    561            for (int i = 0; i < unionCases.size(); i++) {
    562                final UnionCase element = (UnionCase) unionCases.get(i);
    563                if (element.hasTag(tag)) {
    564                    defaultCase = false;
    565                    for (int j = 0; j < n; j++) {
    566                        if (element.hasTag(tagBand[j])) {
    567                            offset++;
    568                        }
    569                    }
    570                    element.addToAttribute(offset, attribute);
    571                }
    572            }
    573            if (defaultCase) {
    574                // default case
    575                int defaultOffset = 0;
    576                for (int j = 0; j < n; j++) {
    577                    boolean found = false;
    578                    for (int i = 0; i < unionCases.size(); i++) {
    579                        final UnionCase element = (UnionCase) unionCases.get(i);
    580                        if (element.hasTag(tagBand[j])) {
    581                            found = true;
    582                        }
    583                    }
    584                    if (!found) {
    585                        defaultOffset++;
    586                    }
    587                }
    588                if (defaultCaseBody != null) {
    589                    for (int i = 0; i < defaultCaseBody.size(); i++) {
    590                        final LayoutElement element = (LayoutElement) defaultCaseBody.get(i);
    591                        element.addToAttribute(defaultOffset, attribute);
    592                    }
    593                }
    594            }
    595        }
    596
    597        public Integral getUnionTag() {
    598            return unionTag;
    599        }
    600
    601        public List getUnionCases() {
    602            return unionCases;
    603        }
    604
    605        public List getDefaultCaseBody() {
    606            return defaultCaseBody;
    607        }
    608
    609    }
    610
    611    public class Call extends LayoutElement {
    612
    613        private final int callableIndex;
    614        private Callable callable;
    615
    616        public Call(final int callableIndex) {
    617            this.callableIndex = callableIndex;
    618        }
    619
    620        public void setCallable(final Callable callable) {
    621            this.callable = callable;
    622            if (callableIndex < 1) {
    623                callable.setBackwardsCallable();
    624            }
    625        }
    626
    627        @Override
    628        public void readBands(final InputStream in, final int count) {
    629            /*
    630             * We don't read anything here, but we need to pass the extra count to the callable if it's a forwards call.
    631             * For backwards callables the count is transmitted directly in the attribute bands and so it is added
    632             * later.
    633             */
    634            if (callableIndex > 0) {
    635                callable.addCount(count);
    636            }
    637        }
    638
    639        @Override
    640        public void addToAttribute(final int n, final NewAttribute attribute) {
    641            callable.addNextToAttribute(attribute);
    642        }
    643
    644        public int getCallableIndex() {
    645            return callableIndex;
    646        }
    647
    648        public Callable getCallable() {
    649            return callable;
    650        }
    651    }
    652
    653    /**
    654     * Constant Pool Reference
    655     */
    656    public class Reference extends LayoutElement {
    657
    658        private final String tag;
    659
    660        private Object band;
    661
    662        private final int length;
    663
    664        public Reference(final String tag) {
    665            this.tag = tag;
    666            length = getLength(tag.charAt(tag.length() - 1));
    667        }
    668
    669        @Override
    670        public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
    671            if (tag.startsWith("KI")) { // Integer
    672                band = parseCPIntReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
    673            } else if (tag.startsWith("KJ")) { // Long
    674                band = parseCPLongReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
    675            } else if (tag.startsWith("KF")) { // Float
    676                band = parseCPFloatReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
    677            } else if (tag.startsWith("KD")) { // Double
    678                band = parseCPDoubleReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
    679            } else if (tag.startsWith("KS")) { // String
    680                band = parseCPStringReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
    681            } else if (tag.startsWith("RC")) { // Class
    682                band = parseCPClassReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
    683            } else if (tag.startsWith("RS")) { // Signature
    684                band = parseCPSignatureReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
    685            } else if (tag.startsWith("RD")) { // Descriptor
    686                band = parseCPDescriptorReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
    687            } else if (tag.startsWith("RF")) { // Field Reference
    688                band = parseCPFieldRefReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
    689            } else if (tag.startsWith("RM")) { // Method Reference
    690                band = parseCPMethodRefReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
    691            } else if (tag.startsWith("RI")) { // Interface Method Reference
    692                band = parseCPInterfaceMethodRefReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
    693            } else if (tag.startsWith("RU")) { // UTF8 String
    694                band = parseCPUTF8References(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
    695            }
    696        }
    697
    698        @Override
    699        public void addToAttribute(final int n, final NewAttribute attribute) {
    700            if (tag.startsWith("KI")) { // Integer
    701                attribute.addToBody(length, ((CPInteger[]) band)[n]);
    702            } else if (tag.startsWith("KJ")) { // Long
    703                attribute.addToBody(length, ((CPLong[]) band)[n]);
    704            } else if (tag.startsWith("KF")) { // Float
    705                attribute.addToBody(length, ((CPFloat[]) band)[n]);
    706            } else if (tag.startsWith("KD")) { // Double
    707                attribute.addToBody(length, ((CPDouble[]) band)[n]);
    708            } else if (tag.startsWith("KS")) { // String
    709                attribute.addToBody(length, ((CPString[]) band)[n]);
    710            } else if (tag.startsWith("RC")) { // Class
    711                attribute.addToBody(length, ((CPClass[]) band)[n]);
    712            } else if (tag.startsWith("RS")) { // Signature
    713                attribute.addToBody(length, ((CPUTF8[]) band)[n]);
    714            } else if (tag.startsWith("RD")) { // Descriptor
    715                attribute.addToBody(length, ((CPNameAndType[]) band)[n]);
    716            } else if (tag.startsWith("RF")) { // Field Reference
    717                attribute.addToBody(length, ((CPFieldRef[]) band)[n]);
    718            } else if (tag.startsWith("RM")) { // Method Reference
    719                attribute.addToBody(length, ((CPMethodRef[]) band)[n]);
    720            } else if (tag.startsWith("RI")) { // Interface Method Reference
    721                attribute.addToBody(length, ((CPInterfaceMethodRef[]) band)[n]);
    722            } else if (tag.startsWith("RU")) { // UTF8 String
    723                attribute.addToBody(length, ((CPUTF8[]) band)[n]);
    724            }
    725        }
    726
    727        public String getTag() {
    728            return tag;
    729        }
    730
    731    }
    732
    733    public static class Callable implements AttributeLayoutElement {
    734
    735        private final List body;
    736
    737        private boolean isBackwardsCallable;
    738
    739        private boolean isFirstCallable;
    740
    741        public Callable(final List body) throws IOException {
    742            this.body = body;
    743        }
    744
    745        private int count;
    746        private int index;
    747
    748        /**
    749         * Used by calls when adding band contents to attributes so they don't have to keep track of the internal index
    750         * of the callable.
    751         *
    752         * @param attribute TODO
    753         */
    754        public void addNextToAttribute(final NewAttribute attribute) {
    755            for (int i = 0; i < body.size(); i++) {
    756                final LayoutElement element = (LayoutElement) body.get(i);
    757                element.addToAttribute(index, attribute);
    758            }
    759            index++;
    760        }
    761
    762        /**
    763         * Adds the count of a call to this callable (ie the number of calls)
    764         *
    765         * @param count TODO
    766         */
    767        public void addCount(final int count) {
    768            this.count += count;
    769        }
    770
    771        @Override
    772        public void readBands(final InputStream in, int count) throws IOException, Pack200Exception {
    773            if (isFirstCallable) {
    774                count += this.count;
    775            } else {
    776                count = this.count;
    777            }
    778            for (int i = 0; i < body.size(); i++) {
    779                final LayoutElement element = (LayoutElement) body.get(i);
    780                element.readBands(in, count);
    781            }
    782        }
    783
    784        @Override
    785        public void addToAttribute(final int n, final NewAttribute attribute) {
    786            if (isFirstCallable) {
    787                // Ignore n because bands also contain element parts from calls
    788                for (int i = 0; i < body.size(); i++) {
    789                    final LayoutElement element = (LayoutElement) body.get(i);
    790                    element.addToAttribute(index, attribute);
    791                }
    792                index++;
    793            }
    794        }
    795
    796        public boolean isBackwardsCallable() {
    797            return isBackwardsCallable;
    798        }
    799
    800        /**
    801         * Tells this Callable that it is a backwards callable
    802         */
    803        public void setBackwardsCallable() {
    804            this.isBackwardsCallable = true;
    805        }
    806
    807        public void setFirstCallable(final boolean isFirstCallable) {
    808            this.isFirstCallable = isFirstCallable;
    809        }
    810
    811        public List getBody() {
    812            return body;
    813        }
    814    }
    815
    816    /**
    817     * A Union case
    818     */
    819    public class UnionCase extends LayoutElement {
    820
    821        private List body;
    822
    823        private final List tags;
    824
    825        public UnionCase(final List tags) {
    826            this.tags = tags;
    827        }
    828
    829        public boolean hasTag(final long l) {
    830            return tags.contains(Integer.valueOf((int) l));
    831        }
    832
    833        public UnionCase(final List tags, final List body) throws IOException {
    834            this.tags = tags;
    835            this.body = body;
    836        }
    837
    838        @Override
    839        public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
    840            if (body != null) {
    841                for (int i = 0; i < body.size(); i++) {
    842                    final LayoutElement element = (LayoutElement) body.get(i);
    843                    element.readBands(in, count);
    844                }
    845            }
    846        }
    847
    848        @Override
    849        public void addToAttribute(final int index, final NewAttribute attribute) {
    850            if (body != null) {
    851                for (int i = 0; i < body.size(); i++) {
    852                    final LayoutElement element = (LayoutElement) body.get(i);
    853                    element.addToAttribute(index, attribute);
    854                }
    855            }
    856        }
    857
    858        public List getBody() {
    859            return body == null ? Collections.EMPTY_LIST : body;
    860        }
    861    }
    862
    863    /**
    864     * Utility method to get the contents of the given stream, up to the next ']', (ignoring pairs of brackets '[' and
    865     * ']')
    866     *
    867     * @param stream
    868     * @return
    869     * @throws IOException If an I/O error occurs.
    870     */
    871    private StringReader getStreamUpToMatchingBracket(final StringReader stream) throws IOException {
    872        final StringBuffer sb = new StringBuffer();
    873        int foundBracket = -1;
    874        while (foundBracket != 0) {
    875            final char c = (char) stream.read();
    876            if (c == ']') {
    877                foundBracket++;
    878            }
    879            if (c == '[') {
    880                foundBracket--;
    881            }
    882            if (!(foundBracket == 0)) {
    883                sb.append(c);
    884            }
    885        }
    886        return new StringReader(sb.toString());
    887    }
    888
    889    /**
    890     * Returns the {@link BHSDCodec} that should be used for the given layout element.
    891     *
    892     * @param layoutElement TODO
    893     * @return the {@link BHSDCodec} that should be used for the given layout element.
    894     */
    895    public BHSDCodec getCodec(final String layoutElement) {
    896        if (layoutElement.indexOf('O') >= 0) {
    897            return Codec.BRANCH5;
    898        }
    899        if (layoutElement.indexOf('P') >= 0) {
    900            return Codec.BCI5;
    901        }
    902        if (layoutElement.indexOf('S') >= 0 && layoutElement.indexOf("KS") < 0 //$NON-NLS-1$
    903            && layoutElement.indexOf("RS") < 0) { //$NON-NLS-1$
    904            return Codec.SIGNED5;
    905        }
    906        if (layoutElement.indexOf('B') >= 0) {
    907            return Codec.BYTE1;
    908        }
    909        return Codec.UNSIGNED5;
    910    }
    911
    912    /**
    913     * Gets the contents of the given stream, up to the next ']', (ignoring pairs of brackets '[' and ']')
    914     *
    915     * @param stream input stream.
    916     * @return the contents of the given stream.
    917     * @throws IOException If an I/O error occurs.
    918     */
    919    private String readUpToMatchingBracket(final StringReader stream) throws IOException {
    920        final StringBuffer sb = new StringBuffer();
    921        int foundBracket = -1;
    922        while (foundBracket != 0) {
    923            final char c = (char) stream.read();
    924            if (c == ']') {
    925                foundBracket++;
    926            }
    927            if (c == '[') {
    928                foundBracket--;
    929            }
    930            if (!(foundBracket == 0)) {
    931                sb.append(c);
    932            }
    933        }
    934        return sb.toString();
    935    }
    936
    937    /**
    938     * Read a number from the stream and return it
    939     *
    940     * @param stream
    941     * @return
    942     * @throws IOException If an I/O error occurs.
    943     */
    944    private Integer readNumber(final StringReader stream) throws IOException {
    945        stream.mark(1);
    946        final char first = (char) stream.read();
    947        final boolean negative = first == '-';
    948        if (!negative) {
    949            stream.reset();
    950        }
    951        stream.mark(100);
    952        int i;
    953        int length = 0;
    954        while ((i = (stream.read())) != -1 && Character.isDigit((char) i)) {
    955            length++;
    956        }
    957        stream.reset();
    958        if (length == 0) {
    959            return null;
    960        }
    961        final char[] digits = new char[length];
    962        final int read = stream.read(digits);
    963        if (read != digits.length) {
    964            throw new IOException("Error reading from the input stream");
    965        }
    966        return Integer.valueOf(Integer.parseInt((negative ? "-" : "") + new String(digits)));
    967    }
    968
    969    /**
    970     * Read a 'body' section of the layout from the given stream
    971     *
    972     * @param stream
    973     * @return List of LayoutElements
    974     * @throws IOException If an I/O error occurs.
    975     */
    976    private List readBody(final StringReader stream) throws IOException {
    977        final List layoutElements = new ArrayList();
    978        LayoutElement e;
    979        while ((e = readNextLayoutElement(stream)) != null) {
    980            layoutElements.add(e);
    981        }
    982        return layoutElements;
    983    }
    984
    985    public int getBackwardsCallCount() {
    986        return backwardsCallCount;
    987    }
    988
    989    /**
    990     * Once the attribute bands have been read the callables can be informed about the number of times each is subject
    991     * to a backwards call. This method is used to set this information.
    992     *
    993     * @param backwardsCalls one int for each backwards callable, which contains the number of times that callable is
    994     *        subject to a backwards call.
    995     * @throws IOException If an I/O error occurs.
    996     */
    997    public void setBackwardsCalls(final int[] backwardsCalls) throws IOException {
    998        int index = 0;
    999        parseLayout();
    1000        for (int i = 0; i < attributeLayoutElements.size(); i++) {
    1001            final AttributeLayoutElement element = (AttributeLayoutElement) attributeLayoutElements.get(i);
    1002            if (element instanceof Callable && ((Callable) element).isBackwardsCallable()) {
    1003                ((Callable) element).addCount(backwardsCalls[index]);
    1004                index++;
    1005            }
    1006        }
    1007    }
    1008
    1009    @Override
    1010    public void unpack() throws IOException, Pack200Exception {
    1011
    1012    }
    1013
    1014}