Add Java Classfile support

Change-Id: Ifcc1d88dabb07dc05b2e5c934743ad52f9b6dc1d
diff --git a/build/tools.atree b/build/tools.atree
index c3b4232..30aaec1 100644
--- a/build/tools.atree
+++ b/build/tools.atree
@@ -126,6 +126,8 @@
 framework/org.eclipse.jface_3.4.2.M20090107-0800.jar          tools/lib/org.eclipse.jface_3.4.2.M20090107-0800.jar
 framework/osgi.jar                                            tools/lib/osgi.jar
 framework/swing-worker-1.1.jar                                tools/lib/swing-worker-1.1.jar
+prebuilts/tools/common/asm-tools/asm-4.0.jar                  tools/lib/asm-4.0.jar
+prebuilts/tools/common/asm-tools/asm-tree-4.0.jar             tools/lib/asm-tree-4.0.jar
 
 # Proguard
 external/proguard/docs/license.html                           tools/proguard/license.html
diff --git a/changes.txt b/changes.txt
index af7bab8..14572c0 100644
--- a/changes.txt
+++ b/changes.txt
@@ -1,5 +1,8 @@
 Change log for Android SDK Tools.
 
+Revision 17:
+* New lint rules which analyze Java source and class files.
+
 Revision 16:
 * New "lint" tool which scans Android project trees for potential
   problems such as missing translations, duplicate ids between layouts
diff --git a/eclipse/dictionary.txt b/eclipse/dictionary.txt
index 068590c..c7e5331 100644
--- a/eclipse/dictionary.txt
+++ b/eclipse/dictionary.txt
@@ -157,6 +157,7 @@
 marquee
 mdpi
 metadata
+micro
 min
 mipmap
 monospace
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
index d7bf98e..7493662 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/.classpath
@@ -24,5 +24,7 @@
 	<classpathentry kind="lib" path="libs/assetstudio.jar" sourcepath="/assetstudio"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/ddmlib"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/ddmuilib"/>
+        <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
+        <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
index ae9f406..58111cc 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/META-INF/MANIFEST.MF
@@ -22,6 +22,8 @@
  libs/httpcore-4.1.jar,
  libs/httpmime-4.1.1.jar,
  libs/httpclient-4.1.1.jar,
+ libs/asm-4.0.jar,
+ libs/asm-tree-4.0.jar,
  libs/commons-logging-1.1.1.jar,
  libs/commons-codec-1.4.jar
 Bundle-Activator: com.android.ide.eclipse.adt.AdtPlugin
diff --git a/eclipse/scripts/create_all_symlinks.sh b/eclipse/scripts/create_all_symlinks.sh
index c6cd467..1000143 100755
--- a/eclipse/scripts/create_all_symlinks.sh
+++ b/eclipse/scripts/create_all_symlinks.sh
@@ -62,6 +62,8 @@
 ADT_LIBS="sdkstats androidprefs common layoutlib_api lint_api lint_checks ide_common rule_api ninepatch sdklib sdkuilib assetstudio"
 ADT_PREBUILTS="\
     prebuilt/common/kxml2/kxml2-2.3.0.jar \
+    prebuilts/tools/common/asm-tools/asm-4.0.jar \
+    prebuilts/tools/common/asm-tools/asm-tree-4.0.jar \
     prebuilt/common/commons-compress/commons-compress-1.0.jar \
     prebuilt/common/http-client/httpclient-4.1.1.jar \
     prebuilt/common/http-client/httpcore-4.1.jar \
diff --git a/lint/cli/.classpath b/lint/cli/.classpath
index f6efb87..cd864d8 100644
--- a/lint/cli/.classpath
+++ b/lint/cli/.classpath
@@ -4,5 +4,7 @@
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/lint-api"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/lint-checks"/>
+        <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
+        <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/lint/cli/Android.mk b/lint/cli/Android.mk
index 5b99dc4..63448d4 100644
--- a/lint/cli/Android.mk
+++ b/lint/cli/Android.mk
@@ -12,6 +12,10 @@
 LOCAL_JAVA_LIBRARIES := \
 	lint_api \
 	lint_checks
+LOCAL_STATIC_JAVA_LIBRARIES := \
+	asm-tools \
+	asm-tree-tools
+
 LOCAL_MODULE := lint
 LOCAL_MODULE_TAGS := optional
 
diff --git a/lint/cli/etc/manifest.txt b/lint/cli/etc/manifest.txt
index e75087d..b75ca8a 100644
--- a/lint/cli/etc/manifest.txt
+++ b/lint/cli/etc/manifest.txt
@@ -1,2 +1,2 @@
 Main-Class: com.android.tools.lint.Main
-Class-Path: lint_api.jar lint_checks.jar
+Class-Path: lint_api.jar lint_checks.jar asm-4.0.jar asm-tree-4.0.jar
diff --git a/lint/libs/lint_api/.classpath b/lint/libs/lint_api/.classpath
index 012d361..12d1046 100644
--- a/lint/libs/lint_api/.classpath
+++ b/lint/libs/lint_api/.classpath
@@ -3,5 +3,7 @@
 	<classpathentry excluding="Android.mk" kind="src" path="src"/>
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/common"/>
+        <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
+        <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/lint/libs/lint_api/Android.mk b/lint/libs/lint_api/Android.mk
index 847f52e..b089ef1 100644
--- a/lint/libs/lint_api/Android.mk
+++ b/lint/libs/lint_api/Android.mk
@@ -21,6 +21,9 @@
 LOCAL_JAVA_RESOURCE_DIRS := src
 LOCAL_JAVA_LIBRARIES := \
         common
+LOCAL_STATIC_JAVA_LIBRARIES := \
+        asm-tools \
+        asm-tree-tools
 
 LOCAL_MODULE := lint_api
 LOCAL_MODULE_TAGS := optional
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java b/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java
index 42c2ec7..2a1d587 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/client/api/Lint.java
@@ -22,6 +22,7 @@
 import com.android.resources.ResourceFolderType;
 import com.android.tools.lint.client.api.LintListener.EventType;
 import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
 import com.android.tools.lint.detector.api.Context;
 import com.android.tools.lint.detector.api.Detector;
 import com.android.tools.lint.detector.api.Issue;
@@ -33,7 +34,11 @@
 import com.android.tools.lint.detector.api.Severity;
 import com.android.tools.lint.detector.api.XmlContext;
 
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.tree.ClassNode;
+
 import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -395,7 +400,7 @@
 
         if (mScope.contains(Scope.CLASS_FILE)) {
             List<Detector> detectors = mScopeDetectors.get(Scope.CLASS_FILE);
-            if (detectors != null) {
+            if (detectors != null && detectors.size() > 0) {
                 List<File> binFolders = project.getJavaClassFolders();
                 checkClasses(project, binFolders, detectors);
             }
@@ -441,13 +446,63 @@
     }
 
     private void checkClasses(Project project, List<File> binFolders, List<Detector> checks) {
-        Context context = new Context(mClient, project, project.getDir(), mScope);
-        fireEvent(EventType.SCANNING_FILE, context);
-        for (Detector detector : checks) {
-            ((Detector.ClassScanner) detector).checkJavaClasses(context);
+        if (binFolders.size() == 0) {
+            //mClient.log(null, "Warning: Class-file checks are enabled, but no " +
+            //        "output folders found. Does the project need to be built first?");
+        }
 
-            if (mCanceled) {
-                return;
+        for (File binDir : binFolders) {
+            List<File> classFiles = new ArrayList<File>();
+            addClassFiles(binDir, classFiles);
+
+            for (File file : classFiles) {
+                try {
+                    byte[] bytes = LintUtils.readBytes(file);
+                    if (bytes != null) {
+                        ClassReader reader = new ClassReader(bytes);
+                        ClassNode classNode = new ClassNode();
+                        reader.accept(classNode, 0 /*flags*/);
+                        ClassContext context = new ClassContext(mClient, project, file, mScope,
+                                binDir, bytes, classNode);
+
+                        for (Detector detector : checks) {
+                            if (detector.appliesTo(context, file)) {
+                                fireEvent(EventType.SCANNING_FILE, context);
+                                detector.beforeCheckFile(context);
+
+                                Detector.ClassScanner scanner = (Detector.ClassScanner) detector;
+                                scanner.checkClass(context, classNode);
+                                detector.afterCheckFile(context);
+                            }
+
+                            if (mCanceled) {
+                                return;
+                            }
+                        }
+                    }
+                } catch (IOException e) {
+                    mClient.log(e, null);
+                    continue;
+                }
+
+                if (mCanceled) {
+                    return;
+                }
+            }
+        }
+    }
+
+    private void addClassFiles(File dir, List<File> classFiles) {
+        // Process the resource folder
+        File[] files = dir.listFiles();
+        if (files != null && files.length > 0) {
+            for (File file : files) {
+                if (file.isFile() && file.getName().endsWith(DOT_CLASS)) {
+                    classFiles.add(file);
+                } else if (file.isDirectory()) {
+                    // Recurse
+                    addClassFiles(file, classFiles);
+                }
             }
         }
     }
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
new file mode 100644
index 0000000..f35889c
--- /dev/null
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/ClassContext.java
@@ -0,0 +1,118 @@
+/*
+ * 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.detector.api;
+
+import static com.android.tools.lint.detector.api.LintConstants.DOT_CLASS;
+import static com.android.tools.lint.detector.api.LintConstants.DOT_JAVA;
+
+import com.android.tools.lint.client.api.LintClient;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.tree.ClassNode;
+
+import java.io.File;
+import java.util.EnumSet;
+import java.util.List;
+
+/**
+ * A {@link Context} used when checking .class files.
+ * <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>
+ */
+public class ClassContext extends Context {
+    private final File mBinDir;
+    private ClassNode mClassNode;
+    private byte[] mBytes;
+
+    /**
+     * Construct a new {@link ClassContext}
+     *
+     * @param client the client requesting a lint check
+     * @param project the project containing the file being checked
+     * @param file the file being checked
+     * @param scope the scope for the lint job
+     * @param binDir the root binary directory containing this .class file.
+     * @param bytes the bytecode raw data
+     * @param classNode the bytecode object model
+     */
+    public ClassContext(LintClient client, Project project, File file,
+            EnumSet<Scope> scope, File binDir, byte[] bytes, ClassNode classNode) {
+        super(client, project, file, scope);
+        mBinDir = binDir;
+        mBytes = bytes;
+        mClassNode = classNode;
+    }
+
+    /**
+     * Returns the raw bytecode data for this class file
+     *
+     * @return the byte array containing the bytecode data, or null
+     */
+    public byte[] getBytecode() {
+        return mBytes;
+    }
+
+    /**
+     * Returns the bytecode object model
+     *
+     * @return the bytecode object model
+     */
+    public ClassNode getClassNode() {
+        return mClassNode;
+    }
+
+    /**
+     * Finds the corresponding source file for the current class file.
+     *
+     * @param source the name of the source file, if known (which is often
+     *            stored in the bytecode and provided by the
+     *            {@link ClassVisitor#visitSource(String, String)} method). If
+     *            not known, this method will guess by stripping out inner-class
+     *            suffixes like $1.
+     * @return the source file corresponding to the {@link #file} field.
+     */
+    public File findSourceFile(String source) {
+        if (source == null) {
+            source = file.getName();
+            if (source.endsWith(DOT_CLASS)) {
+                source = source.substring(0, source.length() - DOT_CLASS.length()) + DOT_JAVA;
+            }
+            int index = source.indexOf('$');
+            if (index != -1) {
+                source = source.substring(0, index) + DOT_JAVA;
+            }
+        }
+        if (source != null) {
+            // Determine package
+            String topPath = mBinDir.getPath();
+            String parentPath = file.getParentFile().getPath();
+            if (parentPath.startsWith(topPath)) {
+                String relative = parentPath.substring(topPath.length() + 1);
+                List<File> sources = getProject().getJavaSourceFolders();
+                for (File dir : sources) {
+                    File sourceFile = new File(dir, relative + File.separator + source);
+                    if (sourceFile.exists()) {
+                        return sourceFile;
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+}
diff --git a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java
index aaeee5a..c502e11 100644
--- a/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java
+++ b/lint/libs/lint_api/src/com/android/tools/lint/detector/api/Detector.java
@@ -16,6 +16,7 @@
 
 package com.android.tools.lint.detector.api;
 
+import org.objectweb.asm.tree.ClassNode;
 import org.w3c.dom.Attr;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
@@ -51,10 +52,14 @@
 
     /** Specialized interface for detectors that scan Java class files */
     public interface ClassScanner  {
-        // TODO: Class file / bytecode scanning support not yet implemented. This
-        // is a placeholder.
-        @SuppressWarnings("javadoc")
-        void checkJavaClasses(Context context);
+        /**
+         * Checks the given class' bytecode for issues.
+         *
+         * @param context the context of the lint check, pointing to for example
+         *            the file
+         * @param classNode the root class node
+         */
+        void checkClass(ClassContext context, ClassNode classNode);
     }
 
     /** Specialized interface for detectors that scan XML files */
@@ -236,4 +241,10 @@
     public void checkJavaSources(Context context, List<File> sourceFolders) {
     }
 
+    // ---- Dummy implementations to make implementing a ClassScanner easier: ----
+
+    @SuppressWarnings("javadoc")
+    public void checkClass(ClassContext context, ClassNode classNode) {
+
+    }
 }
diff --git a/lint/libs/lint_checks/.classpath b/lint/libs/lint_checks/.classpath
index 773c06d..eab475b 100644
--- a/lint/libs/lint_checks/.classpath
+++ b/lint/libs/lint_checks/.classpath
@@ -5,5 +5,7 @@
 	<classpathentry combineaccessrules="false" kind="src" path="/lint-api"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/common"/>
 	<classpathentry combineaccessrules="false" kind="src" path="/AndroidPrefs"/>
+        <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
+        <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-tree-4.0.jar" sourcepath="/ANDROID_SRC/prebuilts/tools/common/asm-tools/src.zip"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/lint/libs/lint_checks/Android.mk b/lint/libs/lint_checks/Android.mk
index b4eb2a1..d51d3c8 100644
--- a/lint/libs/lint_checks/Android.mk
+++ b/lint/libs/lint_checks/Android.mk
@@ -14,6 +14,9 @@
         common \
         androidprefs \
 	lint_api
+LOCAL_STATIC_JAVA_LIBRARIES := \
+        asm-tools \
+        asm-tree-tools
 
 LOCAL_MODULE := lint_checks
 LOCAL_MODULE_TAGS := optional
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
index 8522ea4..b1644d9 100644
--- a/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/BuiltinIssueRegistry.java
@@ -54,6 +54,8 @@
         List<Issue> issues = new ArrayList<Issue>();
 
         issues.add(AccessibilityDetector.ISSUE);
+        issues.add(MathDetector.ISSUE);
+        issues.add(FieldGetterDetector.ISSUE);
         issues.add(DuplicateIdDetector.CROSS_LAYOUT);
         issues.add(DuplicateIdDetector.WITHIN_LAYOUT);
         issues.add(StateListDetector.ISSUE);
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java
new file mode 100644
index 0000000..a9085f7
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/FieldGetterDetector.java
@@ -0,0 +1,245 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+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.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+import com.android.util.Pair;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Looks for getter calls within the same class that could be replaced by
+ * direct field references instead.
+ */
+public class FieldGetterDetector extends Detector implements Detector.ClassScanner {
+    /** The main issue discovered by this detector */
+    public static final Issue ISSUE = Issue.create(
+            "FieldGetter", //$NON-NLS-1$
+            "Suggests replacing uses of getters with direct field access within a class",
+
+            "Accessing a field within the class that defines a getter for that field is " +
+            "at least 3 times faster than calling the getter. For simple getters that do " +
+            "nothing other than return the field, you might want to just reference the " +
+            "local field directly instead.",
+
+            Category.PERFORMANCE,
+            4,
+            Severity.WARNING,
+            FieldGetterDetector.class,
+            EnumSet.of(Scope.CLASS_FILE)).
+            // This is a micro-optimization: not enabled by default
+            setEnabledByDefault(false).setMoreInfo(
+           "http://developer.android.com/guide/practices/design/performance.html#internal_get_set"); //$NON-NLS-1$
+
+    /** Constructs a new accessibility check */
+    public FieldGetterDetector() {
+    }
+
+    @Override
+    public boolean appliesTo(Context context, File file) {
+        return true;
+    }
+
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    // ---- Implements ClassScanner ----
+
+    @Override
+    public void checkClass(ClassContext context, ClassNode classNode) {
+        classNode.accept(new Visitor(context));
+    }
+
+    private static class Visitor extends ClassVisitor {
+        private final ClassContext mContext;
+        private int mCurrentLine;
+        private String mClass;
+        private List<Pair<String, Integer>> mPendingCalls;
+
+        public Visitor(ClassContext context) {
+            super(Opcodes.ASM4);
+            mContext = context;
+        }
+
+        @Override
+        public void visit(int version, int access, String name, String signature,
+                String superName, String[] interfaces) {
+            mClass = name;
+        }
+
+        @Override
+        public void visitEnd() {
+            if (mPendingCalls != null) {
+                Set<String> names = new HashSet<String>(mPendingCalls.size());
+                for (Pair<String, Integer> pair : mPendingCalls) {
+                    String name = pair.getFirst();
+                    names.add(name);
+                }
+
+                List<String> getters = checkMethods(mContext.getClassNode(), names);
+                if (getters.size() > 0) {
+                    File source = mContext.findSourceFile(null);
+                    String contents = null;
+                    if (source != null) {
+                        contents = mContext.getClient().readFile(source);
+                    }
+                    for (String getter : getters) {
+                        for (Pair<String, Integer> pair : mPendingCalls) {
+                            String name = pair.getFirst();
+                            // There can be more than one reference to the same name:
+                            // one for each call site
+                            if (name.equals(getter)) {
+                                Integer line = pair.getSecond();
+                                Location location = null;
+                                if (source != null) {
+                                    location = Location.create(source, contents, line);
+                                } else {
+                                    location = Location.create(mContext.file);
+                                }
+                                mContext.report(ISSUE, location, String.format(
+                                    "Calling getter method %1$s() on self is " +
+                                    "slower than field access", getter), null);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        @Override
+        public MethodVisitor visitMethod(int access, String name, String desc, String signature,
+                String[] exceptions) {
+            return new MethodVisitor(Opcodes.ASM4) {
+                @SuppressWarnings("hiding")
+                @Override
+                public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+                    if (((name.startsWith("get") && name.length() > 3             //$NON-NLS-1$
+                                    && Character.isUpperCase(name.charAt(3)))
+                                || (name.startsWith("is") && name.length() > 2    //$NON-NLS-1$
+                                    && Character.isUpperCase(name.charAt(2))))
+                            && owner.equals(mClass)) {
+                        // Calling a potential getter method on self. We now need to
+                        // investigate the method body of the getter call and make sure
+                        // it's really a plain getter, not just a method which happens
+                        // to have a method name like a getter, or a method which not
+                        // only returns a field but possibly computes it or performs
+                        // other initialization or side effects. This is done in a
+                        // second pass over the bytecode, initiated by the finish()
+                        // method.
+                        if (mPendingCalls == null) {
+                            mPendingCalls = new ArrayList<Pair<String,Integer>>();
+                        }
+                        mPendingCalls.add(Pair.of(name, mCurrentLine));
+                    }
+                }
+
+                @Override
+                public void visitLineNumber(int line, Label start) {
+                    mCurrentLine = line;
+                }
+            };
+        }
+    }
+
+    // Validate that these getter methods are really just simple field getters
+    // like these int and STring getters:
+    // public int getFoo();
+    //   Code:
+    //    0:   aload_0
+    //    1:   getfield    #21; //Field mFoo:I
+    //    4:   ireturn
+    //
+    // public java.lang.String getBar();
+    //   Code:
+    //    0:   aload_0
+    //    1:   getfield    #25; //Field mBar:Ljava/lang/String;
+    //    4:   areturn
+    private static List<String> checkMethods(ClassNode classNode, Set<String> names) {
+        List<String> validGetters = new ArrayList<String>();
+        @SuppressWarnings("rawtypes")
+        List methods = classNode.methods;
+        checkMethod:
+        for (Object methodObject : methods) {
+            MethodNode method = (MethodNode) methodObject;
+            if (names.contains(method.name)
+                    && method.desc.startsWith("()")) { //$NON-NLS-1$ // (): No arguments
+                InsnList instructions = method.instructions;
+                int mState = 1;
+                for (AbstractInsnNode curr = instructions.getFirst();
+                        curr != null;
+                        curr = curr.getNext()) {
+                    switch (curr.getOpcode()) {
+                        case -1:
+                            // Skip label and line number nodes
+                            continue;
+                        case Opcodes.ALOAD:
+                            if (mState == 1) {
+                                mState = 2;
+                            } else {
+                                continue checkMethod;
+                            }
+                            break;
+                        case Opcodes.GETFIELD:
+                            if (mState == 2) {
+                                mState = 3;
+                            } else {
+                                continue checkMethod;
+                            }
+                            break;
+                        case Opcodes.ARETURN:
+                        case Opcodes.FRETURN:
+                        case Opcodes.IRETURN:
+                        case Opcodes.DRETURN:
+                        case Opcodes.LRETURN:
+                        case Opcodes.RETURN:
+                            validGetters.add(method.name);
+                            continue checkMethod;
+                        default:
+                            continue checkMethod;
+                    }
+                    curr = curr.getNext();
+                }
+            }
+        }
+
+        return validGetters;
+    }
+}
diff --git a/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java
new file mode 100644
index 0000000..9eccaef
--- /dev/null
+++ b/lint/libs/lint_checks/src/com/android/tools/lint/checks/MathDetector.java
@@ -0,0 +1,212 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Category;
+import com.android.tools.lint.detector.api.ClassContext;
+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.Location;
+import com.android.tools.lint.detector.api.Scope;
+import com.android.tools.lint.detector.api.Severity;
+import com.android.tools.lint.detector.api.Speed;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.ClassNode;
+
+import java.io.File;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Looks for usages of {@link java.lang.Math} methods which can be replaced with
+ * {@code android.util.FloatMath} methods to avoid casting.
+ */
+public class MathDetector extends Detector implements Detector.ClassScanner {
+    /** The main issue discovered by this detector */
+    public static final Issue ISSUE = Issue.create(
+            "FloatMath", //$NON-NLS-1$
+            "Suggests replacing java.lang.Math calls with android.util.FloatMath to " +
+            "avoid conversions",
+
+            "On modern hardware, \"double\" is just as fast as \"float\" though of course " +
+            "it takes more memory. However, if you are using floats and you need to compute " +
+            "the sine, cosine or square root, then it is better to use the " +
+            "android.util.FloatMath class instead of java.lang.Math since you can call methods " +
+            "written to operate on floats, so you avoid conversions back and forth to double.",
+
+            Category.PERFORMANCE,
+            3,
+            Severity.WARNING,
+            MathDetector.class,
+            EnumSet.of(Scope.CLASS_FILE)).setMoreInfo(
+               //"http://developer.android.com/reference/android/util/FloatMath.html"); //$NON-NLS-1$
+               "http://developer.android.com/guide/practices/design/performance.html#avoidfloat"); //$NON-NLS-1$
+
+    /** Constructs a new accessibility check */
+    public MathDetector() {
+    }
+
+    @Override
+    public boolean appliesTo(Context context, File file) {
+        return true;
+    }
+
+    @Override
+    public Speed getSpeed() {
+        return Speed.FAST;
+    }
+
+    // ---- Implements ClassScanner ----
+
+    @Override
+    public void checkClass(ClassContext context, ClassNode classNode) {
+        classNode.accept(new MathCallFinder(context));
+    }
+
+    /** Methods on java.lang.Math that we want to find and suggest replacements for */
+    private static final Set<String> sFloatMethods = new HashSet<String>();
+    static {
+        sFloatMethods.add("sin");   //$NON-NLS-1$
+        sFloatMethods.add("cos");   //$NON-NLS-1$
+        sFloatMethods.add("ceil");  //$NON-NLS-1$
+        sFloatMethods.add("sqrt");  //$NON-NLS-1$
+        sFloatMethods.add("floor"); //$NON-NLS-1$
+    }
+
+    private static class MathCallFinder extends ClassVisitor {
+        private final ClassContext mContext;
+        private String mCurrentSource;
+
+        public MathCallFinder(ClassContext context) {
+            super(Opcodes.ASM4);
+            mContext = context;
+        }
+
+        @Override
+        public void visitSource(String source, String debug) {
+            mCurrentSource = source;
+        }
+
+        @Override
+        public MethodVisitor visitMethod(int access, String name, String desc, String signature,
+                String[] exceptions) {
+            return new MyMethodVisitor(mContext, mCurrentSource);
+        }
+    }
+
+    private static class MyMethodVisitor extends MethodVisitor {
+        private final ClassContext mContext;
+        private int mCurrentLine;
+        private String mCurrentSource;
+        private int mLastInsn;
+        private String mPendingMethod;
+
+        public MyMethodVisitor(ClassContext context, String currentSource) {
+            super(Opcodes.ASM4);
+            mContext = context;
+            mCurrentSource = currentSource;
+        }
+
+        private Location getCurrentLocation() {
+            // Make utility method!
+            File file = mContext.file; // The binary .class file. Try to find source instead.
+            int line = 0;
+
+            // Determine package
+            File source = mContext.findSourceFile(mCurrentSource);
+            if (source != null) {
+                file = source;
+                line = mCurrentLine;
+
+                if (line > 0) {
+                    String contents = mContext.getClient().readFile(file);
+                    if (contents != null) {
+                        // bytecode line numbers are 1-based
+                        return Location.create(file, contents, line - 1);
+                    }
+                }
+            }
+
+            return Location.create(file);
+        }
+
+        @Override
+        public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+            if (sFloatMethods.contains(name) && owner.equals("java/lang/Math")) { //$NON-NLS-1$
+                if (mLastInsn != Opcodes.F2D) {
+                    mPendingMethod = name;
+                    return;
+                }
+                String message = String.format(
+                        "Use android.util.FloatMath#%1$s() instead of java.lang.Math#%1$s to " +
+                        "avoid argument float to double conversion", name);
+                mContext.report(ISSUE, getCurrentLocation(), message, null /*data*/);
+            }
+        }
+
+        @Override
+        public void visitInsn(int opcode) {
+            if (opcode == Opcodes.D2F) {
+                if (mPendingMethod != null) {
+                    String message = String.format(
+                            "Use android.util.FloatMath#%1$s() instead of java.lang.Math#%1$s " +
+                            "to avoid double to float return value conversion", mPendingMethod);
+                    mContext.report(ISSUE, getCurrentLocation(), message, null /*data*/);
+
+                }
+                mPendingMethod = null;
+            }
+            mLastInsn = opcode;
+        }
+
+        @Override
+        public void visitLineNumber(int line, Label start) {
+            mCurrentLine = line;
+        }
+
+        @Override
+        public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+            mLastInsn = opcode;
+        }
+
+        @Override
+        public void visitIntInsn(int opcode, int operand) {
+            mLastInsn = opcode;
+        }
+
+        @Override
+        public void visitJumpInsn(int opcode, Label label) {
+            mLastInsn = opcode;
+        }
+
+        @Override
+        public void visitTypeInsn(int opcode, String type) {
+            mLastInsn = opcode;
+        }
+
+        @Override
+        public void visitVarInsn(int opcode, int var) {
+            mLastInsn = opcode;
+        }
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/FieldGetterDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/FieldGetterDetectorTest.java
new file mode 100644
index 0000000..e50e6a6
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/FieldGetterDetectorTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class FieldGetterDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new FieldGetterDetector();
+    }
+
+    public void test() throws Exception {
+        assertEquals(
+                "GetterTest.java:48: Warning: Calling getter method getFoo1() on self is slower than field access\n" +
+                "GetterTest.java:49: Warning: Calling getter method getFoo2() on self is slower than field access\n" +
+                "GetterTest.java:53: Warning: Calling getter method isBar1() on self is slower than field access\n" +
+                "GetterTest.java:55: Warning: Calling getter method getFoo1() on self is slower than field access\n" +
+                "GetterTest.java:56: Warning: Calling getter method getFoo2() on self is slower than field access",
+
+                lintProject(
+                    "bytecode/.classpath=>.classpath",
+                    "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+                    "bytecode/GetterTest.java.txt=>src/test/bytecode/GetterTest.java",
+                    "bytecode/GetterTest.class.data=>bin/classes/test/bytecode/GetterTest.class"
+                    ));
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MathDetectorTest.java b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MathDetectorTest.java
new file mode 100644
index 0000000..4c5580d
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/MathDetectorTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.checks;
+
+import com.android.tools.lint.detector.api.Detector;
+
+@SuppressWarnings("javadoc")
+public class MathDetectorTest extends AbstractCheckTest {
+    @Override
+    protected Detector getDetector() {
+        return new MathDetector();
+    }
+
+    public void test() throws Exception {
+        assertEquals(
+                "MathTest.java:12: Warning: Use android.util.FloatMath#sqrt() instead of java.lang.Math#sqrt to avoid argument float to double conversion\n" +
+                "MathTest.java:18: Warning: Use android.util.FloatMath#sqrt() instead of java.lang.Math#sqrt to avoid double to float return value conversion\n" +
+                "MathTest.java:23: Warning: Use android.util.FloatMath#sqrt() instead of java.lang.Math#sqrt to avoid argument float to double conversion",
+
+                lintProject(
+                        "bytecode/.classpath=>.classpath",
+                        "bytecode/AndroidManifest.xml=>AndroidManifest.xml",
+                        "bytecode/MathTest.java.txt=>src/test/bytecode/MathTest.java",
+                        "bytecode/MathTest.class.data=>bin/classes/test/bytecode/MathTest.class"
+                        ));
+    }
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/.classpath b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/.classpath
new file mode 100644
index 0000000..a4763d1
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="gen"/>
+	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+	<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+	<classpathentry kind="output" path="bin/classes"/>
+</classpath>
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/AndroidManifest.xml b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/AndroidManifest.xml
new file mode 100644
index 0000000..2896fc8
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="test.bytecode"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="10" />
+
+    <application
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name" >
+        <activity
+            android:name=".BytecodeTestsActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/GetterTest.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/GetterTest.class.data
new file mode 100644
index 0000000..73a9947
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/GetterTest.class.data
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/GetterTest.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/GetterTest.java.txt
new file mode 100644
index 0000000..25f3421
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/GetterTest.java.txt
@@ -0,0 +1,59 @@
+package test.bytecode;
+
+public class GetterTest {
+	private int mFoo1;
+	private String mFoo2;
+	private int mBar1;
+	private static int sFoo4;
+
+	public int getFoo1() {
+		return mFoo1;
+	}
+
+	public String getFoo2() {
+		return mFoo2;
+	}
+
+	public int isBar1() {
+		return mBar1;
+	}
+
+	// Not "plain" getters:
+
+	public String getFoo3() {
+		// NOT a plain getter
+		if (mFoo2 == null) {
+			mFoo2 = "";
+		}
+		return mFoo2;
+	}
+
+	public int getFoo4() {
+		// NOT a plain getter (using static)
+		return sFoo4;
+	}
+
+	public int getFoo5(int x) {
+		// NOT a plain getter (has extra argument)
+		return sFoo4;
+	}
+
+	public int isBar2(String s) {
+		// NOT a plain getter (has extra argument)
+		return mFoo1;
+	}
+
+	public void test() {
+		getFoo1();
+		getFoo2();
+		getFoo3();
+		getFoo4();
+		getFoo5(42);
+		isBar1();
+		isBar2("foo");
+		this.getFoo1();
+		this.getFoo2();
+		this.getFoo3();
+		this.getFoo4();
+	}
+}
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/MathTest.class.data b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/MathTest.class.data
new file mode 100644
index 0000000..9b419fa
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/MathTest.class.data
Binary files differ
diff --git a/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/MathTest.java.txt b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/MathTest.java.txt
new file mode 100644
index 0000000..684e654
--- /dev/null
+++ b/lint/libs/lint_checks/tests/src/com/android/tools/lint/checks/data/bytecode/MathTest.java.txt
@@ -0,0 +1,30 @@
+package test.bytecode;
+
+//Test data for the MathDetector
+public class MathTest {
+	public float floatResult;
+	public double doubleResult;
+
+	public void floatToFloatTest(float x) {
+		// This forces a cast of the argument
+		// from a float to a double, as well
+		// as a cast of the return code back to a float
+		floatResult = (float) Math.sqrt(x);
+	}
+
+	public void doubleToFloat(double x) {
+		// This forces a cast of the return value
+		// to a float
+		floatResult = (float) Math.sqrt(x);
+	}
+
+	public void floatToDouble(float x) {
+		// This causes a cast of the float argument to a double
+		doubleResult = Math.sqrt(x);
+	}
+
+	public void doubleToDouble(double x) {
+		// No casting
+		doubleResult = Math.sqrt(x);
+	}
+}