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;
042import org.apache.commons.compress.utils.ParsingUtils;
043
044/**
045 * Sets of bands relating to a non-predefined attribute
046 */
047public class NewAttributeBands extends BandSet {
048
049    /**
050     * An AttributeLayoutElement is a part of an attribute layout and has one or more bands associated with it, which transmit the AttributeElement data for
051     * successive Attributes of this type.
052     */
053    private interface AttributeLayoutElement {
054
055        /**
056         * Adds the band data for this element at the given index to the attribute.
057         *
058         * @param index     Index position to add the attribute.
059         * @param attribute The attribute to add.
060         */
061        void addToAttribute(int index, NewAttribute attribute);
062
063        /**
064         * Read the bands associated with this part of the layout.
065         *
066         * @param in    TODO
067         * @param count TODO
068         * @throws Pack200Exception Bad archive.
069         * @throws IOException      If an I/O error occurs.
070         */
071        void readBands(InputStream in, int count) throws IOException, Pack200Exception;
072
073    }
074
075    public class Call extends LayoutElement {
076
077        private final int callableIndex;
078        private Callable callable;
079
080        public Call(final int callableIndex) {
081            this.callableIndex = callableIndex;
082        }
083
084        @Override
085        public void addToAttribute(final int n, final NewAttribute attribute) {
086            callable.addNextToAttribute(attribute);
087        }
088
089        public Callable getCallable() {
090            return callable;
091        }
092
093        public int getCallableIndex() {
094            return callableIndex;
095        }
096
097        @Override
098        public void readBands(final InputStream in, final int count) {
099            /*
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}