ByteArrayOutput.java revision 9ab2b45ec8531658e3acf0b96b11a214ce8d3b60
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
17package org.jf.dexlib.Util;
18
19import java.util.ArrayList;
20
21/**
22 * Implementation of {@link AnnotatedOutput} which stores the written data
23 * into a <code>byte[]</code>.
24 *
25 * <p><b>Note:</b> As per the {@link Output} interface, multi-byte
26 * writes all use little-endian order.</p>
27 */
28public final class ByteArrayOutput implements Output
29{
30    /** default size for stretchy instances */
31    private static final int DEFAULT_SIZE = 1000;
32
33    /**
34     * whether the instance is stretchy, that is, whether its array
35     * may be resized to increase capacity
36     */
37    private final boolean stretchy;
38
39    /** non-null; the data itself */
40    private byte[] data;
41
42    /** &gt;= 0; current output cursor */
43    private int cursor;
44
45    /** whether annotations are to be verbose */
46    private boolean verbose;
47
48    /**
49     * null-ok; list of annotations, or <code>null</code> if this instance
50     * isn't keeping them
51     */
52    private ArrayList<Annotation> annotations;
53
54    /** &gt;= 40 (if used); the desired maximum annotation width */
55    private int annotationWidth;
56
57    /**
58     * &gt;= 8 (if used); the number of bytes of hex output to use
59     * in annotations
60     */
61    private int hexCols;
62
63    /**
64     * Constructs an instance with a fixed maximum size. Note that the
65     * given array is the only one that will be used to store data. In
66     * particular, no reallocation will occur in order to expand the
67     * capacity of the resulting instance. Also, the constructed
68     * instance does not keep annotations by default.
69     *
70     * @param data non-null; data array to use for output
71     */
72    public ByteArrayOutput(byte[] data) {
73        this(data, false);
74    }
75
76    /**
77     * Constructs a "stretchy" instance. The underlying array may be
78     * reallocated. The constructed instance does not keep annotations
79     * by default.
80     */
81    public ByteArrayOutput() {
82        this(new byte[DEFAULT_SIZE], true);
83    }
84
85    /**
86     * Internal constructor.
87     *
88     * @param data non-null; data array to use for output
89     * @param stretchy whether the instance is to be stretchy
90     */
91    private ByteArrayOutput(byte[] data, boolean stretchy) {
92        if (data == null) {
93            throw new NullPointerException("data == null");
94        }
95
96        this.stretchy = stretchy;
97        this.data = data;
98        this.cursor = 0;
99        this.verbose = false;
100        this.annotations = null;
101        this.annotationWidth = 0;
102        this.hexCols = 0;
103    }
104
105    /**
106     * Gets the underlying <code>byte[]</code> of this instance, which
107     * may be larger than the number of bytes written
108     *
109     * @see #toByteArray
110     *
111     * @return non-null; the <code>byte[]</code>
112     */
113    public byte[] getArray() {
114        return data;
115    }
116
117    /**
118     * Constructs and returns a new <code>byte[]</code> that contains
119     * the written contents exactly (that is, with no extra unwritten
120     * bytes at the end).
121     *
122     * @see #getArray
123     *
124     * @return non-null; an appropriately-constructed array
125     */
126    public byte[] toByteArray() {
127        byte[] result = new byte[cursor];
128        System.arraycopy(data, 0, result, 0, cursor);
129        return result;
130    }
131
132    /** {@inheritDoc} */
133    public int getCursor() {
134        return cursor;
135    }
136
137    /** {@inheritDoc} */
138    public void assertCursor(int expectedCursor) {
139        if (cursor != expectedCursor) {
140            throw new ExceptionWithContext("expected cursor " +
141                    expectedCursor + "; actual value: " + cursor);
142        }
143    }
144
145    /** {@inheritDoc} */
146    public void writeByte(int value) {
147        int writeAt = cursor;
148        int end = writeAt + 1;
149
150        if (stretchy) {
151            ensureCapacity(end);
152        } else if (end > data.length) {
153            throwBounds();
154            return;
155        }
156
157        data[writeAt] = (byte) value;
158        cursor = end;
159    }
160
161    /** {@inheritDoc} */
162    public void writeShort(int value) {
163        int writeAt = cursor;
164        int end = writeAt + 2;
165
166        if (stretchy) {
167            ensureCapacity(end);
168        } else if (end > data.length) {
169            throwBounds();
170            return;
171        }
172
173        data[writeAt] = (byte) value;
174        data[writeAt + 1] = (byte) (value >> 8);
175        cursor = end;
176    }
177
178    /** {@inheritDoc} */
179    public void writeInt(int value) {
180        int writeAt = cursor;
181        int end = writeAt + 4;
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        data[writeAt + 2] = (byte) (value >> 16);
193        data[writeAt + 3] = (byte) (value >> 24);
194        cursor = end;
195    }
196
197    /** {@inheritDoc} */
198    public void writeLong(long value) {
199        int writeAt = cursor;
200        int end = writeAt + 8;
201
202        if (stretchy) {
203            ensureCapacity(end);
204        } else if (end > data.length) {
205            throwBounds();
206            return;
207        }
208
209        int half = (int) value;
210        data[writeAt] = (byte) half;
211        data[writeAt + 1] = (byte) (half >> 8);
212        data[writeAt + 2] = (byte) (half >> 16);
213        data[writeAt + 3] = (byte) (half >> 24);
214
215        half = (int) (value >> 32);
216        data[writeAt + 4] = (byte) half;
217        data[writeAt + 5] = (byte) (half >> 8);
218        data[writeAt + 6] = (byte) (half >> 16);
219        data[writeAt + 7] = (byte) (half >> 24);
220
221        cursor = end;
222    }
223
224    /** {@inheritDoc} */
225    public int writeUnsignedLeb128(int value) {
226        int remaining = value >>> 7;
227        int count = 0;
228
229        while (remaining != 0) {
230            writeByte((value & 0x7f) | 0x80);
231            value = remaining;
232            remaining >>>= 7;
233            count++;
234        }
235
236        writeByte(value & 0x7f);
237        return count + 1;
238    }
239
240    /** {@inheritDoc} */
241    public int writeSignedLeb128(int value) {
242        int remaining = value >> 7;
243        int count = 0;
244        boolean hasMore = true;
245        int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1;
246
247        while (hasMore) {
248            hasMore = (remaining != end)
249                || ((remaining & 1) != ((value >> 6) & 1));
250
251            writeByte((value & 0x7f) | (hasMore ? 0x80 : 0));
252            value = remaining;
253            remaining >>= 7;
254            count++;
255        }
256
257        return count;
258    }
259
260    /** {@inheritDoc} */
261    public void write(ByteArray bytes) {
262        int blen = bytes.size();
263        int writeAt = cursor;
264        int end = writeAt + blen;
265
266        if (stretchy) {
267            ensureCapacity(end);
268        } else if (end > data.length) {
269            throwBounds();
270            return;
271        }
272
273        bytes.getBytes(data, writeAt);
274        cursor = end;
275    }
276
277    /** {@inheritDoc} */
278    public void write(byte[] bytes, int offset, int length) {
279        int writeAt = cursor;
280        int end = writeAt + length;
281        int bytesEnd = offset + length;
282
283        // twos-complement math trick: ((x < 0) || (y < 0)) <=> ((x|y) < 0)
284        if (((offset | length | end) < 0) || (bytesEnd > bytes.length)) {
285            throw new IndexOutOfBoundsException("bytes.length " +
286                                                bytes.length + "; " +
287                                                offset + "..!" + end);
288        }
289
290        if (stretchy) {
291            ensureCapacity(end);
292        } else if (end > data.length) {
293            throwBounds();
294            return;
295        }
296
297        System.arraycopy(bytes, offset, data, writeAt, length);
298        cursor = end;
299    }
300
301    /** {@inheritDoc} */
302    public void write(byte[] bytes) {
303        write(bytes, 0, bytes.length);
304    }
305
306    /** {@inheritDoc} */
307    public void writeZeroes(int count) {
308        if (count < 0) {
309            throw new IllegalArgumentException("count < 0");
310        }
311
312        int end = cursor + count;
313
314        if (stretchy) {
315            ensureCapacity(end);
316        } else if (end > data.length) {
317            throwBounds();
318            return;
319        }
320
321        /*
322         * There is no need to actually write zeroes, since the array is
323         * already preinitialized with zeroes.
324         */
325
326        cursor = end;
327    }
328
329    /** {@inheritDoc} */
330    public void alignTo(int alignment) {
331        int end = AlignmentUtils.alignOffset(cursor, alignment);
332
333        if (stretchy) {
334            ensureCapacity(end);
335        } else if (end > data.length) {
336            throwBounds();
337            return;
338        }
339        cursor = end;
340    }
341
342    /** {@inheritDoc} */
343    public boolean annotates() {
344        return (annotations != null);
345    }
346
347    /** {@inheritDoc} */
348    public boolean isVerbose() {
349        return verbose;
350    }
351
352    /** {@inheritDoc} */
353    public void annotate(String msg) {
354        if (annotations == null) {
355            return;
356        }
357
358        endAnnotation();
359        annotations.add(new Annotation(cursor, msg));
360    }
361
362    /** {@inheritDoc} */
363    public void annotate(int amt, String msg) {
364        if (annotations == null) {
365            return;
366        }
367
368        endAnnotation();
369
370        int asz = annotations.size();
371        int lastEnd = (asz == 0) ? 0 : annotations.get(asz - 1).getEnd();
372        int startAt;
373
374        if (lastEnd <= cursor) {
375            startAt = cursor;
376        } else {
377            startAt = lastEnd;
378        }
379
380        annotations.add(new Annotation(startAt, startAt + amt, msg));
381    }
382
383    /** {@inheritDoc} */
384    public void endAnnotation() {
385        if (annotations == null) {
386            return;
387        }
388
389        int sz = annotations.size();
390
391        if (sz != 0) {
392            annotations.get(sz - 1).setEndIfUnset(cursor);
393        }
394    }
395
396    /** {@inheritDoc} */
397    public int getAnnotationWidth() {
398        int leftWidth = 8 + (hexCols * 2) + (hexCols / 2);
399
400        return annotationWidth - leftWidth;
401    }
402
403    /**
404     * Indicates that this instance should keep annotations. This method may
405     * be called only once per instance, and only before any data has been
406     * written to the it.
407     *
408     * @param annotationWidth &gt;= 40; the desired maximum annotation width
409     * @param verbose whether or not to indicate verbose annotations
410     */
411    public void enableAnnotations(int annotationWidth, boolean verbose) {
412        if ((annotations != null) || (cursor != 0)) {
413            throw new RuntimeException("cannot enable annotations");
414        }
415
416        if (annotationWidth < 40) {
417            throw new IllegalArgumentException("annotationWidth < 40");
418        }
419
420        int hexCols = (((annotationWidth - 7) / 15) + 1) & ~1;
421        if (hexCols < 6) {
422            hexCols = 6;
423        } else if (hexCols > 10) {
424            hexCols = 10;
425        }
426
427        this.annotations = new ArrayList<Annotation>(1000);
428        this.annotationWidth = annotationWidth;
429        this.hexCols = hexCols;
430        this.verbose = verbose;
431    }
432
433    /**
434     * Finishes up annotation processing. This closes off any open
435     * annotations and removes annotations that don't refer to written
436     * data.
437     */
438    public void finishAnnotating() {
439        // Close off the final annotation, if any.
440        endAnnotation();
441
442        // Remove annotations that refer to unwritten data.
443        if (annotations != null) {
444            int asz = annotations.size();
445            while (asz > 0) {
446                Annotation last = annotations.get(asz - 1);
447                if (last.getStart() > cursor) {
448                    annotations.remove(asz - 1);
449                    asz--;
450                } else if (last.getEnd() > cursor) {
451                    last.setEnd(cursor);
452                    break;
453                } else {
454                    break;
455                }
456            }
457        }
458    }
459
460    /**
461     * Throws the excpetion for when an attempt is made to write past the
462     * end of the instance.
463     */
464    private static void throwBounds() {
465        throw new IndexOutOfBoundsException("attempt to write past the end");
466    }
467
468    /**
469     * Reallocates the underlying array if necessary. Calls to this method
470     * should be guarded by a test of {@link #stretchy}.
471     *
472     * @param desiredSize &gt;= 0; the desired minimum total size of the array
473     */
474    private void ensureCapacity(int desiredSize) {
475        if (data.length < desiredSize) {
476            byte[] newData = new byte[desiredSize * 2 + 1000];
477            System.arraycopy(data, 0, newData, 0, cursor);
478            data = newData;
479        }
480    }
481
482    /**
483     * Annotation on output.
484     */
485    private static class Annotation {
486        /** &gt;= 0; start of annotated range (inclusive) */
487        private final int start;
488
489        /**
490         * &gt;= 0; end of annotated range (exclusive);
491         * <code>Integer.MAX_VALUE</code> if unclosed
492         */
493        private int end;
494
495        /** non-null; annotation text */
496        private final String text;
497
498        /**
499         * Constructs an instance.
500         *
501         * @param start &gt;= 0; start of annotated range
502         * @param end &gt;= start; end of annotated range (exclusive) or
503         * <code>Integer.MAX_VALUE</code> if unclosed
504         * @param text non-null; annotation text
505         */
506        public Annotation(int start, int end, String text) {
507            this.start = start;
508            this.end = end;
509            this.text = text;
510        }
511
512        /**
513         * Constructs an instance. It is initally unclosed.
514         *
515         * @param start &gt;= 0; start of annotated range
516         * @param text non-null; annotation text
517         */
518        public Annotation(int start, String text) {
519            this(start, Integer.MAX_VALUE, text);
520        }
521
522        /**
523         * Sets the end as given, but only if the instance is unclosed;
524         * otherwise, do nothing.
525         *
526         * @param end &gt;= start; the end
527         */
528        public void setEndIfUnset(int end) {
529            if (this.end == Integer.MAX_VALUE) {
530                this.end = end;
531            }
532        }
533
534        /**
535         * Sets the end as given.
536         *
537         * @param end &gt;= start; the end
538         */
539        public void setEnd(int end) {
540            this.end = end;
541        }
542
543        /**
544         * Gets the start.
545         *
546         * @return the start
547         */
548        public int getStart() {
549            return start;
550        }
551
552        /**
553         * Gets the end.
554         *
555         * @return the end
556         */
557        public int getEnd() {
558            return end;
559        }
560
561        /**
562         * Gets the text.
563         *
564         * @return non-null; the text
565         */
566        public String getText() {
567            return text;
568        }
569    }
570}
571