1/*
2 * Copyright 2012, Google Inc.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32package org.jf.dexlib2.dexbacked.util;
33
34import com.google.common.collect.ImmutableList;
35import com.google.common.collect.ImmutableSet;
36import org.jf.dexlib2.dexbacked.DexBackedAnnotation;
37import org.jf.dexlib2.dexbacked.DexBackedDexFile;
38
39import javax.annotation.Nonnull;
40import java.util.List;
41import java.util.Set;
42
43public abstract class AnnotationsDirectory {
44    public static final AnnotationsDirectory EMPTY = new AnnotationsDirectory() {
45        @Override public int getFieldAnnotationCount() { return 0; }
46        @Nonnull @Override public Set<? extends DexBackedAnnotation> getClassAnnotations() { return ImmutableSet.of(); }
47        @Nonnull @Override public AnnotationIterator getFieldAnnotationIterator() { return AnnotationIterator.EMPTY; }
48        @Nonnull @Override public AnnotationIterator getMethodAnnotationIterator() { return AnnotationIterator.EMPTY; }
49        @Nonnull @Override public AnnotationIterator getParameterAnnotationIterator() {return AnnotationIterator.EMPTY;}
50    };
51
52    public abstract int getFieldAnnotationCount();
53    @Nonnull public abstract Set<? extends DexBackedAnnotation> getClassAnnotations();
54    @Nonnull public abstract AnnotationIterator getFieldAnnotationIterator();
55    @Nonnull public abstract AnnotationIterator getMethodAnnotationIterator();
56    @Nonnull public abstract AnnotationIterator getParameterAnnotationIterator();
57
58    @Nonnull
59    public static AnnotationsDirectory newOrEmpty(@Nonnull DexBackedDexFile dexFile,
60                                                  int directoryAnnotationsOffset) {
61        if (directoryAnnotationsOffset == 0) {
62            return EMPTY;
63        }
64        return new AnnotationsDirectoryImpl(dexFile, directoryAnnotationsOffset);
65    }
66
67    /**
68     * This provides a forward-only, skipable iteration over the field_annotation, method_annotation or
69     * parameter_annotation lists in an annotations_directory_item.
70     *
71     * These lists associate a key, either a field or method index, with an offset to where the annotation data for
72     * that field/method/parameter is stored.
73     */
74    public interface AnnotationIterator {
75        public static final AnnotationIterator EMPTY = new AnnotationIterator() {
76            @Override public int seekTo(int key) { return 0; }
77            @Override public void reset() {}
78        };
79
80        /**
81         * Seeks the iterator forward, to the first item whose key is >= the requested key. If the requested key value
82         * is less than that of the item that the iterator currently points to, it will not be moved forward.
83         *
84         * If an item with the requested key is found, the associated annotation offset is returned. Otherwise, 0 is
85         * returned.
86         *
87         * @param key The method/field index to search for
88         * @return The annotation offset associated with the requested key, or 0 if not found.
89         */
90        public int seekTo(int key);
91
92        /**
93         * Resets the iterator to the beginning of its list.
94         */
95        public void reset();
96    }
97
98    @Nonnull
99    public static Set<? extends DexBackedAnnotation> getAnnotations(@Nonnull final DexBackedDexFile dexFile,
100                                                                     final int annotationSetOffset) {
101        if (annotationSetOffset != 0) {
102            final int size = dexFile.readSmallUint(annotationSetOffset);
103            return new FixedSizeSet<DexBackedAnnotation>() {
104                @Nonnull
105                @Override
106                public DexBackedAnnotation readItem(int index) {
107                    int annotationOffset = dexFile.readSmallUint(annotationSetOffset + 4 + (4*index));
108                    return new DexBackedAnnotation(dexFile, annotationOffset);
109                }
110
111                @Override public int size() { return size; }
112            };
113        }
114
115        return ImmutableSet.of();
116    }
117
118    @Nonnull
119    public static List<Set<? extends DexBackedAnnotation>> getParameterAnnotations(
120            @Nonnull final DexBackedDexFile dexFile, final int annotationSetListOffset) {
121        if (annotationSetListOffset > 0) {
122            final int size = dexFile.readSmallUint(annotationSetListOffset);
123
124            return new FixedSizeList<Set<? extends DexBackedAnnotation>>() {
125                @Nonnull
126                @Override
127                public Set<? extends DexBackedAnnotation> readItem(int index) {
128                    int annotationSetOffset = dexFile.readSmallUint(annotationSetListOffset + 4 + index * 4);
129                    return getAnnotations(dexFile, annotationSetOffset);
130                }
131
132                @Override public int size() { return size; }
133            };
134        }
135        return ImmutableList.of();
136    }
137
138    private static class AnnotationsDirectoryImpl extends AnnotationsDirectory {
139        @Nonnull public final DexBackedDexFile dexFile;
140        private final int directoryOffset;
141
142        private static final int FIELD_COUNT_OFFSET = 4;
143        private static final int METHOD_COUNT_OFFSET = 8;
144        private static final int PARAMETER_COUNT_OFFSET = 12;
145        private static final int ANNOTATIONS_START_OFFSET = 16;
146
147        /** The size of a field_annotation structure */
148        private static final int FIELD_ANNOTATION_SIZE = 8;
149        /** The size of a method_annotation structure */
150        private static final int METHOD_ANNOTATION_SIZE = 8;
151
152        public AnnotationsDirectoryImpl(@Nonnull DexBackedDexFile dexFile,
153                                        int directoryOffset) {
154            this.dexFile = dexFile;
155            this.directoryOffset = directoryOffset;
156        }
157
158        public int getFieldAnnotationCount() {
159            return dexFile.readSmallUint(directoryOffset + FIELD_COUNT_OFFSET);
160        }
161
162        public int getMethodAnnotationCount() {
163            return dexFile.readSmallUint(directoryOffset + METHOD_COUNT_OFFSET);
164        }
165
166        public int getParameterAnnotationCount() {
167            return dexFile.readSmallUint(directoryOffset + PARAMETER_COUNT_OFFSET);
168        }
169
170        @Nonnull
171        public Set<? extends DexBackedAnnotation> getClassAnnotations() {
172            return getAnnotations(dexFile, dexFile.readSmallUint(directoryOffset));
173        }
174
175        @Nonnull
176        public AnnotationIterator getFieldAnnotationIterator() {
177            int fieldAnnotationCount = getFieldAnnotationCount();
178            if (fieldAnnotationCount == 0) {
179                return AnnotationIterator.EMPTY;
180            }
181            return new AnnotationIteratorImpl(directoryOffset + ANNOTATIONS_START_OFFSET, fieldAnnotationCount);
182        }
183
184        @Nonnull
185        public AnnotationIterator getMethodAnnotationIterator() {
186            int methodCount = getMethodAnnotationCount();
187            if (methodCount == 0) {
188                return AnnotationIterator.EMPTY;
189            }
190            int fieldCount = getFieldAnnotationCount();
191            int methodAnnotationsOffset = directoryOffset + ANNOTATIONS_START_OFFSET +
192                    fieldCount * FIELD_ANNOTATION_SIZE;
193            return new AnnotationIteratorImpl(methodAnnotationsOffset, methodCount);
194        }
195
196        @Nonnull
197        public AnnotationIterator getParameterAnnotationIterator() {
198            int parameterAnnotationCount = getParameterAnnotationCount();
199            if (parameterAnnotationCount == 0) {
200                return AnnotationIterator.EMPTY;
201            }
202            int fieldCount = getFieldAnnotationCount();
203            int methodCount = getMethodAnnotationCount();
204            int parameterAnnotationsOffset = directoryOffset + ANNOTATIONS_START_OFFSET +
205                    fieldCount * FIELD_ANNOTATION_SIZE +
206                    methodCount * METHOD_ANNOTATION_SIZE;
207            return new AnnotationIteratorImpl(parameterAnnotationsOffset, parameterAnnotationCount);
208        }
209
210        private class AnnotationIteratorImpl implements AnnotationIterator {
211            private final int startOffset;
212            private final int size;
213            private int currentIndex;
214            private int currentItemIndex;
215
216            public AnnotationIteratorImpl(int startOffset, int size) {
217                this.startOffset = startOffset;
218                this.size = size;
219                this.currentItemIndex = dexFile.readSmallUint(startOffset);
220                this.currentIndex = 0;
221            }
222
223            public int seekTo(int itemIndex) {
224                while (currentItemIndex < itemIndex && (currentIndex+1) < size) {
225                    currentIndex++;
226                    currentItemIndex = dexFile.readSmallUint(startOffset + (currentIndex*8));
227                }
228
229                if (currentItemIndex == itemIndex) {
230                    return dexFile.readSmallUint(startOffset + (currentIndex*8)+4);
231                }
232                return 0;
233            }
234
235            public void reset() {
236                this.currentItemIndex = dexFile.readSmallUint(startOffset);
237                this.currentIndex = 0;
238            }
239        }
240    }
241}
242