| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.tools.lint.client.api; |
| |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.annotations.VisibleForTesting; |
| import com.android.tools.lint.detector.api.Category; |
| import com.android.tools.lint.detector.api.Detector; |
| import com.android.tools.lint.detector.api.Implementation; |
| import com.android.tools.lint.detector.api.Issue; |
| import com.android.tools.lint.detector.api.Scope; |
| import com.android.tools.lint.detector.api.Severity; |
| import com.google.common.annotations.Beta; |
| import com.google.common.collect.Maps; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Registry which provides a list of checks to be performed on an Android project |
| * <p> |
| * <b>NOTE: This is not a public or final API; if you rely on this be prepared |
| * to adjust your code for the next tools release.</b> |
| */ |
| @Beta |
| public abstract class IssueRegistry { |
| private static List<Category> sCategories; |
| private static Map<String, Issue> sIdToIssue; |
| private static Map<EnumSet<Scope>, List<Issue>> sScopeIssues = Maps.newHashMap(); |
| |
| /** |
| * Creates a new {@linkplain IssueRegistry} |
| */ |
| protected IssueRegistry() { |
| } |
| |
| private static final Implementation DUMMY_IMPLEMENTATION = new Implementation(Detector.class, |
| EnumSet.noneOf(Scope.class)); |
| /** |
| * Issue reported by lint (not a specific detector) when it cannot even |
| * parse an XML file prior to analysis |
| */ |
| @NonNull |
| public static final Issue PARSER_ERROR = Issue.create( |
| "ParserError", //$NON-NLS-1$ |
| "Parser Errors", |
| "Finds files that contain fatal parser errors", |
| "Lint will ignore any files that contain fatal parsing errors. These may contain " + |
| "other errors, or contain code which affects issues in other files.", |
| Category.CORRECTNESS, |
| 10, |
| Severity.ERROR, |
| DUMMY_IMPLEMENTATION); |
| |
| /** |
| * Issue reported by lint for various other issues which prevents lint from |
| * running normally when it's not necessarily an error in the user's code base. |
| */ |
| @NonNull |
| public static final Issue LINT_ERROR = Issue.create( |
| "LintError", //$NON-NLS-1$ |
| "Lint Failure", |
| "Issues related to running lint itself, such as failure to read files, etc", |
| "This issue type represents a problem running lint itself. Examples include " + |
| "failure to find bytecode for source files (which means certain detectors " + |
| "could not be run), parsing errors in lint configuration files, etc." + |
| "\n" + |
| "These errors are not errors in your own code, but they are shown to make " + |
| "it clear that some checks were not completed.", |
| |
| Category.LINT, |
| 10, |
| Severity.ERROR, |
| DUMMY_IMPLEMENTATION); |
| |
| /** |
| * Issue reported when lint is canceled |
| */ |
| @NonNull |
| public static final Issue CANCELLED = Issue.create( |
| "LintCanceled", //$NON-NLS-1$ |
| "Lint Canceled", |
| "Lint canceled by user", |
| "Lint canceled by user; the issue report may not be complete.", |
| |
| Category.LINT, |
| 0, |
| Severity.INFORMATIONAL, |
| DUMMY_IMPLEMENTATION); |
| |
| /** |
| * Returns the list of issues that can be found by all known detectors. |
| * |
| * @return the list of issues to be checked (including those that may be |
| * disabled!) |
| */ |
| @NonNull |
| public abstract List<Issue> getIssues(); |
| |
| /** |
| * Get an approximate issue count for a given scope. This is just an optimization, |
| * so the number does not have to be accurate. |
| * |
| * @param scope the scope set |
| * @return an approximate ceiling of the number of issues expected for a given scope set |
| */ |
| protected int getIssueCapacity(@NonNull EnumSet<Scope> scope) { |
| return 20; |
| } |
| |
| /** |
| * Returns all available issues of a given scope (regardless of whether |
| * they are actually enabled for a given configuration etc) |
| * |
| * @param scope the applicable scope set |
| * @return a list of issues |
| */ |
| @NonNull |
| protected List<Issue> getIssuesForScope(@NonNull EnumSet<Scope> scope) { |
| List<Issue> list = sScopeIssues.get(scope); |
| if (list == null) { |
| List<Issue> issues = getIssues(); |
| if (scope.equals(Scope.ALL)) { |
| list = issues; |
| } else { |
| list = new ArrayList<Issue>(getIssueCapacity(scope)); |
| for (Issue issue : issues) { |
| // Determine if the scope matches |
| if (issue.getImplementation().isAdequate(scope)) { |
| list.add(issue); |
| } |
| } |
| } |
| sScopeIssues.put(scope, list); |
| } |
| |
| return list; |
| } |
| |
| /** |
| * Creates a list of detectors applicable to the given scope, and with the |
| * given configuration. |
| * |
| * @param client the client to report errors to |
| * @param configuration the configuration to look up which issues are |
| * enabled etc from |
| * @param scope the scope for the analysis, to filter out detectors that |
| * require wider analysis than is currently being performed |
| * @param scopeToDetectors an optional map which (if not null) will be |
| * filled by this method to contain mappings from each scope to |
| * the applicable detectors for that scope |
| * @return a list of new detector instances |
| */ |
| @NonNull |
| final List<? extends Detector> createDetectors( |
| @NonNull LintClient client, |
| @NonNull Configuration configuration, |
| @NonNull EnumSet<Scope> scope, |
| @Nullable Map<Scope, List<Detector>> scopeToDetectors) { |
| |
| List<Issue> issues = getIssuesForScope(scope); |
| if (issues.isEmpty()) { |
| return Collections.emptyList(); |
| } |
| |
| Set<Class<? extends Detector>> detectorClasses = new HashSet<Class<? extends Detector>>(); |
| Map<Class<? extends Detector>, EnumSet<Scope>> detectorToScope = |
| new HashMap<Class<? extends Detector>, EnumSet<Scope>>(); |
| |
| for (Issue issue : issues) { |
| Implementation implementation = issue.getImplementation(); |
| Class<? extends Detector> detectorClass = implementation.getDetectorClass(); |
| EnumSet<Scope> issueScope = implementation.getScope(); |
| if (!detectorClasses.contains(detectorClass)) { |
| // Determine if the issue is enabled |
| if (!configuration.isEnabled(issue)) { |
| continue; |
| } |
| |
| assert implementation.isAdequate(scope); // Ensured by getIssuesForScope above |
| |
| detectorClass = client.replaceDetector(detectorClass); |
| |
| assert detectorClass != null : issue.getId(); |
| detectorClasses.add(detectorClass); |
| } |
| |
| if (scopeToDetectors != null) { |
| EnumSet<Scope> s = detectorToScope.get(detectorClass); |
| if (s == null) { |
| detectorToScope.put(detectorClass, issueScope); |
| } else if (!s.containsAll(issueScope)) { |
| EnumSet<Scope> union = EnumSet.copyOf(s); |
| union.addAll(issueScope); |
| detectorToScope.put(detectorClass, union); |
| } |
| } |
| } |
| |
| List<Detector> detectors = new ArrayList<Detector>(detectorClasses.size()); |
| for (Class<? extends Detector> clz : detectorClasses) { |
| try { |
| Detector detector = clz.newInstance(); |
| detectors.add(detector); |
| |
| if (scopeToDetectors != null) { |
| EnumSet<Scope> union = detectorToScope.get(clz); |
| for (Scope s : union) { |
| List<Detector> list = scopeToDetectors.get(s); |
| if (list == null) { |
| list = new ArrayList<Detector>(); |
| scopeToDetectors.put(s, list); |
| } |
| list.add(detector); |
| } |
| |
| } |
| } catch (Throwable t) { |
| client.log(t, "Can't initialize detector %1$s", clz.getName()); //$NON-NLS-1$ |
| } |
| } |
| |
| return detectors; |
| } |
| |
| /** |
| * Returns true if the given id represents a valid issue id |
| * |
| * @param id the id to be checked |
| * @return true if the given id is valid |
| */ |
| public final boolean isIssueId(@NonNull String id) { |
| return getIssue(id) != null; |
| } |
| |
| /** |
| * Returns true if the given category is a valid category |
| * |
| * @param name the category name to be checked |
| * @return true if the given string is a valid category |
| */ |
| public final boolean isCategoryName(@NonNull String name) { |
| for (Category category : getCategories()) { |
| if (category.getName().equals(name) || category.getFullName().equals(name)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Returns the available categories |
| * |
| * @return an iterator for all the categories, never null |
| */ |
| @NonNull |
| public List<Category> getCategories() { |
| if (sCategories == null) { |
| final Set<Category> categories = new HashSet<Category>(); |
| for (Issue issue : getIssues()) { |
| categories.add(issue.getCategory()); |
| } |
| List<Category> sorted = new ArrayList<Category>(categories); |
| Collections.sort(sorted); |
| sCategories = Collections.unmodifiableList(sorted); |
| } |
| |
| return sCategories; |
| } |
| |
| /** |
| * Returns the issue for the given id, or null if it's not a valid id |
| * |
| * @param id the id to be checked |
| * @return the corresponding issue, or null |
| */ |
| @Nullable |
| public final Issue getIssue(@NonNull String id) { |
| if (sIdToIssue == null) { |
| List<Issue> issues = getIssues(); |
| sIdToIssue = new HashMap<String, Issue>(issues.size()); |
| for (Issue issue : issues) { |
| sIdToIssue.put(issue.getId(), issue); |
| } |
| |
| sIdToIssue.put(PARSER_ERROR.getId(), PARSER_ERROR); |
| sIdToIssue.put(LINT_ERROR.getId(), LINT_ERROR); |
| } |
| return sIdToIssue.get(id); |
| } |
| |
| /** |
| * Reset the registry such that it recomputes its available issues. |
| * <p> |
| * NOTE: This is only intended for testing purposes. |
| */ |
| @VisibleForTesting |
| protected static void reset() { |
| sIdToIssue = null; |
| sCategories = null; |
| sScopeIssues = Maps.newHashMap(); |
| } |
| } |