1 /* 2 * [The "BSD licence"] 3 * Copyright (c) 2010 Ben Gruver (JesusFreke) 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 package org.jf.dexlib; 30 31 import com.google.common.base.Preconditions; 32 import org.jf.dexlib.Util.AnnotatedOutput; 33 import org.jf.dexlib.Util.ExceptionWithContext; 34 import org.jf.dexlib.Util.Input; 35 import org.jf.dexlib.Util.ReadOnlyArrayList; 36 37 import javax.annotation.Nonnull; 38 import javax.annotation.Nullable; 39 import java.util.*; 40 41 public class AnnotationDirectoryItem extends Item<AnnotationDirectoryItem> { 42 @Nullable 43 private AnnotationSetItem classAnnotations; 44 @Nullable 45 private FieldAnnotation[] fieldAnnotations; 46 @Nullable 47 private MethodAnnotation[] methodAnnotations; 48 @Nullable 49 private ParameterAnnotation[] parameterAnnotations; 50 51 /** 52 * Creates a new uninitialized <code>AnnotationDirectoryItem</code> 53 * @param dexFile The <code>DexFile</code> that this item belongs to 54 */ 55 protected AnnotationDirectoryItem(DexFile dexFile) { 56 super(dexFile); 57 } 58 59 /** 60 * Creates a new <code>AnnotationDirectoryItem</code> with the given values 61 * @param dexFile The <code>DexFile</code> that this item belongs to 62 * @param classAnnotations The annotations associated with the overall class 63 * @param fieldAnnotations A list of <code>FieldAnnotation</code> objects that contain the field annotations for 64 * this class 65 * @param methodAnnotations A list of <code>MethodAnnotation</code> objects that contain the method annotations for 66 * this class 67 * @param parameterAnnotations A list of <code>ParameterAnnotation</code> objects that contain the parameter 68 * annotations for the methods in this class 69 */ 70 private AnnotationDirectoryItem(DexFile dexFile, @Nullable AnnotationSetItem classAnnotations, 71 @Nullable List<FieldAnnotation> fieldAnnotations, 72 @Nullable List<MethodAnnotation> methodAnnotations, 73 @Nullable List<ParameterAnnotation> parameterAnnotations) { 74 super(dexFile); 75 this.classAnnotations = classAnnotations; 76 77 if (fieldAnnotations == null || fieldAnnotations.size() == 0) { 78 this.fieldAnnotations = null; 79 } else { 80 this.fieldAnnotations = new FieldAnnotation[fieldAnnotations.size()]; 81 this.fieldAnnotations = fieldAnnotations.toArray(this.fieldAnnotations); 82 Arrays.sort(this.fieldAnnotations); 83 } 84 85 if (methodAnnotations == null || methodAnnotations.size() == 0) { 86 this.methodAnnotations = null; 87 } else { 88 this.methodAnnotations = new MethodAnnotation[methodAnnotations.size()]; 89 this.methodAnnotations = methodAnnotations.toArray(this.methodAnnotations); 90 Arrays.sort(this.methodAnnotations); 91 } 92 93 if (parameterAnnotations == null || parameterAnnotations.size() == 0) { 94 this.parameterAnnotations = null; 95 } else { 96 this.parameterAnnotations = new ParameterAnnotation[parameterAnnotations.size()]; 97 this.parameterAnnotations = parameterAnnotations.toArray(this.parameterAnnotations); 98 Arrays.sort(this.parameterAnnotations); 99 } 100 } 101 102 /** 103 * Returns an <code>AnnotationDirectoryItem</code> for the given values, and that has been interned into the given 104 * <code>DexFile</code> 105 * @param dexFile The <code>DexFile</code> that this item belongs to 106 * @param classAnnotations The annotations associated with the class 107 * @param fieldAnnotations A list of <code>FieldAnnotation</code> objects containing the field annotations 108 * @param methodAnnotations A list of <code>MethodAnnotation</code> objects containing the method annotations 109 * @param parameterAnnotations A list of <code>ParameterAnnotation</code> objects containin the parameter 110 * annotations 111 * @return an <code>AnnotationItem</code> for the given values, and that has been interned into the given 112 * <code>DexFile</code> 113 */ 114 public static AnnotationDirectoryItem internAnnotationDirectoryItem(DexFile dexFile, 115 AnnotationSetItem classAnnotations, 116 List<FieldAnnotation> fieldAnnotations, 117 List<MethodAnnotation> methodAnnotations, 118 List<ParameterAnnotation> parameterAnnotations) { 119 AnnotationDirectoryItem annotationDirectoryItem = new AnnotationDirectoryItem(dexFile, classAnnotations, 120 fieldAnnotations, methodAnnotations, parameterAnnotations); 121 return dexFile.AnnotationDirectoriesSection.intern(annotationDirectoryItem); 122 } 123 124 /** {@inheritDoc} */ 125 protected void readItem(Input in, ReadContext readContext) { 126 classAnnotations = (AnnotationSetItem)readContext.getOptionalOffsettedItemByOffset( 127 ItemType.TYPE_ANNOTATION_SET_ITEM, in.readInt()); 128 129 int fieldAnnotationCount = in.readInt(); 130 if (fieldAnnotationCount > 0) { 131 fieldAnnotations = new FieldAnnotation[fieldAnnotationCount]; 132 } else { 133 fieldAnnotations = null; 134 } 135 136 int methodAnnotationCount = in.readInt(); 137 if (methodAnnotationCount > 0) { 138 methodAnnotations = new MethodAnnotation[methodAnnotationCount]; 139 } else { 140 methodAnnotations = null; 141 } 142 143 int parameterAnnotationCount = in.readInt(); 144 if (parameterAnnotationCount > 0) { 145 parameterAnnotations = new ParameterAnnotation[parameterAnnotationCount]; 146 } else { 147 parameterAnnotations = null; 148 } 149 150 if (fieldAnnotations != null) { 151 for (int i=0; i<fieldAnnotations.length; i++) { 152 try { 153 FieldIdItem fieldIdItem = dexFile.FieldIdsSection.getItemByIndex(in.readInt()); 154 AnnotationSetItem fieldAnnotationSet = (AnnotationSetItem)readContext.getOffsettedItemByOffset( 155 ItemType.TYPE_ANNOTATION_SET_ITEM, in.readInt()); 156 fieldAnnotations[i] = new FieldAnnotation(fieldIdItem, fieldAnnotationSet); 157 } catch (Exception ex) { 158 throw ExceptionWithContext.withContext(ex, 159 "Error occured while reading FieldAnnotation at index " + i); 160 } 161 } 162 } 163 164 if (methodAnnotations != null) { 165 for (int i=0; i<methodAnnotations.length; i++) { 166 try { 167 MethodIdItem methodIdItem = dexFile.MethodIdsSection.getItemByIndex(in.readInt()); 168 AnnotationSetItem methodAnnotationSet = (AnnotationSetItem)readContext.getOffsettedItemByOffset( 169 ItemType.TYPE_ANNOTATION_SET_ITEM, in.readInt()); 170 methodAnnotations[i] = new MethodAnnotation(methodIdItem, methodAnnotationSet); 171 } catch (Exception ex) { 172 throw ExceptionWithContext.withContext(ex, 173 "Error occured while reading MethodAnnotation at index " + i); 174 } 175 } 176 } 177 178 if (parameterAnnotations != null) { 179 for (int i=0; i<parameterAnnotations.length; i++) { 180 try { 181 MethodIdItem methodIdItem = dexFile.MethodIdsSection.getItemByIndex(in.readInt()); 182 AnnotationSetRefList paramaterAnnotationSet = (AnnotationSetRefList)readContext.getOffsettedItemByOffset( 183 ItemType.TYPE_ANNOTATION_SET_REF_LIST, in.readInt()); 184 parameterAnnotations[i] = new ParameterAnnotation(methodIdItem, paramaterAnnotationSet); 185 } catch (Exception ex) { 186 throw ExceptionWithContext.withContext(ex, 187 "Error occured while reading ParameterAnnotation at index " + i); 188 } 189 } 190 } 191 } 192 193 /** {@inheritDoc} */ 194 protected int placeItem(int offset) { 195 return offset + 16 + ( 196 (fieldAnnotations==null?0:fieldAnnotations.length) + 197 (methodAnnotations==null?0:methodAnnotations.length) + 198 (parameterAnnotations==null?0:parameterAnnotations.length)) * 8; 199 } 200 201 /** {@inheritDoc} */ 202 protected void writeItem(AnnotatedOutput out) { 203 if (out.annotates()) { 204 TypeIdItem parentType = getParentType(); 205 if (parentType != null) { 206 out.annotate(0, parentType.getTypeDescriptor()); 207 } 208 if (classAnnotations != null) { 209 out.annotate(4, "class_annotations_off: 0x" + Integer.toHexString(classAnnotations.getOffset())); 210 } else { 211 out.annotate(4, "class_annotations_off:"); 212 } 213 214 int length = fieldAnnotations==null?0:fieldAnnotations.length; 215 out.annotate(4, "annotated_fields_size: 0x" + Integer.toHexString(length) + " (" + 216 length + ")"); 217 length = methodAnnotations==null?0:methodAnnotations.length; 218 out.annotate(4, "annotated_methods_size: 0x" + Integer.toHexString(length) + " (" + 219 length + ")"); 220 length = parameterAnnotations==null?0:parameterAnnotations.length; 221 out.annotate(4, "annotated_parameters_size: 0x" + Integer.toHexString(length) + " (" + 222 length + ")"); 223 224 int index; 225 if (fieldAnnotations != null) { 226 index = 0; 227 for (FieldAnnotation fieldAnnotation: fieldAnnotations) { 228 out.annotate(0, "[" + index++ + "] field_annotation"); 229 230 out.indent(); 231 out.annotate(4, "field: " + fieldAnnotation.field.getFieldName().getStringValue() + ":" + 232 fieldAnnotation.field.getFieldType().getTypeDescriptor()); 233 out.annotate(4, "annotations_off: 0x" + 234 Integer.toHexString(fieldAnnotation.annotationSet.getOffset())); 235 out.deindent(); 236 } 237 } 238 239 if (methodAnnotations != null) { 240 index = 0; 241 for (MethodAnnotation methodAnnotation: methodAnnotations) { 242 out.annotate(0, "[" + index++ + "] method_annotation"); 243 out.indent(); 244 out.annotate(4, "method: " + methodAnnotation.method.getMethodString()); 245 out.annotate(4, "annotations_off: 0x" + 246 Integer.toHexString(methodAnnotation.annotationSet.getOffset())); 247 out.deindent(); 248 } 249 } 250 251 if (parameterAnnotations != null) { 252 index = 0; 253 for (ParameterAnnotation parameterAnnotation: parameterAnnotations) { 254 out.annotate(0, "[" + index++ + "] parameter_annotation"); 255 out.indent(); 256 out.annotate(4, "method: " + parameterAnnotation.method.getMethodString()); 257 out.annotate(4, "annotations_off: 0x" + 258 Integer.toHexString(parameterAnnotation.annotationSet.getOffset())); 259 } 260 } 261 } 262 263 out.writeInt(classAnnotations==null?0:classAnnotations.getOffset()); 264 out.writeInt(fieldAnnotations==null?0:fieldAnnotations.length); 265 out.writeInt(methodAnnotations==null?0:methodAnnotations.length); 266 out.writeInt(parameterAnnotations==null?0:parameterAnnotations.length); 267 268 if (fieldAnnotations != null) { 269 for (FieldAnnotation fieldAnnotation: fieldAnnotations) { 270 out.writeInt(fieldAnnotation.field.getIndex()); 271 out.writeInt(fieldAnnotation.annotationSet.getOffset()); 272 } 273 } 274 275 if (methodAnnotations != null) { 276 for (MethodAnnotation methodAnnotation: methodAnnotations) { 277 out.writeInt(methodAnnotation.method.getIndex()); 278 out.writeInt(methodAnnotation.annotationSet.getOffset()); 279 } 280 } 281 282 if (parameterAnnotations != null) { 283 for (ParameterAnnotation parameterAnnotation: parameterAnnotations) { 284 out.writeInt(parameterAnnotation.method.getIndex()); 285 out.writeInt(parameterAnnotation.annotationSet.getOffset()); 286 } 287 } 288 } 289 290 /** {@inheritDoc} */ 291 public ItemType getItemType() { 292 return ItemType.TYPE_ANNOTATIONS_DIRECTORY_ITEM; 293 } 294 295 /** {@inheritDoc} */ 296 public String getConciseIdentity() { 297 TypeIdItem parentType = getParentType(); 298 if (parentType == null) { 299 return "annotation_directory_item @0x" + Integer.toHexString(getOffset()); 300 } 301 return "annotation_directory_item @0x" + Integer.toHexString(getOffset()) + 302 " (" + parentType.getTypeDescriptor() + ")"; 303 } 304 305 /** {@inheritDoc} */ 306 public int compareTo(AnnotationDirectoryItem o) { 307 Preconditions.checkNotNull(o); 308 309 TypeIdItem parentType = getParentType(); 310 TypeIdItem otherParentType = o.getParentType(); 311 if (parentType != null) { 312 if (otherParentType != null) { 313 return parentType.compareTo(otherParentType); 314 } 315 return 1; 316 } 317 if (otherParentType != null) { 318 return -1; 319 } 320 321 if (classAnnotations != null) { 322 if (o.classAnnotations != null) { 323 return classAnnotations.compareTo(o.classAnnotations); 324 } 325 return 1; 326 } 327 return -1; 328 } 329 330 /** 331 * Returns the parent type for an AnnotationDirectoryItem that is guaranteed to have a single parent, or null 332 * for one that may be referenced by multiple classes. 333 * 334 * Specifically, the AnnotationDirectoryItem may be referenced by multiple classes if it has only class annotations, 335 * but not field/method/parameter annotations. 336 * 337 * @return The parent type for this AnnotationDirectoryItem, or null if it may have multiple parents 338 */ 339 @Nullable 340 public TypeIdItem getParentType() { 341 if (fieldAnnotations != null && fieldAnnotations.length > 0) { 342 return fieldAnnotations[0].field.getContainingClass(); 343 } 344 if (methodAnnotations != null && methodAnnotations.length > 0) { 345 return methodAnnotations[0].method.getContainingClass(); 346 } 347 if (parameterAnnotations != null && parameterAnnotations.length > 0) { 348 return parameterAnnotations[0].method.getContainingClass(); 349 } 350 return null; 351 } 352 353 /** 354 * @return An <code>AnnotationSetItem</code> containing the annotations associated with this class, or null 355 * if there are no class annotations 356 */ 357 @Nullable 358 public AnnotationSetItem getClassAnnotations() { 359 return classAnnotations; 360 } 361 362 /** 363 * Get a list of the field annotations in this <code>AnnotationDirectoryItem</code> 364 * @return A list of FieldAnnotation objects, or null if there are no field annotations 365 */ 366 @Nonnull 367 public List<FieldAnnotation> getFieldAnnotations() { 368 if (fieldAnnotations == null) { 369 return Collections.emptyList(); 370 } 371 return ReadOnlyArrayList.of(fieldAnnotations); 372 } 373 374 /** 375 * Get a list of the method annotations in this <code>AnnotationDirectoryItem</code> 376 * @return A list of MethodAnnotation objects, or null if there are no method annotations 377 */ 378 @Nonnull 379 public List<MethodAnnotation> getMethodAnnotations() { 380 if (methodAnnotations == null) { 381 return Collections.emptyList(); 382 } 383 return ReadOnlyArrayList.of(methodAnnotations); 384 } 385 386 /** 387 * Get a list of the parameter annotations in this <code>AnnotationDirectoryItem</code> 388 * @return A list of ParameterAnnotation objects, or null if there are no parameter annotations 389 */ 390 @Nonnull 391 public List<ParameterAnnotation> getParameterAnnotations() { 392 if (parameterAnnotations == null) { 393 return Collections.emptyList(); 394 } 395 return ReadOnlyArrayList.of(parameterAnnotations); 396 } 397 398 /** 399 * Gets the field annotations for the given field, or null if no annotations are defined for that field 400 * @param fieldIdItem The field to get the annotations for 401 * @return An <code>AnnotationSetItem</code> containing the field annotations, or null if none are found 402 */ 403 @Nullable 404 public AnnotationSetItem getFieldAnnotations(FieldIdItem fieldIdItem) { 405 if (fieldAnnotations == null) { 406 return null; 407 } 408 int index = Arrays.binarySearch(fieldAnnotations, fieldIdItem); 409 if (index < 0) { 410 return null; 411 } 412 return fieldAnnotations[index].annotationSet; 413 } 414 415 /** 416 * Gets the method annotations for the given method, or null if no annotations are defined for that method 417 * @param methodIdItem The method to get the annotations for 418 * @return An <code>AnnotationSetItem</code> containing the method annotations, or null if none are found 419 */ 420 @Nullable 421 public AnnotationSetItem getMethodAnnotations(MethodIdItem methodIdItem) { 422 if (methodAnnotations == null) { 423 return null; 424 } 425 int index = Arrays.binarySearch(methodAnnotations, methodIdItem); 426 if (index < 0) { 427 return null; 428 } 429 return methodAnnotations[index].annotationSet; 430 } 431 432 /** 433 * Gets the parameter annotations for the given method, or null if no parameter annotations are defined for that 434 * method 435 * @param methodIdItem The method to get the parameter annotations for 436 * @return An <code>AnnotationSetRefList</code> containing the parameter annotations, or null if none are found 437 */ 438 @Nullable 439 public AnnotationSetRefList getParameterAnnotations(MethodIdItem methodIdItem) { 440 if (parameterAnnotations == null) { 441 return null; 442 } 443 int index = Arrays.binarySearch(parameterAnnotations, methodIdItem); 444 if (index < 0) { 445 return null; 446 } 447 return parameterAnnotations[index].annotationSet; 448 } 449 450 /** 451 * 452 */ 453 public int getClassAnnotationCount() { 454 if (classAnnotations == null) { 455 return 0; 456 } 457 AnnotationItem[] annotations = classAnnotations.getAnnotations(); 458 return annotations.length; 459 } 460 461 /** 462 * @return The number of field annotations in this <code>AnnotationDirectoryItem</code> 463 */ 464 public int getFieldAnnotationCount() { 465 if (fieldAnnotations == null) { 466 return 0; 467 } 468 return fieldAnnotations.length; 469 } 470 471 /** 472 * @return The number of method annotations in this <code>AnnotationDirectoryItem</code> 473 */ 474 public int getMethodAnnotationCount() { 475 if (methodAnnotations == null) { 476 return 0; 477 } 478 return methodAnnotations.length; 479 } 480 481 /** 482 * @return The number of parameter annotations in this <code>AnnotationDirectoryItem</code> 483 */ 484 public int getParameterAnnotationCount() { 485 if (parameterAnnotations == null) { 486 return 0; 487 } 488 return parameterAnnotations.length; 489 } 490 491 @Override 492 public int hashCode() { 493 // If the item has a single parent, we can use the re-use the identity (hash) of that parent 494 TypeIdItem parentType = getParentType(); 495 if (parentType != null) { 496 return parentType.hashCode(); 497 } 498 if (classAnnotations != null) { 499 return classAnnotations.hashCode(); 500 } 501 return 0; 502 } 503 504 @Override 505 public boolean equals(Object o) { 506 if (this==o) { 507 return true; 508 } 509 if (o==null || !this.getClass().equals(o.getClass())) { 510 return false; 511 } 512 513 AnnotationDirectoryItem other = (AnnotationDirectoryItem)o; 514 return (this.compareTo(other) == 0); 515 } 516 517 public static class FieldAnnotation implements Comparable<Convertible<FieldIdItem>>, Convertible<FieldIdItem> { 518 public final FieldIdItem field; 519 public final AnnotationSetItem annotationSet; 520 521 public FieldAnnotation(FieldIdItem field, AnnotationSetItem annotationSet) { 522 this.field = field; 523 this.annotationSet = annotationSet; 524 } 525 526 public int compareTo(Convertible<FieldIdItem> other) { 527 return field.compareTo(other.convert()); 528 } 529 530 @Override 531 public boolean equals(Object o) { 532 if (this == o) return true; 533 if (o == null || getClass() != o.getClass()) return false; 534 535 return compareTo((FieldAnnotation)o) == 0; 536 } 537 538 @Override 539 public int hashCode() { 540 return field.hashCode() + 31 * annotationSet.hashCode(); 541 } 542 543 public FieldIdItem convert() { 544 return field; 545 } 546 } 547 548 public static class MethodAnnotation implements Comparable<Convertible<MethodIdItem>>, Convertible<MethodIdItem> { 549 public final MethodIdItem method; 550 public final AnnotationSetItem annotationSet; 551 552 public MethodAnnotation(MethodIdItem method, AnnotationSetItem annotationSet) { 553 this.method = method; 554 this.annotationSet = annotationSet; 555 } 556 557 public int compareTo(Convertible<MethodIdItem> other) { 558 return method.compareTo(other.convert()); 559 } 560 561 @Override 562 public boolean equals(Object o) { 563 if (this == o) return true; 564 if (o == null || getClass() != o.getClass()) return false; 565 566 return compareTo((MethodAnnotation)o) == 0; 567 } 568 569 @Override 570 public int hashCode() { 571 return method.hashCode() + 31 * annotationSet.hashCode(); 572 } 573 574 public MethodIdItem convert() { 575 return method; 576 } 577 } 578 579 public static class ParameterAnnotation implements Comparable<Convertible<MethodIdItem>>, 580 Convertible<MethodIdItem> { 581 public final MethodIdItem method; 582 public final AnnotationSetRefList annotationSet; 583 584 public ParameterAnnotation(MethodIdItem method, AnnotationSetRefList annotationSet) { 585 this.method = method; 586 this.annotationSet = annotationSet; 587 } 588 589 public int compareTo(Convertible<MethodIdItem> other) { 590 return method.compareTo(other.convert()); 591 } 592 593 @Override 594 public boolean equals(Object o) { 595 if (this == o) return true; 596 if (o == null || getClass() != o.getClass()) return false; 597 598 return compareTo((ParameterAnnotation)o) == 0; 599 } 600 601 @Override 602 public int hashCode() { 603 return method.hashCode() + 31 * annotationSet.hashCode(); 604 } 605 606 public MethodIdItem convert() { 607 return method; 608 } 609 } 610 } 611