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