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.detector.api; 18 19 import com.android.annotations.NonNull; 20 import com.android.annotations.Nullable; 21 import com.android.tools.lint.client.api.Configuration; 22 import com.android.tools.lint.client.api.LintClient; 23 import com.android.tools.lint.client.api.LintDriver; 24 import com.android.tools.lint.client.api.SdkInfo; 25 import com.google.common.annotations.Beta; 26 27 import java.io.File; 28 import java.util.EnumSet; 29 import java.util.HashMap; 30 import java.util.Map; 31 import java.util.concurrent.atomic.AtomicBoolean; 32 33 /** 34 * Context passed to the detectors during an analysis run. It provides 35 * information about the file being analyzed, it allows shared properties (so 36 * the detectors can share results), etc. 37 * <p> 38 * <b>NOTE: This is not a public or final API; if you rely on this be prepared 39 * to adjust your code for the next tools release.</b> 40 */ 41 @Beta 42 public class Context { 43 /** 44 * The file being checked. Note that this may not always be to a concrete 45 * file. For example, in the {@link Detector#beforeCheckProject(Context)} 46 * method, the context file is the directory of the project. 47 */ 48 public final File file; 49 50 /** The driver running through the checks */ 51 protected final LintDriver mDriver; 52 53 /** The project containing the file being checked */ 54 @NonNull 55 private final Project mProject; 56 57 /** 58 * The "main" project. For normal projects, this is the same as {@link #mProject}, 59 * but for library projects, it's the root project that includes (possibly indirectly) 60 * the various library projects and their library projects. 61 * <p> 62 * Note that this is a property on the {@link Context}, not the 63 * {@link Project}, since a library project can be included from multiple 64 * different top level projects, so there isn't <b>one</b> main project, 65 * just one per main project being analyzed with its library projects. 66 */ 67 private final Project mMainProject; 68 69 /** The current configuration controlling which checks are enabled etc */ 70 private final Configuration mConfiguration; 71 72 /** The contents of the file */ 73 private String mContents; 74 75 /** 76 * Whether the lint job has been canceled. 77 * <p> 78 * Slow-running detectors should check this flag via 79 * {@link AtomicBoolean#get()} and abort if canceled 80 */ 81 @NonNull 82 public final AtomicBoolean canceled = new AtomicBoolean(); 83 84 /** Map of properties to share results between detectors */ 85 private Map<String, Object> mProperties; 86 87 /** 88 * Construct a new {@link Context} 89 * 90 * @param driver the driver running through the checks 91 * @param project the project containing the file being checked 92 * @param main the main project if this project is a library project, or 93 * null if this is not a library project. The main project is 94 * the root project of all library projects, not necessarily the 95 * directly including project. 96 * @param file the file being checked 97 */ Context( @onNull LintDriver driver, @NonNull Project project, @Nullable Project main, @NonNull File file)98 public Context( 99 @NonNull LintDriver driver, 100 @NonNull Project project, 101 @Nullable Project main, 102 @NonNull File file) { 103 this.file = file; 104 105 mDriver = driver; 106 mProject = project; 107 mMainProject = main; 108 mConfiguration = project.getConfiguration(); 109 } 110 111 /** 112 * Returns the scope for the lint job 113 * 114 * @return the scope, never null 115 */ 116 @NonNull getScope()117 public EnumSet<Scope> getScope() { 118 return mDriver.getScope(); 119 } 120 121 /** 122 * Returns the configuration for this project. 123 * 124 * @return the configuration, never null 125 */ 126 @NonNull getConfiguration()127 public Configuration getConfiguration() { 128 return mConfiguration; 129 } 130 131 /** 132 * Returns the project containing the file being checked 133 * 134 * @return the project, never null 135 */ 136 @NonNull getProject()137 public Project getProject() { 138 return mProject; 139 } 140 141 /** 142 * Returns the main project if this project is a library project, or self 143 * if this is not a library project. The main project is the root project 144 * of all library projects, not necessarily the directly including project. 145 * 146 * @return the main project, never null 147 */ 148 @NonNull getMainProject()149 public Project getMainProject() { 150 return mMainProject != null ? mMainProject : mProject; 151 } 152 153 /** 154 * Returns the lint client requesting the lint check 155 * 156 * @return the client, never null 157 */ 158 @NonNull getClient()159 public LintClient getClient() { 160 return mDriver.getClient(); 161 } 162 163 /** 164 * Returns the driver running through the lint checks 165 * 166 * @return the driver 167 */ 168 @NonNull getDriver()169 public LintDriver getDriver() { 170 return mDriver; 171 } 172 173 /** 174 * Returns the contents of the file. This may not be the contents of the 175 * file on disk, since it delegates to the {@link LintClient}, which in turn 176 * may decide to return the current edited contents of the file open in an 177 * editor. 178 * 179 * @return the contents of the given file, or null if an error occurs. 180 */ 181 @Nullable getContents()182 public String getContents() { 183 if (mContents == null) { 184 mContents = mDriver.getClient().readFile(file); 185 } 186 187 return mContents; 188 } 189 190 /** 191 * Returns the value of the given named property, or null. 192 * 193 * @param name the name of the property 194 * @return the corresponding value, or null 195 */ 196 @Nullable getProperty(String name)197 public Object getProperty(String name) { 198 if (mProperties == null) { 199 return null; 200 } 201 202 return mProperties.get(name); 203 } 204 205 /** 206 * Sets the value of the given named property. 207 * 208 * @param name the name of the property 209 * @param value the corresponding value 210 */ setProperty(@onNull String name, @Nullable Object value)211 public void setProperty(@NonNull String name, @Nullable Object value) { 212 if (value == null) { 213 if (mProperties != null) { 214 mProperties.remove(name); 215 } 216 } else { 217 if (mProperties == null) { 218 mProperties = new HashMap<String, Object>(); 219 } 220 mProperties.put(name, value); 221 } 222 } 223 224 /** 225 * Gets the SDK info for the current project. 226 * 227 * @return the SDK info for the current project, never null 228 */ 229 @NonNull getSdkInfo()230 public SdkInfo getSdkInfo() { 231 return mProject.getSdkInfo(); 232 } 233 234 // ---- Convenience wrappers ---- (makes the detector code a bit leaner) 235 236 /** 237 * Returns false if the given issue has been disabled. Convenience wrapper 238 * around {@link Configuration#getSeverity(Issue)}. 239 * 240 * @param issue the issue to check 241 * @return false if the issue has been disabled 242 */ isEnabled(@onNull Issue issue)243 public boolean isEnabled(@NonNull Issue issue) { 244 return mConfiguration.isEnabled(issue); 245 } 246 247 /** 248 * Reports an issue. Convenience wrapper around {@link LintClient#report} 249 * 250 * @param issue the issue to report 251 * @param location the location of the issue, or null if not known 252 * @param message the message for this warning 253 * @param data any associated data, or null 254 */ report( @onNull Issue issue, @Nullable Location location, @NonNull String message, @Nullable Object data)255 public void report( 256 @NonNull Issue issue, 257 @Nullable Location location, 258 @NonNull String message, 259 @Nullable Object data) { 260 Configuration configuration = mConfiguration; 261 262 // If this error was computed for a context where the context corresponds to 263 // a project instead of a file, the actual error may be in a different project (e.g. 264 // a library project), so adjust the configuration as necessary. 265 if (location != null && location.getFile() != null) { 266 Project project = mDriver.findProjectFor(location.getFile()); 267 if (project != null) { 268 configuration = project.getConfiguration(); 269 } 270 } 271 272 // If an error occurs in a library project, but you've disabled that check in the 273 // main project, disable it in the library project too. (In some cases you don't 274 // control the lint.xml of a library project, and besides, if you're not interested in 275 // a check for your main project you probably don't care about it in the library either.) 276 if (configuration != mConfiguration 277 && mConfiguration.getSeverity(issue) == Severity.IGNORE) { 278 return; 279 } 280 281 Severity severity = configuration.getSeverity(issue); 282 if (severity == Severity.IGNORE) { 283 return; 284 } 285 286 mDriver.getClient().report(this, issue, severity, location, message, data); 287 } 288 289 /** 290 * Send an exception to the log. Convenience wrapper around {@link LintClient#log}. 291 * 292 * @param exception the exception, possibly null 293 * @param format the error message using {@link String#format} syntax, possibly null 294 * @param args any arguments for the format string 295 */ log( @ullable Throwable exception, @Nullable String format, @Nullable Object... args)296 public void log( 297 @Nullable Throwable exception, 298 @Nullable String format, 299 @Nullable Object... args) { 300 mDriver.getClient().log(exception, format, args); 301 } 302 303 /** 304 * Returns the current phase number. The first pass is numbered 1. Only one pass 305 * will be performed, unless a {@link Detector} calls {@link #requestRepeat}. 306 * 307 * @return the current phase, usually 1 308 */ getPhase()309 public int getPhase() { 310 return mDriver.getPhase(); 311 } 312 313 /** 314 * Requests another pass through the data for the given detector. This is 315 * typically done when a detector needs to do more expensive computation, 316 * but it only wants to do this once it <b>knows</b> that an error is 317 * present, or once it knows more specifically what to check for. 318 * 319 * @param detector the detector that should be included in the next pass. 320 * Note that the lint runner may refuse to run more than a couple 321 * of runs. 322 * @param scope the scope to be revisited. This must be a subset of the 323 * current scope ({@link #getScope()}, and it is just a performance hint; 324 * in particular, the detector should be prepared to be called on other 325 * scopes as well (since they may have been requested by other detectors). 326 * You can pall null to indicate "all". 327 */ requestRepeat(@onNull Detector detector, @Nullable EnumSet<Scope> scope)328 public void requestRepeat(@NonNull Detector detector, @Nullable EnumSet<Scope> scope) { 329 mDriver.requestRepeat(detector, scope); 330 } 331 } 332