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