1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.room.solver.query.result
18 
19 import androidx.room.ext.AndroidTypeNames
20 import androidx.room.ext.L
21 import androidx.room.ext.N
22 import androidx.room.ext.RoomGuavaTypeNames
23 import androidx.room.ext.T
24 import androidx.room.ext.typeName
25 import androidx.room.solver.CodeGenScope
26 import androidx.room.writer.DaoWriter
27 import com.squareup.javapoet.FieldSpec
28 import com.squareup.javapoet.MethodSpec
29 import com.squareup.javapoet.ParameterizedTypeName
30 import com.squareup.javapoet.TypeSpec
31 import javax.lang.model.element.Modifier
32 import javax.lang.model.type.TypeMirror
33 
34 /**
35  * A ResultBinder that emits a ListenableFuture<T> where T is the input {@code typeArg}.
36  *
37  * <p>The Future runs on the background thread Executor.
38  */
39 class GuavaListenableFutureQueryResultBinder(
40         val typeArg: TypeMirror,
41         adapter: QueryResultAdapter?)
42     : BaseObservableQueryResultBinder(adapter) {
43 
convertAndReturnnull44     override fun convertAndReturn(
45             roomSQLiteQueryVar: String,
46             canReleaseQuery: Boolean,
47             dbField: FieldSpec,
48             inTransaction: Boolean,
49             scope: CodeGenScope) {
50         // Callable<T>
51         val callableImpl = createCallableOfT(
52                 roomSQLiteQueryVar,
53                 dbField,
54                 inTransaction,
55                 scope)
56 
57         scope.builder().apply {
58             addStatement(
59                     "return $T.createListenableFuture($L, $L, $L)",
60                     RoomGuavaTypeNames.GUAVA_ROOM,
61                     callableImpl,
62                     roomSQLiteQueryVar,
63                     canReleaseQuery)
64         }
65     }
66 
67     /**
68      * Returns an anonymous subclass of Callable<T> that executes the database transaction and
69      * constitutes the result T.
70      *
71      * <p>Note that this method does not release the query object.
72      */
createCallableOfTnull73     private fun createCallableOfT(
74             roomSQLiteQueryVar: String,
75             dbField: FieldSpec,
76             inTransaction: Boolean,
77             scope: CodeGenScope): TypeSpec {
78         return TypeSpec.anonymousClassBuilder("").apply {
79             superclass(
80                     ParameterizedTypeName.get(java.util.concurrent.Callable::class.typeName(),
81                             typeArg.typeName()))
82             addMethod(
83                     MethodSpec.methodBuilder("call").apply {
84                         // public T call() throws Exception {}
85                         returns(typeArg.typeName())
86                         addAnnotation(Override::class.typeName())
87                         addModifiers(Modifier.PUBLIC)
88                         addException(Exception::class.typeName())
89 
90                         // Body.
91                         val transactionWrapper = if (inTransaction) {
92                             transactionWrapper(dbField)
93                         } else {
94                             null
95                         }
96                         transactionWrapper?.beginTransactionWithControlFlow()
97                         apply {
98                             val outVar = scope.getTmpVar("_result")
99                             val cursorVar = scope.getTmpVar("_cursor")
100                             addStatement("final $T $L = $N.query($L)", AndroidTypeNames.CURSOR,
101                                     cursorVar,
102                                     DaoWriter.dbField, roomSQLiteQueryVar)
103                             beginControlFlow("try").apply {
104                                 val adapterScope = scope.fork()
105                                 adapter?.convert(outVar, cursorVar, adapterScope)
106                                 addCode(adapterScope.builder().build())
107                                 transactionWrapper?.commitTransaction()
108                                 addStatement("return $L", outVar)
109                             }
110                             nextControlFlow("finally").apply {
111                                 addStatement("$L.close()", cursorVar)
112                             }
113                             endControlFlow()
114                         }
115                         transactionWrapper?.endTransactionWithControlFlow()
116                     }.build())
117         }.build()
118     }
119 }
120