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.pack200;
018
019import java.io.ByteArrayInputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.OutputStream;
023import java.io.StringReader;
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Map;
029
030import org.apache.commons.compress.harmony.pack200.AttributeDefinitionBands.AttributeDefinition;
031import org.objectweb.asm.Label;
032
033/**
034 * Set of bands relating to a non-predefined attribute that has had a layout definition given to pack200 (e.g. via one
035 * of the -C, -M, -F or -D command line options)
036 */
037public class NewAttributeBands extends BandSet {
038
039    protected List attributeLayoutElements;
040    private int[] backwardsCallCounts;
041    private final CpBands cpBands;
042    private final AttributeDefinition def;
043    private boolean usedAtLeastOnce;
044
045    // used when parsing
046    private Integral lastPIntegral;
047
048    public NewAttributeBands(final int effort, final CpBands cpBands, final SegmentHeader header,
049        final AttributeDefinition def) throws IOException {
050        super(effort, header);
051        this.def = def;
052        this.cpBands = cpBands;
053        parseLayout();
054    }
055
056    public void addAttribute(final NewAttribute attribute) {
057        usedAtLeastOnce = true;
058        final InputStream stream = new ByteArrayInputStream(attribute.getBytes());
059        for (final Iterator iterator = attributeLayoutElements.iterator(); iterator.hasNext();) {
060            final AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next();
061            layoutElement.addAttributeToBand(attribute, stream);
062        }
063    }
064
065    @Override
066    public void pack(final OutputStream out) throws IOException, Pack200Exception {
067        for (final Iterator iterator = attributeLayoutElements.iterator(); iterator.hasNext();) {
068            final AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next();
069            layoutElement.pack(out);
070        }
071    }
072
073    public String getAttributeName() {
074        return def.name.getUnderlyingString();
075    }
076
077    public int getFlagIndex() {
078        return def.index;
079    }
080
081    public int[] numBackwardsCalls() {
082        return backwardsCallCounts;
083    }
084
085    public boolean isUsedAtLeastOnce() {
086        return usedAtLeastOnce;
087    }
088
089    private void parseLayout() throws IOException {
090        final String layout = def.layout.getUnderlyingString();
091        if (attributeLayoutElements == null) {
092            attributeLayoutElements = new ArrayList();
093            final StringReader stream = new StringReader(layout);
094            AttributeLayoutElement e;
095            while ((e = readNextAttributeElement(stream)) != null) {
096                attributeLayoutElements.add(e);
097            }
098            resolveCalls();
099        }
100    }
101
102    /**
103     * Resolve calls in the attribute layout and returns the number of backwards callables
104     *
105     * @param tokens - the attribute layout as a List of AttributeElements
106     */
107    private void resolveCalls() {
108        for (int i = 0; i < attributeLayoutElements.size(); i++) {
109            final AttributeLayoutElement element = (AttributeLayoutElement) attributeLayoutElements.get(i);
110            if (element instanceof Callable) {
111                final Callable callable = (Callable) element;
112                final List body = callable.body; // Look for calls in the body
113                for (int iIndex = 0; iIndex < body.size(); iIndex++) {
114                    final LayoutElement layoutElement = (LayoutElement) body.get(iIndex);
115                    // Set the callable for each call
116                    resolveCallsForElement(i, callable, layoutElement);
117                }
118            }
119        }
120        int backwardsCallableIndex = 0;
121        for (int i = 0; i < attributeLayoutElements.size(); i++) {
122            final AttributeLayoutElement element = (AttributeLayoutElement) attributeLayoutElements.get(i);
123            if (element instanceof Callable) {
124                final Callable callable = (Callable) element;
125                if (callable.isBackwardsCallable) {
126                    callable.setBackwardsCallableIndex(backwardsCallableIndex);
127                    backwardsCallableIndex++;
128                }
129            }
130        }
131        backwardsCallCounts = new int[backwardsCallableIndex];
132    }
133
134    private void resolveCallsForElement(final int i, final Callable currentCallable,
135        final LayoutElement layoutElement) {
136        if (layoutElement instanceof Call) {
137            final Call call = (Call) layoutElement;
138            int index = call.callableIndex;
139            if (index == 0) { // Calls the parent callable
140                call.setCallable(currentCallable);
141            } else if (index > 0) { // Forwards call
142                for (int k = i + 1; k < attributeLayoutElements.size(); k++) {
143                    final AttributeLayoutElement el = (AttributeLayoutElement) attributeLayoutElements.get(k);
144                    if (el instanceof Callable) {
145                        index--;
146                        if (index == 0) {
147                            call.setCallable((Callable) el);
148                            break;
149                        }
150                    }
151                }
152            } else { // Backwards call
153                for (int k = i - 1; k >= 0; k--) {
154                    final AttributeLayoutElement el = (AttributeLayoutElement) attributeLayoutElements.get(k);
155                    if (el instanceof Callable) {
156                        index++;
157                        if (index == 0) {
158                            call.setCallable((Callable) el);
159                            break;
160                        }
161                    }
162                }
163            }
164        } else if (layoutElement instanceof Replication) {
165            final List children = ((Replication) layoutElement).layoutElements;
166            for (final Iterator iterator = children.iterator(); iterator.hasNext();) {
167                final LayoutElement object = (LayoutElement) iterator.next();
168                resolveCallsForElement(i, currentCallable, object);
169            }
170        }
171    }
172
173    private AttributeLayoutElement readNextAttributeElement(final StringReader stream) throws IOException {
174        stream.mark(1);
175        final int nextChar = stream.read();
176        if (nextChar == -1) {
177            return null;
178        }
179        if (nextChar == '[') {
180            final List body = readBody(getStreamUpToMatchingBracket(stream));
181            return new Callable(body);
182        }
183        stream.reset();
184        return readNextLayoutElement(stream);
185    }
186
187    private LayoutElement readNextLayoutElement(final StringReader stream) throws IOException {
188        final int nextChar = stream.read();
189        if (nextChar == -1) {
190            return null;
191        }
192
193        switch (nextChar) {
194        // Integrals
195        case 'B':
196        case 'H':
197        case 'I':
198        case 'V':
199            return new Integral(new String(new char[] {(char) nextChar}));
200        case 'S':
201        case 'F':
202            return new Integral(new String(new char[] {(char) nextChar, (char) stream.read()}));
203        case 'P':
204            stream.mark(1);
205            if (stream.read() != 'O') {
206                stream.reset();
207                lastPIntegral = new Integral("P" + (char) stream.read());
208                return lastPIntegral;
209            } else {
210                lastPIntegral = new Integral("PO" + (char) stream.read(), lastPIntegral);
211                return lastPIntegral;
212            }
213        case 'O':
214            stream.mark(1);
215            if (stream.read() != 'S') {
216                stream.reset();
217                return new Integral("O" + (char) stream.read(), lastPIntegral);
218            } else {
219                return new Integral("OS" + (char) stream.read(), lastPIntegral);
220            }
221
222            // Replication
223        case 'N':
224            final char uint_type = (char) stream.read();
225            stream.read(); // '['
226            final String str = readUpToMatchingBracket(stream);
227            return new Replication("" + uint_type, str);
228
229        // Union
230        case 'T':
231            String int_type = "" + (char) stream.read();
232            if (int_type.equals("S")) {
233                int_type += (char) stream.read();
234            }
235            final List unionCases = new ArrayList();
236            UnionCase c;
237            while ((c = readNextUnionCase(stream)) != null) {
238                unionCases.add(c);
239            }
240            stream.read(); // '('
241            stream.read(); // ')'
242            stream.read(); // '['
243            List body = null;
244            stream.mark(1);
245            final char next = (char) stream.read();
246            if (next != ']') {
247                stream.reset();
248                body = readBody(getStreamUpToMatchingBracket(stream));
249            }
250            return new Union(int_type, unionCases, body);
251
252        // Call
253        case '(':
254            final int number = readNumber(stream).intValue();
255            stream.read(); // ')'
256            return new Call(number);
257        // Reference
258        case 'K':
259        case 'R':
260            final StringBuilder string = new StringBuilder("").append((char) nextChar).append((char) stream.read());
261            final char nxt = (char) stream.read();
262            string.append(nxt);
263            if (nxt == 'N') {
264                string.append((char) stream.read());
265            }
266            return new Reference(string.toString());
267        }
268        return null;
269    }
270
271    /**
272     * Read a UnionCase from the stream
273     *
274     * @param stream
275     * @return
276     * @throws IOException If an I/O error occurs.
277     */
278    private UnionCase readNextUnionCase(final StringReader stream) throws IOException {
279        stream.mark(2);
280        stream.read(); // '('
281        char next = (char) stream.read();
282        if (next == ')') {
283            stream.reset();
284            return null;
285        }
286        stream.reset();
287        stream.read(); // '('
288        final List tags = new ArrayList();
289        Integer nextTag;
290        do {
291            nextTag = readNumber(stream);
292            if (nextTag != null) {
293                tags.add(nextTag);
294                stream.read(); // ',' or ')'
295            }
296        } while (nextTag != null);
297        stream.read(); // '['
298        stream.mark(1);
299        next = (char) stream.read();
300        if (next == ']') {
301            return new UnionCase(tags);
302        }
303        stream.reset();
304        return new UnionCase(tags, readBody(getStreamUpToMatchingBracket(stream)));
305    }
306
307    /**
308     * An AttributeLayoutElement is a part of an attribute layout and has one or more bands associated with it, which
309     * transmit the AttributeElement data for successive Attributes of this type.
310     */
311    public interface AttributeLayoutElement {
312
313        void addAttributeToBand(NewAttribute attribute, InputStream stream);
314
315        void pack(OutputStream out) throws IOException, Pack200Exception;
316
317        void renumberBci(IntList bciRenumbering, Map labelsToOffsets);
318
319    }
320
321    public abstract class LayoutElement implements AttributeLayoutElement {
322
323        protected int getLength(final char uint_type) {
324            int length = 0;
325            switch (uint_type) {
326            case 'B':
327                length = 1;
328                break;
329            case 'H':
330                length = 2;
331                break;
332            case 'I':
333                length = 4;
334                break;
335            case 'V':
336                length = 0;
337                break;
338            }
339            return length;
340        }
341    }
342
343    public class Integral extends LayoutElement {
344
345        private final String tag;
346
347        private final List band = new ArrayList();
348        private final BHSDCodec defaultCodec;
349
350        // used for bytecode offsets (OH and POH)
351        private Integral previousIntegral;
352        private int previousPValue;
353
354        public Integral(final String tag) {
355            this.tag = tag;
356            this.defaultCodec = getCodec(tag);
357        }
358
359        public Integral(final String tag, final Integral previousIntegral) {
360            this.tag = tag;
361            this.defaultCodec = getCodec(tag);
362            this.previousIntegral = previousIntegral;
363        }
364
365        public String getTag() {
366            return tag;
367        }
368
369        @Override
370        public void addAttributeToBand(final NewAttribute attribute, final InputStream stream) {
371            Object val = null;
372            int value = 0;
373            if (tag.equals("B") || tag.equals("FB")) {
374                value = readInteger(1, stream) & 0xFF; // unsigned byte
375            } else if (tag.equals("SB")) {
376                value = readInteger(1, stream);
377            } else if (tag.equals("H") || tag.equals("FH")) {
378                value = readInteger(2, stream) & 0xFFFF; // unsigned short
379            } else if (tag.equals("SH")) {
380                value = readInteger(2, stream);
381            } else if (tag.equals("I") || tag.equals("FI")) {
382                value = readInteger(4, stream);
383            } else if (tag.equals("SI")) {
384                value = readInteger(4, stream);
385            } else if (tag.equals("V") || tag.equals("FV") || tag.equals("SV")) {
386                // Not currently supported
387            } else if (tag.startsWith("PO") || tag.startsWith("OS")) {
388                final char uint_type = tag.substring(2).toCharArray()[0];
389                final int length = getLength(uint_type);
390                value = readInteger(length, stream);
391                value += previousIntegral.previousPValue;
392                val = attribute.getLabel(value);
393                previousPValue = value;
394            } else if (tag.startsWith("P")) {
395                final char uint_type = tag.substring(1).toCharArray()[0];
396                final int length = getLength(uint_type);
397                value = readInteger(length, stream);
398                val = attribute.getLabel(value);
399                previousPValue = value;
400            } else if (tag.startsWith("O")) {
401                final char uint_type = tag.substring(1).toCharArray()[0];
402                final int length = getLength(uint_type);
403                value = readInteger(length, stream);
404                value += previousIntegral.previousPValue;
405                val = attribute.getLabel(value);
406                previousPValue = value;
407            }
408            if (val == null) {
409                val = Integer.valueOf(value);
410            }
411            band.add(val);
412        }
413
414        @Override
415        public void pack(final OutputStream out) throws IOException, Pack200Exception {
416            PackingUtils.log("Writing new attribute bands...");
417            final byte[] encodedBand = encodeBandInt(tag, integerListToArray(band), defaultCodec);
418            out.write(encodedBand);
419            PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + tag + "[" + band.size() + "]");
420        }
421
422        public int latestValue() {
423            return ((Integer) band.get(band.size() - 1)).intValue();
424        }
425
426        @Override
427        public void renumberBci(final IntList bciRenumbering, final Map labelsToOffsets) {
428            if (tag.startsWith("O") || tag.startsWith("PO")) {
429                renumberOffsetBci(previousIntegral.band, bciRenumbering, labelsToOffsets);
430            } else if (tag.startsWith("P")) {
431                for (int i = band.size() - 1; i >= 0; i--) {
432                    final Object label = band.get(i);
433                    if (label instanceof Integer) {
434                        break;
435                    }
436                    if (label instanceof Label) {
437                        band.remove(i);
438                        final Integer bytecodeIndex = (Integer) labelsToOffsets.get(label);
439                        band.add(i, Integer.valueOf(bciRenumbering.get(bytecodeIndex.intValue())));
440                    }
441                }
442            }
443        }
444
445        private void renumberOffsetBci(final List relative, final IntList bciRenumbering, final Map labelsToOffsets) {
446            for (int i = band.size() - 1; i >= 0; i--) {
447                final Object label = band.get(i);
448                if (label instanceof Integer) {
449                    break;
450                }
451                if (label instanceof Label) {
452                    band.remove(i);
453                    final Integer bytecodeIndex = (Integer) labelsToOffsets.get(label);
454                    final Integer renumberedOffset = Integer
455                        .valueOf(bciRenumbering.get(bytecodeIndex.intValue()) - ((Integer) relative.get(i)).intValue());
456                    band.add(i, renumberedOffset);
457                }
458            }
459        }
460
461    }
462
463    /**
464     * A replication is an array of layout elements, with an associated count
465     */
466    public class Replication extends LayoutElement {
467
468        private final Integral countElement;
469
470        private final List layoutElements = new ArrayList();
471
472        public Integral getCountElement() {
473            return countElement;
474        }
475
476        public List getLayoutElements() {
477            return layoutElements;
478        }
479
480        public Replication(final String tag, final String contents) throws IOException {
481            this.countElement = new Integral(tag);
482            final StringReader stream = new StringReader(contents);
483            LayoutElement e;
484            while ((e = readNextLayoutElement(stream)) != null) {
485                layoutElements.add(e);
486            }
487        }
488
489        @Override
490        public void addAttributeToBand(final NewAttribute attribute, final InputStream stream) {
491            countElement.addAttributeToBand(attribute, stream);
492            final int count = countElement.latestValue();
493            for (int i = 0; i < count; i++) {
494                for (final Iterator iterator = layoutElements.iterator(); iterator.hasNext();) {
495                    final AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next();
496                    layoutElement.addAttributeToBand(attribute, stream);
497                }
498            }
499        }
500
501        @Override
502        public void pack(final OutputStream out) throws IOException, Pack200Exception {
503            countElement.pack(out);
504            for (final Iterator iterator = layoutElements.iterator(); iterator.hasNext();) {
505                final AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next();
506                layoutElement.pack(out);
507            }
508        }
509
510        @Override
511        public void renumberBci(final IntList bciRenumbering, final Map labelsToOffsets) {
512            for (final Iterator iterator = layoutElements.iterator(); iterator.hasNext();) {
513                final AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next();
514                layoutElement.renumberBci(bciRenumbering, labelsToOffsets);
515            }
516        }
517    }
518
519    /**
520     * A Union is a type of layout element where the tag value acts as a selector for one of the union cases
521     */
522    public class Union extends LayoutElement {
523
524        private final Integral unionTag;
525        private final List unionCases;
526        private final List defaultCaseBody;
527
528        public Union(final String tag, final List unionCases, final List body) {
529            this.unionTag = new Integral(tag);
530            this.unionCases = unionCases;
531            this.defaultCaseBody = body;
532        }
533
534        @Override
535        public void addAttributeToBand(final NewAttribute attribute, final InputStream stream) {
536            unionTag.addAttributeToBand(attribute, stream);
537            final long tag = unionTag.latestValue();
538            boolean defaultCase = true;
539            for (int i = 0; i < unionCases.size(); i++) {
540                final UnionCase element = (UnionCase) unionCases.get(i);
541                if (element.hasTag(tag)) {
542                    defaultCase = false;
543                    element.addAttributeToBand(attribute, stream);
544                }
545            }
546            if (defaultCase) {
547                for (int i = 0; i < defaultCaseBody.size(); i++) {
548                    final LayoutElement element = (LayoutElement) defaultCaseBody.get(i);
549                    element.addAttributeToBand(attribute, stream);
550                }
551            }
552        }
553
554        @Override
555        public void pack(final OutputStream out) throws IOException, Pack200Exception {
556            unionTag.pack(out);
557            for (final Iterator iterator = unionCases.iterator(); iterator.hasNext();) {
558                final UnionCase unionCase = (UnionCase) iterator.next();
559                unionCase.pack(out);
560            }
561            for (final Iterator iterator = defaultCaseBody.iterator(); iterator.hasNext();) {
562                final AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next();
563                layoutElement.pack(out);
564            }
565        }
566
567        @Override
568        public void renumberBci(final IntList bciRenumbering, final Map labelsToOffsets) {
569            for (final Iterator iterator = unionCases.iterator(); iterator.hasNext();) {
570                final UnionCase unionCase = (UnionCase) iterator.next();
571                unionCase.renumberBci(bciRenumbering, labelsToOffsets);
572            }
573            for (final Iterator iterator = defaultCaseBody.iterator(); iterator.hasNext();) {
574                final AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next();
575                layoutElement.renumberBci(bciRenumbering, labelsToOffsets);
576            }
577        }
578
579        public Integral getUnionTag() {
580            return unionTag;
581        }
582
583        public List getUnionCases() {
584            return unionCases;
585        }
586
587        public List getDefaultCaseBody() {
588            return defaultCaseBody;
589        }
590    }
591
592    public class Call extends LayoutElement {
593
594        private final int callableIndex;
595        private Callable callable;
596
597        public Call(final int callableIndex) {
598            this.callableIndex = callableIndex;
599        }
600
601        public void setCallable(final Callable callable) {
602            this.callable = callable;
603            if (callableIndex < 1) {
604                callable.setBackwardsCallable();
605            }
606        }
607
608        @Override
609        public void addAttributeToBand(final NewAttribute attribute, final InputStream stream) {
610            callable.addAttributeToBand(attribute, stream);
611            if (callableIndex < 1) {
612                callable.addBackwardsCall();
613            }
614        }
615
616        @Override
617        public void pack(final OutputStream out) {
618            // do nothing here as pack will be called on the callable at another time
619        }
620
621        @Override
622        public void renumberBci(final IntList bciRenumbering, final Map labelsToOffsets) {
623            // do nothing here as renumberBci will be called on the callable at another time
624        }
625
626        public int getCallableIndex() {
627            return callableIndex;
628        }
629
630        public Callable getCallable() {
631            return callable;
632        }
633    }
634
635    /**
636     * Constant Pool Reference
637     */
638    public class Reference extends LayoutElement {
639
640        private final String tag;
641
642        private List band;
643
644        private boolean nullsAllowed = false;
645
646        public Reference(final String tag) {
647            this.tag = tag;
648            nullsAllowed = tag.indexOf('N') != -1;
649        }
650
651        @Override
652        public void addAttributeToBand(final NewAttribute attribute, final InputStream stream) {
653            final int index = readInteger(4, stream);
654            if (tag.startsWith("RC")) { // Class
655                band.add(cpBands.getCPClass(attribute.readClass(index)));
656            } else if (tag.startsWith("RU")) { // UTF8 String
657                band.add(cpBands.getCPUtf8(attribute.readUTF8(index)));
658            } else if (tag.startsWith("RS")) { // Signature
659                band.add(cpBands.getCPSignature(attribute.readUTF8(index)));
660            } else { // Constant
661                band.add(cpBands.getConstant(attribute.readConst(index)));
662            }
663            // TODO method and field references
664        }
665
666        public String getTag() {
667            return tag;
668        }
669
670        @Override
671        public void pack(final OutputStream out) throws IOException, Pack200Exception {
672            int[] ints;
673            if (nullsAllowed) {
674                ints = cpEntryOrNullListToArray(band);
675            } else {
676                ints = cpEntryListToArray(band);
677            }
678            final byte[] encodedBand = encodeBandInt(tag, ints, Codec.UNSIGNED5);
679            out.write(encodedBand);
680            PackingUtils.log("Wrote " + encodedBand.length + " bytes from " + tag + "[" + ints.length + "]");
681        }
682
683        @Override
684        public void renumberBci(final IntList bciRenumbering, final Map labelsToOffsets) {
685            // nothing to do here
686        }
687
688    }
689
690    public class Callable implements AttributeLayoutElement {
691
692        private final List body;
693
694        private boolean isBackwardsCallable;
695
696        private int backwardsCallableIndex;
697
698        public Callable(final List body) throws IOException {
699            this.body = body;
700        }
701
702        public void setBackwardsCallableIndex(final int backwardsCallableIndex) {
703            this.backwardsCallableIndex = backwardsCallableIndex;
704        }
705
706        public void addBackwardsCall() {
707            backwardsCallCounts[backwardsCallableIndex]++;
708        }
709
710        public boolean isBackwardsCallable() {
711            return isBackwardsCallable;
712        }
713
714        /**
715         * Tells this Callable that it is a backwards callable
716         */
717        public void setBackwardsCallable() {
718            this.isBackwardsCallable = true;
719        }
720
721        @Override
722        public void addAttributeToBand(final NewAttribute attribute, final InputStream stream) {
723            for (final Iterator iterator = body.iterator(); iterator.hasNext();) {
724                final AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next();
725                layoutElement.addAttributeToBand(attribute, stream);
726            }
727        }
728
729        @Override
730        public void pack(final OutputStream out) throws IOException, Pack200Exception {
731            for (final Iterator iterator = body.iterator(); iterator.hasNext();) {
732                final AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next();
733                layoutElement.pack(out);
734            }
735        }
736
737        @Override
738        public void renumberBci(final IntList bciRenumbering, final Map labelsToOffsets) {
739            for (final Iterator iterator = body.iterator(); iterator.hasNext();) {
740                final AttributeLayoutElement layoutElement = (AttributeLayoutElement) iterator.next();
741                layoutElement.renumberBci(bciRenumbering, labelsToOffsets);
742            }
743        }
744
745        public List getBody() {
746            return body;
747        }
748    }
749
750    /**
751     * A Union case
752     */
753    public class UnionCase extends LayoutElement {
754
755        private final List body;
756
757        private final List tags;
758
759        public UnionCase(final List tags) {
760            this.tags = tags;
761            this.body = Collections.EMPTY_LIST;
762        }
763
764        public boolean hasTag(final long l) {
765            return tags.contains(Integer.valueOf((int) l));
766        }
767
768        public UnionCase(final List tags, final List body) throws IOException {
769            this.tags = tags;
770            this.body = body;
771        }
772
773        @Override
774        public void addAttributeToBand(final NewAttribute attribute, final InputStream stream) {
775            for (int i = 0; i < body.size(); i++) {
776                final LayoutElement element = (LayoutElement) body.get(i);
777                element.addAttributeToBand(attribute, stream);
778            }
779        }
780
781        @Override
782        public void pack(final OutputStream out) throws IOException, Pack200Exception {
783            for (int i = 0; i < body.size(); i++) {
784                final LayoutElement element = (LayoutElement) body.get(i);
785                element.pack(out);
786            }
787        }
788
789        @Override
790        public void renumberBci(final IntList bciRenumbering, final Map labelsToOffsets) {
791            for (int i = 0; i < body.size(); i++) {
792                final LayoutElement element = (LayoutElement) body.get(i);
793                element.renumberBci(bciRenumbering, labelsToOffsets);
794            }
795        }
796
797        public List getBody() {
798            return body;
799        }
800    }
801
802    /**
803     * Utility method to get the contents of the given stream, up to the next ']', (ignoring pairs of brackets '[' and
804     * ']')
805     *
806     * @param stream
807     * @return
808     * @throws IOException If an I/O error occurs.
809     */
810    private StringReader getStreamUpToMatchingBracket(final StringReader stream) throws IOException {
811        final StringBuffer sb = new StringBuffer();
812        int foundBracket = -1;
813        while (foundBracket != 0) {
814            final char c = (char) stream.read();
815            if (c == ']') {
816                foundBracket++;
817            }
818            if (c == '[') {
819                foundBracket--;
820            }
821            if (!(foundBracket == 0)) {
822                sb.append(c);
823            }
824        }
825        return new StringReader(sb.toString());
826    }
827
828    private int readInteger(final int i, final InputStream stream) {
829        int result = 0;
830        for (int j = 0; j < i; j++) {
831            try {
832                result = result << 8 | stream.read();
833            } catch (final IOException e) {
834                throw new RuntimeException("Error reading unknown attribute");
835            }
836        }
837        // use casting to preserve sign
838        if (i == 1) {
839            result = (byte) result;
840        }
841        if (i == 2) {
842            result = (short) result;
843        }
844        return result;
845    }
846
847    /**
848     * Returns the {@link BHSDCodec} that should be used for the given layout element
849     *
850     * @param layoutElement
851     */
852    private BHSDCodec getCodec(final String layoutElement) {
853        if (layoutElement.indexOf('O') >= 0) {
854            return Codec.BRANCH5;
855        }
856        if (layoutElement.indexOf('P') >= 0) {
857            return Codec.BCI5;
858        }
859        if (layoutElement.indexOf('S') >= 0 && layoutElement.indexOf("KS") < 0 //$NON-NLS-1$
860            && layoutElement.indexOf("RS") < 0) { //$NON-NLS-1$
861            return Codec.SIGNED5;
862        }
863        if (layoutElement.indexOf('B') >= 0) {
864            return Codec.BYTE1;
865        }
866        return Codec.UNSIGNED5;
867    }
868
869    /**
870     * Utility method to get the contents of the given stream, up to the next ']', (ignoring pairs of brackets '[' and
871     * ']')
872     *
873     * @param stream
874     * @return
875     * @throws IOException If an I/O error occurs.
876     */
877    private String readUpToMatchingBracket(final StringReader stream) throws IOException {
878        final StringBuffer sb = new StringBuffer();
879        int foundBracket = -1;
880        while (foundBracket != 0) {
881            final char c = (char) stream.read();
882            if (c == ']') {
883                foundBracket++;
884            }
885            if (c == '[') {
886                foundBracket--;
887            }
888            if (!(foundBracket == 0)) {
889                sb.append(c);
890            }
891        }
892        return sb.toString();
893    }
894
895    /**
896     * Read a number from the stream and return it
897     *
898     * @param stream
899     * @return
900     * @throws IOException If an I/O error occurs.
901     */
902    private Integer readNumber(final StringReader stream) throws IOException {
903        stream.mark(1);
904        final char first = (char) stream.read();
905        final boolean negative = first == '-';
906        if (!negative) {
907            stream.reset();
908        }
909        stream.mark(100);
910        int i;
911        int length = 0;
912        while ((i = (stream.read())) != -1 && Character.isDigit((char) i)) {
913            length++;
914        }
915        stream.reset();
916        if (length == 0) {
917            return null;
918        }
919        final char[] digits = new char[length];
920        final int read = stream.read(digits);
921        if (read != digits.length) {
922            throw new IOException("Error reading from the input stream");
923        }
924        return Integer.valueOf(Integer.parseInt((negative ? "-" : "") + new String(digits)));
925    }
926
927    /**
928     * Read a 'body' section of the layout from the given stream
929     *
930     * @param stream
931     * @return List of LayoutElements
932     * @throws IOException If an I/O error occurs.
933     */
934    private List readBody(final StringReader stream) throws IOException {
935        final List layoutElements = new ArrayList();
936        LayoutElement e;
937        while ((e = readNextLayoutElement(stream)) != null) {
938            layoutElements.add(e);
939        }
940        return layoutElements;
941    }
942
943    /**
944     * Renumber any bytecode indexes or offsets as described in section 5.5.2 of the pack200 specification
945     *
946     * @param bciRenumbering TODO
947     * @param labelsToOffsets TODO
948     */
949    public void renumberBci(final IntList bciRenumbering, final Map labelsToOffsets) {
950        for (final Iterator iterator = attributeLayoutElements.iterator(); iterator.hasNext();) {
951            final AttributeLayoutElement element = (AttributeLayoutElement) iterator.next();
952            element.renumberBci(bciRenumbering, labelsToOffsets);
953        }
954    }
955
956}