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