Merge "Fix invalidate range check for lineIndex" into androidx-master-dev
diff --git a/.github/workflows/presubmit.yml b/.github/workflows/presubmit.yml
index 3feb86f..88b05ec 100644
--- a/.github/workflows/presubmit.yml
+++ b/.github/workflows/presubmit.yml
@@ -1,11 +1,8 @@
name: AndroidX Presubmits
on:
pull_request:
- branches:
- - master
- - androidx-master-dev
- repository_dispatch:
- if: always()
+ types: [assigned, opened, synchronize, reopened, labeled]
+ workflow_dispatch:
jobs:
# This workflow contains a single job called "build"
diff --git a/biometric/biometric/src/main/java/androidx/biometric/CancellationSignalProvider.java b/biometric/biometric/src/main/java/androidx/biometric/CancellationSignalProvider.java
index 7cba5a4..e2218f5 100644
--- a/biometric/biometric/src/main/java/androidx/biometric/CancellationSignalProvider.java
+++ b/biometric/biometric/src/main/java/androidx/biometric/CancellationSignalProvider.java
@@ -17,10 +17,12 @@
package androidx.biometric;
import android.os.Build;
+import android.os.CancellationSignal;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
/**
* Creates and caches cancellation signal objects that are compatible with
@@ -30,6 +32,34 @@
@SuppressWarnings("deprecation")
class CancellationSignalProvider {
/**
+ * An injector for various class dependencies. Used for testing.
+ */
+ @VisibleForTesting
+ interface Injector {
+ /**
+ * TODO
+ *
+ * @return TODO
+ */
+ @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
+ @NonNull
+ android.os.CancellationSignal getBiometricCancellationSignal();
+
+ /**
+ * TODO
+ *
+ * @return TODO
+ */
+ @NonNull
+ androidx.core.os.CancellationSignal getFingerprintCancellationSignal();
+ }
+
+ /**
+ * The injector for class dependencies used by this provider.
+ */
+ private final Injector mInjector;
+
+ /**
* A cancellation signal object that is compatible with
* {@link android.hardware.biometrics.BiometricPrompt}.
*/
@@ -42,6 +72,36 @@
@Nullable private androidx.core.os.CancellationSignal mFingerprintCancellationSignal;
/**
+ * Creates a new cancellation signal provider instance.
+ */
+ CancellationSignalProvider() {
+ mInjector = new Injector() {
+ @Override
+ @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
+ @NonNull
+ public CancellationSignal getBiometricCancellationSignal() {
+ return Api16Impl.create();
+ }
+
+ @Override
+ @NonNull
+ public androidx.core.os.CancellationSignal getFingerprintCancellationSignal() {
+ return new androidx.core.os.CancellationSignal();
+ }
+ };
+ }
+
+ /**
+ * Creates a new cancellation signal provider instance with the given injector.
+ *
+ * @param injector An injector for class and method dependencies.
+ */
+ @VisibleForTesting
+ CancellationSignalProvider(Injector injector) {
+ mInjector = injector;
+ }
+
+ /**
* Provides a cancellation signal object that is compatible with
* {@link android.hardware.biometrics.BiometricPrompt}.
*
@@ -55,7 +115,7 @@
@NonNull
android.os.CancellationSignal getBiometricCancellationSignal() {
if (mBiometricCancellationSignal == null) {
- mBiometricCancellationSignal = Api16Impl.create();
+ mBiometricCancellationSignal = mInjector.getBiometricCancellationSignal();
}
return mBiometricCancellationSignal;
}
@@ -73,7 +133,7 @@
@NonNull
androidx.core.os.CancellationSignal getFingerprintCancellationSignal() {
if (mFingerprintCancellationSignal == null) {
- mFingerprintCancellationSignal = new androidx.core.os.CancellationSignal();
+ mFingerprintCancellationSignal = mInjector.getFingerprintCancellationSignal();
}
return mFingerprintCancellationSignal;
}
diff --git a/biometric/biometric/src/test/java/androidx/biometric/AuthenticationCallbackProviderTest.java b/biometric/biometric/src/test/java/androidx/biometric/AuthenticationCallbackProviderTest.java
new file mode 100644
index 0000000..d10f8bf
--- /dev/null
+++ b/biometric/biometric/src/test/java/androidx/biometric/AuthenticationCallbackProviderTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2020 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 androidx.biometric;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.os.Build;
+
+import androidx.test.filters.LargeTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+import javax.crypto.Cipher;
+
+@LargeTest
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class AuthenticationCallbackProviderTest {
+ @Mock private AuthenticationCallbackProvider.Listener mListener;
+ @Mock private Cipher mCipher;
+
+ @Captor private ArgumentCaptor<BiometricPrompt.AuthenticationResult> mResultCaptor;
+
+ private AuthenticationCallbackProvider mAuthenticationCallbackProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mAuthenticationCallbackProvider = new AuthenticationCallbackProvider(mListener);
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.P)
+ public void testBiometricCallback_IsCached() {
+ final android.hardware.biometrics.BiometricPrompt.AuthenticationCallback callback =
+ mAuthenticationCallbackProvider.getBiometricCallback();
+ assertThat(mAuthenticationCallbackProvider.getBiometricCallback()).isEqualTo(callback);
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.P)
+ public void testBiometricCallback_HandlesSuccess() {
+ final android.hardware.biometrics.BiometricPrompt.AuthenticationResult result =
+ mock(android.hardware.biometrics.BiometricPrompt.AuthenticationResult.class);
+ final android.hardware.biometrics.BiometricPrompt.CryptoObject crypto =
+ new android.hardware.biometrics.BiometricPrompt.CryptoObject(mCipher);
+ when(result.getCryptoObject()).thenReturn(crypto);
+
+ mAuthenticationCallbackProvider.getBiometricCallback().onAuthenticationSucceeded(result);
+
+ verify(mListener).onSuccess(mResultCaptor.capture());
+ verifyNoMoreInteractions(mListener);
+ assertThat(mResultCaptor.getValue().getCryptoObject()).isNotNull();
+ assertThat(mResultCaptor.getValue().getCryptoObject().getCipher()).isEqualTo(mCipher);
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.P)
+ public void testBiometricCallback_HandlesError() {
+ final int errorCode = BiometricPrompt.ERROR_HW_NOT_PRESENT;
+ final String errorMessage = "Lorem ipsum";
+
+ mAuthenticationCallbackProvider.getBiometricCallback()
+ .onAuthenticationError(errorCode, errorMessage);
+
+ verify(mListener).onError(errorCode, errorMessage);
+ verifyNoMoreInteractions(mListener);
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.P)
+ public void testBiometricCallback_IgnoresHelp() {
+ final int helpCode = BiometricPrompt.ERROR_UNABLE_TO_PROCESS;
+ final String helpMessage = "Dolor sit amet";
+
+ mAuthenticationCallbackProvider.getBiometricCallback()
+ .onAuthenticationHelp(helpCode, helpMessage);
+
+ verifyZeroInteractions(mListener);
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.P)
+ public void testBiometricCallback_HandlesFailure() {
+ mAuthenticationCallbackProvider.getBiometricCallback().onAuthenticationFailed();
+
+ verify(mListener).onFailure();
+ verifyNoMoreInteractions(mListener);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void testFingerprintCallback_IsCached() {
+ final androidx.core.hardware.fingerprint.FingerprintManagerCompat.AuthenticationCallback
+ callback = mAuthenticationCallbackProvider.getFingerprintCallback();
+ assertThat(mAuthenticationCallbackProvider.getFingerprintCallback()).isEqualTo(callback);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void testFingerprintCallback_HandlesSuccess() {
+ final androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject crypto =
+ new androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject(
+ mCipher);
+ final androidx.core.hardware.fingerprint.FingerprintManagerCompat.AuthenticationResult
+ result = new androidx.core.hardware.fingerprint.FingerprintManagerCompat
+ .AuthenticationResult(crypto);
+
+ mAuthenticationCallbackProvider.getFingerprintCallback().onAuthenticationSucceeded(result);
+
+ verify(mListener).onSuccess(mResultCaptor.capture());
+ verifyNoMoreInteractions(mListener);
+ assertThat(mResultCaptor.getValue().getCryptoObject()).isNotNull();
+ assertThat(mResultCaptor.getValue().getCryptoObject().getCipher()).isEqualTo(mCipher);
+ }
+
+ @Test
+ public void testFingerprintCallback_HandlesError() {
+ final int errorCode = BiometricPrompt.ERROR_CANCELED;
+ final String errorMessage = "Foo bar";
+
+ mAuthenticationCallbackProvider.getFingerprintCallback()
+ .onAuthenticationError(errorCode, errorMessage);
+
+ verify(mListener).onError(errorCode, errorMessage);
+ verifyNoMoreInteractions(mListener);
+ }
+
+ @Test
+ public void testFingerprintCallback_HandlesHelp() {
+ final int helpCode = BiometricPrompt.ERROR_TIMEOUT;
+ final String helpMessage = "Baz qux";
+
+ mAuthenticationCallbackProvider.getFingerprintCallback()
+ .onAuthenticationHelp(helpCode, helpMessage);
+
+ verify(mListener).onHelp(helpMessage);
+ verifyNoMoreInteractions(mListener);
+ }
+
+ @Test
+ public void testFingerprintCallback_HandlesFailure() {
+ mAuthenticationCallbackProvider.getFingerprintCallback().onAuthenticationFailed();
+
+ verify(mListener).onFailure();
+ verifyNoMoreInteractions(mListener);
+ }
+}
diff --git a/biometric/biometric/src/test/java/androidx/biometric/CancellationSignalProviderTest.java b/biometric/biometric/src/test/java/androidx/biometric/CancellationSignalProviderTest.java
new file mode 100644
index 0000000..c81763a
--- /dev/null
+++ b/biometric/biometric/src/test/java/androidx/biometric/CancellationSignalProviderTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2020 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 androidx.biometric;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.LargeTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+@LargeTest
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class CancellationSignalProviderTest {
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN)
+ public void testBiometricCancellationSignal_IsCached() {
+ final CancellationSignalProvider provider = new CancellationSignalProvider();
+ final android.os.CancellationSignal cancellationSignal =
+ provider.getBiometricCancellationSignal();
+ assertThat(provider.getBiometricCancellationSignal()).isEqualTo(cancellationSignal);
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN)
+ public void testBiometricCancellationSignal_ReceivesCancel() {
+ final android.os.CancellationSignal cancellationSignal =
+ mock(android.os.CancellationSignal.class);
+ final CancellationSignalProvider.Injector injector =
+ new CancellationSignalProvider.Injector() {
+ @Override
+ @NonNull
+ public android.os.CancellationSignal getBiometricCancellationSignal() {
+ return cancellationSignal;
+ }
+
+ @Override
+ @NonNull
+ public androidx.core.os.CancellationSignal getFingerprintCancellationSignal() {
+ return mock(androidx.core.os.CancellationSignal.class);
+ }
+ };
+ final CancellationSignalProvider provider = new CancellationSignalProvider(injector);
+
+ assertThat(provider.getBiometricCancellationSignal()).isEqualTo(cancellationSignal);
+
+ provider.cancel();
+
+ verify(cancellationSignal).cancel();
+ }
+
+ @Test
+ public void testFingerprintCancellationSignal_IsCached() {
+ final CancellationSignalProvider provider = new CancellationSignalProvider();
+ final androidx.core.os.CancellationSignal cancellationSignal =
+ provider.getFingerprintCancellationSignal();
+ assertThat(provider.getFingerprintCancellationSignal()).isEqualTo(cancellationSignal);
+ }
+
+ @Test
+ public void testFingerprintCancellationSignal_ReceivesCancel() {
+ final androidx.core.os.CancellationSignal cancellationSignal =
+ mock(androidx.core.os.CancellationSignal.class);
+ final CancellationSignalProvider.Injector injector =
+ new CancellationSignalProvider.Injector() {
+ @Override
+ @NonNull
+ public android.os.CancellationSignal getBiometricCancellationSignal() {
+ return mock(android.os.CancellationSignal.class);
+ }
+
+ @Override
+ @NonNull
+ public androidx.core.os.CancellationSignal getFingerprintCancellationSignal() {
+ return cancellationSignal;
+ }
+ };
+ final CancellationSignalProvider provider = new CancellationSignalProvider(injector);
+
+ assertThat(provider.getFingerprintCancellationSignal()).isEqualTo(cancellationSignal);
+
+ provider.cancel();
+
+ verify(cancellationSignal).cancel();
+ }
+
+ @Test
+ @Config(minSdk = Build.VERSION_CODES.JELLY_BEAN)
+ public void testBothCancellationSignals_ReceiveCancel() {
+ final android.os.CancellationSignal biometricCancellationSignal =
+ mock(android.os.CancellationSignal.class);
+ final androidx.core.os.CancellationSignal fingerprintCancellationSignal =
+ mock(androidx.core.os.CancellationSignal.class);
+ final CancellationSignalProvider.Injector injector =
+ new CancellationSignalProvider.Injector() {
+ @Override
+ @NonNull
+ public android.os.CancellationSignal getBiometricCancellationSignal() {
+ return biometricCancellationSignal;
+ }
+
+ @Override
+ @NonNull
+ public androidx.core.os.CancellationSignal getFingerprintCancellationSignal() {
+ return fingerprintCancellationSignal;
+ }
+ };
+ final CancellationSignalProvider provider = new CancellationSignalProvider(injector);
+
+ assertThat(provider.getBiometricCancellationSignal())
+ .isEqualTo(biometricCancellationSignal);
+ assertThat(provider.getFingerprintCancellationSignal())
+ .isEqualTo(fingerprintCancellationSignal);
+
+ provider.cancel();
+
+ verify(biometricCancellationSignal).cancel();
+ verify(fingerprintCancellationSignal).cancel();
+ }
+}
diff --git a/buildSrc/build_dependencies.gradle b/buildSrc/build_dependencies.gradle
index db08734..3d430c4 100644
--- a/buildSrc/build_dependencies.gradle
+++ b/buildSrc/build_dependencies.gradle
@@ -26,7 +26,7 @@
build_versions.lint = '27.1.0-alpha08'
} else {
build_versions.kotlin = "1.3.71"
- build_versions.kotlin_coroutines = "1.3.4"
+ build_versions.kotlin_coroutines = "1.3.7"
build_versions.agp = '4.1.0-alpha08'
// NOTE: When updating the lint version we also need to update the `api` version supported
// by `IssueRegistry`'s.' For e.g. aosp/1331903
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlaygroundRootPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlaygroundRootPlugin.kt
index 4efd240..338a4ff 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlaygroundRootPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlaygroundRootPlugin.kt
@@ -196,6 +196,6 @@
}
companion object {
- private const val SNAPSHOT_MARKER = "REPLACE_WITH_SNAPSHOT"
+ const val SNAPSHOT_MARKER = "REPLACE_WITH_SNAPSHOT"
}
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
index d8b4067..b95d4d5 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
@@ -187,10 +187,23 @@
}
private fun configureWithAppPlugin(project: Project, extension: AndroidXExtension) {
- project.extensions.getByType<AppExtension>().apply {
+ val appExtension = project.extensions.getByType<AppExtension>().apply {
configureAndroidCommonOptions(project, extension)
configureAndroidApplicationOptions(project)
}
+
+ // TODO: Replace this with a per-variant packagingOption for androidTest specifically once
+ // b/69953968 is resolved.
+ // Workaround for b/161465530 in AGP that fails to strip these <module>.kotlin_module files,
+ // which causes mergeDebugAndroidTestJavaResource to fail for sample apps.
+ appExtension.packagingOptions.exclude("/META-INF/*.kotlin_module")
+ // Workaround a limitation in AGP that fails to merge these META-INF license files.
+ appExtension.packagingOptions.pickFirst("/META-INF/AL2.0")
+ // In addition to working around the above issue, we exclude the LGPL2.1 license as we're
+ // approved to distribute code via AL2.0 and the only dependencies which pull in LGPL2.1
+ // are currently dual-licensed with AL2.0 and LGPL2.1. The affected dependencies are:
+ // - net.java.dev.jna:jna:5.5.0
+ appExtension.packagingOptions.exclude("/META-INF/LGPL2.1")
}
private fun configureWithLibraryPlugin(
@@ -202,6 +215,17 @@
configureAndroidLibraryOptions(project, androidXExtension)
}
libraryExtension.packagingOptions {
+ // TODO: Replace this with a per-variant packagingOption for androidTest specifically
+ // once b/69953968 is resolved.
+ // Workaround for b/161465530 in AGP that fails to merge these META-INF license files
+ // for libraries that publish Java resources under the same name.
+ pickFirst("/META-INF/AL2.0")
+ // In addition to working around the above issue, we exclude the LGPL2.1 license as we're
+ // approved to distribute code via AL2.0 and the only dependencies which pull in LGPL2.1
+ // currently are dual-licensed with AL2.0 and LGPL2.1. The affected dependencies are:
+ // - net.java.dev.jna:jna:5.5.0
+ exclude("/META-INF/LGPL2.1")
+
// We need this as a work-around for b/155721209
// It can be removed when we have a newer plugin version
// 2nd workaround - this DSL was made saner in a breaking way which hasn't landed
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index 2b43dbc..6e30585 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -100,6 +100,8 @@
ignore(LibraryGroups.NAVIGATION.group, "navigation-safe-args-generator")
ignore(LibraryGroups.NAVIGATION.group, "navigation-safe-args-gradle-plugin")
prebuilts(LibraryGroups.NAVIGATION, "2.3.0")
+ // TODO: Remove during release phase of rxjava3 artifact
+ ignore(LibraryGroups.PAGING.group, "paging-rxjava3")
prebuilts(LibraryGroups.PAGING, "3.0.0-alpha02")
prebuilts(LibraryGroups.PALETTE, "1.0.0")
// 1.0.1 was created to fix reference docs. It contains no actual source changes from 1.0.0
@@ -114,6 +116,7 @@
prebuilts(LibraryGroups.REMOTECALLBACK, "1.0.0-alpha02")
// TODO: Remove this once b/157899389 is resolved
ignore(LibraryGroups.ROOM.group, "room-compiler")
+ ignore(LibraryGroups.ROOM.group, "room-compiler-xprocessing")
// TODO: Remove during release phase of rxjava3 artifact
ignore(LibraryGroups.ROOM.group, "room-rxjava3")
prebuilts(LibraryGroups.ROOM, "2.3.0-alpha01")
diff --git a/buildSrc/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt b/buildSrc/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
index 2b1e311..03eec98 100644
--- a/buildSrc/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/VerifyDependencyVersionsTask.kt
@@ -121,5 +121,12 @@
// Only verify dependencies within the scope of our versioning policies.
if (dependency.group == null) return false
if (!dependency.group.toString().startsWith("androidx.")) return false
+ if (dependency.version == AndroidXPlaygroundRootPlugin.SNAPSHOT_MARKER) {
+ // This only happens in playground builds where this magic version gets replaced with
+ // the version from the snapshotBuildId defined in playground-common/playground.properties.
+ // It is best to leave their validation to the aosp build to ensure it is the right
+ // version.
+ return false
+ }
return true
}
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
index 5f13437..54a6632 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencies/Dependencies.kt
@@ -142,6 +142,8 @@
get() = "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutinesVersion"
val KOTLIN_COROUTINES_RX2
get() = "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutinesVersion"
+val KOTLIN_COROUTINES_RX3
+ get() = "org.jetbrains.kotlinx:kotlinx-coroutines-rx3:$kotlinCoroutinesVersion"
internal lateinit var agpVersion: String
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/impl/LiveDataObservableTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/impl/LiveDataObservableTest.kt
index 40dfdec..5ca712c 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/impl/LiveDataObservableTest.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/impl/LiveDataObservableTest.kt
@@ -60,7 +60,7 @@
@Test
fun canSetAndFetchValue_onMainThread() {
- var fetched: Int? = null
+ var fetched: Int?
runBlocking(Dispatchers.Main) {
val observable = LiveDataObservable<Int>().apply {
postValue(MAGIC_VALUE)
diff --git a/jetifier/jetifier/processor/src/main/kotlin/com/android/tools/build/jetifier/processor/transform/metainf/MetaInfTransformer.kt b/jetifier/jetifier/processor/src/main/kotlin/com/android/tools/build/jetifier/processor/transform/metainf/MetaInfTransformer.kt
index 836ca6c..e8eac7b 100644
--- a/jetifier/jetifier/processor/src/main/kotlin/com/android/tools/build/jetifier/processor/transform/metainf/MetaInfTransformer.kt
+++ b/jetifier/jetifier/processor/src/main/kotlin/com/android/tools/build/jetifier/processor/transform/metainf/MetaInfTransformer.kt
@@ -48,6 +48,7 @@
"androidx.navigation_navigation-dynamic-features-fragment.version",
"androidx.navigation_navigation-dynamic-features-runtime.version",
"androidx.navigation_navigation-testing.version",
+ "androidx.paging_paging-rxjava3.version",
"androidx.savedstate_savedstate-ktx.version"
)
}
diff --git a/paging/rxjava3/api/3.0.0-alpha04.txt b/paging/rxjava3/api/3.0.0-alpha04.txt
new file mode 100644
index 0000000..598b727
--- /dev/null
+++ b/paging/rxjava3/api/3.0.0-alpha04.txt
@@ -0,0 +1,26 @@
+// Signature format: 3.0
+package androidx.paging.rxjava3 {
+
+ public final class PagingRx {
+ method public static <T> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+ method public static <T> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+ method public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+ method public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+ }
+
+ public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+ ctor public RxPagingSource();
+ method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+ method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+ }
+
+ @androidx.paging.ExperimentalPagingApi public abstract class RxRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+ ctor public RxRemoteMediator();
+ method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction> p);
+ method public io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.InitializeAction> initializeSingle();
+ method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult> p);
+ method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.MediatorResult> loadSingle(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+ }
+
+}
+
diff --git a/paging/rxjava3/api/current.txt b/paging/rxjava3/api/current.txt
new file mode 100644
index 0000000..598b727
--- /dev/null
+++ b/paging/rxjava3/api/current.txt
@@ -0,0 +1,26 @@
+// Signature format: 3.0
+package androidx.paging.rxjava3 {
+
+ public final class PagingRx {
+ method public static <T> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+ method public static <T> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+ method public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+ method public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+ }
+
+ public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+ ctor public RxPagingSource();
+ method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+ method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+ }
+
+ @androidx.paging.ExperimentalPagingApi public abstract class RxRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+ ctor public RxRemoteMediator();
+ method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction> p);
+ method public io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.InitializeAction> initializeSingle();
+ method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult> p);
+ method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.MediatorResult> loadSingle(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+ }
+
+}
+
diff --git a/paging/rxjava3/api/public_plus_experimental_3.0.0-alpha04.txt b/paging/rxjava3/api/public_plus_experimental_3.0.0-alpha04.txt
new file mode 100644
index 0000000..598b727
--- /dev/null
+++ b/paging/rxjava3/api/public_plus_experimental_3.0.0-alpha04.txt
@@ -0,0 +1,26 @@
+// Signature format: 3.0
+package androidx.paging.rxjava3 {
+
+ public final class PagingRx {
+ method public static <T> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+ method public static <T> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+ method public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+ method public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+ }
+
+ public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+ ctor public RxPagingSource();
+ method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+ method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+ }
+
+ @androidx.paging.ExperimentalPagingApi public abstract class RxRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+ ctor public RxRemoteMediator();
+ method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction> p);
+ method public io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.InitializeAction> initializeSingle();
+ method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult> p);
+ method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.MediatorResult> loadSingle(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+ }
+
+}
+
diff --git a/paging/rxjava3/api/public_plus_experimental_current.txt b/paging/rxjava3/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..598b727
--- /dev/null
+++ b/paging/rxjava3/api/public_plus_experimental_current.txt
@@ -0,0 +1,26 @@
+// Signature format: 3.0
+package androidx.paging.rxjava3 {
+
+ public final class PagingRx {
+ method public static <T> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+ method public static <T> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+ method public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+ method public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+ }
+
+ public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+ ctor public RxPagingSource();
+ method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+ method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+ }
+
+ @androidx.paging.ExperimentalPagingApi public abstract class RxRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+ ctor public RxRemoteMediator();
+ method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction> p);
+ method public io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.InitializeAction> initializeSingle();
+ method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult> p);
+ method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.MediatorResult> loadSingle(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+ }
+
+}
+
diff --git a/paging/rxjava3/api/res-3.0.0-alpha04.txt b/paging/rxjava3/api/res-3.0.0-alpha04.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/rxjava3/api/res-3.0.0-alpha04.txt
diff --git a/paging/rxjava3/api/res-current.txt b/paging/rxjava3/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/rxjava3/api/res-current.txt
diff --git a/paging/rxjava3/api/restricted_3.0.0-alpha04.txt b/paging/rxjava3/api/restricted_3.0.0-alpha04.txt
new file mode 100644
index 0000000..598b727
--- /dev/null
+++ b/paging/rxjava3/api/restricted_3.0.0-alpha04.txt
@@ -0,0 +1,26 @@
+// Signature format: 3.0
+package androidx.paging.rxjava3 {
+
+ public final class PagingRx {
+ method public static <T> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+ method public static <T> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+ method public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+ method public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+ }
+
+ public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+ ctor public RxPagingSource();
+ method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+ method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+ }
+
+ @androidx.paging.ExperimentalPagingApi public abstract class RxRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+ ctor public RxRemoteMediator();
+ method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction> p);
+ method public io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.InitializeAction> initializeSingle();
+ method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult> p);
+ method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.MediatorResult> loadSingle(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+ }
+
+}
+
diff --git a/paging/rxjava3/api/restricted_current.txt b/paging/rxjava3/api/restricted_current.txt
new file mode 100644
index 0000000..598b727
--- /dev/null
+++ b/paging/rxjava3/api/restricted_current.txt
@@ -0,0 +1,26 @@
+// Signature format: 3.0
+package androidx.paging.rxjava3 {
+
+ public final class PagingRx {
+ method public static <T> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+ method public static <T> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>> cachedIn(io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<T>>, kotlinx.coroutines.CoroutineScope scope);
+ method public static <Key, Value> io.reactivex.rxjava3.core.Flowable<androidx.paging.PagingData<Value>> getFlowable(androidx.paging.Pager<Key,Value>);
+ method public static <Key, Value> io.reactivex.rxjava3.core.Observable<androidx.paging.PagingData<Value>> getObservable(androidx.paging.Pager<Key,Value>);
+ }
+
+ public abstract class RxPagingSource<Key, Value> extends androidx.paging.PagingSource<Key,Value> {
+ ctor public RxPagingSource();
+ method public final suspend Object? load(androidx.paging.PagingSource.LoadParams<Key> params, kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>> p);
+ method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.PagingSource.LoadResult<Key,Value>> loadSingle(androidx.paging.PagingSource.LoadParams<Key> params);
+ }
+
+ @androidx.paging.ExperimentalPagingApi public abstract class RxRemoteMediator<Key, Value> extends androidx.paging.RemoteMediator<Key,Value> {
+ ctor public RxRemoteMediator();
+ method public final suspend Object? initialize(kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.InitializeAction> p);
+ method public io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.InitializeAction> initializeSingle();
+ method public final suspend Object? load(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state, kotlin.coroutines.Continuation<? super androidx.paging.RemoteMediator.MediatorResult> p);
+ method public abstract io.reactivex.rxjava3.core.Single<androidx.paging.RemoteMediator.MediatorResult> loadSingle(androidx.paging.LoadType loadType, androidx.paging.PagingState<Key,Value> state);
+ }
+
+}
+
diff --git a/paging/rxjava3/build.gradle b/paging/rxjava3/build.gradle
new file mode 100644
index 0000000..d7895df
--- /dev/null
+++ b/paging/rxjava3/build.gradle
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+
+import androidx.build.AndroidXExtension
+import androidx.build.LibraryGroups
+import androidx.build.Publish
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+ id("AndroidXPlugin")
+ id("com.android.library")
+ id("kotlin-android")
+}
+
+dependencies {
+ api(project(":paging:paging-common"))
+ api("androidx.arch.core:core-runtime:2.1.0")
+ api(RX_JAVA3)
+ implementation(KOTLIN_STDLIB)
+ implementation(KOTLIN_COROUTINES_RX3)
+
+ testImplementation(project(":internal-testutils-common"))
+ testImplementation(project(":internal-testutils-paging"))
+ testImplementation(JUNIT)
+ testImplementation(KOTLIN_TEST)
+ testImplementation(KOTLIN_COROUTINES_TEST)
+}
+
+androidx {
+ name = "Android Paging-RxJava3"
+ publish = Publish.SNAPSHOT_AND_RELEASE
+ mavenGroup = LibraryGroups.PAGING
+ inceptionYear = "2020"
+ description = "Android Paging-RxJava3"
+ url = AndroidXExtension.ARCHITECTURE_URL
+}
+
+// Allow usage of Kotlin's @OptIn.
+tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions {
+ freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"]
+ }
+}
diff --git a/paging/rxjava3/src/main/AndroidManifest.xml b/paging/rxjava3/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..df90e392
--- /dev/null
+++ b/paging/rxjava3/src/main/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="androidx.paging.rxjava3">
+</manifest>
diff --git a/paging/rxjava3/src/main/java/androidx/paging/rxjava3/PagingRx.kt b/paging/rxjava3/src/main/java/androidx/paging/rxjava3/PagingRx.kt
new file mode 100644
index 0000000..5c351c2
--- /dev/null
+++ b/paging/rxjava3/src/main/java/androidx/paging/rxjava3/PagingRx.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2020 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.
+ */
+
+@file:JvmName("PagingRx")
+
+package androidx.paging.rxjava3
+
+import androidx.paging.Pager
+import androidx.paging.PagingData
+import androidx.paging.cachedIn
+import io.reactivex.rxjava3.core.BackpressureStrategy
+import io.reactivex.rxjava3.core.Flowable
+import io.reactivex.rxjava3.core.Observable
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.reactive.asFlow
+import kotlinx.coroutines.rx3.asFlowable
+import kotlinx.coroutines.rx3.asObservable
+
+/**
+ * An [Observable] of [PagingData], which mirrors the stream provided by [Pager.flow], but exposes
+ * it as an [Observable].
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+val <Key : Any, Value : Any> Pager<Key, Value>.observable: Observable<PagingData<Value>>
+ get() = flow
+ .conflate()
+ .asObservable()
+
+/**
+ * A [Flowable] of [PagingData], which mirrors the stream provided by [Pager.flow], but exposes
+ * it as a [Flowable].
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+val <Key : Any, Value : Any> Pager<Key, Value>.flowable: Flowable<PagingData<Value>>
+ get() = flow
+ .conflate()
+ .asFlowable()
+
+/**
+ * Operator which caches an [Observable] of [PagingData] within a [CoroutineScope].
+ *
+ * @param scope The [CoroutineScope] where the page cache will be kept alive. Typically this
+ * would be a managed scope such as `ViewModel.viewModelScope`, which automatically cancels after
+ * the [PagingData] stream is no longer needed. Otherwise, the provided [CoroutineScope] must be
+ * manually cancelled to avoid memory leaks.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+fun <T : Any> Observable<PagingData<T>>.cachedIn(scope: CoroutineScope): Observable<PagingData<T>> {
+ return toFlowable(BackpressureStrategy.LATEST)
+ .asFlow()
+ .cachedIn(scope)
+ .asObservable()
+}
+
+/**
+ * Operator which caches a [Flowable] of [PagingData] within a [CoroutineScope].
+ *
+ * @param scope The [CoroutineScope] where the page cache will be kept alive. Typically this
+ * would be a managed scope such as `ViewModel.viewModelScope`, which automatically cancels after
+ * the [PagingData] stream is no longer needed. Otherwise, the provided [CoroutineScope] must be
+ * manually cancelled to avoid memory leaks.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+fun <T : Any> Flowable<PagingData<T>>.cachedIn(scope: CoroutineScope): Flowable<PagingData<T>> {
+ return asFlow()
+ .cachedIn(scope)
+ .asFlowable()
+}
diff --git a/paging/rxjava3/src/main/java/androidx/paging/rxjava3/RxPagingSource.kt b/paging/rxjava3/src/main/java/androidx/paging/rxjava3/RxPagingSource.kt
new file mode 100644
index 0000000..cb53bfc
--- /dev/null
+++ b/paging/rxjava3/src/main/java/androidx/paging/rxjava3/RxPagingSource.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2020 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 androidx.paging.rxjava3
+
+import androidx.paging.PagingSource
+import io.reactivex.rxjava3.core.Single
+import kotlinx.coroutines.rx3.await
+
+/**
+ * Rx-based compatibility wrapper around [PagingSource]'s suspending APIs.
+ *
+ * @sample androidx.paging.samples.rxPagingSourceSample
+ */
+abstract class RxPagingSource<Key : Any, Value : Any> : PagingSource<Key, Value>() {
+ /**
+ * Loading API for [PagingSource].
+ *
+ * Implement this method to trigger your async load (e.g. from database or network).
+ */
+ abstract fun loadSingle(params: LoadParams<Key>): Single<LoadResult<Key, Value>>
+
+ final override suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value> {
+ return loadSingle(params).await()
+ }
+}
\ No newline at end of file
diff --git a/paging/rxjava3/src/main/java/androidx/paging/rxjava3/RxRemoteMediator.kt b/paging/rxjava3/src/main/java/androidx/paging/rxjava3/RxRemoteMediator.kt
new file mode 100644
index 0000000..1d65a7f
--- /dev/null
+++ b/paging/rxjava3/src/main/java/androidx/paging/rxjava3/RxRemoteMediator.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2020 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 androidx.paging.rxjava3
+
+import androidx.paging.ExperimentalPagingApi
+import androidx.paging.LoadState
+import androidx.paging.LoadType
+import androidx.paging.PagingData
+import androidx.paging.PagingSource
+import androidx.paging.PagingState
+import androidx.paging.RemoteMediator
+import androidx.paging.RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
+import androidx.paging.RemoteMediator.MediatorResult
+import io.reactivex.rxjava3.core.Single
+import kotlinx.coroutines.rx3.await
+
+/**
+ * RxJava3 compatibility wrapper around [RemoteMediator]'s suspending APIs.
+ */
+@ExperimentalPagingApi
+abstract class RxRemoteMediator<Key : Any, Value : Any> : RemoteMediator<Key, Value>() {
+ /**
+ * Implement this method to load additional remote data, which will then be stored for the
+ * [PagingSource] to access. These loads take one of two forms:
+ * * type == [LoadType.PREPEND] / [LoadType.APPEND]
+ * The [PagingSource] has loaded a 'boundary' page, with a `null` adjacent key. This means
+ * this method should load additional remote data to append / prepend as appropriate, and store
+ * it locally.
+ * * type == [LoadType.REFRESH]
+ * The app (or [initialize]) has requested a remote refresh of data. This means the method
+ * should generally load remote data, and **replace** all local data.
+ *
+ * The runtime of this method defines loading state behavior in boundary conditions, which
+ * affects e.g., [LoadState] callbacks registered to [androidx.paging.PagingDataAdapter].
+ *
+ * NOTE: A [PagingSource.load] request which is fulfilled by a page that hits a boundary
+ * condition in either direction will trigger this callback with [LoadType.PREPEND] or
+ * [LoadType.APPEND] or both. [LoadType.REFRESH] occurs as a result of [initialize].
+ *
+ * @param loadType [LoadType] of the boundary condition which triggered this callback.
+ * * [LoadType.PREPEND] indicates a boundary condition at the front of the list.
+ * * [LoadType.APPEND] indicates a boundary condition at the end of the list.
+ * * [LoadType.REFRESH] indicates this callback was triggered as the result of a requested
+ * refresh - either driven by the UI, or by [initialize].
+ * @param state A copy of the state including the list of pages currently held in
+ * memory of the currently presented [PagingData] at the time of starting the load. E.g. for
+ * load(loadType = END), you can use the page or item at the end as input for what to load from
+ * the network.
+ *
+ * @return [MediatorResult] signifying what [LoadState] to be passed to the UI, and whether
+ * there's more data available.
+ */
+ abstract fun loadSingle(
+ loadType: LoadType,
+ state: PagingState<Key, Value>
+ ): Single<MediatorResult>
+
+ /**
+ * Callback fired during initialization of a [PagingData] stream, before initial load.
+ *
+ * This function runs to completion before any loading is performed.
+ *
+ * @return [InitializeAction] indicating the action to take after initialization:
+ * * [LAUNCH_INITIAL_REFRESH] to immediately dispatch a [load] asynchronously with load type
+ * [LoadType.REFRESH], to update paginated content when the stream is initialized.
+ * * [SKIP_INITIAL_REFRESH][InitializeAction.SKIP_INITIAL_REFRESH] to wait for a
+ * refresh request from the UI before dispatching a [load] with load type [LoadType.REFRESH].
+ */
+ open fun initializeSingle(): Single<InitializeAction> = Single.just(LAUNCH_INITIAL_REFRESH)
+
+ final override suspend fun load(loadType: LoadType, state: PagingState<Key, Value>):
+ MediatorResult {
+ return loadSingle(loadType, state).await()
+ }
+
+ final override suspend fun initialize(): InitializeAction {
+ return initializeSingle().await()
+ }
+}
\ No newline at end of file
diff --git a/paging/rxjava3/src/test/java/androidx/paging/RxPagingSourceTest.kt b/paging/rxjava3/src/test/java/androidx/paging/RxPagingSourceTest.kt
new file mode 100644
index 0000000..149b4bf
--- /dev/null
+++ b/paging/rxjava3/src/test/java/androidx/paging/RxPagingSourceTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2020 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 androidx.paging
+
+import androidx.paging.PagingSource.LoadResult.Page
+import androidx.paging.rxjava3.RxPagingSource
+import io.reactivex.rxjava3.core.Single
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+
+@RunWith(JUnit4::class)
+class RxPagingSourceTest {
+ private fun loadInternal(params: PagingSource.LoadParams<Int>): Page<Int, Int> {
+ val key = params.key!! // Intentionally fail on null keys
+ return Page(
+ List(params.loadSize) { it + key },
+ prevKey = key - params.loadSize,
+ nextKey = key + params.loadSize
+ )
+ }
+
+ private val pagingSource = object : PagingSource<Int, Int>() {
+ override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
+ return loadInternal(params)
+ }
+ }
+
+ private val rxPagingSource = object : RxPagingSource<Int, Int>() {
+ override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, Int>> {
+ return Single.create { emitter ->
+ emitter.onSuccess(loadInternal(params))
+ }
+ }
+ }
+
+ @Test
+ fun basic() = runBlocking {
+ val params = PagingSource.LoadParams.Refresh(0, 2, false, 2)
+ assertEquals(pagingSource.load(params), rxPagingSource.load(params))
+ }
+
+ @Test
+ fun error() {
+ runBlocking {
+ val params = PagingSource.LoadParams.Refresh<Int>(null, 2, false, 2)
+ assertFailsWith<NullPointerException> { pagingSource.load(params) }
+ assertFailsWith<NullPointerException> { rxPagingSource.load(params) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/paging/rxjava3/src/test/java/androidx/paging/RxRemoteMediatorTest.kt b/paging/rxjava3/src/test/java/androidx/paging/RxRemoteMediatorTest.kt
new file mode 100644
index 0000000..e7cd095
--- /dev/null
+++ b/paging/rxjava3/src/test/java/androidx/paging/RxRemoteMediatorTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020 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 androidx.paging
+
+import androidx.paging.RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
+import androidx.paging.RemoteMediator.InitializeAction.SKIP_INITIAL_REFRESH
+import androidx.paging.rxjava3.RxRemoteMediator
+import io.reactivex.rxjava3.core.Single
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.test.assertEquals
+import kotlin.test.fail
+
+@OptIn(ExperimentalCoroutinesApi::class, ExperimentalPagingApi::class)
+@RunWith(JUnit4::class)
+class RxRemoteMediatorTest {
+ @Test
+ fun initializeSingle() = runBlockingTest {
+ val remoteMediator = object : RxRemoteMediator<Int, Int>() {
+ override fun loadSingle(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): Single<MediatorResult> {
+ fail("Unexpected call")
+ }
+
+ override fun initializeSingle(): Single<InitializeAction> {
+ return Single.just(SKIP_INITIAL_REFRESH)
+ }
+ }
+
+ assertEquals(SKIP_INITIAL_REFRESH, remoteMediator.initialize())
+ }
+
+ @Test
+ fun initializeSingleDefault() = runBlockingTest {
+ val remoteMediator = object : RxRemoteMediator<Int, Int>() {
+ override fun loadSingle(
+ loadType: LoadType,
+ state: PagingState<Int, Int>
+ ): Single<MediatorResult> {
+ fail("Unexpected call")
+ }
+ }
+
+ assertEquals(LAUNCH_INITIAL_REFRESH, remoteMediator.initialize())
+ }
+}
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerTest.java
index 783307e..ae80cc9 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/StaggeredGridLayoutManagerTest.java
@@ -1420,47 +1420,4 @@
Math.max(start, end), event.getToIndex());
}
-
- @Test
- public void testItemDecorationLayoutParamSpanIndices() throws Throwable {
- // This test validates that item decorators attached to the associated recycler view are
- // invoked at the correct time (relative to the layout process) and more importantly,
- // with the correct span indices readable on them.
- Config config = new Config().spanCount(2).itemCount(20);
- setupByConfig(config);
-
- waitFirstLayout();
-
- // Add an item decoration that can act as a validator for the values received.
- mActivityRule.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
- @Override
- public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
- @NonNull RecyclerView recyclerView, @NonNull RecyclerView.State state) {
- super.getItemOffsets(outRect, view, recyclerView, state);
-
- final int position = recyclerView.getChildAdapterPosition(view);
- final StaggeredGridLayoutManager.LayoutParams layoutParams =
- (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
-
- if (position % 2 == 0) {
- // Even items should be on the left side
- assertEquals(0, layoutParams.getSpanIndex());
- } else {
- // Odd items should be on the right side
- assertEquals(1, layoutParams.getSpanIndex());
- }
- }
- });
- }
- });
-
- // Move an item to trigger a change in both position and span index.
- mAdapter.moveAndNotify(1, 4);
-
- requestLayoutOnUIThread(mRecyclerView);
- mLayoutManager.waitForLayout(2000);
- }
}
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
index eb7ef602..aa794c8 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
@@ -5249,10 +5249,7 @@
return lp.mDecorInsets;
}
- final boolean positionWillChange =
- (lp.mViewHolder.getLayoutPosition() != lp.mViewHolder.getAbsoluteAdapterPosition());
- if (mState.isPreLayout()
- && (lp.isItemChanged() || lp.isViewInvalid() || positionWillChange)) {
+ if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
// changed/invalid items should not be updated until they are rebound.
return lp.mDecorInsets;
}
diff --git a/room/compiler-xprocessing/build.gradle b/room/compiler-xprocessing/build.gradle
new file mode 100644
index 0000000..a0a2ac3
--- /dev/null
+++ b/room/compiler-xprocessing/build.gradle
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+import androidx.build.AndroidXExtension
+import androidx.build.CompilationTarget
+import androidx.build.LibraryGroups
+import androidx.build.Publish
+
+import static androidx.build.dependencies.DependenciesKt.*
+
+plugins {
+ id("AndroidXPlugin")
+ id("kotlin")
+}
+
+dependencies {
+ implementation("androidx.annotation:annotation:1.1.0")
+ implementation(GUAVA)
+ implementation(KOTLIN_STDLIB)
+ implementation(AUTO_COMMON)
+ implementation(AUTO_VALUE_ANNOTATIONS)
+ implementation(JAVAPOET)
+ implementation(KOTLIN_METADATA_JVM)
+ implementation(INTELLIJ_ANNOTATIONS)
+
+ testImplementation(GOOGLE_COMPILE_TESTING)
+ testImplementation(JUNIT)
+ testImplementation(JSR250)
+ testImplementation(KOTLIN_COMPILE_TESTING)
+}
+
+compileKotlin {
+ kotlinOptions {
+ freeCompilerArgs += "-Xopt-in=kotlin.contracts.ExperimentalContracts"
+ }
+}
+androidx {
+ name = "AndroidX Room XProcessor"
+ publish = Publish.NONE
+ toolingProject = true
+ mavenGroup = LibraryGroups.ROOM
+ inceptionYear = "2020"
+ description = "Processing Environment Abstraction for AndroidX Room"
+ url = AndroidXExtension.ARCHITECTURE_URL
+ compilationTarget = CompilationTarget.HOST
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/JavaPoetExt.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/JavaPoetExt.kt
new file mode 100644
index 0000000..3e6ac41
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/JavaPoetExt.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import androidx.room.processing.javac.JavacElement
+import androidx.room.processing.javac.JavacExecutableElement
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.MethodSpec
+import com.squareup.javapoet.ParameterSpec
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeSpec
+import javax.lang.model.element.Modifier
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Javapoet does not model NonType, unlike javac, which makes it hard to rely on TypeName for
+ * common functionality (e.g. ability to implement XType.isLong as typename() == TypeName.LONG
+ * instead of in the base class)
+ *
+ * For those cases, we have this hacky type so that we can always query TypeName on an XType.
+ *
+ * We should still strive to avoid these cases, maybe turn it to an error in tests.
+ */
+private val NONE_TYPE_NAME = ClassName.get("androidx.room.processing.error", "NotAType")
+
+internal fun TypeMirror.safeTypeName(): TypeName = if (kind == TypeKind.NONE) {
+ NONE_TYPE_NAME
+} else {
+ TypeName.get(this)
+}
+
+fun TypeSpec.Builder.addOriginatingElement(element: XElement) {
+ if (element is JavacElement) {
+ this.addOriginatingElement(element.element)
+ }
+}
+
+object MethodSpecHelper {
+ /**
+ * Custom override where we mark parameters as final, overidden method as public and also
+ * preserve method names from kotlin metadata.
+ */
+ fun overridingWithFinalParams(
+ elm: XExecutableElement,
+ owner: XDeclaredType
+ ): MethodSpec.Builder {
+ return overridingWithFinalParams(
+ elm,
+ elm.asMemberOf(owner)
+ )
+ }
+
+ private fun overridingWithFinalParams(
+ executableElement: XExecutableElement,
+ resolvedType: XExecutableType = executableElement.executableType
+ ): MethodSpec.Builder {
+ return MethodSpec.methodBuilder(executableElement.name).apply {
+ addTypeVariables(
+ resolvedType.typeVariableNames
+ )
+ resolvedType.parameterTypes.forEachIndexed { index, paramType ->
+ addParameter(
+ ParameterSpec.builder(
+ paramType.typeName,
+ executableElement.parameters[index].name,
+ Modifier.FINAL
+ ).build()
+ )
+ }
+ if (executableElement.isPublic()) {
+ addModifiers(Modifier.PUBLIC)
+ } else if (executableElement.isProtected()) {
+ addModifiers(Modifier.PROTECTED)
+ }
+ addAnnotation(Override::class.java)
+ varargs(executableElement.isVarArgs())
+ if (executableElement is JavacExecutableElement) {
+ // copy throws for java
+ executableElement.element.thrownTypes.forEach {
+ addException(TypeName.get(it))
+ }
+ }
+ returns(resolvedType.returnType.typeName)
+ }
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/JavacTestProcessor.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/JavacTestProcessor.kt
new file mode 100644
index 0000000..89d6d13
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/JavacTestProcessor.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import androidx.annotation.VisibleForTesting
+import androidx.room.processing.javac.JavacProcessingEnv
+import androidx.room.processing.javac.JavacRoundEnv
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.element.TypeElement
+
+/**
+ * Javac processor implementation that provides access to the round environment.
+ *
+ * This is only used in tests, the main processor uses an API similar to the processing step
+ * in Auto.
+ */
+@VisibleForTesting
+abstract class JavacTestProcessor : AbstractProcessor() {
+ val xProcessingEnv by lazy {
+ XProcessingEnv.create(super.processingEnv)
+ }
+
+ final override fun process(
+ annotations: MutableSet<out TypeElement>,
+ roundEnv: RoundEnvironment
+ ): Boolean {
+ if (roundEnv.processingOver()) {
+ return true
+ }
+ val env = xProcessingEnv as JavacProcessingEnv
+ val javacRoundEnv = JavacRoundEnv(env, roundEnv)
+ val xAnnotations = annotations.mapTo(mutableSetOf()) {
+ env.wrapTypeElement(it)
+ }
+ return doProcess(xAnnotations, javacRoundEnv)
+ }
+
+ abstract fun doProcess(
+ annotations: Set<XTypeElement>,
+ roundEnv: XRoundEnv
+ ): Boolean
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XAnnotationBox.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XAnnotationBox.kt
new file mode 100644
index 0000000..7a7ced9
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XAnnotationBox.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+/**
+ * This wraps an annotation element that is both accessible from the processor and runtime.
+ *
+ * It won't scale to a general purpose processing APIs where an equivelant of the AnnotationMirror
+ * API needs to be provided but works well for Room's case.
+ */
+interface XAnnotationBox<T> {
+ /**
+ * The value field of the annotation
+ */
+ val value: T
+
+ /**
+ * Returns the value of the given [methodName] as a type reference.
+ */
+ fun getAsType(methodName: String): XType?
+
+ /**
+ * Returns the value of the given [methodName] as a list of type references.
+ */
+ fun getAsTypeList(methodName: String): List<XType>
+
+ /**
+ * Returns the value of the given [methodName] as another boxed annotation.
+ */
+ fun <T : Annotation> getAsAnnotationBox(methodName: String): XAnnotationBox<T>
+
+ /**
+ * Returns the value of the given [methodName] as an array of boxed annotations.
+ */
+ fun <T : Annotation> getAsAnnotationBoxArray(methodName: String): Array<XAnnotationBox<T>>
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XArrayType.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XArrayType.kt
new file mode 100644
index 0000000..dd39007
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XArrayType.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+interface XArrayType : XType {
+ val componentType: XType
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XDeclaredType.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XDeclaredType.kt
new file mode 100644
index 0000000..dc1196d
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XDeclaredType.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+interface XDeclaredType : XType {
+ val typeArguments: List<XType>
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XElement.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XElement.kt
new file mode 100644
index 0000000..04b8180
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XElement.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import kotlin.contracts.contract
+import kotlin.reflect.KClass
+
+interface XElement {
+ val name: String
+
+ val packageName: String
+
+ /**
+ * TODO:
+ * Nullability is normally a property of Type not Element but currently Room relies on
+ * Annotations to resolve nullability which exists only on Elements, not Types.
+ * Once we implement KSP version, we might be able to move this to the type by making sure
+ * we carry over nullability when type is resolved from an Element. We also need nullability
+ * on Types to properly handle DAO return types (e.g. Flow<T> vs Flow<T?>)
+ */
+ val nullability: XNullability
+
+ val enclosingElement: XElement?
+
+ fun isPublic(): Boolean
+
+ fun isProtected(): Boolean
+
+ fun isAbstract(): Boolean
+
+ fun isPrivate(): Boolean
+
+ fun isStatic(): Boolean
+
+ fun isTransient(): Boolean
+
+ fun isFinal(): Boolean
+
+ fun kindName(): String
+
+ fun <T : Annotation> toAnnotationBox(annotation: KClass<T>): XAnnotationBox<T>?
+
+ // a very sad method but helps avoid abstraction annotation
+ fun hasAnnotationInPackage(pkg: String): Boolean
+
+ fun hasAnnotation(annotation: KClass<out Annotation>): Boolean
+
+ fun hasAnyOf(vararg annotations: KClass<out Annotation>) = annotations.any(this::hasAnnotation)
+
+ fun isNonNull() = nullability == XNullability.NONNULL
+
+ fun asTypeElement() = this as XTypeElement
+
+ fun asVariableElement() = this as XVariableElement
+
+ fun asExecutableElement() = this as XExecutableElement
+
+ fun asDeclaredType(): XDeclaredType {
+ return asTypeElement().type
+ }
+}
+
+// we keep these as extension methods to be able to use contracts
+fun XElement.isType(): Boolean {
+ contract {
+ returns(true) implies (this@isType is XTypeElement)
+ }
+ return this is XTypeElement
+}
+
+fun XElement.isField(): Boolean {
+ contract {
+ returns(true) implies (this@isField is XVariableElement)
+ }
+ return this is XVariableElement
+}
+
+fun XElement.isMethod(): Boolean {
+ contract {
+ returns(true) implies (this@isMethod is XExecutableElement)
+ }
+ return this is XExecutableElement
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XEquality.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XEquality.kt
new file mode 100644
index 0000000..fc3af21
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XEquality.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+/**
+ * Helper interface to enforce implementing equality in wrappers so that we don't by mistake
+ * create wrappers that do not properly handle equality.
+ *
+ * Enforcement is done in JavacType and JavacElement
+ */
+internal interface XEquality {
+ val equalityItems: Array<out Any?>
+
+ companion object {
+ fun hashCode(elements: Array<out Any?>): Int {
+ return elements.contentHashCode()
+ }
+
+ fun equals(first: Any?, second: Any?): Boolean {
+ if (first !is XEquality || second !is XEquality) {
+ return false
+ }
+ return equals(first.equalityItems, second.equalityItems)
+ }
+
+ fun equals(first: Array<out Any?>, second: Array<out Any?>): Boolean {
+ // TODO there is probably a better way to do this
+ if (first.size != second.size) {
+ return false
+ }
+ repeat(first.size) {
+ if (first[it] != second[it]) {
+ return false
+ }
+ }
+ return true
+ }
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XExecutableElement.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XExecutableElement.kt
new file mode 100644
index 0000000..64ab3ff
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XExecutableElement.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+interface XExecutableElement : XElement {
+ override val enclosingElement: XElement
+
+ val parameters: List<XVariableElement>
+
+ val returnType: XType
+
+ val executableType: XExecutableType
+
+ fun isJavaDefault(): Boolean
+
+ fun isVarArgs(): Boolean
+
+ fun asMemberOf(other: XDeclaredType): XExecutableType
+
+ fun findKotlinDefaultImpl(): XExecutableElement?
+
+ fun isSuspendFunction(): Boolean
+
+ fun isOverrideableIgnoringContainer(): Boolean {
+ return !isFinal() && !isPrivate() && !isStatic()
+ }
+
+ fun isConstructor(): Boolean
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XExecutableType.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XExecutableType.kt
new file mode 100644
index 0000000..8a26e3a
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XExecutableType.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import com.squareup.javapoet.TypeVariableName
+
+/**
+ * Represents a type information for an executable.
+ *
+ * It is not an XType as it does not represent a class or primitive.
+ */
+interface XExecutableType {
+ val returnType: XType
+
+ val parameterTypes: List<XType>
+
+ val typeVariableNames: List<TypeVariableName>
+
+ fun getSuspendFunctionReturnType(): XType
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XMessager.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XMessager.kt
new file mode 100644
index 0000000..0df763c
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XMessager.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import javax.tools.Diagnostic
+
+interface XMessager {
+ fun printMessage(kind: Diagnostic.Kind, msg: String, element: XElement? = null)
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XNullability.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XNullability.kt
new file mode 100644
index 0000000..0cdee82
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XNullability.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+enum class XNullability {
+ NULLABLE,
+ NONNULL,
+ UNKNOWN
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XProcessingEnv.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XProcessingEnv.kt
new file mode 100644
index 0000000..f4ed124
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XProcessingEnv.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import androidx.room.processing.javac.JavacProcessingEnv
+import com.squareup.javapoet.TypeName
+import javax.annotation.processing.Filer
+import javax.annotation.processing.ProcessingEnvironment
+import kotlin.reflect.KClass
+
+interface XProcessingEnv {
+
+ val messager: XMessager
+
+ val options: Map<String, String>
+
+ val filer: Filer
+
+ fun findTypeElement(qName: String): XTypeElement?
+
+ fun findType(qName: String): XType?
+
+ fun requireType(qName: String): XType = checkNotNull(findType(qName)) {
+ "cannot find required type $qName"
+ }
+
+ fun findGeneratedAnnotation(): XTypeElement?
+
+ fun getDeclaredType(type: XTypeElement, vararg types: XType): XDeclaredType
+
+ fun getArrayType(type: XType): XArrayType
+
+ fun requireTypeElement(qName: String): XTypeElement {
+ return checkNotNull(findTypeElement(qName)) {
+ "Cannot find required type element $qName"
+ }
+ }
+
+ // helpers for smooth migration, these could be extension methods
+ fun requireType(typeName: TypeName) = requireType(typeName.toString())
+
+ fun requireType(klass: KClass<*>) = requireType(klass.java.canonicalName!!)
+
+ fun findType(typeName: TypeName) = findType(typeName.toString())
+
+ fun findType(klass: KClass<*>) = findType(klass.java.canonicalName!!)
+
+ fun requireTypeElement(typeName: TypeName) = requireTypeElement(typeName.toString())
+
+ fun requireTypeElement(klass: KClass<*>) = requireTypeElement(klass.java.canonicalName!!)
+
+ fun findTypeElement(typeName: TypeName) = findTypeElement(typeName.toString())
+
+ fun findTypeElement(klass: KClass<*>) = findTypeElement(klass.java.canonicalName!!)
+
+ fun getArrayType(typeName: TypeName) = getArrayType(
+ requireType(typeName)
+ )
+
+ companion object {
+ fun create(env: ProcessingEnvironment): XProcessingEnv = JavacProcessingEnv(env)
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XProcessingStep.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XProcessingStep.kt
new file mode 100644
index 0000000..3cbca26
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XProcessingStep.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import androidx.room.processing.javac.JavacElement
+import androidx.room.processing.javac.JavacProcessingEnv
+import com.google.auto.common.BasicAnnotationProcessor
+import com.google.auto.common.MoreElements
+import com.google.common.collect.SetMultimap
+import javax.lang.model.element.Element
+import javax.tools.Diagnostic
+import kotlin.reflect.KClass
+
+/**
+ * Specialized processing step which only supports annotations on TypeElements.
+ *
+ * We can generalize it but for now, Room only needs annotations on TypeElements to start
+ * processing.
+ */
+interface XProcessingStep {
+ fun process(
+ env: XProcessingEnv,
+ elementsByAnnotation: Map<KClass<out Annotation>, List<XTypeElement>>
+ ): Set<XTypeElement>
+
+ fun annotations(): Set<KClass<out Annotation>>
+
+ fun asAutoCommonProcessor(
+ env: XProcessingEnv
+ ): BasicAnnotationProcessor.ProcessingStep {
+ return JavacProcessingStepDelegate(
+ env = env as JavacProcessingEnv,
+ delegate = this
+ )
+ }
+}
+
+@Suppress("UnstableApiUsage")
+internal class JavacProcessingStepDelegate(
+ val env: JavacProcessingEnv,
+ val delegate: XProcessingStep
+) : BasicAnnotationProcessor.ProcessingStep {
+ override fun process(
+ elementsByAnnotation: SetMultimap<Class<out Annotation>, Element>
+ ): Set<Element> {
+ val converted = mutableMapOf<KClass<out Annotation>, List<XTypeElement>>()
+ annotations().forEach { annotation ->
+ val elements = elementsByAnnotation[annotation].mapNotNull { element ->
+ if (MoreElements.isType(element)) {
+ env.wrapTypeElement(MoreElements.asType(element))
+ } else {
+ env.delegate.messager.printMessage(
+ Diagnostic.Kind.ERROR,
+ "Unsupported element type: ${element.kind}",
+ element
+ )
+ null
+ }
+ }
+ converted[annotation.kotlin] = elements
+ }
+ val result = delegate.process(env, converted)
+ return result.map {
+ (it as JavacElement).element
+ }.toSet()
+ }
+
+ override fun annotations(): Set<Class<out Annotation>> {
+ return delegate.annotations().mapTo(mutableSetOf()) {
+ it.java
+ }
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XRoundEnv.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XRoundEnv.kt
new file mode 100644
index 0000000..2e3c0fe
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XRoundEnv.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import androidx.annotation.VisibleForTesting
+import androidx.room.processing.javac.JavacProcessingEnv
+import androidx.room.processing.javac.JavacRoundEnv
+import javax.annotation.processing.RoundEnvironment
+
+// only used in tests of Room
+@VisibleForTesting
+interface XRoundEnv {
+
+ val rootElements: Set<XElement>
+
+ fun getElementsAnnotatedWith(klass: Class<out Annotation>): Set<XElement>
+
+ companion object {
+ fun create(
+ processingEnv: XProcessingEnv,
+ roundEnvironment: RoundEnvironment
+ ): XRoundEnv {
+ check(processingEnv is JavacProcessingEnv)
+ return JavacRoundEnv(processingEnv, roundEnvironment)
+ }
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XType.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XType.kt
new file mode 100644
index 0000000..24b1499
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XType.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import com.squareup.javapoet.TypeName
+import kotlin.contracts.contract
+import kotlin.reflect.KClass
+
+interface XType {
+ val typeName: TypeName
+
+ fun asTypeElement(): XTypeElement
+
+ fun isAssignableFrom(other: XType): Boolean
+
+ fun isAssignableFromWithoutVariance(other: XType): Boolean {
+ return isAssignableWithoutVariance(other, this)
+ }
+
+ // TODO these is<Type> checks may need to be moved into the implementation.
+ // It is not yet clear how we will model some types in Kotlin (e.g. primitives)
+ fun isNotByte() = !isByte()
+
+ fun isError(): Boolean
+
+ fun defaultValue(): String
+
+ fun boxed(): XType
+
+ fun asArray(): XArrayType = this as XArrayType
+
+ fun isPrimitiveInt(): Boolean {
+ return typeName == TypeName.INT
+ }
+
+ fun isBoxedInt() = typeName == TypeName.INT.box()
+
+ fun isInt() = isPrimitiveInt() || isBoxedInt()
+
+ fun isPrimitiveLong() = typeName == TypeName.LONG
+
+ fun isBoxedLong() = typeName == TypeName.LONG.box()
+
+ fun isLong() = isPrimitiveLong() || isBoxedLong()
+
+ fun isList(): Boolean = isType() && isTypeOf(List::class)
+
+ fun isVoid() = typeName == TypeName.VOID
+
+ fun isVoidObject(): Boolean = isType() && isTypeOf(Void::class)
+
+ fun isPrimitive() = typeName.isPrimitive
+
+ fun isKotlinUnit(): Boolean = isType() && isTypeOf(Unit::class)
+
+ fun isNotVoid() = !isVoid()
+
+ fun isNotError() = !isError()
+
+ fun isByte() = typeName == TypeName.BYTE
+
+ fun isNone(): Boolean
+
+ fun isNotNone() = !isNone()
+
+ fun isType(): Boolean
+
+ fun isTypeOf(other: KClass<*>): Boolean
+
+ fun isSameType(other: XType): Boolean
+
+ fun extendsBoundOrSelf(): XType = extendsBound() ?: this
+
+ fun isAssignableWithoutVariance(other: XType): Boolean {
+ return isAssignableWithoutVariance(other, this)
+ }
+
+ fun erasure(): XType
+
+ fun extendsBound(): XType?
+}
+
+fun XType.isDeclared(): Boolean {
+ contract {
+ returns(true) implies (this@isDeclared is XDeclaredType)
+ }
+ return this is XDeclaredType
+}
+
+fun XType.isArray(): Boolean {
+ contract {
+ returns(true) implies (this@isArray is XArrayType)
+ }
+ return this is XArrayType
+}
+
+fun XType.isCollection(): Boolean {
+ contract {
+ returns(true) implies (this@isCollection is XDeclaredType)
+ }
+ return isType() && (isTypeOf(List::class) || isTypeOf(Set::class))
+}
+
+fun XType.asDeclaredType() = this as XDeclaredType
+
+private fun isAssignableWithoutVariance(from: XType, to: XType): Boolean {
+ val assignable = to.isAssignableFrom(from)
+ if (assignable) {
+ return true
+ }
+ if (!from.isDeclared() || !to.isDeclared()) {
+ return false
+ }
+ val declaredFrom = from.asDeclaredType()
+ val declaredTo = to.asDeclaredType()
+ val fromTypeArgs = declaredFrom.typeArguments
+ val toTypeArgs = declaredTo.typeArguments
+ // no type arguments, we don't need extra checks
+ if (fromTypeArgs.isEmpty() || fromTypeArgs.size != toTypeArgs.size) {
+ return false
+ }
+ // check erasure version first, if it does not match, no reason to proceed
+ if (!to.erasure().isAssignableFrom(from.erasure())) {
+ return false
+ }
+ // convert from args to their upper bounds if it exists
+ val fromExtendsBounds = fromTypeArgs.map {
+ it.extendsBound()
+ }
+ // if there are no upper bound conversions, return.
+ if (fromExtendsBounds.all { it == null }) {
+ return false
+ }
+ // try to move the types of the from to their upper bounds. It does not matter for the "to"
+ // because Types.isAssignable handles it as it is valid java
+ return (fromTypeArgs.indices).all { index ->
+ isAssignableWithoutVariance(
+ from = fromExtendsBounds[index] ?: fromTypeArgs[index],
+ to = toTypeArgs[index]
+ )
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XTypeElement.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XTypeElement.kt
new file mode 100644
index 0000000..320821c
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XTypeElement.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import com.squareup.javapoet.ClassName
+
+interface XTypeElement : XElement {
+ val qualifiedName: String
+
+ val type: XDeclaredType
+
+ val superType: XType?
+
+ val className: ClassName
+
+ fun isInterface(): Boolean
+
+ fun isKotlinObject(): Boolean
+
+ /**
+ * All fields, including private supers.
+ * Room only ever reads fields this way.
+ */
+ fun getAllFieldsIncludingPrivateSupers(): List<XVariableElement>
+
+ // only in kotlin
+ fun findPrimaryConstructor(): XExecutableElement?
+
+ /**
+ * methods declared in this type
+ * includes all instance/static methods in this
+ */
+ fun getDeclaredMethods(): List<XExecutableElement>
+
+ /**
+ * Methods declared in this type and its parents
+ * includes all instance/static methods in this
+ * includes all instance/static methods in parent CLASS if they are accessible from this (e.g. not
+ * private).
+ * does not include static methods in parent interfaces
+ */
+ fun getAllMethods(): List<XExecutableElement>
+
+ /**
+ * Instance methods declared in this and supers
+ * include non private instance methods
+ * also includes non-private instance methods from supers
+ */
+ fun getAllNonPrivateInstanceMethods(): List<XExecutableElement>
+
+ fun getConstructors(): List<XExecutableElement>
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/XVariableElement.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XVariableElement.kt
new file mode 100644
index 0000000..84cc29d
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/XVariableElement.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+interface XVariableElement : XElement {
+ val type: XType
+
+ fun asMemberOf(other: XDeclaredType): XType
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/DefaultJavacType.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/DefaultJavacType.kt
new file mode 100644
index 0000000..af56602
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/DefaultJavacType.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.javac
+
+import javax.lang.model.type.TypeMirror
+
+/**
+ * Catch-all class for XType implementation when we don't need/discover a sub-type
+ */
+internal class DefaultJavacType(
+ env: JavacProcessingEnv,
+ typeMirror: TypeMirror
+) : JavacType(
+ env, typeMirror
+) {
+ override val equalityItems by lazy {
+ arrayOf(typeMirror)
+ }
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/ElementExt.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/ElementExt.kt
new file mode 100644
index 0000000..4ff1abe
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/ElementExt.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.javac
+
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.TypeKind
+import javax.lang.model.util.ElementFilter
+import javax.lang.model.util.Elements
+
+/**
+ * gets all members including super privates. does not handle duplicate field names!!!
+ */
+// TODO handle conflicts with super: b/35568142
+fun TypeElement.getAllFieldsIncludingPrivateSupers(
+ elementUtils: Elements
+): Set<VariableElement> {
+ val myMembers = ElementFilter.fieldsIn(elementUtils.getAllMembers(this))
+ .filterIsInstance<VariableElement>()
+ .toSet()
+ if (superclass.kind != TypeKind.NONE) {
+ return myMembers + MoreTypes.asTypeElement(superclass)
+ .getAllFieldsIncludingPrivateSupers(elementUtils)
+ } else {
+ return myMembers
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacAnnotationBox.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacAnnotationBox.kt
new file mode 100644
index 0000000..06e1dda
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacAnnotationBox.kt
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.javac
+
+import androidx.room.processing.XAnnotationBox
+import androidx.room.processing.XType
+import com.google.auto.common.AnnotationMirrors
+import java.lang.reflect.Proxy
+import javax.lang.model.element.AnnotationMirror
+import javax.lang.model.element.AnnotationValue
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.SimpleAnnotationValueVisitor6
+
+internal interface JavacClassGetter {
+ fun getAsType(methodName: String): XType?
+ fun getAsTypeList(methodName: String): List<XType>
+ fun <T : Annotation> getAsAnnotationBox(methodName: String): XAnnotationBox<T>
+ fun <T : Annotation> getAsAnnotationBoxArray(methodName: String): Array<XAnnotationBox<T>>
+}
+
+/**
+ * Class that helps to read values from annotations. Simple types as string, int, lists can
+ * be read from [value]. If you need to read classes or another annotations from annotation use
+ * [getAsType], [getAsAnnotationBox] and [getAsAnnotationBoxArray] correspondingly.
+ */
+internal class JavacAnnotationBox<T : Annotation>(obj: Any) : XAnnotationBox<T> {
+ private val classGetter = obj as JavacClassGetter
+
+ @Suppress("UNCHECKED_CAST")
+ override val value: T = obj as T
+ override fun getAsType(methodName: String): XType? = classGetter.getAsType(methodName)
+
+ override fun getAsTypeList(methodName: String): List<XType> =
+ classGetter.getAsTypeList(methodName)
+
+ override fun <T : Annotation> getAsAnnotationBox(methodName: String): XAnnotationBox<T> {
+ return classGetter.getAsAnnotationBox(methodName)
+ }
+
+ override fun <T : Annotation> getAsAnnotationBoxArray(
+ methodName: String
+ ): Array<XAnnotationBox<T>> {
+ return classGetter.getAsAnnotationBoxArray(methodName)
+ }
+}
+
+internal fun <T : Annotation> AnnotationMirror.box(
+ env: JavacProcessingEnv,
+ cl: Class<T>
+): JavacAnnotationBox<T> {
+ if (!cl.isAnnotation) {
+ throw IllegalArgumentException("$cl is not annotation")
+ }
+ val map = cl.declaredMethods.associate { method ->
+ val value = AnnotationMirrors.getAnnotationValue(this, method.name)
+ val returnType = method.returnType
+ val defaultValue = method.defaultValue
+ val result: Any? = when {
+ returnType == Boolean::class.java -> value.getAsBoolean(defaultValue as Boolean)
+ returnType == String::class.java -> value.getAsString(defaultValue as String?)
+ returnType == Array<String>::class.java -> value.getAsStringList().toTypedArray()
+ returnType == emptyArray<Class<*>>()::class.java -> value.toListOfClassTypes(env)
+ returnType == IntArray::class.java -> value.getAsIntList().toIntArray()
+ returnType == Class::class.java -> {
+ try {
+ value.toClassType(env)
+ } catch (notPresent: TypeNotPresentException) {
+ null
+ }
+ }
+ returnType == Int::class.java -> value.getAsInt(defaultValue as Int?)
+ returnType.isAnnotation -> {
+ @Suppress("UNCHECKED_CAST")
+ AnnotationClassVisitor(env, returnType as Class<out Annotation>).visit(value)
+ }
+ returnType.isArray && returnType.componentType.isAnnotation -> {
+ @Suppress("UNCHECKED_CAST")
+ ListVisitor(env, returnType.componentType as Class<out Annotation>).visit(value)
+ }
+ returnType.isEnum -> {
+ @Suppress("UNCHECKED_CAST")
+ value.getAsEnum(returnType as Class<out Enum<*>>)
+ }
+ else -> throw UnsupportedOperationException("$returnType isn't supported")
+ }
+ method.name to result
+ }
+ return JavacAnnotationBox(
+ Proxy.newProxyInstance(
+ JavacClassGetter::class.java.classLoader,
+ arrayOf(cl, JavacClassGetter::class.java)
+ ) { _, method, args ->
+ when (method.name) {
+ JavacClassGetter::getAsType.name -> map[args[0]]
+ JavacClassGetter::getAsTypeList.name -> map[args[0]]
+ "getAsAnnotationBox" -> map[args[0]]
+ "getAsAnnotationBoxArray" -> map[args[0]]
+ else -> map[method.name]
+ }
+ })
+}
+
+@Suppress("DEPRECATION")
+private val ANNOTATION_VALUE_TO_INT_VISITOR = object : SimpleAnnotationValueVisitor6<Int?, Void>() {
+ override fun visitInt(i: Int, p: Void?): Int? {
+ return i
+ }
+}
+
+@Suppress("DEPRECATION")
+private val ANNOTATION_VALUE_TO_BOOLEAN_VISITOR = object :
+ SimpleAnnotationValueVisitor6<Boolean?, Void>() {
+ override fun visitBoolean(b: Boolean, p: Void?): Boolean? {
+ return b
+ }
+}
+
+@Suppress("DEPRECATION")
+private val ANNOTATION_VALUE_TO_STRING_VISITOR = object :
+ SimpleAnnotationValueVisitor6<String?, Void>() {
+ override fun visitString(s: String?, p: Void?): String? {
+ return s
+ }
+}
+
+@Suppress("DEPRECATION")
+private val ANNOTATION_VALUE_STRING_ARR_VISITOR = object :
+ SimpleAnnotationValueVisitor6<List<String>, Void>() {
+ override fun visitArray(vals: MutableList<out AnnotationValue>?, p: Void?): List<String> {
+ return vals?.mapNotNull {
+ ANNOTATION_VALUE_TO_STRING_VISITOR.visit(it)
+ } ?: emptyList()
+ }
+}
+
+@Suppress("DEPRECATION")
+private val ANNOTATION_VALUE_INT_ARR_VISITOR = object :
+ SimpleAnnotationValueVisitor6<List<Int>, Void>() {
+ override fun visitArray(vals: MutableList<out AnnotationValue>?, p: Void?): List<Int> {
+ return vals?.mapNotNull {
+ ANNOTATION_VALUE_TO_INT_VISITOR.visit(it)
+ } ?: emptyList()
+ }
+}
+
+private fun AnnotationValue.getAsInt(def: Int? = null): Int? {
+ return ANNOTATION_VALUE_TO_INT_VISITOR.visit(this) ?: def
+}
+
+private fun AnnotationValue.getAsIntList(): List<Int> {
+ return ANNOTATION_VALUE_INT_ARR_VISITOR.visit(this)
+}
+
+private fun AnnotationValue.getAsString(def: String? = null): String? {
+ return ANNOTATION_VALUE_TO_STRING_VISITOR.visit(this) ?: def
+}
+
+private fun AnnotationValue.getAsBoolean(def: Boolean): Boolean {
+ return ANNOTATION_VALUE_TO_BOOLEAN_VISITOR.visit(this) ?: def
+}
+
+private fun AnnotationValue.getAsStringList(): List<String> {
+ return ANNOTATION_VALUE_STRING_ARR_VISITOR.visit(this)
+}
+
+// code below taken from dagger2
+// compiler/src/main/java/dagger/internal/codegen/ConfigurationAnnotations.java
+@Suppress("DEPRECATION")
+private val TO_LIST_OF_TYPES = object :
+ SimpleAnnotationValueVisitor6<List<TypeMirror>, Void?>() {
+ override fun visitArray(values: MutableList<out AnnotationValue>?, p: Void?): List<TypeMirror> {
+ return values?.mapNotNull {
+ val tmp = TO_TYPE.visit(it)
+ tmp
+ } ?: emptyList()
+ }
+
+ override fun defaultAction(o: Any?, p: Void?): List<TypeMirror>? {
+ return emptyList()
+ }
+}
+
+@Suppress("DEPRECATION")
+private val TO_TYPE = object : SimpleAnnotationValueVisitor6<TypeMirror, Void>() {
+
+ override fun visitType(t: TypeMirror, p: Void?): TypeMirror {
+ return t
+ }
+
+ override fun defaultAction(o: Any?, p: Void?): TypeMirror {
+ throw TypeNotPresentException(o!!.toString(), null)
+ }
+}
+
+private fun AnnotationValue.toListOfClassTypes(env: JavacProcessingEnv): List<XType> {
+ return TO_LIST_OF_TYPES.visit(this).map {
+ env.wrap<JavacType>(it)
+ }
+}
+
+private fun AnnotationValue.toClassType(env: JavacProcessingEnv): XType? {
+ return TO_TYPE.visit(this)?.let {
+ env.wrap(it)
+ }
+}
+
+@Suppress("DEPRECATION")
+private class ListVisitor<T : Annotation>(
+ private val env: JavacProcessingEnv,
+ private val annotationClass: Class<T>
+) :
+ SimpleAnnotationValueVisitor6<Array<JavacAnnotationBox<T>>, Void?>() {
+ override fun visitArray(
+ values: MutableList<out AnnotationValue>?,
+ void: Void?
+ ): Array<JavacAnnotationBox<T>> {
+ val visitor = AnnotationClassVisitor(env, annotationClass)
+ return values?.mapNotNull { visitor.visit(it) }?.toTypedArray() ?: emptyArray()
+ }
+}
+
+@Suppress("DEPRECATION")
+private class AnnotationClassVisitor<T : Annotation>(
+ private val env: JavacProcessingEnv,
+ private val annotationClass: Class<T>
+) :
+ SimpleAnnotationValueVisitor6<JavacAnnotationBox<T>?, Void?>() {
+ override fun visitAnnotation(a: AnnotationMirror?, v: Void?) = a?.box(env, annotationClass)
+}
+
+@Suppress("UNCHECKED_CAST", "DEPRECATION")
+private fun <T : Enum<*>> AnnotationValue.getAsEnum(enumClass: Class<T>): T {
+ return object : SimpleAnnotationValueVisitor6<T, Void>() {
+ override fun visitEnumConstant(value: VariableElement?, p: Void?): T {
+ return enumClass.getDeclaredMethod("valueOf", String::class.java)
+ .invoke(null, value!!.simpleName.toString()) as T
+ }
+ }.visit(this)
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacArrayType.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacArrayType.kt
new file mode 100644
index 0000000..42fe064
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacArrayType.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.javac
+
+import androidx.room.processing.XArrayType
+import androidx.room.processing.XType
+import javax.lang.model.type.ArrayType
+
+internal class JavacArrayType(
+ env: JavacProcessingEnv,
+ override val typeMirror: ArrayType
+) : JavacType(
+ env,
+ typeMirror
+), XArrayType {
+
+ override val equalityItems: Array<out Any?> by lazy {
+ arrayOf(typeMirror)
+ }
+ override val componentType: XType by lazy {
+ env.wrap<JavacType>(typeMirror.componentType)
+ }
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacDeclaredType.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacDeclaredType.kt
new file mode 100644
index 0000000..835bc7d
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacDeclaredType.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.javac
+
+import androidx.room.processing.XDeclaredType
+import javax.lang.model.type.DeclaredType
+
+internal class JavacDeclaredType(
+ env: JavacProcessingEnv,
+ override val typeMirror: DeclaredType
+) : JavacType(
+ env, typeMirror
+), XDeclaredType {
+ override val equalityItems: Array<out Any?> by lazy {
+ arrayOf(typeMirror)
+ }
+
+ override val typeArguments: List<JavacType> by lazy {
+ env.wrapTypes<JavacType>(typeMirror.typeArguments)
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacElement.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacElement.kt
new file mode 100644
index 0000000..f4fe020
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacElement.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.javac
+
+import androidx.annotation.NonNull
+import androidx.annotation.Nullable
+import androidx.room.processing.XAnnotationBox
+import androidx.room.processing.XElement
+import androidx.room.processing.XEquality
+import androidx.room.processing.XNullability
+import com.google.auto.common.MoreElements
+import java.util.Locale
+import javax.lang.model.element.Element
+import javax.lang.model.element.Modifier
+import kotlin.reflect.KClass
+
+@Suppress("UnstableApiUsage")
+internal abstract class JavacElement(
+ protected val env: JavacProcessingEnv,
+ open val element: Element
+) : XElement, XEquality {
+
+ override val name: String
+ get() = element.simpleName.toString()
+
+ override val packageName: String
+ get() = MoreElements.getPackage(element).qualifiedName.toString()
+
+ override val nullability: XNullability
+ get() = if (element.asType().kind.isPrimitive ||
+ hasAnnotation(NonNull::class) ||
+ hasAnnotation(org.jetbrains.annotations.NotNull::class)
+ ) {
+ XNullability.NONNULL
+ } else if (hasAnnotation(Nullable::class) ||
+ hasAnnotation(org.jetbrains.annotations.Nullable::class)
+ ) {
+ XNullability.NULLABLE
+ } else {
+ XNullability.UNKNOWN
+ }
+
+ override val enclosingElement: XElement? by lazy {
+ val enclosing = element.enclosingElement
+ if (MoreElements.isType(enclosing)) {
+ env.wrapTypeElement(MoreElements.asType(enclosing))
+ } else {
+ // room only cares if it is another type as we do not model packages
+ // or modules.
+ null
+ }
+ }
+
+ override fun isPublic(): Boolean {
+ return element.modifiers.contains(Modifier.PUBLIC)
+ }
+
+ override fun isProtected(): Boolean {
+ return element.modifiers.contains(Modifier.PROTECTED)
+ }
+
+ override fun isAbstract(): Boolean {
+ return element.modifiers.contains(Modifier.ABSTRACT)
+ }
+
+ override fun isPrivate(): Boolean {
+ return element.modifiers.contains(Modifier.PRIVATE)
+ }
+
+ override fun isStatic(): Boolean {
+ return element.modifiers.contains(Modifier.STATIC)
+ }
+
+ override fun isTransient(): Boolean {
+ return element.modifiers.contains(Modifier.TRANSIENT)
+ }
+
+ override fun isFinal(): Boolean {
+ return element.modifiers.contains(Modifier.FINAL)
+ }
+
+ override fun <T : Annotation> toAnnotationBox(annotation: KClass<T>): XAnnotationBox<T>? {
+ return MoreElements
+ .getAnnotationMirror(element, annotation.java)
+ .orNull()
+ ?.box(env, annotation.java)
+ }
+
+ override fun hasAnnotation(annotation: KClass<out Annotation>): Boolean {
+ return MoreElements.isAnnotationPresent(element, annotation.java)
+ }
+
+ override fun toString(): String {
+ return element.toString()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return XEquality.equals(this, other)
+ }
+
+ override fun hashCode(): Int {
+ return XEquality.hashCode(equalityItems)
+ }
+
+ override fun kindName(): String {
+ return element.kind.name.toLowerCase(Locale.US)
+ }
+
+ override fun hasAnnotationInPackage(pkg: String): Boolean {
+ return element.annotationMirrors.any {
+ MoreElements.getPackage(it.annotationType.asElement()).toString() == pkg
+ }
+ }
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacExecutableElement.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacExecutableElement.kt
new file mode 100644
index 0000000..675fa8e
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacExecutableElement.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.javac
+
+import androidx.room.processing.XDeclaredType
+import androidx.room.processing.XExecutableElement
+import androidx.room.processing.XExecutableType
+import androidx.room.processing.XTypeElement
+import androidx.room.processing.XVariableElement
+import androidx.room.processing.javac.kotlin.KotlinMetadataElement
+import androidx.room.processing.javac.kotlin.descriptor
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+
+internal class JavacExecutableElement(
+ env: JavacProcessingEnv,
+ val containing: JavacTypeElement,
+ override val element: ExecutableElement
+) : JavacElement(
+ env,
+ element
+), XExecutableElement {
+ private val kotlinMetadata by lazy {
+ KotlinMetadataElement.createFor(element)
+ }
+
+ val descriptor by lazy {
+ element.descriptor()
+ }
+
+ private val isSuspend by lazy {
+ kotlinMetadata?.isSuspendFunction(element) == true
+ }
+
+ override val enclosingElement: XTypeElement
+ get() = super.enclosingElement as XTypeElement
+
+ override val parameters: List<JavacVariableElement> by lazy {
+ val kotlinParamNames = kotlinMetadata?.getParameterNames(element)
+ element.parameters.mapIndexed { index, variable ->
+ JavacMethodParameter(
+ env = env,
+ containing = containing,
+ element = variable,
+ kotlinName = kotlinParamNames?.getOrNull(index)
+ )
+ }
+ }
+
+ override val returnType: JavacType by lazy {
+ val asMember = env.typeUtils.asMemberOf(containing.type.typeMirror, element)
+ val asExec = MoreTypes.asExecutable(asMember)
+ env.wrap<JavacType>(asExec.returnType)
+ }
+
+ override val equalityItems: Array<out Any?> by lazy {
+ arrayOf(element, containing)
+ }
+
+ @Suppress("UnstableApiUsage")
+ private val kotlinDefaultImplClass by lazy {
+ val parent = element.enclosingElement as? TypeElement
+ val defaultImplElement = parent?.enclosedElements?.find {
+ MoreElements.isType(it) && it.simpleName.contentEquals(DEFAULT_IMPLS_CLASS_NAME)
+ } as? TypeElement
+ defaultImplElement?.let {
+ env.wrapTypeElement(it)
+ }
+ }
+
+ override fun findKotlinDefaultImpl(): XExecutableElement? {
+ fun paramsMatch(
+ ourParams: List<XVariableElement>,
+ theirParams: List<XVariableElement>
+ ): Boolean {
+ if (ourParams.size != theirParams.size - 1) {
+ return false
+ }
+ ourParams.forEachIndexed { i, variableElement ->
+ // Plus 1 to their index because their first param is a self object.
+ if (!theirParams[i + 1].type.isSameType(
+ variableElement.type
+ )
+ ) {
+ return false
+ }
+ }
+ return true
+ }
+ return kotlinDefaultImplClass?.getDeclaredMethods()?.find {
+ it.name == this.name && paramsMatch(parameters, it.parameters)
+ }
+ }
+
+ override fun isSuspendFunction() = isSuspend
+
+ override val executableType: JavacExecutableType by lazy {
+ val asMemberOf = env.typeUtils.asMemberOf(containing.type.typeMirror, element)
+ JavacExecutableType(
+ env = env,
+ executableType = MoreTypes.asExecutable(asMemberOf)
+ )
+ }
+
+ override fun isJavaDefault() = element.modifiers.contains(Modifier.DEFAULT)
+
+ override fun isVarArgs(): Boolean {
+ return element.isVarArgs
+ }
+
+ override fun asMemberOf(other: XDeclaredType): XExecutableType {
+ return if (containing.type.isSameType(other)) {
+ executableType
+ } else {
+ check(other is JavacDeclaredType)
+ val asMemberOf = env.typeUtils.asMemberOf(other.typeMirror, element)
+ JavacExecutableType(
+ env = env,
+ executableType = MoreTypes.asExecutable(asMemberOf)
+ )
+ }
+ }
+
+ override fun isConstructor(): Boolean {
+ return element.kind == ElementKind.CONSTRUCTOR
+ }
+
+ companion object {
+ internal const val DEFAULT_IMPLS_CLASS_NAME = "DefaultImpls"
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacExecutableType.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacExecutableType.kt
new file mode 100644
index 0000000..a321b23
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacExecutableType.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.javac
+
+import androidx.room.processing.XExecutableType
+import androidx.room.processing.XType
+import com.google.auto.common.MoreTypes
+import com.squareup.javapoet.TypeVariableName
+import javax.lang.model.type.ExecutableType
+
+internal class JavacExecutableType(
+ val env: JavacProcessingEnv,
+ val executableType: ExecutableType
+) : XExecutableType {
+ override val returnType: JavacType by lazy {
+ env.wrap<JavacType>(executableType.returnType)
+ }
+
+ override val typeVariableNames by lazy {
+ executableType.typeVariables.map {
+ TypeVariableName.get(it)
+ }
+ }
+
+ override val parameterTypes: List<JavacType> by lazy {
+ executableType.parameterTypes.map {
+ env.wrap<JavacType>(it)
+ }
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is JavacExecutableType) return false
+ return executableType == other.executableType
+ }
+
+ override fun hashCode(): Int {
+ return executableType.hashCode()
+ }
+
+ override fun getSuspendFunctionReturnType(): XType {
+ // the continuation parameter is always the last parameter of a suspend function and it only
+ // has one type parameter, e.g Continuation<? super T>
+ val typeParam =
+ MoreTypes.asDeclared(executableType.parameterTypes.last()).typeArguments.first()
+ return env.wrap<JavacType>(typeParam).extendsBoundOrSelf() // reduce the type param
+ }
+
+ override fun toString(): String {
+ return executableType.toString()
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacMethodParameter.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacMethodParameter.kt
new file mode 100644
index 0000000..afef7ff
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacMethodParameter.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.javac
+
+import javax.lang.model.element.VariableElement
+
+internal class JavacMethodParameter(
+ env: JavacProcessingEnv,
+ containing: JavacTypeElement,
+ element: VariableElement,
+ val kotlinName: String? = null
+) : JavacVariableElement(env, containing, element) {
+ override val name: String
+ get() = kotlinName ?: super.name
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacProcessingEnv.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacProcessingEnv.kt
new file mode 100644
index 0000000..dbe582b
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacProcessingEnv.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.javac
+
+import androidx.room.processing.XDeclaredType
+import androidx.room.processing.XMessager
+import androidx.room.processing.XProcessingEnv
+import androidx.room.processing.XType
+import androidx.room.processing.XTypeElement
+import com.google.auto.common.GeneratedAnnotations
+import com.google.auto.common.MoreTypes
+import java.util.Locale
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.util.Elements
+import javax.lang.model.util.Types
+
+internal class JavacProcessingEnv(
+ val delegate: ProcessingEnvironment
+) : XProcessingEnv {
+
+ val elementUtils: Elements = delegate.elementUtils
+
+ val typeUtils: Types = delegate.typeUtils
+
+ override val messager: XMessager by lazy {
+ JavacProcessingEnvMessager(delegate)
+ }
+
+ override val filer = delegate.filer
+
+ override val options: Map<String, String>
+ get() = delegate.options
+
+ override fun findTypeElement(qName: String): JavacTypeElement? {
+ val result = delegate.elementUtils.getTypeElement(qName)
+ return result?.let(this::wrapTypeElement)
+ }
+
+ override fun findType(qName: String): XType? {
+ // check for primitives first
+ PRIMITIVE_TYPES[qName]?.let {
+ return wrap(
+ typeUtils.getPrimitiveType(it)
+ )
+ }
+ return findTypeElement(qName)?.type
+ }
+
+ override fun findGeneratedAnnotation(): XTypeElement? {
+ val element = GeneratedAnnotations.generatedAnnotation(elementUtils, delegate.sourceVersion)
+ return if (element.isPresent) {
+ wrapTypeElement(element.get())
+ } else {
+ null
+ }
+ }
+
+ override fun getArrayType(type: XType): JavacArrayType {
+ check(type is JavacType) {
+ "given type must be from java, $type is not"
+ }
+ return wrap<JavacArrayType>(
+ typeUtils.getArrayType(type.typeMirror)
+ )
+ }
+
+ override fun getDeclaredType(type: XTypeElement, vararg types: XType): XDeclaredType {
+ check(type is JavacTypeElement)
+ val args = types.map {
+ check(it is JavacType)
+ it.typeMirror
+ }.toTypedArray()
+ check(types.all {
+ it is JavacType
+ })
+ return wrap<JavacDeclaredType>(
+ typeUtils.getDeclaredType(type.element, *args)
+ )
+ }
+
+ // maybe cache here ?
+ fun wrapTypeElement(element: TypeElement) = JavacTypeElement(this, element)
+
+ inline fun <reified T : JavacType> wrapTypes(types: Iterable<TypeMirror>): List<T> {
+ return types.map {
+ wrap<T>(it)
+ }
+ }
+
+ inline fun <reified T : JavacType> wrap(typeMirror: TypeMirror): T {
+ return when (typeMirror.kind) {
+ TypeKind.ARRAY -> JavacArrayType(
+ env = this,
+ typeMirror = MoreTypes.asArray(typeMirror)
+ )
+ TypeKind.DECLARED -> JavacDeclaredType(
+ env = this,
+ typeMirror = MoreTypes.asDeclared(typeMirror)
+ )
+ else -> DefaultJavacType(this, typeMirror)
+ } as T
+ }
+
+ companion object {
+ val PRIMITIVE_TYPES = TypeKind.values().filter {
+ it.isPrimitive
+ }.associateBy {
+ it.name.toLowerCase(Locale.US)
+ }
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacProcessingEnvMessager.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacProcessingEnvMessager.kt
new file mode 100644
index 0000000..dbb0825
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacProcessingEnvMessager.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.javac
+
+import androidx.room.processing.XElement
+import androidx.room.processing.XMessager
+import java.io.StringWriter
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.util.Elements
+import javax.tools.Diagnostic
+
+internal class JavacProcessingEnvMessager(
+ private val processingEnv: ProcessingEnvironment
+) : XMessager {
+ override fun printMessage(kind: Diagnostic.Kind, msg: String, element: XElement?) {
+ val javacElement = (element as? JavacElement)?.element
+ processingEnv.messager.printMessage(
+ kind,
+ if (javacElement != null && javacElement.isFromCompiledClass()) {
+ msg.appendElement(processingEnv.elementUtils, javacElement)
+ } else {
+ msg
+ },
+ javacElement
+ )
+ }
+
+ companion object {
+ /**
+ * Indicates whether an element comes from a compiled class.
+ *
+ * If this method fails to identify if the element comes from a compiled class it will
+ * default to returning false. Note that this is a poor-man's method of identifying if
+ * the java source of the element is available without depending on compiler tools.
+ */
+ private fun Element.isFromCompiledClass(): Boolean {
+ fun getClassFileString(symbol: Any): String =
+ try {
+ symbol.javaClass.getDeclaredField("classfile").get(symbol).toString()
+ } catch (ex: NoSuchFieldException) {
+ getClassFileString(
+ symbol.javaClass.superclass.getDeclaredField("owner").get(symbol)
+ )
+ }
+
+ return try {
+ getClassFileString(this).let {
+ it.contains(".jar") || it.contains(".class")
+ }
+ } catch (ex: Throwable) {
+ false
+ }
+ }
+
+ private fun String.appendElement(elementUtils: Elements, element: Element): String {
+ return StringBuilder(this).apply {
+ append(" - ")
+ when (element.kind) {
+ ElementKind.CLASS, ElementKind.INTERFACE, ElementKind.CONSTRUCTOR ->
+ append(element)
+ ElementKind.FIELD, ElementKind.METHOD, ElementKind.PARAMETER ->
+ append("$element in ${element.enclosingElement}")
+ else -> {
+ // Not sure how to nicely print the element, delegate to utils then.
+ append("In:\n")
+ append(StringWriter().apply {
+ elementUtils.printElements(this, element)
+ }.toString())
+ }
+ }
+ }.toString()
+ }
+ }
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacRoundEnv.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacRoundEnv.kt
new file mode 100644
index 0000000..7ed95ee
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacRoundEnv.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.javac
+
+import androidx.room.processing.XElement
+import androidx.room.processing.XRoundEnv
+import com.google.auto.common.MoreElements
+import javax.annotation.processing.RoundEnvironment
+
+@Suppress("VisibleForTests", "UnstableApiUsage")
+internal class JavacRoundEnv(
+ private val env: JavacProcessingEnv,
+ val delegate: RoundEnvironment
+) : XRoundEnv {
+ override val rootElements: Set<XElement> by lazy {
+ delegate.rootElements.map {
+ check(MoreElements.isType(it))
+ env.wrapTypeElement(MoreElements.asType(it))
+ }.toSet()
+ }
+
+ // TODO this is only for tests but we may need to support more types of elements
+ override fun getElementsAnnotatedWith(klass: Class<out Annotation>): Set<XElement> {
+ val result = delegate.getElementsAnnotatedWith(klass)
+ return result.map {
+ check(MoreElements.isType(it))
+ env.wrapTypeElement(MoreElements.asType(it))
+ }.toSet()
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacType.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacType.kt
new file mode 100644
index 0000000..71f094d
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacType.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.javac
+
+import androidx.room.processing.XEquality
+import androidx.room.processing.XType
+import androidx.room.processing.XTypeElement
+import androidx.room.processing.safeTypeName
+import com.google.auto.common.MoreTypes
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.type.WildcardType
+import javax.lang.model.util.SimpleTypeVisitor7
+import kotlin.reflect.KClass
+
+// TODO make this abstract for XEquality, not open
+internal abstract class JavacType(
+ protected val env: JavacProcessingEnv,
+ open val typeMirror: TypeMirror
+) : XType, XEquality {
+
+ override fun isError() = typeMirror.kind == TypeKind.ERROR
+
+ override val typeName by lazy {
+ typeMirror.safeTypeName()
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return XEquality.equals(this, other)
+ }
+
+ override fun hashCode(): Int {
+ return XEquality.hashCode(equalityItems)
+ }
+
+ override fun defaultValue(): String {
+ return when (typeMirror.kind) {
+ TypeKind.BOOLEAN -> "false"
+ TypeKind.BYTE, TypeKind.SHORT, TypeKind.INT, TypeKind.LONG, TypeKind.CHAR -> "0"
+ TypeKind.FLOAT -> "0f"
+ TypeKind.DOUBLE -> "0.0"
+ else -> "null"
+ }
+ }
+
+ override fun boxed(): XType {
+ return if (typeMirror.kind.isPrimitive) {
+ env.wrap(
+ env.typeUtils.boxedClass(MoreTypes.asPrimitiveType(typeMirror)).asType()
+ )
+ } else {
+ this
+ }
+ }
+
+ override fun asTypeElement(): XTypeElement {
+ return env.wrapTypeElement(
+ MoreTypes.asTypeElement(typeMirror)
+ )
+ }
+
+ override fun isNone() = typeMirror.kind == TypeKind.NONE
+
+ override fun toString(): String {
+ return typeMirror.toString()
+ }
+
+ override fun extendsBound(): XType? {
+ return typeMirror.extendsBound()?.let {
+ env.wrap<JavacType>(it)
+ }
+ }
+
+ private fun TypeMirror.extendsBound(): TypeMirror? {
+ return this.accept(object : SimpleTypeVisitor7<TypeMirror?, Void?>() {
+ override fun visitWildcard(type: WildcardType, ignored: Void?): TypeMirror? {
+ return type.extendsBound ?: type.superBound
+ }
+ }, null)
+ }
+
+ override fun isAssignableFrom(other: XType): Boolean {
+ return other is JavacType && env.typeUtils.isAssignable(
+ other.typeMirror,
+ typeMirror
+ )
+ }
+
+ override fun erasure(): JavacType {
+ return env.wrap(env.typeUtils.erasure(typeMirror))
+ }
+
+ override fun isTypeOf(other: KClass<*>): Boolean {
+ return MoreTypes.isTypeOf(
+ other.java,
+ typeMirror
+ )
+ }
+
+ override fun isSameType(other: XType): Boolean {
+ return other is JavacType && env.typeUtils.isSameType(typeMirror, other.typeMirror)
+ }
+
+ override fun isType(): Boolean {
+ return MoreTypes.isType(typeMirror)
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacTypeElement.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacTypeElement.kt
new file mode 100644
index 0000000..731793e
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacTypeElement.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.javac
+
+import androidx.room.processing.XExecutableElement
+import androidx.room.processing.XTypeElement
+import androidx.room.processing.XVariableElement
+import androidx.room.processing.javac.kotlin.KotlinMetadataElement
+import com.google.auto.common.MoreElements
+import com.squareup.javapoet.ClassName
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.TypeKind
+import javax.lang.model.util.ElementFilter
+
+internal class JavacTypeElement(
+ env: JavacProcessingEnv,
+ override val element: TypeElement
+) : JavacElement(env, element), XTypeElement {
+
+ private val kotlinMetadata by lazy {
+ KotlinMetadataElement.createFor(element)
+ }
+
+ override val qualifiedName by lazy {
+ element.qualifiedName.toString()
+ }
+
+ override val className: ClassName by lazy {
+ ClassName.get(element)
+ }
+
+ override fun isInterface() = element.kind == ElementKind.INTERFACE
+
+ private val _allFieldsIncludingPrivateSupers by lazy {
+ element.getAllFieldsIncludingPrivateSupers(
+ env.elementUtils
+ ).map {
+ JavacVariableElement(
+ env = env,
+ element = it,
+ containing = this
+ )
+ }
+ }
+
+ override fun getAllFieldsIncludingPrivateSupers(): List<XVariableElement> {
+ return _allFieldsIncludingPrivateSupers
+ }
+
+ override fun isKotlinObject() = kotlinMetadata?.isObject() == true
+
+ override fun findPrimaryConstructor(): XExecutableElement? {
+ val primarySignature = kotlinMetadata?.findPrimaryConstructorSignature() ?: return null
+ return getConstructors().firstOrNull {
+ primarySignature == it.descriptor
+ }
+ }
+
+ override fun getDeclaredMethods(): List<JavacExecutableElement> {
+ return ElementFilter.methodsIn(element.enclosedElements).map {
+ JavacExecutableElement(
+ env = env,
+ containing = this,
+ element = it
+ )
+ }
+ }
+
+ override fun getAllMethods(): List<JavacExecutableElement> {
+ return ElementFilter.methodsIn(env.elementUtils.getAllMembers(element)).map {
+ JavacExecutableElement(
+ env = env,
+ containing = this,
+ element = it
+ )
+ }
+ }
+
+ override fun getAllNonPrivateInstanceMethods(): List<JavacExecutableElement> {
+ return MoreElements.getLocalAndInheritedMethods(
+ element,
+ env.typeUtils,
+ env.elementUtils
+ ).map {
+ JavacExecutableElement(
+ env = env,
+ containing = this,
+ element = it
+ )
+ }
+ }
+
+ override fun getConstructors(): List<JavacExecutableElement> {
+ return ElementFilter.constructorsIn(element.enclosedElements).map {
+ JavacExecutableElement(
+ env = env,
+ containing = this,
+ element = it
+ )
+ }
+ }
+
+ override val type: JavacDeclaredType by lazy {
+ env.wrap<JavacDeclaredType>(element.asType())
+ }
+
+ override val superType: JavacType? by lazy {
+ // javac models non-existing types as TypeKind.NONE but we prefer to make it nullable.
+ // just makes more sense and safer as we don't need to check for none.
+
+ // The result value is a JavacType instead of JavacDeclaredType to gracefully handle
+ // cases where super is an error type.
+ val superClass = element.superclass
+ if (superClass.kind == TypeKind.NONE) {
+ null
+ } else {
+ env.wrap<JavacType>(superClass)
+ }
+ }
+
+ override val equalityItems: Array<out Any?> by lazy {
+ arrayOf(element)
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacVariableElement.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacVariableElement.kt
new file mode 100644
index 0000000..4c5aee9
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/JavacVariableElement.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.javac
+
+import androidx.room.processing.XDeclaredType
+import androidx.room.processing.XType
+import androidx.room.processing.XVariableElement
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.VariableElement
+
+internal open class JavacVariableElement(
+ env: JavacProcessingEnv,
+ val containing: JavacTypeElement,
+ override val element: VariableElement
+) : JavacElement(env, element), XVariableElement {
+ override val type: JavacType by lazy {
+ MoreTypes.asMemberOf(env.typeUtils, containing.type.typeMirror, element).let {
+ env.wrap<JavacType>(it)
+ }
+ }
+
+ override fun asMemberOf(other: XDeclaredType): XType {
+ return if (containing.type.isSameType(other)) {
+ type
+ } else {
+ check(other is JavacDeclaredType)
+ val asMember = MoreTypes.asMemberOf(env.typeUtils, other.typeMirror, element)
+ env.wrap<JavacType>(asMember)
+ }
+ }
+
+ override val equalityItems: Array<out Any?> by lazy {
+ arrayOf(element, containing)
+ }
+}
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/JvmDescriptorUtils.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/JvmDescriptorUtils.kt
new file mode 100644
index 0000000..cabec27
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/JvmDescriptorUtils.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2020 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 androidx.room.processing.javac.kotlin
+
+import com.google.auto.common.MoreTypes
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.NestingKind
+import javax.lang.model.element.QualifiedNameable
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.ArrayType
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.ErrorType
+import javax.lang.model.type.ExecutableType
+import javax.lang.model.type.IntersectionType
+import javax.lang.model.type.NoType
+import javax.lang.model.type.NullType
+import javax.lang.model.type.PrimitiveType
+import javax.lang.model.type.TypeKind
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.type.TypeVariable
+import javax.lang.model.type.UnionType
+import javax.lang.model.type.WildcardType
+import javax.lang.model.util.AbstractTypeVisitor8
+
+/**
+ * Returns the method descriptor of this [ExecutableElement].
+ *
+ * For reference, see the [JVM specification, section 4.3.2](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.2)
+ */
+internal fun VariableElement.descriptor() = "$simpleName:${asType().descriptor()}"
+
+/**
+ * Returns the method descriptor of this [ExecutableElement].
+ *
+ * For reference, see the [JVM specification, section 4.3.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.3)
+ */
+internal fun ExecutableElement.descriptor() =
+ "$simpleName${MoreTypes.asExecutable(asType()).descriptor()}"
+
+/**
+ * Returns the name of this [TypeElement] in its "internal form".
+ *
+ * For reference, see the [JVM specification, section 4.2](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.2).
+ */
+internal val Element.internalName: String
+ get() = when (this) {
+ is TypeElement ->
+ when (nestingKind) {
+ NestingKind.TOP_LEVEL ->
+ qualifiedName.toString().replace('.', '/')
+ NestingKind.MEMBER ->
+ enclosingElement.internalName + "$" + simpleName
+ NestingKind.LOCAL, NestingKind.ANONYMOUS ->
+ error("Unsupported nesting $nestingKind")
+ else ->
+ error("Unsupported, nestingKind == null")
+ }
+ is QualifiedNameable -> qualifiedName.toString().replace('.', '/')
+ else -> simpleName.toString()
+ }
+
+@Suppress("unused")
+internal val NoType.descriptor: String
+ get() = "V"
+
+internal val DeclaredType.descriptor: String
+ get() = "L" + asElement().internalName + ";"
+
+internal val PrimitiveType.descriptor: String
+ get() = when (this.kind) {
+ TypeKind.BYTE -> "B"
+ TypeKind.CHAR -> "C"
+ TypeKind.DOUBLE -> "D"
+ TypeKind.FLOAT -> "F"
+ TypeKind.INT -> "I"
+ TypeKind.LONG -> "J"
+ TypeKind.SHORT -> "S"
+ TypeKind.BOOLEAN -> "Z"
+ else -> error("Unknown primitive type $this")
+ }
+
+internal fun TypeMirror.descriptor(): String = accept(JvmDescriptorTypeVisitor, Unit)
+
+@Suppress("unused")
+internal fun WildcardType.descriptor(): String = ""
+
+// The erasure of a type variable is the erasure of its leftmost bound. - JVM Spec Sec 4.6
+internal fun TypeVariable.descriptor(): String = this.upperBound.descriptor()
+
+// For a type variable with multiple bounds: "the erasure of a type variable is determined by
+// the first type in its bound" - JVM Spec Sec 4.4
+internal fun IntersectionType.descriptor(): String =
+ this.bounds[0].descriptor()
+
+internal fun ArrayType.descriptor(): String =
+ "[" + componentType.descriptor()
+
+internal fun ExecutableType.descriptor(): String {
+ val parameterDescriptors =
+ parameterTypes.joinToString(separator = "") { it.descriptor() }
+ val returnDescriptor = returnType.descriptor()
+ return "($parameterDescriptors)$returnDescriptor"
+}
+
+/**
+ * When applied over a type, it returns either:
+ * + a "field descriptor", for example: `Ljava/lang/Object;`
+ * + a "method descriptor", for example: `(Ljava/lang/Object;)Z`
+ *
+ * The easiest way to use this is through [TypeMirror.descriptor]
+ *
+ * For reference, see the [JVM specification, section 4.3](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+@Suppress("DEPRECATION")
+internal object JvmDescriptorTypeVisitor : AbstractTypeVisitor8<String, Unit>() {
+
+ override fun visitNoType(t: NoType, u: Unit): String = t.descriptor
+
+ override fun visitDeclared(t: DeclaredType, u: Unit): String = t.descriptor
+
+ override fun visitPrimitive(t: PrimitiveType, u: Unit): String = t.descriptor
+
+ override fun visitArray(t: ArrayType, u: Unit): String = t.descriptor()
+
+ override fun visitWildcard(t: WildcardType, u: Unit): String = t.descriptor()
+
+ override fun visitExecutable(t: ExecutableType, u: Unit): String = t.descriptor()
+
+ override fun visitTypeVariable(t: TypeVariable, u: Unit): String = t.descriptor()
+
+ override fun visitNull(t: NullType, u: Unit): String = visitUnknown(t, u)
+
+ override fun visitError(t: ErrorType, u: Unit): String = visitUnknown(t, u)
+
+ override fun visitIntersection(t: IntersectionType, u: Unit) = t.descriptor()
+
+ override fun visitUnion(t: UnionType, u: Unit) = visitUnknown(t, u)
+
+ override fun visitUnknown(t: TypeMirror, u: Unit): String = error("Unsupported type $t")
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/KotlinClassMetadataUtils.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/KotlinClassMetadataUtils.kt
new file mode 100644
index 0000000..fefd8e0
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/KotlinClassMetadataUtils.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2020 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 androidx.room.processing.javac.kotlin
+
+import kotlinx.metadata.ClassName
+import kotlinx.metadata.Flag
+import kotlinx.metadata.Flags
+import kotlinx.metadata.KmClassVisitor
+import kotlinx.metadata.KmConstructorExtensionVisitor
+import kotlinx.metadata.KmConstructorVisitor
+import kotlinx.metadata.KmExtensionType
+import kotlinx.metadata.KmFunctionExtensionVisitor
+import kotlinx.metadata.KmFunctionVisitor
+import kotlinx.metadata.KmValueParameterVisitor
+import kotlinx.metadata.jvm.JvmConstructorExtensionVisitor
+import kotlinx.metadata.jvm.JvmFunctionExtensionVisitor
+import kotlinx.metadata.jvm.JvmMethodSignature
+import kotlinx.metadata.jvm.KotlinClassMetadata
+
+/**
+ * Represents the kotlin metadata of a function
+ */
+internal data class KmFunction(
+ val descriptor: String,
+ private val flags: Flags,
+ val parameters: List<KmValueParameter>
+) {
+ fun isSuspend() = Flag.Function.IS_SUSPEND(flags)
+}
+
+/**
+ * Represents the kotlin metadata of a constructor
+ */
+internal data class KmConstructor(
+ val descriptor: String,
+ private val flags: Flags,
+ val parameters: List<KmValueParameter>
+) {
+ fun isPrimary() = Flag.Constructor.IS_PRIMARY(flags)
+}
+
+/**
+ * Represents the kotlin metadata of a parameter
+ */
+internal data class KmValueParameter(val name: String, private val flags: Flags)
+
+internal fun KotlinClassMetadata.Class.readFunctions(): List<KmFunction> =
+ mutableListOf<KmFunction>().apply { accept(FunctionReader(this)) }
+
+private class FunctionReader(val result: MutableList<KmFunction>) : KmClassVisitor() {
+ override fun visitFunction(flags: Flags, name: String): KmFunctionVisitor? {
+ return object : KmFunctionVisitor() {
+
+ lateinit var descriptor: String
+ val parameters = mutableListOf<KmValueParameter>()
+
+ override fun visitValueParameter(
+ flags: Flags,
+ name: String
+ ): KmValueParameterVisitor? {
+ parameters.add(KmValueParameter(name, flags))
+ return super.visitValueParameter(flags, name)
+ }
+
+ override fun visitExtensions(type: KmExtensionType): KmFunctionExtensionVisitor? {
+ if (type != JvmFunctionExtensionVisitor.TYPE) {
+ error("Unsupported extension type: $type")
+ }
+ return object : JvmFunctionExtensionVisitor() {
+ override fun visit(signature: JvmMethodSignature?) {
+ descriptor = signature!!.asString()
+ }
+ }
+ }
+
+ override fun visitEnd() {
+ result.add(KmFunction(descriptor, flags, parameters))
+ }
+ }
+ }
+}
+
+internal fun KotlinClassMetadata.Class.readConstructors(): List<KmConstructor> =
+ mutableListOf<KmConstructor>().apply { accept(ConstructorReader(this)) }
+
+private class ConstructorReader(val result: MutableList<KmConstructor>) : KmClassVisitor() {
+ override fun visitConstructor(flags: Flags): KmConstructorVisitor? {
+ return object : KmConstructorVisitor() {
+
+ lateinit var descriptor: String
+ val parameters = mutableListOf<KmValueParameter>()
+
+ override fun visitValueParameter(
+ flags: Flags,
+ name: String
+ ): KmValueParameterVisitor? {
+ parameters.add(KmValueParameter(name, flags))
+ return super.visitValueParameter(flags, name)
+ }
+
+ override fun visitExtensions(type: KmExtensionType): KmConstructorExtensionVisitor? {
+ if (type != JvmConstructorExtensionVisitor.TYPE) {
+ error("Unsupported extension type: $type")
+ }
+ return object : JvmConstructorExtensionVisitor() {
+ override fun visit(signature: JvmMethodSignature?) {
+ descriptor = signature!!.asString()
+ }
+ }
+ }
+
+ override fun visitEnd() {
+ result.add(KmConstructor(descriptor, flags, parameters))
+ }
+ }
+ }
+}
+
+internal fun KotlinClassMetadata.Class.isObject(): Boolean = ObjectReader().let {
+ this@isObject.accept(it)
+ it.isObject
+}
+
+private class ObjectReader() : KmClassVisitor() {
+ var isObject: Boolean = false
+ override fun visit(flags: Flags, name: ClassName) {
+ isObject = Flag.Class.IS_OBJECT(flags)
+ }
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/KotlinMetadataElement.kt b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/KotlinMetadataElement.kt
new file mode 100644
index 0000000..5e822a2
--- /dev/null
+++ b/room/compiler-xprocessing/src/main/java/androidx/room/processing/javac/kotlin/KotlinMetadataElement.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2020 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 androidx.room.processing.javac.kotlin
+
+import kotlinx.metadata.jvm.KotlinClassHeader
+import kotlinx.metadata.jvm.KotlinClassMetadata
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+
+/**
+ * Utility class for processors that wants to run kotlin specific code.
+ */
+internal class KotlinMetadataElement(
+ val element: Element,
+ private val classMetadata: KotlinClassMetadata.Class
+) {
+
+ private val functionList: List<KmFunction> by lazy { classMetadata.readFunctions() }
+ private val constructorList: List<KmConstructor> by lazy { classMetadata.readConstructors() }
+
+ private val ExecutableElement.descriptor: String
+ get() = descriptor()
+
+ /**
+ * Returns the parameter names of the function or constructor if all have names embedded in the
+ * metadata.
+ */
+ fun getParameterNames(method: ExecutableElement): List<String>? {
+ val methodSignature = method.descriptor
+ val paramList =
+ functionList.firstOrNull { it.descriptor == methodSignature }?.parameters
+ ?: constructorList.firstOrNull { it.descriptor == methodSignature }?.parameters
+ return paramList?.map { it.name }
+ }
+
+ /**
+ * Finds the primary constructor descriptor of the class.
+ */
+ fun findPrimaryConstructorSignature() = constructorList.first { it.isPrimary() }.descriptor
+
+ /**
+ * Checks if a method is a suspend function.
+ */
+ fun isSuspendFunction(method: ExecutableElement) = functionList.firstOrNull {
+ it.descriptor == method.descriptor
+ }?.isSuspend() ?: false
+
+ fun isObject(): Boolean = classMetadata.isObject()
+
+ companion object {
+ /**
+ * Creates a [KotlinMetadataElement] for the given element if it contains Kotlin metadata,
+ * otherwise this method returns null.
+ *
+ * Usually the [element] passed must represent a class. For example, if kotlin metadata is
+ * desired for a method, then the containing class should be used as parameter.
+ */
+ fun createFor(element: Element): KotlinMetadataElement? {
+ val metadata = getMetadataAnnotation(element)?.run {
+ KotlinClassHeader(
+ kind = kind,
+ metadataVersion = metadataVersion,
+ bytecodeVersion = bytecodeVersion,
+ data1 = data1,
+ data2 = data2,
+ extraString = extraString,
+ packageName = packageName,
+ extraInt = extraInt
+ ).let {
+ // TODO: Support more metadata kind (file facade, synthetic class, etc...)
+ KotlinClassMetadata.read(it) as? KotlinClassMetadata.Class
+ }
+ }
+ return if (metadata != null) {
+ KotlinMetadataElement(element, metadata)
+ } else {
+ null
+ }
+ }
+
+ /**
+ * Search for Kotlin's Metadata annotation across the element's hierarchy.
+ */
+ private fun getMetadataAnnotation(element: Element?): Metadata? =
+ if (element != null) {
+ element.getAnnotation(Metadata::class.java)
+ ?: getMetadataAnnotation(element.enclosingElement)
+ } else {
+ null
+ }
+ }
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/JavacTestProcessorTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/JavacTestProcessorTest.kt
new file mode 100644
index 0000000..7a48e00
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/JavacTestProcessorTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import androidx.room.processing.testcode.OtherAnnotation
+import androidx.room.processing.util.Source
+import com.google.common.truth.Truth.assertAbout
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import org.junit.Test
+import java.util.concurrent.atomic.AtomicBoolean
+
+class JavacTestProcessorTest {
+ @Test
+ fun testGetAnnotations() {
+ val source = Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ import androidx.room.processing.testcode.OtherAnnotation;
+ @OtherAnnotation(value="xx")
+ class Baz {
+ }
+ """.trimIndent()
+ )
+ val invoked = AtomicBoolean(false)
+ val testProcessor = object : JavacTestProcessor() {
+ override fun doProcess(annotations: Set<XTypeElement>, roundEnv: XRoundEnv): Boolean {
+ invoked.set(true)
+ val annotatedElements = roundEnv.getElementsAnnotatedWith(
+ OtherAnnotation::class.java
+ )
+ val targetElement = xProcessingEnv.requireTypeElement("foo.bar.Baz")
+ assertThat(
+ annotatedElements
+ ).containsExactly(
+ targetElement
+ )
+ return true
+ }
+
+ override fun getSupportedAnnotationTypes(): Set<String> {
+ return setOf(OtherAnnotation::class.java.canonicalName)
+ }
+ }
+ assertAbout(
+ JavaSourcesSubjectFactory.javaSources()
+ ).that(
+ listOf(source.toJFO())
+ ).processedWith(
+ testProcessor
+ ).compilesWithoutError()
+
+ assertThat(invoked.get()).isTrue()
+ }
+}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/KotlinMetadataTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/KotlinMetadataTest.kt
new file mode 100644
index 0000000..8e0df4d
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/KotlinMetadataTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import androidx.room.processing.testcode.KotlinTestClass
+import androidx.room.processing.util.Source
+import androidx.room.processing.util.getMethod
+import androidx.room.processing.util.getParameter
+import androidx.room.processing.util.runProcessorTest
+import com.google.common.truth.Truth.assertThat
+import com.squareup.javapoet.TypeName
+import org.junit.Test
+
+class KotlinMetadataTest {
+ @Test
+ fun readWithMetadata() {
+ val source = Source.kotlin(
+ "Dummy.kt", """
+ class Dummy
+ """.trimIndent()
+ )
+ runProcessorTest(
+ sources = listOf(source)
+ ) {
+ val element = it.processingEnv.requireTypeElement(KotlinTestClass::class)
+ element.getMethod("mySuspendMethod").apply {
+ assertThat(parameters).hasSize(2)
+ assertThat(getParameter("param1").type.typeName)
+ .isEqualTo(TypeName.get(String::class.java))
+ assertThat(isSuspendFunction()).isTrue()
+ }
+ }
+ }
+}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/MethodSpecHelperTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/MethodSpecHelperTest.kt
new file mode 100644
index 0000000..049e047
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/MethodSpecHelperTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import androidx.room.processing.util.Source
+import androidx.room.processing.util.runProcessorTest
+import com.google.auto.common.MoreTypes
+import com.google.common.truth.Truth.assertAbout
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.squareup.javapoet.MethodSpec
+import org.junit.Test
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.SourceVersion
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.Modifier
+import javax.lang.model.element.TypeElement
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.util.ElementFilter
+import javax.lang.model.util.Types
+
+class MethodSpecHelperTest {
+ @Test
+ fun overrides() {
+ // check our override impl matches javapoet
+ val source = Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ import androidx.room.processing.testcode.OtherAnnotation;
+
+ public class Baz {
+ public void method1() {
+ }
+
+ public void method2(int x) {
+ }
+
+ public int parameterAnnotation(@OtherAnnotation("x") int y) {
+ return 3;
+ }
+
+ @OtherAnnotation("x")
+ public int methodAnntation(int y) {
+ return 3;
+ }
+
+ public int varargMethod(int... y) {
+ return 3;
+ }
+
+ protected <R> R typeArgs(R r) {
+ return r;
+ }
+
+ protected void throwsException() throws Exception {
+ }
+ }
+ """.trimIndent()
+ )
+ // first build golden image with Java processor so we can use JavaPoet's API
+ val golden = buildMethodsViaJavaPoet(source)
+ runProcessorTest(
+ sources = listOf(source)
+ ) {
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ element.getDeclaredMethods().forEachIndexed { index, method ->
+ val subject = MethodSpecHelper.overridingWithFinalParams(
+ method,
+ element.type
+ ).build().toString()
+ assertThat(subject).isEqualTo(golden[index])
+ }
+ }
+ }
+
+ private fun buildMethodsViaJavaPoet(source: Source): List<String> {
+ lateinit var result: List<String>
+ assertAbout(
+ JavaSourcesSubjectFactory.javaSources()
+ ).that(
+ listOf(source.toJFO())
+ ).processedWith(
+ object : AbstractProcessor() {
+ override fun process(
+ annotations: MutableSet<out TypeElement>,
+ roundEnv: RoundEnvironment
+ ): Boolean {
+ val element = processingEnv.elementUtils.getTypeElement("foo.bar.Baz")
+ result = ElementFilter.methodsIn(element.enclosedElements)
+ .map {
+ generateFromJavapoet(
+ it,
+ MoreTypes.asDeclared(element.asType()),
+ processingEnv.typeUtils
+ ).build().toString()
+ }
+ return true
+ }
+
+ override fun getSupportedSourceVersion(): SourceVersion {
+ return SourceVersion.latestSupported()
+ }
+
+ override fun getSupportedAnnotationTypes(): Set<String> {
+ return setOf("*")
+ }
+ }
+ ).compilesWithoutError()
+ return result
+ }
+
+ private fun generateFromJavapoet(
+ method: ExecutableElement,
+ owner: DeclaredType,
+ typeUtils: Types
+ ): MethodSpec.Builder {
+ return overrideWithoutAnnotations(
+ elm = method,
+ owner = owner,
+ typeUtils = typeUtils
+ )
+ }
+
+ /**
+ * Copied from DaoWriter for backwards compatibility
+ */
+ private fun overrideWithoutAnnotations(
+ elm: ExecutableElement,
+ owner: DeclaredType,
+ typeUtils: Types
+ ): MethodSpec.Builder {
+ val baseSpec = MethodSpec.overriding(elm, owner, typeUtils)
+ .build()
+
+ // make all the params final
+ val params = baseSpec.parameters.map { it.toBuilder().addModifiers(Modifier.FINAL).build() }
+
+ return MethodSpec.methodBuilder(baseSpec.name).apply {
+ addAnnotation(Override::class.java)
+ addModifiers(baseSpec.modifiers)
+ addParameters(params)
+ addTypeVariables(baseSpec.typeVariables)
+ addExceptions(baseSpec.exceptions)
+ varargs(baseSpec.varargs)
+ returns(baseSpec.returnType)
+ }
+ }
+}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/SyntheticJavacProcessor.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/SyntheticJavacProcessor.kt
new file mode 100644
index 0000000..cfb3526
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/SyntheticJavacProcessor.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import androidx.room.processing.javac.JavacProcessingEnv
+import androidx.room.processing.util.TestInvocation
+import javax.lang.model.SourceVersion
+
+class SyntheticJavacProcessor(
+ val handler: (TestInvocation) -> Unit
+) : JavacTestProcessor() {
+ private var result: Result<Unit>? = null
+
+ override fun doProcess(annotations: Set<XTypeElement>, roundEnv: XRoundEnv): Boolean {
+ result = kotlin.runCatching {
+ handler(
+ TestInvocation(
+ processingEnv = JavacProcessingEnv(
+ processingEnv
+ )
+ )
+ )
+ }
+ return true
+ }
+
+ override fun getSupportedSourceVersion(): SourceVersion {
+ return SourceVersion.latest()
+ }
+
+ override fun getSupportedAnnotationTypes() = setOf("*")
+
+ fun throwIfFailed() {
+ val result = checkNotNull(result) {
+ "did not compile"
+ }
+ if (result.isFailure) {
+ throw result.exceptionOrNull()!!
+ }
+ }
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/TypeAssignmentTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/TypeAssignmentTest.kt
new file mode 100644
index 0000000..5856a38
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/TypeAssignmentTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 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 androidx.room.processing
+
+import androidx.room.processing.util.Source
+import androidx.room.processing.util.TestInvocation
+import androidx.room.processing.util.getField
+import androidx.room.processing.util.runProcessorTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class TypeAssignmentTest {
+ companion object {
+ private val TEST_OBJECT = Source.java(
+ "foo.bar.MyObject",
+ """
+ package foo.bar;
+ import java.util.Set;
+ import java.util.HashSet;
+ import java.util.Map;
+ class MyObject {
+ String mString;
+ Integer mInteger;
+ Set<MyObject> mSet;
+ Set<? extends MyObject> mVarianceSet;
+ HashSet<MyObject> mHashSet;
+ Map<String, ?> mUnboundedMap;
+ Map<String, String> mStringMap;
+ }
+ """.trimIndent()
+ )
+ }
+
+ @Test
+ fun basic() {
+ runTest {
+ val testObject = processingEnv.requireTypeElement("foo.bar.MyObject")
+ val string = testObject.getField("mString")
+ val integer = testObject.getField("mInteger")
+ assertThat(
+ integer.type.isAssignableFromWithoutVariance(string.type)
+ ).isFalse()
+ }
+ }
+
+ @Test
+ fun generics() {
+ runTest {
+ val testObject = processingEnv.requireTypeElement("foo.bar.MyObject")
+ val set = testObject.getField("mSet").type
+ val hashSet = testObject.getField("mHashSet").type
+ assertThat(hashSet.isAssignableFromWithoutVariance(set)).isFalse()
+ assertThat(set.isAssignableFromWithoutVariance(hashSet)).isTrue()
+ }
+ }
+
+ @Test
+ fun variance() {
+ /**
+ * Set<User> userSet = null;
+ * Set<? extends User> userSet2 = null;
+ * userSet = userSet2; // NOT OK for java but kotlin data classes hit this so we want
+ * // to accept it
+ */
+ runTest {
+ val testObject = processingEnv.requireTypeElement("foo.bar.MyObject")
+ val set = testObject.getField("mSet").type
+ val varianceSet = testObject.getField("mVarianceSet").type
+ assertThat(varianceSet.isAssignableFromWithoutVariance(set)).isTrue()
+ assertThat(set.isAssignableFromWithoutVariance(varianceSet)).isTrue()
+ }
+ }
+
+ @Test
+ fun unboundedVariance() {
+ runTest {
+ val testObject = processingEnv.requireTypeElement("foo.bar.MyObject")
+ val unbounded = testObject.getField("mUnboundedMap").type
+ val objectMap = testObject.getField("mStringMap").type
+ assertThat(objectMap.isAssignableFromWithoutVariance(unbounded)).isFalse()
+ assertThat(unbounded.isAssignableFromWithoutVariance(objectMap)).isTrue()
+ }
+ }
+
+ private fun runTest(handler: TestInvocation.() -> Unit) {
+ runProcessorTest(
+ sources = listOf(TEST_OBJECT),
+ handler = handler
+ )
+ }
+}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/XAnnotationBoxTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XAnnotationBoxTest.kt
new file mode 100644
index 0000000..62d76f7
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XAnnotationBoxTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import androidx.room.processing.testcode.MainAnnotation
+import androidx.room.processing.testcode.OtherAnnotation
+import androidx.room.processing.util.Source
+import androidx.room.processing.util.runProcessorTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class XAnnotationBoxTest {
+ @Test
+ fun readSimpleAnotationValue() {
+ val source = Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ @SuppressWarnings({"warning1", "warning 2"})
+ public class Baz {
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ sources = listOf(source)
+ ) {
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ val annotationBox = element.toAnnotationBox(SuppressWarnings::class)
+ assertThat(annotationBox).isNotNull()
+ assertThat(
+ annotationBox!!.value.value
+ ).isEqualTo(
+ arrayOf("warning1", "warning 2")
+ )
+ }
+ }
+
+ @Test
+ fun typeReference() {
+ val mySource = Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ import androidx.room.processing.testcode.MainAnnotation;
+ import androidx.room.processing.testcode.OtherAnnotation;
+ @MainAnnotation(
+ typeList = {String.class, Integer.class},
+ singleType = Long.class,
+ intMethod = 3,
+ otherAnnotationArray = {
+ @OtherAnnotation(
+ value = "other list 1"
+ ),
+ @OtherAnnotation("other list 2"),
+ },
+ singleOtherAnnotation = @OtherAnnotation("other single")
+ )
+ public class Baz {
+ }
+ """.trimIndent()
+ )
+ val targetName = "foo.bar.Baz"
+ runProcessorTest(
+ listOf(mySource)
+ ) {
+ val element = it.processingEnv.requireTypeElement(targetName)
+ element.toAnnotationBox(MainAnnotation::class)!!.let { annotation ->
+ assertThat(
+ annotation.getAsTypeList("typeList")
+ ).containsExactly(
+ it.processingEnv.requireType(java.lang.String::class.java.canonicalName),
+ it.processingEnv.requireType(java.lang.Integer::class.java.canonicalName)
+ )
+ assertThat(
+ annotation.getAsType("singleType")
+ ).isEqualTo(
+ it.processingEnv.requireType(java.lang.Long::class.java.canonicalName)
+ )
+
+ assertThat(annotation.value.intMethod).isEqualTo(3)
+ annotation.getAsAnnotationBox<OtherAnnotation>("singleOtherAnnotation")
+ .let { other ->
+ assertThat(other.value.value).isEqualTo("other single")
+ }
+ annotation.getAsAnnotationBoxArray<OtherAnnotation>("otherAnnotationArray")
+ .let { boxArray ->
+ assertThat(boxArray).hasLength(2)
+ assertThat(boxArray[0].value.value).isEqualTo("other list 1")
+ assertThat(boxArray[1].value.value).isEqualTo("other list 2")
+ }
+ }
+ }
+ }
+}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/XArrayTypeTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XArrayTypeTest.kt
new file mode 100644
index 0000000..9544160
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XArrayTypeTest.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import androidx.room.processing.util.Source
+import androidx.room.processing.util.getField
+import androidx.room.processing.util.runProcessorTest
+import com.google.common.truth.Truth.assertThat
+import com.squareup.javapoet.ArrayTypeName
+import com.squareup.javapoet.TypeName
+import org.junit.Test
+
+class XArrayTypeTest {
+ @Test
+ fun xArrayType() {
+ val source = Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ class Baz {
+ String[] param;
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ sources = listOf(source)
+ ) {
+ val type = it.processingEnv
+ .requireTypeElement("foo.bar.Baz")
+ .getField("param")
+ .type
+ assertThat(type.isArray()).isTrue()
+ assertThat(type.typeName).isEqualTo(
+ ArrayTypeName.of(TypeName.get(String::class.java))
+ )
+ assertThat(type.asArray().componentType.typeName).isEqualTo(
+ TypeName.get(String::class.java)
+ )
+
+ val objArray = it.processingEnv.getArrayType(
+ TypeName.OBJECT
+ )
+ assertThat(objArray.isArray()).isTrue()
+ assertThat(objArray.asArray().componentType.typeName).isEqualTo(
+ TypeName.OBJECT
+ )
+ assertThat(objArray.typeName).isEqualTo(
+ ArrayTypeName.of(TypeName.OBJECT)
+ )
+ }
+ }
+
+ @Test
+ fun notAnArray() {
+ runProcessorTest {
+ val list = it.processingEnv.requireType("java.util.List")
+ assertThat(list.isArray()).isFalse()
+ }
+ }
+}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/XElementTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XElementTest.kt
new file mode 100644
index 0000000..6cf6f2e
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XElementTest.kt
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import androidx.room.processing.testcode.OtherAnnotation
+import androidx.room.processing.util.Source
+import androidx.room.processing.util.getField
+import androidx.room.processing.util.getMethod
+import androidx.room.processing.util.getParameter
+import androidx.room.processing.util.runProcessorTest
+import com.google.common.truth.Truth.assertThat
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeVariableName
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class XElementTest {
+ @Test
+ fun modifiers() {
+ runProcessorTest(
+ listOf(
+ Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ public abstract class Baz {
+ private int privateField;
+ int packagePrivateField;
+ protected int protectedField;
+ public int publicField;
+ transient int transientField;
+ static int staticField;
+
+ private void privateMethod() {}
+ void packagePrivateMethod() {}
+ public void publicMethod() {}
+ protected void protectedMethod() {}
+ final void finalMethod() {}
+ abstract void abstractMethod();
+ static void staticMethod() {}
+ }
+ """.trimIndent()
+ )
+ )
+ ) {
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ fun XElement.readModifiers(): Set<String> {
+ val result = mutableSetOf<String>()
+ if (isPrivate()) result.add("private")
+ if (isPublic()) result.add("public")
+ if (isTransient()) result.add("transient")
+ if (isStatic()) result.add("static")
+ if (isFinal()) result.add("final")
+ if (isAbstract()) result.add("abstract")
+ if (isProtected()) result.add("protected")
+ return result
+ }
+
+ fun XElement.assertModifiers(vararg expected: String) {
+ assertThat(readModifiers()).containsExactlyElementsIn(expected)
+ }
+ element.assertModifiers("abstract", "public")
+ element.getField("privateField").assertModifiers("private")
+ element.getField("packagePrivateField").assertModifiers()
+ // we don't read isProtected, no reason. we should eventually get rid of most if not
+ // all anyways
+ element.getField("protectedField").assertModifiers("protected")
+ element.getField("publicField").assertModifiers("public")
+ element.getField("transientField").assertModifiers("transient")
+ element.getField("staticField").assertModifiers("static")
+
+ element.getMethod("privateMethod").assertModifiers("private")
+ element.getMethod("packagePrivateMethod").assertModifiers()
+ element.getMethod("publicMethod").assertModifiers("public")
+ element.getMethod("protectedMethod").assertModifiers("protected")
+ element.getMethod("finalMethod").assertModifiers("final")
+ element.getMethod("abstractMethod").assertModifiers("abstract")
+ element.getMethod("staticMethod").assertModifiers("static")
+
+ assertThat(
+ element.getMethod("privateMethod").isOverrideableIgnoringContainer()
+ ).isFalse()
+ assertThat(
+ element.getMethod("packagePrivateMethod").isOverrideableIgnoringContainer()
+ ).isTrue()
+ assertThat(element.getMethod("publicMethod").isOverrideableIgnoringContainer()).isTrue()
+ assertThat(
+ element.getMethod("protectedMethod").isOverrideableIgnoringContainer()
+ ).isTrue()
+ assertThat(element.getMethod("finalMethod").isOverrideableIgnoringContainer()).isFalse()
+ assertThat(
+ element.getMethod("abstractMethod").isOverrideableIgnoringContainer()
+ ).isTrue()
+ assertThat(
+ element.getMethod("staticMethod").isOverrideableIgnoringContainer()
+ ).isFalse()
+ }
+ }
+
+ @Test
+ fun typeParams() {
+ val genericBase = Source.java(
+ "foo.bar.Base", """
+ package foo.bar;
+ public class Base<T> {
+ protected T returnT() {
+ throw new RuntimeException("Stub");
+ }
+ public int receiveT(T param1) {
+ return 3;
+ }
+ public <R> int receiveR(R param1) {
+ return 3;
+ }
+ public <R> R returnR() {
+ throw new RuntimeException("Stub");
+ }
+ }
+ """.trimIndent()
+ )
+ val boundedChild = Source.java(
+ "foo.bar.Child", """
+ package foo.bar;
+ public class Child extends Base<String> {
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ listOf(genericBase, boundedChild)
+ ) {
+ fun validateElement(element: XTypeElement, tTypeName: TypeName, rTypeName: TypeName) {
+ element.getMethod("returnT").let { method ->
+ assertThat(method.parameters).isEmpty()
+ assertThat(method.returnType.typeName).isEqualTo(tTypeName)
+ }
+ element.getMethod("receiveT").let { method ->
+ method.getParameter("param1").let { param ->
+ assertThat(param.type.typeName).isEqualTo(tTypeName)
+ }
+ assertThat(method.returnType.typeName).isEqualTo(TypeName.INT)
+ }
+ element.getMethod("receiveR").let { method ->
+ method.getParameter("param1").let { param ->
+ assertThat(param.type.typeName).isEqualTo(rTypeName)
+ }
+ assertThat(method.returnType.typeName).isEqualTo(TypeName.INT)
+ }
+ element.getMethod("returnR").let { method ->
+ assertThat(method.parameters).isEmpty()
+ assertThat(method.returnType.typeName).isEqualTo(rTypeName)
+ }
+ }
+ validateElement(
+ element = it.processingEnv.requireTypeElement("foo.bar.Base"),
+ tTypeName = TypeVariableName.get("T"),
+ rTypeName = TypeVariableName.get("R")
+ )
+ validateElement(
+ element = it.processingEnv.requireTypeElement("foo.bar.Child"),
+ tTypeName = ClassName.get(String::class.java),
+ rTypeName = TypeVariableName.get("R")
+ )
+ }
+ }
+
+ @Test
+ fun annotationAvailability() {
+ val source = Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ import org.junit.*;
+ import org.junit.runner.*;
+ import org.junit.runners.*;
+ import androidx.room.processing.testcode.OtherAnnotation;
+
+ @RunWith(JUnit4.class)
+ class Baz {
+ @OtherAnnotation(value="xx")
+ String testField;
+
+ @org.junit.Test
+ void testMethod() {}
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ listOf(source)
+ ) {
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ assertThat(element.hasAnnotation(RunWith::class)).isTrue()
+ assertThat(element.hasAnnotation(Test::class)).isFalse()
+ element.getMethod("testMethod").let { method ->
+ assertThat(method.hasAnnotation(Test::class)).isTrue()
+ assertThat(method.hasAnnotation(Override::class)).isFalse()
+ assertThat(
+ method.hasAnnotationInPackage(
+ "org.junit"
+ )
+ ).isTrue()
+ }
+ element.getField("testField").let { field ->
+ assertThat(field.hasAnnotation(OtherAnnotation::class)).isTrue()
+ assertThat(field.hasAnnotation(Test::class)).isFalse()
+ }
+ assertThat(
+ element.hasAnnotationInPackage(
+ "org.junit.runner"
+ )
+ ).isTrue()
+ assertThat(
+ element.hasAnnotationInPackage(
+ "org.junit"
+ )
+ ).isFalse()
+ assertThat(
+ element.hasAnnotationInPackage(
+ "foo.bar"
+ )
+ ).isFalse()
+ }
+ }
+
+ @Test
+ fun nonType() {
+ val source = Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ class Baz {
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ listOf(source)
+ ) {
+ val element = it.processingEnv.requireTypeElement("java.lang.Object")
+ // make sure we return null for not existing types
+ assertThat(element.superType).isNull()
+ }
+ }
+
+ @Test
+ fun isSomething() {
+ val subject = Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ class Baz {
+ int field;
+
+ void method() {}
+ static interface Inner {}
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ sources = listOf(subject)
+ ) {
+ val inner = ClassName.get("foo.bar", "Baz.Inner")
+ assertThat(
+ it.processingEnv.requireTypeElement(inner).isInterface()
+ ).isTrue()
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ assertThat(element.isInterface()).isFalse()
+ assertThat(element.isAbstract()).isFalse()
+ assertThat(element.isType()).isTrue()
+ element.getField("field").let { field ->
+ assertThat(field.isType()).isFalse()
+ assertThat(field.isAbstract()).isFalse()
+ assertThat(field.isField()).isTrue()
+ assertThat(field.isMethod()).isFalse()
+ }
+ element.getMethod("method").let { method ->
+ assertThat(method.isType()).isFalse()
+ assertThat(method.isAbstract()).isFalse()
+ assertThat(method.isField()).isFalse()
+ assertThat(method.isMethod()).isTrue()
+ }
+ }
+ }
+
+ @Test
+ fun notATypeElement() {
+ val source = Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ class Baz {
+ public static int x;
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ listOf(source)
+ ) {
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ element.getField("x").let { field ->
+ assertThat(field.isStatic()).isTrue()
+ val fail = runCatching {
+ field.asTypeElement()
+ }
+ assertThat(fail.exceptionOrNull()).isNotNull()
+ }
+ }
+ }
+
+ @Test
+ fun nullability() {
+ val source = Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+
+ import androidx.annotation.*;
+ import java.util.List;
+ class Baz {
+ public static int primitiveInt;
+ public static Integer boxedInt;
+ @NonNull
+ public static List<String> nonNullAnnotated;
+ @Nullable
+ public static List<String> nullableAnnotated;
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ sources = listOf(source)
+ ) {
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ element.getField("primitiveInt").let { field ->
+ assertThat(field.nullability).isEqualTo(XNullability.NONNULL)
+ }
+ element.getField("boxedInt").let { field ->
+ assertThat(field.nullability).isEqualTo(XNullability.UNKNOWN)
+ }
+ element.getField("nonNullAnnotated").let { field ->
+ assertThat(field.nullability).isEqualTo(XNullability.NONNULL)
+ }
+ element.getField("nullableAnnotated").let { field ->
+ assertThat(field.nullability).isEqualTo(XNullability.NULLABLE)
+ }
+ }
+ }
+
+ @Test
+ fun toStringMatchesUnderlyingElement() {
+ runProcessorTest {
+ it.processingEnv.findTypeElement("java.util.List").let { list ->
+ assertThat(list.toString()).isEqualTo("java.util.List")
+ }
+ }
+ }
+}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/XExecutableElementTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XExecutableElementTest.kt
new file mode 100644
index 0000000..4181d68
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XExecutableElementTest.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import androidx.room.processing.util.Source
+import androidx.room.processing.util.getDeclaredMethod
+import androidx.room.processing.util.getMethod
+import androidx.room.processing.util.getParameter
+import androidx.room.processing.util.runProcessorTest
+import com.google.common.truth.Truth.assertThat
+import com.squareup.javapoet.TypeName
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class XExecutableElementTest {
+ @Test
+ fun basic() {
+ runProcessorTest(
+ listOf(
+ Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ public class Baz {
+ private void foo() {}
+ public int bar(int param1) {
+ return 3;
+ }
+ }
+ """.trimIndent()
+ )
+ )
+ ) {
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ element.getDeclaredMethod("foo").let { method ->
+ assertThat(method.isJavaDefault()).isFalse()
+ assertThat(method.isVarArgs()).isFalse()
+ assertThat(method.isOverrideableIgnoringContainer()).isFalse()
+ assertThat(method.parameters).isEmpty()
+ val returnType = method.returnType
+ assertThat(returnType.isVoid()).isTrue()
+ assertThat(returnType.defaultValue()).isEqualTo("null")
+ }
+ element.getDeclaredMethod("bar").let { method ->
+ assertThat(method.isOverrideableIgnoringContainer()).isTrue()
+ assertThat(method.parameters).hasSize(1)
+ method.getParameter("param1").let { param ->
+ assertThat(param.type.isPrimitiveInt()).isTrue()
+ }
+ assertThat(method.returnType.isPrimitiveInt()).isTrue()
+ }
+ }
+ }
+ @Test
+ fun isVarArgs() {
+ val subject = Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ interface Baz {
+ void method(String... inputs);
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ sources = listOf(subject)
+ ) {
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ assertThat(element.getMethod("method").isVarArgs()).isTrue()
+ }
+ }
+
+ @Test
+ fun kotlinDefaultImpl() {
+ val subject = Source.kotlin(
+ "Baz.kt", """
+ package foo.bar;
+ import java.util.List;
+ interface Baz {
+ fun noDefault()
+ fun withDefault(): Int {
+ return 3;
+ }
+ fun nameMatch()
+ fun nameMatch(param:Int) {}
+ fun withDefaultWithParams(param1:Int, param2:String) {}
+ fun withDefaultWithTypeArgs(param1: List<String>): String {
+ return param1.first();
+ }
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ sources = listOf(subject)
+ ) {
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ element.getDeclaredMethod("noDefault").let { method ->
+ assertThat(method.findKotlinDefaultImpl()).isNull()
+ }
+ element.getDeclaredMethod("withDefault").let { method ->
+ val defaultImpl = method.findKotlinDefaultImpl()
+ assertThat(defaultImpl).isNotNull()
+ assertThat(defaultImpl!!.returnType.typeName).isEqualTo(TypeName.INT)
+ // default impl gets self as first parameter
+ assertThat(defaultImpl.parameters).hasSize(1)
+ assertThat(defaultImpl.parameters.first().type)
+ .isEqualTo(element.type)
+ }
+ element.getDeclaredMethods().first {
+ it.name == "nameMatch" && it.parameters.isEmpty()
+ }.let { nameMatchWithoutDefault ->
+ assertThat(nameMatchWithoutDefault.findKotlinDefaultImpl()).isNull()
+ }
+
+ element.getDeclaredMethods().first {
+ it.name == "nameMatch" && it.parameters.size == 1
+ }.let { nameMatchWithoutDefault ->
+ assertThat(nameMatchWithoutDefault.findKotlinDefaultImpl()).isNotNull()
+ }
+
+ element.getDeclaredMethod("withDefaultWithParams").let { method ->
+ val defaultImpl = method.findKotlinDefaultImpl()
+ assertThat(defaultImpl).isNotNull()
+ assertThat(defaultImpl!!.parameters.drop(1).map {
+ it.name
+ }).containsExactly("param1", "param2")
+ }
+
+ element.getDeclaredMethod("withDefaultWithTypeArgs").let { method ->
+ val defaultImpl = method.findKotlinDefaultImpl()
+ assertThat(defaultImpl).isNotNull()
+ assertThat(defaultImpl!!.parameters.drop(1).map {
+ it.name
+ }).containsExactly("param1")
+ }
+ }
+ }
+}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/XProcessingEnvTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XProcessingEnvTest.kt
new file mode 100644
index 0000000..7305f37
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XProcessingEnvTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import androidx.room.processing.util.Source
+import androidx.room.processing.util.runProcessorTest
+import com.google.common.truth.Truth.assertThat
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class XProcessingEnvTest {
+ @Test
+ fun getElement() {
+ runProcessorTest(
+ listOf(
+ Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ public class Baz {
+ }
+ """.trimIndent()
+ )
+ )
+ ) {
+ val qName = "java.util.List"
+ val className = ClassName.get("java.util", "List")
+ val klass = List::class
+ val element = it.processingEnv.requireTypeElement(qName)
+ assertThat(element).isNotNull()
+ assertThat(element.packageName).isEqualTo("java.util")
+ assertThat(element.name).isEqualTo("List")
+
+ val type = element.type
+
+ assertThat(
+ it.processingEnv.findTypeElement(qName)
+ ).isEqualTo(element)
+ assertThat(
+ it.processingEnv.findTypeElement(className)
+ ).isEqualTo(element)
+ assertThat(
+ it.processingEnv.findTypeElement(klass)
+ ).isEqualTo(element)
+
+ assertThat(
+ it.processingEnv.requireTypeElement(className)
+ ).isEqualTo(element)
+ assertThat(
+ it.processingEnv.requireTypeElement(klass)
+ ).isEqualTo(element)
+
+ assertThat(
+ it.processingEnv.findType(qName)
+ ).isEqualTo(type)
+ assertThat(
+ it.processingEnv.findType(className)
+ ).isEqualTo(type)
+ assertThat(
+ it.processingEnv.findType(klass)
+ ).isEqualTo(type)
+
+ assertThat(
+ it.processingEnv.requireType(className)
+ ).isEqualTo(type)
+ assertThat(
+ it.processingEnv.requireType(klass)
+ ).isEqualTo(type)
+ assertThat(
+ it.processingEnv.requireType(qName)
+ ).isEqualTo(type)
+ }
+ }
+
+ @Test
+ fun basic() {
+ runProcessorTest(
+ listOf(
+ Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ public class Baz {
+ private void foo() {}
+ public int bar(int param1) {
+ return 3;
+ }
+ }
+ """.trimIndent()
+ )
+ )
+ ) {
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ assertThat(element.packageName).isEqualTo("foo.bar")
+ assertThat(element.name).isEqualTo("Baz")
+ assertThat(element.asDeclaredType().typeName)
+ .isEqualTo(ClassName.get("foo.bar", "Baz"))
+ assertThat(element.getConstructors()).hasSize(1)
+ assertThat(element.getDeclaredMethods()).hasSize(2)
+ assertThat(element.kindName()).isEqualTo("class")
+ assertThat(element.isInterface()).isFalse()
+ assertThat(element.superType?.typeName).isEqualTo(TypeName.OBJECT)
+ }
+ }
+
+ @Test
+ fun getPrimitives() {
+ val source = Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ class Baz {
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ listOf(source)
+ ) { invocation ->
+ PRIMITIVE_TYPES.forEach {
+ val targetType = invocation.processingEnv.findType(it.key)
+ assertThat(targetType?.typeName).isEqualTo(it.value)
+ assertThat(targetType?.boxed()?.typeName).isEqualTo(it.value.box())
+ }
+ }
+ }
+
+ companion object {
+ val PRIMITIVE_TYPES = mapOf(
+ TypeName.BOOLEAN.toString() to TypeName.BOOLEAN,
+ TypeName.BYTE.toString() to TypeName.BYTE,
+ TypeName.SHORT.toString() to TypeName.SHORT,
+ TypeName.INT.toString() to TypeName.INT,
+ TypeName.LONG.toString() to TypeName.LONG,
+ TypeName.CHAR.toString() to TypeName.CHAR,
+ TypeName.FLOAT.toString() to TypeName.FLOAT,
+ TypeName.DOUBLE.toString() to TypeName.DOUBLE
+ )
+ }
+}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/XProcessingStepTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XProcessingStepTest.kt
new file mode 100644
index 0000000..f26a7ba
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XProcessingStepTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import androidx.room.processing.testcode.MainAnnotation
+import androidx.room.processing.testcode.OtherAnnotation
+import com.google.auto.common.BasicAnnotationProcessor
+import com.google.common.truth.Truth
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import org.junit.Test
+import kotlin.reflect.KClass
+
+class XProcessingStepTest {
+ @Test
+ fun xProcessingStep() {
+ val annotatedElements = mutableMapOf<KClass<out Annotation>, String>()
+ val processingStep = object : XProcessingStep {
+ override fun process(
+ env: XProcessingEnv,
+ elementsByAnnotation: Map<KClass<out Annotation>, List<XTypeElement>>
+ ): Set<XTypeElement> {
+ elementsByAnnotation[OtherAnnotation::class]?.forEach {
+ annotatedElements[OtherAnnotation::class] = it.qualifiedName
+ }
+ elementsByAnnotation[MainAnnotation::class]?.forEach {
+ annotatedElements[MainAnnotation::class] = it.qualifiedName
+ }
+ return emptySet()
+ }
+
+ override fun annotations(): Set<KClass<out Annotation>> {
+ return setOf(OtherAnnotation::class, MainAnnotation::class)
+ }
+ }
+ val mainProcessor = object : BasicAnnotationProcessor() {
+ override fun initSteps(): Iterable<ProcessingStep> {
+ return listOf(
+ processingStep.asAutoCommonProcessor(
+ XProcessingEnv.create(processingEnv)
+ )
+ )
+ }
+ }
+ val main = JavaFileObjects.forSourceString(
+ "foo.bar.Main", """
+ package foo.bar;
+ import androidx.room.processing.testcode.*;
+ @MainAnnotation(
+ typeList = {},
+ singleType = Object.class,
+ intMethod = 3,
+ singleOtherAnnotation = @OtherAnnotation("y")
+ )
+ class Main {
+ }
+ """.trimIndent()
+ )
+ val other = JavaFileObjects.forSourceString(
+ "foo.bar.Other", """
+ package foo.bar;
+ import androidx.room.processing.testcode.*;
+ @OtherAnnotation("x")
+ class Other {
+ }
+ """.trimIndent()
+ )
+ Truth.assertAbout(
+ JavaSourcesSubjectFactory.javaSources()
+ ).that(
+ listOf(main, other)
+ ).processedWith(
+ mainProcessor
+ ).compilesWithoutError()
+ Truth.assertThat(annotatedElements).containsExactlyEntriesIn(
+ mapOf(
+ MainAnnotation::class to "foo.bar.Main",
+ OtherAnnotation::class to "foo.bar.Other"
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/XTypeTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XTypeTest.kt
new file mode 100644
index 0000000..9ef38f7
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/XTypeTest.kt
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing
+
+import androidx.room.processing.util.Source
+import androidx.room.processing.util.getDeclaredMethod
+import androidx.room.processing.util.getField
+import androidx.room.processing.util.getMethod
+import androidx.room.processing.util.runProcessorTest
+import androidx.room.processing.util.runProcessorTestForFailedCompilation
+import com.google.common.truth.Truth.assertThat
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.ParameterizedTypeName
+import com.squareup.javapoet.TypeName
+import com.squareup.javapoet.TypeVariableName
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class XTypeTest {
+ @Test
+ fun declaredTypeArguments() {
+ val parent = Source.java(
+ "foo.bar.Parent", """
+ package foo.bar;
+ import java.io.InputStream;
+ import java.util.Set;
+ class Parent<InputStreamType extends InputStream> {
+ public void wildcardParam(Set<?> param1) {}
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ sources = listOf(parent)
+ ) {
+ val type = it.processingEnv.requireType("foo.bar.Parent") as XDeclaredType
+ val className = ClassName.get("foo.bar", "Parent")
+ assertThat(type.typeName).isEqualTo(
+ ParameterizedTypeName.get(
+ className,
+ ClassName.get("", "InputStreamType")
+ )
+ )
+ assertThat(type.isDeclared()).isTrue()
+
+ val typeArguments = type.typeArguments
+ assertThat(typeArguments).hasSize(1)
+ typeArguments.first().let { firstType ->
+ assertThat(firstType.isDeclared()).isFalse()
+ val expected = TypeVariableName.get(
+ "InputStreamType",
+ ClassName.get("java.io", "InputStream")
+ )
+ assertThat(firstType.typeName).isEqualTo(expected)
+ }
+
+ type.asTypeElement().getMethod("wildcardParam").let { method ->
+ val wildcardParam = method.parameters.first()
+ val extendsBoundOrSelf = wildcardParam.type.extendsBoundOrSelf()
+ assertThat(extendsBoundOrSelf.erasure())
+ .isEqualTo(
+ it.processingEnv.requireType("java.util.Set").erasure()
+ )
+ }
+ }
+ }
+
+ @Test
+ fun errorType() {
+ val missingTypeRef = Source.java(
+ "foo.bar.Baz",
+ """
+ package foo.bar;
+ public class Baz {
+ NotExistingType badField;
+ NotExistingType badMethod() {
+ throw new RuntimeException("Stub");
+ }
+ }
+ """.trimIndent()
+ )
+ runProcessorTestForFailedCompilation(
+ sources = listOf(missingTypeRef)
+ ) {
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ element.getField("badField").let { field ->
+ assertThat(field.type.isError()).isTrue()
+ assertThat(field.type.typeName).isEqualTo(
+ ClassName.get("", "NotExistingType")
+ )
+ }
+ element.getDeclaredMethod("badMethod").let { method ->
+ assertThat(method.returnType.isError()).isTrue()
+ assertThat(method.returnType.typeName).isEqualTo(
+ ClassName.get("", "NotExistingType")
+ )
+ }
+ }
+ }
+
+ @Test
+ fun sameType() {
+ val subject = Source.java(
+ "foo.bar.Baz", """
+ package foo.bar;
+ interface Baz {
+ void method(String... inputs);
+ }
+ """.trimIndent()
+ )
+ runProcessorTest(
+ sources = listOf(subject)
+ ) {
+ val type = it.processingEnv.requireType("foo.bar.Baz")
+ val list = it.processingEnv.requireType("java.util.List")
+ val string = it.processingEnv.requireType("java.lang.String")
+ assertThat(type.isSameType(type)).isTrue()
+ assertThat(type.isSameType(list)).isFalse()
+ assertThat(list.isSameType(string)).isFalse()
+ }
+ }
+
+ @Test
+ fun isCollection() {
+ runProcessorTest {
+ it.processingEnv.requireType("java.util.List").let { list ->
+ assertThat(list.isCollection()).isTrue()
+ }
+ it.processingEnv.requireType("java.util.Set").let { list ->
+ assertThat(list.isCollection()).isTrue()
+ }
+ it.processingEnv.requireType("java.util.Map").let { list ->
+ assertThat(list.isCollection()).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun toStringMatchesUnderlyingElement() {
+ runProcessorTest {
+ it.processingEnv.requireType("java.lang.Integer").let { map ->
+ assertThat(map.toString()).isEqualTo("java.lang.Integer")
+ }
+ }
+ }
+
+ @Test
+ fun errorTypeForSuper() {
+ val missingTypeRef = Source.java(
+ "foo.bar.Baz",
+ """
+ package foo.bar;
+ public class Baz extends IDontExist {
+ NotExistingType foo() {
+ throw new RuntimeException("Stub");
+ }
+ }
+ """.trimIndent()
+ )
+ runProcessorTestForFailedCompilation(
+ sources = listOf(missingTypeRef)
+ ) {
+ val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
+ assertThat(element.superType?.isError()).isTrue()
+ }
+ }
+
+ @Test
+ fun defaultValues() {
+ runProcessorTest {
+ assertThat(
+ it.processingEnv.requireType("int").defaultValue()
+ ).isEqualTo("0")
+ assertThat(
+ it.processingEnv.requireType("java.lang.String").defaultValue()
+ ).isEqualTo("null")
+ assertThat(
+ it.processingEnv.requireType("double").defaultValue()
+ ).isEqualTo("0.0")
+ assertThat(
+ it.processingEnv.requireType("float").defaultValue()
+ ).isEqualTo("0f")
+ assertThat(
+ it.processingEnv.requireType("char").defaultValue()
+ ).isEqualTo("0")
+ }
+ }
+
+ @Test
+ fun boxed() {
+ runProcessorTest {
+ assertThat(
+ it.processingEnv.requireType("int").boxed().typeName
+ ).isEqualTo(TypeName.get(java.lang.Integer::class.java))
+ assertThat(
+ it.processingEnv.requireType("java.lang.String").boxed().typeName
+ ).isEqualTo(TypeName.get(String::class.java))
+ }
+ }
+
+ @Test
+ fun erasure() {
+ runProcessorTest {
+ val subject = it.processingEnv.getDeclaredType(
+ it.processingEnv.requireTypeElement(List::class),
+ it.processingEnv.requireType(String::class)
+ )
+ val listClassName = ClassName.get(List::class.java)
+ assertThat(subject.typeName).isEqualTo(
+ ParameterizedTypeName.get(listClassName, TypeName.get(String::class.java))
+ )
+ assertThat(subject.erasure().typeName).isEqualTo(
+ listClassName
+ )
+ }
+ }
+}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/javac/kotlin/JvmDescriptorUtilsTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/javac/kotlin/JvmDescriptorUtilsTest.kt
new file mode 100644
index 0000000..4bb6c67
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/javac/kotlin/JvmDescriptorUtilsTest.kt
@@ -0,0 +1,369 @@
+/*
+ * Copyright 2020 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 androidx.room.processing.javac.kotlin
+
+import com.google.auto.common.MoreElements
+import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaFileObjects
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.RoundEnvironment
+import javax.lang.model.element.ElementKind.CONSTRUCTOR
+import javax.lang.model.element.ElementKind.FIELD
+import javax.lang.model.element.ElementKind.METHOD
+import javax.lang.model.element.TypeElement
+import javax.tools.JavaFileObject
+
+@RunWith(JUnit4::class)
+class JvmDescriptorUtilsTest {
+
+ private val describeAnnotation =
+ """
+ package androidx.room.test;
+
+ import java.lang.annotation.ElementType;
+ import java.lang.annotation.Target;
+
+ @Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR})
+ public @interface Describe { }
+ """.toJFO("androidx.room.test.Describe")
+
+ @Test
+ fun descriptor_method_simple() {
+ singleRun(
+ """
+ package androidx.room.test;
+
+ public class DummyClass {
+ @Describe
+ public void emptyMethod() {
+ }
+ }
+ """.toJFO("androidx.room.test.DummyClass")
+ ) { descriptors ->
+ assertThat(descriptors.first())
+ .isEqualTo("emptyMethod()V")
+ }
+ }
+
+ @Test
+ fun descriptor_field() {
+ singleRun(
+ """
+ package androidx.room.test;
+
+ import java.util.List;
+
+ class DummyClass<T> {
+ @Describe
+ int field1;
+
+ @Describe
+ String field2;
+
+ @Describe
+ T field3;
+
+ @Describe
+ List<String> field4;
+ }
+ """.toJFO("androidx.room.test.DummyClass")
+ ) { descriptors ->
+ assertThat(descriptors).isEqualTo(
+ setOf(
+ "field1:I",
+ "field2:Ljava/lang/String;",
+ "field3:Ljava/lang/Object;",
+ "field4:Ljava/util/List;"
+ )
+ )
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun descriptor_method_erasured() {
+ singleRun(
+ """
+ package androidx.room.test;
+
+ import java.util.ArrayList;
+ import java.util.Collection;
+ import java.util.List;
+ import java.util.Map;
+
+ class DummyClass<T> {
+ @Describe
+ void method1(T something) { }
+
+ @Describe
+ T method2() { return null; }
+
+ @Describe
+ List<? extends String> method3() { return null; }
+
+ @Describe
+ Map<T, String> method4() { return null; }
+
+ @Describe
+ ArrayList<Map<T, String>> method5() { return null; }
+
+ @Describe
+ static <I, O extends I> O method6(I input) { return null; }
+
+ @Describe
+ static <I, O extends String> O method7(I input) { return null; }
+
+ @Describe
+ static <P extends Collection & Comparable> P method8() { return null; }
+
+ @Describe
+ static <P extends String & List<Character>> P method9() { return null; }
+ }
+ """.toJFO("androidx.room.test.DummyClass")
+ ) { descriptors ->
+ assertThat(descriptors).isEqualTo(
+ setOf(
+ "method1(Ljava/lang/Object;)V",
+ "method2()Ljava/lang/Object;",
+ "method3()Ljava/util/List;",
+ "method4()Ljava/util/Map;",
+ "method5()Ljava/util/ArrayList;",
+ "method6(Ljava/lang/Object;)Ljava/lang/Object;",
+ "method7(Ljava/lang/Object;)Ljava/lang/String;",
+ "method8()Ljava/util/Collection;",
+ "method9()Ljava/lang/String;"
+ )
+ )
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun descriptor_method_primitiveParams() {
+ singleRun(
+ """
+ package androidx.room.test;
+
+ class DummyClass {
+ @Describe
+ void method1(boolean yesOrNo, int number) { }
+
+ @Describe
+ byte method2(char letter) { return 0; }
+
+ @Describe
+ void method3(double realNumber1, float realNummber2) { }
+
+ @Describe
+ void method4(long bigNumber, short littlerNumber) { }
+ }
+ """.toJFO("androidx.room.test.DummyClass")
+ ) { descriptors ->
+ assertThat(descriptors)
+ .isEqualTo(setOf("method1(ZI)V", "method2(C)B", "method3(DF)V", "method4(JS)V"))
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun descriptor_method_classParam_javaTypes() {
+ singleRun(
+ """
+ package androidx.room.test;
+
+ import java.util.ArrayList;
+ import java.util.List;
+ import java.util.Map;
+
+ class DummyClass {
+ @Describe
+ void method1(Object something) { }
+
+ @Describe
+ Object method2() { return null; }
+
+ @Describe
+ List<String> method3(ArrayList<Integer> list) { return null; }
+
+ @Describe
+ Map<String, Object> method4() { return null; }
+ }
+ """.toJFO("androidx.room.test.DummyClass")
+ ) { descriptors ->
+ assertThat(descriptors).isEqualTo(
+ setOf(
+ "method1(Ljava/lang/Object;)V",
+ "method2()Ljava/lang/Object;",
+ "method3(Ljava/util/ArrayList;)Ljava/util/List;",
+ "method4()Ljava/util/Map;"
+ )
+ )
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun descriptor_method_classParam_testClass() {
+ val extraJfo =
+ """
+ package androidx.room.test;
+
+ class DataClass { }
+ """.toJFO("androidx.room.test.DataClass")
+
+ singleRun(
+ """
+ package androidx.room.test;
+
+ class DummyClass {
+ @Describe
+ void method1(DataClass data) { }
+
+ @Describe
+ DataClass method2() { return null; }
+ }
+ """.toJFO("androidx.room.test.DummyClass"), extraJfo
+ ) { descriptors ->
+ assertThat(descriptors).isEqualTo(
+ setOf(
+ "method1(Landroidx/room/test/DataClass;)V",
+ "method2()Landroidx/room/test/DataClass;"
+ )
+ )
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun descriptor_method_classParam_innerTestClass() {
+ val extraJfo =
+ """
+ package androidx.room.test;
+
+ class DataClass {
+
+ class MemberInnerData { }
+
+ static class StaticInnerData { }
+
+ enum EnumData {
+ VALUE1, VALUE2
+ }
+ }
+ """.toJFO("androidx.room.test.DataClass")
+
+ singleRun(
+ """
+ package androidx.room.test;
+
+ class DummyClass {
+ @Describe
+ void method1(DataClass.MemberInnerData data) { }
+
+ @Describe
+ void method2(DataClass.StaticInnerData data) { }
+
+ @Describe
+ void method3(DataClass.EnumData enumData) { }
+
+ @Describe
+ DataClass.StaticInnerData method4() { return null; }
+ }
+ """.toJFO("androidx.room.test.DummyClass"), extraJfo
+ ) { descriptors ->
+ assertThat(descriptors).isEqualTo(
+ setOf(
+ "method1(Landroidx/room/test/DataClass\$MemberInnerData;)V",
+ "method2(Landroidx/room/test/DataClass\$StaticInnerData;)V",
+ "method3(Landroidx/room/test/DataClass\$EnumData;)V",
+ "method4()Landroidx/room/test/DataClass\$StaticInnerData;"
+ )
+ )
+ }.compilesWithoutError()
+ }
+
+ @Test
+ fun descriptor_method_arrayParams() {
+ val extraJfo =
+ """
+ package androidx.room.test;
+
+ class DataClass { }
+ """.toJFO("androidx.room.test.DataClass")
+
+ singleRun(
+ """
+ package androidx.room.test;
+
+ class DummyClass {
+ @Describe
+ void method1(DataClass[] data) { }
+
+ @Describe
+ DataClass[] method2() { return null; }
+
+ @Describe
+ void method3(int[] array) { }
+
+ @Describe
+ void method4(int... array) { }
+ }
+ """.toJFO("androidx.room.test.DummyClass"), extraJfo
+ ) { descriptors ->
+ assertThat(descriptors).isEqualTo(
+ setOf(
+ "method1([Landroidx/room/test/DataClass;)V",
+ "method2()[Landroidx/room/test/DataClass;",
+ "method3([I)V",
+ "method4([I)V"
+ )
+ )
+ }.compilesWithoutError()
+ }
+
+ private fun String.toJFO(qName: String): JavaFileObject =
+ JavaFileObjects.forSourceLines(qName, this)
+
+ @Suppress("UnstableApiUsage")
+ private fun singleRun(
+ vararg jfo: JavaFileObject,
+ handler: (Set<String>) -> Unit
+ ): CompileTester {
+ return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+ .that(listOf(describeAnnotation) + jfo)
+ .processedWith(object : AbstractProcessor() {
+ override fun process(
+ annotations: Set<TypeElement>,
+ roundEnv: RoundEnvironment
+ ): Boolean {
+ roundEnv.getElementsAnnotatedWith(annotations.first()).map { element ->
+ when (element.kind) {
+ FIELD -> MoreElements.asVariable(element).descriptor()
+ METHOD, CONSTRUCTOR -> MoreElements.asExecutable(element).descriptor()
+ else -> error("Unsupported element to describe.")
+ }
+ }.toSet().let(handler)
+ return true
+ }
+
+ override fun getSupportedOptions(): Set<String> {
+ return setOf("androidx.room.test.Describe")
+ }
+ })
+ }
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/javac/kotlin/KotlinMetadataElementTest.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/javac/kotlin/KotlinMetadataElementTest.kt
new file mode 100644
index 0000000..d60932e
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/javac/kotlin/KotlinMetadataElementTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2020 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 androidx.room.processing.javac.kotlin
+
+import androidx.room.processing.javac.JavacProcessingEnv
+import androidx.room.processing.util.runProcessorTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.AssumptionViolatedException
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import javax.annotation.processing.ProcessingEnvironment
+import javax.lang.model.element.TypeElement
+import javax.lang.model.util.ElementFilter
+import kotlin.reflect.KClass
+
+@RunWith(JUnit4::class)
+class KotlinMetadataElementTest {
+
+ @Test
+ fun getParameterNames() {
+ simpleRun { processingEnv ->
+ val (testClassElement, metadataElement) = getMetadataElement(
+ processingEnv,
+ TestData::class
+ )
+ assertThat(testClassElement.getDeclaredMethods()
+ .first { it.simpleName.toString() == "functionWithParams" }
+ .let { metadataElement.getParameterNames(it) }
+ ).isEqualTo(
+ listOf("param1", "yesOrNo", "number")
+ )
+ }
+ }
+
+ @Test
+ fun findPrimaryConstructorSignature() {
+ simpleRun { invocation ->
+ val (testClassElement, metadataElement) = getMetadataElement(
+ invocation,
+ TestData::class
+ )
+ assertThat(
+ testClassElement.getConstructors().map {
+ val desc = it.descriptor()
+ desc to (desc == metadataElement.findPrimaryConstructorSignature())
+ }
+ ).containsExactly(
+ "<init>(Ljava/lang/String;)V" to true,
+ "<init>()V" to false
+ )
+ }
+ }
+
+ @Test
+ fun isSuspendFunction() {
+ simpleRun { invocation ->
+ val (testClassElement, metadataElement) = getMetadataElement(
+ invocation,
+ TestData::class
+ )
+ assertThat(testClassElement.getDeclaredMethods().map {
+ it.simpleName.toString() to metadataElement.isSuspendFunction(it)
+ }).containsExactly(
+ "emptyFunction" to false,
+ "suspendFunction" to true,
+ "functionWithParams" to false,
+ "getConstructorParam" to false
+ )
+ }
+ }
+
+ @Test
+ fun isObject() {
+ simpleRun { invocation ->
+ val (_, objectTypeMetadata) = getMetadataElement(invocation, ObjectType::class)
+ assertThat(objectTypeMetadata.isObject()).isTrue()
+ val (_, testDataMetadata) = getMetadataElement(invocation, TestData::class)
+ assertThat(testDataMetadata.isObject()).isFalse()
+ }
+ }
+
+ private fun TypeElement.getDeclaredMethods() = ElementFilter.methodsIn(enclosedElements)
+
+ private fun TypeElement.getConstructors() = ElementFilter.constructorsIn(enclosedElements)
+
+ private fun simpleRun(
+ handler: (ProcessingEnvironment) -> Unit
+ ) {
+ runProcessorTest {
+ if (it.processingEnv !is JavacProcessingEnv) {
+ throw AssumptionViolatedException("This test only works for java/kapt compilation")
+ }
+ handler(it.processingEnv.delegate)
+ }
+ }
+
+ private fun getMetadataElement(processingEnv: ProcessingEnvironment, klass: KClass<*>) =
+ processingEnv.elementUtils.getTypeElement(klass.java.canonicalName).let {
+ it to KotlinMetadataElement.createFor(it)!!
+ }
+
+ @Suppress("unused")
+ private class TestData(val constructorParam: String) {
+
+ constructor() : this("anything")
+
+ fun emptyFunction() {}
+
+ @Suppress("RedundantSuspendModifier")
+ suspend fun suspendFunction() {
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ fun functionWithParams(param1: String, yesOrNo: Boolean, number: Int) {
+ }
+ }
+
+ object ObjectType {
+ val foo: String = ""
+ }
+}
\ No newline at end of file
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/testcode/KotlinTestClass.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/testcode/KotlinTestClass.kt
new file mode 100644
index 0000000..b3b17a7
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/testcode/KotlinTestClass.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+@file:Suppress("unused")
+
+package androidx.room.processing.testcode
+
+@Suppress("UNUSED_PARAMETER", "RedundantSuspendModifier")
+class KotlinTestClass {
+ suspend fun mySuspendMethod(
+ param1: String
+ ) {
+ }
+}
+
+object KotlinTestObject
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/testcode/MainAnnotation.java b/room/compiler-xprocessing/src/test/java/androidx/room/processing/testcode/MainAnnotation.java
new file mode 100644
index 0000000..f587f46
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/testcode/MainAnnotation.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.testcode;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * used in compilation tests
+ */
+@SuppressWarnings("unused")
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface MainAnnotation {
+ Class<?>[] typeList();
+
+ Class<?> singleType();
+
+ int intMethod();
+
+ boolean boolMethodWithDefault() default true;
+
+ OtherAnnotation[] otherAnnotationArray() default {};
+
+ OtherAnnotation singleOtherAnnotation();
+}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/testcode/OtherAnnotation.java b/room/compiler-xprocessing/src/test/java/androidx/room/processing/testcode/OtherAnnotation.java
new file mode 100644
index 0000000..e1468e5
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/testcode/OtherAnnotation.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.testcode;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * used in compilation tests
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface OtherAnnotation {
+ String value();
+}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/util/ProcessorTestExt.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/util/ProcessorTestExt.kt
new file mode 100644
index 0000000..2044072
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/util/ProcessorTestExt.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.util
+
+import androidx.room.processing.SyntheticJavacProcessor
+import com.google.common.truth.Truth
+import com.google.testing.compile.CompileTester
+import com.google.testing.compile.JavaSourcesSubjectFactory
+import com.tschuchort.compiletesting.KotlinCompilation
+
+private fun compileSources(
+ sources: List<Source>,
+ handler: (TestInvocation) -> Unit
+): Pair<SyntheticJavacProcessor, CompileTester> {
+ val syntheticJavacProcessor = SyntheticJavacProcessor(handler)
+ return syntheticJavacProcessor to Truth.assertAbout(
+ JavaSourcesSubjectFactory.javaSources()
+ ).that(
+ sources.map {
+ it.toJFO()
+ }
+ ).processedWith(
+ syntheticJavacProcessor
+ )
+}
+
+private fun compileWithKapt(
+ sources: List<Source>,
+ handler: (TestInvocation) -> Unit
+): Pair<SyntheticJavacProcessor, KotlinCompilation> {
+ val syntheticJavacProcessor = SyntheticJavacProcessor(handler)
+ val compilation = KotlinCompilation()
+ sources.forEach {
+ compilation.workingDir.resolve("sources")
+ .resolve(it.relativePath())
+ .parentFile
+ .mkdirs()
+ }
+ compilation.sources = sources.map {
+ it.toKotlinSourceFile()
+ }
+ compilation.annotationProcessors = listOf(syntheticJavacProcessor)
+ compilation.inheritClassPath = true
+ compilation.verbose = false
+
+ return syntheticJavacProcessor to compilation
+}
+
+fun runProcessorTest(
+ sources: List<Source> = emptyList(),
+ handler: (TestInvocation) -> Unit
+) {
+ @Suppress("NAME_SHADOWING")
+ val sources = if (sources.isEmpty()) {
+ // synthesize a source to trigger compilation
+ listOf(Source.java("foo.bar.SyntheticSource", """
+ package foo.bar;
+ public class SyntheticSource {}
+ """.trimIndent()))
+ } else {
+ sources
+ }
+ // we can compile w/ javac only if all code is in java
+ if (sources.all { it is Source.JavaSource }) {
+ val (syntheticJavacProcessor, compileTester) = compileSources(sources, handler)
+ compileTester.compilesWithoutError()
+ syntheticJavacProcessor.throwIfFailed()
+ }
+
+ // now run with kapt
+ val (kaptProcessor, kotlinCompilation) = compileWithKapt(sources, handler)
+ val compilationResult = kotlinCompilation.compile()
+ Truth.assertThat(compilationResult.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
+ kaptProcessor.throwIfFailed()
+}
+
+fun runProcessorTestForFailedCompilation(
+ sources: List<Source>,
+ handler: (TestInvocation) -> Unit
+) {
+ val (syntheticJavacProcessor, compileTester) = compileSources(sources, handler)
+ compileTester.failsToCompile()
+ syntheticJavacProcessor.throwIfFailed()
+
+ // now run with kapt
+ val (kaptProcessor, kotlinCompilation) = compileWithKapt(sources, handler)
+ val compilationResult = kotlinCompilation.compile()
+ Truth.assertThat(compilationResult.exitCode).isNotEqualTo(KotlinCompilation.ExitCode.OK)
+ kaptProcessor.throwIfFailed()
+}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/util/Source.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/util/Source.kt
new file mode 100644
index 0000000..868f4ba
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/util/Source.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.util
+
+import com.google.testing.compile.JavaFileObjects
+import com.tschuchort.compiletesting.SourceFile
+import org.intellij.lang.annotations.Language
+import javax.tools.JavaFileObject
+
+/**
+ * Common abstraction for test sources in kotlin and java
+ */
+sealed class Source {
+ abstract fun toJFO(): JavaFileObject
+ abstract fun toKotlinSourceFile(): SourceFile
+
+ // we need this for kotlin compile testing as it doesn't create directories
+ abstract fun relativePath(): String
+
+ class JavaSource(
+ val qName: String,
+ val contents: String
+ ) : Source() {
+ override fun toJFO(): JavaFileObject {
+ return JavaFileObjects.forSourceString(
+ qName,
+ contents
+ )
+ }
+
+ override fun toKotlinSourceFile(): SourceFile {
+ return SourceFile.java(
+ relativePath(),
+ contents
+ )
+ }
+
+ override fun relativePath(): String {
+ return qName.replace(".", "/") + ".java"
+ }
+ }
+
+ class KotlinSource(
+ val filePath: String,
+ val contents: String
+ ) : Source() {
+ override fun toJFO(): JavaFileObject {
+ throw IllegalStateException("cannot include kotlin code in javac compilation")
+ }
+
+ override fun toKotlinSourceFile(): SourceFile {
+ return SourceFile.kotlin(
+ filePath,
+ contents
+ )
+ }
+
+ override fun relativePath(): String = filePath
+ }
+
+ companion object {
+ fun java(
+ qName: String,
+ @Language("java")
+ code: String
+ ): Source {
+ return JavaSource(
+ qName,
+ code
+ )
+ }
+
+ fun kotlin(
+ filePath: String,
+ @Language("kotlin")
+ code: String
+ ): Source {
+ return KotlinSource(
+ filePath,
+ code
+ )
+ }
+ }
+}
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/util/TestExtensions.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/util/TestExtensions.kt
new file mode 100644
index 0000000..22e2859
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/util/TestExtensions.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.util
+
+import androidx.room.processing.XExecutableElement
+import androidx.room.processing.XTypeElement
+
+fun XTypeElement.getField(name: String) = getAllFieldsIncludingPrivateSupers().first {
+ it.name == name
+}
+
+fun XTypeElement.getDeclaredMethod(name: String) = getDeclaredMethods().firstOrNull {
+ it.name == name
+} ?: throw AssertionError("cannot find method with name $name")
+
+fun XTypeElement.getMethod(name: String) = getAllMethods().firstOrNull {
+ it.name == name
+} ?: throw AssertionError("cannot find method with name $name")
+
+fun XExecutableElement.getParameter(name: String) = parameters.firstOrNull {
+ it.name == name
+} ?: throw AssertionError("cannot find parameter with name $name")
diff --git a/room/compiler-xprocessing/src/test/java/androidx/room/processing/util/TestInvocation.kt b/room/compiler-xprocessing/src/test/java/androidx/room/processing/util/TestInvocation.kt
new file mode 100644
index 0000000..ab1f69e
--- /dev/null
+++ b/room/compiler-xprocessing/src/test/java/androidx/room/processing/util/TestInvocation.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 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 androidx.room.processing.util
+
+import androidx.room.processing.XProcessingEnv
+
+class TestInvocation(
+ val processingEnv: XProcessingEnv
+)
diff --git a/settings.gradle b/settings.gradle
index 90be06a..6434bb1 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -242,6 +242,7 @@
includeProject(":paging:paging-runtime-ktx", "paging/runtime/ktx")
includeProject(":paging:paging-rxjava2", "paging/rxjava2")
includeProject(":paging:paging-rxjava2-ktx", "paging/rxjava2/ktx")
+includeProject(":paging:paging-rxjava3", "paging/rxjava3")
includeProject(":paging:paging-guava", "paging/guava")
includeProject(":paging:samples", "paging/samples")
includeProject(":palette:palette", "palette/palette")
@@ -263,6 +264,7 @@
includeProject(":room:room-benchmark", "room/benchmark")
includeProject(":room:room-common", "room/common")
includeProject(":room:room-compiler", "room/compiler")
+includeProject(":room:room-compiler-xprocessing", "room/compiler-xprocessing")
includeProject(":room:room-guava", "room/guava")
includeProject(":room:room-ktx", "room/ktx")
includeProject(":room:room-migration", "room/migration")
diff --git a/ui/ui-internal-lint-checks/src/main/java/androidx/ui/lint/ComposeIssueRegistry.kt b/ui/ui-internal-lint-checks/src/main/java/androidx/ui/lint/ComposeIssueRegistry.kt
index 178fd66..2ccc7b7 100644
--- a/ui/ui-internal-lint-checks/src/main/java/androidx/ui/lint/ComposeIssueRegistry.kt
+++ b/ui/ui-internal-lint-checks/src/main/java/androidx/ui/lint/ComposeIssueRegistry.kt
@@ -25,6 +25,9 @@
override val minApi = CURRENT_API
override val api = 8
override val issues get(): List<Issue> {
- return listOf(UnnecessaryLambdaCreationDetector.ISSUE) + AndroidXIssueRegistry.Issues
+ return listOf(
+ UnnecessaryLambdaCreationDetector.ISSUE,
+ PackageNameMigrationDetector.ISSUE
+ ) + AndroidXIssueRegistry.Issues
}
}
diff --git a/ui/ui-internal-lint-checks/src/main/java/androidx/ui/lint/PackageNameMigrationDetector.kt b/ui/ui-internal-lint-checks/src/main/java/androidx/ui/lint/PackageNameMigrationDetector.kt
new file mode 100644
index 0000000..1846910
--- /dev/null
+++ b/ui/ui-internal-lint-checks/src/main/java/androidx/ui/lint/PackageNameMigrationDetector.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2020 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.ui.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import org.jetbrains.uast.UFile
+
+/**
+ * Simple lint check that prevents using old package names (map defined in
+ * [PackageNameMigrationMap]) after a library has migrated to the new name.
+ *
+ * TODO: b/160233169 remove this lint check after the migration has finished.
+ */
+class PackageNameMigrationDetector : Detector(), SourceCodeScanner {
+ override fun getApplicableUastTypes() = listOf(UFile::class.java)
+
+ override fun createUastHandler(context: JavaContext) = object : UElementHandler() {
+ override fun visitFile(node: UFile) {
+ val packageName = node.packageName
+
+ if (packageName in PackageNameMigrationMap) {
+ val newPackageName = PackageNameMigrationMap[packageName]
+ context.report(
+ ISSUE,
+ node,
+ context.getLocation(node),
+ "The package name '$packageName' has been migrated to '$newPackageName', " +
+ "please update the package name of this file accordingly."
+ )
+ }
+ }
+ }
+ companion object {
+ private val PackageNameMigrationMap: Map<String, String> = mapOf(
+ // placeholder package name used in PackageNameMigrationDetectorTest, since the
+ // migration has not started yet
+ "androidx.ui.foo" to "androidx.compose.foo"
+ )
+
+ val ISSUE = Issue.create(
+ "PackageNameMigration",
+ "Using an old package name that has recently been migrated to androidx.compose",
+ "As part of a large migration from androidx.ui to androidx.compose, package names " +
+ "across all libraries are being refactored. If you are seeing this Lint " +
+ "error, you are adding new files to the old package name, once the rest of " +
+ "the library has migrated to the new package name.",
+ Category.PERFORMANCE, 5, Severity.ERROR,
+ Implementation(
+ PackageNameMigrationDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+ }
+}
diff --git a/ui/ui-internal-lint-checks/src/test/java/androidx/ui/lint/PackageNameMigrationDetectorTest.kt b/ui/ui-internal-lint-checks/src/test/java/androidx/ui/lint/PackageNameMigrationDetectorTest.kt
new file mode 100644
index 0000000..41344a6
--- /dev/null
+++ b/ui/ui-internal-lint-checks/src/test/java/androidx/ui/lint/PackageNameMigrationDetectorTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2020 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.
+ */
+
+@file:Suppress("UnstableApiUsage")
+
+package androidx.ui.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/* ktlint-disable max-line-length */
+@RunWith(JUnit4::class)
+/**
+ * Test for [PackageNameMigrationDetector]
+ *
+ * TODO: b/160233169 remove this lint check after the migration has finished.
+ */
+class PackageNameMigrationDetectorTest : LintDetectorTest() {
+ override fun getDetector(): Detector = PackageNameMigrationDetector()
+
+ override fun getIssues(): MutableList<Issue> = mutableListOf(PackageNameMigrationDetector.ISSUE)
+
+ @Test
+ fun oldPackageShouldFail() {
+ lint().files(
+ kotlin(
+ """
+ package androidx.ui.foo
+
+ fun someApi() {}
+ """
+ )
+ )
+ .run()
+ .expect(
+ """
+src/androidx/ui/foo/test.kt:1: Error: The package name 'androidx.ui.foo' has been migrated to 'androidx.compose.foo', please update the package name of this file accordingly. [PackageNameMigration]
+
+^
+1 errors, 0 warnings
+ """
+ )
+ }
+
+ @Test
+ fun newPackageShouldPass() {
+ lint().files(
+ kotlin(
+ """
+ package androidx.compose.foo
+
+ fun someApi() {}
+ """
+ )
+ )
+ .run()
+ .expectClean()
+ }
+}
+/* ktlint-enable max-line-length */
diff --git a/ui/ui-material/src/main/java/androidx/ui/material/Surface.kt b/ui/ui-material/src/main/java/androidx/ui/material/Surface.kt
index 17b34a6..7f129d4 100644
--- a/ui/ui-material/src/main/java/androidx/ui/material/Surface.kt
+++ b/ui/ui-material/src/main/java/androidx/ui/material/Surface.kt
@@ -18,10 +18,11 @@
import androidx.compose.Composable
import androidx.compose.Providers
+import androidx.ui.core.DensityAmbient
import androidx.ui.core.Layout
import androidx.ui.core.Modifier
import androidx.ui.core.clip
-import androidx.ui.core.drawShadow
+import androidx.ui.core.drawLayer
import androidx.ui.core.zIndex
import androidx.ui.foundation.Border
import androidx.ui.foundation.ContentColorAmbient
@@ -87,8 +88,9 @@
elevation: Dp = 0.dp,
content: @Composable () -> Unit
) {
+ val elevationPx = with(DensityAmbient.current) { elevation.toPx() }
SurfaceLayout(
- modifier.drawShadow(elevation = elevation, shape = shape, clip = false)
+ modifier.drawLayer(shadowElevation = elevationPx, shape = shape)
.zIndex(elevation.value)
.plus(if (border != null) Modifier.drawBorder(border, shape) else Modifier)
.background(
diff --git a/ui/ui-text/src/commonMain/kotlin/androidx/ui/text/CoreText.kt b/ui/ui-text/src/commonMain/kotlin/androidx/ui/text/CoreText.kt
index 97f1d54..2967eee 100644
--- a/ui/ui-text/src/commonMain/kotlin/androidx/ui/text/CoreText.kt
+++ b/ui/ui-text/src/commonMain/kotlin/androidx/ui/text/CoreText.kt
@@ -31,6 +31,7 @@
import androidx.ui.core.LayoutCoordinates
import androidx.ui.core.Modifier
import androidx.ui.core.drawBehind
+import androidx.ui.core.drawLayer
import androidx.ui.core.globalPosition
import androidx.ui.core.onPositioned
import androidx.ui.core.selection.Selectable
@@ -126,7 +127,7 @@
Layout(
children = { InlineChildren(text, inlineComposables) },
- modifier = modifier.drawBehind {
+ modifier = modifier.drawLayer().drawBehind {
state.layoutResult?.let { layoutResult ->
drawCanvas { canvas, _ ->
state.selectionRange?.let {
diff --git a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ComposeViewAdapterTest.kt b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ComposeViewAdapterTest.kt
index 430fdb4..d35e3e58 100644
--- a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ComposeViewAdapterTest.kt
+++ b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/ComposeViewAdapterTest.kt
@@ -71,9 +71,6 @@
// Verify that valid line numbers are being recorded
assertTrue(viewInfos.map { it.lineNumber }.all { it > 0 })
// Verify that method names are being captured
- assertTrue(viewInfos.map { it.methodName }.all {
- it.startsWith("androidx.ui.tooling.")
- })
}
}
diff --git a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/inspector/LayoutInspectorTreeTest.kt b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/inspector/LayoutInspectorTreeTest.kt
index 6ece044..9be0e70 100644
--- a/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/inspector/LayoutInspectorTreeTest.kt
+++ b/ui/ui-tooling/src/androidTest/java/androidx/ui/tooling/inspector/LayoutInspectorTreeTest.kt
@@ -215,6 +215,7 @@
name = "CoreText",
fileName = "CoreText.kt",
function = "androidx.ui.text.CoreTextKt.CoreText",
+ isRenderNode = true,
left = 0.0.dp, top = 0.0.dp, width = 70.5.dp, height = 18.9.dp
) {
parameter(name = "maxLines", type = ParameterType.Int32, value = 2147483647)
@@ -301,6 +302,7 @@
name = "SurfaceLayout",
fileName = "Surface.kt",
function = "androidx.ui.material.SurfaceKt.Surface",
+ isRenderNode = true,
left = 0.0.dp, top = 18.9.dp, width = 64.0.dp, height = 36.0.dp,
children = listOf("Box")
)
@@ -405,6 +407,7 @@
name = "CoreText",
fileName = "CoreText.kt",
function = "androidx.ui.text.CoreTextKt.CoreText",
+ isRenderNode = true,
left = 21.8.dp, top = 27.6.dp, width = 20.4.dp, height = 18.9.dp
) {
parameter(name = "maxLines", type = ParameterType.Int32, value = 2147483647)
diff --git a/ui/ui-tooling/src/main/java/androidx/ui/tooling/inspector/LayoutInspectorTree.kt b/ui/ui-tooling/src/main/java/androidx/ui/tooling/inspector/LayoutInspectorTree.kt
index 46f9062..d85b518 100644
--- a/ui/ui-tooling/src/main/java/androidx/ui/tooling/inspector/LayoutInspectorTree.kt
+++ b/ui/ui-tooling/src/main/java/androidx/ui/tooling/inspector/LayoutInspectorTree.kt
@@ -221,7 +221,11 @@
}
private fun getRenderNode(group: Group): Long =
- group.modifierInfo.mapNotNull { (it.extra as? OwnedLayer)?.layerId }.singleOrNull() ?: 0
+ group.modifierInfo.asSequence()
+ .map { it.extra }
+ .filterIsInstance<OwnedLayer>()
+ .map { it.layerId }
+ .firstOrNull() ?: 0
private fun addParameters(parameters: List<ParameterInformation>, node: MutableInspectorNode) =
parameters.forEach { addParameter(it, node) }
diff --git a/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/ComposeViewAdapter.kt b/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/ComposeViewAdapter.kt
index b1d0f4e9..4ffa2e2 100644
--- a/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/ComposeViewAdapter.kt
+++ b/ui/ui-tooling/src/main/java/androidx/ui/tooling/preview/ComposeViewAdapter.kt
@@ -64,7 +64,6 @@
data class ViewInfo(
val fileName: String,
val lineNumber: Int,
- val methodName: String,
val bounds: IntBounds,
val location: SourceLocation?,
val children: List<ViewInfo>
@@ -77,26 +76,12 @@
override fun toString(): String =
"""($fileName:$lineNumber,
|bounds=(top=${bounds.top}, left=${bounds.left},
+ |location=${location?.let { "(${it.offset}L${it.length}"} ?: "<none>" }
|bottom=${bounds.bottom}, right=${bounds.right}),
|childrenCount=${children.size})""".trimMargin()
}
/**
- * Regular expression that matches and extracts the key information as serialized in
- * [KeySourceInfo#recordSourceKeyInfo]. The expression supports two formats for backwards
- * compatibility:
- *
- * - fileName:lineNumber
- * - methodName (fileName:lineNumber)
- *
- * API <=21 does not support named regex but for documentation purposes, the named version
- * of the regex would be:
- * `(?<method>[\w\\.$]*?)\s?\(?(?<fileName>[\w.]+):(?<lineNumber>\d+)\)?`
- */
-private val KEY_INFO_REGEX =
- """([\w\\.$]*?)\s?\(?([\w.]+):(\d+)\)?""".toRegex()
-
-/**
* View adapter that renders a `@Composable`. The `@Composable` is found by
* reading the `tools:composableName` attribute that contains the FQN. Additional attributes can
* be used to customize the behaviour of this view:
@@ -190,14 +175,10 @@
.filter { !it.isNullGroup() }
.map { it.toViewInfo() }
- val match = KEY_INFO_REGEX.matchEntire(key as? String ?: "")
- ?: return ViewInfo("", -1, "", box, location, childrenViewInfo)
-
// TODO: Use group names instead of indexing once it's supported
return ViewInfo(
- match.groups[2]?.value ?: "",
- match.groups[3]?.value?.toInt() ?: -1,
- match.groups[1]?.value ?: "",
+ location?.sourceFile ?: "",
+ location?.lineNumber ?: -1,
box,
location,
childrenViewInfo