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