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