Home | History | Annotate | Download | only in api
      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      */
     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
    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
    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
    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
    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
    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
    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
    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
    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      */
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    328     public void requestRepeat(@NonNull Detector detector, @Nullable EnumSet<Scope> scope) {
    329         mDriver.requestRepeat(detector, scope);
    330     }
    331 }
    332