| /* |
| * Copyright (C) 2016 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.solver |
| |
| import COMMON |
| import androidx.paging.DataSource |
| import androidx.paging.PagingSource |
| import androidx.room.Dao |
| import androidx.room.compiler.codegen.XCodeBlock |
| import androidx.room.compiler.codegen.XTypeName |
| import androidx.room.compiler.codegen.XTypeName.Companion.PRIMITIVE_INT |
| import androidx.room.compiler.processing.XProcessingEnv |
| import androidx.room.compiler.processing.XRawType |
| import androidx.room.compiler.processing.isTypeElement |
| import androidx.room.compiler.processing.util.Source |
| import androidx.room.compiler.processing.util.XTestInvocation |
| import androidx.room.compiler.processing.util.runProcessorTest |
| import androidx.room.ext.CommonTypeNames |
| import androidx.room.ext.GuavaUtilConcurrentTypeNames |
| import androidx.room.ext.LifecyclesTypeNames |
| import androidx.room.ext.PagingTypeNames |
| import androidx.room.ext.ReactiveStreamsTypeNames |
| import androidx.room.ext.RoomTypeNames.ROOM_DB |
| import androidx.room.ext.RoomTypeNames.STRING_UTIL |
| import androidx.room.ext.RxJava2TypeNames |
| import androidx.room.ext.RxJava3TypeNames |
| import androidx.room.ext.implementsEqualsAndHashcode |
| import androidx.room.parser.SQLTypeAffinity |
| import androidx.room.processor.Context |
| import androidx.room.processor.CustomConverterProcessor |
| import androidx.room.processor.DaoProcessor |
| import androidx.room.processor.DaoProcessorTest |
| import androidx.room.processor.ProcessorErrors |
| import androidx.room.solver.binderprovider.DataSourceFactoryQueryResultBinderProvider |
| import androidx.room.solver.binderprovider.DataSourceQueryResultBinderProvider |
| import androidx.room.solver.binderprovider.ListenableFuturePagingSourceQueryResultBinderProvider |
| import androidx.room.solver.binderprovider.LiveDataQueryResultBinderProvider |
| import androidx.room.solver.binderprovider.PagingSourceQueryResultBinderProvider |
| import androidx.room.solver.binderprovider.RxJava2PagingSourceQueryResultBinderProvider |
| import androidx.room.solver.binderprovider.RxJava3PagingSourceQueryResultBinderProvider |
| import androidx.room.solver.binderprovider.RxQueryResultBinderProvider |
| import androidx.room.solver.query.parameter.CollectionQueryParameterAdapter |
| import androidx.room.solver.query.result.MultiTypedPagingSourceQueryResultBinder |
| import androidx.room.solver.shortcut.binderprovider.GuavaListenableFutureDeleteOrUpdateMethodBinderProvider |
| import androidx.room.solver.shortcut.binderprovider.GuavaListenableFutureInsertMethodBinderProvider |
| import androidx.room.solver.shortcut.binderprovider.GuavaListenableFutureUpsertMethodBinderProvider |
| import androidx.room.solver.shortcut.binderprovider.RxCallableDeleteOrUpdateMethodBinderProvider |
| import androidx.room.solver.shortcut.binderprovider.RxCallableInsertMethodBinderProvider |
| import androidx.room.solver.shortcut.binderprovider.RxCallableUpsertMethodBinderProvider |
| import androidx.room.solver.types.BoxedPrimitiveColumnTypeAdapter |
| import androidx.room.solver.types.ByteBufferColumnTypeAdapter |
| import androidx.room.solver.types.ColumnTypeAdapter |
| import androidx.room.solver.types.CompositeAdapter |
| import androidx.room.solver.types.CustomTypeConverterWrapper |
| import androidx.room.solver.types.EnumColumnTypeAdapter |
| import androidx.room.solver.types.PrimitiveColumnTypeAdapter |
| import androidx.room.solver.types.SingleStatementTypeConverter |
| import androidx.room.solver.types.StringColumnTypeAdapter |
| import androidx.room.solver.types.TypeConverter |
| import androidx.room.solver.types.UuidColumnTypeAdapter |
| import androidx.room.solver.types.ValueClassConverterWrapper |
| import androidx.room.testing.context |
| import androidx.room.vo.BuiltInConverterFlags |
| import androidx.room.vo.ReadQueryMethod |
| import com.google.common.truth.Truth.assertThat |
| import org.hamcrest.CoreMatchers.instanceOf |
| import org.hamcrest.CoreMatchers.`is` |
| import org.hamcrest.CoreMatchers.notNullValue |
| import org.hamcrest.CoreMatchers.nullValue |
| import org.hamcrest.MatcherAssert.assertThat |
| import org.junit.Test |
| import org.junit.runner.RunWith |
| import org.junit.runners.JUnit4 |
| import testCodeGenScope |
| |
| @RunWith(JUnit4::class) |
| class TypeAdapterStoreTest { |
| companion object { |
| fun tmp(index: Int) = CodeGenScope.getTmpVarString(index) |
| } |
| |
| @Test |
| fun testInvalidNonStaticInnerClass() { |
| val converter = Source.java( |
| "foo.bar.EmptyClass", |
| """ |
| package foo.bar; |
| import androidx.room.*; |
| public class EmptyClass { |
| public enum Color { |
| RED, |
| GREEN |
| } |
| public class ColorTypeConverter { |
| @TypeConverter |
| public Color fromIntToColorEnum(int colorInt) { |
| if (colorInt == 1) { |
| return Color.RED; |
| } else { |
| return Color.GREEN; |
| } |
| } |
| } |
| } |
| """.trimIndent() |
| ) |
| val entity = Source.java( |
| "foo.bar.EntityWithOneWayEnum", |
| """ |
| package foo.bar; |
| import androidx.room.*; |
| @Entity |
| @TypeConverters(EmptyClass.ColorTypeConverter.class) |
| public class EntityWithOneWayEnum { |
| public enum Color { |
| RED, |
| GREEN |
| } |
| @PrimaryKey public Long id; |
| public Color color; |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(entity, converter) |
| ) { invocation -> |
| val typeElement = |
| invocation.processingEnv.requireTypeElement("foo.bar.EntityWithOneWayEnum") |
| val context = Context(invocation.processingEnv) |
| CustomConverterProcessor.Companion.findConverters(context, typeElement) |
| invocation.assertCompilationResult { |
| hasErrorContaining(ProcessorErrors.INNER_CLASS_TYPE_CONVERTER_MUST_BE_STATIC) |
| } |
| } |
| } |
| |
| @Test |
| fun testDirect() { |
| runProcessorTest { invocation -> |
| val store = TypeAdapterStore.create( |
| Context(invocation.processingEnv), |
| BuiltInConverterFlags.DEFAULT |
| ) |
| val primitiveType = invocation.processingEnv.requireType(XTypeName.PRIMITIVE_INT) |
| val adapter = store.findColumnTypeAdapter( |
| primitiveType, |
| null, |
| skipDefaultConverter = false |
| ) |
| assertThat(adapter, notNullValue()) |
| } |
| } |
| |
| @Test |
| fun testJavaLangBoolean() { |
| runProcessorTest { invocation -> |
| val store = TypeAdapterStore.create( |
| Context(invocation.processingEnv), |
| BuiltInConverterFlags.DEFAULT |
| ) |
| val boolean = invocation |
| .processingEnv |
| .requireType("java.lang.Boolean") |
| .makeNullable() |
| val adapter = store.findColumnTypeAdapter( |
| boolean, |
| null, |
| skipDefaultConverter = false |
| ) |
| assertThat(adapter, notNullValue()) |
| assertThat(adapter, instanceOf(CompositeAdapter::class.java)) |
| val composite = adapter as CompositeAdapter |
| assertThat( |
| composite.intoStatementConverter?.from?.asTypeName(), |
| `is`(XTypeName.BOXED_BOOLEAN.copy(nullable = true)) |
| ) |
| assertThat( |
| composite.columnTypeAdapter.out.asTypeName(), |
| `is`(XTypeName.BOXED_INT.copy(nullable = true)) |
| ) |
| } |
| } |
| |
| @Test |
| fun testJavaLangEnumCompilesWithoutError() { |
| val enumSrc = Source.java( |
| "foo.bar.Fruit", |
| """ package foo.bar; |
| import androidx.room.*; |
| enum Fruit { |
| APPLE, |
| BANANA, |
| STRAWBERRY} |
| """.trimMargin() |
| ) |
| runProcessorTest( |
| sources = listOf(enumSrc) |
| ) { invocation -> |
| val store = TypeAdapterStore.create( |
| Context(invocation.processingEnv), |
| BuiltInConverterFlags.DEFAULT |
| ) |
| val enum = invocation |
| .processingEnv |
| .requireType("foo.bar.Fruit") |
| val adapter = store.findColumnTypeAdapter(enum, null, skipDefaultConverter = false) |
| assertThat(adapter, notNullValue()) |
| assertThat(adapter, instanceOf(EnumColumnTypeAdapter::class.java)) |
| } |
| } |
| |
| @Test |
| fun testKotlinLangValueClassCompilesWithoutError() { |
| val source = Source.kotlin( |
| "Foo.kt", |
| """ |
| import androidx.room.* |
| @JvmInline |
| value class IntValueClass(val data: Int) |
| @JvmInline |
| value class StringValueClass(val data: String) |
| class EntityWithValueClass { |
| val intData = IntValueClass(123) |
| val stringData = StringValueClass("bla") |
| } |
| """.trimIndent() |
| ) |
| var results: Map<String, String?> = mutableMapOf() |
| |
| runProcessorTest( |
| sources = listOf(source) |
| ) { invocation -> |
| val typeAdapterStore = TypeAdapterStore.create( |
| context = invocation.context, |
| builtInConverterFlags = BuiltInConverterFlags.DEFAULT |
| ) |
| val subject = invocation.processingEnv.requireTypeElement("EntityWithValueClass") |
| results = subject.getAllFieldsIncludingPrivateSupers().associate { field -> |
| val columnAdapter = typeAdapterStore.findColumnTypeAdapter( |
| out = field.type, |
| affinity = null, |
| false |
| ) |
| |
| val typeElementColumnAdapter: ColumnTypeAdapter? = |
| if (columnAdapter is ValueClassConverterWrapper) { |
| columnAdapter.valueTypeColumnAdapter |
| } else { |
| columnAdapter |
| } |
| |
| when (typeElementColumnAdapter) { |
| is PrimitiveColumnTypeAdapter -> { |
| field.name to "primitive" |
| } |
| |
| is StringColumnTypeAdapter -> { |
| field.name to "string" |
| } |
| |
| else -> { |
| field.name to null |
| } |
| } |
| } |
| } |
| assertThat(results).containsExactlyEntriesIn( |
| mapOf( |
| "intData" to "primitive", |
| "stringData" to "string" |
| ) |
| ) |
| } |
| |
| @Test |
| fun testValueClassWithDifferentTypeVal() { |
| val source = Source.kotlin( |
| "Foo.kt", |
| """ |
| import androidx.room.* |
| @JvmInline |
| value class Foo(val value : Int) { |
| val double |
| get() = value * 2 |
| } |
| """.trimIndent() |
| ) |
| |
| runProcessorTest( |
| sources = listOf(source) |
| ) { invocation -> |
| TypeAdapterStore.create( |
| context = invocation.context, |
| builtInConverterFlags = BuiltInConverterFlags.DEFAULT |
| ) |
| val typeElement = invocation |
| .processingEnv |
| .requireTypeElement("Foo") |
| assertThat(typeElement.getDeclaredFields()).hasSize(1) |
| assertThat(typeElement.getDeclaredFields().single().type.asTypeName()) |
| .isEqualTo(PRIMITIVE_INT) |
| } |
| } |
| |
| @Test |
| fun testJavaLangByteBufferCompilesWithoutError() { |
| runProcessorTest { invocation -> |
| val store = TypeAdapterStore.create( |
| Context(invocation.processingEnv), |
| BuiltInConverterFlags.DEFAULT |
| ) |
| val byteBufferType = invocation.processingEnv.requireType(CommonTypeNames.BYTE_BUFFER) |
| val adapter = store.findColumnTypeAdapter( |
| byteBufferType, |
| null, |
| skipDefaultConverter = false |
| ) |
| assertThat(adapter, notNullValue()) |
| assertThat(adapter, instanceOf(ByteBufferColumnTypeAdapter::class.java)) |
| } |
| } |
| |
| @Test |
| fun testJavaUtilUUIDCompilesWithoutError() { |
| runProcessorTest { invocation -> |
| val store = TypeAdapterStore.create( |
| Context(invocation.processingEnv), |
| BuiltInConverterFlags.DEFAULT |
| ) |
| val uuid = invocation |
| .processingEnv |
| .requireType(CommonTypeNames.UUID) |
| val adapter = store.findColumnTypeAdapter( |
| out = uuid, |
| affinity = null, |
| skipDefaultConverter = false |
| ) |
| |
| assertThat(adapter).isNotNull() |
| assertThat(adapter).isInstanceOf(UuidColumnTypeAdapter::class.java) |
| } |
| } |
| |
| @Test |
| fun testVia1TypeAdapter() { |
| runProcessorTest { invocation -> |
| val store = TypeAdapterStore.create( |
| Context(invocation.processingEnv), |
| BuiltInConverterFlags.DEFAULT |
| ) |
| val booleanType = invocation.processingEnv.requireType(XTypeName.PRIMITIVE_BOOLEAN) |
| val adapter = store.findColumnTypeAdapter( |
| booleanType, |
| null, |
| skipDefaultConverter = false |
| ) |
| assertThat(adapter, notNullValue()) |
| assertThat(adapter, instanceOf(CompositeAdapter::class.java)) |
| val bindScope = testCodeGenScope() |
| adapter!!.bindToStmt("stmt", "41", "fooVar", bindScope) |
| assertThat( |
| bindScope.generate().toString().trim(), |
| `is`( |
| """ |
| final int ${tmp(0)} = fooVar ? 1 : 0; |
| stmt.bindLong(41, ${tmp(0)}); |
| """.trimIndent() |
| ) |
| ) |
| |
| val cursorScope = testCodeGenScope() |
| adapter.readFromCursor("res", "curs", "7", cursorScope) |
| assertThat( |
| cursorScope.generate().toString().trim(), |
| `is`( |
| """ |
| final int ${tmp(0)}; |
| ${tmp(0)} = curs.getInt(7); |
| res = ${tmp(0)} != 0; |
| """.trimIndent() |
| ) |
| ) |
| } |
| } |
| |
| @Test |
| fun testVia2TypeAdapters() { |
| val point = Source.java( |
| "foo.bar.Point", |
| """ |
| package foo.bar; |
| import androidx.room.*; |
| @Entity |
| public class Point { |
| public int x, y; |
| public Point(int x, int y) { |
| this.x = x; |
| this.y = y; |
| } |
| @TypeConverter |
| public static Point fromBoolean(boolean val) { |
| return val ? new Point(1, 1) : new Point(0, 0); |
| } |
| @TypeConverter |
| public static boolean toBoolean(Point point) { |
| return point.x > 0; |
| } |
| } |
| """ |
| ) |
| runProcessorTest( |
| sources = listOf(point) |
| ) { invocation -> |
| |
| val context = Context(invocation.processingEnv) |
| val converters = CustomConverterProcessor( |
| context = context, |
| element = invocation.processingEnv.requireTypeElement("foo.bar.Point") |
| ).process().map(::CustomTypeConverterWrapper) |
| val store = TypeAdapterStore.create( |
| context, |
| BuiltInConverterFlags.DEFAULT, |
| converters |
| ) |
| val pointType = invocation.processingEnv.requireType("foo.bar.Point") |
| val adapter = store.findColumnTypeAdapter( |
| pointType, |
| null, |
| skipDefaultConverter = false |
| ) |
| assertThat(adapter, notNullValue()) |
| assertThat(adapter, instanceOf(CompositeAdapter::class.java)) |
| |
| val bindScope = testCodeGenScope() |
| adapter!!.bindToStmt("stmt", "41", "fooVar", bindScope) |
| assertThat( |
| bindScope.generate().toString().trim(), |
| `is`( |
| """ |
| final boolean ${tmp(0)} = foo.bar.Point.toBoolean(fooVar); |
| final int ${tmp(1)} = ${tmp(0)} ? 1 : 0; |
| stmt.bindLong(41, ${tmp(1)}); |
| """.trimIndent() |
| ) |
| ) |
| |
| val cursorScope = testCodeGenScope() |
| adapter.readFromCursor("res", "curs", "11", cursorScope).toString() |
| assertThat( |
| cursorScope.generate().toString().trim(), |
| `is`( |
| """ |
| final int ${tmp(0)}; |
| ${tmp(0)} = curs.getInt(11); |
| final boolean ${tmp(1)} = ${tmp(0)} != 0; |
| res = foo.bar.Point.fromBoolean(${tmp(1)}); |
| """.trimIndent() |
| ) |
| ) |
| } |
| } |
| |
| @Test |
| fun testDate() { |
| runProcessorTest { invocation -> |
| val store = TypeAdapterStore.create( |
| invocation.context, |
| BuiltInConverterFlags.DEFAULT, |
| dateTypeConverters(invocation.processingEnv) |
| ) |
| val tDate = invocation.processingEnv.requireType("java.util.Date").makeNullable() |
| val adapter = store.findCursorValueReader(tDate, SQLTypeAffinity.INTEGER) |
| assertThat(adapter, notNullValue()) |
| assertThat(adapter?.typeMirror(), `is`(tDate)) |
| val bindScope = testCodeGenScope() |
| adapter!!.readFromCursor("outDate", "curs", "0", bindScope) |
| assertThat( |
| bindScope.generate().toString().trim(), |
| `is`( |
| """ |
| final java.lang.Long _tmp; |
| if (curs.isNull(0)) { |
| _tmp = null; |
| } else { |
| _tmp = curs.getLong(0); |
| } |
| outDate = new java.util.Date(_tmp); |
| """.trimIndent() |
| ) |
| ) |
| } |
| } |
| |
| @Test |
| fun testIntList() { |
| runProcessorTest { invocation -> |
| val binders = createIntListToStringBinders(invocation) |
| val store = TypeAdapterStore.create( |
| Context(invocation.processingEnv), |
| BuiltInConverterFlags.DEFAULT, |
| binders[0], |
| binders[1] |
| ) |
| |
| val adapter = store.findColumnTypeAdapter( |
| binders[0].from, |
| null, |
| skipDefaultConverter = false |
| ) |
| assertThat(adapter).isNotNull() |
| |
| val bindScope = testCodeGenScope() |
| adapter!!.bindToStmt("stmt", "41", "fooVar", bindScope) |
| val expectedAdapterCode = if (invocation.isKsp) { |
| """ |
| stmt.bindString(41, ${tmp(0)}); |
| """.trimIndent() |
| } else { |
| """ |
| if (${tmp(0)} == null) { |
| stmt.bindNull(41); |
| } else { |
| stmt.bindString(41, ${tmp(0)}); |
| } |
| """.trimIndent() |
| } |
| assertThat(bindScope.generate().toString().trim()).isEqualTo( |
| """ |
| |final java.lang.String ${tmp(0)} = androidx.room.util.StringUtil.joinIntoString(fooVar); |
| |$expectedAdapterCode |
| """.trimMargin() |
| ) |
| |
| val converter = store.typeConverterStore.findTypeConverter( |
| binders[0].from, |
| invocation.context.COMMON_TYPES.STRING |
| ) |
| assertThat(converter).isNotNull() |
| assertThat(store.typeConverterStore.reverse(converter!!)).isEqualTo(binders[1]) |
| } |
| } |
| |
| @Test |
| fun testOneWayConversion() { |
| runProcessorTest { invocation -> |
| val binders = createIntListToStringBinders(invocation) |
| val store = TypeAdapterStore.create( |
| Context(invocation.processingEnv), |
| BuiltInConverterFlags.DEFAULT, |
| binders[0] |
| ) |
| val adapter = store.findColumnTypeAdapter( |
| binders[0].from, |
| null, |
| skipDefaultConverter = false |
| ) |
| assertThat(adapter, nullValue()) |
| |
| val stmtBinder = store.findStatementValueBinder(binders[0].from, null) |
| assertThat(stmtBinder, notNullValue()) |
| |
| val converter = store.typeConverterStore.findTypeConverter( |
| binders[0].from, |
| invocation.context.COMMON_TYPES.STRING |
| ) |
| assertThat(converter, notNullValue()) |
| assertThat(store.typeConverterStore.reverse(converter!!), nullValue()) |
| } |
| } |
| |
| @Test |
| fun testMissingRx2Room() { |
| runProcessorTest( |
| sources = listOf(COMMON.PUBLISHER, COMMON.RX2_FLOWABLE) |
| ) { invocation -> |
| val publisherElement = invocation.processingEnv |
| .requireTypeElement(ReactiveStreamsTypeNames.PUBLISHER) |
| assertThat(publisherElement, notNullValue()) |
| assertThat( |
| RxQueryResultBinderProvider.getAll(invocation.context).any { |
| it.matches(publisherElement.type) |
| }, |
| `is`(true) |
| ) |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.MISSING_ROOM_RXJAVA2_ARTIFACT) |
| } |
| } |
| } |
| |
| @Test |
| fun testMissingRx3Room() { |
| runProcessorTest( |
| sources = listOf(COMMON.PUBLISHER, COMMON.RX3_FLOWABLE) |
| ) { invocation -> |
| val publisherElement = invocation.processingEnv |
| .requireTypeElement(ReactiveStreamsTypeNames.PUBLISHER) |
| assertThat(publisherElement, notNullValue()) |
| assertThat( |
| RxQueryResultBinderProvider.getAll(invocation.context).any { |
| it.matches(publisherElement.type) |
| }, |
| `is`(true) |
| ) |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.MISSING_ROOM_RXJAVA3_ARTIFACT) |
| } |
| } |
| } |
| |
| @Test |
| fun testMissingRoomPaging() { |
| runProcessorTest { invocation -> |
| val pagingSourceElement = invocation.processingEnv |
| .requireTypeElement(PagingSource::class) |
| val intType = invocation.processingEnv.requireType(Integer::class) |
| val pagingSourceIntIntType = invocation.processingEnv |
| .getDeclaredType(pagingSourceElement, intType, intType) |
| |
| assertThat(pagingSourceIntIntType, notNullValue()) |
| assertThat( |
| PagingSourceQueryResultBinderProvider(invocation.context) |
| .matches(pagingSourceIntIntType), |
| `is`(true) |
| ) |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.MISSING_ROOM_PAGING_ARTIFACT) |
| } |
| } |
| } |
| |
| @Test |
| fun testMissingRoomPagingGuava() { |
| runProcessorTest( |
| sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE) |
| ) { invocation -> |
| val listenableFuturePagingSourceElement = invocation.processingEnv |
| .requireTypeElement(PagingTypeNames.LISTENABLE_FUTURE_PAGING_SOURCE) |
| val intType = invocation.processingEnv.requireType(Integer::class) |
| val listenableFuturePagingSourceIntIntType = invocation.processingEnv |
| .getDeclaredType(listenableFuturePagingSourceElement, intType, intType) |
| |
| assertThat(listenableFuturePagingSourceElement, notNullValue()) |
| assertThat( |
| ListenableFuturePagingSourceQueryResultBinderProvider(invocation.context) |
| .matches(listenableFuturePagingSourceIntIntType), |
| `is`(true) |
| ) |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.MISSING_ROOM_PAGING_GUAVA_ARTIFACT) |
| } |
| } |
| } |
| |
| @Test |
| fun testMissingRoomPagingRx2() { |
| runProcessorTest( |
| sources = listOf(COMMON.RX2_PAGING_SOURCE) |
| ) { invocation -> |
| val rx2PagingSourceElement = invocation.processingEnv |
| .requireTypeElement(PagingTypeNames.RX2_PAGING_SOURCE) |
| val intType = invocation.processingEnv.requireType(Integer::class) |
| val rx2PagingSourceIntIntType = invocation.processingEnv |
| .getDeclaredType(rx2PagingSourceElement, intType, intType) |
| |
| assertThat(rx2PagingSourceElement, notNullValue()) |
| assertThat( |
| RxJava2PagingSourceQueryResultBinderProvider(invocation.context) |
| .matches(rx2PagingSourceIntIntType), |
| `is`(true) |
| ) |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.MISSING_ROOM_PAGING_RXJAVA2_ARTIFACT) |
| } |
| } |
| } |
| |
| @Test |
| fun testMissingRoomPagingRx3() { |
| runProcessorTest( |
| sources = listOf(COMMON.RX3_PAGING_SOURCE) |
| ) { invocation -> |
| val rx3PagingSourceElement = invocation.processingEnv |
| .requireTypeElement(PagingTypeNames.RX3_PAGING_SOURCE) |
| val intType = invocation.processingEnv.requireType(Integer::class) |
| val rx3PagingSourceIntIntType = invocation.processingEnv |
| .getDeclaredType(rx3PagingSourceElement, intType, intType) |
| |
| assertThat(rx3PagingSourceElement, notNullValue()) |
| assertThat( |
| RxJava3PagingSourceQueryResultBinderProvider(invocation.context) |
| .matches(rx3PagingSourceIntIntType), |
| `is`(true) |
| ) |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.MISSING_ROOM_PAGING_RXJAVA3_ARTIFACT) |
| } |
| } |
| } |
| |
| @Test |
| fun testFindPublisher() { |
| listOf( |
| COMMON.RX2_FLOWABLE to COMMON.RX2_ROOM, |
| COMMON.RX3_FLOWABLE to COMMON.RX3_ROOM |
| ).forEach { (rxTypeSrc, rxRoomSrc) -> |
| runProcessorTest( |
| sources = listOf( |
| COMMON.RX2_SINGLE, |
| COMMON.RX3_SINGLE, |
| COMMON.RX2_OBSERVABLE, |
| COMMON.RX3_OBSERVABLE, |
| COMMON.PUBLISHER, |
| rxTypeSrc, |
| rxRoomSrc |
| ) |
| ) { invocation -> |
| val publisher = invocation.processingEnv |
| .requireTypeElement(ReactiveStreamsTypeNames.PUBLISHER) |
| assertThat(publisher, notNullValue()) |
| assertThat( |
| RxQueryResultBinderProvider.getAll(invocation.context).any { |
| it.matches(publisher.type) |
| }, |
| `is`(true) |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun testFindFlowable() { |
| listOf( |
| Triple(COMMON.RX2_FLOWABLE, COMMON.RX2_ROOM, RxJava2TypeNames.FLOWABLE), |
| Triple(COMMON.RX3_FLOWABLE, COMMON.RX3_ROOM, RxJava3TypeNames.FLOWABLE) |
| ).forEach { (rxTypeSrc, rxRoomSrc, rxTypeClassName) -> |
| runProcessorTest( |
| sources = listOf( |
| COMMON.RX2_SINGLE, |
| COMMON.RX3_SINGLE, |
| COMMON.RX2_OBSERVABLE, |
| COMMON.RX3_OBSERVABLE, |
| COMMON.PUBLISHER, |
| rxTypeSrc, |
| rxRoomSrc |
| ) |
| ) { invocation -> |
| val flowable = invocation.processingEnv.requireTypeElement(rxTypeClassName) |
| assertThat( |
| RxQueryResultBinderProvider.getAll(invocation.context).any { |
| it.matches(flowable.type) |
| }, |
| `is`(true) |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun testFindObservable() { |
| listOf( |
| Triple(COMMON.RX2_OBSERVABLE, COMMON.RX2_ROOM, RxJava2TypeNames.OBSERVABLE), |
| Triple(COMMON.RX3_OBSERVABLE, COMMON.RX3_ROOM, RxJava3TypeNames.OBSERVABLE) |
| ).forEach { (rxTypeSrc, rxRoomSrc, rxTypeClassName) -> |
| runProcessorTest( |
| sources = listOf( |
| COMMON.RX2_SINGLE, |
| COMMON.RX3_SINGLE, |
| COMMON.RX2_FLOWABLE, |
| COMMON.RX3_FLOWABLE, |
| COMMON.PUBLISHER, |
| rxTypeSrc, |
| rxRoomSrc |
| ) |
| ) { invocation -> |
| val observable = invocation.processingEnv.requireTypeElement(rxTypeClassName) |
| assertThat(observable, notNullValue()) |
| assertThat( |
| RxQueryResultBinderProvider.getAll(invocation.context).any { |
| it.matches(observable.type) |
| }, |
| `is`(true) |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun testFindInsertSingle() { |
| listOf( |
| Triple(COMMON.RX2_SINGLE, COMMON.RX2_ROOM, RxJava2TypeNames.SINGLE), |
| Triple(COMMON.RX3_SINGLE, COMMON.RX3_ROOM, RxJava3TypeNames.SINGLE) |
| ).forEach { (rxTypeSrc, _, rxTypeClassName) -> |
| runProcessorTest(sources = listOf(rxTypeSrc)) { invocation -> |
| val single = invocation.processingEnv.requireTypeElement(rxTypeClassName) |
| assertThat(single, notNullValue()) |
| assertThat( |
| RxCallableInsertMethodBinderProvider.getAll(invocation.context).any { |
| it.matches(single.type) |
| }, |
| `is`(true) |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun testFindInsertMaybe() { |
| listOf( |
| Triple(COMMON.RX2_MAYBE, COMMON.RX2_ROOM, RxJava2TypeNames.MAYBE), |
| Triple(COMMON.RX3_MAYBE, COMMON.RX3_ROOM, RxJava3TypeNames.MAYBE) |
| ).forEach { (rxTypeSrc, _, rxTypeClassName) -> |
| runProcessorTest(sources = listOf(rxTypeSrc)) { invocation -> |
| val maybe = invocation.processingEnv.requireTypeElement(rxTypeClassName) |
| assertThat( |
| RxCallableInsertMethodBinderProvider.getAll(invocation.context).any { |
| it.matches(maybe.type) |
| }, |
| `is`(true) |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun testFindInsertCompletable() { |
| listOf( |
| Triple(COMMON.RX2_COMPLETABLE, COMMON.RX2_ROOM, RxJava2TypeNames.COMPLETABLE), |
| Triple(COMMON.RX3_COMPLETABLE, COMMON.RX3_ROOM, RxJava3TypeNames.COMPLETABLE) |
| ).forEach { (rxTypeSrc, _, rxTypeClassName) -> |
| runProcessorTest(sources = listOf(rxTypeSrc)) { invocation -> |
| val completable = invocation.processingEnv.requireTypeElement(rxTypeClassName) |
| assertThat( |
| RxCallableInsertMethodBinderProvider.getAll(invocation.context).any { |
| it.matches(completable.type) |
| }, |
| `is`(true) |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun testFindInsertListenableFuture() { |
| runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE)) { |
| invocation -> |
| val future = invocation.processingEnv |
| .requireTypeElement(GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE) |
| assertThat( |
| GuavaListenableFutureInsertMethodBinderProvider(invocation.context).matches( |
| future.type |
| ), |
| `is`(true) |
| ) |
| } |
| } |
| |
| @Test |
| fun testFindDeleteOrUpdateSingle() { |
| runProcessorTest(sources = listOf(COMMON.RX2_SINGLE)) { invocation -> |
| val single = invocation.processingEnv.requireTypeElement(RxJava2TypeNames.SINGLE) |
| assertThat(single, notNullValue()) |
| assertThat( |
| RxCallableDeleteOrUpdateMethodBinderProvider.getAll(invocation.context).any { |
| it.matches(single.type) |
| }, |
| `is`(true) |
| ) |
| } |
| } |
| |
| @Test |
| fun testFindDeleteOrUpdateMaybe() { |
| runProcessorTest(sources = listOf(COMMON.RX2_MAYBE)) { |
| invocation -> |
| val maybe = invocation.processingEnv.requireTypeElement(RxJava2TypeNames.MAYBE) |
| assertThat(maybe, notNullValue()) |
| assertThat( |
| RxCallableDeleteOrUpdateMethodBinderProvider.getAll(invocation.context).any { |
| it.matches(maybe.type) |
| }, |
| `is`(true) |
| ) |
| } |
| } |
| |
| @Test |
| fun testFindDeleteOrUpdateCompletable() { |
| runProcessorTest(sources = listOf(COMMON.RX2_COMPLETABLE)) { |
| invocation -> |
| val completable = invocation.processingEnv |
| .requireTypeElement(RxJava2TypeNames.COMPLETABLE) |
| assertThat(completable, notNullValue()) |
| assertThat( |
| RxCallableDeleteOrUpdateMethodBinderProvider.getAll(invocation.context).any { |
| it.matches(completable.type) |
| }, |
| `is`(true) |
| ) |
| } |
| } |
| |
| @Test |
| fun testFindDeleteOrUpdateListenableFuture() { |
| runProcessorTest( |
| sources = listOf(COMMON.LISTENABLE_FUTURE) |
| ) { invocation -> |
| val future = invocation.processingEnv |
| .requireTypeElement(GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE) |
| assertThat(future, notNullValue()) |
| assertThat( |
| GuavaListenableFutureDeleteOrUpdateMethodBinderProvider(invocation.context) |
| .matches(future.type), |
| `is`(true) |
| ) |
| } |
| } |
| |
| @Test |
| fun testFindUpsertSingle() { |
| listOf( |
| Triple(COMMON.RX2_SINGLE, COMMON.RX2_ROOM, RxJava2TypeNames.SINGLE), |
| Triple(COMMON.RX3_SINGLE, COMMON.RX3_ROOM, RxJava3TypeNames.SINGLE) |
| ).forEach { (rxTypeSrc, _, rxTypeClassName) -> |
| runProcessorTest(sources = listOf(rxTypeSrc)) { invocation -> |
| val single = invocation.processingEnv.requireTypeElement(rxTypeClassName) |
| assertThat(single).isNotNull() |
| assertThat( |
| RxCallableUpsertMethodBinderProvider.getAll(invocation.context).any { |
| it.matches(single.type) |
| }).isTrue() |
| } |
| } |
| } |
| |
| @Test |
| fun testFindUpsertMaybe() { |
| listOf( |
| Triple(COMMON.RX2_MAYBE, COMMON.RX2_ROOM, RxJava2TypeNames.MAYBE), |
| Triple(COMMON.RX3_MAYBE, COMMON.RX3_ROOM, RxJava3TypeNames.MAYBE) |
| ).forEach { (rxTypeSrc, _, rxTypeClassName) -> |
| runProcessorTest(sources = listOf(rxTypeSrc)) { invocation -> |
| val maybe = invocation.processingEnv.requireTypeElement(rxTypeClassName) |
| assertThat( |
| RxCallableUpsertMethodBinderProvider.getAll(invocation.context).any { |
| it.matches(maybe.type) |
| }).isTrue() |
| } |
| } |
| } |
| |
| @Test |
| fun testFindUpsertCompletable() { |
| listOf( |
| Triple(COMMON.RX2_COMPLETABLE, COMMON.RX2_ROOM, RxJava2TypeNames.COMPLETABLE), |
| Triple(COMMON.RX3_COMPLETABLE, COMMON.RX3_ROOM, RxJava3TypeNames.COMPLETABLE) |
| ).forEach { (rxTypeSrc, _, rxTypeClassName) -> |
| runProcessorTest(sources = listOf(rxTypeSrc)) { invocation -> |
| val completable = invocation.processingEnv.requireTypeElement(rxTypeClassName) |
| assertThat( |
| RxCallableUpsertMethodBinderProvider.getAll(invocation.context).any { |
| it.matches(completable.type) |
| }).isTrue() |
| } |
| } |
| } |
| |
| @Test |
| fun testFindUpsertListenableFuture() { |
| runProcessorTest(sources = listOf(COMMON.LISTENABLE_FUTURE)) { |
| invocation -> |
| val future = invocation.processingEnv |
| .requireTypeElement(GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE) |
| assertThat( |
| GuavaListenableFutureUpsertMethodBinderProvider(invocation.context).matches( |
| future.type |
| )).isTrue() |
| } |
| } |
| |
| @Test |
| fun testFindLiveData() { |
| runProcessorTest( |
| sources = listOf(COMMON.COMPUTABLE_LIVE_DATA, COMMON.LIVE_DATA) |
| ) { invocation -> |
| val liveData = invocation.processingEnv |
| .requireTypeElement(LifecyclesTypeNames.LIVE_DATA) |
| assertThat(liveData, notNullValue()) |
| assertThat( |
| LiveDataQueryResultBinderProvider(invocation.context).matches( |
| liveData.type |
| ), |
| `is`(true) |
| ) |
| } |
| } |
| |
| @Test |
| fun findPagingSourceIntKey() { |
| runProcessorTest( |
| sources = listOf(COMMON.LIMIT_OFFSET_PAGING_SOURCE), |
| ) { invocation -> |
| val pagingSourceElement = invocation.processingEnv |
| .requireTypeElement(PagingSource::class) |
| val intType = invocation.processingEnv.requireType(Integer::class) |
| val pagingSourceIntIntType = invocation.processingEnv |
| .getDeclaredType(pagingSourceElement, intType, intType) |
| |
| assertThat(pagingSourceIntIntType, notNullValue()) |
| assertThat( |
| PagingSourceQueryResultBinderProvider(invocation.context) |
| .matches(pagingSourceIntIntType), |
| `is`(true) |
| ) |
| } |
| } |
| |
| @Test |
| fun findPagingSourceStringKey() { |
| runProcessorTest { invocation -> |
| val pagingSourceElement = invocation.processingEnv |
| .requireTypeElement(PagingSource::class) |
| val stringType = invocation.processingEnv.requireType(String::class) |
| val pagingSourceIntIntType = invocation.processingEnv |
| .getDeclaredType(pagingSourceElement, stringType, stringType) |
| |
| assertThat(pagingSourceIntIntType, notNullValue()) |
| assertThat( |
| PagingSourceQueryResultBinderProvider(invocation.context) |
| .matches(pagingSourceIntIntType), |
| `is`(true) |
| ) |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.PAGING_SPECIFY_PAGING_SOURCE_TYPE) |
| } |
| } |
| } |
| |
| @Test |
| fun findPagingSourceJavaCollectionValue() { |
| runProcessorTest { invocation -> |
| val pagingSourceElement = invocation.processingEnv |
| .requireTypeElement(PagingSource::class) |
| val intType = invocation.processingEnv.requireType(Integer::class) |
| val collectionType = invocation.processingEnv.requireType("java.util.Collection") |
| val pagingSourceIntCollectionType = invocation.processingEnv |
| .getDeclaredType(pagingSourceElement, intType, collectionType) |
| |
| assertThat(pagingSourceIntCollectionType).isNotNull() |
| assertThat( |
| PagingSourceQueryResultBinderProvider(invocation.context) |
| .matches(pagingSourceIntCollectionType) |
| ).isTrue() |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.PAGING_SPECIFY_PAGING_SOURCE_VALUE_TYPE) |
| } |
| } |
| } |
| |
| @Test |
| fun findPagingSourceKotlinCollectionValue() { |
| runProcessorTest { invocation -> |
| val pagingSourceElement = invocation.processingEnv |
| .requireTypeElement(PagingSource::class) |
| val intType = invocation.processingEnv.requireType(Integer::class) |
| val kotlinCollectionType = invocation.processingEnv.requireType(Collection::class) |
| val pagingSourceIntCollectionType = invocation.processingEnv |
| .getDeclaredType(pagingSourceElement, intType, kotlinCollectionType) |
| |
| assertThat(pagingSourceIntCollectionType).isNotNull() |
| assertThat( |
| PagingSourceQueryResultBinderProvider(invocation.context) |
| .matches(pagingSourceIntCollectionType) |
| ).isTrue() |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.PAGING_SPECIFY_PAGING_SOURCE_VALUE_TYPE) |
| } |
| } |
| } |
| |
| @Test |
| fun findPagingSourceJavaListValue() { |
| runProcessorTest { invocation -> |
| val pagingSourceElement = invocation.processingEnv |
| .requireTypeElement(PagingSource::class) |
| val intType = invocation.processingEnv.requireType(Integer::class) |
| val javaListType = invocation.processingEnv.requireType("java.util.List") |
| val pagingSourceIntListType = invocation.processingEnv |
| .getDeclaredType(pagingSourceElement, intType, javaListType) |
| assertThat(pagingSourceIntListType).isNotNull() |
| assertThat( |
| PagingSourceQueryResultBinderProvider(invocation.context) |
| .matches(pagingSourceIntListType) |
| ).isTrue() |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.PAGING_SPECIFY_PAGING_SOURCE_VALUE_TYPE) |
| } |
| } |
| } |
| |
| @Test |
| fun findPagingSourceKotlinMutableSetValue() { |
| runProcessorTest { invocation -> |
| val pagingSourceElement = invocation.processingEnv |
| .requireTypeElement(PagingSource::class) |
| val intType = invocation.processingEnv.requireType(Integer::class) |
| val mutableSetType = invocation.processingEnv.requireType(MutableSet::class) |
| val pagingSourceIntCollectionType = invocation.processingEnv |
| .getDeclaredType(pagingSourceElement, intType, mutableSetType) |
| |
| assertThat(pagingSourceIntCollectionType).isNotNull() |
| assertThat( |
| PagingSourceQueryResultBinderProvider(invocation.context) |
| .matches(pagingSourceIntCollectionType) |
| ).isTrue() |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.PAGING_SPECIFY_PAGING_SOURCE_VALUE_TYPE) |
| } |
| } |
| } |
| |
| @Test |
| fun findListenableFuturePagingSourceJavaCollectionValue() { |
| runProcessorTest( |
| sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE) |
| ) { invocation -> |
| val listenableFuturePagingSourceElement = invocation.processingEnv |
| .requireTypeElement(PagingTypeNames.LISTENABLE_FUTURE_PAGING_SOURCE) |
| val intType = invocation.processingEnv.requireType(Integer::class) |
| val collectionType = invocation.processingEnv.requireType("java.util.Collection") |
| val listenableFuturePagingSourceIntCollectionType = invocation.processingEnv |
| .getDeclaredType(listenableFuturePagingSourceElement, intType, collectionType) |
| |
| assertThat(listenableFuturePagingSourceIntCollectionType).isNotNull() |
| assertThat( |
| ListenableFuturePagingSourceQueryResultBinderProvider(invocation.context) |
| .matches(listenableFuturePagingSourceIntCollectionType) |
| ).isTrue() |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.PAGING_SPECIFY_PAGING_SOURCE_VALUE_TYPE) |
| } |
| } |
| } |
| |
| @Test |
| fun findListenableFutureKotlinCollectionValue() { |
| runProcessorTest( |
| sources = listOf(COMMON.LISTENABLE_FUTURE_PAGING_SOURCE) |
| ) { invocation -> |
| val listenableFuturePagingSourceElement = invocation.processingEnv |
| .requireTypeElement(PagingTypeNames.LISTENABLE_FUTURE_PAGING_SOURCE) |
| val intType = invocation.processingEnv.requireType(Integer::class) |
| val kotlinCollectionType = invocation.processingEnv.requireType(Collection::class) |
| val listenableFuturePagingSourceIntCollectionType = invocation.processingEnv |
| .getDeclaredType(listenableFuturePagingSourceElement, intType, kotlinCollectionType) |
| |
| assertThat(listenableFuturePagingSourceIntCollectionType).isNotNull() |
| assertThat( |
| ListenableFuturePagingSourceQueryResultBinderProvider(invocation.context) |
| .matches(listenableFuturePagingSourceIntCollectionType) |
| ).isTrue() |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.PAGING_SPECIFY_PAGING_SOURCE_VALUE_TYPE) |
| } |
| } |
| } |
| |
| @Test |
| fun findRx2PagingSourceJavaCollectionValue() { |
| runProcessorTest( |
| sources = listOf(COMMON.RX2_PAGING_SOURCE) |
| ) { invocation -> |
| val rx2PagingSourceElement = invocation.processingEnv |
| .requireTypeElement(PagingTypeNames.RX2_PAGING_SOURCE) |
| val intType = invocation.processingEnv.requireType(Integer::class) |
| val collectionType = invocation.processingEnv.requireType("java.util.Collection") |
| val rx2PagingSourceIntCollectionType = invocation.processingEnv |
| .getDeclaredType(rx2PagingSourceElement, intType, collectionType) |
| |
| assertThat(rx2PagingSourceElement).isNotNull() |
| assertThat( |
| RxJava2PagingSourceQueryResultBinderProvider(invocation.context) |
| .matches(rx2PagingSourceIntCollectionType) |
| ).isTrue() |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.PAGING_SPECIFY_PAGING_SOURCE_VALUE_TYPE) |
| } |
| } |
| } |
| |
| @Test |
| fun findRx2PagingSourceKotlinCollectionValue() { |
| runProcessorTest( |
| sources = listOf(COMMON.RX2_PAGING_SOURCE) |
| ) { invocation -> |
| val rx2PagingSourceElement = invocation.processingEnv |
| .requireTypeElement(PagingTypeNames.RX2_PAGING_SOURCE) |
| val intType = invocation.processingEnv.requireType(Integer::class) |
| val kotlinCollectionType = invocation.processingEnv.requireType(Collection::class) |
| val rx2PagingSourceIntCollectionType = invocation.processingEnv |
| .getDeclaredType(rx2PagingSourceElement, intType, kotlinCollectionType) |
| |
| assertThat(rx2PagingSourceElement).isNotNull() |
| assertThat( |
| RxJava2PagingSourceQueryResultBinderProvider(invocation.context) |
| .matches(rx2PagingSourceIntCollectionType) |
| ).isTrue() |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.PAGING_SPECIFY_PAGING_SOURCE_VALUE_TYPE) |
| } |
| } |
| } |
| |
| @Test |
| fun findRx3PagingSourceJavaCollectionValue() { |
| runProcessorTest( |
| sources = listOf(COMMON.RX3_PAGING_SOURCE) |
| ) { invocation -> |
| val rx3PagingSourceElement = invocation.processingEnv |
| .requireTypeElement(PagingTypeNames.RX3_PAGING_SOURCE) |
| val intType = invocation.processingEnv.requireType(Integer::class) |
| val collectionType = invocation.processingEnv.requireType("java.util.Collection") |
| val rx3PagingSourceIntCollectionType = invocation.processingEnv |
| .getDeclaredType(rx3PagingSourceElement, intType, collectionType) |
| |
| assertThat(rx3PagingSourceElement).isNotNull() |
| assertThat( |
| RxJava3PagingSourceQueryResultBinderProvider(invocation.context) |
| .matches(rx3PagingSourceIntCollectionType) |
| ).isTrue() |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.PAGING_SPECIFY_PAGING_SOURCE_VALUE_TYPE) |
| } |
| } |
| } |
| |
| @Test |
| fun findRx3PagingSourceKotlinCollectionValue() { |
| runProcessorTest( |
| sources = listOf(COMMON.RX3_PAGING_SOURCE) |
| ) { invocation -> |
| val rx3PagingSourceElement = invocation.processingEnv |
| .requireTypeElement(PagingTypeNames.RX3_PAGING_SOURCE) |
| val intType = invocation.processingEnv.requireType(Integer::class) |
| val kotlinCollectionType = invocation.processingEnv.requireType(Collection::class) |
| val rx3PagingSourceIntCollectionType = invocation.processingEnv |
| .getDeclaredType(rx3PagingSourceElement, intType, kotlinCollectionType) |
| |
| assertThat(rx3PagingSourceElement).isNotNull() |
| assertThat( |
| RxJava3PagingSourceQueryResultBinderProvider(invocation.context) |
| .matches(rx3PagingSourceIntCollectionType) |
| ).isTrue() |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.PAGING_SPECIFY_PAGING_SOURCE_VALUE_TYPE) |
| } |
| } |
| } |
| |
| @Test |
| fun testPagingSourceBinder() { |
| val inputSource = |
| Source.java( |
| qName = "foo.bar.MyDao", |
| code = |
| """ |
| ${DaoProcessorTest.DAO_PREFIX} |
| |
| @Dao abstract class MyDao { |
| @Query("SELECT uid FROM User") |
| abstract androidx.paging.PagingSource<Integer, User> getAllIds(); |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf( |
| inputSource, |
| COMMON.USER, |
| COMMON.PAGING_SOURCE, |
| COMMON.LIMIT_OFFSET_PAGING_SOURCE, |
| COMMON.LISTENABLE_FUTURE_PAGING_SOURCE, |
| ), |
| ) { invocation: XTestInvocation -> |
| val dao = invocation.roundEnv |
| .getElementsAnnotatedWith( |
| Dao::class.qualifiedName!! |
| ).first() |
| check(dao.isTypeElement()) |
| val dbType = invocation.context.processingEnv |
| .requireType(ROOM_DB) |
| val parser = DaoProcessor( |
| invocation.context, |
| dao, dbType, null, |
| ) |
| val parsedDao = parser.process() |
| val binder = parsedDao.queryMethods.filterIsInstance<ReadQueryMethod>() |
| .first().queryResultBinder |
| assertThat(binder is MultiTypedPagingSourceQueryResultBinder).isTrue() |
| |
| val pagingSourceXRawType: XRawType? = invocation.context.processingEnv |
| .findType(PagingTypeNames.PAGING_SOURCE.canonicalName)?.rawType |
| val returnedXRawType = parsedDao.queryMethods |
| .filterIsInstance<ReadQueryMethod>().first().returnType.rawType |
| // make sure returned type is the original PagingSource |
| assertThat(returnedXRawType).isEqualTo(pagingSourceXRawType) |
| |
| val listenableFuturePagingSourceXRawType: XRawType? = invocation.context.processingEnv |
| .findType(PagingTypeNames.LISTENABLE_FUTURE_PAGING_SOURCE.canonicalName)?.rawType |
| assertThat(listenableFuturePagingSourceXRawType!!.isAssignableFrom(returnedXRawType)) |
| .isFalse() |
| } |
| } |
| |
| @Test |
| fun testListenableFuturePagingSourceBinder() { |
| val inputSource = |
| Source.java( |
| qName = "foo.bar.MyDao", |
| code = |
| """ |
| ${DaoProcessorTest.DAO_PREFIX} |
| |
| @Dao abstract class MyDao { |
| @Query("SELECT uid FROM User") |
| abstract androidx.paging.ListenableFuturePagingSource<Integer, User> getAllIds(); |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf( |
| inputSource, |
| COMMON.USER, |
| COMMON.LISTENABLE_FUTURE_PAGING_SOURCE, |
| COMMON.LIMIT_OFFSET_LISTENABLE_FUTURE_PAGING_SOURCE, |
| ), |
| ) { invocation: XTestInvocation -> |
| val dao = invocation.roundEnv |
| .getElementsAnnotatedWith( |
| Dao::class.qualifiedName!! |
| ).first() |
| check(dao.isTypeElement()) |
| val dbType = invocation.context.processingEnv |
| .requireType(ROOM_DB) |
| val parser = DaoProcessor( |
| invocation.context, |
| dao, dbType, null, |
| ) |
| val parsedDao = parser.process() |
| val binder = parsedDao.queryMethods.filterIsInstance<ReadQueryMethod>() |
| .first().queryResultBinder |
| |
| // assert that room correctly binds to ListenableFuturePagingSource instead of |
| // its supertype PagingSource. ListenableFuturePagingSourceBinderProvider |
| // must be added into list of binder providers in TypeAdapterStore before |
| // generic PagingSource. |
| assertThat(binder is MultiTypedPagingSourceQueryResultBinder).isTrue() |
| val listenableFuturePagingSourceXRawType: XRawType? = invocation.context.processingEnv |
| .findType(PagingTypeNames.LISTENABLE_FUTURE_PAGING_SOURCE.canonicalName)?.rawType |
| val returnedXRawType = parsedDao.queryMethods |
| .filterIsInstance<ReadQueryMethod>().first().returnType.rawType |
| // make sure the actual returned type from Provider is ListenableFuturePagingSource |
| assertThat(returnedXRawType).isEqualTo(listenableFuturePagingSourceXRawType) |
| } |
| } |
| |
| @Test |
| fun testRx2PagingSourceBinder() { |
| val inputSource = |
| Source.java( |
| qName = "foo.bar.MyDao", |
| code = |
| """ |
| ${DaoProcessorTest.DAO_PREFIX} |
| |
| @Dao abstract class MyDao { |
| @Query("SELECT uid FROM User") |
| abstract androidx.paging.rxjava2.RxPagingSource<Integer, User> getAllIds(); |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf( |
| inputSource, |
| COMMON.USER, |
| COMMON.RX2_PAGING_SOURCE, |
| COMMON.LIMIT_OFFSET_RX2_PAGING_SOURCE, |
| ), |
| ) { invocation: XTestInvocation -> |
| val dao = invocation.roundEnv |
| .getElementsAnnotatedWith( |
| Dao::class.qualifiedName!! |
| ).first() |
| check(dao.isTypeElement()) |
| val dbType = invocation.context.processingEnv |
| .requireType(ROOM_DB) |
| val parser = DaoProcessor( |
| invocation.context, |
| dao, dbType, null, |
| ) |
| val parsedDao = parser.process() |
| val binder = parsedDao.queryMethods.filterIsInstance<ReadQueryMethod>() |
| .first().queryResultBinder |
| |
| assertThat(binder is MultiTypedPagingSourceQueryResultBinder).isTrue() |
| val rxPagingSourceXRawType: XRawType? = invocation.context.processingEnv |
| .findType(PagingTypeNames.RX2_PAGING_SOURCE.canonicalName)?.rawType |
| val returnedXRawType = parsedDao.queryMethods |
| .filterIsInstance<ReadQueryMethod>().first().returnType.rawType |
| // make sure the actual returned type from Provider is a Rx2PagingSource |
| assertThat(returnedXRawType).isEqualTo(rxPagingSourceXRawType) |
| } |
| } |
| |
| @Test |
| fun testRx3PagingSourceBinder() { |
| val inputSource = |
| Source.java( |
| qName = "foo.bar.MyDao", |
| code = |
| """ |
| ${DaoProcessorTest.DAO_PREFIX} |
| |
| @Dao abstract class MyDao { |
| @Query("SELECT uid FROM User") |
| abstract androidx.paging.rxjava3.RxPagingSource<Integer, User> getAllIds(); |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf( |
| inputSource, |
| COMMON.USER, |
| COMMON.RX3_PAGING_SOURCE, |
| COMMON.LIMIT_OFFSET_RX3_PAGING_SOURCE, |
| ), |
| ) { invocation: XTestInvocation -> |
| val dao = invocation.roundEnv |
| .getElementsAnnotatedWith( |
| Dao::class.qualifiedName!! |
| ).first() |
| check(dao.isTypeElement()) |
| val dbType = invocation.context.processingEnv |
| .requireType(ROOM_DB) |
| val parser = DaoProcessor( |
| invocation.context, |
| dao, dbType, null, |
| ) |
| val parsedDao = parser.process() |
| val binder = parsedDao.queryMethods.filterIsInstance<ReadQueryMethod>() |
| .first().queryResultBinder |
| |
| assertThat(binder is MultiTypedPagingSourceQueryResultBinder).isTrue() |
| val rxPagingSourceXRawType: XRawType? = invocation.context.processingEnv |
| .findType(PagingTypeNames.RX3_PAGING_SOURCE.canonicalName)?.rawType |
| val returnedXRawType = parsedDao.queryMethods |
| .filterIsInstance<ReadQueryMethod>().first().returnType.rawType |
| // make sure the actual returned type from Provider is a RxPagingSource |
| assertThat(returnedXRawType).isEqualTo(rxPagingSourceXRawType) |
| } |
| } |
| |
| @Test |
| fun findDataSource() { |
| runProcessorTest { |
| invocation -> |
| val dataSource = invocation.processingEnv.requireTypeElement(DataSource::class) |
| assertThat(dataSource, notNullValue()) |
| assertThat( |
| DataSourceQueryResultBinderProvider(invocation.context).matches( |
| dataSource.type |
| ), |
| `is`(true) |
| ) |
| invocation.assertCompilationResult { |
| hasError(ProcessorErrors.PAGING_SPECIFY_DATA_SOURCE_TYPE) |
| } |
| } |
| } |
| |
| @Test |
| fun findPositionalDataSource() { |
| runProcessorTest { |
| invocation -> |
| @Suppress("DEPRECATION") |
| val dataSource = invocation.processingEnv |
| .requireTypeElement(androidx.paging.PositionalDataSource::class) |
| assertThat(dataSource, notNullValue()) |
| assertThat( |
| DataSourceQueryResultBinderProvider(invocation.context).matches( |
| dataSource.type |
| ), |
| `is`(true) |
| ) |
| } |
| } |
| |
| @Test |
| fun findDataSourceFactory() { |
| runProcessorTest(sources = listOf(COMMON.DATA_SOURCE_FACTORY)) { |
| invocation -> |
| val pagedListProvider = invocation.processingEnv |
| .requireTypeElement(PagingTypeNames.DATA_SOURCE_FACTORY) |
| assertThat(pagedListProvider, notNullValue()) |
| assertThat( |
| DataSourceFactoryQueryResultBinderProvider(invocation.context).matches( |
| pagedListProvider.type |
| ), |
| `is`(true) |
| ) |
| } |
| } |
| |
| @Test |
| fun findQueryParameterAdapter_collections() { |
| runProcessorTest { invocation -> |
| val store = TypeAdapterStore.create( |
| context = invocation.context, |
| builtInConverterFlags = BuiltInConverterFlags.DEFAULT |
| ) |
| val javacCollectionTypes = listOf( |
| "java.util.Set", |
| "java.util.List", |
| "java.util.ArrayList" |
| ) |
| val kotlinCollectionTypes = listOf( |
| "kotlin.collections.List", |
| "kotlin.collections.MutableList" |
| ) |
| val collectionTypes = if (invocation.isKsp) { |
| javacCollectionTypes + kotlinCollectionTypes |
| } else { |
| javacCollectionTypes |
| } |
| collectionTypes.map { collectionType -> |
| invocation.processingEnv.getDeclaredType( |
| invocation.processingEnv.requireTypeElement(collectionType), |
| invocation.processingEnv.requireType(XTypeName.PRIMITIVE_INT).boxed() |
| ) |
| }.forEach { type -> |
| val adapter = store.findQueryParameterAdapter( |
| typeMirror = type, |
| isMultipleParameter = true |
| ) |
| assertThat(adapter).isNotNull() |
| assertThat(adapter).isInstanceOf(CollectionQueryParameterAdapter::class.java) |
| } |
| } |
| } |
| |
| @Test |
| fun typeAliases() { |
| val source = Source.kotlin( |
| "Foo.kt", |
| """ |
| import androidx.room.* |
| typealias MyLongAlias = Long |
| typealias MyNullableLongAlias = Long? |
| |
| data class MyClass(val foo:String) |
| typealias MyClassAlias = MyClass |
| typealias MyClassNullableAlias = MyClass? |
| |
| object MyConverters { |
| @TypeConverter |
| fun myClassToString(myClass : MyClass): String = TODO() |
| @TypeConverter |
| fun nullableMyClassToString(myClass : MyClass?): String? = TODO() |
| } |
| class Subject { |
| val myLongAlias : MyLongAlias = TODO() |
| val myLongAlias_nullable : MyLongAlias? = TODO() |
| val myNullableLongAlias : MyNullableLongAlias = TODO() |
| val myNullableLongAlias_nullable : MyNullableLongAlias? = TODO() |
| val myClass : MyClass = TODO() |
| val myClassAlias : MyClassAlias = TODO() |
| val myClassAlias_nullable : MyClassAlias? = TODO() |
| val myClassNullableAlias : MyClassNullableAlias = TODO() |
| val myClassNullableAlias_nullable : MyClassNullableAlias = TODO() |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(source) |
| ) { invocation -> |
| val converters = CustomConverterProcessor( |
| context = invocation.context, |
| element = invocation.processingEnv.requireTypeElement("MyConverters") |
| ).process().map(::CustomTypeConverterWrapper) |
| val typeAdapterStore = TypeAdapterStore.create( |
| context = invocation.context, |
| builtInConverterFlags = BuiltInConverterFlags.DEFAULT, |
| extras = converters.toTypedArray() |
| ) |
| val subject = invocation.processingEnv.requireTypeElement("Subject") |
| val results = subject.getAllFieldsIncludingPrivateSupers().associate { field -> |
| val binder = typeAdapterStore.findStatementValueBinder( |
| input = field.type, |
| affinity = null |
| ) |
| |
| val signature = when (binder) { |
| null -> null |
| is PrimitiveColumnTypeAdapter -> "primitive" |
| is BoxedPrimitiveColumnTypeAdapter -> "boxed" |
| is CompositeAdapter -> { |
| when (val converter = binder.intoStatementConverter) { |
| null -> "composite null" |
| is CustomTypeConverterWrapper -> converter.custom.method.name |
| else -> "composite unknown" |
| } |
| } |
| else -> "unknown" |
| } |
| field.name to signature |
| } |
| // see: 187359339. We use nullability for assignments only in KSP |
| val nullableClassAdapter = if (invocation.isKsp) { |
| "nullableMyClassToString" |
| } else { |
| "myClassToString" |
| } |
| assertThat(results).containsExactlyEntriesIn( |
| mapOf( |
| "myLongAlias" to "primitive", |
| "myLongAlias_nullable" to "boxed", |
| "myNullableLongAlias" to "boxed", |
| "myNullableLongAlias_nullable" to "boxed", |
| "myClass" to "myClassToString", |
| "myClassAlias" to "myClassToString", |
| "myClassAlias_nullable" to nullableClassAdapter, |
| "myClassNullableAlias" to nullableClassAdapter, |
| "myClassNullableAlias_nullable" to nullableClassAdapter, |
| ) |
| ) |
| } |
| } |
| |
| @Test |
| fun testEqualsAndHashcodeImplemented() { |
| val classExtendsClassWithEqualsAndHashcodeFunctions = Source.java( |
| "foo.bar.Human", |
| """ |
| package foo.bar; |
| public class Human extends Username { |
| public String relationId; |
| } |
| """.trimIndent() |
| ) |
| val classWithFncs = Source.java( |
| "foo.bar.Username", |
| """ |
| package foo.bar; |
| public class Username extends Person { |
| public String name; |
| @Override |
| public boolean equals(Object o) { |
| return false; |
| } |
| @Override |
| public int hashCode() { |
| return 0; |
| } |
| } |
| """.trimIndent() |
| ) |
| val classWithoutFncs = Source.java( |
| "foo.bar.Person", |
| """ |
| package foo.bar; |
| public class Person { |
| public String userId; |
| } |
| """.trimIndent() |
| ) |
| val enumClass = Source.java( |
| "foo.bar.Names", |
| """ |
| package foo.bar; |
| public enum Names { |
| ELLA, |
| BOB, |
| JAMES |
| } |
| """.trimIndent() |
| ) |
| val classWithWrongFncs = Source.java( |
| "foo.bar.UsernameWithWrongFncs", |
| """ |
| package foo.bar; |
| public class UsernameWithWrongFncs { |
| public String name; |
| public boolean equals() { |
| return true; |
| } |
| public int hashCode(int num) { |
| return num; |
| } |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf( |
| classExtendsClassWithEqualsAndHashcodeFunctions, |
| classWithFncs, |
| classWithoutFncs, |
| enumClass, |
| classWithWrongFncs |
| ) |
| ) { invocation -> |
| val enumCase = invocation.processingEnv.requireTypeElement("foo.bar.Names") |
| val inheritedCase = invocation.processingEnv.requireTypeElement("foo.bar.Human") |
| val wrongFunctionsCase = invocation.processingEnv.requireTypeElement( |
| "foo.bar.UsernameWithWrongFncs" |
| ) |
| val noEqualsOrHashcodeCase = invocation.processingEnv.requireTypeElement( |
| "foo.bar.Person" |
| ) |
| assertThat(enumCase.type.implementsEqualsAndHashcode()).isTrue() |
| assertThat(inheritedCase.type.implementsEqualsAndHashcode()).isTrue() |
| assertThat(wrongFunctionsCase.type.implementsEqualsAndHashcode()).isFalse() |
| assertThat(noEqualsOrHashcodeCase.type.implementsEqualsAndHashcode()).isFalse() |
| } |
| } |
| |
| @Test |
| fun testEqualsAndHashcodeCheckWithJavaPrimitive() { |
| val inputSource = Source.java( |
| "foo.bar.Subject", |
| """ |
| package foo.bar; |
| public class Subject { |
| public int primitiveInt = 0; |
| public Integer boxedInt = 1; |
| public boolean primitiveBool = true; |
| public Boolean boxedBool = false; |
| public double primitiveDouble = 2.2; |
| public Double boxedDouble = 3.3; |
| public long primitiveLong = 4L; |
| public Long boxedLong = 5L; |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf( |
| inputSource, |
| COMMON.USER, |
| COMMON.PAGING_SOURCE, |
| COMMON.LIMIT_OFFSET_PAGING_SOURCE, |
| ), |
| ) { invocation -> |
| val subjectTypeElement = |
| invocation.processingEnv.requireTypeElement("foo.bar.Subject") |
| subjectTypeElement.getAllFieldsIncludingPrivateSupers().forEach { field -> |
| assertThat(field.type.implementsEqualsAndHashcode()).isTrue() |
| } |
| } |
| } |
| |
| @Test |
| fun testEqualsAndHashcodeCheckWithKotlinPrimitive() { |
| val source = Source.kotlin( |
| "Foo.kt", |
| """ |
| import androidx.room.* |
| class Subject { |
| val anInteger = 0 |
| val aBoolean = true |
| val aDouble = 2.2 |
| val aLong = 5L |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(source) |
| ) { invocation -> |
| val subjectTypeElement = invocation.processingEnv.requireTypeElement("Subject") |
| |
| subjectTypeElement.getDeclaredFields().forEach { |
| assertThat(it.type.implementsEqualsAndHashcode()).isTrue() |
| } |
| } |
| } |
| |
| private fun createIntListToStringBinders(invocation: XTestInvocation): List<TypeConverter> { |
| val intType = invocation.processingEnv.requireType(Integer::class) |
| val listElement = invocation.processingEnv.requireTypeElement(java.util.List::class) |
| val listOfInts = invocation.processingEnv.getDeclaredType(listElement, intType) |
| val intListConverter = object : SingleStatementTypeConverter( |
| listOfInts, |
| invocation.context.COMMON_TYPES.STRING |
| ) { |
| override fun buildStatement(inputVarName: String, scope: CodeGenScope): XCodeBlock { |
| return XCodeBlock.of( |
| scope.language, |
| "%T.joinIntoString(%L)", |
| STRING_UTIL, |
| inputVarName |
| ) |
| } |
| } |
| |
| val stringToIntListConverter = object : SingleStatementTypeConverter( |
| invocation.context.COMMON_TYPES.STRING, listOfInts |
| ) { |
| override fun buildStatement(inputVarName: String, scope: CodeGenScope): XCodeBlock { |
| return XCodeBlock.of( |
| scope.language, |
| "%T.splitToIntList(%L)", |
| STRING_UTIL, |
| inputVarName |
| ) |
| } |
| } |
| return listOf(intListConverter, stringToIntListConverter) |
| } |
| |
| private fun dateTypeConverters(env: XProcessingEnv): List<TypeConverter> { |
| val tDate = env.requireType("java.util.Date").makeNullable() |
| val tLong = env.requireType("java.lang.Long").makeNullable() |
| return listOf( |
| object : SingleStatementTypeConverter(tDate, tLong) { |
| override fun buildStatement(inputVarName: String, scope: CodeGenScope): XCodeBlock { |
| return XCodeBlock.of(scope.language, "%L.time", inputVarName) |
| } |
| }, |
| object : SingleStatementTypeConverter(tLong, tDate) { |
| override fun buildStatement(inputVarName: String, scope: CodeGenScope): XCodeBlock { |
| return XCodeBlock.ofNewInstance( |
| scope.language, |
| tDate.asTypeName(), |
| "%L", |
| inputVarName |
| ) |
| } |
| } |
| ) |
| } |
| } |