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