blob: 3714be01fb48aa35f4b98aa78a9c1b3f89d54735 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.lint.client.api;
import static com.android.SdkConstants.CLASS_FOLDER;
import static com.android.SdkConstants.DOT_AAR;
import static com.android.SdkConstants.DOT_JAR;
import static com.android.SdkConstants.GEN_FOLDER;
import static com.android.SdkConstants.LIBS_FOLDER;
import static com.android.SdkConstants.RES_FOLDER;
import static com.android.SdkConstants.SRC_FOLDER;
import static com.android.tools.lint.detector.api.LintUtils.endsWith;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.res2.AbstractResourceRepository;
import com.android.ide.common.res2.ResourceItem;
import com.android.ide.common.sdk.SdkVersionInfo;
import com.android.prefs.AndroidLocation;
import com.android.sdklib.IAndroidTarget;
import com.android.sdklib.repository.local.LocalSdk;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.LintUtils;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.Project;
import com.android.tools.lint.detector.api.Severity;
import com.android.utils.XmlUtils;
import com.google.common.annotations.Beta;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Information about the tool embedding the lint analyzer. IDEs and other tools
* implementing lint support will extend this to integrate logging, displaying errors,
* etc.
* <p/>
* <b>NOTE: This is not a public or final API; if you rely on this be prepared
* to adjust your code for the next tools release.</b>
*/
@Beta
public abstract class LintClient {
private static final String PROP_BIN_DIR = "com.android.tools.lint.bindir"; //$NON-NLS-1$
/**
* Returns a configuration for use by the given project. The configuration
* provides information about which issues are enabled, any customizations
* to the severity of an issue, etc.
* <p>
* By default this method returns a {@link DefaultConfiguration}.
*
* @param project the project to obtain a configuration for
* @return a configuration, never null.
*/
public Configuration getConfiguration(@NonNull Project project) {
return DefaultConfiguration.create(this, project, null);
}
/**
* Report the given issue. This method will only be called if the configuration
* provided by {@link #getConfiguration(Project)} has reported the corresponding
* issue as enabled and has not filtered out the issue with its
* {@link Configuration#ignore(Context, Issue, Location, String, Object)} method.
* <p>
*
* @param context the context used by the detector when the issue was found
* @param issue the issue that was found
* @param severity the severity of the issue
* @param location the location of the issue
* @param message the associated user message
* @param data optional extra data for a discovered issue, or null. The
* content depends on the specific issue. Detectors can pass
* extra info here which automatic fix tools etc can use to
* extract relevant information instead of relying on parsing the
* error message text. See each detector for details on which
* data if any is supplied for a given issue.
*/
public abstract void report(
@NonNull Context context,
@NonNull Issue issue,
@NonNull Severity severity,
@Nullable Location location,
@NonNull String message,
@Nullable Object data);
/**
* Send an exception or error message (with warning severity) to the log
*
* @param exception the exception, possibly null
* @param format the error message using {@link String#format} syntax, possibly null
* (though in that case the exception should not be null)
* @param args any arguments for the format string
*/
public void log(
@Nullable Throwable exception,
@Nullable String format,
@Nullable Object... args) {
log(Severity.WARNING, exception, format, args);
}
/**
* Send an exception or error message to the log
*
* @param severity the severity of the warning
* @param exception the exception, possibly null
* @param format the error message using {@link String#format} syntax, possibly null
* (though in that case the exception should not be null)
* @param args any arguments for the format string
*/
public abstract void log(
@NonNull Severity severity,
@Nullable Throwable exception,
@Nullable String format,
@Nullable Object... args);
/**
* Returns a {@link XmlParser} to use to parse XML
*
* @return a new {@link XmlParser}, or null if this client does not support
* XML analysis
*/
@Nullable
public abstract XmlParser getXmlParser();
/**
* Returns a {@link JavaParser} to use to parse Java
*
* @param project the project to parse, if known (this can be used to look up
* the class path for type attribution etc, and it can also be used
* to more efficiently process a set of files, for example to
* perform type attribution for multiple units in a single pass)
* @return a new {@link JavaParser}, or null if this client does not
* support Java analysis
*/
@Nullable
public abstract JavaParser getJavaParser(@Nullable Project project);
/**
* Returns an optimal detector, if applicable. By default, just returns the
* original detector, but tools can replace detectors using this hook with a version
* that takes advantage of native capabilities of the tool.
*
* @param detectorClass the class of the detector to be replaced
* @return the new detector class, or just the original detector (not null)
*/
@NonNull
public Class<? extends Detector> replaceDetector(
@NonNull Class<? extends Detector> detectorClass) {
return detectorClass;
}
/**
* Reads the given text file and returns the content as a string
*
* @param file the file to read
* @return the string to return, never null (will be empty if there is an
* I/O error)
*/
@NonNull
public abstract String readFile(@NonNull File file);
/**
* Reads the given binary file and returns the content as a byte array.
* By default this method will read the bytes from the file directly,
* but this can be customized by a client if for example I/O could be
* held in memory and not flushed to disk yet.
*
* @param file the file to read
* @return the bytes in the file, never null
* @throws IOException if the file does not exist, or if the file cannot be
* read for some reason
*/
@NonNull
public byte[] readBytes(@NonNull File file) throws IOException {
return Files.toByteArray(file);
}
/**
* Returns the list of source folders for Java source files
*
* @param project the project to look up Java source file locations for
* @return a list of source folders to search for .java files
*/
@NonNull
public List<File> getJavaSourceFolders(@NonNull Project project) {
return getClassPath(project).getSourceFolders();
}
/**
* Returns the list of output folders for class files
*
* @param project the project to look up class file locations for
* @return a list of output folders to search for .class files
*/
@NonNull
public List<File> getJavaClassFolders(@NonNull Project project) {
return getClassPath(project).getClassFolders();
}
/**
* Returns the list of Java libraries
*
* @param project the project to look up jar dependencies for
* @return a list of jar dependencies containing .class files
*/
@NonNull
public List<File> getJavaLibraries(@NonNull Project project) {
return getClassPath(project).getLibraries();
}
/**
* Returns the resource folders.
*
* @param project the project to look up the resource folder for
* @return a list of files pointing to the resource folders, possibly empty
*/
@NonNull
public List<File> getResourceFolders(@NonNull Project project) {
File res = new File(project.getDir(), RES_FOLDER);
if (res.exists()) {
return Collections.singletonList(res);
}
return Collections.emptyList();
}
/**
* Returns the {@link SdkInfo} to use for the given project.
*
* @param project the project to look up an {@link SdkInfo} for
* @return an {@link SdkInfo} for the project
*/
@NonNull
public SdkInfo getSdkInfo(@NonNull Project project) {
// By default no per-platform SDK info
return new DefaultSdkInfo();
}
/**
* Returns a suitable location for storing cache files. Note that the
* directory may not exist.
*
* @param create if true, attempt to create the cache dir if it does not
* exist
* @return a suitable location for storing cache files, which may be null if
* the create flag was false, or if for some reason the directory
* could not be created
*/
@Nullable
public File getCacheDir(boolean create) {
String home = System.getProperty("user.home");
String relative = ".android" + File.separator + "cache"; //$NON-NLS-1$ //$NON-NLS-2$
File dir = new File(home, relative);
if (create && !dir.exists()) {
if (!dir.mkdirs()) {
return null;
}
}
return dir;
}
/**
* Returns the File corresponding to the system property or the environment variable
* for {@link #PROP_BIN_DIR}.
* This property is typically set by the SDK/tools/lint[.bat] wrapper.
* It denotes the path of the wrapper on disk.
*
* @return A new File corresponding to {@link LintClient#PROP_BIN_DIR} or null.
*/
@Nullable
private static File getLintBinDir() {
// First check the Java properties (e.g. set using "java -jar ... -Dname=value")
String path = System.getProperty(PROP_BIN_DIR);
if (path == null || path.isEmpty()) {
// If not found, check environment variables.
path = System.getenv(PROP_BIN_DIR);
}
if (path != null && !path.isEmpty()) {
File file = new File(path);
if (file.exists()) {
return file;
}
}
return null;
}
/**
* Returns the File pointing to the user's SDK install area. This is generally
* the root directory containing the lint tool (but also platforms/ etc).
*
* @return a file pointing to the user's install area
*/
@Nullable
public File getSdkHome() {
File binDir = getLintBinDir();
if (binDir != null) {
assert binDir.getName().equals("tools");
File root = binDir.getParentFile();
if (root != null && root.isDirectory()) {
return root;
}
}
String home = System.getenv("ANDROID_HOME"); //$NON-NLS-1$
if (home != null) {
return new File(home);
}
return null;
}
/**
* Locates an SDK resource (relative to the SDK root directory).
* <p>
* TODO: Consider switching to a {@link URL} return type instead.
*
* @param relativePath A relative path (using {@link File#separator} to
* separate path components) to the given resource
* @return a {@link File} pointing to the resource, or null if it does not
* exist
*/
@Nullable
public File findResource(@NonNull String relativePath) {
File top = getSdkHome();
if (top == null) {
throw new IllegalArgumentException("Lint must be invoked with the System property "
+ PROP_BIN_DIR + " pointing to the ANDROID_SDK tools directory");
}
File file = new File(top, relativePath);
if (file.exists()) {
return file;
} else {
return null;
}
}
private Map<Project, ClassPathInfo> mProjectInfo;
/**
* Returns true if this project is a Gradle-based Android project
*
* @param project the project to check
* @return true if this is a Gradle-based project
*/
public boolean isGradleProject(Project project) {
// This is not an accurate test; specific LintClient implementations (e.g.
// IDEs or a gradle-integration of lint) have more context and can perform a more accurate
// check
return new File(project.getDir(), "build.gradle").exists();
}
/**
* Information about class paths (sources, class files and libraries)
* usually associated with a project.
*/
protected static class ClassPathInfo {
private final List<File> mClassFolders;
private final List<File> mSourceFolders;
private final List<File> mLibraries;
public ClassPathInfo(
@NonNull List<File> sourceFolders,
@NonNull List<File> classFolders,
@NonNull List<File> libraries) {
mSourceFolders = sourceFolders;
mClassFolders = classFolders;
mLibraries = libraries;
}
@NonNull
public List<File> getSourceFolders() {
return mSourceFolders;
}
@NonNull
public List<File> getClassFolders() {
return mClassFolders;
}
@NonNull
public List<File> getLibraries() {
return mLibraries;
}
}
/**
* Considers the given project as an Eclipse project and returns class path
* information for the project - the source folder(s), the output folder and
* any libraries.
* <p>
* Callers will not cache calls to this method, so if it's expensive to compute
* the classpath info, this method should perform its own caching.
*
* @param project the project to look up class path info for
* @return a class path info object, never null
*/
@NonNull
protected ClassPathInfo getClassPath(@NonNull Project project) {
ClassPathInfo info;
if (mProjectInfo == null) {
mProjectInfo = Maps.newHashMap();
info = null;
} else {
info = mProjectInfo.get(project);
}
if (info == null) {
List<File> sources = new ArrayList<File>(2);
List<File> classes = new ArrayList<File>(1);
List<File> libraries = new ArrayList<File>();
File projectDir = project.getDir();
File classpathFile = new File(projectDir, ".classpath"); //$NON-NLS-1$
if (classpathFile.exists()) {
String classpathXml = readFile(classpathFile);
try {
Document document = XmlUtils.parseDocument(classpathXml, false);
NodeList tags = document.getElementsByTagName("classpathentry"); //$NON-NLS-1$
for (int i = 0, n = tags.getLength(); i < n; i++) {
Element element = (Element) tags.item(i);
String kind = element.getAttribute("kind"); //$NON-NLS-1$
List<File> addTo = null;
if (kind.equals("src")) { //$NON-NLS-1$
addTo = sources;
} else if (kind.equals("output")) { //$NON-NLS-1$
addTo = classes;
} else if (kind.equals("lib")) { //$NON-NLS-1$
addTo = libraries;
}
if (addTo != null) {
String path = element.getAttribute("path"); //$NON-NLS-1$
File folder = new File(projectDir, path);
if (folder.exists()) {
addTo.add(folder);
}
}
}
} catch (Exception e) {
log(null, null);
}
}
// Add in libraries that aren't specified in the .classpath file
File libs = new File(project.getDir(), LIBS_FOLDER);
if (libs.isDirectory()) {
File[] jars = libs.listFiles();
if (jars != null) {
for (File jar : jars) {
if (LintUtils.endsWith(jar.getPath(), DOT_JAR)
&& !libraries.contains(jar)) {
libraries.add(jar);
}
}
}
}
if (classes.isEmpty()) {
File folder = new File(projectDir, CLASS_FOLDER);
if (folder.exists()) {
classes.add(folder);
} else {
// Maven checks
folder = new File(projectDir,
"target" + File.separator + "classes"); //$NON-NLS-1$ //$NON-NLS-2$
if (folder.exists()) {
classes.add(folder);
// If it's maven, also correct the source path, "src" works but
// it's in a more specific subfolder
if (sources.isEmpty()) {
File src = new File(projectDir,
"src" + File.separator //$NON-NLS-1$
+ "main" + File.separator //$NON-NLS-1$
+ "java"); //$NON-NLS-1$
if (src.exists()) {
sources.add(src);
} else {
src = new File(projectDir, SRC_FOLDER);
if (src.exists()) {
sources.add(src);
}
}
File gen = new File(projectDir,
"target" + File.separator //$NON-NLS-1$
+ "generated-sources" + File.separator //$NON-NLS-1$
+ "r"); //$NON-NLS-1$
if (gen.exists()) {
sources.add(gen);
}
}
}
}
}
// Fallback, in case there is no Eclipse project metadata here
if (sources.isEmpty()) {
File src = new File(projectDir, SRC_FOLDER);
if (src.exists()) {
sources.add(src);
}
File gen = new File(projectDir, GEN_FOLDER);
if (gen.exists()) {
sources.add(gen);
}
}
info = new ClassPathInfo(sources, classes, libraries);
mProjectInfo.put(project, info);
}
return info;
}
/**
* A map from directory to existing projects, or null. Used to ensure that
* projects are unique for a directory (in case we process a library project
* before its including project for example)
*/
private Map<File, Project> mDirToProject;
/**
* Returns a project for the given directory. This should return the same
* project for the same directory if called repeatedly.
*
* @param dir the directory containing the project
* @param referenceDir See {@link Project#getReferenceDir()}.
* @return a project, never null
*/
@NonNull
public Project getProject(@NonNull File dir, @NonNull File referenceDir) {
if (mDirToProject == null) {
mDirToProject = new HashMap<File, Project>();
}
File canonicalDir = dir;
try {
// Attempt to use the canonical handle for the file, in case there
// are symlinks etc present (since when handling library projects,
// we also call getCanonicalFile to compute the result of appending
// relative paths, which can then resolve symlinks and end up with
// a different prefix)
canonicalDir = dir.getCanonicalFile();
} catch (IOException ioe) {
// pass
}
Project project = mDirToProject.get(canonicalDir);
if (project != null) {
return project;
}
project = createProject(dir, referenceDir);
mDirToProject.put(canonicalDir, project);
return project;
}
/**
* Registers the given project for the given directory. This can
* be used when projects are initialized outside of the client itself.
*
* @param dir the directory of the project, which must be unique
* @param project the project
*/
public void registerProject(@NonNull File dir, @NonNull Project project) {
File canonicalDir = dir;
try {
// Attempt to use the canonical handle for the file, in case there
// are symlinks etc present (since when handling library projects,
// we also call getCanonicalFile to compute the result of appending
// relative paths, which can then resolve symlinks and end up with
// a different prefix)
canonicalDir = dir.getCanonicalFile();
} catch (IOException ioe) {
// pass
}
if (mDirToProject == null) {
mDirToProject = new HashMap<File, Project>();
} else {
assert !mDirToProject.containsKey(dir) : dir;
}
mDirToProject.put(canonicalDir, project);
}
private Set<File> mProjectDirs = Sets.newHashSet();
/**
* Create a project for the given directory
* @param dir the root directory of the project
* @param referenceDir See {@link Project#getReferenceDir()}.
* @return a new project
*/
@NonNull
protected Project createProject(@NonNull File dir, @NonNull File referenceDir) {
if (mProjectDirs.contains(dir)) {
throw new CircularDependencyException(
"Circular library dependencies; check your project.properties files carefully");
}
mProjectDirs.add(dir);
return Project.create(this, dir, referenceDir);
}
/**
* Returns the name of the given project
*
* @param project the project to look up
* @return the name of the project
*/
@NonNull
public String getProjectName(@NonNull Project project) {
return project.getDir().getName();
}
protected IAndroidTarget[] mTargets;
/**
* Returns all the {@link IAndroidTarget} versions installed in the user's SDK install
* area.
*
* @return all the installed targets
*/
@NonNull
public IAndroidTarget[] getTargets() {
if (mTargets == null) {
LocalSdk localSdk = getSdk();
if (localSdk != null) {
mTargets = localSdk.getTargets();
} else {
mTargets = new IAndroidTarget[0];
}
}
return mTargets;
}
protected LocalSdk mSdk;
/**
* Returns the SDK installation (used to look up platforms etc)
*
* @return the SDK if known
*/
@Nullable
public LocalSdk getSdk() {
if (mSdk == null) {
File sdkHome = getSdkHome();
if (sdkHome != null) {
mSdk = new LocalSdk(sdkHome);
}
}
return mSdk;
}
/**
* Returns the compile target to use for the given project
*
* @param project the project in question
*
* @return the compile target to use to build the given project
*/
@Nullable
public IAndroidTarget getCompileTarget(@NonNull Project project) {
int buildSdk = project.getBuildSdk();
IAndroidTarget[] targets = getTargets();
for (int i = targets.length - 1; i >= 0; i--) {
IAndroidTarget target = targets[i];
if (target.isPlatform() && target.getVersion().getApiLevel() == buildSdk) {
return target;
}
}
return null;
}
/**
* Returns the highest known API level.
*
* @return the highest known API level
*/
public int getHighestKnownApiLevel() {
int max = SdkVersionInfo.HIGHEST_KNOWN_STABLE_API;
for (IAndroidTarget target : getTargets()) {
if (target.isPlatform()) {
int api = target.getVersion().getApiLevel();
if (api > max && !target.getVersion().isPreview()) {
max = api;
}
}
}
return max;
}
/**
* Returns the super class for the given class name, which should be in VM
* format (e.g. java/lang/Integer, not java.lang.Integer, and using $ rather
* than . for inner classes). If the super class is not known, returns null.
* <p>
* This is typically not necessary, since lint analyzes all the available
* classes. However, if this lint client is invoking lint in an incremental
* context (for example, an IDE offering incremental analysis of a single
* source file), then lint may not see all the classes, and the client can
* provide its own super class lookup.
*
* @param project the project containing the class
* @param name the fully qualified class name
* @return the corresponding super class name (in VM format), or null if not
* known
*/
@Nullable
public String getSuperClass(@NonNull Project project, @NonNull String name) {
return null;
}
/**
* Checks whether the given name is a subclass of the given super class. If
* the method does not know, it should return null, and otherwise return
* {@link Boolean#TRUE} or {@link Boolean#FALSE}.
* <p>
* Note that the class names are in internal VM format (java/lang/Integer,
* not java.lang.Integer, and using $ rather than . for inner classes).
*
* @param project the project context to look up the class in
* @param name the name of the class to be checked
* @param superClassName the name of the super class to compare to
* @return true if the class of the given name extends the given super class
*/
@SuppressWarnings("NonBooleanMethodNameMayNotStartWithQuestion")
@Nullable
public Boolean isSubclassOf(
@NonNull Project project,
@NonNull String name, @NonNull
String superClassName) {
return null;
}
/**
* Finds any custom lint rule jars that should be included for analysis,
* regardless of project.
* <p>
* The default implementation locates custom lint jars in ~/.android/lint/ and
* in $ANDROID_LINT_JARS
*
* @return a list of rule jars (possibly empty).
*/
@SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden
@NonNull
public List<File> findGlobalRuleJars() {
// Look for additional detectors registered by the user, via
// (1) an environment variable (useful for build servers etc), and
// (2) via jar files in the .android/lint directory
List<File> files = null;
try {
String androidHome = AndroidLocation.getFolder();
File lint = new File(androidHome + File.separator + "lint"); //$NON-NLS-1$
if (lint.exists()) {
File[] list = lint.listFiles();
if (list != null) {
for (File jarFile : list) {
if (endsWith(jarFile.getName(), DOT_JAR)) {
if (files == null) {
files = new ArrayList<File>();
}
files.add(jarFile);
}
}
}
}
} catch (AndroidLocation.AndroidLocationException e) {
// Ignore -- no android dir, so no rules to load.
}
String lintClassPath = System.getenv("ANDROID_LINT_JARS"); //$NON-NLS-1$
if (lintClassPath != null && !lintClassPath.isEmpty()) {
String[] paths = lintClassPath.split(File.pathSeparator);
for (String path : paths) {
File jarFile = new File(path);
if (jarFile.exists()) {
if (files == null) {
files = new ArrayList<File>();
} else if (files.contains(jarFile)) {
continue;
}
files.add(jarFile);
}
}
}
return files != null ? files : Collections.<File>emptyList();
}
/**
* Finds any custom lint rule jars that should be included for analysis
* in the given project
*
* @param project the project to look up rule jars from
* @return a list of rule jars (possibly empty).
*/
@SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden
@NonNull
public List<File> findRuleJars(@NonNull Project project) {
if (project.getDir().getPath().endsWith(DOT_AAR)) {
File lintJar = new File(project.getDir(), "lint.jar"); //$NON-NLS-1$
if (lintJar.exists()) {
return Collections.singletonList(lintJar);
}
}
return Collections.emptyList();
}
/**
* Opens a URL connection.
*
* Clients such as IDEs can override this to for example consider the user's IDE proxy
* settings.
*
* @param url the URL to read
* @return a {@link java.net.URLConnection} or null
* @throws IOException if any kind of IO exception occurs
*/
@Nullable
public URLConnection openConnection(@NonNull URL url) throws IOException {
return url.openConnection();
}
/** Closes a connection previously returned by {@link #openConnection(java.net.URL)} */
public void closeConnection(@NonNull URLConnection connection) throws IOException {
if (connection instanceof HttpURLConnection) {
((HttpURLConnection)connection).disconnect();
}
}
/**
* Returns true if the given directory is a lint project directory.
* By default, a project directory is the directory containing a manifest file,
* but in Gradle projects for example it's the root gradle directory.
*
* @param dir the directory to check
* @return true if the directory represents a lint project
*/
@SuppressWarnings("MethodMayBeStatic") // Intentionally instance method so it can be overridden
public boolean isProjectDirectory(@NonNull File dir) {
return LintUtils.isManifestFolder(dir) || Project.isAospFrameworksProject(dir);
}
/**
* Returns whether lint should look for suppress comments. Tools that already do
* this on their own can return false here to avoid doing unnecessary work.
*/
public boolean checkForSuppressComments() {
return true;
}
/**
* Adds in any custom lint rules and returns the result as a new issue registry,
* or the same one if no custom rules were found
*
* @param registry the main registry to add rules to
* @return a new registry containing the passed in rules plus any custom rules,
* or the original registry if no custom rules were found
*/
public IssueRegistry addCustomLintRules(@NonNull IssueRegistry registry) {
List<File> jarFiles = findGlobalRuleJars();
if (!jarFiles.isEmpty()) {
List<IssueRegistry> registries = Lists.newArrayListWithExpectedSize(jarFiles.size());
registries.add(registry);
for (File jarFile : jarFiles) {
try {
registries.add(JarFileIssueRegistry.get(this, jarFile));
} catch (Throwable e) {
log(e, "Could not load custom rule jar file %1$s", jarFile);
}
}
if (registries.size() > 1) { // the first item is the passed in registry itself
return new CompositeIssueRegistry(registries);
}
}
return registry;
}
/**
* Returns true if this client supports project resource repository lookup via
* {@link #getProjectResources(Project,boolean)}
*
* @return true if the client can provide project resources
*/
public boolean supportsProjectResources() {
return false;
}
/**
* Returns the project resources, if available
*
* @param includeDependencies if true, include merged view of all dependencies
* @return the project resources, or null if not available
*/
@Nullable
public AbstractResourceRepository getProjectResources(Project project,
boolean includeDependencies) {
return null;
}
/**
* For a lint client which supports resource items (via {@link #supportsProjectResources()})
* return a handle for a resource item
*
* @param item the resource item to look up a location handle for
* @return a corresponding handle
*/
@NonNull
public Location.Handle createResourceItemHandle(@NonNull ResourceItem item) {
return new Location.ResourceItemHandle(item);
}
}