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