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.client.api;
     18 
     19 import com.android.annotations.NonNull;
     20 import com.android.annotations.Nullable;
     21 import com.android.tools.lint.detector.api.Context;
     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.Location;
     25 import com.android.tools.lint.detector.api.Project;
     26 import com.android.tools.lint.detector.api.Severity;
     27 import com.google.common.annotations.Beta;
     28 
     29 import org.w3c.dom.Document;
     30 import org.w3c.dom.Element;
     31 import org.w3c.dom.NodeList;
     32 import org.xml.sax.InputSource;
     33 
     34 import java.io.File;
     35 import java.io.IOException;
     36 import java.io.StringReader;
     37 import java.net.URL;
     38 import java.util.ArrayList;
     39 import java.util.HashMap;
     40 import java.util.List;
     41 import java.util.Map;
     42 
     43 import javax.xml.parsers.DocumentBuilder;
     44 import javax.xml.parsers.DocumentBuilderFactory;
     45 
     46 /**
     47  * Information about the tool embedding the lint analyzer. IDEs and other tools
     48  * implementing lint support will extend this to integrate logging, displaying errors,
     49  * etc.
     50  * <p/>
     51  * <b>NOTE: This is not a public or final API; if you rely on this be prepared
     52  * to adjust your code for the next tools release.</b>
     53  */
     54 @Beta
     55 public abstract class LintClient {
     56 
     57     private static final String PROP_BIN_DIR  = "com.android.tools.lint.bindir";  //$NON-NLS-1$
     58 
     59     /**
     60      * Returns a configuration for use by the given project. The configuration
     61      * provides information about which issues are enabled, any customizations
     62      * to the severity of an issue, etc.
     63      * <p>
     64      * By default this method returns a {@link DefaultConfiguration}.
     65      *
     66      * @param project the project to obtain a configuration for
     67      * @return a configuration, never null.
     68      */
     69     public Configuration getConfiguration(@NonNull Project project) {
     70         return DefaultConfiguration.create(this, project, null);
     71     }
     72 
     73     /**
     74      * Report the given issue. This method will only be called if the configuration
     75      * provided by {@link #getConfiguration(Project)} has reported the corresponding
     76      * issue as enabled and has not filtered out the issue with its
     77      * {@link Configuration#ignore(Context, Issue, Location, String, Object)} method.
     78      * <p>
     79      *
     80      * @param context the context used by the detector when the issue was found
     81      * @param issue the issue that was found
     82      * @param severity the severity of the issue
     83      * @param location the location of the issue
     84      * @param message the associated user message
     85      * @param data optional extra data for a discovered issue, or null. The
     86      *            content depends on the specific issue. Detectors can pass
     87      *            extra info here which automatic fix tools etc can use to
     88      *            extract relevant information instead of relying on parsing the
     89      *            error message text. See each detector for details on which
     90      *            data if any is supplied for a given issue.
     91      */
     92     public abstract void report(
     93             @NonNull Context context,
     94             @NonNull Issue issue,
     95             @NonNull Severity severity,
     96             @Nullable Location location,
     97             @NonNull String message,
     98             @Nullable Object data);
     99 
    100     /**
    101      * Send an exception or error message (with warning severity) to the log
    102      *
    103      * @param exception the exception, possibly null
    104      * @param format the error message using {@link String#format} syntax, possibly null
    105      *    (though in that case the exception should not be null)
    106      * @param args any arguments for the format string
    107      */
    108     public void log(
    109             @Nullable Throwable exception,
    110             @Nullable String format,
    111             @Nullable Object... args) {
    112         log(Severity.WARNING, exception, format, args);
    113     }
    114 
    115     /**
    116      * Send an exception or error message to the log
    117      *
    118      * @param severity the severity of the warning
    119      * @param exception the exception, possibly null
    120      * @param format the error message using {@link String#format} syntax, possibly null
    121      *    (though in that case the exception should not be null)
    122      * @param args any arguments for the format string
    123      */
    124     public abstract void log(
    125             @NonNull Severity severity,
    126             @Nullable Throwable exception,
    127             @Nullable String format,
    128             @Nullable Object... args);
    129 
    130     /**
    131      * Returns a {@link IDomParser} to use to parse XML
    132      *
    133      * @return a new {@link IDomParser}, or null if this client does not support
    134      *         XML analysis
    135      */
    136     @Nullable
    137     public abstract IDomParser getDomParser();
    138 
    139     /**
    140      * Returns a {@link IJavaParser} to use to parse Java
    141      *
    142      * @return a new {@link IJavaParser}, or null if this client does not
    143      *         support Java analysis
    144      */
    145     @Nullable
    146     public abstract IJavaParser getJavaParser();
    147 
    148     /**
    149      * Returns an optimal detector, if applicable. By default, just returns the
    150      * original detector, but tools can replace detectors using this hook with a version
    151      * that takes advantage of native capabilities of the tool.
    152      *
    153      * @param detectorClass the class of the detector to be replaced
    154      * @return the new detector class, or just the original detector (not null)
    155      */
    156     @NonNull
    157     public Class<? extends Detector> replaceDetector(
    158             @NonNull Class<? extends Detector> detectorClass) {
    159         return detectorClass;
    160     }
    161 
    162     /**
    163      * Reads the given text file and returns the content as a string
    164      *
    165      * @param file the file to read
    166      * @return the string to return, never null (will be empty if there is an
    167      *         I/O error)
    168      */
    169     @NonNull
    170     public abstract String readFile(@NonNull File file);
    171 
    172     /**
    173      * Returns the list of source folders for Java source files
    174      *
    175      * @param project the project to look up Java source file locations for
    176      * @return a list of source folders to search for .java files
    177      */
    178     @NonNull
    179     public List<File> getJavaSourceFolders(@NonNull Project project) {
    180         return getEclipseClasspath(project, "src", "src", "gen"); //$NON-NLS-1$ //$NON-NLS-2$
    181     }
    182 
    183     /**
    184      * Returns the list of output folders for class files
    185      *
    186      * @param project the project to look up class file locations for
    187      * @return a list of output folders to search for .class files
    188      */
    189     @NonNull
    190     public List<File> getJavaClassFolders(@NonNull Project project) {
    191         return getEclipseClasspath(project, "output", "bin"); //$NON-NLS-1$ //$NON-NLS-2$
    192     }
    193 
    194     /**
    195      * Returns the list of Java libraries
    196      *
    197      * @param project the project to look up jar dependencies for
    198      * @return a list of jar dependencies containing .class files
    199      */
    200     @NonNull
    201     public List<File> getJavaLibraries(@NonNull Project project) {
    202         return getEclipseClasspath(project, "lib"); //$NON-NLS-1$
    203     }
    204 
    205     /**
    206      * Returns the {@link SdkInfo} to use for the given project.
    207      *
    208      * @param project the project to look up an {@link SdkInfo} for
    209      * @return an {@link SdkInfo} for the project
    210      */
    211     @NonNull
    212     public SdkInfo getSdkInfo(@NonNull Project project) {
    213         // By default no per-platform SDK info
    214         return new DefaultSdkInfo();
    215     }
    216 
    217     /**
    218      * Returns a suitable location for storing cache files. Note that the
    219      * directory may not exist.
    220      *
    221      * @param create if true, attempt to create the cache dir if it does not
    222      *            exist
    223      * @return a suitable location for storing cache files, which may be null if
    224      *         the create flag was false, or if for some reason the directory
    225      *         could not be created
    226      */
    227     @NonNull
    228     public File getCacheDir(boolean create) {
    229         String home = System.getProperty("user.home");
    230         String relative = ".android" + File.separator + "cache"; //$NON-NLS-1$ //$NON-NLS-2$
    231         File dir = new File(home, relative);
    232         if (create && !dir.exists()) {
    233             if (!dir.mkdirs()) {
    234                 return null;
    235             }
    236         }
    237         return dir;
    238     }
    239 
    240     /**
    241      * Returns the File corresponding to the system property or the environment variable
    242      * for {@link #PROP_BIN_DIR}.
    243      * This property is typically set by the SDK/tools/lint[.bat] wrapper.
    244      * It denotes the path of the wrapper on disk.
    245      *
    246      * @return A new File corresponding to {@link LintClient#PROP_BIN_DIR} or null.
    247      */
    248     @Nullable
    249     private File getLintBinDir() {
    250         // First check the Java properties (e.g. set using "java -jar ... -Dname=value")
    251         String path = System.getProperty(PROP_BIN_DIR);
    252         if (path == null || path.length() == 0) {
    253             // If not found, check environment variables.
    254             path = System.getenv(PROP_BIN_DIR);
    255         }
    256         if (path != null && path.length() > 0) {
    257             return new File(path);
    258         }
    259         return null;
    260     }
    261 
    262     /**
    263      * Locates an SDK resource (relative to the SDK root directory).
    264      * <p>
    265      * TODO: Consider switching to a {@link URL} return type instead.
    266      *
    267      * @param relativePath A relative path (using {@link File#separator} to
    268      *            separate path components) to the given resource
    269      * @return a {@link File} pointing to the resource, or null if it does not
    270      *         exist
    271      */
    272     @Nullable
    273     public File findResource(@NonNull String relativePath) {
    274         File dir = getLintBinDir();
    275         if (dir == null) {
    276             throw new IllegalArgumentException("Lint must be invoked with the System property "
    277                     + PROP_BIN_DIR + " pointing to the ANDROID_SDK tools directory");
    278         }
    279 
    280         File top = dir.getParentFile();
    281         File file = new File(top, relativePath);
    282         if (file.exists()) {
    283             return file;
    284         } else {
    285             return null;
    286         }
    287     }
    288 
    289     /**
    290      * Considers the given directory as an Eclipse project and returns either
    291      * its source or its output folders depending on the {@code attribute} parameter.
    292      */
    293     @NonNull
    294     private List<File> getEclipseClasspath(@NonNull Project project, @NonNull String attribute,
    295             @NonNull String... fallbackPaths) {
    296         List<File> folders = new ArrayList<File>();
    297         File projectDir = project.getDir();
    298         File classpathFile = new File(projectDir, ".classpath"); //$NON-NLS-1$
    299         if (classpathFile.exists()) {
    300             String classpathXml = readFile(classpathFile);
    301             DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    302             InputSource is = new InputSource(new StringReader(classpathXml));
    303             factory.setNamespaceAware(false);
    304             factory.setValidating(false);
    305             try {
    306                 DocumentBuilder builder = factory.newDocumentBuilder();
    307                 Document document = builder.parse(is);
    308                 NodeList tags = document.getElementsByTagName("classpathentry"); //$NON-NLS-1$
    309                 for (int i = 0, n = tags.getLength(); i < n; i++) {
    310                     Element element = (Element) tags.item(i);
    311                     String kind = element.getAttribute("kind"); //$NON-NLS-1$
    312                     if (kind.equals(attribute)) {
    313                         String path = element.getAttribute("path"); //$NON-NLS-1$
    314                         File sourceFolder = new File(projectDir, path);
    315                         if (sourceFolder.exists()) {
    316                             folders.add(sourceFolder);
    317                         }
    318                     }
    319                 }
    320             } catch (Exception e) {
    321                 log(null, null);
    322             }
    323         }
    324 
    325         // Fallback?
    326         if (folders.size() == 0) {
    327             for (String fallbackPath : fallbackPaths) {
    328                 File folder = new File(projectDir, fallbackPath);
    329                 if (folder.exists()) {
    330                     folders.add(folder);
    331                 }
    332             }
    333         }
    334 
    335         return folders;
    336     }
    337 
    338     /**
    339      * A map from directory to existing projects, or null. Used to ensure that
    340      * projects are unique for a directory (in case we process a library project
    341      * before its including project for example)
    342      */
    343     private Map<File, Project> mDirToProject;
    344 
    345     /**
    346      * Returns a project for the given directory. This should return the same
    347      * project for the same directory if called repeatedly.
    348      *
    349      * @param dir the directory containing the project
    350      * @param referenceDir See {@link Project#getReferenceDir()}.
    351      * @return a project, never null
    352      */
    353     @NonNull
    354     public Project getProject(@NonNull File dir, @NonNull File referenceDir) {
    355         if (mDirToProject == null) {
    356             mDirToProject = new HashMap<File, Project>();
    357         }
    358 
    359         File canonicalDir = dir;
    360         try {
    361             // Attempt to use the canonical handle for the file, in case there
    362             // are symlinks etc present (since when handling library projects,
    363             // we also call getCanonicalFile to compute the result of appending
    364             // relative paths, which can then resolve symlinks and end up with
    365             // a different prefix)
    366             canonicalDir = dir.getCanonicalFile();
    367         } catch (IOException ioe) {
    368             // pass
    369         }
    370 
    371         Project project = mDirToProject.get(canonicalDir);
    372         if (project != null) {
    373             return project;
    374         }
    375 
    376 
    377         project = Project.create(this, dir, referenceDir);
    378         mDirToProject.put(canonicalDir, project);
    379         return project;
    380     }
    381 }
    382