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[]</code>. 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 /** non-null; the data itself */ 42 private byte[] data; 43 44 /** >= 0; current output cursor */ 45 private int cursor; 46 47 /** whether annotations are to be verbose */ 48 private boolean verbose; 49 50 /** 51 * null-ok; list of annotations, or <code>null</code> if this instance 52 * isn't keeping them 53 */ 54 private ArrayList<Annotation> annotations; 55 56 /** >= 40 (if used); the desired maximum annotation width */ 57 private int annotationWidth; 58 59 /** 60 * >= 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 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(new byte[DEFAULT_SIZE], true); 85 } 86 87 /** 88 * Internal constructor. 89 * 90 * @param data non-null; data array to use for output 91 * @param stretchy whether the instance is to be stretchy 92 */ ByteArrayAnnotatedOutput(byte[] data, boolean stretchy)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[]</code> of this instance, which 109 * may be larger than the number of bytes written 110 * 111 * @see #toByteArray 112 * 113 * @return non-null; the <code>byte[]</code> 114 */ getArray()115 public byte[] getArray() { 116 return data; 117 } 118 119 /** 120 * Constructs and returns a new <code>byte[]</code> 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 non-null; an appropriately-constructed array 127 */ toByteArray()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} */ getCursor()135 public int getCursor() { 136 return cursor; 137 } 138 139 /** {@inheritDoc} */ assertCursor(int expectedCursor)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} */ writeByte(int value)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} */ writeShort(int value)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} */ writeInt(int value)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} */ writeLong(long value)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} */ writeUnsignedLeb128(int value)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} */ writeSignedLeb128(int value)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} */ write(ByteArray bytes)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} */ write(byte[] bytes, int offset, int length)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} */ write(byte[] bytes)304 public void write(byte[] bytes) { 305 write(bytes, 0, bytes.length); 306 } 307 308 /** {@inheritDoc} */ writeZeroes(int count)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} */ alignTo(int alignment)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} */ annotates()357 public boolean annotates() { 358 return (annotations != null); 359 } 360 361 /** {@inheritDoc} */ isVerbose()362 public boolean isVerbose() { 363 return verbose; 364 } 365 366 /** {@inheritDoc} */ annotate(String msg)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} */ annotate(int amt, String msg)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} */ endAnnotation()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} */ getAnnotationWidth()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 >= 40; the desired maximum annotation width 423 * @param verbose whether or not to indicate verbose annotations 424 */ enableAnnotations(int annotationWidth, boolean verbose)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 */ finishAnnotating()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 non-null; where to write to 478 */ writeAnnotationsTo(Writer out)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 */ throwBounds()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 >= 0; the desired minimum total size of the array 542 */ ensureCapacity(int desiredSize)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 /** >= 0; start of annotated range (inclusive) */ 556 private final int start; 557 558 /** 559 * >= 0; end of annotated range (exclusive); 560 * <code>Integer.MAX_VALUE</code> if unclosed 561 */ 562 private int end; 563 564 /** non-null; annotation text */ 565 private final String text; 566 567 /** 568 * Constructs an instance. 569 * 570 * @param start >= 0; start of annotated range 571 * @param end >= start; end of annotated range (exclusive) or 572 * <code>Integer.MAX_VALUE</code> if unclosed 573 * @param text non-null; annotation text 574 */ Annotation(int start, int end, String text)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 >= 0; start of annotated range 585 * @param text non-null; annotation text 586 */ Annotation(int start, String text)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 >= start; the end 596 */ setEndIfUnset(int end)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 >= start; the end 607 */ setEnd(int end)608 public void setEnd(int end) { 609 this.end = end; 610 } 611 612 /** 613 * Gets the start. 614 * 615 * @return the start 616 */ getStart()617 public int getStart() { 618 return start; 619 } 620 621 /** 622 * Gets the end. 623 * 624 * @return the end 625 */ getEnd()626 public int getEnd() { 627 return end; 628 } 629 630 /** 631 * Gets the text. 632 * 633 * @return non-null; the text 634 */ getText()635 public String getText() { 636 return text; 637 } 638 } 639 } 640