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