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