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