001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.commons.compress.harmony.unpack200;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.StringReader;
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.List;
027
028import org.apache.commons.compress.harmony.pack200.BHSDCodec;
029import org.apache.commons.compress.harmony.pack200.Codec;
030import org.apache.commons.compress.harmony.pack200.Pack200Exception;
031import org.apache.commons.compress.harmony.unpack200.bytecode.Attribute;
032import org.apache.commons.compress.harmony.unpack200.bytecode.CPClass;
033import org.apache.commons.compress.harmony.unpack200.bytecode.CPDouble;
034import org.apache.commons.compress.harmony.unpack200.bytecode.CPFieldRef;
035import org.apache.commons.compress.harmony.unpack200.bytecode.CPFloat;
036import org.apache.commons.compress.harmony.unpack200.bytecode.CPInteger;
037import org.apache.commons.compress.harmony.unpack200.bytecode.CPInterfaceMethodRef;
038import org.apache.commons.compress.harmony.unpack200.bytecode.CPLong;
039import org.apache.commons.compress.harmony.unpack200.bytecode.CPMethodRef;
040import org.apache.commons.compress.harmony.unpack200.bytecode.CPNameAndType;
041import org.apache.commons.compress.harmony.unpack200.bytecode.CPString;
042import org.apache.commons.compress.harmony.unpack200.bytecode.CPUTF8;
043import org.apache.commons.compress.harmony.unpack200.bytecode.NewAttribute;
044import org.apache.commons.compress.utils.ParsingUtils;
045
046/**
047 * Sets of bands relating to a non-predefined attribute
048 */
049public class NewAttributeBands extends BandSet {
050
051    /**
052     * An AttributeLayoutElement is a part of an attribute layout and has one or more bands associated with it, which transmit the AttributeElement data for
053     * successive Attributes of this type.
054     */
055    private interface AttributeLayoutElement {
056
057        /**
058         * Adds the band data for this element at the given index to the attribute.
059         *
060         * @param index     Index position to add the attribute.
061         * @param attribute The attribute to add.
062         */
063        void addToAttribute(int index, NewAttribute attribute);
064
065        /**
066         * Reads the bands associated with this part of the layout.
067         *
068         * @param in    TODO
069         * @param count TODO
070         * @throws Pack200Exception Bad archive.
071         * @throws IOException      If an I/O error occurs.
072         */
073        void readBands(InputStream in, int count) throws IOException, Pack200Exception;
074
075    }
076
077    public class Call extends LayoutElement {
078
079        private final int callableIndex;
080        private Callable callable;
081
082        public Call(final int callableIndex) {
083            this.callableIndex = callableIndex;
084        }
085
086        @Override
087        public void addToAttribute(final int n, final NewAttribute attribute) {
088            callable.addNextToAttribute(attribute);
089        }
090
091        public Callable getCallable() {
092            return callable;
093        }
094
095        public int getCallableIndex() {
096            return callableIndex;
097        }
098
099        @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}