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.unpack200;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.StringReader;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.List;
27  
28  import org.apache.commons.compress.harmony.pack200.BHSDCodec;
29  import org.apache.commons.compress.harmony.pack200.Codec;
30  import org.apache.commons.compress.harmony.pack200.Pack200Exception;
31  import org.apache.commons.compress.harmony.unpack200.bytecode.Attribute;
32  import org.apache.commons.compress.harmony.unpack200.bytecode.CPClass;
33  import org.apache.commons.compress.harmony.unpack200.bytecode.CPDouble;
34  import org.apache.commons.compress.harmony.unpack200.bytecode.CPFieldRef;
35  import org.apache.commons.compress.harmony.unpack200.bytecode.CPFloat;
36  import org.apache.commons.compress.harmony.unpack200.bytecode.CPInteger;
37  import org.apache.commons.compress.harmony.unpack200.bytecode.CPInterfaceMethodRef;
38  import org.apache.commons.compress.harmony.unpack200.bytecode.CPLong;
39  import org.apache.commons.compress.harmony.unpack200.bytecode.CPMethodRef;
40  import org.apache.commons.compress.harmony.unpack200.bytecode.CPNameAndType;
41  import org.apache.commons.compress.harmony.unpack200.bytecode.CPString;
42  import org.apache.commons.compress.harmony.unpack200.bytecode.CPUTF8;
43  import org.apache.commons.compress.harmony.unpack200.bytecode.NewAttribute;
44  import org.apache.commons.compress.utils.ParsingUtils;
45  
46  /**
47   * Sets of bands relating to a non-predefined attribute
48   */
49  public class NewAttributeBands extends BandSet {
50  
51      /**
52       * An AttributeLayoutElement is a part of an attribute layout and has one or more bands associated with it, which transmit the AttributeElement data for
53       * successive Attributes of this type.
54       */
55      private interface AttributeLayoutElement {
56  
57          /**
58           * Adds the band data for this element at the given index to the attribute.
59           *
60           * @param index     Index position to add the attribute.
61           * @param attribute The attribute to add.
62           */
63          void addToAttribute(int index, NewAttribute attribute);
64  
65          /**
66           * Reads the bands associated with this part of the layout.
67           *
68           * @param in    TODO
69           * @param count TODO
70           * @throws Pack200Exception Bad archive.
71           * @throws IOException      If an I/O error occurs.
72           */
73          void readBands(InputStream in, int count) throws IOException, Pack200Exception;
74  
75      }
76  
77      public class Call extends LayoutElement {
78  
79          private final int callableIndex;
80          private Callable callable;
81  
82          public Call(final int callableIndex) {
83              this.callableIndex = callableIndex;
84          }
85  
86          @Override
87          public void addToAttribute(final int n, final NewAttribute attribute) {
88              callable.addNextToAttribute(attribute);
89          }
90  
91          public Callable getCallable() {
92              return callable;
93          }
94  
95          public int getCallableIndex() {
96              return callableIndex;
97          }
98  
99          @Override
100         public void readBands(final InputStream in, final int count) {
101             /*
102              * We don't read anything here, but we need to pass the extra count to the callable if it's a forwards call. For backwards callables the count is
103              * transmitted directly in the attribute bands and so it is added later.
104              */
105             if (callableIndex > 0) {
106                 callable.addCount(count);
107             }
108         }
109 
110         public void setCallable(final Callable callable) {
111             this.callable = callable;
112             if (callableIndex < 1) {
113                 callable.setBackwardsCallable();
114             }
115         }
116     }
117 
118     public static class Callable implements AttributeLayoutElement {
119 
120         private final List<LayoutElement> body;
121 
122         private boolean isBackwardsCallable;
123 
124         private boolean isFirstCallable;
125 
126         private int count;
127 
128         private int index;
129 
130         public Callable(final List<LayoutElement> body) {
131             this.body = body;
132         }
133 
134         /**
135          * Adds the count of a call to this callable (ie the number of calls)
136          *
137          * @param count TODO
138          */
139         public void addCount(final int count) {
140             this.count += count;
141         }
142 
143         /**
144          * Used by calls when adding band contents to attributes, so they don't have to keep track of the internal index of the callable.
145          *
146          * @param attribute TODO
147          */
148         public void addNextToAttribute(final NewAttribute attribute) {
149             for (final LayoutElement element : body) {
150                 element.addToAttribute(index, attribute);
151             }
152             index++;
153         }
154 
155         @Override
156         public void addToAttribute(final int n, final NewAttribute attribute) {
157             if (isFirstCallable) {
158                 // Ignore n because bands also contain element parts from calls
159                 for (final LayoutElement element : body) {
160                     element.addToAttribute(index, attribute);
161                 }
162                 index++;
163             }
164         }
165 
166         public List<LayoutElement> getBody() {
167             return body;
168         }
169 
170         public boolean isBackwardsCallable() {
171             return isBackwardsCallable;
172         }
173 
174         @Override
175         public void readBands(final InputStream in, int count) throws IOException, Pack200Exception {
176             if (isFirstCallable) {
177                 count += this.count;
178             } else {
179                 count = this.count;
180             }
181             for (final LayoutElement element : body) {
182                 element.readBands(in, count);
183             }
184         }
185 
186         /**
187          * Tells this Callable that it is a backwards callable
188          */
189         public void setBackwardsCallable() {
190             this.isBackwardsCallable = true;
191         }
192 
193         public void setFirstCallable(final boolean isFirstCallable) {
194             this.isFirstCallable = isFirstCallable;
195         }
196     }
197 
198     public class Integral extends LayoutElement {
199 
200         private final String tag;
201 
202         private int[] band;
203 
204         public Integral(final String tag) {
205             this.tag = tag;
206         }
207 
208         @Override
209         public void addToAttribute(final int n, final NewAttribute attribute) {
210             int value = band[n];
211             switch (tag) {
212             case "B":
213             case "FB":
214                 attribute.addInteger(1, value);
215                 break;
216             case "SB":
217                 attribute.addInteger(1, (byte) value);
218                 break;
219             case "H":
220             case "FH":
221                 attribute.addInteger(2, value);
222                 break;
223             case "SH":
224                 attribute.addInteger(2, (short) value);
225                 break;
226             case "I":
227             case "FI":
228             case "SI":
229                 attribute.addInteger(4, value);
230                 break;
231             case "V":
232             case "FV":
233             case "SV":
234                 break;
235             default:
236                 if (tag.startsWith("PO")) {
237                     final char uintType = tag.substring(2).toCharArray()[0];
238                     final int length = getLength(uintType);
239                     attribute.addBCOffset(length, value);
240                 } else if (tag.startsWith("P")) {
241                     final char uintType = tag.substring(1).toCharArray()[0];
242                     final int length = getLength(uintType);
243                     attribute.addBCIndex(length, value);
244                 } else if (tag.startsWith("OS")) {
245                     final char uintType = tag.substring(2).toCharArray()[0];
246                     final int length = getLength(uintType);
247                     switch (length) {
248                     case 1:
249                         value = (byte) value;
250                         break;
251                     case 2:
252                         value = (short) value;
253                         break;
254                     case 4:
255                         value = value;
256                         break;
257                     default:
258                         break;
259                     }
260                     attribute.addBCLength(length, value);
261                 } else if (tag.startsWith("O")) {
262                     final char uintType = tag.substring(1).toCharArray()[0];
263                     final int length = getLength(uintType);
264                     attribute.addBCLength(length, value);
265                 }
266                 break;
267             }
268         }
269 
270         public String getTag() {
271             return tag;
272         }
273 
274         int getValue(final int index) {
275             return band[index];
276         }
277 
278         @Override
279         public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
280             band = decodeBandInt(attributeLayout.getName() + "_" + tag, in, getCodec(tag), count);
281         }
282 
283     }
284 
285     private abstract static class LayoutElement implements AttributeLayoutElement {
286 
287         protected int getLength(final char uintType) {
288             int length = 0;
289             switch (uintType) {
290             case 'B':
291                 length = 1;
292                 break;
293             case 'H':
294                 length = 2;
295                 break;
296             case 'I':
297                 length = 4;
298                 break;
299             case 'V':
300                 length = 0;
301                 break;
302             }
303             return length;
304         }
305     }
306 
307     /**
308      * Constant Pool Reference
309      */
310     public class Reference extends LayoutElement {
311 
312         private final String tag;
313 
314         private Object band;
315 
316         private final int length;
317 
318         public Reference(final String tag) {
319             this.tag = tag;
320             length = getLength(tag.charAt(tag.length() - 1));
321         }
322 
323         @Override
324         public void addToAttribute(final int n, final NewAttribute attribute) {
325             if (tag.startsWith("KI")) { // Integer
326                 attribute.addToBody(length, ((CPInteger[]) band)[n]);
327             } else if (tag.startsWith("KJ")) { // Long
328                 attribute.addToBody(length, ((CPLong[]) band)[n]);
329             } else if (tag.startsWith("KF")) { // Float
330                 attribute.addToBody(length, ((CPFloat[]) band)[n]);
331             } else if (tag.startsWith("KD")) { // Double
332                 attribute.addToBody(length, ((CPDouble[]) band)[n]);
333             } else if (tag.startsWith("KS")) { // String
334                 attribute.addToBody(length, ((CPString[]) band)[n]);
335             } else if (tag.startsWith("RC")) { // Class
336                 attribute.addToBody(length, ((CPClass[]) band)[n]);
337             } else if (tag.startsWith("RS")) { // Signature
338                 attribute.addToBody(length, ((CPUTF8[]) band)[n]);
339             } else if (tag.startsWith("RD")) { // Descriptor
340                 attribute.addToBody(length, ((CPNameAndType[]) band)[n]);
341             } else if (tag.startsWith("RF")) { // Field Reference
342                 attribute.addToBody(length, ((CPFieldRef[]) band)[n]);
343             } else if (tag.startsWith("RM")) { // Method Reference
344                 attribute.addToBody(length, ((CPMethodRef[]) band)[n]);
345             } else if (tag.startsWith("RI")) { // Interface Method Reference
346                 attribute.addToBody(length, ((CPInterfaceMethodRef[]) band)[n]);
347             } else if (tag.startsWith("RU")) { // UTF8 String
348                 attribute.addToBody(length, ((CPUTF8[]) band)[n]);
349             }
350         }
351 
352         public String getTag() {
353             return tag;
354         }
355 
356         @Override
357         public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
358             if (tag.startsWith("KI")) { // Integer
359                 band = parseCPIntReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
360             } else if (tag.startsWith("KJ")) { // Long
361                 band = parseCPLongReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
362             } else if (tag.startsWith("KF")) { // Float
363                 band = parseCPFloatReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
364             } else if (tag.startsWith("KD")) { // Double
365                 band = parseCPDoubleReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
366             } else if (tag.startsWith("KS")) { // String
367                 band = parseCPStringReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
368             } else if (tag.startsWith("RC")) { // Class
369                 band = parseCPClassReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
370             } else if (tag.startsWith("RS")) { // Signature
371                 band = parseCPSignatureReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
372             } else if (tag.startsWith("RD")) { // Descriptor
373                 band = parseCPDescriptorReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
374             } else if (tag.startsWith("RF")) { // Field Reference
375                 band = parseCPFieldRefReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
376             } else if (tag.startsWith("RM")) { // Method Reference
377                 band = parseCPMethodRefReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
378             } else if (tag.startsWith("RI")) { // Interface Method Reference
379                 band = parseCPInterfaceMethodRefReferences(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
380             } else if (tag.startsWith("RU")) { // UTF8 String
381                 band = parseCPUTF8References(attributeLayout.getName(), in, Codec.UNSIGNED5, count);
382             }
383         }
384 
385     }
386 
387     /**
388      * A replication is an array of layout elements, with an associated count
389      */
390     public class Replication extends LayoutElement {
391 
392         private final Integral countElement;
393 
394         private final List<LayoutElement> layoutElements = new ArrayList<>();
395 
396         public Replication(final String tag, final String contents) throws IOException {
397             this.countElement = new Integral(tag);
398             final StringReader stream = new StringReader(contents);
399             LayoutElement e;
400             while ((e = readNextLayoutElement(stream)) != null) {
401                 layoutElements.add(e);
402             }
403         }
404 
405         @Override
406         public void addToAttribute(final int index, final NewAttribute attribute) {
407             // Add the count value
408             countElement.addToAttribute(index, attribute);
409 
410             // Add the corresponding array values
411             int offset = 0;
412             for (int i = 0; i < index; i++) {
413                 offset += countElement.getValue(i);
414             }
415             final long numElements = countElement.getValue(index);
416             for (int i = offset; i < offset + numElements; i++) {
417                 for (final LayoutElement layoutElement : layoutElements) {
418                     layoutElement.addToAttribute(i, attribute);
419                 }
420             }
421         }
422 
423         public Integral getCountElement() {
424             return countElement;
425         }
426 
427         public List<LayoutElement> getLayoutElements() {
428             return layoutElements;
429         }
430 
431         @Override
432         public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
433             countElement.readBands(in, count);
434             int arrayCount = 0;
435             for (int i = 0; i < count; i++) {
436                 arrayCount += countElement.getValue(i);
437             }
438             for (final LayoutElement layoutElement : layoutElements) {
439                 layoutElement.readBands(in, arrayCount);
440             }
441         }
442     }
443 
444     /**
445      * A Union is a type of layout element where the tag value acts as a selector for one of the union cases
446      */
447     public class Union extends LayoutElement {
448 
449         private final Integral unionTag;
450         private final List<UnionCase> unionCases;
451         private final List<LayoutElement> defaultCaseBody;
452         private int[] caseCounts;
453         private int defaultCount;
454 
455         public Union(final String tag, final List<UnionCase> unionCases, final List<LayoutElement> body) {
456             this.unionTag = new Integral(tag);
457             this.unionCases = unionCases;
458             this.defaultCaseBody = body;
459         }
460 
461         @Override
462         public void addToAttribute(final int n, final NewAttribute attribute) {
463             unionTag.addToAttribute(n, attribute);
464             int offset = 0;
465             final int[] tagBand = unionTag.band;
466             final int tag = unionTag.getValue(n);
467             boolean defaultCase = true;
468             for (final UnionCase unionCase : unionCases) {
469                 if (unionCase.hasTag(tag)) {
470                     defaultCase = false;
471                     for (int j = 0; j < n; j++) {
472                         if (unionCase.hasTag(tagBand[j])) {
473                             offset++;
474                         }
475                     }
476                     unionCase.addToAttribute(offset, attribute);
477                 }
478             }
479             if (defaultCase) {
480                 // default case
481                 int defaultOffset = 0;
482                 for (int j = 0; j < n; j++) {
483                     boolean found = false;
484                     for (final UnionCase unionCase : unionCases) {
485                         if (unionCase.hasTag(tagBand[j])) {
486                             found = true;
487                         }
488                     }
489                     if (!found) {
490                         defaultOffset++;
491                     }
492                 }
493                 if (defaultCaseBody != null) {
494                     for (final LayoutElement element : defaultCaseBody) {
495                         element.addToAttribute(defaultOffset, attribute);
496                     }
497                 }
498             }
499         }
500 
501         public List<LayoutElement> getDefaultCaseBody() {
502             return defaultCaseBody;
503         }
504 
505         public List<UnionCase> getUnionCases() {
506             return unionCases;
507         }
508 
509         public Integral getUnionTag() {
510             return unionTag;
511         }
512 
513         @Override
514         public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
515             unionTag.readBands(in, count);
516             final int[] values = unionTag.band;
517             // Count the band size for each union case then read the bands
518             caseCounts = new int[unionCases.size()];
519             for (int i = 0; i < caseCounts.length; i++) {
520                 final UnionCase unionCase = unionCases.get(i);
521                 for (final int value : values) {
522                     if (unionCase.hasTag(value)) {
523                         caseCounts[i]++;
524                     }
525                 }
526                 unionCase.readBands(in, caseCounts[i]);
527             }
528             // Count number of default cases then read the default bands
529             for (final int value : values) {
530                 boolean found = false;
531                 for (final UnionCase unionCase : unionCases) {
532                     if (unionCase.hasTag(value)) {
533                         found = true;
534                     }
535                 }
536                 if (!found) {
537                     defaultCount++;
538                 }
539             }
540             if (defaultCaseBody != null) {
541                 for (final LayoutElement element : defaultCaseBody) {
542                     element.readBands(in, defaultCount);
543                 }
544             }
545         }
546 
547     }
548 
549     /**
550      * A Union case
551      */
552     public class UnionCase extends LayoutElement {
553 
554         private List<LayoutElement> body;
555 
556         private final List<Integer> tags;
557 
558         public UnionCase(final List<Integer> tags) {
559             this.tags = tags;
560         }
561 
562         public UnionCase(final List<Integer> tags, final List<LayoutElement> body) {
563             this.tags = tags;
564             this.body = body;
565         }
566 
567         @Override
568         public void addToAttribute(final int index, final NewAttribute attribute) {
569             if (body != null) {
570                 for (final LayoutElement element : body) {
571                     element.addToAttribute(index, attribute);
572                 }
573             }
574         }
575 
576         public List<LayoutElement> getBody() {
577             return body == null ? Collections.EMPTY_LIST : body;
578         }
579 
580         public boolean hasTag(final int i) {
581             return tags.contains(Integer.valueOf(i));
582         }
583 
584         public boolean hasTag(final long l) {
585             return tags.contains(Integer.valueOf((int) l));
586         }
587 
588         @Override
589         public void readBands(final InputStream in, final int count) throws IOException, Pack200Exception {
590             if (body != null) {
591                 for (final LayoutElement element : body) {
592                     element.readBands(in, count);
593                 }
594             }
595         }
596     }
597 
598     private final AttributeLayout attributeLayout;
599 
600     private int backwardsCallCount;
601 
602     protected List<AttributeLayoutElement> attributeLayoutElements;
603 
604     public NewAttributeBands(final Segment segment, final AttributeLayout attributeLayout) throws IOException {
605         super(segment);
606         this.attributeLayout = attributeLayout;
607         parseLayout();
608         attributeLayout.setBackwardsCallCount(backwardsCallCount);
609     }
610 
611     public int getBackwardsCallCount() {
612         return backwardsCallCount;
613     }
614 
615     /**
616      * Returns the {@link BHSDCodec} that should be used for the given layout element.
617      *
618      * @param layoutElement TODO
619      * @return the {@link BHSDCodec} that should be used for the given layout element.
620      */
621     public BHSDCodec getCodec(final String layoutElement) {
622         if (layoutElement.indexOf('O') >= 0) {
623             return Codec.BRANCH5;
624         }
625         if (layoutElement.indexOf('P') >= 0) {
626             return Codec.BCI5;
627         }
628         if (layoutElement.indexOf('S') >= 0 && !layoutElement.contains("KS") //$NON-NLS-1$
629                 && !layoutElement.contains("RS")) { //$NON-NLS-1$
630             return Codec.SIGNED5;
631         }
632         if (layoutElement.indexOf('B') >= 0) {
633             return Codec.BYTE1;
634         }
635         return Codec.UNSIGNED5;
636     }
637 
638     /**
639      * Gets one attribute at the given index from the various bands. The correct bands must have already been read in.
640      *
641      * @param index    TODO
642      * @param elements TODO
643      * @return attribute at the given index.
644      */
645     private Attribute getOneAttribute(final int index, final List<AttributeLayoutElement> elements) {
646         final NewAttribute attribute = new NewAttribute(segment.getCpBands().cpUTF8Value(attributeLayout.getName()), attributeLayout.getIndex());
647         for (final AttributeLayoutElement element : elements) {
648             element.addToAttribute(index, attribute);
649         }
650         return attribute;
651     }
652 
653     /**
654      * Utility method to get the contents of the given stream, up to the next {@code ]}, (ignoring pairs of brackets {@code [} and {@code ]})
655      *
656      * @param stream
657      * @return
658      * @throws IOException If an I/O error occurs.
659      */
660     private StringReader getStreamUpToMatchingBracket(final StringReader stream) throws IOException {
661         final StringBuilder sb = new StringBuilder();
662         int foundBracket = -1;
663         while (foundBracket != 0) {
664             final int read = stream.read();
665             if (read == -1) {
666                 break;
667             }
668             final char c = (char) read;
669             if (c == ']') {
670                 foundBracket++;
671             }
672             if (c == '[') {
673                 foundBracket--;
674             }
675             if (!(foundBracket == 0)) {
676                 sb.append(c);
677             }
678         }
679         return new StringReader(sb.toString());
680     }
681 
682     /**
683      * Parse the bands relating to this AttributeLayout and return the correct class file attributes as a List of {@link Attribute}.
684      *
685      * @param in              parse source.
686      * @param occurrenceCount TODO
687      * @return Class file attributes as a List of {@link Attribute}.
688      * @throws IOException      If an I/O error occurs.
689      * @throws Pack200Exception If a Pack200 semantic error occurs.
690      */
691     public List<Attribute> parseAttributes(final InputStream in, final int occurrenceCount) throws IOException, Pack200Exception {
692         for (final AttributeLayoutElement element : attributeLayoutElements) {
693             element.readBands(in, occurrenceCount);
694         }
695 
696         final List<Attribute> attributes = new ArrayList<>(occurrenceCount);
697         for (int i = 0; i < occurrenceCount; i++) {
698             attributes.add(getOneAttribute(i, attributeLayoutElements));
699         }
700         return attributes;
701     }
702 
703     /**
704      * Tokenize the layout into AttributeElements
705      *
706      * @throws IOException If an I/O error occurs.
707      */
708     private void parseLayout() throws IOException {
709         if (attributeLayoutElements == null) {
710             attributeLayoutElements = new ArrayList<>();
711             final StringReader stream = new StringReader(attributeLayout.getLayout());
712             AttributeLayoutElement e;
713             while ((e = readNextAttributeElement(stream)) != null) {
714                 attributeLayoutElements.add(e);
715             }
716             resolveCalls();
717         }
718     }
719 
720     /*
721      * (non-Javadoc)
722      *
723      * @see org.apache.commons.compress.harmony.unpack200.BandSet#unpack(java.io.InputStream)
724      */
725     @Override
726     public void read(final InputStream in) throws IOException, Pack200Exception {
727         // does nothing - use parseAttributes instead
728     }
729 
730     /**
731      * Reads a 'body' section of the layout from the given stream
732      *
733      * @param stream
734      * @return List of LayoutElements
735      * @throws IOException If an I/O error occurs.
736      */
737     private List<LayoutElement> readBody(final StringReader stream) throws IOException {
738         final List<LayoutElement> layoutElements = new ArrayList<>();
739         LayoutElement e;
740         while ((e = readNextLayoutElement(stream)) != null) {
741             layoutElements.add(e);
742         }
743         return layoutElements;
744     }
745 
746     private AttributeLayoutElement readNextAttributeElement(final StringReader stream) throws IOException {
747         stream.mark(1);
748         final int next = stream.read();
749         if (next == -1) {
750             return null;
751         }
752         if (next == '[') {
753             return new Callable(readBody(getStreamUpToMatchingBracket(stream)));
754         }
755         stream.reset();
756         return readNextLayoutElement(stream);
757     }
758 
759     private LayoutElement readNextLayoutElement(final StringReader stream) throws IOException {
760         final int nextChar = stream.read();
761         if (nextChar == -1) {
762             return null;
763         }
764         switch (nextChar) {
765         // Integrals
766         case 'B':
767         case 'H':
768         case 'I':
769         case 'V':
770             return new Integral(new String(new char[] { (char) nextChar }));
771         case 'S':
772         case 'F':
773             return new Integral(new String(new char[] { (char) nextChar, (char) stream.read() }));
774         case 'P':
775             stream.mark(1);
776             if (stream.read() != 'O') {
777                 stream.reset();
778                 return new Integral("P" + (char) stream.read());
779             }
780             return new Integral("PO" + (char) stream.read());
781         case 'O':
782             stream.mark(1);
783             if (stream.read() != 'S') {
784                 stream.reset();
785                 return new Integral("O" + (char) stream.read());
786             }
787             return new Integral("OS" + (char) stream.read());
788 
789         // Replication
790         case 'N':
791             final char uintType = (char) stream.read();
792             stream.read(); // '['
793             final String str = readUpToMatchingBracket(stream);
794             return new Replication("" + uintType, str);
795 
796         // Union
797         case 'T':
798             String intType = "" + (char) stream.read();
799             if (intType.equals("S")) {
800                 intType += (char) stream.read();
801             }
802             final List<UnionCase> unionCases = new ArrayList<>();
803             UnionCase c;
804             while ((c = readNextUnionCase(stream)) != null) {
805                 unionCases.add(c);
806             }
807             stream.read(); // '('
808             stream.read(); // ')'
809             stream.read(); // '['
810             List<LayoutElement> body = null;
811             stream.mark(1);
812             final char next = (char) stream.read();
813             if (next != ']') {
814                 stream.reset();
815                 body = readBody(getStreamUpToMatchingBracket(stream));
816             }
817             return new Union(intType, unionCases, body);
818 
819         // Call
820         case '(':
821             final int number = readNumber(stream).intValue();
822             stream.read(); // ')'
823             return new Call(number);
824         // Reference
825         case 'K':
826         case 'R':
827             final StringBuilder string = new StringBuilder("").append((char) nextChar).append((char) stream.read());
828             final char nxt = (char) stream.read();
829             string.append(nxt);
830             if (nxt == 'N') {
831                 string.append((char) stream.read());
832             }
833             return new Reference(string.toString());
834         }
835         return null;
836     }
837 
838     /**
839      * Reads a UnionCase from the stream.
840      *
841      * @param stream source stream.
842      * @return A UnionCase from the stream.
843      * @throws IOException If an I/O error occurs.
844      */
845     private UnionCase readNextUnionCase(final StringReader stream) throws IOException {
846         stream.mark(2);
847         stream.read(); // '('
848         final int next = stream.read();
849         char ch = (char) next;
850         if (ch == ')' || next == -1) {
851             stream.reset();
852             return null;
853         }
854         stream.reset();
855         stream.read(); // '('
856         final List<Integer> tags = new ArrayList<>();
857         Integer nextTag;
858         do {
859             nextTag = readNumber(stream);
860             if (nextTag != null) {
861                 tags.add(nextTag);
862                 stream.read(); // ',' or ')'
863             }
864         } while (nextTag != null);
865         stream.read(); // '['
866         stream.mark(1);
867         ch = (char) stream.read();
868         if (ch == ']') {
869             return new UnionCase(tags);
870         }
871         stream.reset();
872         return new UnionCase(tags, readBody(getStreamUpToMatchingBracket(stream)));
873     }
874 
875     /**
876      * Reads a number from the stream and return it
877      *
878      * @param stream
879      * @return
880      * @throws IOException If an I/O error occurs.
881      */
882     private Integer readNumber(final StringReader stream) throws IOException {
883         stream.mark(1);
884         final char first = (char) stream.read();
885         final boolean negative = first == '-';
886         if (!negative) {
887             stream.reset();
888         }
889         stream.mark(100);
890         int i;
891         int length = 0;
892         while ((i = stream.read()) != -1 && Character.isDigit((char) i)) {
893             length++;
894         }
895         stream.reset();
896         if (length == 0) {
897             return null;
898         }
899         final char[] digits = new char[length];
900         final int read = stream.read(digits);
901         if (read != digits.length) {
902             throw new IOException("Error reading from the input stream");
903         }
904         return ParsingUtils.parseIntValue((negative ? "-" : "") + new String(digits));
905     }
906 
907     /**
908      * Gets the contents of the given stream, up to the next {@code ]}, (ignoring pairs of brackets {@code [} and {@code ]})
909      *
910      * @param stream input stream.
911      * @return the contents of the given stream.
912      * @throws IOException If an I/O error occurs.
913      */
914     private String readUpToMatchingBracket(final StringReader stream) throws IOException {
915         final StringBuilder sb = new StringBuilder();
916         int foundBracket = -1;
917         while (foundBracket != 0) {
918             final int read = stream.read();
919             if (read == -1) {
920                 break;
921             }
922             final char c = (char) read;
923             if (c == ']') {
924                 foundBracket++;
925             }
926             if (c == '[') {
927                 foundBracket--;
928             }
929             if (!(foundBracket == 0)) {
930                 sb.append(c);
931             }
932         }
933         return sb.toString();
934     }
935 
936     /**
937      * Resolve calls in the attribute layout and returns the number of backwards calls
938      */
939     private void resolveCalls() {
940         int backwardsCalls = 0;
941         for (int i = 0; i < attributeLayoutElements.size(); i++) {
942             final AttributeLayoutElement element = attributeLayoutElements.get(i);
943             if (element instanceof Callable) {
944                 final Callable callable = (Callable) element;
945                 if (i == 0) {
946                     callable.setFirstCallable(true);
947                 }
948                 // Look for calls in the body
949                 for (final LayoutElement layoutElement : callable.body) {
950                     // Set the callable for each call
951                     backwardsCalls += resolveCallsForElement(i, callable, layoutElement);
952                 }
953             }
954         }
955         backwardsCallCount = backwardsCalls;
956     }
957 
958     private int resolveCallsForElement(final int i, final Callable currentCallable, final LayoutElement layoutElement) {
959         int backwardsCalls = 0;
960         if (layoutElement instanceof Call) {
961             final Call call = (Call) layoutElement;
962             int index = call.callableIndex;
963             if (index == 0) { // Calls the parent callable
964                 backwardsCalls++;
965                 call.setCallable(currentCallable);
966             } else if (index > 0) { // Forwards call
967                 for (int k = i + 1; k < attributeLayoutElements.size(); k++) {
968                     final AttributeLayoutElement el = attributeLayoutElements.get(k);
969                     if (el instanceof Callable) {
970                         index--;
971                         if (index == 0) {
972                             call.setCallable((Callable) el);
973                             break;
974                         }
975                     }
976                 }
977             } else { // Backwards call
978                 backwardsCalls++;
979                 for (int k = i - 1; k >= 0; k--) {
980                     final AttributeLayoutElement el = attributeLayoutElements.get(k);
981                     if (el instanceof Callable) {
982                         index++;
983                         if (index == 0) {
984                             call.setCallable((Callable) el);
985                             break;
986                         }
987                     }
988                 }
989             }
990         } else if (layoutElement instanceof Replication) {
991             final List<LayoutElement> children = ((Replication) layoutElement).layoutElements;
992             for (final LayoutElement child : children) {
993                 backwardsCalls += resolveCallsForElement(i, currentCallable, child);
994             }
995         }
996         return backwardsCalls;
997     }
998 
999     /**
1000      * Once the attribute bands have been read the callables can be informed about the number of times each is subject to a backwards call. This method is used
1001      * to set this information.
1002      *
1003      * @param backwardsCalls one int for each backwards callable, which contains the number of times that callable is subject to a backwards call.
1004      * @throws IOException If an I/O error occurs.
1005      */
1006     public void setBackwardsCalls(final int[] backwardsCalls) throws IOException {
1007         int index = 0;
1008         parseLayout();
1009         for (final AttributeLayoutElement element : attributeLayoutElements) {
1010             if (element instanceof Callable && ((Callable) element).isBackwardsCallable()) {
1011                 ((Callable) element).addCount(backwardsCalls[index]);
1012                 index++;
1013             }
1014         }
1015     }
1016 
1017     @Override
1018     public void unpack() throws IOException, Pack200Exception {
1019 
1020     }
1021 
1022 }