001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one or more
003 *  contributor license agreements.  See the NOTICE file distributed with
004 *  this work for additional information regarding copyright ownership.
005 *  The ASF licenses this file to You under the Apache License, Version 2.0
006 *  (the "License"); you may not use this file except in compliance with
007 *  the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 */
017package org.apache.commons.compress.harmony.unpack200;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.io.StringReader;
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.List;
025
026import org.apache.commons.compress.harmony.pack200.BHSDCodec;
027import org.apache.commons.compress.harmony.pack200.Codec;
028import org.apache.commons.compress.harmony.pack200.Pack200Exception;
029import org.apache.commons.compress.harmony.unpack200.bytecode.Attribute;
030import org.apache.commons.compress.harmony.unpack200.bytecode.CPClass;
031import org.apache.commons.compress.harmony.unpack200.bytecode.CPDouble;
032import org.apache.commons.compress.harmony.unpack200.bytecode.CPFieldRef;
033import org.apache.commons.compress.harmony.unpack200.bytecode.CPFloat;
034import org.apache.commons.compress.harmony.unpack200.bytecode.CPInteger;
035import org.apache.commons.compress.harmony.unpack200.bytecode.CPInterfaceMethodRef;
036import org.apache.commons.compress.harmony.unpack200.bytecode.CPLong;
037import org.apache.commons.compress.harmony.unpack200.bytecode.CPMethodRef;
038import org.apache.commons.compress.harmony.unpack200.bytecode.CPNameAndType;
039import org.apache.commons.compress.harmony.unpack200.bytecode.CPString;
040import org.apache.commons.compress.harmony.unpack200.bytecode.CPUTF8;
041import org.apache.commons.compress.harmony.unpack200.bytecode.NewAttribute;
042
043/**
044 * Sets of bands relating to a non-predefined attribute
045 */
046public class NewAttributeBands extends BandSet {
047
048    /**
049     * An AttributeLayoutElement is a part of an attribute layout and has one or more bands associated with it, which
050     * transmit the AttributeElement data for successive Attributes of this type.
051     */
052    private interface AttributeLayoutElement {
053
054        /**
055         * Adds the band data for this element at the given index to the attribute.
056         *
057         * @param index Index position to add the attribute.
058         * @param attribute The attribute to add.
059         */
060        void addToAttribute(int index, NewAttribute attribute);
061
062        /**
063         * Read the bands associated with this part of the layout.
064         *
065         * @param in TODO
066         * @param count TODO
067         * @throws Pack200Exception Bad archive.
068         * @throws IOException If an I/O error occurs.
069         */
070        void readBands(InputStream in, int count) throws IOException, Pack200Exception;
071
072    }
073
074    public class Call extends LayoutElement {
075
076        private final int callableIndex;
077        private Callable callable;
078
079        public Call(final int callableIndex) {
080            this.callableIndex = callableIndex;
081        }
082
083        @Override
084        public void addToAttribute(final int n, final NewAttribute attribute) {
085            callable.addNextToAttribute(attribute);
086        }
087
088        public Callable getCallable() {
089            return callable;
090        }
091
092        public int getCallableIndex() {
093            return callableIndex;
094        }
095
096        @Override
097        public void readBands(final InputStream in, final int count) {
098            /*
099             * We don't read anything here, but we need to pass the extra count to the callable if it's a forwards call.
100             * For backwards callables the count is transmitted directly in the attribute bands and so it is added
101             * 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        public Callable(final List<LayoutElement> body) {
128            this.body = body;
129        }
130
131        /**
132         * Adds the count of a call to this callable (ie the number of calls)
133         *
134         * @param count TODO
135         */
136        public void addCount(final int count) {
137            this.count += count;
138        }
139
140        /**
141         * Used by calls when adding band contents to attributes, so they don't have to keep track of the internal index
142         * 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.indexOf("KS") < 0 //$NON-NLS-1$
613            && layoutElement.indexOf("RS") < 0) { //$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()),
631            attributeLayout.getIndex());
632        for (final AttributeLayoutElement element : elements) {
633            element.addToAttribute(index, attribute);
634        }
635        return attribute;
636    }
637
638    /**
639     * Utility method to get the contents of the given stream, up to the next {@code ]},
640     * (ignoring pairs of brackets {@code [} and {@code ]})
641     *
642     * @param stream
643     * @return
644     * @throws IOException If an I/O error occurs.
645     */
646    private StringReader getStreamUpToMatchingBracket(final StringReader stream) throws IOException {
647        final StringBuilder sb = new StringBuilder();
648        int foundBracket = -1;
649        while (foundBracket != 0) {
650            final int read = stream.read();
651            if (read == -1) {
652                break;
653            }
654                        final char c = (char) read;
655            if (c == ']') {
656                foundBracket++;
657            }
658            if (c == '[') {
659                foundBracket--;
660            }
661            if (!(foundBracket == 0)) {
662                sb.append(c);
663            }
664        }
665        return new StringReader(sb.toString());
666    }
667
668    /**
669     * Parse the bands relating to this AttributeLayout and return the correct class file attributes as a List of
670     * {@link Attribute}.
671     *
672     * @param in parse source.
673     * @param occurrenceCount TODO
674     * @return Class file attributes as a List of {@link Attribute}.
675     * @throws IOException If an I/O error occurs.
676     * @throws Pack200Exception TODO
677     */
678    public List<Attribute> parseAttributes(final InputStream in, final int occurrenceCount) throws IOException, Pack200Exception {
679        for (final AttributeLayoutElement element : attributeLayoutElements) {
680            element.readBands(in, occurrenceCount);
681        }
682
683        final List<Attribute> attributes = new ArrayList<>(occurrenceCount);
684        for (int i = 0; i < occurrenceCount; i++) {
685            attributes.add(getOneAttribute(i, attributeLayoutElements));
686        }
687        return attributes;
688    }
689
690    /**
691     * Tokenize the layout into AttributeElements
692     *
693     * @throws IOException If an I/O error occurs.
694     */
695    private void parseLayout() throws IOException {
696        if (attributeLayoutElements == null) {
697            attributeLayoutElements = new ArrayList<>();
698            final StringReader stream = new StringReader(attributeLayout.getLayout());
699            AttributeLayoutElement e;
700            while ((e = readNextAttributeElement(stream)) != null) {
701                attributeLayoutElements.add(e);
702            }
703            resolveCalls();
704        }
705    }
706
707    /*
708     * (non-Javadoc)
709     *
710     * @see org.apache.commons.compress.harmony.unpack200.BandSet#unpack(java.io.InputStream)
711     */
712    @Override
713    public void read(final InputStream in) throws IOException, Pack200Exception {
714        // does nothing - use parseAttributes instead
715    }
716
717    /**
718     * Read a 'body' section of the layout from the given stream
719     *
720     * @param stream
721     * @return List of LayoutElements
722     * @throws IOException If an I/O error occurs.
723     */
724    private List<LayoutElement> readBody(final StringReader stream) throws IOException {
725        final List<LayoutElement> layoutElements = new ArrayList<>();
726        LayoutElement e;
727        while ((e = readNextLayoutElement(stream)) != null) {
728            layoutElements.add(e);
729        }
730        return layoutElements;
731    }
732
733    private AttributeLayoutElement readNextAttributeElement(final StringReader stream) throws IOException {
734        stream.mark(1);
735        final int next = stream.read();
736        if (next == -1) {
737            return null;
738        }
739        if (next == '[') {
740            return new Callable(readBody(getStreamUpToMatchingBracket(stream)));
741        }
742        stream.reset();
743        return readNextLayoutElement(stream);
744    }
745
746    private LayoutElement readNextLayoutElement(final StringReader stream) throws IOException {
747        final int nextChar = stream.read();
748        if (nextChar == -1) {
749            return null;
750        }
751        switch (nextChar) {
752        // Integrals
753        case 'B':
754        case 'H':
755        case 'I':
756        case 'V':
757            return new Integral(new String(new char[] {(char) nextChar}));
758        case 'S':
759        case 'F':
760            return new Integral(new String(new char[] {(char) nextChar, (char) stream.read()}));
761        case 'P':
762            stream.mark(1);
763            if (stream.read() != 'O') {
764                stream.reset();
765                return new Integral("P" + (char) stream.read());
766            }
767            return new Integral("PO" + (char) stream.read());
768        case 'O':
769            stream.mark(1);
770            if (stream.read() != 'S') {
771                stream.reset();
772                return new Integral("O" + (char) stream.read());
773            }
774            return new Integral("OS" + (char) stream.read());
775
776            // Replication
777        case 'N':
778            final char uintType = (char) stream.read();
779            stream.read(); // '['
780            final String str = readUpToMatchingBracket(stream);
781            return new Replication("" + uintType, str);
782
783        // Union
784        case 'T':
785            String intType = "" + (char) stream.read();
786            if (intType.equals("S")) {
787                intType += (char) stream.read();
788            }
789            final List<UnionCase> unionCases = new ArrayList<>();
790            UnionCase c;
791            while ((c = readNextUnionCase(stream)) != null) {
792                unionCases.add(c);
793            }
794            stream.read(); // '('
795            stream.read(); // ')'
796            stream.read(); // '['
797            List<LayoutElement> body = null;
798            stream.mark(1);
799            final char next = (char) stream.read();
800            if (next != ']') {
801                stream.reset();
802                body = readBody(getStreamUpToMatchingBracket(stream));
803            }
804            return new Union(intType, unionCases, body);
805
806        // Call
807        case '(':
808            final int number = readNumber(stream).intValue();
809            stream.read(); // ')'
810            return new Call(number);
811        // Reference
812        case 'K':
813        case 'R':
814            final StringBuilder string = new StringBuilder("").append((char) nextChar).append((char) stream.read());
815            final char nxt = (char) stream.read();
816            string.append(nxt);
817            if (nxt == 'N') {
818                string.append((char) stream.read());
819            }
820            return new Reference(string.toString());
821        }
822        return null;
823    }
824
825    /**
826     * Read a UnionCase from the stream.
827     *
828     * @param stream source stream.
829     * @return A UnionCase from the stream.
830     * @throws IOException If an I/O error occurs.
831     */
832    private UnionCase readNextUnionCase(final StringReader stream) throws IOException {
833        stream.mark(2);
834        stream.read(); // '('
835        final int next = stream.read();
836        char ch = (char) next;
837        if (ch == ')'|| next == -1) {
838            stream.reset();
839            return null;
840        }
841        stream.reset();
842        stream.read(); // '('
843        final List<Integer> tags = new ArrayList<>();
844        Integer nextTag;
845        do {
846            nextTag = readNumber(stream);
847            if (nextTag != null) {
848                tags.add(nextTag);
849                stream.read(); // ',' or ')'
850            }
851        } while (nextTag != null);
852        stream.read(); // '['
853        stream.mark(1);
854        ch = (char) stream.read();
855        if (ch == ']') {
856            return new UnionCase(tags);
857        }
858        stream.reset();
859        return new UnionCase(tags, readBody(getStreamUpToMatchingBracket(stream)));
860    }
861
862    /**
863     * Read a number from the stream and return it
864     *
865     * @param stream
866     * @return
867     * @throws IOException If an I/O error occurs.
868     */
869    private Integer readNumber(final StringReader stream) throws IOException {
870        stream.mark(1);
871        final char first = (char) stream.read();
872        final boolean negative = first == '-';
873        if (!negative) {
874            stream.reset();
875        }
876        stream.mark(100);
877        int i;
878        int length = 0;
879        while ((i = stream.read()) != -1 && Character.isDigit((char) i)) {
880            length++;
881        }
882        stream.reset();
883        if (length == 0) {
884            return null;
885        }
886        final char[] digits = new char[length];
887        final int read = stream.read(digits);
888        if (read != digits.length) {
889            throw new IOException("Error reading from the input stream");
890        }
891        return Integer.valueOf(Integer.parseInt((negative ? "-" : "") + new String(digits)));
892    }
893
894    /**
895     * Gets the contents of the given stream, up to the next {@code ]},
896     * (ignoring pairs of brackets {@code [} and {@code ]})
897     *
898     * @param stream input stream.
899     * @return the contents of the given stream.
900     * @throws IOException If an I/O error occurs.
901     */
902    private String readUpToMatchingBracket(final StringReader stream) throws IOException {
903        final StringBuilder sb = new StringBuilder();
904        int foundBracket = -1;
905        while (foundBracket != 0) {
906            final int read = stream.read();
907            if (read == -1) {
908                break;
909            }
910                        final char c = (char) read;
911            if (c == ']') {
912                foundBracket++;
913            }
914            if (c == '[') {
915                foundBracket--;
916            }
917            if (!(foundBracket == 0)) {
918                sb.append(c);
919            }
920        }
921        return sb.toString();
922    }
923
924    /**
925     * Resolve calls in the attribute layout and returns the number of backwards calls
926     */
927    private void resolveCalls() {
928        int backwardsCalls = 0;
929        for (int i = 0; i < attributeLayoutElements.size(); i++) {
930            final AttributeLayoutElement element = attributeLayoutElements.get(i);
931            if (element instanceof Callable) {
932                final Callable callable = (Callable) element;
933                if (i == 0) {
934                    callable.setFirstCallable(true);
935                }
936                // Look for calls in the body
937                for (final LayoutElement layoutElement : callable.body) {
938                    // Set the callable for each call
939                    backwardsCalls += resolveCallsForElement(i, callable, layoutElement);
940                }
941            }
942        }
943        backwardsCallCount = backwardsCalls;
944    }
945
946    private int resolveCallsForElement(final int i, final Callable currentCallable, final LayoutElement layoutElement) {
947        int backwardsCalls = 0;
948        if (layoutElement instanceof Call) {
949            final Call call = (Call) layoutElement;
950            int index = call.callableIndex;
951            if (index == 0) { // Calls the parent callable
952                backwardsCalls++;
953                call.setCallable(currentCallable);
954            } else if (index > 0) { // Forwards call
955                for (int k = i + 1; k < attributeLayoutElements.size(); k++) {
956                    final AttributeLayoutElement el = attributeLayoutElements.get(k);
957                    if (el instanceof Callable) {
958                        index--;
959                        if (index == 0) {
960                            call.setCallable((Callable) el);
961                            break;
962                        }
963                    }
964                }
965            } else { // Backwards call
966                backwardsCalls++;
967                for (int k = i - 1; k >= 0; 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            }
978        } else if (layoutElement instanceof Replication) {
979            final List<LayoutElement> children = ((Replication) layoutElement).layoutElements;
980            for (final LayoutElement child : children) {
981                backwardsCalls += resolveCallsForElement(i, currentCallable, child);
982            }
983        }
984        return backwardsCalls;
985    }
986
987    /**
988     * Once the attribute bands have been read the callables can be informed about the number of times each is subject
989     * to a backwards call. This method is used to set this information.
990     *
991     * @param backwardsCalls one int for each backwards callable, which contains the number of times that callable is
992     *        subject to a backwards call.
993     * @throws IOException If an I/O error occurs.
994     */
995    public void setBackwardsCalls(final int[] backwardsCalls) throws IOException {
996        int index = 0;
997        parseLayout();
998        for (final AttributeLayoutElement element : attributeLayoutElements) {
999            if (element instanceof Callable && ((Callable) element).isBackwardsCallable()) {
1000                ((Callable) element).addCount(backwardsCalls[index]);
1001                index++;
1002            }
1003        }
1004    }
1005
1006    @Override
1007    public void unpack() throws IOException, Pack200Exception {
1008
1009    }
1010
1011}