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 17 package androidx.room.vo 18 19 import androidx.room.compiler.codegen.XTypeName 20 import androidx.room.compiler.processing.XFieldElement 21 import androidx.room.compiler.processing.XNullability 22 import androidx.room.compiler.processing.XType 23 import androidx.room.ext.capitalize 24 import androidx.room.ext.decapitalize 25 import androidx.room.migration.bundle.FieldBundle 26 import androidx.room.parser.Collate 27 import androidx.room.parser.SQLTypeAffinity 28 import androidx.room.solver.types.StatementValueBinder 29 import androidx.room.solver.types.StatementValueReader 30 import java.util.Locale 31 32 // used in cache matching, must stay as a data class or implement equals 33 data class Property( 34 val element: XFieldElement, 35 val name: String, 36 val type: XType, 37 var affinity: SQLTypeAffinity?, 38 val collate: Collate? = null, 39 val columnName: String = name, 40 val defaultValue: String? = null, 41 // null here means that this property does not belong to parent, instead, it belongs to an 42 // embedded child of the main Pojo 43 val parent: EmbeddedProperty? = null, 44 // index might be removed when being merged into an Entity 45 var indexed: Boolean = false, 46 /** Whether the table column for this property should be NOT NULL */ 47 val nonNull: Boolean = calcNonNull(type, parent) 48 ) : HasSchemaIdentity { 49 lateinit var getter: PropertyGetter 50 lateinit var setter: PropertySetter 51 // binds the property into a statement 52 var statementBinder: StatementValueBinder? = null 53 54 // reads this property from a cursor column 55 var statementValueReader: StatementValueReader? = null <lambda>null56 val typeName: XTypeName by lazy { type.asTypeName() } 57 getIdKeynull58 override fun getIdKey(): String { 59 return buildString { 60 // we don't get the collate information from sqlite so ignoring it here. 61 append("$columnName-${affinity?.name ?: SQLTypeAffinity.TEXT.name}-$nonNull") 62 // defaultValue was newly added; it should affect the ID only when it is used. 63 if (defaultValue != null) { 64 append("-defaultValue=$defaultValue") 65 } 66 } 67 } 68 69 /** Used when reporting errors on duplicate names */ getPathnull70 fun getPath(): String { 71 return if (parent == null) { 72 name 73 } else { 74 "${parent.property.getPath()} > $name" 75 } 76 } 77 <lambda>null78 private val pathWithDotNotation: String by lazy { 79 if (parent == null) { 80 name 81 } else { 82 "${parent.property.pathWithDotNotation}.$name" 83 } 84 } 85 86 /** 87 * List of names that include variations. e.g. if it is mUser, user is added to the list or if 88 * it is isAdmin, admin is added to the list 89 */ <lambda>null90 val nameWithVariations by lazy { 91 val result = arrayListOf(name) 92 if (name.length > 1) { 93 if (name.startsWith('_')) { 94 result.add(name.substring(1)) 95 } 96 if (name.startsWith("m") && name[1].isUpperCase()) { 97 result.add(name.substring(1).decapitalize(Locale.US)) 98 } 99 100 if (typeName == XTypeName.PRIMITIVE_BOOLEAN || typeName == XTypeName.BOXED_BOOLEAN) { 101 if (name.length > 2 && name.startsWith("is") && name[2].isUpperCase()) { 102 result.add(name.substring(2).decapitalize(Locale.US)) 103 } 104 if (name.length > 3 && name.startsWith("has") && name[3].isUpperCase()) { 105 result.add(name.substring(3).decapitalize(Locale.US)) 106 } 107 } 108 } 109 result 110 } 111 <lambda>null112 val getterNameWithVariations by lazy { 113 nameWithVariations.map { "get${it.capitalize(Locale.US)}" } + 114 if (typeName == XTypeName.PRIMITIVE_BOOLEAN || typeName == XTypeName.BOXED_BOOLEAN) { 115 nameWithVariations.flatMap { 116 listOf("is${it.capitalize(Locale.US)}", "has${it.capitalize(Locale.US)}") 117 } 118 } else { 119 emptyList() 120 } 121 } 122 <lambda>null123 val setterNameWithVariations by lazy { 124 nameWithVariations.map { "set${it.capitalize(Locale.US)}" } 125 } 126 127 /** definition to be used in create query */ databaseDefinitionnull128 fun databaseDefinition(autoIncrementPKey: Boolean): String { 129 val columnSpec = StringBuilder("") 130 if (autoIncrementPKey) { 131 columnSpec.append(" PRIMARY KEY AUTOINCREMENT") 132 } 133 if (nonNull) { 134 columnSpec.append(" NOT NULL") 135 } 136 if (collate != null) { 137 columnSpec.append(" COLLATE ${collate.name}") 138 } 139 if (defaultValue != null) { 140 columnSpec.append(" DEFAULT $defaultValue") 141 } 142 return "`$columnName` ${(affinity ?: SQLTypeAffinity.TEXT).name}$columnSpec" 143 } 144 toBundlenull145 fun toBundle(): FieldBundle = 146 FieldBundle( 147 pathWithDotNotation, 148 columnName, 149 affinity?.name ?: SQLTypeAffinity.TEXT.name, 150 nonNull, 151 defaultValue 152 ) 153 154 companion object { 155 fun calcNonNull(type: XType, parent: EmbeddedProperty?): Boolean { 156 return XNullability.NONNULL == type.nullability && 157 (parent == null || parent.isNonNullRecursively()) 158 } 159 } 160 } 161