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.compiler.processing.XType
20 import androidx.room.parser.ParsedQuery
21 import androidx.room.processor.Context
22 import androidx.room.processor.ProcessorErrors
23 import androidx.room.solver.CodeGenScope
24 import androidx.room.verifier.QueryResultInfo
25 import androidx.room.vo.ColumnIndexVar
26 import androidx.room.vo.DataClass
27 import androidx.room.vo.Property
28 import androidx.room.vo.PropertyWithIndex
29 import androidx.room.vo.RelationCollector
30 import androidx.room.writer.PropertyReadWriteWriter
31 
32 /**
33  * Creates the entity from the given info.
34  *
35  * The info comes from the query processor so we know about the order of columns in the result etc.
36  */
37 class DataClassRowAdapter(
38     private val context: Context,
39     private val info: QueryResultInfo?,
40     private val query: ParsedQuery?,
41     val dataClass: DataClass,
42     out: XType
43 ) : QueryMappedRowAdapter(out) {
44     override val mapping: DataClassMapping
45     val relationCollectors: List<RelationCollector>
46 
47     private val indexAdapter: DataClassIndexAdapter
48 
49     // Set when statement is ready.
50     private lateinit var fieldsWithIndices: List<PropertyWithIndex>
51 
52     init {
53         val remainingFields = dataClass.properties.toMutableList()
54         val unusedColumns = arrayListOf<String>()
55         val matchedFields: List<Property>
56         if (info != null) {
57             matchedFields =
58                 info.columns.mapNotNull { column ->
59                     val field = remainingFields.firstOrNull { it.columnName == column.name }
60                     if (field == null) {
61                         unusedColumns.add(column.name)
62                         null
63                     } else {
64                         remainingFields.remove(field)
65                         field
66                     }
67                 }
68             val nonNulls = remainingFields.filter { it.nonNull }
69             if (nonNulls.isNotEmpty()) {
70                 context.logger.e(
71                     ProcessorErrors.dataClassMissingNonNull(
72                         dataClassTypeName = dataClass.typeName.toString(context.codeLanguage),
73                         missingDataClassProperties = nonNulls.map { it.name },
74                         allQueryColumns = info.columns.map { it.name }
75                     )
76                 )
77             }
78             if (matchedFields.isEmpty()) {
79                 context.logger.e(
80                     ProcessorErrors.cannotFindQueryResultAdapter(
81                         out.asTypeName().toString(context.codeLanguage)
82                     )
83                 )
84             }
85         } else {
86             matchedFields = remainingFields.map { it }
87             remainingFields.clear()
88         }
89         relationCollectors = RelationCollector.createCollectors(context, dataClass.relations)
90 
91         mapping =
92             DataClassMapping(
93                 dataClass = dataClass,
94                 matchedFields = matchedFields,
95                 unusedColumns = unusedColumns,
96                 unusedFields = remainingFields
97             )
98 
99         indexAdapter = DataClassIndexAdapter(mapping, info, query)
100     }
101 
102     fun relationTableNames(): List<String> {
103         return relationCollectors
104             .flatMap {
105                 val queryTableNames = it.loadAllQuery.tables.map { it.name }
106                 if (it.rowAdapter is DataClassRowAdapter) {
107                     it.rowAdapter.relationTableNames() + queryTableNames
108                 } else {
109                     queryTableNames
110                 }
111             }
112             .distinct()
113     }
114 
115     override fun onStatementReady(
116         stmtVarName: String,
117         scope: CodeGenScope,
118         indices: List<ColumnIndexVar>
119     ) {
120         fieldsWithIndices =
121             indices.map { (column, indexVar) ->
122                 val field = mapping.matchedFields.first { it.columnName == column }
123                 PropertyWithIndex(
124                     property = field,
125                     indexVar = indexVar,
126                     alwaysExists = info != null
127                 )
128             }
129         emitRelationCollectorsReady(stmtVarName, scope)
130     }
131 
132     private fun emitRelationCollectorsReady(stmtVarName: String, scope: CodeGenScope) {
133         if (relationCollectors.isNotEmpty()) {
134             relationCollectors.forEach { it.writeInitCode(scope) }
135             scope.builder.apply {
136                 beginControlFlow("while (%L.step())", stmtVarName).apply {
137                     relationCollectors.forEach {
138                         it.writeReadParentKeyCode(stmtVarName, fieldsWithIndices, scope)
139                     }
140                 }
141                 endControlFlow()
142                 addStatement("%L.reset()", stmtVarName)
143             }
144             relationCollectors.forEach { it.writeFetchRelationCall(scope) }
145         }
146     }
147 
148     override fun convert(outVarName: String, stmtVarName: String, scope: CodeGenScope) {
149         PropertyReadWriteWriter.readFromStatement(
150             outVar = outVarName,
151             outDataClass = dataClass,
152             stmtVar = stmtVarName,
153             propertiesWithIndices = fieldsWithIndices,
154             relationCollectors = relationCollectors,
155             scope = scope
156         )
157     }
158 
159     override fun getDefaultIndexAdapter() = indexAdapter
160 
161     data class DataClassMapping(
162         val dataClass: DataClass,
163         val matchedFields: List<Property>,
164         val unusedColumns: List<String>,
165         val unusedFields: List<Property>
166     ) : Mapping() {
167         override val usedColumns = matchedFields.map { it.columnName }
168     }
169 }
170