using error prone library to detect SDK public API used by mainline
module.

Test: atest JavaApiUsedByMainlineModuleTest.
Change-Id: I10716609e1c2bbb89a0054934365c1d07601d12e
diff --git a/Android.bp b/Android.bp
new file mode 100644
index 0000000..afd3fa0
--- /dev/null
+++ b/Android.bp
@@ -0,0 +1,59 @@
+// Copyright (C) 2019 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.
+
+java_plugin {
+    name: "java_api_finder",
+
+    static_libs: [
+        "java_api_used_by_mainline_module",
+    ],
+}
+
+java_library_host {
+    name: "java_api_used_by_mainline_module",
+
+    srcs: ["src/main/**/*.java"],
+
+    static_libs: [
+        "//external/error_prone:error_prone_core",
+        "//external/dagger2:dagger2-auto-service",
+    ],
+
+    plugins: [
+        "//external/dagger2:dagger2-auto-service",
+    ],
+
+    javacflags: ["-verbose"],
+}
+
+java_test_host {
+    name: "JavaApiUsedByMainlineModuleTest",
+    srcs: ["src/test/**/JavaApiUsedByMainlineModuleTest.java"],
+    java_resource_dirs: ["src/test/res"],
+    java_resources: [":java_api_used_by_mainline_module_testdata"],
+    static_libs: [
+        "java_api_used_by_mainline_module",
+        "error_prone_test_helpers",
+        "hamcrest-library",
+        "hamcrest",
+        "platform-test-annotations",
+        "junit",
+    ],
+}
+
+filegroup {
+    name: "java_api_used_by_mainline_module_testdata",
+    path: "src/test/res",
+    srcs: ["src/test/res/**/*.java"],
+}
diff --git a/src/main/com/android/apifinder/JavaApiUsedByMainlineModule.java b/src/main/com/android/apifinder/JavaApiUsedByMainlineModule.java
new file mode 100644
index 0000000..8e04688
--- /dev/null
+++ b/src/main/com/android/apifinder/JavaApiUsedByMainlineModule.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2018 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.apifinder;
+
+import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
+
+import com.google.auto.service.AutoService;
+import com.google.errorprone.BugPattern;
+import com.google.errorprone.VisitorState;
+import com.google.errorprone.bugpatterns.BugChecker;
+import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
+import com.google.errorprone.bugpatterns.BugChecker.NewClassTreeMatcher;
+import com.google.errorprone.matchers.Description;
+import com.google.errorprone.util.ASTHelpers;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.NewClassTree;
+import com.sun.tools.javac.code.Symbol;
+import com.sun.tools.javac.code.Symbol.MethodSymbol;
+import com.sun.tools.javac.code.Symbol.VarSymbol;
+import java.util.ArrayList;
+import java.util.List;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.Modifier;
+
+/** Bug checker to detect method or field used by a mainline module */
+@AutoService(BugChecker.class)
+@BugPattern(
+    name = "JavaApiUsedByMainlineModule",
+    summary = "Any public method used by a mainline module.",
+    severity = WARNING)
+public final class JavaApiUsedByMainlineModule extends BugChecker
+    implements MethodInvocationTreeMatcher, NewClassTreeMatcher {
+
+    /*
+     * Checks if a method or class is private.
+     * A method is considered as private method when any of the following condition met.
+     *    1. Method is defined as private.
+     *    2. Method's class is defined as private.
+     *    3. Method's ancestor classes is defined as private.
+     */
+    private boolean isPrivate(Symbol sym) {
+        Symbol tmpSym = sym;
+        while (tmpSym != null) {
+            if (!tmpSym.getModifiers().contains(Modifier.PUBLIC)) {
+                return true;
+            }
+            tmpSym = ASTHelpers.enclosingClass(tmpSym);
+        }
+        return false;
+    }
+
+    /*
+     * Constructs parameters. Only return parameter type.
+     * For example
+     *     (int, boolean, java.lang.String)
+     */
+    private String constructParameters(Symbol sym) {
+        List<VarSymbol> paramsList = ((MethodSymbol) sym).getParameters();
+        List<StringBuilder> stringParamsList = new ArrayList();
+        for (VarSymbol paramSymbol : paramsList) {
+            StringBuilder tmpParam = new StringBuilder(paramSymbol.asType().toString());
+
+            // Removes "<*>" in parameter type.
+            if (tmpParam.indexOf("<") != -1) {
+                tmpParam = tmpParam.replace(
+                    tmpParam.indexOf("<"), tmpParam.lastIndexOf(">") + 1, "");
+            }
+
+            stringParamsList.add(tmpParam);
+        }
+        return "(" + String.join(", ", stringParamsList) + ")";
+    }
+
+    @Override
+    public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
+        Symbol sym = ASTHelpers.getSymbol(tree);
+
+        // Exclude private function.
+        if (isPrivate(sym)) {
+            return Description.NO_MATCH;
+        }
+
+        // Constructs method name formatted as superClassName.className.methodName,
+        // using supperClassName.className.className for constructor
+        String methodName = sym.name.toString();
+        List<String> nameList = new ArrayList();
+        if (sym.getKind() == ElementKind.CONSTRUCTOR) {
+            Symbol classSymbol = ASTHelpers.enclosingClass(sym);
+            while (classSymbol != null) {
+                nameList.add(0, classSymbol.name.toString());
+                classSymbol = ASTHelpers.enclosingClass(classSymbol);
+            }
+            methodName = String.join(".", nameList);
+        }
+
+        String params = constructParameters(sym);
+
+        return buildDescription(tree)
+            .setMessage(
+                String.format("%s.%s%s", ASTHelpers.enclosingClass(sym), methodName, params))
+            .build();
+    }
+
+    @Override
+    public Description matchNewClass(NewClassTree tree, VisitorState state) {
+        Symbol sym = ASTHelpers.getSymbol(tree);
+
+        // Excludes private class.
+        if (isPrivate(sym)) {
+            return Description.NO_MATCH;
+        }
+
+        String params = constructParameters(sym);
+
+        // Constructs constructor name.
+        Symbol tmpSymbol = ASTHelpers.enclosingClass(sym);
+        List<String> nameList = new ArrayList();
+        while (tmpSymbol != null) {
+            nameList.add(0, tmpSymbol.name.toString());
+            tmpSymbol = ASTHelpers.enclosingClass(tmpSymbol);
+        }
+        String constructorName = String.join(".", nameList);
+
+        return buildDescription(tree)
+            .setMessage(
+                String.format(
+                    "%s.%s%s", ASTHelpers.enclosingClass(sym), constructorName, params))
+            .build();
+    }
+}
+
diff --git a/src/test/com/android/apifinder/JavaApiUsedByMainlineModuleTest.java b/src/test/com/android/apifinder/JavaApiUsedByMainlineModuleTest.java
new file mode 100644
index 0000000..9d79706
--- /dev/null
+++ b/src/test/com/android/apifinder/JavaApiUsedByMainlineModuleTest.java
@@ -0,0 +1,32 @@
+package com.android.apifinder;
+
+import com.android.apifinder.JavaApiUsedByMainlineModule;
+import com.google.errorprone.CompilationTestHelper;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link JavaApiUsedByMainlineModule}. */
+@RunWith(JUnit4.class)
+public final class JavaApiUsedByMainlineModuleTest {
+
+  private CompilationTestHelper compilationHelper;
+
+  @Before
+  public void setUp() {
+    compilationHelper = CompilationTestHelper.newInstance(
+        JavaApiUsedByMainlineModule.class, getClass());
+  }
+
+  /*
+   * The error prone testing library will run the plugin on the resource file.
+   * The error prone testing library will compare the comment of each method in the
+   * resource file to determine if the return value is expected.
+   */
+  @Test
+  public void positiveFindPublicMethod() {
+    compilationHelper
+        .addSourceFile("JavaApiUsedByMainlineModuleCases.java").doTest();
+  }
+}
diff --git a/src/test/res/com/android/apifinder/JavaApiUsedByMainlineModuleCases.java b/src/test/res/com/android/apifinder/JavaApiUsedByMainlineModuleCases.java
new file mode 100644
index 0000000..603871f
--- /dev/null
+++ b/src/test/res/com/android/apifinder/JavaApiUsedByMainlineModuleCases.java
@@ -0,0 +1,27 @@
+package com.android.apifinder;
+
+public class JavaApiUsedByMainlineModuleCases {
+
+  public class PublicSubClass {
+    public void publicMethod() {}
+
+    private void privateMethod() {}
+  }
+
+  private class PrivateSubClass {
+    public void publicMethod() {}
+  }
+
+  public void testMethod() {
+    // BUG: Diagnostic contains: JavaApiUsedByMainlineModuleCases.PublicSubClass
+    // .JavaApiUsedByMainlineModuleCases.PublicSubClass()
+    PublicSubClass publicTestClass = new PublicSubClass();
+
+    // BUG: Diagnostic contains: JavaApiUsedByMainlineModuleCases.PublicSubClass.publicMethod()
+    publicTestClass.publicMethod();
+
+    /** Should not be reported since PrivateSubClass is a private class. */
+    PrivateSubClass privateTestClass = new PrivateSubClass();
+    privateTestClass.publicMethod();
+  }
+}