blob: aed9c09de7e053669fcc2c6fadeb39237e4d7261 [file] [log] [blame]
/*
* 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();
}
}