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