1 /*
<lambda>null2  * Copyright (C) 2017 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.L
20 import androidx.room.ext.S
21 import androidx.room.ext.T
22 import androidx.room.processor.Context
23 import androidx.room.processor.ProcessorErrors
24 import androidx.room.solver.CodeGenScope
25 import androidx.room.verifier.QueryResultInfo
26 import androidx.room.vo.Field
27 import androidx.room.vo.FieldWithIndex
28 import androidx.room.vo.Pojo
29 import androidx.room.vo.RelationCollector
30 import androidx.room.vo.Warning
31 import androidx.room.writer.FieldReadWriteWriter
32 import com.squareup.javapoet.TypeName
33 import stripNonJava
34 import javax.lang.model.type.TypeMirror
35 
36 /**
37  * Creates the entity from the given info.
38  * <p>
39  * The info comes from the query processor so we know about the order of columns in the result etc.
40  */
41 class PojoRowAdapter(
42         context: Context, private val info: QueryResultInfo?,
43         val pojo: Pojo, out: TypeMirror) : RowAdapter(out) {
44     val mapping: Mapping
45     val relationCollectors: List<RelationCollector>
46 
47     init {
48         // toMutableList documentation is not clear if it copies so lets be safe.
49         val remainingFields = pojo.fields.mapTo(mutableListOf(), { it })
50         val unusedColumns = arrayListOf<String>()
51         val matchedFields: List<Field>
52         if (info != null) {
53             matchedFields = info.columns.mapNotNull { column ->
54                 // first check remaining, otherwise check any. maybe developer wants to map the same
55                 // column into 2 fields. (if they want to post process etc)
56                 val field = remainingFields.firstOrNull { it.columnName == column.name } ?:
57                         pojo.fields.firstOrNull { it.columnName == column.name }
58                 if (field == null) {
59                     unusedColumns.add(column.name)
60                     null
61                 } else {
62                     remainingFields.remove(field)
63                     field
64                 }
65             }
66             if (unusedColumns.isNotEmpty() || remainingFields.isNotEmpty()) {
67                 val warningMsg = ProcessorErrors.cursorPojoMismatch(
68                         pojoTypeName = pojo.typeName,
69                         unusedColumns = unusedColumns,
70                         allColumns = info.columns.map { it.name },
71                         unusedFields = remainingFields,
72                         allFields = pojo.fields
73                 )
74                 context.logger.w(Warning.CURSOR_MISMATCH, null, warningMsg)
75             }
76             val nonNulls = remainingFields.filter { it.nonNull }
77             if (nonNulls.isNotEmpty()) {
78                 context.logger.e(ProcessorErrors.pojoMissingNonNull(
79                         pojoTypeName = pojo.typeName,
80                         missingPojoFields = nonNulls.map { it.name },
81                         allQueryColumns = info.columns.map { it.name }))
82             }
83             if (matchedFields.isEmpty()) {
84                 context.logger.e(ProcessorErrors.CANNOT_FIND_QUERY_RESULT_ADAPTER)
85             }
86         } else {
87             matchedFields = remainingFields.map { it }
88             remainingFields.clear()
89         }
90         relationCollectors = RelationCollector.createCollectors(context, pojo.relations)
91 
92         mapping = Mapping(
93                 matchedFields = matchedFields,
94                 unusedColumns = unusedColumns,
95                 unusedFields = remainingFields
96         )
97     }
98 
99     fun relationTableNames(): List<String> {
100         return relationCollectors.flatMap {
101             val queryTableNames = it.loadAllQuery.tables.map { it.name }
102             if (it.rowAdapter is PojoRowAdapter) {
103                 it.rowAdapter.relationTableNames() + queryTableNames
104             } else {
105                 queryTableNames
106             }
107         }.distinct()
108     }
109 
110     override fun onCursorReady(cursorVarName: String, scope: CodeGenScope) {
111         relationCollectors.forEach { it.writeInitCode(scope) }
112         mapping.fieldsWithIndices = mapping.matchedFields.map {
113             val indexVar = scope.getTmpVar("_cursorIndexOf${it.name.stripNonJava().capitalize()}")
114             val indexMethod = if (info == null) {
115                 "getColumnIndex"
116             } else {
117                 "getColumnIndexOrThrow"
118             }
119             scope.builder().addStatement("final $T $L = $L.$L($S)",
120                     TypeName.INT, indexVar, cursorVarName, indexMethod, it.columnName)
121             FieldWithIndex(field = it, indexVar = indexVar, alwaysExists = info != null)
122         }
123     }
124 
125     override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) {
126         scope.builder().apply {
127             FieldReadWriteWriter.readFromCursor(
128                     outVar = outVarName,
129                     outPojo = pojo,
130                     cursorVar = cursorVarName,
131                     fieldsWithIndices = mapping.fieldsWithIndices,
132                     relationCollectors = relationCollectors,
133                     scope = scope)
134         }
135     }
136 
137     override fun onCursorFinished(): ((CodeGenScope) -> Unit)? =
138             if (relationCollectors.isEmpty()) {
139                 // it is important to return empty to notify that we don't need any post process
140                 // task
141                 null
142             } else {
143                 { scope ->
144                     relationCollectors.forEach { collector ->
145                         collector.writeCollectionCode(scope)
146                     }
147                 }
148             }
149 
150     data class Mapping(
151             val matchedFields: List<Field>,
152             val unusedColumns: List<String>,
153             val unusedFields: List<Field>) {
154         // set when cursor is ready.
155         lateinit var fieldsWithIndices: List<FieldWithIndex>
156     }
157 }
158