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