View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.commons.compress.harmony.pack200;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.io.StringReader;
24  import java.io.UncheckedIOException;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.commons.compress.harmony.pack200.AttributeDefinitionBands.AttributeDefinition;
31  import org.apache.commons.compress.utils.ParsingUtils;
32  import org.objectweb.asm.Label;
33  
34  /**
35   * 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
36   * options)
37   */
38  public class NewAttributeBands extends BandSet {
39  
40      /**
41       * An AttributeLayoutElement is a part of an attribute layout and has one or more bands associated with it, which transmit the AttributeElement data for
42       * successive Attributes of this type.
43       */
44      public interface AttributeLayoutElement {
45  
46          void addAttributeToBand(NewAttribute attribute, InputStream inputStream);
47  
48          void pack(OutputStream ouputStream) throws IOException, Pack200Exception;
49  
50          void renumberBci(IntList bciRenumbering, Map<Label, Integer> labelsToOffsets);
51  
52      }
53  
54      public class Call extends LayoutElement {
55  
56          private final int callableIndex;
57          private Callable callable;
58  
59          public Call(final int callableIndex) {
60              this.callableIndex = callableIndex;
61          }
62  
63          @Override
64          public void addAttributeToBand(final NewAttribute attribute, final InputStream inputStream) {
65              callable.addAttributeToBand(attribute, inputStream);
66              if (callableIndex < 1) {
67                  callable.addBackwardsCall();
68              }
69          }
70  
71          public Callable getCallable() {
72              return callable;
73          }
74  
75          public int getCallableIndex() {
76              return callableIndex;
77          }
78  
79          @Override
80          public void pack(final OutputStream outputStream) {
81              // do nothing here as pack will be called on the callable at another time
82          }
83  
84          @Override
85          public void renumberBci(final IntList bciRenumbering, final Map<Label, Integer> labelsToOffsets) {
86              // do nothing here as renumberBci will be called on the callable at another time
87          }
88  
89          public void setCallable(final Callable callable) {
90              this.callable = callable;
91              if (callableIndex < 1) {
92                  callable.setBackwardsCallable();
93              }
94          }
95      }
96  
97      public class Callable implements AttributeLayoutElement {
98  
99          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 }