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