using error prone library to detect SDK public API used by mainline
module.
Test: atest JavaApiUsedByMainlineModuleTest.
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();
+ }
+}