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