AnnotationLister.java revision 99409883d9c4c0ffb49b070ce307bb33a9dfe9f1
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    /**
42     * The string name of the pseudo-class that
43     * contains package-wide annotations
44     */
45    private static final String PACKAGE_INFO = "package-info";
46
47    /** current match configuration */
48    private final Main.Arguments args;
49
50    /** Set of classes whose inner classes should be considered matched */
51    HashSet<String> matchInnerClassesOf = new HashSet<String>();
52
53    /** set of packages whose classes should be considered matched */
54    HashSet<String> matchPackages = new HashSet<String>();
55
56    AnnotationLister (Main.Arguments args) {
57        this.args = args;
58    }
59
60    /** Processes based on configuration specified in constructor. */
61    void process() {
62        for (String path: args.files) {
63            ClassPathOpener opener;
64
65            opener = new ClassPathOpener(path, true,
66                    new ClassPathOpener.Consumer() {
67                public boolean processFileBytes(String name, byte[] bytes) {
68                    if (!name.endsWith(".class")) {
69                        return true;
70                    }
71
72                    ByteArray ba = new ByteArray(bytes);
73                    DirectClassFile cf
74                        = new DirectClassFile(ba, name, true);
75
76                    cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
77                    AttributeList attributes = cf.getAttributes();
78                    Attribute att;
79
80                    String cfClassName
81                            = cf.getThisClass().getClassType().getClassName();
82
83                    if (cfClassName.endsWith(PACKAGE_INFO)) {
84                        att = attributes.findFirst(
85                                AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME);
86
87                        for (;att != null; att = attributes.findNext(att)) {
88                            BaseAnnotations ann = (BaseAnnotations)att;
89                            visitPackageAnnotation(cf, ann);
90                        }
91
92                        att = attributes.findFirst(
93                                AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME);
94
95                        for (;att != null; att = attributes.findNext(att)) {
96                            BaseAnnotations ann = (BaseAnnotations)att;
97                            visitPackageAnnotation(cf, ann);
98                        }
99                    } else if (isMatchingInnerClass(cfClassName)
100                            || isMatchingPackage(cfClassName)) {
101                        printMatch(cf);
102                    } else {
103                        att = attributes.findFirst(
104                                AttRuntimeInvisibleAnnotations.ATTRIBUTE_NAME);
105
106                        for (;att != null; att = attributes.findNext(att)) {
107                            BaseAnnotations ann = (BaseAnnotations)att;
108                            visitClassAnnotation(cf, ann);
109                        }
110
111                        att = attributes.findFirst(
112                                AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME);
113
114                        for (;att != null; att = attributes.findNext(att)) {
115                            BaseAnnotations ann = (BaseAnnotations)att;
116                            visitClassAnnotation(cf, ann);
117                        }
118                    }
119
120                    return true;
121                }
122
123                public void onException(Exception ex) {
124                    throw new RuntimeException(ex);
125                }
126
127                public void onProcessArchiveStart(File file) {
128
129                }
130
131            });
132
133            opener.process();
134        }
135    }
136
137    /**
138     * Inspects a class annotation.
139     *
140     * @param cf {@code non-null;} class file
141     * @param ann {@code non-null;} annotation
142     */
143    private void visitClassAnnotation(DirectClassFile cf,
144            BaseAnnotations ann) {
145
146        if (!args.eTypes.contains(ElementType.TYPE)) {
147            return;
148        }
149
150        for (Annotation anAnn: ann.getAnnotations().getAnnotations()) {
151            String annClassName
152                    = anAnn.getType().getClassType().getClassName();
153            if (args.aclass.equals(annClassName)) {
154                printMatch(cf);
155            }
156        }
157    }
158
159    /**
160     * Inspects a package annotation
161     *
162     * @param cf {@code non-null;} class file of "package-info" pseudo-class
163     * @param ann {@code non-null;} annotation
164     */
165    private void visitPackageAnnotation(
166            DirectClassFile cf, BaseAnnotations ann) {
167
168        if (!args.eTypes.contains(ElementType.PACKAGE)) {
169            return;
170        }
171
172        String packageName = cf.getThisClass().getClassType().getClassName();
173
174        int slashIndex = packageName.lastIndexOf('/');
175
176        if (slashIndex == -1) {
177            packageName = "";
178        } else {
179            packageName
180                    = packageName.substring(0, slashIndex);
181        }
182
183
184        for (Annotation anAnn: ann.getAnnotations().getAnnotations()) {
185            String annClassName
186                    = anAnn.getType().getClassType().getClassName();
187            if (args.aclass.equals(annClassName)) {
188                printMatchPackage(packageName);
189            }
190        }
191    }
192
193
194    /**
195     * Prints, or schedules for printing, elements related to a
196     * matching package.
197     *
198     * @param packageName {@code non-null;} name of package
199     */
200    private void printMatchPackage(String packageName) {
201        for(Main.PrintType pt: args.printTypes) {
202            switch (pt) {
203                case CLASS:
204                case INNERCLASS:
205                case METHOD:
206                    matchPackages.add(packageName);
207                    break;
208                case PACKAGE:
209                    System.out.println(packageName.replace('/','.'));
210                    break;
211            }
212        }
213    }
214
215    /**
216     * Prints, or schedules for printing, elements related to a matching
217     * class.
218     *
219     * @param cf {@code non-null;} matching class
220     */
221    private void printMatch(DirectClassFile cf) {
222        for(Main.PrintType pt: args.printTypes) {
223            switch (pt) {
224                case CLASS:
225                    String classname;
226                    classname = 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