    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.pack200;
    018
    019import java.io.ByteArrayInputStream;
    020import java.io.IOException;
    021import java.io.InputStream;
    022import java.io.OutputStream;
    023import java.io.StringReader;
    024import java.io.UncheckedIOException;
    025import java.util.ArrayList;
    026import java.util.Collections;
    027import java.util.List;
    028import java.util.Map;
    029
    030import org.apache.commons.compress.harmony.pack200.AttributeDefinitionBands.AttributeDefinition;
    031import org.objectweb.asm.Label;
    032
    033/**
    034 * Sets of bands relating to a non-predefined attribute that has had a layout definition given to pack200 (e.g. via one
    035 * of the -C, -M, -F or -D command line options)
    036 */
    037public class NewAttributeBands extends BandSet {
    038
    039    /**
    040     * An AttributeLayoutElement is a part of an attribute layout and has one or more bands associated with it, which
    041     * transmit the AttributeElement data for successive Attributes of this type.
    042     */
    043    public interface AttributeLayoutElement {
    044
    045        void addAttributeToBand(NewAttribute attribute, InputStream inputStream);
    046
    047        void pack(OutputStream ouputStream) throws IOException, Pack200Exception;
    048
    049        void renumberBci(IntList bciRenumbering, Map<Label, Integer> labelsToOffsets);
    050
    051    }
    052    public class Call extends LayoutElement {
    053
    054        private final int callableIndex;
    055        private Callable callable;
    056
    057        public Call(final int callableIndex) {
    058            this.callableIndex = callableIndex;
    059        }
    060
    061        @Override
    062        public void addAttributeToBand(final NewAttribute attribute, final InputStream inputStream) {
    063            callable.addAttributeToBand(attribute, inputStream);
    064            if (callableIndex < 1) {
    065                callable.addBackwardsCall();
    066            }
    067        }
    068
    069        public Callable getCallable() {
    070            return callable;
    071        }
    072
    073        public int getCallableIndex() {
    074            return callableIndex;
    075        }
    076
    077        @Override
    078        public void pack(final OutputStream outputStream) {
    079            // do nothing here as pack will be called on the callable at another time
    080        }
    081
    082        @Override
    083        public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
    084            // do nothing here as renumberBci will be called on the callable at another time
    085        }
    086
    087        public void setCallable(final Callable callable) {
    088            this.callable = callable;
    089            if (callableIndex < 1) {
    090                callable.setBackwardsCallable();
    091            }
    092        }
    093    }
    094    public class Callable implements AttributeLayoutElement {
    095
    096        private final List<LayoutElement> body;
    097
    098        private boolean isBackwardsCallable;
    099
    100        private int backwardsCallableIndex;
    101
    102        public Callable(final List<LayoutElement> body) {
    103            this.body = body;
    104        }
    105
    106        @Override
    107        public void addAttributeToBand(final NewAttribute attribute, final InputStream inputStream) {
    108            for (final AttributeLayoutElement element : body) {
    109                element.addAttributeToBand(attribute, inputStream);
    110            }
    111        }
    112
    113        public void addBackwardsCall() {
    114            backwardsCallCounts[backwardsCallableIndex]++;
    115        }
    116
    117        public List<LayoutElement> getBody() {
    118            return body;
    119        }
    120
    121        public boolean isBackwardsCallable() {
    122            return isBackwardsCallable;
    123        }
    124
    125        @Override
    126        public void pack(final OutputStream outputStream) throws IOException, Pack200Exception {
    127            for (final AttributeLayoutElement element : body) {
    128                element.pack(outputStream);
    129            }
    130        }
    131
    132        @Override
    133        public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
    134            for (final AttributeLayoutElement element : body) {
    135                element.renumberBci(bciRenumbering, labelsToOffsets);
    136            }
    137        }
    138
    139        /**
    140         * Tells this Callable that it is a backwards callable
    141         */
    142        public void setBackwardsCallable() {
    143            this.isBackwardsCallable = true;
    144        }
    145
    146        public void setBackwardsCallableIndex(final int backwardsCallableIndex) {
    147            this.backwardsCallableIndex = backwardsCallableIndex;
    148        }
    149    }
    150    public class Integral extends LayoutElement {
    151
    152        private final String tag;
    153
    154        private final List band = new ArrayList();
    155        private final BHSDCodec defaultCodec;
    156
    157        // used for bytecode offsets (OH and POH)
    158        private Integral previousIntegral;
    159        private int previousPValue;
    160
    161        public Integral(final String tag) {
    162            this.tag = tag;
    163            this.defaultCodec = getCodec(tag);
    164        }
    165
    166        public Integral(final String tag, final Integral previousIntegral) {
    167            this.tag = tag;
    168            this.defaultCodec = getCodec(tag);
    169            this.previousIntegral = previousIntegral;
    170        }
    171
    172        @Override
    173        public void addAttributeToBand(final NewAttribute attribute, final InputStream inputStream) {
    174            Object val = null;
    175            int value = 0;
    176            if (tag.equals("B") || tag.equals("FB")) {
    177                value = readInteger(1, inputStream) & 0xFF; // unsigned byte
    178            } else if (tag.equals("SB")) {
    179                value = readInteger(1, inputStream);
    180            } else if (tag.equals("H") || tag.equals("FH")) {
    181                value = readInteger(2, inputStream) & 0xFFFF; // unsigned short
    182            } else if (tag.equals("SH")) {
    183                value = readInteger(2, inputStream);
    184            } else if (tag.equals("I") || tag.equals("FI") || tag.equals("SI")) {
    185                value = readInteger(4, inputStream);
    186            } else if (tag.equals("V") || tag.equals("FV") || tag.equals("SV")) {
    187                // Not currently supported
    188            } else if (tag.startsWith("PO") || tag.startsWith("OS")) {
    189                final char uint_type = tag.substring(2).toCharArray()[0];
    190                final int length = getLength(uint_type);
    191                value = readInteger(length, inputStream);
    192                value += previousIntegral.previousPValue;
    193                val = attribute.getLabel(value);
    194                previousPValue = value;
    195            } else if (tag.startsWith("P")) {
    196                final char uint_type = tag.substring(1).toCharArray()[0];
    197                final int length = getLength(uint_type);
    198                value = readInteger(length, inputStream);
    199                val = attribute.getLabel(value);
    200                previousPValue = value;
    201            } else if (tag.startsWith("O")) {
    202                final char uint_type = tag.substring(1).toCharArray()[0];
    203                final int length = getLength(uint_type);
    204                value = readInteger(length, inputStream);
    205                value += previousIntegral.previousPValue;
    206                val = attribute.getLabel(value);
    207                previousPValue = value;
    208            }
    209            if (val == null) {
    210                val = Integer.valueOf(value);
    211            }
    212            band.add(val);
    213        }
    214
    215        public String getTag() {
    216            return tag;
    217        }
    218
    219        public int latestValue() {
    220            return ((Integer) band.get(band.size() - 1)).intValue();
    221        }
    222
    223        @Override
    224        public void pack(final OutputStream outputStream) throws IOException, Pack200Exception {
    225            PackingUtils.log("Writing new attribute bands...");
    226            final byte[] encodedBand = encodeBandInt(tag, integerListToArray(band), defaultCodec);
    227            outputStream.write(encodedBand);
    228            PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + tag + "[" + band.size() + "]");
    229        }
    230
    231        @Override
    232        public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
    233            if (tag.startsWith("O") || tag.startsWith("PO")) {
    234                renumberOffsetBci(previousIntegral.band, bciRenumbering, labelsToOffsets);
    235            } else if (tag.startsWith("P")) {
    236                for (int i = band.size() - 1; i >= 0; i--) {
    237                    final Object label = band.get(i);
    238                    if (label instanceof Integer) {
    239                        break;
    240                    }
    241                    if (label instanceof Label) {
    242                        band.remove(i);
    243                        final Integer bytecodeIndex = labelsToOffsets.get(label);
    244                        band.add(i, Integer.valueOf(bciRenumbering.get(bytecodeIndex.intValue())));
    245                    }
    246                }
    247            }
    248        }
    249
    250        private void renumberOffsetBci(final List relative, final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
    251            for (int i = band.size() - 1; i >= 0; i--) {
    252                final Object label = band.get(i);
    253                if (label instanceof Integer) {
    254                    break;
    255                }
    256                if (label instanceof Label) {
    257                    band.remove(i);
    258                    final Integer bytecodeIndex = labelsToOffsets.get(label);
    259                    final Integer renumberedOffset = Integer
    260                        .valueOf(bciRenumbering.get(bytecodeIndex.intValue()) - ((Integer) relative.get(i)).intValue());
    261                    band.add(i, renumberedOffset);
    262                }
    263            }
    264        }
    265
    266    }
    267    public abstract class LayoutElement implements AttributeLayoutElement {
    268
    269        protected int getLength(final char uint_type) {
    270            int length = 0;
    271            switch (uint_type) {
    272            case 'B':
    273                length = 1;
    274                break;
    275            case 'H':
    276                length = 2;
    277                break;
    278            case 'I':
    279                length = 4;
    280                break;
    281            case 'V':
    282                length = 0;
    283                break;
    284            }
    285            return length;
    286        }
    287    }
    288
    289    /**
    290     * Constant Pool Reference
    291     */
    292    public class Reference extends LayoutElement {
    293
    294        private final String tag;
    295
    296        private List<ConstantPoolEntry> band = new ArrayList<>();
    297
    298        private boolean nullsAllowed;
    299
    300        public Reference(final String tag) {
    301            this.tag = tag;
    302            nullsAllowed = tag.indexOf('N') != -1;
    303        }
    304
    305        @Override
    306        public void addAttributeToBand(final NewAttribute attribute, final InputStream inputStream) {
    307            final int index = readInteger(4, inputStream);
    308            if (tag.startsWith("RC")) { // Class
    309                band.add(cpBands.getCPClass(attribute.readClass(index)));
    310            } else if (tag.startsWith("RU")) { // UTF8 String
    311                band.add(cpBands.getCPUtf8(attribute.readUTF8(index)));
    312            } else if (tag.startsWith("RS")) { // Signature
    313                band.add(cpBands.getCPSignature(attribute.readUTF8(index)));
    314            } else { // Constant
    315                band.add(cpBands.getConstant(attribute.readConst(index)));
    316            }
    317            // TODO method and field references
    318        }
    319
    320        public String getTag() {
    321            return tag;
    322        }
    323
    324        @Override
    325        public void pack(final OutputStream outputStream) throws IOException, Pack200Exception {
    326            int[] ints;
    327            if (nullsAllowed) {
    328                ints = cpEntryOrNullListToArray(band);
    329            } else {
    330                ints = cpEntryListToArray(band);
    331            }
    332            final byte[] encodedBand = encodeBandInt(tag, ints, Codec.UNSIGNED5);
    333            outputStream.write(encodedBand);
    334            PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + tag + "[" + ints.length + "]");
    335        }
    336
    337        @Override
    338        public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
    339            // nothing to do here
    340        }
    341
    342    }
    343
    344    /**
    345     * A replication is an array of layout elements, with an associated count
    346     */
    347    public class Replication extends LayoutElement {
    348
    349        private final Integral countElement;
    350
    351        private final List<LayoutElement> layoutElements = new ArrayList<>();
    352
    353        public Replication(final String tag, final String contents) throws IOException {
    354            this.countElement = new Integral(tag);
    355            final StringReader stream = new StringReader(contents);
    356            LayoutElement e;
    357            while ((e = readNextLayoutElement(stream)) != null) {
    358                layoutElements.add(e);
    359            }
    360        }
    361
    362        @Override
    363        public void addAttributeToBand(final NewAttribute attribute, final InputStream inputStream) {
    364            countElement.addAttributeToBand(attribute, inputStream);
    365            final int count = countElement.latestValue();
    366            for (int i = 0; i < count; i++) {
    367                for (final AttributeLayoutElement layoutElement : layoutElements) {
    368                    layoutElement.addAttributeToBand(attribute, inputStream);
    369                }
    370            }
    371        }
    372
    373        public Integral getCountElement() {
    374            return countElement;
    375        }
    376
    377        public List<LayoutElement> getLayoutElements() {
    378            return layoutElements;
    379        }
    380
    381        @Override
    382        public void pack(final OutputStream out) throws IOException, Pack200Exception {
    383            countElement.pack(out);
    384            for (final AttributeLayoutElement layoutElement : layoutElements) {
    385                layoutElement.pack(out);
    386            }
    387        }
    388
    389        @Override
    390        public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
    391            for (final AttributeLayoutElement layoutElement : layoutElements) {
    392                layoutElement.renumberBci(bciRenumbering, labelsToOffsets);
    393            }
    394        }
    395    }
    396
    397    /**
    398     * A Union is a type of layout element where the tag value acts as a selector for one of the union cases
    399     */
    400    public class Union extends LayoutElement {
    401
    402        private final Integral unionTag;
    403        private final List<UnionCase> unionCases;
    404        private final List<LayoutElement> defaultCaseBody;
    405
    406        public Union(final String tag, final List<UnionCase> unionCases, final List<LayoutElement> body) {
    407            this.unionTag = new Integral(tag);
    408            this.unionCases = unionCases;
    409            this.defaultCaseBody = body;
    410        }
    411
    412        @Override
    413        public void addAttributeToBand(final NewAttribute attribute, final InputStream inputStream) {
    414            unionTag.addAttributeToBand(attribute, inputStream);
    415            final long tag = unionTag.latestValue();
    416            boolean defaultCase = true;
    417            for (final UnionCase unionCase : unionCases) {
    418                if (unionCase.hasTag(tag)) {
    419                    defaultCase = false;
    420                    unionCase.addAttributeToBand(attribute, inputStream);
    421                }
    422            }
    423            if (defaultCase) {
    424                for (final LayoutElement layoutElement : defaultCaseBody) {
    425                    layoutElement.addAttributeToBand(attribute, inputStream);
    426                }
    427            }
    428        }
    429
    430        public List<LayoutElement> getDefaultCaseBody() {
    431            return defaultCaseBody;
    432        }
    433
    434        public List<UnionCase> getUnionCases() {
    435            return unionCases;
    436        }
    437
    438        public Integral getUnionTag() {
    439            return unionTag;
    440        }
    441
    442        @Override
    443        public void pack(final OutputStream outputStream) throws IOException, Pack200Exception {
    444            unionTag.pack(outputStream);
    445            for (final UnionCase unionCase : unionCases) {
    446                unionCase.pack(outputStream);
    447            }
    448            for (final LayoutElement element : defaultCaseBody) {
    449                element.pack(outputStream);
    450            }
    451        }
    452
    453        @Override
    454        public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
    455            for (final UnionCase unionCase : unionCases) {
    456                unionCase.renumberBci(bciRenumbering, labelsToOffsets);
    457            }
    458            for (final LayoutElement element : defaultCaseBody) {
    459                element.renumberBci(bciRenumbering, labelsToOffsets);
    460            }
    461        }
    462    }
    463
    464    /**
    465     * A Union case
    466     */
    467    public class UnionCase extends LayoutElement {
    468
    469        private final List<LayoutElement> body;
    470
    471        private final List<Integer> tags;
    472
    473        public UnionCase(final List<Integer> tags) {
    474            this.tags = tags;
    475            this.body = Collections.EMPTY_LIST;
    476        }
    477
    478        public UnionCase(final List<Integer> tags, final List<LayoutElement> body) {
    479            this.tags = tags;
    480            this.body = body;
    481        }
    482
    483        @Override
    484        public void addAttributeToBand(final NewAttribute attribute, final InputStream inputStream) {
    485            for (final LayoutElement element : body) {
    486                element.addAttributeToBand(attribute, inputStream);
    487            }
    488        }
    489
    490        public List<LayoutElement> getBody() {
    491            return body;
    492        }
    493
    494        public boolean hasTag(final long l) {
    495            return tags.contains(Integer.valueOf((int) l));
    496        }
    497
    498        @Override
    499        public void pack(final OutputStream outputStream) throws IOException, Pack200Exception {
    500            for (final LayoutElement element : body) {
    501                element.pack(outputStream);
    502            }
    503        }
    504
    505        @Override
    506        public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
    507            for (final LayoutElement element : body) {
    508                element.renumberBci(bciRenumbering, labelsToOffsets);
    509            }
    510        }
    511    }
    512
    513    protected List<AttributeLayoutElement> attributeLayoutElements;
    514
    515    private int[] backwardsCallCounts;
    516
    517    private final CpBands cpBands;
    518
    519    private final AttributeDefinition def;
    520
    521    private boolean usedAtLeastOnce;
    522
    523    // used when parsing
    524    private Integral lastPIntegral;
    525
    526    public NewAttributeBands(final int effort, final CpBands cpBands, final SegmentHeader header,
    527        final AttributeDefinition def) throws IOException {
    528        super(effort, header);
    529        this.def = def;
    530        this.cpBands = cpBands;
    531        parseLayout();
    532    }
    533
    534    public void addAttribute(final NewAttribute attribute) {
    535        usedAtLeastOnce = true;
    536        final InputStream stream = new ByteArrayInputStream(attribute.getBytes());
    537        for (final AttributeLayoutElement attributeLayoutElement : attributeLayoutElements) {
    538            attributeLayoutElement.addAttributeToBand(attribute, stream);
    539        }
    540    }
    541
    542    public String getAttributeName() {
    543        return def.name.getUnderlyingString();
    544    }
    545
    546    /**
    547     * Returns the {@link BHSDCodec} that should be used for the given layout element
    548     *
    549     * @param layoutElement
    550     */
    551    private BHSDCodec getCodec(final String layoutElement) {
    552        if (layoutElement.indexOf('O') >= 0) {
    553            return Codec.BRANCH5;
    554        }
    555        if (layoutElement.indexOf('P') >= 0) {
    556            return Codec.BCI5;
    557        }
    558        if (layoutElement.indexOf('S') >= 0 && layoutElement.indexOf("KS") < 0 //$NON-NLS-1$
    559            && layoutElement.indexOf("RS") < 0) { //$NON-NLS-1$
    560            return Codec.SIGNED5;
    561        }
    562        if (layoutElement.indexOf('B') >= 0) {
    563            return Codec.BYTE1;
    564        }
    565        return Codec.UNSIGNED5;
    566    }
    567
    568    public int getFlagIndex() {
    569        return def.index;
    570    }
    571
    572    /**
    573     * Utility method to get the contents of the given stream, up to the next {@code ]},
    574     * (ignoring pairs of brackets {@code [} and {@code ]})
    575     *
    576     * @param reader
    577     * @return
    578     * @throws IOException If an I/O error occurs.
    579     */
    580    private StringReader getStreamUpToMatchingBracket(final StringReader reader) throws IOException {
    581        final StringBuilder sb = new StringBuilder();
    582        int foundBracket = -1;
    583        while (foundBracket != 0) {
    584            final int read = reader.read();
    585            if (read == -1) {
    586                break;
    587            }
    588                        final char c = (char) read;
    589            if (c == ']') {
    590                foundBracket++;
    591            }
    592            if (c == '[') {
    593                foundBracket--;
    594            }
    595            if (!(foundBracket == 0)) {
    596                sb.append(c);
    597            }
    598        }
    599        return new StringReader(sb.toString());
    600    }
    601
    602    public boolean isUsedAtLeastOnce() {
    603        return usedAtLeastOnce;
    604    }
    605
    606    public int[] numBackwardsCalls() {
    607        return backwardsCallCounts;
    608    }
    609
    610    @Override
    611    public void pack(final OutputStream outputStream) throws IOException, Pack200Exception {
    612        for (final AttributeLayoutElement attributeLayoutElement : attributeLayoutElements) {
    613            attributeLayoutElement.pack(outputStream);
    614        }
    615    }
    616
    617    private void parseLayout() throws IOException {
    618        final String layout = def.layout.getUnderlyingString();
    619        if (attributeLayoutElements == null) {
    620            attributeLayoutElements = new ArrayList<>();
    621            final StringReader reader = new StringReader(layout);
    622            AttributeLayoutElement e;
    623            while ((e = readNextAttributeElement(reader)) != null) {
    624                attributeLayoutElements.add(e);
    625            }
    626            resolveCalls();
    627        }
    628    }
    629
    630    /**
    631     * Read a 'body' section of the layout from the given stream
    632     *
    633     * @param reader
    634     * @return List of LayoutElements
    635     * @throws IOException If an I/O error occurs.
    636     */
    637    private List<LayoutElement> readBody(final StringReader reader) throws IOException {
    638        final List<LayoutElement> layoutElements = new ArrayList<>();
    639        LayoutElement e;
    640        while ((e = readNextLayoutElement(reader)) != null) {
    641            layoutElements.add(e);
    642        }
    643        return layoutElements;
    644    }
    645
    646    private int readInteger(final int i, final InputStream inputStream) {
    647        int result = 0;
    648        for (int j = 0; j < i; j++) {
    649            try {
    650                result = result << 8 | inputStream.read();
    651            } catch (final IOException e) {
    652                throw new UncheckedIOException("Error reading unknown attribute", e);
    653            }
    654        }
    655        // use casting to preserve sign
    656        if (i == 1) {
    657            result = (byte) result;
    658        }
    659        if (i == 2) {
    660            result = (short) result;
    661        }
    662        return result;
    663    }
    664
    665    private AttributeLayoutElement readNextAttributeElement(final StringReader reader) throws IOException {
    666        reader.mark(1);
    667        final int next = reader.read();
    668        if (next == -1) {
    669            return null;
    670        }
    671        if (next == '[') {
    672            return new Callable(readBody(getStreamUpToMatchingBracket(reader)));
    673        }
    674        reader.reset();
    675        return readNextLayoutElement(reader);
    676    }
    677
    678    private LayoutElement readNextLayoutElement(final StringReader reader) throws IOException {
    679        final int nextChar = reader.read();
    680        if (nextChar == -1) {
    681            return null;
    682        }
    683
    684        switch (nextChar) {
    685        // Integrals
    686        case 'B':
    687        case 'H':
    688        case 'I':
    689        case 'V':
    690            return new Integral(new String(new char[] {(char) nextChar}));
    691        case 'S':
    692        case 'F':
    693            return new Integral(new String(new char[] {(char) nextChar, (char) reader.read()}));
    694        case 'P':
    695            reader.mark(1);
    696            if (reader.read() != 'O') {
    697                reader.reset();
    698                lastPIntegral = new Integral("P" + (char) reader.read());
    699                return lastPIntegral;
    700            }
    701            lastPIntegral = new Integral("PO" + (char) reader.read(), lastPIntegral);
    702            return lastPIntegral;
    703        case 'O':
    704            reader.mark(1);
    705            if (reader.read() != 'S') {
    706                reader.reset();
    707                return new Integral("O" + (char) reader.read(), lastPIntegral);
    708            }
    709            return new Integral("OS" + (char) reader.read(), lastPIntegral);
    710
    711            // Replication
    712        case 'N':
    713            final char uint_type = (char) reader.read();
    714            reader.read(); // '['
    715            final String str = readUpToMatchingBracket(reader);
    716            return new Replication("" + uint_type, str);
    717
    718        // Union
    719        case 'T':
    720            String int_type = String.valueOf((char) reader.read());
    721            if (int_type.equals("S")) {
    722                int_type += (char) reader.read();
    723            }
    724            final List<UnionCase> unionCases = new ArrayList<>();
    725            UnionCase c;
    726            while ((c = readNextUnionCase(reader)) != null) {
    727                unionCases.add(c);
    728            }
    729            reader.read(); // '('
    730            reader.read(); // ')'
    731            reader.read(); // '['
    732            List<LayoutElement> body = null;
    733            reader.mark(1);
    734            final char next = (char) reader.read();
    735            if (next != ']') {
    736                reader.reset();
    737                body = readBody(getStreamUpToMatchingBracket(reader));
    738            }
    739            return new Union(int_type, unionCases, body);
    740
    741        // Call
    742        case '(':
    743            final int number = readNumber(reader).intValue();
    744            reader.read(); // ')'
    745            return new Call(number);
    746        // Reference
    747        case 'K':
    748        case 'R':
    749            final StringBuilder string = new StringBuilder("").append((char) nextChar).append((char) reader.read());
    750            final char nxt = (char) reader.read();
    751            string.append(nxt);
    752            if (nxt == 'N') {
    753                string.append((char) reader.read());
    754            }
    755            return new Reference(string.toString());
    756        }
    757        return null;
    758    }
    759
    760    /**
    761     * Read a UnionCase from the stream
    762     *
    763     * @param reader
    764     * @return
    765     * @throws IOException If an I/O error occurs.
    766     */
    767    private UnionCase readNextUnionCase(final StringReader reader) throws IOException {
    768        reader.mark(2);
    769        reader.read(); // '('
    770        final int next = reader.read();
    771        char ch = (char) next;
    772        if (ch == ')' || next == -1) {
    773            reader.reset();
    774            return null;
    775        }
    776        reader.reset();
    777        reader.read(); // '('
    778        final List<Integer> tags = new ArrayList<>();
    779        Integer nextTag;
    780        do {
    781            nextTag = readNumber(reader);
    782            if (nextTag != null) {
    783                tags.add(nextTag);
    784                reader.read(); // ',' or ')'
    785            }
    786        } while (nextTag != null);
    787        reader.read(); // '['
    788        reader.mark(1);
    789        ch = (char) reader.read();
    790        if (ch == ']') {
    791            return new UnionCase(tags);
    792        }
    793        reader.reset();
    794        return new UnionCase(tags, readBody(getStreamUpToMatchingBracket(reader)));
    795    }
    796
    797    /**
    798     * Read a number from the stream and return it
    799     *
    800     * @param stream
    801     * @return
    802     * @throws IOException If an I/O error occurs.
    803     */
    804    private Integer readNumber(final StringReader stream) throws IOException {
    805        stream.mark(1);
    806        final char first = (char) stream.read();
    807        final boolean negative = first == '-';
    808        if (!negative) {
    809            stream.reset();
    810        }
    811        stream.mark(100);
    812        int i;
    813        int length = 0;
    814        while ((i = stream.read()) != -1 && Character.isDigit((char) i)) {
    815            length++;
    816        }
    817        stream.reset();
    818        if (length == 0) {
    819            return null;
    820        }
    821        final char[] digits = new char[length];
    822        final int read = stream.read(digits);
    823        if (read != digits.length) {
    824            throw new IOException("Error reading from the input stream");
    825        }
    826        return Integer.valueOf(Integer.parseInt((negative ? "-" : "") + new String(digits)));
    827    }
    828
    829    /**
    830     * Utility method to get the contents of the given stream, up to the next ']', (ignoring pairs of brackets '[' and
    831     * ']')
    832     *
    833     * @param reader
    834     * @return
    835     * @throws IOException If an I/O error occurs.
    836     */
    837    private String readUpToMatchingBracket(final StringReader reader) throws IOException {
    838        final StringBuilder sb = new StringBuilder();
    839        int foundBracket = -1;
    840        while (foundBracket != 0) {
    841            final int read = reader.read();
    842            if (read == -1) {
    843                break;
    844            }
    845                        final char c = (char) read;
    846            if (c == ']') {
    847                foundBracket++;
    848            }
    849            if (c == '[') {
    850                foundBracket--;
    851            }
    852            if (!(foundBracket == 0)) {
    853                sb.append(c);
    854            }
    855        }
    856        return sb.toString();
    857    }
    858
    859    /**
    860     * Renumber any bytecode indexes or offsets as described in section 5.5.2 of the pack200 specification
    861     *
    862     * @param bciRenumbering TODO
    863     * @param labelsToOffsets TODO
    864     */
    865    public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
    866        for (final AttributeLayoutElement attributeLayoutElement : attributeLayoutElements) {
    867            attributeLayoutElement.renumberBci(bciRenumbering, labelsToOffsets);
    868        }
    869    }
    870
    871    /**
    872     * Resolve calls in the attribute layout and returns the number of backwards callables
    873     *
    874     * @param tokens - the attribute layout as a List of AttributeElements
    875     */
    876    private void resolveCalls() {
    877        for (int i = 0; i < attributeLayoutElements.size(); i++) {
    878            final AttributeLayoutElement element = attributeLayoutElements.get(i);
    879            if (element instanceof Callable) {
    880                final Callable callable = (Callable) element;
    881                final List<LayoutElement> body = callable.body; // Look for calls in the body
    882                for (final LayoutElement layoutElement : body) {
    883                    // Set the callable for each call
    884                    resolveCallsForElement(i, callable, layoutElement);
    885                }
    886            }
    887        }
    888        int backwardsCallableIndex = 0;
    889        for (final AttributeLayoutElement attributeLayoutElement : attributeLayoutElements) {
    890            if (attributeLayoutElement instanceof Callable) {
    891                final Callable callable = (Callable) attributeLayoutElement;
    892                if (callable.isBackwardsCallable) {
    893                    callable.setBackwardsCallableIndex(backwardsCallableIndex);
    894                    backwardsCallableIndex++;
    895                }
    896            }
    897        }
    898        backwardsCallCounts = new int[backwardsCallableIndex];
    899    }
    900
    901    private void resolveCallsForElement(final int i, final Callable currentCallable,
    902        final LayoutElement layoutElement) {
    903        if (layoutElement instanceof Call) {
    904            final Call call = (Call) layoutElement;
    905            int index = call.callableIndex;
    906            if (index == 0) { // Calls the parent callable
    907                call.setCallable(currentCallable);
    908            } else if (index > 0) { // Forwards call
    909                for (int k = i + 1; k < attributeLayoutElements.size(); k++) {
    910                    final AttributeLayoutElement el = attributeLayoutElements.get(k);
    911                    if (el instanceof Callable) {
    912                        index--;
    913                        if (index == 0) {
    914                            call.setCallable((Callable) el);
    915                            break;
    916                        }
    917                    }
    918                }
    919            } else { // Backwards call
    920                for (int k = i - 1; k >= 0; k--) {
    921                    final AttributeLayoutElement el = attributeLayoutElements.get(k);
    922                    if (el instanceof Callable) {
    923                        index++;
    924                        if (index == 0) {
    925                            call.setCallable((Callable) el);
    926                            break;
    927                        }
    928                    }
    929                }
    930            }
    931        } else if (layoutElement instanceof Replication) {
    932            final List<LayoutElement> children = ((Replication) layoutElement).layoutElements;
    933            for (final LayoutElement child : children) {
    934                resolveCallsForElement(i, currentCallable, child);
    935            }
    936        }
    937    }
    938
    939}