1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 * As per the Apache license requirements, this file has been modified
19 * from its original state.
20 *
21 * Such modifications are Copyright (C) 2010 Ben Gruver, and are released
22 * under the original license
23 */
24
25package org.jf.dexlib.Util;
26
27import java.io.IOException;
28import java.io.Writer;
29import java.util.ArrayList;
30
31/**
32 * Implementation of {@link AnnotatedOutput} which stores the written data
33 * into a <code>byte[]</code>.
34 *
35 * <p><b>Note:</b> As per the {@link Output} interface, multi-byte
36 * writes all use little-endian order.</p>
37 */
38public final class ByteArrayAnnotatedOutput
39        implements AnnotatedOutput {
40    /** default size for stretchy instances */
41    private static final int DEFAULT_SIZE = 1000;
42
43    /**
44     * whether the instance is stretchy, that is, whether its array
45     * may be resized to increase capacity
46     */
47    private final boolean stretchy;
48
49    /** non-null; the data itself */
50    private byte[] data;
51
52    /** &gt;= 0; current output cursor */
53    private int cursor;
54
55    /** whether annotations are to be verbose */
56    private boolean verbose;
57
58    /**
59     * null-ok; list of annotations, or <code>null</code> if this instance
60     * isn't keeping them
61     */
62    private ArrayList<Annotation> annotations;
63
64    /** &gt;= 40 (if used); the desired maximum annotation width */
65    private int annotationWidth;
66
67    /**
68     * &gt;= 8 (if used); the number of bytes of hex output to use
69     * in annotations
70     */
71    private int hexCols;
72
73    private int currentIndent = 0;
74    private int indentAmount = 2;
75
76    /**
77     * Constructs an instance with a fixed maximum size. Note that the
78     * given array is the only one that will be used to store data. In
79     * particular, no reallocation will occur in order to expand the
80     * capacity of the resulting instance. Also, the constructed
81     * instance does not keep annotations by default.
82     *
83     * @param data non-null; data array to use for output
84     */
85    public ByteArrayAnnotatedOutput(byte[] data) {
86        this(data, false);
87    }
88
89    /**
90     * Constructs a "stretchy" instance. The underlying array may be
91     * reallocated. The constructed instance does not keep annotations
92     * by default.
93     */
94    public ByteArrayAnnotatedOutput() {
95        this(new byte[DEFAULT_SIZE], true);
96    }
97
98    /**
99     * Internal constructor.
100     *
101     * @param data non-null; data array to use for output
102     * @param stretchy whether the instance is to be stretchy
103     */
104    private ByteArrayAnnotatedOutput(byte[] data, boolean stretchy) {
105        if (data == null) {
106            throw new NullPointerException("data == null");
107        }
108
109        this.stretchy = stretchy;
110        this.data = data;
111        this.cursor = 0;
112        this.verbose = false;
113        this.annotations = null;
114        this.annotationWidth = 0;
115        this.hexCols = 0;
116    }
117
118    /**
119     * Gets the underlying <code>byte[]</code> of this instance, which
120     * may be larger than the number of bytes written
121     *
122     * @see #toByteArray
123     *
124     * @return non-null; the <code>byte[]</code>
125     */
126    public byte[] getArray() {
127        return data;
128    }
129
130    /**
131     * Constructs and returns a new <code>byte[]</code> that contains
132     * the written contents exactly (that is, with no extra unwritten
133     * bytes at the end).
134     *
135     * @see #getArray
136     *
137     * @return non-null; an appropriately-constructed array
138     */
139    public byte[] toByteArray() {
140        byte[] result = new byte[cursor];
141        System.arraycopy(data, 0, result, 0, cursor);
142        return result;
143    }
144
145    /** {@inheritDoc} */
146    public int getCursor() {
147        return cursor;
148    }
149
150    /** {@inheritDoc} */
151    public void assertCursor(int expectedCursor) {
152        if (cursor != expectedCursor) {
153            throw new ExceptionWithContext("expected cursor " +
154                    expectedCursor + "; actual value: " + cursor);
155        }
156    }
157
158    /** {@inheritDoc} */
159    public void writeByte(int value) {
160        int writeAt = cursor;
161        int end = writeAt + 1;
162
163        if (stretchy) {
164            ensureCapacity(end);
165        } else if (end > data.length) {
166            throwBounds();
167            return;
168        }
169
170        data[writeAt] = (byte) value;
171        cursor = end;
172    }
173
174    /** {@inheritDoc} */
175    public void writeShort(int value) {
176        int writeAt = cursor;
177        int end = writeAt + 2;
178
179        if (stretchy) {
180            ensureCapacity(end);
181        } else if (end > data.length) {
182            throwBounds();
183            return;
184        }
185
186        data[writeAt] = (byte) value;
187        data[writeAt + 1] = (byte) (value >> 8);
188        cursor = end;
189    }
190
191    /** {@inheritDoc} */
192    public void writeInt(int value) {
193        int writeAt = cursor;
194        int end = writeAt + 4;
195
196        if (stretchy) {
197            ensureCapacity(end);
198        } else if (end > data.length) {
199            throwBounds();
200            return;
201        }
202
203        data[writeAt] = (byte) value;
204        data[writeAt + 1] = (byte) (value >> 8);
205        data[writeAt + 2] = (byte) (value >> 16);
206        data[writeAt + 3] = (byte) (value >> 24);
207        cursor = end;
208    }
209
210    /** {@inheritDoc} */
211    public void writeLong(long value) {
212        int writeAt = cursor;
213        int end = writeAt + 8;
214
215        if (stretchy) {
216            ensureCapacity(end);
217        } else if (end > data.length) {
218            throwBounds();
219            return;
220        }
221
222        int half = (int) value;
223        data[writeAt] = (byte) half;
224        data[writeAt + 1] = (byte) (half >> 8);
225        data[writeAt + 2] = (byte) (half >> 16);
226        data[writeAt + 3] = (byte) (half >> 24);
227
228        half = (int) (value >> 32);
229        data[writeAt + 4] = (byte) half;
230        data[writeAt + 5] = (byte) (half >> 8);
231        data[writeAt + 6] = (byte) (half >> 16);
232        data[writeAt + 7] = (byte) (half >> 24);
233
234        cursor = end;
235    }
236
237    /** {@inheritDoc} */
238    public int writeUnsignedLeb128(int value) {
239        long remaining = (value & 0xFFFFFFFFL) >> 7;
240        long lValue = value;
241        int count = 0;
242
243        while (remaining != 0) {
244            writeByte((int)(lValue & 0x7f) | 0x80);
245            lValue = remaining;
246            remaining >>= 7;
247            count++;
248        }
249
250        writeByte((int)(lValue & 0x7f));
251        return count + 1;
252    }
253
254    /** {@inheritDoc} */
255    public int writeSignedLeb128(int value) {
256        int remaining = value >> 7;
257        int count = 0;
258        boolean hasMore = true;
259        int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1;
260
261        while (hasMore) {
262            hasMore = (remaining != end)
263                || ((remaining & 1) != ((value >> 6) & 1));
264
265            writeByte((value & 0x7f) | (hasMore ? 0x80 : 0));
266            value = remaining;
267            remaining >>= 7;
268            count++;
269        }
270
271        return count;
272    }
273
274    /** {@inheritDoc} */
275    public void write(ByteArray bytes) {
276        int blen = bytes.size();
277        int writeAt = cursor;
278        int end = writeAt + blen;
279
280        if (stretchy) {
281            ensureCapacity(end);
282        } else if (end > data.length) {
283            throwBounds();
284            return;
285        }
286
287        bytes.getBytes(data, writeAt);
288        cursor = end;
289    }
290
291    /** {@inheritDoc} */
292    public void write(byte[] bytes, int offset, int length) {
293        int writeAt = cursor;
294        int end = writeAt + length;
295        int bytesEnd = offset + length;
296
297        // twos-complement math trick: ((x < 0) || (y < 0)) <=> ((x|y) < 0)
298        if (((offset | length | end) < 0) || (bytesEnd > bytes.length)) {
299            throw new IndexOutOfBoundsException("bytes.length " +
300                                                bytes.length + "; " +
301                                                offset + "..!" + end);
302        }
303
304        if (stretchy) {
305            ensureCapacity(end);
306        } else if (end > data.length) {
307            throwBounds();
308            return;
309        }
310
311        System.arraycopy(bytes, offset, data, writeAt, length);
312        cursor = end;
313    }
314
315    /** {@inheritDoc} */
316    public void write(byte[] bytes) {
317        write(bytes, 0, bytes.length);
318    }
319
320    /** {@inheritDoc} */
321    public void writeZeroes(int count) {
322        if (count < 0) {
323            throw new IllegalArgumentException("count < 0");
324        }
325
326        int end = cursor + count;
327
328        if (stretchy) {
329            ensureCapacity(end);
330        } else if (end > data.length) {
331            throwBounds();
332            return;
333        }
334
335        /*
336         * There is no need to actually write zeroes, since the array is
337         * already preinitialized with zeroes.
338         */
339
340        cursor = end;
341    }
342
343    /** {@inheritDoc} */
344    public void alignTo(int alignment) {
345        int mask = alignment - 1;
346
347        if ((alignment < 0) || ((mask & alignment) != 0)) {
348            throw new IllegalArgumentException("bogus alignment");
349        }
350
351        int end = (cursor + mask) & ~mask;
352
353        if (stretchy) {
354            ensureCapacity(end);
355        } else if (end > data.length) {
356            throwBounds();
357            return;
358        }
359
360        /*
361         * There is no need to actually write zeroes, since the array is
362         * already preinitialized with zeroes.
363         */
364
365        cursor = end;
366    }
367
368    /** {@inheritDoc} */
369    public boolean annotates() {
370        return (annotations != null);
371    }
372
373    /** {@inheritDoc} */
374    public boolean isVerbose() {
375        return verbose;
376    }
377
378    /** {@inheritDoc} */
379    public void annotate(String msg) {
380        if (annotations == null) {
381            return;
382        }
383
384        endAnnotation();
385        annotations.add(new Annotation(cursor, msg, currentIndent));
386    }
387
388    public void indent() {
389        currentIndent++;
390    }
391
392    public void deindent() {
393        currentIndent--;
394        if (currentIndent < 0) {
395            currentIndent = 0;
396        }
397    }
398
399    public void setIndentAmount(int indentAmount) {
400        this.indentAmount = indentAmount;
401    }
402
403    /** {@inheritDoc} */
404    public void annotate(int amt, String msg) {
405        if (annotations == null) {
406            return;
407        }
408
409        endAnnotation();
410
411        int asz = annotations.size();
412        int lastEnd = (asz == 0) ? 0 : annotations.get(asz - 1).getEnd();
413        int startAt;
414
415        if (lastEnd <= cursor) {
416            startAt = cursor;
417        } else {
418            startAt = lastEnd;
419        }
420
421        annotations.add(new Annotation(startAt, startAt + amt, msg, currentIndent));
422    }
423
424    /** {@inheritDoc} */
425    public void endAnnotation() {
426        if (annotations == null) {
427            return;
428        }
429
430        int sz = annotations.size();
431
432        if (sz != 0) {
433            annotations.get(sz - 1).setEndIfUnset(cursor);
434        }
435    }
436
437    /** {@inheritDoc} */
438    public int getAnnotationWidth() {
439        int leftWidth = 8 + (hexCols * 2) + (hexCols / 2);
440
441        return annotationWidth - leftWidth;
442    }
443
444    /**
445     * Indicates that this instance should keep annotations. This method may
446     * be called only once per instance, and only before any data has been
447     * written to the it.
448     *
449     * @param annotationWidth &gt;= 40; the desired maximum annotation width
450     * @param verbose whether or not to indicate verbose annotations
451     */
452    public void enableAnnotations(int annotationWidth, boolean verbose) {
453        if ((annotations != null) || (cursor != 0)) {
454            throw new RuntimeException("cannot enable annotations");
455        }
456
457        if (annotationWidth < 40) {
458            throw new IllegalArgumentException("annotationWidth < 40");
459        }
460
461        int hexCols = (((annotationWidth - 7) / 15) + 1) & ~1;
462        if (hexCols < 6) {
463            hexCols = 6;
464        } else if (hexCols > 10) {
465            hexCols = 10;
466        }
467
468        this.annotations = new ArrayList<Annotation>(1000);
469        this.annotationWidth = annotationWidth;
470        this.hexCols = hexCols;
471        this.verbose = verbose;
472    }
473
474    /**
475     * Finishes up annotation processing. This closes off any open
476     * annotations and removes annotations that don't refer to written
477     * data.
478     */
479    public void finishAnnotating() {
480        // Close off the final annotation, if any.
481        endAnnotation();
482
483        // Remove annotations that refer to unwritten data.
484        if (annotations != null) {
485            int asz = annotations.size();
486            while (asz > 0) {
487                Annotation last = annotations.get(asz - 1);
488                if (last.getStart() > cursor) {
489                    annotations.remove(asz - 1);
490                    asz--;
491                } else if (last.getEnd() > cursor) {
492                    last.setEnd(cursor);
493                    break;
494                } else {
495                    break;
496                }
497            }
498        }
499    }
500
501    /**
502     * Writes the annotated content of this instance to the given writer.
503     *
504     * @param out non-null; where to write to
505     */
506    public void writeAnnotationsTo(Writer out) throws IOException {
507        int width2 = getAnnotationWidth();
508        int width1 = annotationWidth - width2 - 1;
509
510        StringBuilder padding = new StringBuilder();
511        for (int i=0; i<1000; i++) {
512            padding.append(' ');
513        }
514
515        TwoColumnOutput twoc = new TwoColumnOutput(out, width1, width2, "|");
516        Writer left = twoc.getLeft();
517        Writer right = twoc.getRight();
518        int leftAt = 0; // left-hand byte output cursor
519        int rightAt = 0; // right-hand annotation index
520        int rightSz = annotations.size();
521
522        while ((leftAt < cursor) && (rightAt < rightSz)) {
523            Annotation a = annotations.get(rightAt);
524            int start = a.getStart();
525            int end;
526            String text;
527
528            if (leftAt < start) {
529                // This is an area with no annotation.
530                end = start;
531                start = leftAt;
532                text = "";
533            } else {
534                // This is an area with an annotation.
535                end = a.getEnd();
536                text = padding.substring(0, a.getIndent() * this.indentAmount) + a.getText();
537                rightAt++;
538            }
539
540            left.write(Hex.dump(data, start, end - start, start, hexCols, 6));
541            right.write(text);
542            twoc.flush();
543            leftAt = end;
544        }
545
546        if (leftAt < cursor) {
547            // There is unannotated output at the end.
548            left.write(Hex.dump(data, leftAt, cursor - leftAt, leftAt,
549                                hexCols, 6));
550        }
551
552        while (rightAt < rightSz) {
553            // There are zero-byte annotations at the end.
554            right.write(annotations.get(rightAt).getText());
555            rightAt++;
556        }
557
558        twoc.flush();
559    }
560
561    /**
562     * Throws the excpetion for when an attempt is made to write past the
563     * end of the instance.
564     */
565    private static void throwBounds() {
566        throw new IndexOutOfBoundsException("attempt to write past the end");
567    }
568
569    /**
570     * Reallocates the underlying array if necessary. Calls to this method
571     * should be guarded by a test of {@link #stretchy}.
572     *
573     * @param desiredSize &gt;= 0; the desired minimum total size of the array
574     */
575    private void ensureCapacity(int desiredSize) {
576        if (data.length < desiredSize) {
577            byte[] newData = new byte[desiredSize * 2 + 1000];
578            System.arraycopy(data, 0, newData, 0, cursor);
579            data = newData;
580        }
581    }
582
583    /**
584     * Annotation on output.
585     */
586    private static class Annotation {
587        /** &gt;= 0; start of annotated range (inclusive) */
588        private final int start;
589
590        /**
591         * &gt;= 0; end of annotated range (exclusive);
592         * <code>Integer.MAX_VALUE</code> if unclosed
593         */
594        private int end;
595
596        /** non-null; annotation text */
597        private final String text;
598
599        private int indent;
600
601        /**
602         * Constructs an instance.
603         *
604         * @param start &gt;= 0; start of annotated range
605         * @param end &gt;= start; end of annotated range (exclusive) or
606         * <code>Integer.MAX_VALUE</code> if unclosed
607         * @param text non-null; annotation text
608         */
609        public Annotation(int start, int end, String text, int indent) {
610            this.start = start;
611            this.end = end;
612            this.text = text;
613            this.indent = indent;
614        }
615
616        /**
617         * Constructs an instance. It is initally unclosed.
618         *
619         * @param start &gt;= 0; start of annotated range
620         * @param text non-null; annotation text
621         */
622        public Annotation(int start, String text, int indent) {
623            this(start, Integer.MAX_VALUE, text, indent);
624        }
625
626        /**
627         * Sets the end as given, but only if the instance is unclosed;
628         * otherwise, do nothing.
629         *
630         * @param end &gt;= start; the end
631         */
632        public void setEndIfUnset(int end) {
633            if (this.end == Integer.MAX_VALUE) {
634                this.end = end;
635            }
636        }
637
638        /**
639         * Sets the end as given.
640         *
641         * @param end &gt;= start; the end
642         */
643        public void setEnd(int end) {
644            this.end = end;
645        }
646
647        /**
648         * Gets the start.
649         *
650         * @return the start
651         */
652        public int getStart() {
653            return start;
654        }
655
656        /**
657         * Gets the end.
658         *
659         * @return the end
660         */
661        public int getEnd() {
662            return end;
663        }
664
665        /**
666         * Gets the text.
667         *
668         * @return non-null; the text
669         */
670        public String getText() {
671            return text;
672        }
673
674        public int getIndent() {
675            return indent;
676        }
677    }
678}
679