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