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