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