• 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[]}.
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, 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      */
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(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      */
ByteArrayAnnotatedOutput(int size)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      */
ByteArrayAnnotatedOutput(byte[] data, boolean stretchy)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      */
getArray()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      */
toByteArray()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} */
getCursor()144     public int getCursor() {
145         return cursor;
146     }
147 
148     /** {@inheritDoc} */
assertCursor(int expectedCursor)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} */
writeByte(int value)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} */
writeShort(int value)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} */
writeInt(int value)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} */
writeLong(long value)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} */
writeUleb128(int value)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} */
writeSleb128(int value)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} */
write(ByteArray bytes)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} */
write(byte[] bytes, int offset, int length)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} */
write(byte[] bytes)297     public void write(byte[] bytes) {
298         write(bytes, 0, bytes.length);
299     }
300 
301     /** {@inheritDoc} */
writeZeroes(int count)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} */
alignTo(int alignment)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} */
annotates()350     public boolean annotates() {
351         return (annotations != null);
352     }
353 
354     /** {@inheritDoc} */
isVerbose()355     public boolean isVerbose() {
356         return verbose;
357     }
358 
359     /** {@inheritDoc} */
annotate(String msg)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} */
annotate(int amt, String msg)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} */
endAnnotation()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} */
getAnnotationWidth()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      */
enableAnnotations(int annotationWidth, boolean verbose)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      */
finishAnnotating()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      */
writeAnnotationsTo(Writer out)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      */
throwBounds()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      */
ensureCapacity(int desiredSize)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          */
Annotation(int start, int end, String text)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          */
Annotation(int start, String text)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          */
setEndIfUnset(int end)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          */
setEnd(int end)601         public void setEnd(int end) {
602             this.end = end;
603         }
604 
605         /**
606          * Gets the start.
607          *
608          * @return the start
609          */
getStart()610         public int getStart() {
611             return start;
612         }
613 
614         /**
615          * Gets the end.
616          *
617          * @return the end
618          */
getEnd()619         public int getEnd() {
620             return end;
621         }
622 
623         /**
624          * Gets the text.
625          *
626          * @return {@code non-null;} the text
627          */
getText()628         public String getText() {
629             return text;
630         }
631     }
632 }
633