    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;
    042import org.apache.commons.compress.utils.ParsingUtils;
    043
    044/**
    045 * Sets of bands relating to a non-predefined attribute
    046 */
    047public class NewAttributeBands extends BandSet {
    048
    049    /**
    050     * An AttributeLayoutElement is a part of an attribute layout and has one or more bands associated with it, which transmit the AttributeElement data for
    051     * successive Attributes of this type.
    052     */
    053    private interface AttributeLayoutElement {
    054
    055        /**
    056         * Adds the band data for this element at the given index to the attribute.
    057         *
    058         * @param index     Index position to add the attribute.
    059         * @param attribute The attribute to add.
    060         */
    061        void addToAttribute(int index, NewAttribute attribute);
    062
    063        /**
    064         * Read the bands associated with this part of the layout.
    065         *
    066         * @param in    TODO
    067         * @param count TODO
    068         * @throws Pack200Exception Bad archive.
    069         * @throws IOException      If an I/O error occurs.
    070         */
    071        void readBands(InputStream in, int count) throws IOException, Pack200Exception;
    072
    073    }
    074
    075    public class Call extends LayoutElement {
    076
    077        private final int callableIndex;
    078        private Callable callable;
    079
    080        public Call(final int callableIndex) {
    081            this.callableIndex = callableIndex;
    082        }
    083
    084        @Override
    085        public void addToAttribute(final int n, final NewAttribute attribute) {
    086            callable.addNextToAttribute(attribute);
    087        }
    088
    089        public Callable getCallable() {
    090            return callable;
    091        }
    092
    093        public int getCallableIndex() {
    094            return callableIndex;
    095        }
    096
    097        @Override
    098        public void readBands(final InputStream in, final int count) {
    099            /*
    100             * 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
    101             * transmitted directly in the attribute bands and so it is added 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
    128        public Callable(final List<LayoutElement> body) {
    129            this.body = body;
    130        }
    131
    132        /**
    133         * Adds the count of a call to this callable (ie the number of calls)
    134         *
    135         * @param count TODO
    136         */
    137        public void addCount(final int count) {
    138            this.count += count;
    139        }
    140
    141        /**
    142         * Used by calls when adding band contents to attributes, so they don't have to keep track of the internal index 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.contains("KS") //$NON-NLS-1$
    613                && !layoutElement.contains("RS")) { //$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()), attributeLayout.getIndex());
    631        for (final AttributeLayoutElement element : elements) {
    632            element.addToAttribute(index, attribute);
    633        }
    634        return attribute;
    635    }
    636
    637    /**
    638     * Utility method to get the contents of the given stream, up to the next {@code ]}, (ignoring pairs of brackets {@code [} and {@code ]})
    639     *
    640     * @param stream
    641     * @return
    642     * @throws IOException If an I/O error occurs.
    643     */
    644    private StringReader getStreamUpToMatchingBracket(final StringReader stream) throws IOException {
    645        final StringBuilder sb = new StringBuilder();
    646        int foundBracket = -1;
    647        while (foundBracket != 0) {
    648            final int read = stream.read();
    649            if (read == -1) {
    650                break;
    651            }
    652            final char c = (char) read;
    653            if (c == ']') {
    654                foundBracket++;
    655            }
    656            if (c == '[') {
    657                foundBracket--;
    658            }
    659            if (!(foundBracket == 0)) {
    660                sb.append(c);
    661            }
    662        }
    663        return new StringReader(sb.toString());
    664    }
    665
    666    /**
    667     * Parse the bands relating to this AttributeLayout and return the correct class file attributes as a List of {@link Attribute}.
    668     *
    669     * @param in              parse source.
    670     * @param occurrenceCount TODO
    671     * @return Class file attributes as a List of {@link Attribute}.
    672     * @throws IOException      If an I/O error occurs.
    673     * @throws Pack200Exception TODO
    674     */
    675    public List<Attribute> parseAttributes(final InputStream in, final int occurrenceCount) throws IOException, Pack200Exception {
    676        for (final AttributeLayoutElement element : attributeLayoutElements) {
    677            element.readBands(in, occurrenceCount);
    678        }
    679
    680        final List<Attribute> attributes = new ArrayList<>(occurrenceCount);
    681        for (int i = 0; i < occurrenceCount; i++) {
    682            attributes.add(getOneAttribute(i, attributeLayoutElements));
    683        }
    684        return attributes;
    685    }
    686
    687    /**
    688     * Tokenize the layout into AttributeElements
    689     *
    690     * @throws IOException If an I/O error occurs.
    691     */
    692    private void parseLayout() throws IOException {
    693        if (attributeLayoutElements == null) {
    694            attributeLayoutElements = new ArrayList<>();
    695            final StringReader stream = new StringReader(attributeLayout.getLayout());
    696            AttributeLayoutElement e;
    697            while ((e = readNextAttributeElement(stream)) != null) {
    698                attributeLayoutElements.add(e);
    699            }
    700            resolveCalls();
    701        }
    702    }
    703
    704    /*
    705     * (non-Javadoc)
    706     *
    707     * @see org.apache.commons.compress.harmony.unpack200.BandSet#unpack(java.io.InputStream)
    708     */
    709    @Override
    710    public void read(final InputStream in) throws IOException, Pack200Exception {
    711        // does nothing - use parseAttributes instead
    712    }
    713
    714    /**
    715     * Read a 'body' section of the layout from the given stream
    716     *
    717     * @param stream
    718     * @return List of LayoutElements
    719     * @throws IOException If an I/O error occurs.
    720     */
    721    private List<LayoutElement> readBody(final StringReader stream) throws IOException {
    722        final List<LayoutElement> layoutElements = new ArrayList<>();
    723        LayoutElement e;
    724        while ((e = readNextLayoutElement(stream)) != null) {
    725            layoutElements.add(e);
    726        }
    727        return layoutElements;
    728    }
    729
    730    private AttributeLayoutElement readNextAttributeElement(final StringReader stream) throws IOException {
    731        stream.mark(1);
    732        final int next = stream.read();
    733        if (next == -1) {
    734            return null;
    735        }
    736        if (next == '[') {
    737            return new Callable(readBody(getStreamUpToMatchingBracket(stream)));
    738        }
    739        stream.reset();
    740        return readNextLayoutElement(stream);
    741    }
    742
    743    private LayoutElement readNextLayoutElement(final StringReader stream) throws IOException {
    744        final int nextChar = stream.read();
    745        if (nextChar == -1) {
    746            return null;
    747        }
    748        switch (nextChar) {
    749        // Integrals
    750        case 'B':
    751        case 'H':
    752        case 'I':
    753        case 'V':
    754            return new Integral(new String(new char[] { (char) nextChar }));
    755        case 'S':
    756        case 'F':
    757            return new Integral(new String(new char[] { (char) nextChar, (char) stream.read() }));
    758        case 'P':
    759            stream.mark(1);
    760            if (stream.read() != 'O') {
    761                stream.reset();
    762                return new Integral("P" + (char) stream.read());
    763            }
    764            return new Integral("PO" + (char) stream.read());
    765        case 'O':
    766            stream.mark(1);
    767            if (stream.read() != 'S') {
    768                stream.reset();
    769                return new Integral("O" + (char) stream.read());
    770            }
    771            return new Integral("OS" + (char) stream.read());
    772
    773        // Replication
    774        case 'N':
    775            final char uintType = (char) stream.read();
    776            stream.read(); // '['
    777            final String str = readUpToMatchingBracket(stream);
    778            return new Replication("" + uintType, str);
    779
    780        // Union
    781        case 'T':
    782            String intType = "" + (char) stream.read();
    783            if (intType.equals("S")) {
    784                intType += (char) stream.read();
    785            }
    786            final List<UnionCase> unionCases = new ArrayList<>();
    787            UnionCase c;
    788            while ((c = readNextUnionCase(stream)) != null) {
    789                unionCases.add(c);
    790            }
    791            stream.read(); // '('
    792            stream.read(); // ')'
    793            stream.read(); // '['
    794            List<LayoutElement> body = null;
    795            stream.mark(1);
    796            final char next = (char) stream.read();
    797            if (next != ']') {
    798                stream.reset();
    799                body = readBody(getStreamUpToMatchingBracket(stream));
    800            }
    801            return new Union(intType, unionCases, body);
    802
    803        // Call
    804        case '(':
    805            final int number = readNumber(stream).intValue();
    806            stream.read(); // ')'
    807            return new Call(number);
    808        // Reference
    809        case 'K':
    810        case 'R':
    811            final StringBuilder string = new StringBuilder("").append((char) nextChar).append((char) stream.read());
    812            final char nxt = (char) stream.read();
    813            string.append(nxt);
    814            if (nxt == 'N') {
    815                string.append((char) stream.read());
    816            }
    817            return new Reference(string.toString());
    818        }
    819        return null;
    820    }
    821
    822    /**
    823     * Read a UnionCase from the stream.
    824     *
    825     * @param stream source stream.
    826     * @return A UnionCase from the stream.
    827     * @throws IOException If an I/O error occurs.
    828     */
    829    private UnionCase readNextUnionCase(final StringReader stream) throws IOException {
    830        stream.mark(2);
    831        stream.read(); // '('
    832        final int next = stream.read();
    833        char ch = (char) next;
    834        if (ch == ')' || next == -1) {
    835            stream.reset();
    836            return null;
    837        }
    838        stream.reset();
    839        stream.read(); // '('
    840        final List<Integer> tags = new ArrayList<>();
    841        Integer nextTag;
    842        do {
    843            nextTag = readNumber(stream);
    844            if (nextTag != null) {
    845                tags.add(nextTag);
    846                stream.read(); // ',' or ')'
    847            }
    848        } while (nextTag != null);
    849        stream.read(); // '['
    850        stream.mark(1);
    851        ch = (char) stream.read();
    852        if (ch == ']') {
    853            return new UnionCase(tags);
    854        }
    855        stream.reset();
    856        return new UnionCase(tags, readBody(getStreamUpToMatchingBracket(stream)));
    857    }
    858
    859    /**
    860     * Read a number from the stream and return it
    861     *
    862     * @param stream
    863     * @return
    864     * @throws IOException If an I/O error occurs.
    865     */
    866    private Integer readNumber(final StringReader stream) throws IOException {
    867        stream.mark(1);
    868        final char first = (char) stream.read();
    869        final boolean negative = first == '-';
    870        if (!negative) {
    871            stream.reset();
    872        }
    873        stream.mark(100);
    874        int i;
    875        int length = 0;
    876        while ((i = stream.read()) != -1 && Character.isDigit((char) i)) {
    877            length++;
    878        }
    879        stream.reset();
    880        if (length == 0) {
    881            return null;
    882        }
    883        final char[] digits = new char[length];
    884        final int read = stream.read(digits);
    885        if (read != digits.length) {
    886            throw new IOException("Error reading from the input stream");
    887        }
    888        return ParsingUtils.parseIntValue((negative ? "-" : "") + new String(digits));
    889    }
    890
    891    /**
    892     * Gets the contents of the given stream, up to the next {@code ]}, (ignoring pairs of brackets {@code [} and {@code ]})
    893     *
    894     * @param stream input stream.
    895     * @return the contents of the given stream.
    896     * @throws IOException If an I/O error occurs.
    897     */
    898    private String readUpToMatchingBracket(final StringReader stream) throws IOException {
    899        final StringBuilder sb = new StringBuilder();
    900        int foundBracket = -1;
    901        while (foundBracket != 0) {
    902            final int read = stream.read();
    903            if (read == -1) {
    904                break;
    905            }
    906            final char c = (char) read;
    907            if (c == ']') {
    908                foundBracket++;
    909            }
    910            if (c == '[') {
    911                foundBracket--;
    912            }
    913            if (!(foundBracket == 0)) {
    914                sb.append(c);
    915            }
    916        }
    917        return sb.toString();
    918    }
    919
    920    /**
    921     * Resolve calls in the attribute layout and returns the number of backwards calls
    922     */
    923    private void resolveCalls() {
    924        int backwardsCalls = 0;
    925        for (int i = 0; i < attributeLayoutElements.size(); i++) {
    926            final AttributeLayoutElement element = attributeLayoutElements.get(i);
    927            if (element instanceof Callable) {
    928                final Callable callable = (Callable) element;
    929                if (i == 0) {
    930                    callable.setFirstCallable(true);
    931                }
    932                // Look for calls in the body
    933                for (final LayoutElement layoutElement : callable.body) {
    934                    // Set the callable for each call
    935                    backwardsCalls += resolveCallsForElement(i, callable, layoutElement);
    936                }
    937            }
    938        }
    939        backwardsCallCount = backwardsCalls;
    940    }
    941
    942    private int resolveCallsForElement(final int i, final Callable currentCallable, final LayoutElement layoutElement) {
    943        int backwardsCalls = 0;
    944        if (layoutElement instanceof Call) {
    945            final Call call = (Call) layoutElement;
    946            int index = call.callableIndex;
    947            if (index == 0) { // Calls the parent callable
    948                backwardsCalls++;
    949                call.setCallable(currentCallable);
    950            } else if (index > 0) { // Forwards call
    951                for (int k = i + 1; k < attributeLayoutElements.size(); k++) {
    952                    final AttributeLayoutElement el = attributeLayoutElements.get(k);
    953                    if (el instanceof Callable) {
    954                        index--;
    955                        if (index == 0) {
    956                            call.setCallable((Callable) el);
    957                            break;
    958                        }
    959                    }
    960                }
    961            } else { // Backwards call
    962                backwardsCalls++;
    963                for (int k = i - 1; k >= 0; k--) {
    964                    final AttributeLayoutElement el = attributeLayoutElements.get(k);
    965                    if (el instanceof Callable) {
    966                        index++;
    967                        if (index == 0) {
    968                            call.setCallable((Callable) el);
    969                            break;
    970                        }
    971                    }
    972                }
    973            }
    974        } else if (layoutElement instanceof Replication) {
    975            final List<LayoutElement> children = ((Replication) layoutElement).layoutElements;
    976            for (final LayoutElement child : children) {
    977                backwardsCalls += resolveCallsForElement(i, currentCallable, child);
    978            }
    979        }
    980        return backwardsCalls;
    981    }
    982
    983    /**
    984     * 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
    985     * to set this information.
    986     *
    987     * @param backwardsCalls one int for each backwards callable, which contains the number of times that callable is subject to a backwards call.
    988     * @throws IOException If an I/O error occurs.
    989     */
    990    public void setBackwardsCalls(final int[] backwardsCalls) throws IOException {
    991        int index = 0;
    992        parseLayout();
    993        for (final AttributeLayoutElement element : attributeLayoutElements) {
    994            if (element instanceof Callable && ((Callable) element).isBackwardsCallable()) {
    995                ((Callable) element).addCount(backwardsCalls[index]);
    996                index++;
    997            }
    998        }
    999    }
    1000
    1001    @Override
    1002    public void unpack() throws IOException, Pack200Exception {
    1003
    1004    }
    1005
    1006}