| /* |
| * 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.Entity |
| import androidx.room.ext.GuavaUtilConcurrentTypeNames |
| import androidx.room.ext.L |
| import androidx.room.ext.LifecyclesTypeNames |
| import androidx.room.ext.PagingTypeNames |
| import androidx.room.ext.ReactiveStreamsTypeNames |
| import androidx.room.ext.RoomTypeNames.STRING_UTIL |
| import androidx.room.ext.RxJava2TypeNames |
| import androidx.room.ext.RxJava3TypeNames |
| import androidx.room.ext.T |
| import androidx.room.parser.SQLTypeAffinity |
| import androidx.room.compiler.processing.XProcessingEnv |
| import androidx.room.compiler.processing.asDeclaredType |
| import androidx.room.processor.Context |
| import androidx.room.processor.ProcessorErrors |
| import androidx.room.solver.binderprovider.DataSourceFactoryQueryResultBinderProvider |
| import androidx.room.solver.binderprovider.DataSourceQueryResultBinderProvider |
| import androidx.room.solver.binderprovider.LiveDataQueryResultBinderProvider |
| import androidx.room.solver.binderprovider.PagingSourceQueryResultBinderProvider |
| import androidx.room.solver.binderprovider.RxQueryResultBinderProvider |
| import androidx.room.solver.shortcut.binderprovider.GuavaListenableFutureDeleteOrUpdateMethodBinderProvider |
| import androidx.room.solver.shortcut.binderprovider.GuavaListenableFutureInsertMethodBinderProvider |
| import androidx.room.solver.shortcut.binderprovider.RxCallableDeleteOrUpdateMethodBinderProvider |
| import androidx.room.solver.shortcut.binderprovider.RxCallableInsertMethodBinderProvider |
| import androidx.room.solver.types.CompositeAdapter |
| import androidx.room.solver.types.TypeConverter |
| import androidx.room.testing.TestInvocation |
| import androidx.room.testing.TestProcessor |
| import com.google.common.truth.Truth |
| import com.google.testing.compile.CompileTester |
| import com.google.testing.compile.JavaFileObjects |
| import com.google.testing.compile.JavaSourcesSubjectFactory |
| import com.squareup.javapoet.TypeName |
| import org.hamcrest.CoreMatchers.`is` |
| import org.hamcrest.CoreMatchers.instanceOf |
| 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 simpleRun |
| import testCodeGenScope |
| |
| @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") |
| @RunWith(JUnit4::class) |
| class TypeAdapterStoreTest { |
| companion object { |
| fun tmp(index: Int) = CodeGenScope._tmpVar(index) |
| } |
| |
| @Test |
| fun testDirect() { |
| singleRun { invocation -> |
| val store = TypeAdapterStore.create(Context(invocation.processingEnv)) |
| val primitiveType = invocation.processingEnv.requireType(TypeName.INT) |
| val adapter = store.findColumnTypeAdapter(primitiveType, null) |
| assertThat(adapter, notNullValue()) |
| }.compilesWithoutError() |
| } |
| |
| @Test |
| fun testJavaLangBoolean() { |
| singleRun { invocation -> |
| val store = TypeAdapterStore.create(Context(invocation.processingEnv)) |
| val boolean = invocation |
| .processingEnv |
| .requireType("java.lang.Boolean") |
| val adapter = store.findColumnTypeAdapter(boolean, null) |
| assertThat(adapter, notNullValue()) |
| assertThat(adapter, instanceOf(CompositeAdapter::class.java)) |
| val composite = adapter as CompositeAdapter |
| assertThat(composite.intoStatementConverter?.from?.typeName, |
| `is`(TypeName.BOOLEAN.box())) |
| assertThat(composite.columnTypeAdapter.out.typeName, |
| `is`(TypeName.INT.box())) |
| }.compilesWithoutError() |
| } |
| |
| @Test |
| fun testVia1TypeAdapter() { |
| singleRun { invocation -> |
| val store = TypeAdapterStore.create(Context(invocation.processingEnv)) |
| val booleanType = invocation.processingEnv.requireType(TypeName.BOOLEAN) |
| val adapter = store.findColumnTypeAdapter(booleanType, null) |
| 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)}; |
| ${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())) |
| }.compilesWithoutError() |
| } |
| |
| @Test |
| fun testVia2TypeAdapters() { |
| singleRun { invocation -> |
| val store = TypeAdapterStore.create(Context(invocation.processingEnv), |
| pointTypeConverters(invocation.processingEnv)) |
| val pointType = invocation.processingEnv.requireType("foo.bar.Point") |
| val adapter = store.findColumnTypeAdapter(pointType, null) |
| 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)}; |
| final boolean ${tmp(1)}; |
| ${tmp(1)} = foo.bar.Point.toBoolean(fooVar); |
| ${tmp(0)} = ${tmp(1)} ? 1 : 0; |
| stmt.bindLong(41, ${tmp(0)}); |
| """.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(1)} = ${tmp(0)} != 0; |
| res = foo.bar.Point.fromBoolean(${tmp(1)}); |
| """.trimIndent())) |
| }.compilesWithoutError() |
| } |
| |
| @Test |
| fun testDate() { |
| singleRun { (processingEnv) -> |
| val store = TypeAdapterStore.create(Context(processingEnv), |
| dateTypeConverters(processingEnv)) |
| val tDate = processingEnv.requireType("java.util.Date") |
| 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); |
| } |
| // convert Long to Date; |
| """.trimIndent())) |
| }.compilesWithoutError() |
| } |
| |
| @Test |
| fun testIntList() { |
| singleRun { invocation -> |
| val binders = createIntListToStringBinders(invocation) |
| val store = TypeAdapterStore.create(Context(invocation.processingEnv), binders[0], |
| binders[1]) |
| |
| val adapter = store.findColumnTypeAdapter(binders[0].from, null) |
| assertThat(adapter, notNullValue()) |
| |
| val bindScope = testCodeGenScope() |
| adapter!!.bindToStmt("stmt", "41", "fooVar", bindScope) |
| assertThat(bindScope.generate().toString().trim(), `is`(""" |
| final java.lang.String ${tmp(0)}; |
| ${tmp(0)} = androidx.room.util.StringUtil.joinIntoString(fooVar); |
| if (${tmp(0)} == null) { |
| stmt.bindNull(41); |
| } else { |
| stmt.bindString(41, ${tmp(0)}); |
| } |
| """.trimIndent())) |
| |
| val converter = store.findTypeConverter(binders[0].from, |
| invocation.context.COMMON_TYPES.STRING) |
| assertThat(converter, notNullValue()) |
| assertThat(store.reverse(converter!!), `is`(binders[1])) |
| }.compilesWithoutError() |
| } |
| |
| @Test |
| fun testOneWayConversion() { |
| singleRun { invocation -> |
| val binders = createIntListToStringBinders(invocation) |
| val store = TypeAdapterStore.create(Context(invocation.processingEnv), binders[0]) |
| val adapter = store.findColumnTypeAdapter(binders[0].from, null) |
| assertThat(adapter, nullValue()) |
| |
| val stmtBinder = store.findStatementValueBinder(binders[0].from, null) |
| assertThat(stmtBinder, notNullValue()) |
| |
| val converter = store.findTypeConverter(binders[0].from, |
| invocation.context.COMMON_TYPES.STRING) |
| assertThat(converter, notNullValue()) |
| assertThat(store.reverse(converter!!), nullValue()) |
| } |
| } |
| |
| @Test |
| fun testMissingRx2Room() { |
| simpleRun(jfos = *arrayOf(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.asDeclaredType()) |
| }, `is`(true)) |
| }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_ROOM_RXJAVA2_ARTIFACT) |
| } |
| |
| @Test |
| fun testMissingRx3Room() { |
| simpleRun(jfos = *arrayOf(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.asDeclaredType()) |
| }, `is`(true)) |
| }.failsToCompile().withErrorContaining(ProcessorErrors.MISSING_ROOM_RXJAVA3_ARTIFACT) |
| } |
| |
| @Test |
| fun testFindPublisher() { |
| listOf( |
| COMMON.RX2_FLOWABLE to COMMON.RX2_ROOM, |
| COMMON.RX3_FLOWABLE to COMMON.RX3_ROOM |
| ).forEach { (rxTypeSrc, rxRoomSrc) -> |
| simpleRun(jfos = *arrayOf(COMMON.PUBLISHER, rxTypeSrc, rxRoomSrc)) { |
| invocation -> |
| val publisher = invocation.processingEnv |
| .requireTypeElement(ReactiveStreamsTypeNames.PUBLISHER) |
| assertThat(publisher, notNullValue()) |
| assertThat( |
| RxQueryResultBinderProvider.getAll(invocation.context).any { |
| it.matches(publisher.asDeclaredType()) |
| }, `is`(true)) |
| }.compilesWithoutError() |
| } |
| } |
| |
| @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) -> |
| simpleRun(jfos = *arrayOf(COMMON.PUBLISHER, rxTypeSrc, rxRoomSrc)) { |
| invocation -> |
| val flowable = invocation.processingEnv.requireTypeElement(rxTypeClassName) |
| assertThat( |
| RxQueryResultBinderProvider.getAll(invocation.context).any { |
| it.matches(flowable.asDeclaredType()) |
| }, `is`(true)) |
| }.compilesWithoutError() |
| } |
| } |
| |
| @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) -> |
| simpleRun(jfos = *arrayOf(rxTypeSrc, rxRoomSrc)) { |
| invocation -> |
| val observable = invocation.processingEnv.requireTypeElement(rxTypeClassName) |
| assertThat(observable, notNullValue()) |
| assertThat( |
| RxQueryResultBinderProvider.getAll(invocation.context).any { |
| it.matches(observable.asDeclaredType()) |
| }, `is`(true)) |
| }.compilesWithoutError() |
| } |
| } |
| |
| @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) -> |
| simpleRun(jfos = *arrayOf(rxTypeSrc)) { |
| invocation -> |
| val single = invocation.processingEnv.requireTypeElement(rxTypeClassName) |
| assertThat(single, notNullValue()) |
| assertThat( |
| RxCallableInsertMethodBinderProvider.getAll(invocation.context).any { |
| it.matches(single.asDeclaredType()) |
| }, `is`(true)) |
| }.compilesWithoutError() |
| } |
| } |
| |
| @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) -> |
| simpleRun(jfos = *arrayOf(rxTypeSrc)) { |
| invocation -> |
| val maybe = invocation.processingEnv.requireTypeElement(rxTypeClassName) |
| assertThat( |
| RxCallableInsertMethodBinderProvider.getAll(invocation.context).any { |
| it.matches(maybe.asDeclaredType()) |
| }, `is`(true)) |
| }.compilesWithoutError() |
| } |
| } |
| |
| @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) -> |
| simpleRun(jfos = *arrayOf(rxTypeSrc)) { |
| invocation -> |
| val completable = invocation.processingEnv.requireTypeElement(rxTypeClassName) |
| assertThat( |
| RxCallableInsertMethodBinderProvider.getAll(invocation.context).any { |
| it.matches(completable.asDeclaredType()) |
| }, `is`(true)) |
| }.compilesWithoutError() |
| } |
| } |
| |
| @Test |
| fun testFindInsertListenableFuture() { |
| simpleRun(jfos = *arrayOf(COMMON.LISTENABLE_FUTURE)) { |
| invocation -> |
| val future = invocation.processingEnv |
| .requireTypeElement(GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE) |
| assertThat(GuavaListenableFutureInsertMethodBinderProvider(invocation.context).matches( |
| future.asDeclaredType()), `is`(true)) |
| }.compilesWithoutError() |
| } |
| |
| @Test |
| fun testFindDeleteOrUpdateSingle() { |
| simpleRun(jfos = *arrayOf(COMMON.RX2_SINGLE)) { |
| invocation -> |
| val single = invocation.processingEnv.requireTypeElement(RxJava2TypeNames.SINGLE) |
| assertThat(single, notNullValue()) |
| assertThat( |
| RxCallableDeleteOrUpdateMethodBinderProvider.getAll(invocation.context).any { |
| it.matches(single.asDeclaredType()) |
| }, `is`(true)) |
| }.compilesWithoutError() |
| } |
| |
| @Test |
| fun testFindDeleteOrUpdateMaybe() { |
| simpleRun(jfos = *arrayOf(COMMON.RX2_MAYBE)) { |
| invocation -> |
| val maybe = invocation.processingEnv.requireTypeElement(RxJava2TypeNames.MAYBE) |
| assertThat(maybe, notNullValue()) |
| assertThat( |
| RxCallableDeleteOrUpdateMethodBinderProvider.getAll(invocation.context).any { |
| it.matches(maybe.asDeclaredType()) |
| }, `is`(true)) |
| }.compilesWithoutError() |
| } |
| |
| @Test |
| fun testFindDeleteOrUpdateCompletable() { |
| simpleRun(jfos = *arrayOf(COMMON.RX2_COMPLETABLE)) { |
| invocation -> |
| val completable = invocation.processingEnv |
| .requireTypeElement(RxJava2TypeNames.COMPLETABLE) |
| assertThat(completable, notNullValue()) |
| assertThat( |
| RxCallableDeleteOrUpdateMethodBinderProvider.getAll(invocation.context).any { |
| it.matches(completable.asDeclaredType()) |
| }, `is`(true)) |
| }.compilesWithoutError() |
| } |
| |
| @Test |
| fun testFindDeleteOrUpdateListenableFuture() { |
| simpleRun(jfos = *arrayOf(COMMON.LISTENABLE_FUTURE)) { |
| invocation -> |
| val future = invocation.processingEnv |
| .requireTypeElement(GuavaUtilConcurrentTypeNames.LISTENABLE_FUTURE) |
| assertThat(future, notNullValue()) |
| assertThat(GuavaListenableFutureDeleteOrUpdateMethodBinderProvider(invocation.context) |
| .matches(future.asDeclaredType()), `is`(true)) |
| }.compilesWithoutError() |
| } |
| |
| @Test |
| fun testFindLiveData() { |
| simpleRun(jfos = *arrayOf(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.asDeclaredType()), `is`(true)) |
| }.compilesWithoutError() |
| } |
| |
| @Test |
| fun findPagingSourceIntKey() { |
| simpleRun { 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.asDeclaredType()), `is`(true)) |
| } |
| } |
| |
| @Test |
| fun findPagingSourceStringKey() { |
| simpleRun { 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.asDeclaredType()), `is`(true)) |
| }.failsToCompile().withErrorContaining(ProcessorErrors.PAGING_SPECIFY_PAGING_SOURCE_TYPE) |
| } |
| |
| @Test |
| fun findDataSource() { |
| simpleRun { |
| invocation -> |
| val dataSource = invocation.processingEnv.requireTypeElement(DataSource::class) |
| assertThat(dataSource, notNullValue()) |
| assertThat(DataSourceQueryResultBinderProvider(invocation.context).matches( |
| dataSource.asDeclaredType()), `is`(true)) |
| }.failsToCompile().withErrorContaining(ProcessorErrors.PAGING_SPECIFY_DATA_SOURCE_TYPE) |
| } |
| |
| @Test |
| fun findPositionalDataSource() { |
| simpleRun { |
| invocation -> |
| @Suppress("DEPRECATION") |
| val dataSource = invocation.processingEnv |
| .requireTypeElement(androidx.paging.PositionalDataSource::class) |
| assertThat(dataSource, notNullValue()) |
| assertThat(DataSourceQueryResultBinderProvider(invocation.context).matches( |
| dataSource.asDeclaredType()), `is`(true)) |
| }.compilesWithoutError() |
| } |
| |
| @Test |
| fun findDataSourceFactory() { |
| simpleRun(jfos = *arrayOf(COMMON.DATA_SOURCE_FACTORY)) { |
| invocation -> |
| val pagedListProvider = invocation.processingEnv |
| .requireTypeElement(PagingTypeNames.DATA_SOURCE_FACTORY) |
| assertThat(pagedListProvider, notNullValue()) |
| assertThat(DataSourceFactoryQueryResultBinderProvider(invocation.context).matches( |
| pagedListProvider.asDeclaredType()), `is`(true)) |
| }.compilesWithoutError() |
| } |
| |
| private fun createIntListToStringBinders(invocation: TestInvocation): 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 : TypeConverter(listOfInts, |
| invocation.context.COMMON_TYPES.STRING) { |
| override fun convert( |
| inputVarName: String, |
| outputVarName: String, |
| scope: CodeGenScope |
| ) { |
| scope.builder().apply { |
| addStatement("$L = $T.joinIntoString($L)", outputVarName, STRING_UTIL, |
| inputVarName) |
| } |
| } |
| } |
| |
| val stringToIntListConverter = object : TypeConverter( |
| invocation.context.COMMON_TYPES.STRING, listOfInts) { |
| override fun convert( |
| inputVarName: String, |
| outputVarName: String, |
| scope: CodeGenScope |
| ) { |
| scope.builder().apply { |
| addStatement("$L = $T.splitToIntList($L)", outputVarName, STRING_UTIL, |
| inputVarName) |
| } |
| } |
| } |
| return listOf(intListConverter, stringToIntListConverter) |
| } |
| |
| fun singleRun(handler: (TestInvocation) -> Unit): CompileTester { |
| return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources()) |
| .that(listOf(JavaFileObjects.forSourceString("foo.bar.DummyClass", |
| """ |
| package foo.bar; |
| import androidx.room.*; |
| @Entity |
| public class DummyClass {} |
| """ |
| ), JavaFileObjects.forSourceString("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; |
| } |
| public static Point fromBoolean(boolean val) { |
| return val ? new Point(1, 1) : new Point(0, 0); |
| } |
| public static boolean toBoolean(Point point) { |
| return point.x > 0; |
| } |
| } |
| """ |
| ))) |
| .processedWith(TestProcessor.builder() |
| .forAnnotations(Entity::class) |
| .nextRunHandler { invocation -> |
| handler(invocation) |
| true |
| } |
| .build()) |
| } |
| |
| fun pointTypeConverters(env: XProcessingEnv): List<TypeConverter> { |
| val tPoint = env.requireType("foo.bar.Point") |
| val tBoolean = env.requireType(TypeName.BOOLEAN) |
| return listOf( |
| object : TypeConverter(tPoint, tBoolean) { |
| override fun convert( |
| inputVarName: String, |
| outputVarName: String, |
| scope: CodeGenScope |
| ) { |
| scope.builder().apply { |
| addStatement("$L = $T.toBoolean($L)", outputVarName, from.typeName, |
| inputVarName) |
| } |
| } |
| }, |
| object : TypeConverter(tBoolean, tPoint) { |
| override fun convert( |
| inputVarName: String, |
| outputVarName: String, |
| scope: CodeGenScope |
| ) { |
| scope.builder().apply { |
| addStatement("$L = $T.fromBoolean($L)", outputVarName, tPoint.typeName, |
| inputVarName) |
| } |
| } |
| } |
| ) |
| } |
| |
| fun dateTypeConverters(env: XProcessingEnv): List<TypeConverter> { |
| val tDate = env.requireType("java.util.Date") |
| val tLong = env.requireType("java.lang.Long") |
| return listOf( |
| object : TypeConverter(tDate, tLong) { |
| override fun convert( |
| inputVarName: String, |
| outputVarName: String, |
| scope: CodeGenScope |
| ) { |
| scope.builder().apply { |
| addStatement("// convert Date to Long") |
| } |
| } |
| }, |
| object : TypeConverter(tLong, tDate) { |
| override fun convert( |
| inputVarName: String, |
| outputVarName: String, |
| scope: CodeGenScope |
| ) { |
| scope.builder().apply { |
| addStatement("// convert Long to Date") |
| } |
| } |
| } |
| ) |
| } |
| } |