• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.dx.util;
18 
19 import java.io.IOException;
20 import java.io.Writer;
21 import java.util.ArrayList;
22 
23 /**
24  * Implementation of {@link AnnotatedOutput} which stores the written data
25  * into a <code>byte[]</code>.
26  *
27  * <p><b>Note:</b> As per the {@link Output} interface, multi-byte
28  * writes all use little-endian order.</p>
29  */
30 public 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     /** non-null; the data itself */
42     private byte[] data;
43 
44     /** &gt;= 0; current output cursor */
45     private int cursor;
46 
47     /** whether annotations are to be verbose */
48     private boolean verbose;
49 
50     /**
51      * null-ok; list of annotations, or <code>null</code> if this instance
52      * isn't keeping them
53      */
54     private ArrayList<Annotation> annotations;
55 
56     /** &gt;= 40 (if used); the desired maximum annotation width */
57     private int annotationWidth;
58 
59     /**
60      * &gt;= 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 non-null; data array to use for output
73      */
ByteArrayAnnotatedOutput(byte[] data)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      */
ByteArrayAnnotatedOutput()83     public ByteArrayAnnotatedOutput() {
84         this(new byte[DEFAULT_SIZE], true);
85     }
86 
87     /**
88      * Internal constructor.
89      *
90      * @param data non-null; data array to use for output
91      * @param stretchy whether the instance is to be stretchy
92      */
ByteArrayAnnotatedOutput(byte[] data, boolean stretchy)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[]</code> of this instance, which
109      * may be larger than the number of bytes written
110      *
111      * @see #toByteArray
112      *
113      * @return non-null; the <code>byte[]</code>
114      */
getArray()115     public byte[] getArray() {
116         return data;
117     }
118 
119     /**
120      * Constructs and returns a new <code>byte[]</code> 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 non-null; an appropriately-constructed array
127      */
toByteArray()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} */
getCursor()135     public int getCursor() {
136         return cursor;
137     }
138 
139     /** {@inheritDoc} */
assertCursor(int expectedCursor)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} */
writeByte(int value)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} */
writeShort(int value)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} */
writeInt(int value)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} */
writeLong(long value)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} */
writeUnsignedLeb128(int value)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} */
writeSignedLeb128(int value)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} */
write(ByteArray bytes)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} */
write(byte[] bytes, int offset, int length)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} */
write(byte[] bytes)304     public void write(byte[] bytes) {
305         write(bytes, 0, bytes.length);
306     }
307 
308     /** {@inheritDoc} */
writeZeroes(int count)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} */
alignTo(int alignment)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} */
annotates()357     public boolean annotates() {
358         return (annotations != null);
359     }
360 
361     /** {@inheritDoc} */
isVerbose()362     public boolean isVerbose() {
363         return verbose;
364     }
365 
366     /** {@inheritDoc} */
annotate(String msg)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} */
annotate(int amt, String msg)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} */
endAnnotation()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} */
getAnnotationWidth()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 &gt;= 40; the desired maximum annotation width
423      * @param verbose whether or not to indicate verbose annotations
424      */
enableAnnotations(int annotationWidth, boolean verbose)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      */
finishAnnotating()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 non-null; where to write to
478      */
writeAnnotationsTo(Writer out)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      */
throwBounds()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 &gt;= 0; the desired minimum total size of the array
542      */
ensureCapacity(int desiredSize)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         /** &gt;= 0; start of annotated range (inclusive) */
556         private final int start;
557 
558         /**
559          * &gt;= 0; end of annotated range (exclusive);
560          * <code>Integer.MAX_VALUE</code> if unclosed
561          */
562         private int end;
563 
564         /** non-null; annotation text */
565         private final String text;
566 
567         /**
568          * Constructs an instance.
569          *
570          * @param start &gt;= 0; start of annotated range
571          * @param end &gt;= start; end of annotated range (exclusive) or
572          * <code>Integer.MAX_VALUE</code> if unclosed
573          * @param text non-null; annotation text
574          */
Annotation(int start, int end, String text)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 &gt;= 0; start of annotated range
585          * @param text non-null; annotation text
586          */
Annotation(int start, String text)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 &gt;= start; the end
596          */
setEndIfUnset(int end)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 &gt;= start; the end
607          */
setEnd(int end)608         public void setEnd(int end) {
609             this.end = end;
610         }
611 
612         /**
613          * Gets the start.
614          *
615          * @return the start
616          */
getStart()617         public int getStart() {
618             return start;
619         }
620 
621         /**
622          * Gets the end.
623          *
624          * @return the end
625          */
getEnd()626         public int getEnd() {
627             return end;
628         }
629 
630         /**
631          * Gets the text.
632          *
633          * @return non-null; the text
634          */
getText()635         public String getText() {
636             return text;
637         }
638     }
639 }
640