| /* |
| * Copyright (C) 2017 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.RoomTypeNames |
| import androidx.room.ext.S |
| import androidx.room.ext.T |
| import androidx.room.compiler.processing.XType |
| import androidx.room.processor.Context |
| import androidx.room.processor.ProcessorErrors |
| import androidx.room.solver.CodeGenScope |
| import androidx.room.verifier.QueryResultInfo |
| import androidx.room.vo.Field |
| import androidx.room.vo.FieldWithIndex |
| import androidx.room.vo.Pojo |
| import androidx.room.vo.RelationCollector |
| import androidx.room.vo.Warning |
| import androidx.room.vo.findFieldByColumnName |
| import androidx.room.writer.FieldReadWriteWriter |
| import com.squareup.javapoet.TypeName |
| import stripNonJava |
| |
| /** |
| * Creates the entity from the given info. |
| * <p> |
| * The info comes from the query processor so we know about the order of columns in the result etc. |
| */ |
| class PojoRowAdapter( |
| context: Context, |
| private val info: QueryResultInfo?, |
| val pojo: Pojo, |
| out: XType |
| ) : RowAdapter(out) { |
| val mapping: Mapping |
| val relationCollectors: List<RelationCollector> |
| |
| init { |
| |
| // toMutableList documentation is not clear if it copies so lets be safe. |
| val remainingFields = pojo.fields.mapTo(mutableListOf(), { it }) |
| val unusedColumns = arrayListOf<String>() |
| val matchedFields: List<Field> |
| if (info != null) { |
| matchedFields = info.columns.mapNotNull { column -> |
| // first check remaining, otherwise check any. maybe developer wants to map the same |
| // column into 2 fields. (if they want to post process etc) |
| val field = remainingFields.firstOrNull { it.columnName == column.name } |
| ?: pojo.findFieldByColumnName(column.name) |
| if (field == null) { |
| unusedColumns.add(column.name) |
| null |
| } else { |
| remainingFields.remove(field) |
| field |
| } |
| } |
| if (unusedColumns.isNotEmpty() || remainingFields.isNotEmpty()) { |
| val warningMsg = ProcessorErrors.cursorPojoMismatch( |
| pojoTypeName = pojo.typeName, |
| unusedColumns = unusedColumns, |
| allColumns = info.columns.map { it.name }, |
| unusedFields = remainingFields, |
| allFields = pojo.fields |
| ) |
| context.logger.w(Warning.CURSOR_MISMATCH, null, warningMsg) |
| } |
| val nonNulls = remainingFields.filter { it.nonNull } |
| if (nonNulls.isNotEmpty()) { |
| context.logger.e( |
| ProcessorErrors.pojoMissingNonNull( |
| pojoTypeName = pojo.typeName, |
| missingPojoFields = nonNulls.map { it.name }, |
| allQueryColumns = info.columns.map { it.name }) |
| ) |
| } |
| if (matchedFields.isEmpty()) { |
| context.logger.e(ProcessorErrors.cannotFindQueryResultAdapter(out.toString())) |
| } |
| } else { |
| matchedFields = remainingFields.map { it } |
| remainingFields.clear() |
| } |
| relationCollectors = RelationCollector.createCollectors(context, pojo.relations) |
| |
| mapping = Mapping( |
| matchedFields = matchedFields, |
| unusedColumns = unusedColumns, |
| unusedFields = remainingFields |
| ) |
| } |
| |
| fun relationTableNames(): List<String> { |
| return relationCollectors.flatMap { |
| val queryTableNames = it.loadAllQuery.tables.map { it.name } |
| if (it.rowAdapter is PojoRowAdapter) { |
| it.rowAdapter.relationTableNames() + queryTableNames |
| } else { |
| queryTableNames |
| } |
| }.distinct() |
| } |
| |
| override fun onCursorReady(cursorVarName: String, scope: CodeGenScope) { |
| mapping.fieldsWithIndices = mapping.matchedFields.map { |
| val indexVar = scope.getTmpVar("_cursorIndexOf${it.name.stripNonJava().capitalize()}") |
| val indexMethod = if (info == null) { |
| "getColumnIndex" |
| } else { |
| "getColumnIndexOrThrow" |
| } |
| scope.builder().addStatement( |
| "final $T $L = $T.$L($L, $S)", |
| TypeName.INT, indexVar, RoomTypeNames.CURSOR_UTIL, indexMethod, cursorVarName, |
| it.columnName |
| ) |
| FieldWithIndex(field = it, indexVar = indexVar, alwaysExists = info != null) |
| } |
| if (relationCollectors.isNotEmpty()) { |
| relationCollectors.forEach { it.writeInitCode(scope) } |
| scope.builder().apply { |
| beginControlFlow("while ($L.moveToNext())", cursorVarName).apply { |
| relationCollectors.forEach { |
| it.writeReadParentKeyCode(cursorVarName, mapping.fieldsWithIndices, scope) |
| } |
| } |
| endControlFlow() |
| } |
| scope.builder().addStatement("$L.moveToPosition(-1)", cursorVarName) |
| relationCollectors.forEach { it.writeCollectionCode(scope) } |
| } |
| } |
| |
| override fun convert(outVarName: String, cursorVarName: String, scope: CodeGenScope) { |
| scope.builder().apply { |
| FieldReadWriteWriter.readFromCursor( |
| outVar = outVarName, |
| outPojo = pojo, |
| cursorVar = cursorVarName, |
| fieldsWithIndices = mapping.fieldsWithIndices, |
| relationCollectors = relationCollectors, |
| scope = scope |
| ) |
| } |
| } |
| |
| data class Mapping( |
| val matchedFields: List<Field>, |
| val unusedColumns: List<String>, |
| val unusedFields: List<Field> |
| ) { |
| // set when cursor is ready. |
| lateinit var fieldsWithIndices: List<FieldWithIndex> |
| } |
| } |