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