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