• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 
17 package com.android.tools.lint.client.api;
18 
19 import com.android.annotations.NonNull;
20 import com.android.annotations.Nullable;
21 import com.android.tools.lint.detector.api.Category;
22 import com.android.tools.lint.detector.api.Detector;
23 import com.android.tools.lint.detector.api.Issue;
24 import com.android.tools.lint.detector.api.Scope;
25 import com.android.tools.lint.detector.api.Severity;
26 import com.google.common.annotations.Beta;
27 
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.EnumSet;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.Set;
36 
37 /** Registry which provides a list of checks to be performed on an Android project
38  * <p>
39  * <b>NOTE: This is not a public or final API; if you rely on this be prepared
40  * to adjust your code for the next tools release.</b>
41  */
42 @Beta
43 public abstract class IssueRegistry {
44     private static List<Category> sCategories;
45     private static Map<String, Issue> sIdToIssue;
46 
47     /**
48      * Issue reported by lint (not a specific detector) when it cannot even
49      * parse an XML file prior to analysis
50      */
51     @NonNull
52     public static final Issue PARSER_ERROR = Issue.create(
53             "ParserError", //$NON-NLS-1$
54             "Finds files that contain fatal parser errors",
55             "Lint will ignore any files that contain fatal parsing errors. These may contain " +
56             "other errors, or contain code which affects issues in other files.",
57             Category.CORRECTNESS,
58             10,
59             Severity.ERROR,
60             null,
61             Scope.RESOURCE_FILE_SCOPE);
62 
63     /**
64      * Issue reported by lint for various other issues which prevents lint from
65      * running normally when it's not necessarily an error in the user's code base.
66      */
67     @NonNull
68     public static final Issue LINT_ERROR = Issue.create(
69             "LintError", //$NON-NLS-1$
70             "Isues related to running lint itself, such as failure to read files, etc",
71             "This issue type represents a problem running lint itself. Examples include " +
72             "failure to find bytecode for source files (which means certain detectors " +
73             "could not be run), parsing errors in lint configuration files, etc." +
74             "\n" +
75             "These errors are not errors in your own code, but they are shown to make " +
76             "it clear that some checks were not completed.",
77 
78             Category.LINT,
79             10,
80             Severity.ERROR,
81             null,
82             Scope.RESOURCE_FILE_SCOPE);
83 
84     /**
85      * Returns the list of issues that can be found by all known detectors.
86      *
87      * @return the list of issues to be checked (including those that may be
88      *         disabled!)
89      */
90     @NonNull
getIssues()91     public abstract List<Issue> getIssues();
92 
93     /**
94      * Creates a list of detectors applicable to the given cope, and with the
95      * given configuration.
96      *
97      * @param client the client to report errors to
98      * @param configuration the configuration to look up which issues are
99      *            enabled etc from
100      * @param scope the scope for the analysis, to filter out detectors that
101      *            require wider analysis than is currently being performed
102      * @param scopeToDetectors an optional map which (if not null) will be
103      *            filled by this method to contain mappings from each scope to
104      *            the applicable detectors for that scope
105      * @return a list of new detector instances
106      */
107     @NonNull
createDetectors( @onNull LintClient client, @NonNull Configuration configuration, @NonNull EnumSet<Scope> scope, @Nullable Map<Scope, List<Detector>> scopeToDetectors)108     final List<? extends Detector> createDetectors(
109             @NonNull LintClient client,
110             @NonNull Configuration configuration,
111             @NonNull EnumSet<Scope> scope,
112             @Nullable Map<Scope, List<Detector>> scopeToDetectors) {
113         List<Issue> issues = getIssues();
114         Set<Class<? extends Detector>> detectorClasses = new HashSet<Class<? extends Detector>>();
115         Map<Class<? extends Detector>, EnumSet<Scope>> detectorToScope =
116                 new HashMap<Class<? extends Detector>, EnumSet<Scope>>();
117         for (Issue issue : issues) {
118             Class<? extends Detector> detectorClass = issue.getDetectorClass();
119             EnumSet<Scope> issueScope = issue.getScope();
120             if (!detectorClasses.contains(detectorClass)) {
121                 // Determine if the issue is enabled
122                 if (!configuration.isEnabled(issue)) {
123                     continue;
124                 }
125 
126                 // Determine if the scope matches
127                 if (!scope.containsAll(issueScope)) {
128                     continue;
129                 }
130 
131                 detectorClass = client.replaceDetector(detectorClass);
132 
133                 assert detectorClass != null : issue.getId();
134                 detectorClasses.add(detectorClass);
135             }
136 
137             if (scopeToDetectors != null) {
138                 EnumSet<Scope> s = detectorToScope.get(detectorClass);
139                 if (s == null) {
140                     detectorToScope.put(detectorClass, issueScope);
141                 } else if (!s.containsAll(issueScope)) {
142                     EnumSet<Scope> union = EnumSet.copyOf(s);
143                     union.addAll(issueScope);
144                     detectorToScope.put(detectorClass, union);
145                 }
146             }
147         }
148 
149         List<Detector> detectors = new ArrayList<Detector>(detectorClasses.size());
150         for (Class<? extends Detector> clz : detectorClasses) {
151             try {
152                 Detector detector = clz.newInstance();
153                 detectors.add(detector);
154 
155                 if (scopeToDetectors != null) {
156                     EnumSet<Scope> union = detectorToScope.get(clz);
157                     for (Scope s : union) {
158                         List<Detector> list = scopeToDetectors.get(s);
159                         if (list == null) {
160                             list = new ArrayList<Detector>();
161                             scopeToDetectors.put(s, list);
162                         }
163                         list.add(detector);
164                     }
165 
166                 }
167             } catch (Throwable t) {
168                 client.log(t, "Can't initialize detector %1$s", clz.getName()); //$NON-NLS-1$
169             }
170         }
171 
172         return detectors;
173     }
174 
175     /**
176      * Returns true if the given id represents a valid issue id
177      *
178      * @param id the id to be checked
179      * @return true if the given id is valid
180      */
isIssueId(@onNull String id)181     public final boolean isIssueId(@NonNull String id) {
182         return getIssue(id) != null;
183     }
184 
185     /**
186      * Returns true if the given category is a valid category
187      *
188      * @param name the category name to be checked
189      * @return true if the given string is a valid category
190      */
isCategoryName(@onNull String name)191     public final boolean isCategoryName(@NonNull String name) {
192         for (Category c : getCategories()) {
193             if (c.getName().equals(name) || c.getFullName().equals(name)) {
194                 return true;
195             }
196         }
197 
198         return false;
199     }
200 
201     /**
202      * Returns the available categories
203      *
204      * @return an iterator for all the categories, never null
205      */
206     @NonNull
getCategories()207     public List<Category> getCategories() {
208         if (sCategories == null) {
209             final Set<Category> categories = new HashSet<Category>();
210             for (Issue issue : getIssues()) {
211                 categories.add(issue.getCategory());
212             }
213             List<Category> sorted = new ArrayList<Category>(categories);
214             Collections.sort(sorted);
215             sCategories = Collections.unmodifiableList(sorted);
216         }
217 
218         return sCategories;
219     }
220 
221     /**
222      * Returns the issue for the given id, or null if it's not a valid id
223      *
224      * @param id the id to be checked
225      * @return the corresponding issue, or null
226      */
227     @Nullable
getIssue(@onNull String id)228     public final Issue getIssue(@NonNull String id) {
229         if (sIdToIssue == null) {
230             List<Issue> issues = getIssues();
231             sIdToIssue = new HashMap<String, Issue>(issues.size());
232             for (Issue issue : issues) {
233                 sIdToIssue.put(issue.getId(), issue);
234             }
235 
236             sIdToIssue.put(PARSER_ERROR.getId(), PARSER_ERROR);
237             sIdToIssue.put(LINT_ERROR.getId(), LINT_ERROR);
238         }
239         return sIdToIssue.get(id);
240     }
241 }
242