aboutsummaryrefslogtreecommitdiffstats
path: root/lint/libs/lint_api
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2012-08-01 11:03:56 -0700
committerTor Norbye <tnorbye@google.com>2012-08-01 14:32:44 -0700
commit8dcd0726088eee54368e032e7dc6b31a450b4ee1 (patch)
treef8f4af2c8d1fde403997212ddabfe89c59e4dcab /lint/libs/lint_api
parent2de64c74ebc0976a2954cde07bb205069bd36bb6 (diff)
downloadsdk-8dcd0726088eee54368e032e7dc6b31a450b4ee1.zip
sdk-8dcd0726088eee54368e032e7dc6b31a450b4ee1.tar.gz
sdk-8dcd0726088eee54368e032e7dc6b31a450b4ee1.tar.bz2
Fix lint-on-save for .class file detectors in Java files
This changeset fixes the lint-on-save behavior in Java files such that the classfile based checks are run after the .class files are up to date. It also makes lint-on-save work when Project > Build Automatically is turned off, by adding a new resource listener, and it modifies the IFileListener interface to make resource listening more efficient; in particular, it passes the flag mask such that listeners can ignore events such as markers getting added or removed from a file without the content changing. It also makes some improvements to the lint infrastructure. First, it adds an indirection in the LintClient such that reading bytes from files can be customized by the client (to for example add caching or to read contents from memory not yet flushed to disk). It also allows inner classes to share the contents of the source file between each context (while debugging the above I noticed that each inner class node had its own class context and therefore would re-read the source file repeatedly.) Change-Id: Ib9572cebe1269fe05c3af1369610525ea3b44061
Diffstat (limited to 'lint/libs/lint_api')
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java17
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java40
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java28
-rw-r--r--lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java9
4 files changed, 86 insertions, 8 deletions
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java
index 93e311e..4cca31c 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintClient.java
@@ -33,6 +33,7 @@ import com.android.tools.lint.detector.api.Project;
import com.android.tools.lint.detector.api.Severity;
import com.google.common.annotations.Beta;
import com.google.common.collect.Maps;
+import com.google.common.io.Files;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -177,6 +178,22 @@ public abstract class LintClient {
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
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java
index 270ffa4..d91b155 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/LintDriver.java
@@ -55,7 +55,6 @@ import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.ByteStreams;
import com.google.common.io.Closeables;
-import com.google.common.io.Files;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
@@ -1023,7 +1022,7 @@ public class LintDriver {
String path = file.getPath();
if (file.isFile() && path.endsWith(DOT_CLASS)) {
try {
- byte[] bytes = Files.toByteArray(file);
+ byte[] bytes = mClient.readBytes(file);
if (bytes != null) {
for (File dir : classFolders) {
if (path.startsWith(dir.getPath())) {
@@ -1068,6 +1067,8 @@ public class LintDriver {
if (classDetectors != null && classDetectors.size() > 0 && entries.size() > 0) {
AsmVisitor visitor = new AsmVisitor(mClient, classDetectors);
+ String sourceContents = null;
+ String sourceName = "";
mOuterClasses = new ArrayDeque<ClassNode>();
for (ClassEntry entry : entries) {
ClassReader reader;
@@ -1097,9 +1098,31 @@ public class LintDriver {
continue;
}
+ if (sourceContents != null) {
+ // Attempt to reuse the source buffer if initialized
+ // This means making sure that the source files
+ // foo/bar/MyClass and foo/bar/MyClass$Bar
+ // and foo/bar/MyClass$3 and foo/bar/MyClass$3$1 have the same prefix.
+ String newName = classNode.name;
+ int newRootLength = newName.indexOf('$');
+ if (newRootLength == -1) {
+ newRootLength = newName.length();
+ }
+ int oldRootLength = sourceName.indexOf('$');
+ if (oldRootLength == -1) {
+ oldRootLength = sourceName.length();
+ }
+ if (newRootLength != oldRootLength ||
+ !sourceName.regionMatches(0, newName, 0, newRootLength)) {
+ sourceContents = null;
+ }
+ }
+
ClassContext context = new ClassContext(this, project, main,
entry.file, entry.jarFile, entry.binDir, entry.bytes,
- classNode, scope == Scope.JAVA_LIBRARIES /*fromLibrary*/);
+ classNode, scope == Scope.JAVA_LIBRARIES /*fromLibrary*/,
+ sourceContents);
+
try {
visitor.runClassDetectors(context);
} catch (Exception e) {
@@ -1109,6 +1132,9 @@ public class LintDriver {
if (mCanceled) {
return;
}
+
+ sourceContents = context.getSourceContents(false/*read*/);
+ sourceName = classNode.name;
}
mOuterClasses = null;
@@ -1229,7 +1255,7 @@ public class LintDriver {
for (File file : classFiles) {
try {
- byte[] bytes = Files.toByteArray(file);
+ byte[] bytes = mClient.readBytes(file);
if (bytes != null) {
entries.add(new ClassEntry(file, null /* jarFile*/, binDir, bytes));
}
@@ -1568,6 +1594,12 @@ public class LintDriver {
@Override
@NonNull
+ public byte[] readBytes(@NonNull File file) throws IOException {
+ return mDelegate.readBytes(file);
+ }
+
+ @Override
+ @NonNull
public List<File> getJavaSourceFolders(@NonNull Project project) {
return mDelegate.getJavaSourceFolders(project);
}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
index 4aa21d4..5f8cac9 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
@@ -82,6 +82,8 @@ public class ClassContext extends Context {
* @param classNode the bytecode object model
* @param fromLibrary whether this class is from a library rather than part
* of this project
+ * @param sourceContents initial contents of the Java source, if known, or
+ * null
*/
public ClassContext(
@NonNull LintDriver driver,
@@ -92,13 +94,15 @@ public class ClassContext extends Context {
@NonNull File binDir,
@NonNull byte[] bytes,
@NonNull ClassNode classNode,
- boolean fromLibrary) {
+ boolean fromLibrary,
+ @Nullable String sourceContents) {
super(driver, project, main, file);
mJarFile = jarFile;
mBinDir = binDir;
mBytes = bytes;
mClassNode = classNode;
mFromLibrary = fromLibrary;
+ mSourceContents = sourceContents;
}
/**
@@ -217,6 +221,28 @@ public class ClassContext extends Context {
}
/**
+ * Returns the contents of the source file for this class file, if found. If
+ * {@code read} is false, do not read the source contents if it has not
+ * already been read. (This is primarily intended for the lint
+ * infrastructure; most client code would call {@link #getSourceContents()}
+ * .)
+ *
+ * @param read whether to read the source contents if it has not already
+ * been initialized
+ * @return the source contents, which will never be null if {@code read} is
+ * true, or null if {@code read} is false and the source contents
+ * hasn't already been read.
+ */
+ @Nullable
+ public String getSourceContents(boolean read) {
+ if (read) {
+ return getSourceContents();
+ } else {
+ return mSourceContents;
+ }
+ }
+
+ /**
* Returns a location for the given source line number in this class file's
* source file, if available.
*
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java
index 9bfb7cc..ce64fe3 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/LintUtils.java
@@ -27,9 +27,9 @@ import com.android.annotations.Nullable;
import com.android.resources.FolderTypeRelationship;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
+import com.android.tools.lint.client.api.LintClient;
import com.android.util.PositionXmlParser;
import com.google.common.annotations.Beta;
-import com.google.common.io.Files;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
@@ -394,13 +394,16 @@ public class LintUtils {
* same as {@code Files.toString(file, Charsets.UTF8}, but if there's a UTF byte order mark
* (for UTF8, UTF_16 or UTF_16LE), use that instead.
*
+ * @param client the client to use for I/O operations
* @param file the file to read from
* @return the string
* @throws IOException if the file cannot be read properly
*/
@NonNull
- public static String getEncodedString(@NonNull File file) throws IOException {
- byte[] bytes = Files.toByteArray(file);
+ public static String getEncodedString(
+ @NonNull LintClient client,
+ @NonNull File file) throws IOException {
+ byte[] bytes = client.readBytes(file);
if (endsWith(file.getName(), DOT_XML)) {
return PositionXmlParser.getXmlString(bytes);
}