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