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