1/* 2 * Copyright (C) 2016 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 17package androidx.room.vo 18 19import androidx.room.ext.isNonNull 20import androidx.room.ext.typeName 21import androidx.room.migration.bundle.FieldBundle 22import androidx.room.parser.Collate 23import androidx.room.parser.SQLTypeAffinity 24import androidx.room.solver.types.CursorValueReader 25import androidx.room.solver.types.StatementValueBinder 26import com.squareup.javapoet.TypeName 27import javax.lang.model.element.Element 28import javax.lang.model.type.TypeMirror 29// used in cache matching, must stay as a data class or implement equals 30data class Field(val element: Element, val name: String, val type: TypeMirror, 31 var affinity: SQLTypeAffinity?, 32 val collate: Collate? = null, 33 val columnName: String = name, 34 /* means that this field does not belong to parent, instead, it belongs to a 35 * embedded child of the main Pojo*/ 36 val parent: EmbeddedField? = null, 37 // index might be removed when being merged into an Entity 38 var indexed: Boolean = false) : HasSchemaIdentity { 39 lateinit var getter: FieldGetter 40 lateinit var setter: FieldSetter 41 // binds the field into a statement 42 var statementBinder: StatementValueBinder? = null 43 // reads this field from a cursor column 44 var cursorValueReader: CursorValueReader? = null 45 val typeName: TypeName by lazy { type.typeName() } 46 47 /** Whether the table column for this field should be NOT NULL */ 48 val nonNull = element.isNonNull() && (parent == null || parent.isNonNullRecursively()) 49 50 override fun getIdKey(): String { 51 // we don't get the collate information from sqlite so ignoring it here. 52 return "$columnName-${affinity?.name ?: SQLTypeAffinity.TEXT.name}-$nonNull" 53 } 54 55 /** 56 * Used when reporting errors on duplicate names 57 */ 58 fun getPath(): String { 59 return if (parent == null) { 60 name 61 } else { 62 "${parent.field.getPath()} > $name" 63 } 64 } 65 66 private val pathWithDotNotation: String by lazy { 67 if (parent == null) { 68 name 69 } else { 70 "${parent.field.pathWithDotNotation}.$name" 71 } 72 } 73 74 /** 75 * List of names that include variations. 76 * e.g. if it is mUser, user is added to the list 77 * or if it is isAdmin, admin is added to the list 78 */ 79 val nameWithVariations by lazy { 80 val result = arrayListOf(name) 81 if (name.length > 1) { 82 if (name.startsWith('_')) { 83 result.add(name.substring(1)) 84 } 85 if (name.startsWith("m") && name[1].isUpperCase()) { 86 result.add(name.substring(1).decapitalize()) 87 } 88 89 if (typeName == TypeName.BOOLEAN || typeName == TypeName.BOOLEAN.box()) { 90 if (name.length > 2 && name.startsWith("is") && name[2].isUpperCase()) { 91 result.add(name.substring(2).decapitalize()) 92 } 93 if (name.length > 3 && name.startsWith("has") && name[3].isUpperCase()) { 94 result.add(name.substring(3).decapitalize()) 95 } 96 } 97 } 98 result 99 } 100 101 val getterNameWithVariations by lazy { 102 nameWithVariations.map { "get${it.capitalize()}" } + 103 if (typeName == TypeName.BOOLEAN || typeName == TypeName.BOOLEAN.box()) { 104 nameWithVariations.flatMap { 105 listOf("is${it.capitalize()}", "has${it.capitalize()}") 106 } 107 } else { 108 emptyList() 109 } 110 } 111 112 val setterNameWithVariations by lazy { 113 nameWithVariations.map { "set${it.capitalize()}" } 114 } 115 116 /** 117 * definition to be used in create query 118 */ 119 fun databaseDefinition(autoIncrementPKey: Boolean): String { 120 val columnSpec = StringBuilder("") 121 if (autoIncrementPKey) { 122 columnSpec.append(" PRIMARY KEY AUTOINCREMENT") 123 } 124 if (nonNull) { 125 columnSpec.append(" NOT NULL") 126 } 127 if (collate != null) { 128 columnSpec.append(" COLLATE ${collate.name}") 129 } 130 return "`$columnName` ${(affinity ?: SQLTypeAffinity.TEXT).name}$columnSpec" 131 } 132 133 fun toBundle(): FieldBundle = FieldBundle(pathWithDotNotation, columnName, 134 affinity?.name ?: SQLTypeAffinity.TEXT.name, nonNull 135 ) 136} 137