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 * Set 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;
297
298        private boolean nullsAllowed = false;
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}