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}