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