AnnotationLister.java revision dc86cd9edc8b80953c8b698a83cdaebf6825d798
1/*
2 * Copyright (C) 2008 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 com.android.dx.command.annotool;
18
19import com.android.dx.cf.direct.ClassPathOpener;
20import com.android.dx.cf.direct.DirectClassFile;
21import com.android.dx.cf.direct.StdAttributeFactory;
22import com.android.dx.cf.iface.AttributeList;
23import com.android.dx.cf.iface.Attribute;
24import com.android.dx.cf.attrib.AttRuntimeInvisibleAnnotations;
25import com.android.dx.cf.attrib.BaseAnnotations;
26import com.android.dx.cf.attrib.AttRuntimeVisibleAnnotations;
27import com.android.dx.util.ByteArray;
28import com.android.dx.rop.annotation.Annotation;
29
30import java.io.File;
31import java.lang.annotation.ElementType;
32import java.util.HashSet;
33
34/**
35 * Greps annotations on a set of class files and prints matching elements
36 * to stdout. What counts as a match and what should be printed is controlled
37 * by the {@code Main.Arguments} instance.
38 */
39class AnnotationLister {
40    /**
41     * The string name of the pseudo-class that
42     * contains package-wide annotations
43     */
44    private static final String PACKAGE_INFO = "package-info";
45
46    /** current match configuration */
47    private final Main.Arguments args;
48
49    /** Set of classes whose inner classes should be considered matched */
50    HashSet<String> matchInnerClassesOf = new HashSet<String>();
51
52    /** set of packages whose classes should be considered matched */
53    HashSet<String> matchPackages = new HashSet<String>();
54
55    AnnotationLister (Main.Arguments args) {
56        this.args = args;
57    }
58
59    /** Processes based on configuration specified in constructor. */
60    void process() {
61        for (String path : args.files) {
62            ClassPathOpener opener;
63
64            opener = new ClassPathOpener(path, true,
65                    new ClassPathOpener.Consumer() {
66                public boolean processFileBytes(String name, long lastModified, byte[] bytes) {
67                    if (!name.endsWith(".class")) {
68                        return true;
69                    }
70
71                    ByteArray ba = new ByteArray(bytes);
72                    DirectClassFile cf
73                        = new DirectClassFile(ba, name, true);
74
75                    cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
76                    AttributeList attributes = cf.getAttributes();
77                    Attribute att;
78
79                    String cfClassName
80                            = cf.getThisClass().getClassType().getClassName();
81
82                    if (cfClassName.endsWith(PACKAGE_INFO)) {
83                        att = attributes.findFirst(
84                                AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME);
85
86                        for (;att != null; att = attributes.findNext(att)) {
87                            BaseAnnotations ann = (BaseAnnotations)att;
88                            visitPackageAnnotation(cf, ann);
89                        }
90
91                        att = attributes.findFirst(
92                                AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME);
93
94                        for (;att != null; att = attributes.findNext(att)) {
95                            BaseAnnotations ann = (BaseAnnotations)att;
96                            visitPackageAnnotation(cf, ann);
97                        }
98                    } else if (isMatchingInnerClass(cfClassName)
99                            || isMatchingPackage(cfClassName)) {
100                        printMatch(cf);
101                    } else {
102                        att = attributes.findFirst(
103                                AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME);
104
105                        for (;att != null; att = attributes.findNext(att)) {
106                            BaseAnnotations ann = (BaseAnnotations)att;
107                            visitClassAnnotation(cf, ann);
108                        }
109
110                        att = attributes.findFirst(
111                                AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME);
112
113                        for (;att != null; att = attributes.findNext(att)) {
114                            BaseAnnotations ann = (BaseAnnotations)att;
115                            visitClassAnnotation(cf, ann);
116                        }
117                    }
118
119                    return true;
120                }
121
122                public void onException(Exception ex) {
123                    throw new RuntimeException(ex);
124                }
125
126                public void onProcessArchiveStart(File file) {
127
128                }
129
130            });
131
132            opener.process();
133        }
134    }
135
136    /**
137     * Inspects a class annotation.
138     *
139     * @param cf {@code non-null;} class file
140     * @param ann {@code non-null;} annotation
141     */
142    private void visitClassAnnotation(DirectClassFile cf,
143            BaseAnnotations ann) {
144
145        if (!args.eTypes.contains(ElementType.TYPE)) {
146            return;
147        }
148
149        for (Annotation anAnn : ann.getAnnotations().getAnnotations()) {
150            String annClassName
151                    = anAnn.getType().getClassType().getClassName();
152            if (args.aclass.equals(annClassName)) {
153                printMatch(cf);
154            }
155        }
156    }
157
158    /**
159     * Inspects a package annotation
160     *
161     * @param cf {@code non-null;} class file of "package-info" pseudo-class
162     * @param ann {@code non-null;} annotation
163     */
164    private void visitPackageAnnotation(
165            DirectClassFile cf, BaseAnnotations ann) {
166
167        if (!args.eTypes.contains(ElementType.PACKAGE)) {
168            return;
169        }
170
171        String packageName = cf.getThisClass().getClassType().getClassName();
172
173        int slashIndex = packageName.lastIndexOf('/');
174
175        if (slashIndex == -1) {
176            packageName = "";
177        } else {
178            packageName
179                    = packageName.substring(0, slashIndex);
180        }
181
182
183        for (Annotation anAnn : ann.getAnnotations().getAnnotations()) {
184            String annClassName
185                    = anAnn.getType().getClassType().getClassName();
186            if (args.aclass.equals(annClassName)) {
187                printMatchPackage(packageName);
188            }
189        }
190    }
191
192
193    /**
194     * Prints, or schedules for printing, elements related to a
195     * matching package.
196     *
197     * @param packageName {@code non-null;} name of package
198     */
199    private void printMatchPackage(String packageName) {
200        for (Main.PrintType pt : args.printTypes) {
201            switch (pt) {
202                case CLASS:
203                case INNERCLASS:
204                case METHOD:
205                    matchPackages.add(packageName);
206                    break;
207                case PACKAGE:
208                    System.out.println(packageName.replace('/','.'));
209                    break;
210            }
211        }
212    }
213
214    /**
215     * Prints, or schedules for printing, elements related to a matching
216     * class.
217     *
218     * @param cf {@code non-null;} matching class
219     */
220    private void printMatch(DirectClassFile cf) {
221        for (Main.PrintType pt : args.printTypes) {
222            switch (pt) {
223                case CLASS:
224                    String classname;
225                    classname =
226                        cf.getThisClass().getClassType().getClassName();
227                    classname = classname.replace('/','.');
228                    System.out.println(classname);
229                    break;
230                case INNERCLASS:
231                    matchInnerClassesOf.add(
232                            cf.getThisClass().getClassType().getClassName());
233                    break;
234                case METHOD:
235                    //TODO
236                    break;
237                case PACKAGE:
238                    break;
239            }
240        }
241    }
242
243    /**
244     * Checks to see if a specified class name should be considered a match
245     * due to previous matches.
246     *
247     * @param s {@code non-null;} class name
248     * @return true if this class should be considered a match
249     */
250    private boolean isMatchingInnerClass(String s) {
251        int i;
252
253        while (0 < (i = s.lastIndexOf('$'))) {
254            s = s.substring(0, i);
255            if (matchInnerClassesOf.contains(s)) {
256                return true;
257            }
258        }
259
260        return false;
261    }
262
263    /**
264     * Checks to see if a specified package should be considered a match due
265     * to previous matches.
266     *
267     * @param s {@code non-null;} package name
268     * @return true if this package should be considered a match
269     */
270    private boolean isMatchingPackage(String s) {
271        int slashIndex = s.lastIndexOf('/');
272
273        String packageName;
274        if (slashIndex == -1) {
275            packageName = "";
276        } else {
277            packageName
278                    = s.substring(0, slashIndex);
279        }
280
281        return matchPackages.contains(packageName);
282    }
283}
284