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