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