diff options
author | Tor Norbye <tnorbye@google.com> | 2012-08-01 11:03:56 -0700 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2012-08-01 14:32:44 -0700 |
commit | 8dcd0726088eee54368e032e7dc6b31a450b4ee1 (patch) | |
tree | f8f4af2c8d1fde403997212ddabfe89c59e4dcab /lint/libs/lint_api | |
parent | 2de64c74ebc0976a2954cde07bb205069bd36bb6 (diff) | |
download | sdk-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')
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); } |