AnnotationDirectoryItem.java revision a59fe7e5232eea406a6f7b6055eeb5884683f8b2
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
29package org.jf.dexlib;
30
31import com.google.common.base.Preconditions;
32import org.jf.dexlib.Util.AnnotatedOutput;
33import org.jf.dexlib.Util.ExceptionWithContext;
34import org.jf.dexlib.Util.Input;
35import org.jf.dexlib.Util.ReadOnlyArrayList;
36
37import javax.annotation.Nonnull;
38import javax.annotation.Nullable;
39import java.util.*;
40
41public 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