Merge "Print a warning if we cant' generate correct adapter" into oc-mr1-support-27.0-dev
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/ErrorMessages.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/ErrorMessages.kt
index 10180a6..2f300e7 100644
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/ErrorMessages.kt
+++ b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/ErrorMessages.kt
@@ -16,6 +16,9 @@
 
 package android.arch.lifecycle
 
+import android.arch.lifecycle.model.EventMethod
+import javax.lang.model.element.TypeElement
+
 object ErrorMessages {
     const val TOO_MANY_ARGS = "callback method cannot have more than 2 parameters"
     const val TOO_MANY_ARGS_NOT_ON_ANY = "only callback annotated with ON_ANY " +
@@ -33,4 +36,10 @@
     const val INVALID_ENCLOSING_ELEMENT =
             "Parent of OnLifecycleEvent should be a class or interface"
     const val INVALID_ANNOTATED_ELEMENT = "OnLifecycleEvent can only be added to methods"
+
+    fun failedToGenerateAdapter(type: TypeElement, failureReason: EventMethod) =
+            """
+             Failed to generate an Adapter for $type, because it needs to be able to access to
+             package private method ${failureReason.method.name()} from ${failureReason.type}
+            """.trim()
 }
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/input_collector.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/input_collector.kt
index 43368936..e31b0e9 100644
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/input_collector.kt
+++ b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/input_collector.kt
@@ -39,17 +39,22 @@
                           roundEnv: RoundEnvironment): Map<TypeElement, LifecycleObserverInfo> {
     val validator = Validator(processingEnv)
     val worldCollector = ObserversCollector(processingEnv)
-    roundEnv.getElementsAnnotatedWith(OnLifecycleEvent::class.java).forEach { elem ->
+    val roots = roundEnv.getElementsAnnotatedWith(OnLifecycleEvent::class.java).map { elem ->
         if (elem.kind != ElementKind.METHOD) {
             validator.printErrorMessage(ErrorMessages.INVALID_ANNOTATED_ELEMENT, elem)
+            null
         } else {
             val enclosingElement = elem.enclosingElement
             if (validator.validateClass(enclosingElement)) {
-                worldCollector.collect(MoreElements.asType(enclosingElement))
+                MoreElements.asType(enclosingElement)
+            } else {
+                null
             }
         }
-    }
-    return worldCollector.observers
+    }.filterNotNull().toSet()
+    roots.forEach { worldCollector.collect(it) }
+    // filter out everything that arrived from jars
+    return worldCollector.observers.filterKeys { k -> k in roots }
 }
 
 class ObserversCollector(processingEnv: ProcessingEnvironment) {
diff --git a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/transformation.kt b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/transformation.kt
index 16c3d81..efe1ed7 100644
--- a/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/transformation.kt
+++ b/lifecycle/compiler/src/main/kotlin/android/arch/lifecycle/transformation.kt
@@ -78,18 +78,34 @@
     return flattened.values.toList()
 }
 
+private fun needsSyntheticAccess(type: TypeElement, eventMethod: EventMethod): Boolean {
+    val executable = eventMethod.method
+    return type.getPackageQName() != eventMethod.packageName()
+            && (executable.isPackagePrivate() || executable.isProtected())
+}
+
 fun transformToOutput(processingEnv: ProcessingEnvironment,
                       world: Map<TypeElement, LifecycleObserverInfo>): List<AdapterClass> {
     val flatObservers = flattenObservers(processingEnv, world)
     val syntheticMethods = HashMultimap.create<TypeElement, EventMethodCall>()
-    flatObservers.filterNot(LifecycleObserverInfo::hasAdapter)
     val adapterCalls = flatObservers
-            .filterNot(LifecycleObserverInfo::hasAdapter)
+            // filter out everything that arrived from jars
+            .filter { (type) -> type in world }
+            // filter out if it needs synthetic access and we can't generate adapter for it
+            .filterNot { (type, methods) ->
+                methods.any { eventMethod ->
+                    val cantGenerate = needsSyntheticAccess(type, eventMethod)
+                            && (eventMethod.type !in world)
+                    if (cantGenerate) {
+                        processingEnv.messager.printMessage(Diagnostic.Kind.WARNING,
+                                ErrorMessages.failedToGenerateAdapter(type, eventMethod))
+                    }
+                    cantGenerate
+                }
+            }
             .map { (type, methods) ->
                 val calls = methods.map { eventMethod ->
-                    val executable = eventMethod.method
-                    if (type.getPackageQName() != eventMethod.packageName()
-                            && (executable.isPackagePrivate() || executable.isProtected())) {
+                    if (needsSyntheticAccess(type, eventMethod)) {
                         EventMethodCall(eventMethod, eventMethod.type)
                     } else {
                         EventMethodCall(eventMethod)
@@ -101,9 +117,10 @@
                 type to calls
             }.toMap()
 
-    return adapterCalls.map { (type, calls) ->
-        val methods = syntheticMethods.get(type) ?: setOf()
-        val synthetic = methods.map { eventMethod -> eventMethod!!.method.method }.toSet()
-        AdapterClass(type, calls, synthetic)
-    }
+    return adapterCalls
+            .map { (type, calls) ->
+                val methods = syntheticMethods.get(type) ?: emptySet()
+                val synthetic = methods.map { eventMethod -> eventMethod!!.method.method }.toSet()
+                AdapterClass(type, calls, synthetic)
+            }
 }
diff --git a/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/ValidCasesTest.kt b/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/ValidCasesTest.kt
index a4fda54..83d69e0 100644
--- a/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/ValidCasesTest.kt
+++ b/lifecycle/compiler/src/tests/kotlin/android/arch/lifecycle/ValidCasesTest.kt
@@ -23,9 +23,10 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import javax.tools.StandardLocation
 import java.io.File
+import java.lang.Exception
 import java.net.URLClassLoader
+import javax.tools.StandardLocation
 
 @RunWith(JUnit4::class)
 class ValidCasesTest {
@@ -72,7 +73,7 @@
     }
 
     @Test
-    fun testInterface1(){
+    fun testInterface1() {
         processClass("foo.InterfaceOk1").compilesWithoutError()
     }
 
@@ -118,13 +119,37 @@
 
     @Test
     fun testJar() {
-        val jarUrl = File("src/tests/test-data/lib/test-library.jar").toURI().toURL()
-        val classLoader = URLClassLoader(arrayOf(jarUrl), this.javaClass.classLoader)
         JavaSourcesSubject.assertThat(load("foo.DerivedFromJar", ""))
-                .withClasspathFrom(classLoader)
+                .withClasspathFrom(libraryClassLoader())
                 .processedWith(LifecycleProcessor())
                 .compilesWithoutError().and()
-                .generatesSources(load("foo.DerivedFromJar_LifecycleAdapter", "expected")
-        )
+                .generatesSources(load("foo.DerivedFromJar_LifecycleAdapter", "expected"))
+    }
+
+    @Test
+    fun testExtendFromJarFailToGenerateAdapter() {
+        val compileTester = JavaSourcesSubject.assertThat(load("foo.DerivedFromJar1", ""))
+                .withClasspathFrom(libraryClassLoader())
+                .processedWith(LifecycleProcessor())
+                .compilesWithoutError()
+        compileTester.withWarningContaining("Failed to generate an Adapter for")
+        doesntGenerateClass(compileTester, "test.library", "ObserverNoAdapter_LifecycleAdapter")
+        doesntGenerateClass(compileTester, "foo", "DerivedFromJar1_LifecycleAdapter")
+    }
+
+    // compile-testing has fancy, but not always convenient API
+    private fun doesntGenerateClass(compile: CompileTester.SuccessfulCompilationClause,
+                                    packageName: String, className: String) {
+        try {
+            compile.and().generatesFileNamed(StandardLocation.CLASS_OUTPUT,
+                    packageName, "$className.class")
+            throw Exception("$packageName.$className shouldn't be generated")
+        } catch (e: AssertionError) {
+        }
+    }
+
+    private fun libraryClassLoader(): URLClassLoader {
+        val jarUrl = File("src/tests/test-data/lib/test-library.jar").toURI().toURL()
+        return URLClassLoader(arrayOf(jarUrl), this.javaClass.classLoader)
     }
 }
diff --git a/lifecycle/compiler/src/tests/test-data/DerivedFromJar1.java b/lifecycle/compiler/src/tests/test-data/DerivedFromJar1.java
new file mode 100644
index 0000000..efcfaff
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/DerivedFromJar1.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 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 foo;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class DerivedFromJar1 extends test.library.PPObserverNoAdapter {
+    @OnLifecycleEvent(ON_START)
+    public void doAnother() {
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/DerivedFromJar1_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/DerivedFromJar1_LifecycleAdapter.java
new file mode 100644
index 0000000..2ff2db2
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/DerivedFromJar1_LifecycleAdapter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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 foo;
+
+import android.arch.lifecycle.GeneratedAdapter;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.MethodCallsLogger;
+import java.lang.Override;
+import javax.annotation.Generated;
+
+@Generated("android.arch.lifecycle.LifecycleProcessor")
+public class DerivedFromJar1_LifecycleAdapter implements GeneratedAdapter {
+    final DerivedFromJar1 mReceiver;
+
+    DerivedFromJar_LifecycleAdapter(DerivedFromJar1 receiver) {
+        this.mReceiver = receiver;
+    }
+
+    @Override
+    public void callMethods(LifecycleOwner owner, Lifecycle.Event event, boolean onAny,
+            MethodCallsLogger logger) {
+        boolean hasLogger = logger != null;
+        if (onAny) {
+            return;
+        }
+        if (event == Lifecycle.Event.ON_STOP) {
+            if (!hasLogger || logger.approveCall("doOnStop", 1)) {
+                mReceiver.doOnStop();
+            }
+            return;
+        }
+        if (event == Lifecycle.Event.ON_START) {
+            if (!hasLogger || logger.approveCall("doAnother", 1)) {
+                mReceiver.doAnother();
+            }
+            return;
+        }
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/expected/ObserverNoAdapter_LifecycleAdapter.java b/lifecycle/compiler/src/tests/test-data/expected/ObserverNoAdapter_LifecycleAdapter.java
new file mode 100644
index 0000000..c2dc1b6
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/expected/ObserverNoAdapter_LifecycleAdapter.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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 test.library;
+
+import android.arch.lifecycle.GeneratedAdapter;
+import android.arch.lifecycle.Lifecycle;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.MethodCallsLogger;
+import java.lang.Override;
+import javax.annotation.Generated;
+
+@Generated("android.arch.lifecycle.LifecycleProcessor")
+public class ObserverNoAdapter_LifecycleAdapter implements GeneratedAdapter {
+    final ObserverNoAdapter mReceiver;
+
+    ObserverNoAdapter_LifecycleAdapter(ObserverNoAdapter receiver) {
+        this.mReceiver = receiver;
+    }
+
+    @Override
+    public void callMethods(LifecycleOwner owner, Lifecycle.Event event, boolean onAny,
+            MethodCallsLogger logger) {
+        boolean hasLogger = logger != null;
+        if (onAny) {
+            return;
+        }
+        if (event == Lifecycle.Event.ON_STOP) {
+            if (!hasLogger || logger.approveCall("doOnStop", 1)) {
+                mReceiver.doOnStop();
+            }
+            return;
+        }
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/lib/src/test/library/ObserverNoAdapter.java b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/ObserverNoAdapter.java
new file mode 100644
index 0000000..ea000f7
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/ObserverNoAdapter.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 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 test.library;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class ObserverNoAdapter implements LifecycleObserver {
+    @OnLifecycleEvent(ON_STOP)
+    public void doOnStop() {
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverNoAdapter.java b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverNoAdapter.java
new file mode 100644
index 0000000..06e10b5
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverNoAdapter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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 test.library;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class PPObserverNoAdapter implements LifecycleObserver {
+    @OnLifecycleEvent(ON_START)
+    protected void doOnStart() {
+    }
+
+    @OnLifecycleEvent(ON_STOP)
+    protected void doOnStop() {
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverWithAdapter.java b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverWithAdapter.java
new file mode 100644
index 0000000..25a9b5d
--- /dev/null
+++ b/lifecycle/compiler/src/tests/test-data/lib/src/test/library/PPObserverWithAdapter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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 test.library;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import android.arch.lifecycle.Lifecycle.Event;
+import android.arch.lifecycle.LifecycleObserver;
+import android.arch.lifecycle.LifecycleOwner;
+import android.arch.lifecycle.OnLifecycleEvent;
+
+public class PPObserverWithAdapter implements LifecycleObserver {
+    @OnLifecycleEvent(ON_START)
+    protected void doOnStart() {
+    }
+
+    @OnLifecycleEvent(ON_STOP)
+    protected void doOnStop() {
+    }
+}
diff --git a/lifecycle/compiler/src/tests/test-data/lib/test-library.jar b/lifecycle/compiler/src/tests/test-data/lib/test-library.jar
index f1156ad..71af37f 100644
--- a/lifecycle/compiler/src/tests/test-data/lib/test-library.jar
+++ b/lifecycle/compiler/src/tests/test-data/lib/test-library.jar
Binary files differ