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