Home | History | Annotate | Download | only in util
      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.dexgen.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 {
     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