Added ImmutableList as a possible return collection type for room

Bug:146107232

Test: unit test passes, also exported to my app and played with it a bit.
Change-Id: If4f349450823a7a475c05698ba91c631494b6d73
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt b/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
index f528444..c652658 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
@@ -54,6 +54,7 @@
 import androidx.room.solver.query.result.ArrayQueryResultAdapter
 import androidx.room.solver.query.result.EntityRowAdapter
 import androidx.room.solver.query.result.GuavaOptionalQueryResultAdapter
+import androidx.room.solver.query.result.ImmutableListQueryResultAdapter
 import androidx.room.solver.query.result.InstantQueryResultBinder
 import androidx.room.solver.query.result.ListQueryResultAdapter
 import androidx.room.solver.query.result.OptionalQueryResultAdapter
@@ -98,6 +99,7 @@
 import com.google.auto.common.MoreElements
 import com.google.auto.common.MoreTypes
 import com.google.common.annotations.VisibleForTesting
+import com.google.common.collect.ImmutableList
 import java.util.LinkedList
 import javax.lang.model.type.ArrayType
 import javax.lang.model.type.TypeKind
@@ -409,6 +411,10 @@
                 val typeArg = declared.typeArguments.first()
                 val rowAdapter = findRowAdapter(typeArg, query) ?: return null
                 return OptionalQueryResultAdapter(SingleEntityQueryResultAdapter(rowAdapter))
+            } else if (MoreTypes.isTypeOf(ImmutableList::class.java, typeMirror)) {
+                val typeArg = declared.typeArguments.first().extendsBoundOrSelf()
+                val rowAdapter = findRowAdapter(typeArg, query) ?: return null
+                return ImmutableListQueryResultAdapter(rowAdapter)
             } else if (MoreTypes.isTypeOf(java.util.List::class.java, typeMirror)) {
                 val typeArg = declared.typeArguments.first().extendsBoundOrSelf()
                 val rowAdapter = findRowAdapter(typeArg, query) ?: return null
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/ImmutableListQueryResultAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/ImmutableListQueryResultAdapter.kt
new file mode 100644
index 0000000..33dd5ab
--- /dev/null
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/ImmutableListQueryResultAdapter.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.solver.query.result
+
+import androidx.room.ext.L
+import androidx.room.ext.T
+import androidx.room.ext.typeName
+import androidx.room.solver.CodeGenScope
+import com.google.common.collect.ImmutableList
+import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.ParameterizedTypeName
+
+class ImmutableListQueryResultAdapter(rowAdapter: RowAdapter) : QueryResultAdapter(rowAdapter) {
+    val type = rowAdapter.out
+    override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
+        scope.builder().apply {
+            rowAdapter?.onCursorReady(cursorVarName, scope)
+            val collectionType = ParameterizedTypeName
+                .get(ClassName.get(ImmutableList::class.java), type.typeName())
+            val immutableListBuilderType = ParameterizedTypeName
+                .get(ClassName.get(ImmutableList.Builder::class.java), type.typeName())
+            val immutableListBuilderName = scope.getTmpVar("_immutableListBuilder")
+            addStatement("final $T $L = $T.<$T>builder()",
+                immutableListBuilderType, immutableListBuilderName,
+                ClassName.get(ImmutableList::class.java), type.typeName())
+            val tmpVarName = scope.getTmpVar("_item")
+            beginControlFlow("while($L.moveToNext())", cursorVarName).apply {
+                addStatement("final $T $L", type.typeName(), tmpVarName)
+                rowAdapter?.convert(tmpVarName, cursorVarName, scope)
+                addStatement("$L.add($L)", immutableListBuilderName, tmpVarName)
+            }
+            endControlFlow()
+            addStatement("final $T $L = $L.build()",
+                collectionType, outVarName, immutableListBuilderName)
+            rowAdapter?.onCursorFinished()?.invoke(scope)
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt b/room/compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
index 09a37e8..2b8ba75 100644
--- a/room/compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/solver/query/QueryWriterTest.kt
@@ -44,6 +44,7 @@
                 package foo.bar;
                 import androidx.room.*;
                 import java.util.*;
+                import com.google.common.collect.ImmutableList;
                 @Dao
                 abstract class MyClass {
                 """
@@ -174,6 +175,18 @@
     }
 
     @Test
+    fun aLongAndIntegerImmutableList() {
+        singleQueryMethod("""
+                @Query("SELECT id FROM users WHERE id IN(:ids) AND age > :time")
+                abstract ImmutableList<Integer> selectAllIds(long time, List<Integer> ids);
+                """) { writer ->
+            val scope = testCodeGenScope()
+            writer.prepareReadAndBind("_sql", "_stmt", scope)
+            assertThat(scope.generate().toString().trim(), `is`(collectionOut))
+        }.compilesWithoutError()
+    }
+
+    @Test
     fun aLongAndIntegerSet() {
         singleQueryMethod("""
                 @Query("SELECT id FROM users WHERE id IN(:ids) AND age > :time")
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/BlobEntityDao.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/BlobEntityDao.java
index 3c582fd..205699a 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/BlobEntityDao.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/dao/BlobEntityDao.java
@@ -21,6 +21,8 @@
 import androidx.room.Query;
 import androidx.room.integration.testapp.vo.BlobEntity;
 
+import com.google.common.collect.ImmutableList;
+
 import java.util.List;
 
 @Dao
@@ -32,6 +34,9 @@
     @Query("SELECT * FROM BlobEntity")
     List<BlobEntity> selectAll();
 
+    @Query("SELECT * FROM BlobEntity")
+    ImmutableList<BlobEntity> selectAllImmutable();
+
     @Query("SELECT content FROM BlobEntity WHERE id = :id")
     byte[] getContent(long id);
 
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SimpleEntityReadWriteTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SimpleEntityReadWriteTest.java
index fa94e6b..8530604 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SimpleEntityReadWriteTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/SimpleEntityReadWriteTest.java
@@ -55,6 +55,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -493,11 +494,24 @@
         mBlobEntityDao.insert(b);
         List<BlobEntity> list = mBlobEntityDao.selectAll();
         assertThat(list, hasSize(2));
+        ImmutableList<BlobEntity> immutableList = mBlobEntityDao.selectAllImmutable();
+        assertThat(immutableList, hasSize(2));
         mBlobEntityDao.updateContent(2, "ghi".getBytes(Charsets.UTF_8));
         assertThat(mBlobEntityDao.getContent(2), is(equalTo("ghi".getBytes(Charsets.UTF_8))));
     }
 
     @Test
+    public void blobImmutable() {
+        BlobEntity a = new BlobEntity(1, "abc".getBytes(Charsets.UTF_8));
+        BlobEntity b = new BlobEntity(2, "def".getBytes(Charsets.UTF_8));
+        mBlobEntityDao.insert(a);
+        mBlobEntityDao.insert(b);
+        ImmutableList<BlobEntity> immutableList = mBlobEntityDao.selectAllImmutable();
+        assertThat(immutableList, hasSize(2));
+        assertThat(immutableList.get(1).content, is(equalTo("def".getBytes(Charsets.UTF_8))));
+    }
+
+    @Test
     public void transactionByRunnable() {
         User a = TestUtil.createUser(3);
         User b = TestUtil.createUser(5);