diff --git a/README.md b/README.md index 947669073..6ca8d3879 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Add the library to the project-level build.gradle, using the apt plugin to enabl ```groovy - def dbflow_version = "4.0.1" + def dbflow_version = "4.0.2" // or dbflow_version = "develop-SNAPSHOT" for grabbing latest dependency in your project on the develop branch // or 10-digit short-hash of a specific commit. (Useful for bugs fixed in develop, but not in a release yet) diff --git a/build.gradle b/build.gradle index 07002b362..dad2eb1af 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,10 @@ buildscript { - ext.kotlin_version = '1.1.2' + ext.kotlin_version = '1.1.2-3' repositories { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.1' + classpath 'com.android.tools.build:gradle:2.3.2' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.6.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" diff --git a/dbflow-core/src/main/java/com/raizlabs/android/dbflow/annotation/InheritedColumn.java b/dbflow-core/src/main/java/com/raizlabs/android/dbflow/annotation/InheritedColumn.java index 164f0fd19..158ec83ec 100644 --- a/dbflow-core/src/main/java/com/raizlabs/android/dbflow/annotation/InheritedColumn.java +++ b/dbflow-core/src/main/java/com/raizlabs/android/dbflow/annotation/InheritedColumn.java @@ -23,4 +23,8 @@ */ String fieldName(); + /** + * @return If specified other than {@link ConflictAction#NONE}, then we assume {@link NotNull}. + */ + ConflictAction nonNullConflict() default ConflictAction.NONE; } diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/ProcessorManager.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/ProcessorManager.kt index cfe84d855..5ce85aae1 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/ProcessorManager.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/ProcessorManager.kt @@ -93,8 +93,8 @@ class ProcessorManager internal constructor(val processingEnvironment: Processin holderDefinition?.tableDefinitionMap?.put(it, tableDefinition) holderDefinition?.tableNameMap?.let { if (holderDefinition.tableNameMap.containsKey(tableDefinition.tableName)) { - logError("Found duplicate table %1s for database %1s", tableDefinition.tableName, - holderDefinition.databaseDefinition?.databaseName) + logError("Found duplicate table ${tableDefinition.tableName} " + + "for database ${holderDefinition.databaseDefinition?.databaseName}") } else tableDefinition.tableName?.let { holderDefinition.tableNameMap.put(it, tableDefinition) } diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/ProcessorUtils.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/ProcessorUtils.kt new file mode 100644 index 000000000..44a2fe433 --- /dev/null +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/ProcessorUtils.kt @@ -0,0 +1,114 @@ +package com.raizlabs.android.dbflow.processor + +import com.raizlabs.android.dbflow.processor.ProcessorManager.Companion.manager +import com.raizlabs.android.dbflow.processor.utils.ElementUtility +import com.raizlabs.android.dbflow.processor.utils.erasure +import com.raizlabs.android.dbflow.processor.utils.toTypeElement +import com.squareup.javapoet.ClassName +import javax.annotation.processing.ProcessingEnvironment +import javax.lang.model.element.Element +import javax.lang.model.element.Modifier +import javax.lang.model.element.TypeElement +import javax.lang.model.type.TypeMirror +import javax.tools.Diagnostic + +/** + * Description: Provides handy methods for processing + */ +object ProcessorUtils { + + /** + * Whether the specified element is assignable to the fqTn parameter + + * @param processingEnvironment The environment this runs in + * * + * @param fqTn THe fully qualified type name of the element we want to check + * * + * @param element The element to check that implements + * * + * @return true if element implements the fqTn + */ + fun implementsClass(processingEnvironment: ProcessingEnvironment, fqTn: String, element: TypeElement?): Boolean { + val typeElement = processingEnvironment.elementUtils.getTypeElement(fqTn) + if (typeElement == null) { + processingEnvironment.messager.printMessage(Diagnostic.Kind.ERROR, + "Type Element was null for: $fqTn ensure that the visibility of the class is not private.") + return false + } else { + val classMirror: TypeMirror? = typeElement.asType().erasure() + if (classMirror == null || element?.asType() == null) { + return false + } + val elementType = element.asType() + return elementType != null && (processingEnvironment.typeUtils.isAssignable(elementType, classMirror) || elementType == classMirror) + } + } + + /** + * Whether the specified element is assignable to the fqTn parameter + + * @param processingEnvironment The environment this runs in + * * + * @param fqTn THe fully qualified type name of the element we want to check + * * + * @param element The element to check that implements + * * + * @return true if element implements the fqTn + */ + fun isSubclass(processingEnvironment: ProcessingEnvironment, fqTn: String, element: TypeElement?): Boolean { + val typeElement = processingEnvironment.elementUtils.getTypeElement(fqTn) + if (typeElement == null) { + processingEnvironment.messager.printMessage(Diagnostic.Kind.ERROR, "Type Element was null for: $fqTn ensure that the visibility of the class is not private.") + return false + } else { + val classMirror = typeElement.asType() + return classMirror != null && element != null && element.asType() != null && processingEnvironment.typeUtils.isSubtype(element.asType(), classMirror) + } + } + + fun fromTypeMirror(typeMirror: TypeMirror, processorManager: ProcessorManager): ClassName? { + var className: ClassName? = null + val element = getTypeElement(typeMirror) + if (element != null) { + className = ClassName.get(element) + } else { + className = ElementUtility.getClassName(typeMirror.toString(), processorManager) + } + return className + } + + fun getTypeElement(element: Element): TypeElement? { + val typeElement: TypeElement? + if (element is TypeElement) { + typeElement = element + } else { + typeElement = getTypeElement(element.asType()) + } + return typeElement + } + + fun getTypeElement(typeMirror: TypeMirror): TypeElement? { + val manager = ProcessorManager.manager + var typeElement: TypeElement? = typeMirror.toTypeElement(manager) + if (typeElement == null) { + val el = manager.typeUtils.asElement(typeMirror) + typeElement = if (el != null) (el as TypeElement) else null + } + return typeElement + } + + fun ensureVisibleStatic(element: Element, typeElement: TypeElement, + name: String) { + if (element.modifiers.contains(Modifier.PRIVATE) + || element.modifiers.contains(Modifier.PROTECTED)) { + manager.logError("$name must be visible from: " + typeElement) + } + if (!element.modifiers.contains(Modifier.STATIC)) { + manager.logError("$name must be static from: " + typeElement) + } + + if (!element.modifiers.contains(Modifier.FINAL)) { + manager.logError("The $name must be final") + } + } +} diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/InternalAdapterHelper.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/InternalAdapterHelper.kt new file mode 100644 index 000000000..5df837639 --- /dev/null +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/InternalAdapterHelper.kt @@ -0,0 +1,102 @@ +package com.raizlabs.android.dbflow.processor.definition + +import com.raizlabs.android.dbflow.processor.ClassNames +import com.raizlabs.android.dbflow.processor.definition.column.ColumnDefinition +import com.raizlabs.android.dbflow.processor.definition.column.DefinitionUtils +import com.raizlabs.android.dbflow.processor.utils.ModelUtils +import com.raizlabs.android.dbflow.sql.QueryBuilder +import com.squareup.javapoet.ArrayTypeName +import com.squareup.javapoet.ClassName +import com.squareup.javapoet.MethodSpec +import com.squareup.javapoet.ParameterizedTypeName +import com.squareup.javapoet.TypeName +import com.squareup.javapoet.TypeSpec +import javax.lang.model.element.Modifier + +/** + * Description: Assists in writing methods for adapters + */ +object InternalAdapterHelper { + + fun writeGetModelClass(typeBuilder: TypeSpec.Builder, modelClassName: ClassName?) { + typeBuilder.addMethod(MethodSpec.methodBuilder("getModelClass") + .addAnnotation(Override::class.java) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return \$T.class", modelClassName) + .returns(ParameterizedTypeName.get(ClassName.get(Class::class.java), modelClassName)) + .build()) + } + + fun writeGetTableName(typeBuilder: TypeSpec.Builder, tableName: String?) { + typeBuilder.addMethod(MethodSpec.methodBuilder("getTableName") + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addStatement("return \$S", QueryBuilder.quote(tableName)) + .returns(ClassName.get(String::class.java)) + .build()) + } + + fun writeUpdateAutoIncrement(typeBuilder: TypeSpec.Builder, modelClassName: TypeName?, + autoIncrementDefinition: ColumnDefinition) { + typeBuilder.addMethod(MethodSpec.methodBuilder("updateAutoIncrement") + .addAnnotation(Override::class.java) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addParameter(modelClassName, ModelUtils.variable) + .addParameter(ClassName.get(Number::class.java), "id") + .addCode(autoIncrementDefinition.updateAutoIncrementMethod) + .build()) + } + + fun writeGetCachingId(typeBuilder: TypeSpec.Builder, modelClassName: TypeName?, + primaryColumns: List) { + if (primaryColumns.size > 1) { + var methodBuilder: MethodSpec.Builder = MethodSpec.methodBuilder("getCachingColumnValuesFromModel") + .addAnnotation(Override::class.java).addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addParameter(ArrayTypeName.of(Any::class.java), "inValues") + .addParameter(modelClassName, ModelUtils.variable) + for (i in primaryColumns.indices) { + val column = primaryColumns[i] + methodBuilder.addCode(column.getColumnAccessString(i)) + } + methodBuilder.addStatement("return \$L", "inValues").returns(ArrayTypeName.of(Any::class.java)) + typeBuilder.addMethod(methodBuilder.build()) + + methodBuilder = MethodSpec.methodBuilder("getCachingColumnValuesFromCursor") + .addAnnotation(Override::class.java).addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addParameter(ArrayTypeName.of(Any::class.java), "inValues") + .addParameter(ClassNames.CURSOR, "cursor") + for (i in primaryColumns.indices) { + val column = primaryColumns[i] + val method = DefinitionUtils.getLoadFromCursorMethodString(column.elementTypeName, column.wrapperTypeName) + methodBuilder.addStatement("inValues[\$L] = \$L.\$L(\$L.getColumnIndex(\$S))", i, LoadFromCursorMethod.PARAM_CURSOR, + method, LoadFromCursorMethod.PARAM_CURSOR, column.columnName) + } + methodBuilder.addStatement("return \$L", "inValues").returns(ArrayTypeName.of(Any::class.java)) + typeBuilder.addMethod(methodBuilder.build()) + } else { + // single primary key + var methodBuilder: MethodSpec.Builder = MethodSpec.methodBuilder("getCachingColumnValueFromModel") + .addAnnotation(Override::class.java).addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addParameter(modelClassName, ModelUtils.variable) + methodBuilder.addCode(primaryColumns[0].getSimpleAccessString()) + .returns(Any::class.java) + typeBuilder.addMethod(methodBuilder.build()) + + methodBuilder = MethodSpec.methodBuilder("getCachingColumnValueFromCursor") + .addAnnotation(Override::class.java).addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addParameter(ClassNames.CURSOR, "cursor") + val column = primaryColumns[0] + val method = DefinitionUtils.getLoadFromCursorMethodString(column.elementTypeName, column.wrapperTypeName) + methodBuilder.addStatement("return \$L.\$L(\$L.getColumnIndex(\$S))", LoadFromCursorMethod.PARAM_CURSOR, + method, LoadFromCursorMethod.PARAM_CURSOR, column.columnName).returns(Any::class.java) + typeBuilder.addMethod(methodBuilder.build()) + + methodBuilder = MethodSpec.methodBuilder("getCachingId") + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addParameter(modelClassName, ModelUtils.variable) + .addStatement("return getCachingColumnValueFromModel(\$L)", + ModelUtils.variable).returns(TypeName.OBJECT) + typeBuilder.addMethod(methodBuilder.build()) + } + } + +} diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/Methods.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/Methods.kt index 78d303808..d2427450b 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/Methods.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/Methods.kt @@ -36,11 +36,11 @@ class BindToContentValuesMethod(private val baseTableDefinition: BaseTableDefini override val methodSpec: MethodSpec? get() { val methodBuilder = MethodSpec.methodBuilder(if (isInsert) "bindToInsertValues" else "bindToContentValues") - .addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addParameter(ClassNames.CONTENT_VALUES, PARAM_CONTENT_VALUES) - .addParameter(baseTableDefinition.parameterClassName, ModelUtils.variable) - .returns(TypeName.VOID) + .addAnnotation(Override::class.java) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addParameter(ClassNames.CONTENT_VALUES, PARAM_CONTENT_VALUES) + .addParameter(baseTableDefinition.parameterClassName, ModelUtils.variable) + .returns(TypeName.VOID) var retMethodBuilder: MethodSpec.Builder? = methodBuilder @@ -53,7 +53,7 @@ class BindToContentValuesMethod(private val baseTableDefinition: BaseTableDefini if (implementsContentValuesListener) { methodBuilder.addStatement("\$L.onBindTo\$LValues(\$L)", - ModelUtils.variable, if (isInsert) "Insert" else "Content", PARAM_CONTENT_VALUES) + ModelUtils.variable, if (isInsert) "Insert" else "Content", PARAM_CONTENT_VALUES) } } else { if (baseTableDefinition.hasAutoIncrement || baseTableDefinition.hasRowID) { @@ -68,7 +68,7 @@ class BindToContentValuesMethod(private val baseTableDefinition: BaseTableDefini methodBuilder.addStatement("bindToInsertValues(\$L, \$L)", PARAM_CONTENT_VALUES, ModelUtils.variable) if (implementsContentValuesListener) { methodBuilder.addStatement("\$L.onBindTo\$LValues(\$L)", - ModelUtils.variable, if (isInsert) "Insert" else "Content", PARAM_CONTENT_VALUES) + ModelUtils.variable, if (isInsert) "Insert" else "Content", PARAM_CONTENT_VALUES) } } @@ -83,51 +83,99 @@ class BindToContentValuesMethod(private val baseTableDefinition: BaseTableDefini /** * Description: */ -class BindToStatementMethod(private val tableDefinition: TableDefinition, private val isInsert: Boolean) : MethodDefinition { +class BindToStatementMethod(private val tableDefinition: TableDefinition, private val mode: Mode) : MethodDefinition { + + enum class Mode { + INSERT { + override val methodName = "bindToInsertStatement" + + override val sqlListenerName = "onBindToInsertStatement" + }, + UPDATE { + override val methodName = "bindToUpdateStatement" + + override val sqlListenerName = "onBindToUpdateStatement" + }, + DELETE { + override val methodName = "bindToDeleteStatement" + + override val sqlListenerName = "onBindToDeleteStatement" + }, + NON_INSERT { + override val methodName = "bindToStatement" + + override val sqlListenerName = "onBindToStatement" + }; + + abstract val methodName: String + + abstract val sqlListenerName: String + } override val methodSpec: MethodSpec? get() { - val methodBuilder = MethodSpec.methodBuilder(if (isInsert) "bindToInsertStatement" else "bindToStatement") - .addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addParameter(ClassNames.DATABASE_STATEMENT, PARAM_STATEMENT) - .addParameter(tableDefinition.parameterClassName, - ModelUtils.variable).returns(TypeName.VOID) + val methodBuilder = MethodSpec.methodBuilder(mode.methodName) + .addAnnotation(Override::class.java) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addParameter(ClassNames.DATABASE_STATEMENT, PARAM_STATEMENT) + .addParameter(tableDefinition.parameterClassName, + ModelUtils.variable).returns(TypeName.VOID) // write the reference method - if (isInsert) { + if (mode == Mode.INSERT) { methodBuilder.addParameter(TypeName.INT, PARAM_START) val realCount = AtomicInteger(1) - tableDefinition.columnDefinitions.forEach { - if (!it.isPrimaryKeyAutoIncrement && !it.isRowId) { - methodBuilder.addCode(it.getSQLiteStatementMethod(realCount)) - realCount.incrementAndGet() - } - } + tableDefinition.columnDefinitions + .filter { !it.isPrimaryKeyAutoIncrement && !it.isRowId } + .forEach { + methodBuilder.addCode(it.getSQLiteStatementMethod(realCount, true)) + realCount.incrementAndGet() + } - if (tableDefinition.implementsSqlStatementListener) { - methodBuilder.addStatement("${ModelUtils.variable}.onBindTo\$LStatement($PARAM_STATEMENT)", - if (isInsert) "Insert" else "") - } } else { - var start = 0 - if (tableDefinition.hasAutoIncrement || tableDefinition.hasRowID) { - val autoIncrement = tableDefinition.autoIncrementColumn - autoIncrement?.let { - methodBuilder.addStatement("int start = 0") - methodBuilder.addCode(it.getSQLiteStatementMethod(AtomicInteger(++start))) + if (mode == Mode.UPDATE) { + val realCount = AtomicInteger(1) + // attach non rowid first, then go onto the WHERE clause + tableDefinition.columnDefinitions + .filter { !it.isRowId } + .forEach { + methodBuilder.addCode(it.getSQLiteStatementMethod(realCount, + useStart = false)) + realCount.incrementAndGet() + } + tableDefinition.primaryColumnDefinitions.forEach { + methodBuilder.addCode(it.getSQLiteStatementMethod(realCount, + useStart = false, defineProperty = false)) + realCount.incrementAndGet() + } + } else if (mode == Mode.DELETE) { + val realCount = AtomicInteger(1) + tableDefinition.primaryColumnDefinitions.forEach { + methodBuilder.addCode(it.getSQLiteStatementMethod(realCount, useStart = false)) + realCount.incrementAndGet() } - methodBuilder.addStatement("bindToInsertStatement($PARAM_STATEMENT, ${ModelUtils.variable}, $start)") - } else if (tableDefinition.implementsSqlStatementListener) { - methodBuilder.addStatement("bindToInsertStatement($PARAM_STATEMENT, ${ModelUtils.variable}, $start)") - methodBuilder.addStatement("${ModelUtils.variable}.onBindTo\$LStatement($PARAM_STATEMENT)", - if (isInsert) "Insert" else "") } else { - // don't generate method - return null + var start = 0 + if (tableDefinition.hasAutoIncrement || tableDefinition.hasRowID) { + val autoIncrement = tableDefinition.autoIncrementColumn + autoIncrement?.let { + methodBuilder.addStatement("int start = 0") + methodBuilder.addCode(it.getSQLiteStatementMethod(AtomicInteger(++start), true)) + } + methodBuilder.addStatement("bindToInsertStatement($PARAM_STATEMENT, ${ModelUtils.variable}, $start)") + } else if (tableDefinition.implementsSqlStatementListener) { + methodBuilder.addStatement("bindToInsertStatement($PARAM_STATEMENT, ${ModelUtils.variable}, $start)") + } else { + // don't generate method + return null + } } } + if (tableDefinition.implementsSqlStatementListener) { + methodBuilder.addStatement("${ModelUtils.variable}.${mode.sqlListenerName}($PARAM_STATEMENT)") + } + return methodBuilder.build() } @@ -172,7 +220,7 @@ class CreationQueryMethod(private val tableDefinition: TableDefinition) : Method } val codeBuilder = CodeBlock.builder() - .add("return ${creationBuilder.toString().S}") + .add("return ${creationBuilder.toString().S}") val foreignKeyBlocks = ArrayList() val tableNameBlocks = ArrayList() @@ -240,7 +288,7 @@ class CustomTypeConverterPropertyMethod(private val baseTableDefinition: BaseTab val firstDef = def?.get(0) firstDef?.typeConverterElementNames?.forEach { elementName -> code.statement("global_typeConverter${it.simpleName()} " + - "= (\$T) holder.getTypeConverterForClass(\$T.class)", it, elementName) + "= (\$T) holder.getTypeConverterForClass(\$T.class)", it, elementName) } } return code @@ -254,8 +302,8 @@ class ExistenceMethod(private val tableDefinition: BaseTableDefinition) : Method override val methodSpec get() = `override fun`(TypeName.BOOLEAN, "exists", - param(tableDefinition.parameterClassName!!, ModelUtils.variable), - param(ClassNames.DATABASE_WRAPPER, "wrapper")) { + param(tableDefinition.parameterClassName!!, ModelUtils.variable), + param(ClassNames.DATABASE_WRAPPER, "wrapper")) { modifiers(public, final) code { // only quick check if enabled. @@ -280,10 +328,10 @@ class InsertStatementQueryMethod(private val tableDefinition: TableDefinition, p return null // dont write method here because we reuse the compiled statement query method } return `override fun`(String::class, - if (isInsert) "getInsertStatementQuery" else "getCompiledStatementQuery") { + if (isInsert) "getInsertStatementQuery" else "getCompiledStatementQuery") { modifiers(public, final) val isSingleAutoincrement = tableDefinition.hasAutoIncrement - && tableDefinition.columnDefinitions.size == 1 && isInsert + && tableDefinition.columnDefinitions.size == 1 && isInsert `return`(codeBlock { add("INSERT ") if (!tableDefinition.insertConflictActionName.isEmpty()) { @@ -301,10 +349,10 @@ class InsertStatementQueryMethod(private val tableDefinition: TableDefinition, p add(") VALUES (") tableDefinition.columnDefinitions.filter { !it.isPrimaryKeyAutoIncrement && !it.isRowId || !isInsert } - .forEachIndexed { index, columnDefinition -> - if (index > 0) add(",") - add(columnDefinition.insertStatementValuesString) - } + .forEachIndexed { index, columnDefinition -> + if (index > 0) add(",") + add(columnDefinition.insertStatementValuesString) + } if (isSingleAutoincrement) add("NULL") @@ -314,6 +362,64 @@ class InsertStatementQueryMethod(private val tableDefinition: TableDefinition, p } } +class UpdateStatementQueryMethod(private val tableDefinition: TableDefinition) : MethodDefinition { + + override val methodSpec: MethodSpec? + get() { + return `override fun`(String::class, "getUpdateStatementQuery") { + modifiers(public, final) + `return`(codeBlock { + add("UPDATE") + if (!tableDefinition.updateConflictActionName.isEmpty()) { + add(" OR ${tableDefinition.updateConflictActionName}") + } + add(" ${QueryBuilder.quote(tableDefinition.tableName)} SET ") + + // can only change non primary key values. + tableDefinition.columnDefinitions.filter { + !it.isRowId + }.forEachIndexed { index, columnDefinition -> + if (index > 0) add(",") + add(columnDefinition.updateStatementBlock) + + } + add(" WHERE ") + + // primary key values used as WHERE + tableDefinition.columnDefinitions.filter { + it.isPrimaryKey || it.isPrimaryKeyAutoIncrement || it.isRowId + }.forEachIndexed { index, columnDefinition -> + if (index > 0) add(" AND ") + add(columnDefinition.updateStatementBlock) + } + this + }.S) + } + } +} + +class DeleteStatementQueryMethod(private val tableDefinition: TableDefinition) : MethodDefinition { + + override val methodSpec: MethodSpec? + get() { + return `override fun`(String::class, "getDeleteStatementQuery") { + modifiers(public, final) + `return`(codeBlock { + add("DELETE FROM ${QueryBuilder.quote(tableDefinition.tableName)} WHERE ") + + // primary key values used as WHERE + tableDefinition.columnDefinitions.filter { + it.isPrimaryKey || it.isPrimaryKeyAutoIncrement || it.isRowId + }.forEachIndexed { index, columnDefinition -> + if (index > 0) add(" AND ") + add(columnDefinition.updateStatementBlock) + } + this + }.S) + } + } +} + /** * Description: */ @@ -321,8 +427,8 @@ class LoadFromCursorMethod(private val baseTableDefinition: BaseTableDefinition) override val methodSpec: MethodSpec get() = `override fun`(TypeName.VOID, "loadFromCursor", - param(ClassNames.FLOW_CURSOR, PARAM_CURSOR), - param(baseTableDefinition.parameterClassName!!, ModelUtils.variable)) { + param(ClassNames.FLOW_CURSOR, PARAM_CURSOR), + param(baseTableDefinition.parameterClassName!!, ModelUtils.variable)) { modifiers(public, final) val index = AtomicInteger(0) val nameAllocator = NameAllocator() // unique names @@ -337,8 +443,8 @@ class LoadFromCursorMethod(private val baseTableDefinition: BaseTableDefinition) code { baseTableDefinition.oneToManyDefinitions - .filter { it.isLoad } - .forEach { it.writeLoad(this) } + .filter { it.isLoad } + .forEach { it.writeLoad(this) } this } } @@ -367,7 +473,7 @@ class OneToManyDeleteMethod(private val tableDefinition: TableDefinition, val shouldWrite = tableDefinition.oneToManyDefinitions.any { it.isDelete } if (shouldWrite || tableDefinition.cachingEnabled) { return `override fun`(TypeName.BOOLEAN, "delete", - param(tableDefinition.elementClassName!!, ModelUtils.variable)) { + param(tableDefinition.elementClassName!!, ModelUtils.variable)) { modifiers(public, final) if (useWrapper) { addParameter(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper) @@ -412,7 +518,7 @@ class OneToManySaveMethod(private val tableDefinition: TableDefinition, } return `override fun`(retType, methodName, - param(tableDefinition.elementClassName!!, ModelUtils.variable)) { + param(tableDefinition.elementClassName!!, ModelUtils.variable)) { modifiers(public, final) if (useWrapper) { @@ -468,7 +574,7 @@ class PrimaryConditionMethod(private val tableDefinition: BaseTableDefinition) : override val methodSpec: MethodSpec? get() = `override fun`(ClassNames.OPERATOR_GROUP, "getPrimaryConditionClause", - param(tableDefinition.parameterClassName!!, ModelUtils.variable)) { + param(tableDefinition.parameterClassName!!, ModelUtils.variable)) { modifiers(public, final) code { statement("\$T clause = \$T.clause()", ClassNames.OPERATOR_GROUP, ClassNames.OPERATOR_GROUP) diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/OneToManyDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/OneToManyDefinition.kt index 6a8c702b5..95a409d01 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/OneToManyDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/OneToManyDefinition.kt @@ -103,7 +103,7 @@ class OneToManyDefinition(executableElement: ExecutableElement, fun writeWrapperStatement(method: MethodSpec.Builder) { method.statement("\$T ${ModelUtils.wrapper} = \$T.getWritableDatabaseForTable(\$T.class)", - ClassNames.DATABASE_WRAPPER, ClassNames.FLOW_MANAGER, referencedTableType) + ClassNames.DATABASE_WRAPPER, ClassNames.FLOW_MANAGER, referencedTableType) } /** @@ -142,10 +142,10 @@ class OneToManyDefinition(executableElement: ExecutableElement, codeBuilder.apply { `if`("$oneToManyMethodName != null") { // need to load adapter for non-model classes - if (!extendsModel) { + if (!extendsModel || efficientCodeMethods) { statement("\$T adapter = \$T.getModelAdapter(\$T.class)", - ParameterizedTypeName.get(ClassNames.MODEL_ADAPTER, referencedTableType), - ClassNames.FLOW_MANAGER, referencedTableType) + ParameterizedTypeName.get(ClassNames.MODEL_ADAPTER, referencedTableType), + ClassNames.FLOW_MANAGER, referencedTableType) } if (efficientCodeMethods) { diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/TableDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/TableDefinition.kt index 883b41a28..00291f74c 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/TableDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/TableDefinition.kt @@ -7,6 +7,7 @@ import com.raizlabs.android.dbflow.processor.ClassNames import com.raizlabs.android.dbflow.processor.ColumnValidator import com.raizlabs.android.dbflow.processor.OneToManyValidator import com.raizlabs.android.dbflow.processor.ProcessorManager +import com.raizlabs.android.dbflow.processor.definition.BindToStatementMethod.Mode.* import com.raizlabs.android.dbflow.processor.definition.column.ColumnDefinition import com.raizlabs.android.dbflow.processor.definition.column.DefinitionUtils import com.raizlabs.android.dbflow.processor.definition.column.ForeignKeyColumnDefinition @@ -99,7 +100,7 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab inheritedColumns.forEach { if (inheritedFieldNameList.contains(it.fieldName)) { manager.logError("A duplicate inherited column with name %1s was found for %1s", - it.fieldName, tableName) + it.fieldName, tableName) } inheritedFieldNameList.add(it.fieldName) inheritedColumnMap.put(it.fieldName, it) @@ -109,35 +110,37 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab inheritedPrimaryKeys.forEach { if (inheritedFieldNameList.contains(it.fieldName)) { manager.logError("A duplicate inherited column with name %1s was found for %1s", - it.fieldName, tableName) + it.fieldName, tableName) } inheritedFieldNameList.add(it.fieldName) inheritedPrimaryKeyMap.put(it.fieldName, it) } implementsLoadFromCursorListener = element.implementsClass(manager.processingEnvironment, - ClassNames.LOAD_FROM_CURSOR_LISTENER) + ClassNames.LOAD_FROM_CURSOR_LISTENER) implementsContentValuesListener = element.implementsClass(manager.processingEnvironment, - ClassNames.CONTENT_VALUES_LISTENER) + ClassNames.CONTENT_VALUES_LISTENER) implementsSqlStatementListener = element.implementsClass(manager.processingEnvironment, - ClassNames.SQLITE_STATEMENT_LISTENER) + ClassNames.SQLITE_STATEMENT_LISTENER) } methods = arrayOf(BindToContentValuesMethod(this, true, implementsContentValuesListener), - BindToContentValuesMethod(this, false, implementsContentValuesListener), - BindToStatementMethod(this, true), BindToStatementMethod(this, false), - InsertStatementQueryMethod(this, true), InsertStatementQueryMethod(this, false), - CreationQueryMethod(this), LoadFromCursorMethod(this), ExistenceMethod(this), - PrimaryConditionMethod(this), OneToManyDeleteMethod(this, false), - OneToManyDeleteMethod(this, true), - OneToManySaveMethod(this, OneToManySaveMethod.METHOD_SAVE, false), - OneToManySaveMethod(this, OneToManySaveMethod.METHOD_INSERT, false), - OneToManySaveMethod(this, OneToManySaveMethod.METHOD_UPDATE, false), - OneToManySaveMethod(this, OneToManySaveMethod.METHOD_SAVE, true), - OneToManySaveMethod(this, OneToManySaveMethod.METHOD_INSERT, true), - OneToManySaveMethod(this, OneToManySaveMethod.METHOD_UPDATE, true)) + BindToContentValuesMethod(this, false, implementsContentValuesListener), + BindToStatementMethod(this, INSERT), BindToStatementMethod(this, NON_INSERT), + BindToStatementMethod(this, UPDATE), BindToStatementMethod(this, DELETE), + InsertStatementQueryMethod(this, true), InsertStatementQueryMethod(this, false), + UpdateStatementQueryMethod(this), DeleteStatementQueryMethod(this), + CreationQueryMethod(this), LoadFromCursorMethod(this), ExistenceMethod(this), + PrimaryConditionMethod(this), OneToManyDeleteMethod(this, false), + OneToManyDeleteMethod(this, true), + OneToManySaveMethod(this, OneToManySaveMethod.METHOD_SAVE, false), + OneToManySaveMethod(this, OneToManySaveMethod.METHOD_INSERT, false), + OneToManySaveMethod(this, OneToManySaveMethod.METHOD_UPDATE, false), + OneToManySaveMethod(this, OneToManySaveMethod.METHOD_SAVE, true), + OneToManySaveMethod(this, OneToManySaveMethod.METHOD_INSERT, true), + OneToManySaveMethod(this, OneToManySaveMethod.METHOD_UPDATE, true)) } override fun prepareForWrite() { @@ -191,7 +194,7 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab } val definition = UniqueGroupsDefinition(uniqueGroup) columnDefinitions.filter { it.uniqueGroups.contains(definition.number) } - .forEach { definition.addColumnDefinition(it) } + .forEach { definition.addColumnDefinition(it) } uniqueGroupsDefinitions.add(definition) uniqueNumbersSet.add(uniqueGroup.groupNumber) } @@ -204,7 +207,7 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab } val definition = IndexGroupsDefinition(this, indexGroup) columnDefinitions.filter { it.indexGroups.contains(definition.indexNumber) } - .forEach { definition.columnDefinitionList.add(it) } + .forEach { definition.columnDefinitionList.add(it) } indexGroupsDefinitions.add(definition) uniqueNumbersSet.add(indexGroup.number) } @@ -218,9 +221,9 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab for (element in elements) { classElementLookUpMap.put(element.simpleName.toString(), element) if (element is ExecutableElement && element.parameters.isEmpty() - && element.simpleName.toString() == "" - && element.enclosingElement == typeElement - && !element.modifiers.contains(Modifier.PRIVATE)) { + && element.simpleName.toString() == "" + && element.enclosingElement == typeElement + && !element.modifiers.contains(Modifier.PRIVATE)) { hasPrimaryConstructor = true } } @@ -240,23 +243,23 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab val isInherited = inheritedColumnMap.containsKey(element.simpleName.toString()) val isInheritedPrimaryKey = inheritedPrimaryKeyMap.containsKey(element.simpleName.toString()) if (element.annotation() != null || isForeign || isPrimary - || isAllFields || isInherited || isInheritedPrimaryKey) { + || isAllFields || isInherited || isInheritedPrimaryKey) { val columnDefinition: ColumnDefinition if (isInheritedPrimaryKey) { val inherited = inheritedPrimaryKeyMap[element.simpleName.toString()] columnDefinition = ColumnDefinition(manager, element, this, isPackagePrivateNotInSamePackage, - inherited?.column, inherited?.primaryKey) + inherited?.column, inherited?.primaryKey) } else if (isInherited) { val inherited = inheritedColumnMap[element.simpleName.toString()] columnDefinition = ColumnDefinition(manager, element, this, isPackagePrivateNotInSamePackage, - inherited?.column, null) + inherited?.column, null, inherited?.nonNullConflict ?: ConflictAction.NONE) } else if (isForeign) { columnDefinition = ForeignKeyColumnDefinition(manager, this, - element, isPackagePrivateNotInSamePackage) + element, isPackagePrivateNotInSamePackage) } else { columnDefinition = ColumnDefinition(manager, element, - this, isPackagePrivateNotInSamePackage) + this, isPackagePrivateNotInSamePackage) } if (columnValidator.validate(manager, columnDefinition)) { @@ -359,7 +362,7 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab val getPropertiesBuilder = CodeBlock.builder() `override fun`(ClassNames.PROPERTY, "getProperty", - param(String::class, paramColumnName)) { + param(String::class, paramColumnName)) { modifiers(public, final) statement("$paramColumnName = \$T.quoteIfNeeded($paramColumnName)", ClassName.get(QueryBuilder::class.java)) @@ -393,7 +396,7 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab val autoIncrement = autoIncrementColumn autoIncrement?.let { `override fun`(TypeName.VOID, "updateAutoIncrement", param(elementClassName!!, ModelUtils.variable), - param(Number::class, "id")) { + param(Number::class, "id")) { modifiers(public, final) addCode(autoIncrement.updateAutoIncrementMethod) } @@ -410,28 +413,28 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab } val saveForeignKeyFields = columnDefinitions - .filter { (it is ForeignKeyColumnDefinition) && it.saveForeignKeyModel } - .map { it as ForeignKeyColumnDefinition } + .filter { (it is ForeignKeyColumnDefinition) && it.saveForeignKeyModel } + .map { it as ForeignKeyColumnDefinition } if (saveForeignKeyFields.isNotEmpty()) { val code = CodeBlock.builder() saveForeignKeyFields.forEach { it.appendSaveMethod(code) } `override fun`(TypeName.VOID, "saveForeignKeys", param(elementClassName!!, ModelUtils.variable), - param(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper)) { + param(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper)) { modifiers(public, final) addCode(code.build()) } } val deleteForeignKeyFields = columnDefinitions - .filter { (it is ForeignKeyColumnDefinition) && it.deleteForeignKeyModel } - .map { it as ForeignKeyColumnDefinition } + .filter { (it is ForeignKeyColumnDefinition) && it.deleteForeignKeyModel } + .map { it as ForeignKeyColumnDefinition } if (deleteForeignKeyFields.isNotEmpty()) { val code = CodeBlock.builder() deleteForeignKeyFields.forEach { it.appendDeleteMethod(code) } `override fun`(TypeName.VOID, "deleteForeignKeys", param(elementClassName!!, ModelUtils.variable), - param(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper)) { + param(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper)) { modifiers(public, final) addCode(code.build()) } @@ -449,21 +452,21 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab `override fun`(ClassNames.SINGLE_MODEL_LOADER, "createSingleModelLoader") { modifiers(public, final) addStatement("return new \$T<>(getModelClass())", - if (singlePrimaryKey) - ClassNames.SINGLE_KEY_CACHEABLE_MODEL_LOADER - else - ClassNames.CACHEABLE_MODEL_LOADER) + if (singlePrimaryKey) + ClassNames.SINGLE_KEY_CACHEABLE_MODEL_LOADER + else + ClassNames.CACHEABLE_MODEL_LOADER) } `override fun`(ClassNames.LIST_MODEL_LOADER, "createListModelLoader") { modifiers(public, final) `return`("new \$T<>(getModelClass())", - if (singlePrimaryKey) - ClassNames.SINGLE_KEY_CACHEABLE_LIST_MODEL_LOADER - else - ClassNames.CACHEABLE_LIST_MODEL_LOADER) + if (singlePrimaryKey) + ClassNames.SINGLE_KEY_CACHEABLE_LIST_MODEL_LOADER + else + ClassNames.CACHEABLE_LIST_MODEL_LOADER) } `override fun`(ParameterizedTypeName.get(ClassNames.CACHEABLE_LIST_MODEL_SAVER, elementClassName), - "createListModelSaver") { + "createListModelSaver") { modifiers(protected) `return`("new \$T<>(getModelSaver())", ClassNames.CACHEABLE_LIST_MODEL_SAVER) } @@ -475,8 +478,8 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab val primaryColumns = primaryColumnDefinitions if (primaryColumns.size > 1) { `override fun`(ArrayTypeName.of(Any::class.java), "getCachingColumnValuesFromModel", - param(ArrayTypeName.of(Any::class.java), "inValues"), - param(elementClassName!!, ModelUtils.variable)) { + param(ArrayTypeName.of(Any::class.java), "inValues"), + param(elementClassName!!, ModelUtils.variable)) { modifiers(public, final) for (i in primaryColumns.indices) { val column = primaryColumns[i] @@ -487,21 +490,21 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab } `override fun`(ArrayTypeName.of(Any::class.java), "getCachingColumnValuesFromCursor", - param(ArrayTypeName.of(Any::class.java), "inValues"), - param(ClassNames.FLOW_CURSOR, "cursor")) { + param(ArrayTypeName.of(Any::class.java), "inValues"), + param(ClassNames.FLOW_CURSOR, "cursor")) { modifiers(public, final) for (i in primaryColumns.indices) { val column = primaryColumns[i] val method = DefinitionUtils.getLoadFromCursorMethodString(column.elementTypeName, column.wrapperTypeName) statement("inValues[$i] = ${LoadFromCursorMethod.PARAM_CURSOR}" + - ".$method(${LoadFromCursorMethod.PARAM_CURSOR}.getColumnIndex(${column.columnName.S}))") + ".$method(${LoadFromCursorMethod.PARAM_CURSOR}.getColumnIndex(${column.columnName.S}))") } `return`("inValues") } } else { // single primary key `override fun`(Any::class, "getCachingColumnValueFromModel", - param(elementClassName!!, ModelUtils.variable)) { + param(elementClassName!!, ModelUtils.variable)) { modifiers(public, final) addCode(primaryColumns[0].getSimpleAccessString()) } @@ -532,7 +535,7 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab if (!customCacheFieldName.isNullOrEmpty()) { `override fun`(ParameterizedTypeName.get(ClassNames.MODEL_CACHE, elementClassName, - WildcardTypeName.subtypeOf(Any::class.java)), "createModelCache") { + WildcardTypeName.subtypeOf(Any::class.java)), "createModelCache") { modifiers(public, final) `return`("\$T.$customCacheFieldName", elementClassName) } @@ -540,7 +543,7 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab if (!customMultiCacheFieldName.isNullOrEmpty()) { `override fun`(ParameterizedTypeName.get(ClassNames.MULTI_KEY_CACHE_CONVERTER, - WildcardTypeName.subtypeOf(Any::class.java)), "getCacheConverter") { + WildcardTypeName.subtypeOf(Any::class.java)), "getCacheConverter") { modifiers(public, final) `return`("\$T.$customMultiCacheFieldName", elementClassName) } @@ -548,8 +551,8 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab if (foreignKeyDefinitions.isNotEmpty()) { `override fun`(TypeName.VOID, "reloadRelationships", - param(elementClassName!!, ModelUtils.variable), - param(ClassNames.FLOW_CURSOR, LoadFromCursorMethod.PARAM_CURSOR)) { + param(elementClassName!!, ModelUtils.variable), + param(ClassNames.FLOW_CURSOR, LoadFromCursorMethod.PARAM_CURSOR)) { modifiers(public, final) code { val noIndex = AtomicInteger(-1) @@ -563,6 +566,6 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab } methods.mapNotNull { it.methodSpec } - .forEach { typeBuilder.addMethod(it) } + .forEach { typeBuilder.addMethod(it) } } } diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ColumnAccessCombiner.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ColumnAccessCombiner.kt index 9e6dd9688..3711c12c1 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ColumnAccessCombiner.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ColumnAccessCombiner.kt @@ -23,16 +23,19 @@ abstract class ColumnAccessCombiner(val combiner: Combiner) { fun getFieldAccessBlock(existingBuilder: CodeBlock.Builder, modelBlock: CodeBlock, - useWrapper: Boolean = true): CodeBlock { + useWrapper: Boolean = true, + defineProperty: Boolean = true): CodeBlock { var fieldAccess: CodeBlock = CodeBlock.of("") combiner.apply { if (wrapperLevelAccessor != null && !fieldTypeName.isPrimitive) { fieldAccess = CodeBlock.of("ref" + fieldLevelAccessor.propertyName) - existingBuilder.addStatement("\$T \$L = \$L != null ? \$L : null", - wrapperFieldTypeName, fieldAccess, - fieldLevelAccessor.get(modelBlock), - wrapperLevelAccessor.get(fieldLevelAccessor.get(modelBlock))) + if (defineProperty) { + existingBuilder.addStatement("\$T \$L = \$L != null ? \$L : null", + wrapperFieldTypeName, fieldAccess, + fieldLevelAccessor.get(modelBlock), + wrapperLevelAccessor.get(fieldLevelAccessor.get(modelBlock))) + } } else { if (useWrapper && wrapperLevelAccessor != null) { fieldAccess = wrapperLevelAccessor.get(fieldLevelAccessor.get(modelBlock)) @@ -46,7 +49,8 @@ abstract class ColumnAccessCombiner(val combiner: Combiner) { abstract fun CodeBlock.Builder.addCode(columnRepresentation: String, defaultValue: CodeBlock? = null, index: Int = -1, - modelBlock: CodeBlock = CodeBlock.of("model")) + modelBlock: CodeBlock = CodeBlock.of("model"), + defineProperty: Boolean = true) open fun addNull(code: CodeBlock.Builder, columnRepresentation: String, index: Int = -1) { @@ -56,7 +60,7 @@ abstract class ColumnAccessCombiner(val combiner: Combiner) { class SimpleAccessCombiner(combiner: Combiner) : ColumnAccessCombiner(combiner) { override fun CodeBlock.Builder.addCode(columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock) { + defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock, defineProperty: Boolean) { statement("return \$L", getFieldAccessBlock(this, modelBlock)) } @@ -68,7 +72,7 @@ class ExistenceAccessCombiner(combiner: Combiner, val tableClassName: ClassName) : ColumnAccessCombiner(combiner) { override fun CodeBlock.Builder.addCode(columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock) { + defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock, defineProperty: Boolean) { combiner.apply { if (autoRowId) { @@ -94,9 +98,9 @@ class ExistenceAccessCombiner(combiner: Combiner, } add("\$T.selectCountOf()\n.from(\$T.class)\n" + - ".where(getPrimaryConditionClause(\$L))\n" + - ".hasData(wrapper)", - ClassNames.SQLITE, tableClassName, modelBlock) + ".where(getPrimaryConditionClause(\$L))\n" + + ".hasData(wrapper)", + ClassNames.SQLITE, tableClassName, modelBlock) } add(";\n") } @@ -109,7 +113,7 @@ class ContentValuesCombiner(combiner: Combiner) override fun CodeBlock.Builder.addCode(columnRepresentation: String, defaultValue: CodeBlock?, index: Int, - modelBlock: CodeBlock) { + modelBlock: CodeBlock, defineProperty: Boolean) { combiner.apply { val fieldAccess: CodeBlock = getFieldAccessBlock(this@addCode, modelBlock) if (fieldTypeName.isPrimitive) { @@ -122,10 +126,10 @@ class ContentValuesCombiner(combiner: Combiner) subWrapperFieldAccess = subWrapperAccessor.get(storedFieldAccess) } statement("values.put(\$S, \$L != null ? \$L : \$L)", - QueryBuilder.quote(columnRepresentation), storedFieldAccess, subWrapperFieldAccess, defaultValue) + QueryBuilder.quote(columnRepresentation), storedFieldAccess, subWrapperFieldAccess, defaultValue) } else { statement("values.put(\$S, \$L)", - QueryBuilder.quote(columnRepresentation), fieldAccess) + QueryBuilder.quote(columnRepresentation), fieldAccess) } } } @@ -140,34 +144,40 @@ class SqliteStatementAccessCombiner(combiner: Combiner) : ColumnAccessCombiner(combiner) { override fun CodeBlock.Builder.addCode(columnRepresentation: String, defaultValue: CodeBlock?, index: Int, - modelBlock: CodeBlock) { + modelBlock: CodeBlock, defineProperty: Boolean) { combiner.apply { - val fieldAccess: CodeBlock = getFieldAccessBlock(this@addCode, modelBlock) + val fieldAccess: CodeBlock = getFieldAccessBlock(this@addCode, modelBlock, + defineProperty = defineProperty) val wrapperMethod = SQLiteHelper[wrapperFieldTypeName ?: fieldTypeName].sqliteStatementWrapperMethod val statementMethod = SQLiteHelper[fieldTypeName].sqLiteStatementMethod + var offset = "$index + $columnRepresentation" + if (columnRepresentation.isNullOrEmpty()) { + offset = "$index" + } if (fieldTypeName.isPrimitive) { - statement("statement.bind$statementMethod($index + $columnRepresentation, $fieldAccess)") + statement("statement.bind$statementMethod($offset, $fieldAccess)") } else { val subWrapperFieldAccess = subWrapperAccessor?.get(fieldAccess) ?: fieldAccess if (!defaultValue.toString().isNullOrEmpty()) { `if`("$fieldAccess != null") { - statement("statement.bind$wrapperMethod($index + $columnRepresentation," + - " $subWrapperFieldAccess)") + statement("statement.bind$wrapperMethod($offset, $subWrapperFieldAccess)") }.`else` { - statement("statement.bind$statementMethod($index + $columnRepresentation," + - " $defaultValue)") + statement("statement.bind$statementMethod($offset, $defaultValue)") } } else { - statement("statement.bind${wrapperMethod}OrNull($index + $columnRepresentation," + - " $subWrapperFieldAccess)") + statement("statement.bind${wrapperMethod}OrNull($offset, $subWrapperFieldAccess)") } } } } override fun addNull(code: CodeBlock.Builder, columnRepresentation: String, index: Int) { - code.addStatement("statement.bindNull($index + $columnRepresentation)") + var access = "$index + $columnRepresentation" + if (columnRepresentation.isEmpty()) { + access = "$index" + } + code.addStatement("statement.bindNull($access)") } } @@ -180,7 +190,7 @@ class LoadFromCursorAccessCombiner(combiner: Combiner, override fun CodeBlock.Builder.addCode(columnRepresentation: String, defaultValue: CodeBlock?, index: Int, - modelBlock: CodeBlock) { + modelBlock: CodeBlock, defineProperty: Boolean) { combiner.apply { var indexName = if (!orderedCursorLookup) { CodeBlock.of(columnRepresentation.S) @@ -192,13 +202,13 @@ class LoadFromCursorAccessCombiner(combiner: Combiner, if (!orderedCursorLookup) { indexName = CodeBlock.of(nameAllocator.newName("index_$columnRepresentation", columnRepresentation)) statement("\$T \$L = cursor.getColumnIndex(\$S)", Int::class.java, indexName, - columnRepresentation) + columnRepresentation) beginControlFlow("if (\$1L != -1 && !cursor.isNull(\$1L))", indexName) } else { beginControlFlow("if (!cursor.isNull(\$1L))", index) } val cursorAccess = CodeBlock.of("cursor.\$L(\$L)", - SQLiteHelper.getMethod(wrapperFieldTypeName ?: fieldTypeName), indexName) + SQLiteHelper.getMethod(wrapperFieldTypeName ?: fieldTypeName), indexName) // special case where we need to append try catch hack val isEnum = wrapperLevelAccessor is EnumColumnAccessor if (isEnum) { @@ -206,16 +216,16 @@ class LoadFromCursorAccessCombiner(combiner: Combiner, } if (subWrapperAccessor != null) { statement(fieldLevelAccessor.set( - wrapperLevelAccessor.set(subWrapperAccessor.set(cursorAccess)), modelBlock)) + wrapperLevelAccessor.set(subWrapperAccessor.set(cursorAccess)), modelBlock)) } else { statement(fieldLevelAccessor.set( - wrapperLevelAccessor.set(cursorAccess), modelBlock)) + wrapperLevelAccessor.set(cursorAccess), modelBlock)) } if (isEnum) { catch(IllegalArgumentException::class) { if (assignDefaultValuesFromCursor) { statement(fieldLevelAccessor.set(wrapperLevelAccessor.set(defaultValue, - isDefault = true), modelBlock)) + isDefault = true), modelBlock)) } else { statement(fieldLevelAccessor.set(defaultValue, modelBlock)) } @@ -224,7 +234,7 @@ class LoadFromCursorAccessCombiner(combiner: Combiner, if (assignDefaultValuesFromCursor) { nextControlFlow("else") statement(fieldLevelAccessor.set(wrapperLevelAccessor.set(defaultValue, - isDefault = true), modelBlock)) + isDefault = true), modelBlock)) } endControlFlow() } else { @@ -234,7 +244,7 @@ class LoadFromCursorAccessCombiner(combiner: Combiner, defaultValueBlock = fieldLevelAccessor.get(modelBlock) } val cursorAccess = CodeBlock.of("cursor.\$LOrDefault(\$L${if (hasDefaultValue) ", $defaultValueBlock" else ""})", - SQLiteHelper.getMethod(wrapperFieldTypeName ?: fieldTypeName), indexName) + SQLiteHelper.getMethod(wrapperFieldTypeName ?: fieldTypeName), indexName) statement(fieldLevelAccessor.set(cursorAccess, modelBlock)) } } @@ -245,23 +255,23 @@ class PrimaryReferenceAccessCombiner(combiner: Combiner) : ColumnAccessCombiner(combiner) { override fun CodeBlock.Builder.addCode(columnRepresentation: String, defaultValue: CodeBlock?, index: Int, - modelBlock: CodeBlock) { + modelBlock: CodeBlock, defineProperty: Boolean) { val wrapperLevelAccessor = this@PrimaryReferenceAccessCombiner.combiner.wrapperLevelAccessor statement("clause.and(\$L.\$Leq(\$L))", columnRepresentation, - if (!wrapperLevelAccessor.isPrimitiveTarget()) "invertProperty()." else "", - getFieldAccessBlock(this, modelBlock, wrapperLevelAccessor !is BooleanColumnAccessor)) + if (!wrapperLevelAccessor.isPrimitiveTarget()) "invertProperty()." else "", + getFieldAccessBlock(this, modelBlock, wrapperLevelAccessor !is BooleanColumnAccessor)) } override fun addNull(code: CodeBlock.Builder, columnRepresentation: String, index: Int) { code.addStatement("clause.and(\$L.eq((\$T) \$L))", columnRepresentation, - ClassNames.ICONDITIONAL, "null") + ClassNames.ICONDITIONAL, "null") } } class UpdateAutoIncrementAccessCombiner(combiner: Combiner) : ColumnAccessCombiner(combiner) { override fun CodeBlock.Builder.addCode(columnRepresentation: String, defaultValue: CodeBlock?, - index: Int, modelBlock: CodeBlock) { + index: Int, modelBlock: CodeBlock, defineProperty: Boolean) { combiner.apply { var method = "" if (SQLiteHelper.containsNumberMethod(fieldTypeName.unbox())) { @@ -277,7 +287,7 @@ class UpdateAutoIncrementAccessCombiner(combiner: Combiner) class CachingIdAccessCombiner(combiner: Combiner) : ColumnAccessCombiner(combiner) { override fun CodeBlock.Builder.addCode(columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock) { + defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock, defineProperty: Boolean) { statement("inValues[\$L] = \$L", index, getFieldAccessBlock(this, modelBlock)) } @@ -288,7 +298,7 @@ class SaveModelAccessCombiner(combiner: Combiner, val extendsBaseModel: Boolean) : ColumnAccessCombiner(combiner) { override fun CodeBlock.Builder.addCode(columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock) { + defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock, defineProperty: Boolean) { combiner.apply { val access = getFieldAccessBlock(this@addCode, modelBlock) `if`("$access != null") { @@ -296,7 +306,7 @@ class SaveModelAccessCombiner(combiner: Combiner, statement("$access.save(${wrapperIfBaseModel(extendsBaseModel)})") } else { statement("\$T.getModelAdapter(\$T.class).save($access, ${ModelUtils.wrapper})", - ClassNames.FLOW_MANAGER, fieldTypeName) + ClassNames.FLOW_MANAGER, fieldTypeName) } }.end() } @@ -309,7 +319,7 @@ class DeleteModelAccessCombiner(combiner: Combiner, val extendsBaseModel: Boolean) : ColumnAccessCombiner(combiner) { override fun CodeBlock.Builder.addCode(columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock) { + defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock, defineProperty: Boolean) { combiner.apply { val access = getFieldAccessBlock(this@addCode, modelBlock) `if`("$access != null") { @@ -317,7 +327,7 @@ class DeleteModelAccessCombiner(combiner: Combiner, statement("$access.delete(${wrapperIfBaseModel(extendsBaseModel)})") } else { statement("\$T.getModelAdapter(\$T.class).delete($access, ${ModelUtils.wrapper})", - ClassNames.FLOW_MANAGER, fieldTypeName) + ClassNames.FLOW_MANAGER, fieldTypeName) } }.end() } diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ColumnDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ColumnDefinition.kt index fba1b4e14..070b92619 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ColumnDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ColumnDefinition.kt @@ -30,7 +30,8 @@ open class ColumnDefinition @JvmOverloads constructor(processorManager: ProcessorManager, element: Element, var baseTableDefinition: BaseTableDefinition, isPackagePrivate: Boolean, var column: Column? = element.annotation(), - primaryKey: PrimaryKey? = element.annotation()) + primaryKey: PrimaryKey? = element.annotation(), + notNullConflict: ConflictAction = ConflictAction.NONE) : BaseDefinition(element, processorManager) { private val QUOTE_PATTERN = Pattern.compile("\".*\"") @@ -69,11 +70,13 @@ constructor(processorManager: ProcessorManager, element: Element, var typeConverterDefinition: TypeConverterDefinition? = null - open val insertStatementColumnName: CodeBlock - get() = CodeBlock.builder().add("\$L", QueryBuilder.quote(columnName)).build() + open val updateStatementBlock: CodeBlock + get() = CodeBlock.of("${QueryBuilder.quote(columnName)}=?") - open val insertStatementValuesString: CodeBlock? = CodeBlock.builder().add("?").build() + open val insertStatementColumnName: CodeBlock + get() = CodeBlock.of("\$L", QueryBuilder.quote(columnName)) + open val insertStatementValuesString: CodeBlock? = CodeBlock.of("?") open val typeConverterElementNames: List get() = arrayListOf(elementTypeName) @@ -87,6 +90,11 @@ constructor(processorManager: ProcessorManager, element: Element, onNullConflict = notNullAnno.onNullConflict } + if (onNullConflict == ConflictAction.NONE && notNullConflict != ConflictAction.NONE) { + onNullConflict = notNullConflict + notNull = true + } + column?.let { this.columnName = if (it.name == "") element.simpleName.toString() @@ -101,8 +109,8 @@ constructor(processorManager: ProcessorManager, element: Element, } if (defaultValue != null - && elementClassName == ClassName.get(String::class.java) - && !QUOTE_PATTERN.matcher(defaultValue).find()) { + && elementClassName == ClassName.get(String::class.java) + && !QUOTE_PATTERN.matcher(defaultValue).find()) { defaultValue = "\"" + defaultValue + "\"" } } @@ -115,19 +123,19 @@ constructor(processorManager: ProcessorManager, element: Element, if (isPackagePrivate) { columnAccessor = PackagePrivateScopeColumnAccessor(elementName, packageName, - baseTableDefinition.databaseDefinition?.classSeparator, - ClassName.get(element.enclosingElement as TypeElement).simpleName()) + baseTableDefinition.databaseDefinition?.classSeparator, + ClassName.get(element.enclosingElement as TypeElement).simpleName()) PackagePrivateScopeColumnAccessor.putElement( - (columnAccessor as PackagePrivateScopeColumnAccessor).helperClassName, - columnName) + (columnAccessor as PackagePrivateScopeColumnAccessor).helperClassName, + columnName) } else { val isPrivate = element.modifiers.contains(Modifier.PRIVATE) if (isPrivate) { val isBoolean = elementTypeName?.box() == TypeName.BOOLEAN.box() val useIs = isBoolean - && baseTableDefinition is TableDefinition && (baseTableDefinition as TableDefinition).useIsForPrivateBooleans + && baseTableDefinition is TableDefinition && (baseTableDefinition as TableDefinition).useIsForPrivateBooleans columnAccessor = PrivateScopeColumnAccessor(elementName, object : GetterSetter { override val getterName: String = column?.getterName ?: "" override val setterName: String = column?.setterName ?: "" @@ -176,7 +184,7 @@ constructor(processorManager: ProcessorManager, element: Element, hasCustomConverter = false if (typeConverterClassName != null && typeMirror != null && - typeConverterClassName != ClassNames.TYPE_CONVERTER) { + typeConverterClassName != ClassNames.TYPE_CONVERTER) { typeConverterDefinition = TypeConverterDefinition(typeConverterClassName, typeMirror, manager) evaluateTypeConverter(typeConverterDefinition, true) } @@ -194,7 +202,7 @@ constructor(processorManager: ProcessorManager, element: Element, // do nothing, for now. } else if (elementTypeName is ArrayTypeName) { processorManager.messager.printMessage(Diagnostic.Kind.ERROR, - "Columns cannot be of array type.") + "Columns cannot be of array type.") } else { if (elementTypeName == TypeName.BOOLEAN) { wrapperAccessor = BooleanColumnAccessor() @@ -214,7 +222,7 @@ constructor(processorManager: ProcessorManager, element: Element, } combiner = Combiner(columnAccessor, elementTypeName!!, wrapperAccessor, wrapperTypeName, - subWrapperAccessor) + subWrapperAccessor) } private fun evaluateTypeConverter(typeConverterDefinition: TypeConverterDefinition?, @@ -224,7 +232,7 @@ constructor(processorManager: ProcessorManager, element: Element, if (it.modelTypeName != elementTypeName) { manager.logError("The specified custom TypeConverter's Model Value ${it.modelTypeName}" + - " from ${it.className} must match the type of the column $elementTypeName. ") + " from ${it.className} must match the type of the column $elementTypeName. ") } else { hasTypeConverter = true hasCustomConverter = isCustom @@ -263,21 +271,21 @@ constructor(processorManager: ProcessorManager, element: Element, } val fieldBuilder = FieldSpec.builder(propParam, - propertyFieldName, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + propertyFieldName, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) if (isNonPrimitiveTypeConverter) { val codeBlock = CodeBlock.builder() codeBlock.add("new \$T(\$T.class, \$S, true,", propParam, tableClass, columnName) codeBlock.add("\nnew \$T() {" + - "\n@Override" + - "\npublic \$T getTypeConverter(Class modelClass) {" + - "\n \$T adapter = (\$T) \$T.getInstanceAdapter(modelClass);" + - "\nreturn adapter.\$L;" + - "\n}" + - "\n})", ClassNames.TYPE_CONVERTER_GETTER, ClassNames.TYPE_CONVERTER, - baseTableDefinition.outputClassName, baseTableDefinition.outputClassName, - ClassNames.FLOW_MANAGER, - (wrapperAccessor as TypeConverterScopeColumnAccessor).typeConverterFieldName) + "\n@Override" + + "\npublic \$T getTypeConverter(Class modelClass) {" + + "\n \$T adapter = (\$T) \$T.getInstanceAdapter(modelClass);" + + "\nreturn adapter.\$L;" + + "\n}" + + "\n})", ClassNames.TYPE_CONVERTER_GETTER, ClassNames.TYPE_CONVERTER, + baseTableDefinition.outputClassName, baseTableDefinition.outputClassName, + ClassNames.FLOW_MANAGER, + (wrapperAccessor as TypeConverterScopeColumnAccessor).typeConverterFieldName) fieldBuilder.initializer(codeBlock.build()) } else { fieldBuilder.initializer("new \$T(\$T.class, \$S)", propParam, tableClass, columnName) @@ -322,9 +330,11 @@ constructor(processorManager: ProcessorManager, element: Element, index.incrementAndGet() } - open fun getSQLiteStatementMethod(index: AtomicInteger) = code { + open fun getSQLiteStatementMethod(index: AtomicInteger, useStart: Boolean, + defineProperty: Boolean = true) = code { SqliteStatementAccessCombiner(combiner).apply { - addCode("start", getDefaultValueBlock(), index.get(), modelBlock) + addCode(if (useStart) "start" else "", getDefaultValueBlock(), index.get(), modelBlock, + defineProperty) } this } @@ -333,8 +343,8 @@ constructor(processorManager: ProcessorManager, element: Element, nameAllocator: NameAllocator) = code { LoadFromCursorAccessCombiner(combiner, defaultValue != null, - nameAllocator, baseTableDefinition.orderedCursorLookUp, - baseTableDefinition.assignDefaultValuesFromCursor).apply { + nameAllocator, baseTableDefinition.orderedCursorLookUp, + baseTableDefinition.assignDefaultValuesFromCursor).apply { addCode(columnName, getDefaultValueBlock(), index.get(), modelBlock) } this @@ -369,10 +379,10 @@ constructor(processorManager: ProcessorManager, element: Element, open fun appendExistenceMethod(codeBuilder: CodeBlock.Builder) { ExistenceAccessCombiner(combiner, isRowId || isPrimaryKeyAutoIncrement, - isQuickCheckPrimaryKeyAutoIncrement, baseTableDefinition.elementClassName!!) - .apply { - codeBuilder.addCode(columnName, getDefaultValueBlock(), 0, modelBlock) - } + isQuickCheckPrimaryKeyAutoIncrement, baseTableDefinition.elementClassName!!) + .apply { + codeBuilder.addCode(columnName, getDefaultValueBlock(), 0, modelBlock) + } } open fun appendPropertyComparisonAccessStatement(codeBuilder: CodeBlock.Builder) { @@ -389,9 +399,9 @@ constructor(processorManager: ProcessorManager, element: Element, codeBlockBuilder.add(" PRIMARY KEY ") if (baseTableDefinition is TableDefinition && - !(baseTableDefinition as TableDefinition).primaryKeyConflictActionName.isNullOrEmpty()) { + !(baseTableDefinition as TableDefinition).primaryKeyConflictActionName.isNullOrEmpty()) { codeBlockBuilder.add("ON CONFLICT \$L ", - (baseTableDefinition as TableDefinition).primaryKeyConflictActionName) + (baseTableDefinition as TableDefinition).primaryKeyConflictActionName) } codeBlockBuilder.add("AUTOINCREMENT") @@ -428,8 +438,8 @@ constructor(processorManager: ProcessorManager, element: Element, if (elementTypeName == TypeName.BOOLEAN) { defaultValue = "false" } else if (elementTypeName == TypeName.BYTE || elementTypeName == TypeName.INT - || elementTypeName == TypeName.DOUBLE || elementTypeName == TypeName.FLOAT - || elementTypeName == TypeName.LONG || elementTypeName == TypeName.SHORT) { + || elementTypeName == TypeName.DOUBLE || elementTypeName == TypeName.FLOAT + || elementTypeName == TypeName.LONG || elementTypeName == TypeName.SHORT) { defaultValue = "($elementTypeName) 0" } else if (elementTypeName == TypeName.CHAR) { defaultValue = "'\\u0000'" diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ForeignKeyAccessCombiner.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ForeignKeyAccessCombiner.kt index d4011b8b2..4ded1e4c0 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ForeignKeyAccessCombiner.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ForeignKeyAccessCombiner.kt @@ -19,13 +19,14 @@ class ForeignKeyAccessCombiner(val fieldAccessor: ColumnAccessor) { var fieldAccesses: List = arrayListOf() - fun addCode(code: CodeBlock.Builder, index: AtomicInteger) { + fun addCode(code: CodeBlock.Builder, index: AtomicInteger, useStart: Boolean = true, + defineProperty: Boolean = true) { val modelAccessBlock = fieldAccessor.get(modelBlock) code.beginControlFlow("if (\$L != null)", modelAccessBlock) val nullAccessBlock = CodeBlock.builder() for ((i, it) in fieldAccesses.withIndex()) { - it.addCode(code, index.get(), modelAccessBlock) - it.addNull(nullAccessBlock, index.get()) + it.addCode(code, index.get(), modelAccessBlock, useStart, defineProperty) + it.addNull(nullAccessBlock, index.get(), useStart) // do not increment last if (i < fieldAccesses.size - 1) { @@ -33,8 +34,8 @@ class ForeignKeyAccessCombiner(val fieldAccessor: ColumnAccessor) { } } code.nextControlFlow("else") - .add(nullAccessBlock.build().toString()) - .endControlFlow() + .add(nullAccessBlock.build().toString()) + .endControlFlow() } } @@ -42,15 +43,17 @@ class ForeignKeyAccessField(val columnRepresentation: String, val columnAccessCombiner: ColumnAccessCombiner, val defaultValue: CodeBlock? = null) { - fun addCode(code: CodeBlock.Builder, index: Int, - modelAccessBlock: CodeBlock) { + fun addCode(code: CodeBlock.Builder, index: Int, modelAccessBlock: CodeBlock, + useStart: Boolean = true, + defineProperty: Boolean = true) { columnAccessCombiner.apply { - code.addCode(columnRepresentation, defaultValue, index, modelAccessBlock) + code.addCode(if (useStart) columnRepresentation else "", defaultValue, index, + modelAccessBlock, defineProperty) } } - fun addNull(code: CodeBlock.Builder, index: Int) { - columnAccessCombiner.addNull(code, columnRepresentation, index) + fun addNull(code: CodeBlock.Builder, index: Int, useStart: Boolean) { + columnAccessCombiner.addNull(code, if (useStart) columnRepresentation else "", index) } } @@ -67,16 +70,16 @@ class ForeignKeyLoadFromCursorCombiner(val fieldAccessor: ColumnAccessor, if (!isStubbed) { setterBlock.add("\$T.select().from(\$T.class).where()", - ClassNames.SQLITE, referencedTypeName) + ClassNames.SQLITE, referencedTypeName) } else { setterBlock.statement( - fieldAccessor.set(CodeBlock.of("new \$T()", referencedTypeName), modelBlock)) + fieldAccessor.set(CodeBlock.of("new \$T()", referencedTypeName), modelBlock)) } for ((i, it) in fieldAccesses.withIndex()) { it.addRetrieval(setterBlock, index.get(), referencedTableTypeName, isStubbed, fieldAccessor, nameAllocator) it.addColumnIndex(code, index.get(), referencedTableTypeName, nameAllocator) it.addIndexCheckStatement(ifChecker, index.get(), referencedTableTypeName, - i == fieldAccesses.size - 1, nameAllocator) + i == fieldAccesses.size - 1, nameAllocator) if (i < fieldAccesses.size - 1) { index.incrementAndGet() @@ -92,19 +95,19 @@ class ForeignKeyLoadFromCursorCombiner(val fieldAccessor: ColumnAccessor, code.add(setterBlock.build()) } code.nextControlFlow("else") - .statement(fieldAccessor.set(CodeBlock.of("null"), modelBlock)) - .endControlFlow() + .statement(fieldAccessor.set(CodeBlock.of("null"), modelBlock)) + .endControlFlow() } } class PartialLoadFromCursorAccessCombiner( - val columnRepresentation: String, - val propertyRepresentation: String, - val fieldTypeName: TypeName, - val orderedCursorLookup: Boolean = false, - val fieldLevelAccessor: ColumnAccessor? = null, - val subWrapperAccessor: ColumnAccessor? = null, - val subWrapperTypeName: TypeName? = null) { + val columnRepresentation: String, + val propertyRepresentation: String, + val fieldTypeName: TypeName, + val orderedCursorLookup: Boolean = false, + val fieldLevelAccessor: ColumnAccessor? = null, + val subWrapperAccessor: ColumnAccessor? = null, + val subWrapperTypeName: TypeName? = null) { var indexName: CodeBlock? = null @@ -113,7 +116,7 @@ class PartialLoadFromCursorAccessCombiner( indexName = if (!orderedCursorLookup) { // post fix with referenced type name simple name CodeBlock.of(nameAllocator.newName("index_${columnRepresentation}_" + - if (referencedTypeName is ClassName) referencedTypeName.simpleName() else "", columnRepresentation)) + if (referencedTypeName is ClassName) referencedTypeName.simpleName() else "", columnRepresentation)) } else { CodeBlock.of(index.toString()) } @@ -126,13 +129,13 @@ class PartialLoadFromCursorAccessCombiner( isStubbed: Boolean, parentAccessor: ColumnAccessor, nameAllocator: NameAllocator) { val cursorAccess = CodeBlock.of("cursor.\$L(\$L)", - SQLiteHelper.getMethod(subWrapperTypeName ?: fieldTypeName), - getIndexName(index, nameAllocator, referencedTableTypeName)) + SQLiteHelper.getMethod(subWrapperTypeName ?: fieldTypeName), + getIndexName(index, nameAllocator, referencedTableTypeName)) val fieldAccessBlock = subWrapperAccessor?.set(cursorAccess) ?: cursorAccess if (!isStubbed) { code.add(CodeBlock.of("\n.and(\$T.\$L.eq(\$L))", - referencedTableTypeName, propertyRepresentation, fieldAccessBlock)) + referencedTableTypeName, propertyRepresentation, fieldAccessBlock)) } else if (fieldLevelAccessor != null) { code.statement(fieldLevelAccessor.set(cursorAccess, parentAccessor.get(modelBlock))) } @@ -144,7 +147,7 @@ class PartialLoadFromCursorAccessCombiner( nameAllocator: NameAllocator) { if (!orderedCursorLookup) { code.statement(CodeBlock.of("int \$L = cursor.getColumnIndex(\$S)", - getIndexName(index, nameAllocator, referencedTableTypeName), columnRepresentation)) + getIndexName(index, nameAllocator, referencedTableTypeName), columnRepresentation)) } } diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ForeignKeyColumnDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ForeignKeyColumnDefinition.kt index 7b342c4db..46297f071 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ForeignKeyColumnDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ForeignKeyColumnDefinition.kt @@ -111,12 +111,12 @@ class ForeignKeyColumnDefinition(manager: ProcessorManager, tableDefinition: Tab } if (it.columnName.isNullOrEmpty()) { manager.logError("Found empty reference name at ${it.foreignColumnName}" + - " from table ${baseTableDefinition.elementName}") + " from table ${baseTableDefinition.elementName}") } typeBuilder.addField(FieldSpec.builder(propParam, it.columnName, - Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) - .initializer("new \$T(\$T.class, \$S)", propParam, tableClass, it.columnName) - .addJavadoc("Foreign Key" + if (isPrimaryKey) " / Primary Key" else "").build()) + Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + .initializer("new \$T(\$T.class, \$S)", propParam, tableClass, it.columnName) + .addJavadoc("Foreign Key" + if (isPrimaryKey) " / Primary Key" else "").build()) } } @@ -155,6 +155,20 @@ class ForeignKeyColumnDefinition(manager: ProcessorManager, tableDefinition: Tab } } + override val updateStatementBlock: CodeBlock + get() { + checkNeedsReferences() + val builder = CodeBlock.builder() + _foreignKeyReferenceDefinitionList.indices.forEach { i -> + if (i > 0) { + builder.add(",") + } + val referenceDefinition = _foreignKeyReferenceDefinitionList[i] + builder.add(CodeBlock.of("${QueryBuilder.quote(referenceDefinition.columnName)}=?")) + } + return builder.build() + } + override val insertStatementColumnName: CodeBlock get() { checkNeedsReferences() @@ -226,9 +240,10 @@ class ForeignKeyColumnDefinition(manager: ProcessorManager, tableDefinition: Tab codeBuilder.build() } - override fun getSQLiteStatementMethod(index: AtomicInteger): CodeBlock { + override fun getSQLiteStatementMethod(index: AtomicInteger, useStart: Boolean, + defineProperty: Boolean): CodeBlock { if (nonModelColumn) { - return super.getSQLiteStatementMethod(index) + return super.getSQLiteStatementMethod(index, useStart, defineProperty) } else { checkNeedsReferences() val codeBuilder = CodeBlock.builder() @@ -237,7 +252,7 @@ class ForeignKeyColumnDefinition(manager: ProcessorManager, tableDefinition: Tab _foreignKeyReferenceDefinitionList.forEach { foreignKeyCombiner.fieldAccesses += it.sqliteStatementField } - foreignKeyCombiner.addCode(codeBuilder, index) + foreignKeyCombiner.addCode(codeBuilder, index, useStart, defineProperty) } return codeBuilder.build() } @@ -253,11 +268,11 @@ class ForeignKeyColumnDefinition(manager: ProcessorManager, tableDefinition: Tab referencedTableClassName?.let { referencedTableClassName -> val tableDefinition = manager.getTableDefinition(baseTableDefinition.databaseDefinition?.elementTypeName, - referencedTableClassName) + referencedTableClassName) val outputClassName = tableDefinition?.outputClassName outputClassName?.let { val foreignKeyCombiner = ForeignKeyLoadFromCursorCombiner(columnAccessor, - referencedTableClassName, outputClassName, isStubbedRelationship, nameAllocator) + referencedTableClassName, outputClassName, isStubbedRelationship, nameAllocator) _foreignKeyReferenceDefinitionList.forEach { foreignKeyCombiner.fieldAccesses += it.partialAccessor } @@ -287,8 +302,8 @@ class ForeignKeyColumnDefinition(manager: ProcessorManager, tableDefinition: Tab if (!nonModelColumn && columnAccessor !is TypeConverterScopeColumnAccessor) { referencedTableClassName?.let { referencedTableClassName -> val saveAccessor = ForeignKeyAccessField(columnName, - SaveModelAccessCombiner(Combiner(columnAccessor, referencedTableClassName, wrapperAccessor, - wrapperTypeName, subWrapperAccessor), implementsModel, extendsBaseModel)) + SaveModelAccessCombiner(Combiner(columnAccessor, referencedTableClassName, wrapperAccessor, + wrapperTypeName, subWrapperAccessor), implementsModel, extendsBaseModel)) saveAccessor.addCode(codeBuilder, 0, modelBlock) } } @@ -298,8 +313,8 @@ class ForeignKeyColumnDefinition(manager: ProcessorManager, tableDefinition: Tab if (!nonModelColumn && columnAccessor !is TypeConverterScopeColumnAccessor) { referencedTableClassName?.let { referencedTableClassName -> val deleteAccessor = ForeignKeyAccessField(columnName, - DeleteModelAccessCombiner(Combiner(columnAccessor, referencedTableClassName, wrapperAccessor, - wrapperTypeName, subWrapperAccessor), implementsModel, extendsBaseModel)) + DeleteModelAccessCombiner(Combiner(columnAccessor, referencedTableClassName, wrapperAccessor, + wrapperTypeName, subWrapperAccessor), implementsModel, extendsBaseModel)) deleteAccessor.addCode(codeBuilder, 0, modelBlock) } } @@ -312,18 +327,18 @@ class ForeignKeyColumnDefinition(manager: ProcessorManager, tableDefinition: Tab private fun checkNeedsReferences() { val tableDefinition = (baseTableDefinition as TableDefinition) val referencedTableDefinition = manager.getTableDefinition( - tableDefinition.databaseTypeName, referencedTableClassName) + tableDefinition.databaseTypeName, referencedTableClassName) if (referencedTableDefinition == null) { manager.logError(ForeignKeyColumnDefinition::class, - "Could not find the referenced table definition $referencedTableClassName" + - " from ${tableDefinition.tableName}. " + - "Ensure it exists in the samedatabase ${tableDefinition.databaseTypeName}") + "Could not find the referenced table definition $referencedTableClassName" + + " from ${tableDefinition.tableName}. " + + "Ensure it exists in the samedatabase ${tableDefinition.databaseTypeName}") } else if (needsReferences) { val primaryColumns = referencedTableDefinition.primaryColumnDefinitions if (references?.isEmpty() ?: true) { primaryColumns.forEach { val foreignKeyReferenceDefinition = ForeignKeyReferenceDefinition(manager, - elementName, it.elementName, it, this, primaryColumns.size) + elementName, it.elementName, it, this, primaryColumns.size) _foreignKeyReferenceDefinitionList.add(foreignKeyReferenceDefinition) } needsReferences = false @@ -332,13 +347,13 @@ class ForeignKeyColumnDefinition(manager: ProcessorManager, tableDefinition: Tab val foundDefinition = primaryColumns.find { it.columnName == reference.foreignKeyColumnName } if (foundDefinition == null) { manager.logError(ForeignKeyColumnDefinition::class, - "Could not find referenced column ${reference.foreignKeyColumnName} " + - "from reference named ${reference.columnName}") + "Could not find referenced column ${reference.foreignKeyColumnName} " + + "from reference named ${reference.columnName}") } else { _foreignKeyReferenceDefinitionList.add( - ForeignKeyReferenceDefinition(manager, elementName, - foundDefinition.elementName, foundDefinition, this, - primaryColumns.size, reference.columnName)) + ForeignKeyReferenceDefinition(manager, elementName, + foundDefinition.elementName, foundDefinition, this, + primaryColumns.size, reference.columnName)) } } needsReferences = false diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ForeignKeyReferenceDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ForeignKeyReferenceDefinition.kt index 3d0593ee7..ea0a54e3a 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ForeignKeyReferenceDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ForeignKeyReferenceDefinition.kt @@ -52,12 +52,12 @@ class ForeignKeyReferenceDefinition(private val manager: ProcessorManager, columnAccessor = PrivateScopeColumnAccessor(foreignKeyFieldName, getterSetter, false) } else if (isReferencedFieldPackagePrivate) { columnAccessor = PackagePrivateScopeColumnAccessor(foreignKeyFieldName, packageName, - foreignKeyColumnDefinition.baseTableDefinition.databaseDefinition?.classSeparator, - name) + foreignKeyColumnDefinition.baseTableDefinition.databaseDefinition?.classSeparator, + name) PackagePrivateScopeColumnAccessor.putElement( - (columnAccessor as PackagePrivateScopeColumnAccessor).helperClassName, - foreignKeyFieldName) + (columnAccessor as PackagePrivateScopeColumnAccessor).helperClassName, + foreignKeyFieldName) } else { columnAccessor = VisibleScopeColumnAccessor(foreignKeyFieldName) } @@ -69,10 +69,10 @@ class ForeignKeyReferenceDefinition(private val manager: ProcessorManager, evaluateTypeConverter(typeConverterDefinition) val combiner = Combiner(columnAccessor, columnClassName!!, wrapperAccessor, - wrapperTypeName, subWrapperAccessor) + wrapperTypeName, subWrapperAccessor) partialAccessor = PartialLoadFromCursorAccessCombiner(columnName, foreignColumnName, - columnClassName, foreignKeyColumnDefinition.baseTableDefinition.orderedCursorLookUp, - columnAccessor, wrapperAccessor, wrapperTypeName) + columnClassName, foreignKeyColumnDefinition.baseTableDefinition.orderedCursorLookUp, + columnAccessor, wrapperAccessor, wrapperTypeName) primaryReferenceField = ForeignKeyAccessField(columnName, PrimaryReferenceAccessCombiner(combiner)) @@ -88,12 +88,12 @@ class ForeignKeyReferenceDefinition(private val manager: ProcessorManager, if (it.modelTypeName != columnClassName) { manager.logError("The specified custom TypeConverter's Model Value %1s from %1s must match the type of the column %1s. ", - it.modelTypeName, it.className, columnClassName) + it.modelTypeName, it.className, columnClassName) } else { hasTypeConverter = true val fieldName = foreignKeyColumnDefinition.baseTableDefinition - .addColumnForTypeConverter(foreignKeyColumnDefinition, it.className) + .addColumnForTypeConverter(foreignKeyColumnDefinition, it.className) wrapperAccessor = TypeConverterScopeColumnAccessor(fieldName) wrapperTypeName = it.dbTypeName @@ -109,7 +109,7 @@ class ForeignKeyReferenceDefinition(private val manager: ProcessorManager, if (!localColumnName.isNullOrEmpty()) { this.columnName = localColumnName } else if (!foreignKeyColumnDefinition.isPrimaryKey && !foreignKeyColumnDefinition.isPrimaryKeyAutoIncrement - && !foreignKeyColumnDefinition.isRowId || referenceCount > 0) { + && !foreignKeyColumnDefinition.isRowId || referenceCount > 0) { this.columnName = foreignKeyFieldName + "_" + referencedColumn.columnName } else { this.columnName = foreignKeyFieldName @@ -120,8 +120,8 @@ class ForeignKeyReferenceDefinition(private val manager: ProcessorManager, isReferencedFieldPackagePrivate = referencedColumn.columnAccessor is PackagePrivateScopeColumnAccessor val isPackagePrivate = ElementUtility.isPackagePrivate(referencedColumn.element) val isPackagePrivateNotInSamePackage = isPackagePrivate && - !ElementUtility.isInSamePackage(manager, referencedColumn.element, - foreignKeyColumnDefinition.element) + !ElementUtility.isInSamePackage(manager, referencedColumn.element, + foreignKeyColumnDefinition.element) isReferencedFieldPackagePrivate = isReferencedFieldPackagePrivate || isPackagePrivateNotInSamePackage val packageName = referencedColumn.packageName val name = ClassName.get(referencedColumn.element.enclosingElement as TypeElement).simpleName() diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/CodeExtensions.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/CodeExtensions.kt index 669aaf242..ad1a58f68 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/CodeExtensions.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/CodeExtensions.kt @@ -37,7 +37,7 @@ fun MethodSpec.Builder.statement(codeBlock: CodeBlock?): MethodSpec.Builder inline fun CodeBlock.Builder.catch(exception: KClass, function: CodeBlock.Builder.() -> CodeBlock.Builder) - = nextControl("catch", statement = "\$T e", args = arrayOf(exception), function = function).end() + = nextControl("catch", statement = "\$T e", args = exception.java, function = function).end() fun codeBlock(function: CodeBlock.Builder.() -> CodeBlock.Builder) = CodeBlock.builder().function().build() diff --git a/dbflow-processor/src/test/java/com/raizlabs/android/dbflow/processor/test/ForeignKeyAccessCombinerTests.kt b/dbflow-processor/src/test/java/com/raizlabs/android/dbflow/processor/test/ForeignKeyAccessCombinerTests.kt index d6d98a393..e3e178ad4 100644 --- a/dbflow-processor/src/test/java/com/raizlabs/android/dbflow/processor/test/ForeignKeyAccessCombinerTests.kt +++ b/dbflow-processor/src/test/java/com/raizlabs/android/dbflow/processor/test/ForeignKeyAccessCombinerTests.kt @@ -4,6 +4,7 @@ import com.raizlabs.android.dbflow.processor.definition.column.* import com.raizlabs.android.dbflow.processor.definition.column.PrimaryReferenceAccessCombiner import com.squareup.javapoet.ClassName import com.squareup.javapoet.CodeBlock +import com.squareup.javapoet.NameAllocator import com.squareup.javapoet.TypeName import org.junit.Assert.assertEquals import org.junit.Test @@ -105,7 +106,8 @@ class ForeignKeyAccessCombinerTest { fun test_canLoadFromCursor() { val foreignKeyAccessCombiner = ForeignKeyLoadFromCursorCombiner(VisibleScopeColumnAccessor("testModel1"), ClassName.get("com.raizlabs.android.dbflow.test.container", "ParentModel"), - ClassName.get("com.raizlabs.android.dbflow.test.container", "ParentModel_Table"), false) + ClassName.get("com.raizlabs.android.dbflow.test.container", "ParentModel_Table"), false, + NameAllocator()) foreignKeyAccessCombiner.fieldAccesses += PartialLoadFromCursorAccessCombiner("testmodel_id", "name", TypeName.get(String::class.java), false, null) foreignKeyAccessCombiner.fieldAccesses += PartialLoadFromCursorAccessCombiner("testmodel_type", @@ -130,7 +132,8 @@ class ForeignKeyAccessCombinerTest { fun test_canLoadFromCursorStubbed() { val foreignKeyAccessCombiner = ForeignKeyLoadFromCursorCombiner(VisibleScopeColumnAccessor("testModel1"), ClassName.get("com.raizlabs.android.dbflow.test.container", "ParentModel"), - ClassName.get("com.raizlabs.android.dbflow.test.container", "ParentModel_Table"), true) + ClassName.get("com.raizlabs.android.dbflow.test.container", "ParentModel_Table"), true, + NameAllocator()) foreignKeyAccessCombiner.fieldAccesses += PartialLoadFromCursorAccessCombiner("testmodel_id", "name", TypeName.get(String::class.java), false, VisibleScopeColumnAccessor("name")) foreignKeyAccessCombiner.fieldAccesses += PartialLoadFromCursorAccessCombiner("testmodel_type", diff --git a/dbflow-rx/src/main/java/com/raizlabs/android/dbflow/rx/language/TableChangeListenerEmitter.java b/dbflow-rx/src/main/java/com/raizlabs/android/dbflow/rx/language/TableChangeListenerEmitter.java index 1733a57f3..3dcc48bd1 100644 --- a/dbflow-rx/src/main/java/com/raizlabs/android/dbflow/rx/language/TableChangeListenerEmitter.java +++ b/dbflow-rx/src/main/java/com/raizlabs/android/dbflow/rx/language/TableChangeListenerEmitter.java @@ -1,9 +1,11 @@ package com.raizlabs.android.dbflow.rx.language; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.raizlabs.android.dbflow.config.FlowManager; -import com.raizlabs.android.dbflow.runtime.FlowContentObserver; +import com.raizlabs.android.dbflow.runtime.OnTableChangedListener; +import com.raizlabs.android.dbflow.runtime.TableNotifierRegister; import com.raizlabs.android.dbflow.sql.language.From; import com.raizlabs.android.dbflow.sql.language.Join; import com.raizlabs.android.dbflow.sql.language.Where; @@ -29,21 +31,21 @@ public TableChangeListenerEmitter(ModelQueriable modelQueriable) { @Override public void call(Emitter> modelQueriableEmitter) { modelQueriableEmitter.setSubscription( - new FlowContentObserverSubscription(modelQueriableEmitter)); + new FlowContentObserverSubscription(modelQueriableEmitter, modelQueriable.getTable())); } private class FlowContentObserverSubscription implements Subscription { private final Emitter> modelQueriableEmitter; - private final FlowContentObserver flowContentObserver = new FlowContentObserver(); + private final TableNotifierRegister register; private FlowContentObserverSubscription( - Emitter> modelQueriableEmitter) { + Emitter> modelQueriableEmitter, Class table) { this.modelQueriableEmitter = modelQueriableEmitter; + register = FlowManager.newRegisterForTable(table); - - From from = null; + From from = null; if (modelQueriable instanceof From) { from = (From) modelQueriable; } else if (modelQueriable instanceof Where @@ -55,32 +57,30 @@ private FlowContentObserverSubscription( // From could be part of many joins, so we register for all affected tables here. if (from != null) { java.util.Set> associatedTables = from.getAssociatedTables(); - for (Class table : associatedTables) { - flowContentObserver.registerForContentChanges(FlowManager.getContext(), table); + for (Class associated : associatedTables) { + register.register(associated); } } else { - flowContentObserver.registerForContentChanges(FlowManager.getContext(), - modelQueriable.getTable()); + register.register(table); } - flowContentObserver.addOnTableChangedListener(onTableChangedListener); + register.setListener(onTableChangedListener); } @Override public void unsubscribe() { - flowContentObserver.unregisterForContentChanges(FlowManager.getContext()); - flowContentObserver.removeTableChangedListener(onTableChangedListener); + register.unregisterAll(); } @Override public boolean isUnsubscribed() { - return !flowContentObserver.isSubscribed(); + return !register.isSubscribed(); } - private final FlowContentObserver.OnTableChangedListener onTableChangedListener - = new FlowContentObserver.OnTableChangedListener() { + private final OnTableChangedListener onTableChangedListener + = new OnTableChangedListener() { @Override - public void onTableChanged(@Nullable Class tableChanged, BaseModel.Action action) { + public void onTableChanged(@Nullable Class tableChanged, @NonNull BaseModel.Action action) { if (modelQueriable.getTable().equals(tableChanged)) { modelQueriableEmitter.onNext(modelQueriable); } diff --git a/dbflow-rx2/src/main/java/com/raizlabs/android/dbflow/rx2/language/TableChangeOnSubscribe.java b/dbflow-rx2/src/main/java/com/raizlabs/android/dbflow/rx2/language/TableChangeOnSubscribe.java index dbb78a059..e98a0288d 100644 --- a/dbflow-rx2/src/main/java/com/raizlabs/android/dbflow/rx2/language/TableChangeOnSubscribe.java +++ b/dbflow-rx2/src/main/java/com/raizlabs/android/dbflow/rx2/language/TableChangeOnSubscribe.java @@ -1,9 +1,11 @@ package com.raizlabs.android.dbflow.rx2.language; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.raizlabs.android.dbflow.config.FlowManager; -import com.raizlabs.android.dbflow.runtime.FlowContentObserver; +import com.raizlabs.android.dbflow.runtime.OnTableChangedListener; +import com.raizlabs.android.dbflow.runtime.TableNotifierRegister; import com.raizlabs.android.dbflow.sql.language.From; import com.raizlabs.android.dbflow.sql.language.Join; import com.raizlabs.android.dbflow.sql.language.Where; @@ -22,11 +24,12 @@ public class TableChangeOnSubscribe implements FlowableOnSubscribe modelQueriable; - private final FlowContentObserver flowContentObserver = new FlowContentObserver(); + private final TableNotifierRegister register; private FlowableEmitter> flowableEmitter; public TableChangeOnSubscribe(ModelQueriable modelQueriable) { this.modelQueriable = modelQueriable; + register = FlowManager.newRegisterForTable(modelQueriable.getTable()); } @Override @@ -35,8 +38,7 @@ public void subscribe(FlowableEmitter> e) throws Exceptio flowableEmitter.setDisposable(Disposables.fromRunnable(new Runnable() { @Override public void run() { - flowContentObserver.unregisterForContentChanges(FlowManager.getContext()); - flowContentObserver.removeTableChangedListener(onTableChangedListener); + register.unregisterAll(); } })); @@ -44,7 +46,7 @@ public void run() { if (modelQueriable instanceof From) { from = (From) modelQueriable; } else if (modelQueriable instanceof Where - && ((Where) modelQueriable).getWhereBase() instanceof From) { + && ((Where) modelQueriable).getWhereBase() instanceof From) { //noinspection unchecked from = (From) ((Where) modelQueriable).getWhereBase(); } @@ -53,21 +55,20 @@ public void run() { if (from != null) { java.util.Set> associatedTables = from.getAssociatedTables(); for (Class table : associatedTables) { - flowContentObserver.registerForContentChanges(FlowManager.getContext(), table); + register.register(table); } } else { - flowContentObserver.registerForContentChanges(FlowManager.getContext(), - modelQueriable.getTable()); + register.register(modelQueriable.getTable()); } - flowContentObserver.addOnTableChangedListener(onTableChangedListener); + register.setListener(onTableChangedListener); flowableEmitter.onNext(modelQueriable); } - private final FlowContentObserver.OnTableChangedListener onTableChangedListener - = new FlowContentObserver.OnTableChangedListener() { + private final OnTableChangedListener onTableChangedListener + = new OnTableChangedListener() { @Override - public void onTableChanged(@Nullable Class tableChanged, BaseModel.Action action) { + public void onTableChanged(@Nullable Class tableChanged, @NonNull BaseModel.Action action) { if (modelQueriable.getTable().equals(tableChanged)) { flowableEmitter.onNext(modelQueriable); } diff --git a/dbflow-tests/build.gradle b/dbflow-tests/build.gradle index 9a281dd3e..9202e26ae 100644 --- a/dbflow-tests/build.gradle +++ b/dbflow-tests/build.gradle @@ -1,5 +1,6 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' apply plugin: 'com.getkeepsafe.dexcount' android { diff --git a/dbflow-tests/src/main/java/com/raizlabs/android/dbflow/User.java b/dbflow-tests/src/main/java/com/raizlabs/android/dbflow/User.java new file mode 100644 index 000000000..60ca0376b --- /dev/null +++ b/dbflow-tests/src/main/java/com/raizlabs/android/dbflow/User.java @@ -0,0 +1,21 @@ +package com.raizlabs.android.dbflow; + +import com.raizlabs.android.dbflow.annotation.Column; +import com.raizlabs.android.dbflow.annotation.PrimaryKey; +import com.raizlabs.android.dbflow.annotation.Table; + +@Table(database = AppDatabase.class, name = "User2") +public class User { + + @PrimaryKey + int id; + + @Column + String firstName; + + @Column + String lastName; + + @Column + String email; +} diff --git a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/OneToManyModels.kt b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/OneToManyModels.kt index 3c7374f75..0e92586ef 100644 --- a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/OneToManyModels.kt +++ b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/OneToManyModels.kt @@ -8,22 +8,40 @@ import com.raizlabs.android.dbflow.kotlinextensions.from import com.raizlabs.android.dbflow.kotlinextensions.select import com.raizlabs.android.dbflow.kotlinextensions.where import com.raizlabs.android.dbflow.models.TwoColumnModel_Table.id +import com.raizlabs.android.dbflow.structure.BaseModel @Table(database = TestDatabase::class) class OneToManyModel(@PrimaryKey var name: String? = null) { var orders: List? = null + var models: List? = null + @OneToMany(methods = arrayOf(OneToMany.Method.ALL), isVariablePrivate = true, - variableName = "orders", efficientMethods = false) + variableName = "orders", efficientMethods = false) fun getRelatedOrders(): List { var localOrders = orders if (localOrders == null) { localOrders = (select from TwoColumnModel::class where id.greaterThan(3)) - .queryList() + .queryList() } orders = localOrders return localOrders } -} \ No newline at end of file + @OneToMany(methods = arrayOf(OneToMany.Method.DELETE), isVariablePrivate = true, + variableName = "models") + fun getRelatedModels(): List { + var localModels = models + if (localModels == null) { + localModels = (select from OneToManyBaseModel::class).queryList() + } + models = localModels + return localModels + } + + +} + +@Table(database = TestDatabase::class) +class OneToManyBaseModel(@PrimaryKey var id: Int = 0) : BaseModel() \ No newline at end of file diff --git a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/SimpleTestModels.kt b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/SimpleTestModels.kt index cce4690c8..b6804b0da 100644 --- a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/SimpleTestModels.kt +++ b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/SimpleTestModels.kt @@ -4,6 +4,8 @@ import com.raizlabs.android.dbflow.TestDatabase import com.raizlabs.android.dbflow.annotation.* import com.raizlabs.android.dbflow.converter.TypeConverter import com.raizlabs.android.dbflow.data.Blob +import com.raizlabs.android.dbflow.structure.database.DatabaseStatement +import com.raizlabs.android.dbflow.structure.listener.SQLiteStatementListener /** * Description: @@ -18,11 +20,20 @@ class SimpleCustomModel(@Column var name: String? = "") class NumberModel(@PrimaryKey var id: Int = 0) @Table(database = TestDatabase::class) -class CharModel(@PrimaryKey var id: Int = 0, @Column var char: Char? = null) +class CharModel(@PrimaryKey var id: Int = 0, @Column var exampleChar: Char? = null) @Table(database = TestDatabase::class) class TwoColumnModel(@PrimaryKey var name: String? = "", @Column var id: Int = 0) +enum class Difficulty { + EASY, + MEDIUM, + HARD +} + +@Table(database = TestDatabase::class) +class EnumModel(@PrimaryKey var id: Int = 0, @Column var difficulty: Difficulty? = Difficulty.EASY) + @Table(database = TestDatabase::class, allFields = true) open class AllFieldsModel(@PrimaryKey var name: String? = null, var count: Int? = 0, @@ -53,7 +64,27 @@ class OrderCursorModel(@PrimaryKey var id: Int = 0, @Column var name: String? = @Table(database = TestDatabase::class) class TypeConverterModel(@PrimaryKey var id: Int = 0, @Column var blob: Blob? = null, - @Column(typeConverter = CustomTypeConverter::class) var customType: CustomType? = null) + @Column(typeConverter = CustomTypeConverter::class) + @PrimaryKey var customType: CustomType? = null) + +@Table(database = TestDatabase::class) +class SqlListenerModel(@PrimaryKey var id: Int = 0) : SQLiteStatementListener { + override fun onBindToStatement(databaseStatement: DatabaseStatement?) { + TODO("not implemented") + } + + override fun onBindToInsertStatement(databaseStatement: DatabaseStatement?) { + TODO("not implemented") + } + + override fun onBindToUpdateStatement(databaseStatement: DatabaseStatement?) { + TODO("not implemented") + } + + override fun onBindToDeleteStatement(databaseStatement: DatabaseStatement?) { + TODO("not implemented") + } +} class CustomType(var name: String? = "") diff --git a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/runtime/DirectNotifierTest.kt b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/runtime/DirectNotifierTest.kt index 5aff972ef..41335228e 100644 --- a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/runtime/DirectNotifierTest.kt +++ b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/runtime/DirectNotifierTest.kt @@ -19,13 +19,14 @@ import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(constants = BuildConfig::class, sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP), - assetDir = "build/intermediates/classes/test/") + assetDir = "build/intermediates/classes/test/") class DirectNotifierTest { val context: Context @@ -34,18 +35,18 @@ class DirectNotifierTest { @Before fun setupTest() { FlowManager.init(FlowConfig.Builder(context) - .addDatabaseConfig(DatabaseConfig.Builder(TestDatabase::class.java) - .transactionManagerCreator(::ImmediateTransactionManager) - .modelNotifier(DirectModelNotifier.get()) - .build()).build()) + .addDatabaseConfig(DatabaseConfig.Builder(TestDatabase::class.java) + .transactionManagerCreator(::ImmediateTransactionManager) + .modelNotifier(DirectModelNotifier.get()) + .build()).build()) } @Test fun validateCanNotifyDirect() { val simpleModel = SimpleModel("Name") - val modelChange = mock>() - DirectModelNotifier.get().registerForModelChanges(SimpleModel::class.java, modelChange) + val modelChange = mock>() + DirectModelNotifier.get().registerForModelStateChanges(SimpleModel::class.java, modelChange) simpleModel.insert() verify(modelChange).onModelChanged(simpleModel, BaseModel.Action.INSERT) @@ -62,20 +63,20 @@ class DirectNotifierTest { @Test fun validateCanNotifyWrapperClasses() { - val modelChange = mock>() - DirectModelNotifier.get().registerForModelChanges(SimpleModel::class.java, modelChange) + val modelChange = Mockito.mock(OnTableChangedListener::class.java) + DirectModelNotifier.get().registerForTableChanges(SimpleModel::class.java, modelChange) insert().columnValues(SimpleModel_Table.name to "name").executeInsert() - verify(modelChange).onTableChanged(BaseModel.Action.INSERT) + verify(modelChange).onTableChanged(SimpleModel::class.java, BaseModel.Action.INSERT) (update() set SimpleModel_Table.name.eq("name2")).executeUpdateDelete() - verify(modelChange).onTableChanged(BaseModel.Action.UPDATE) + verify(modelChange).onTableChanged(SimpleModel::class.java, BaseModel.Action.UPDATE) delete().executeUpdateDelete() - verify(modelChange).onTableChanged(BaseModel.Action.DELETE) + verify(modelChange).onTableChanged(SimpleModel::class.java, BaseModel.Action.DELETE) } @After diff --git a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/sql/language/UpdateTest.kt b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/sql/language/UpdateTest.kt index 619d7904d..09fb28e90 100644 --- a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/sql/language/UpdateTest.kt +++ b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/sql/language/UpdateTest.kt @@ -1,12 +1,16 @@ package com.raizlabs.android.dbflow.sql.language import com.raizlabs.android.dbflow.BaseUnitTest -import com.raizlabs.android.dbflow.models.SimpleModel -import com.raizlabs.android.dbflow.models.SimpleModel_Table.name +import com.raizlabs.android.dbflow.annotation.ConflictAction import com.raizlabs.android.dbflow.assertEquals import com.raizlabs.android.dbflow.kotlinextensions.eq import com.raizlabs.android.dbflow.kotlinextensions.set import com.raizlabs.android.dbflow.kotlinextensions.update +import com.raizlabs.android.dbflow.models.NumberModel +import com.raizlabs.android.dbflow.models.NumberModel_Table.id +import com.raizlabs.android.dbflow.models.SimpleModel +import com.raizlabs.android.dbflow.models.SimpleModel_Table.name +import com.raizlabs.android.dbflow.sql.language.property.Property import org.junit.Test class UpdateTest : BaseUnitTest() { @@ -40,4 +44,12 @@ class UpdateTest : BaseUnitTest() { fun validateSetQuery() { assertEquals("UPDATE `SimpleModel` SET `name`='name'", update() set (name eq "name")) } + + @Test + fun validateWildcardQuery() { + assertEquals("UPDATE OR FAIL `NumberModel` SET `id`=? WHERE `id`=?", + update().or(ConflictAction.FAIL) + .set(id.eq(Property.WILDCARD)) + .where(id.eq(Property.WILDCARD))) + } } \ No newline at end of file diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/config/DatabaseConfig.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/config/DatabaseConfig.java index 40c170d41..347b41996 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/config/DatabaseConfig.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/config/DatabaseConfig.java @@ -1,5 +1,8 @@ package com.raizlabs.android.dbflow.config; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + import com.raizlabs.android.dbflow.runtime.BaseTransactionManager; import com.raizlabs.android.dbflow.runtime.ModelNotifier; import com.raizlabs.android.dbflow.structure.database.DatabaseHelperListener; @@ -40,31 +43,38 @@ public interface TransactionManagerCreator { modelNotifier = builder.modelNotifier; } + @Nullable public OpenHelperCreator helperCreator() { return openHelperCreator; } + @Nullable public DatabaseHelperListener helperListener() { return helperListener; } + @NonNull public Class databaseClass() { return databaseClass; } + @Nullable public TransactionManagerCreator transactionManagerCreator() { return transactionManagerCreator; } + @Nullable public ModelNotifier modelNotifier() { return modelNotifier; } + @NonNull public Map, TableConfig> tableConfigMap() { return tableConfigMap; } @SuppressWarnings("unchecked") + @Nullable public TableConfig getTableConfigForTable(Class modelClass) { return tableConfigMap().get(modelClass); } @@ -78,7 +88,7 @@ public static final class Builder { final Map, TableConfig> tableConfigMap = new HashMap<>(); ModelNotifier modelNotifier; - public Builder(Class databaseClass) { + public Builder(@NonNull Class databaseClass) { this.databaseClass = databaseClass; } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/config/DatabaseDefinition.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/config/DatabaseDefinition.java index 3a82771f0..b7607353a 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/config/DatabaseDefinition.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/config/DatabaseDefinition.java @@ -1,6 +1,8 @@ package com.raizlabs.android.dbflow.config; import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import com.raizlabs.android.dbflow.StringUtils; import com.raizlabs.android.dbflow.annotation.Database; @@ -34,6 +36,7 @@ * Description: The main interface that all Database implementations extend from. This is for internal usage only * as it will be generated for every {@link Database}. */ +@SuppressWarnings("NullableProblems") public abstract class DatabaseDefinition { private final Map> migrationMap = new HashMap<>(); @@ -61,10 +64,13 @@ public abstract class DatabaseDefinition { */ private boolean isResetting = false; + @NonNull private BaseTransactionManager transactionManager; + @Nullable private DatabaseConfig databaseConfig; + @NonNull private ModelNotifier modelNotifier; @SuppressWarnings("unchecked") @@ -131,10 +137,12 @@ protected void addMigration(int version, Migration migration) { /** * @return a list of all model classes in this database. */ + @NonNull public List> getModelClasses() { return new ArrayList<>(modelAdapters.keySet()); } + @NonNull public BaseTransactionManager getTransactionManager() { return transactionManager; } @@ -144,6 +152,7 @@ public BaseTransactionManager getTransactionManager() { * * @return List of Model Adapters */ + @NonNull public List getModelAdapters() { return new ArrayList<>(modelAdapters.values()); } @@ -157,6 +166,7 @@ public List getModelAdapters() { * @return The ModelAdapter for the table. */ @SuppressWarnings("unchecked") + @Nullable public ModelAdapter getModelAdapterForTable(Class table) { return modelAdapters.get(table); } @@ -166,6 +176,7 @@ public ModelAdapter getModelAdapterForTable(Class table) { * @return The associated {@link ModelAdapter} within this database for the specified table name. * If the Model is missing the {@link Table} annotation, this will return null. */ + @Nullable public Class getModelClassForName(String tableName) { return modelTableNames.get(tableName); } @@ -173,6 +184,7 @@ public Class getModelClassForName(String tableName) { /** * @return the {@link BaseModelView} list for this database. */ + @NonNull public List> getModelViews() { return new ArrayList<>(modelViewAdapterMap.keySet()); } @@ -182,6 +194,7 @@ public List> getModelViews() { * @return the associated {@link ModelViewAdapter} for the specified table. */ @SuppressWarnings("unchecked") + @Nullable public ModelViewAdapter getModelViewAdapterForTable(Class table) { return modelViewAdapterMap.get(table); } @@ -190,6 +203,7 @@ public ModelViewAdapter getModelViewAdapterForTable(Class table) { * @return The list of {@link ModelViewAdapter}. Internal method for * creating model views in the DB. */ + @NonNull public List getModelViewAdapters() { return new ArrayList<>(modelViewAdapterMap.values()); } @@ -197,6 +211,7 @@ public List getModelViewAdapters() { /** * @return The list of {@link QueryModelAdapter}. Internal method for creating query models in the DB. */ + @NonNull public List getModelQueryAdapters() { return new ArrayList<>(queryModelAdapterMap.values()); } @@ -206,6 +221,7 @@ public List getModelQueryAdapters() { * @return The adapter that corresponds to the specified class. */ @SuppressWarnings("unchecked") + @Nullable public QueryModelAdapter getQueryModelAdapterForQueryClass(Class queryModel) { return queryModelAdapterMap.get(queryModel); } @@ -213,10 +229,12 @@ public QueryModelAdapter getQueryModelAdapterForQueryClass(Class query /** * @return The map of migrations to DB version */ + @NonNull public Map> getMigrations() { return migrationMap; } + @NonNull public synchronized OpenHelper getHelper() { if (openHelper == null) { DatabaseConfig config = FlowManager.getConfig().databaseConfigMap() @@ -231,10 +249,12 @@ public synchronized OpenHelper getHelper() { return openHelper; } + @NonNull public DatabaseWrapper getWritableDatabase() { return getHelper().getDatabase(); } + @NonNull public ModelNotifier getModelNotifier() { if (modelNotifier == null) { DatabaseConfig config = FlowManager.getConfig().databaseConfigMap() @@ -248,11 +268,12 @@ public ModelNotifier getModelNotifier() { return modelNotifier; } - public Transaction.Builder beginTransactionAsync(ITransaction transaction) { + @NonNull + public Transaction.Builder beginTransactionAsync(@NonNull ITransaction transaction) { return new Transaction.Builder(transaction, this); } - public void executeTransaction(ITransaction transaction) { + public void executeTransaction(@NonNull ITransaction transaction) { DatabaseWrapper database = getWritableDatabase(); try { database.beginTransaction(); @@ -266,11 +287,13 @@ public void executeTransaction(ITransaction transaction) { /** * @return The name of this database as defined in {@link Database} */ + @NonNull public abstract String getDatabaseName(); /** * @return The file name that this database points to */ + @NonNull public String getDatabaseFileName() { return getDatabaseName() + (StringUtils.isNotNullOrEmpty(getDatabaseExtensionName()) ? "." + getDatabaseExtensionName() : ""); @@ -279,6 +302,7 @@ public String getDatabaseFileName() { /** * @return the extension for the file name. */ + @NonNull public String getDatabaseExtensionName() { return "db"; } @@ -311,6 +335,7 @@ public String getDatabaseExtensionName() { /** * @return The class that defines the {@link Database} annotation. */ + @NonNull public abstract Class getAssociatedDatabaseClassFile(); /** @@ -318,7 +343,7 @@ public String getDatabaseExtensionName() { * * @param context Where the database resides */ - public void reset(Context context) { + public void reset(@NonNull Context context) { if (!isResetting) { isResetting = true; getTransactionManager().stopQueue(); @@ -341,7 +366,7 @@ public void reset(Context context) { } } - public void destroy(Context context) { + public void destroy(@NonNull Context context) { if (!isResetting) { isResetting = true; getTransactionManager().stopQueue(); diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/config/FlowManager.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/config/FlowManager.java index 605cabe67..3acd7b6c4 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/config/FlowManager.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/config/FlowManager.java @@ -6,6 +6,8 @@ import com.raizlabs.android.dbflow.annotation.Table; import com.raizlabs.android.dbflow.converter.TypeConverter; +import com.raizlabs.android.dbflow.runtime.ModelNotifier; +import com.raizlabs.android.dbflow.runtime.TableNotifierRegister; import com.raizlabs.android.dbflow.sql.QueryBuilder; import com.raizlabs.android.dbflow.sql.migration.Migration; import com.raizlabs.android.dbflow.structure.BaseModel; @@ -409,6 +411,16 @@ public static QueryModelAdapter getQueryModelAdapter( return queryModelAdapter; } + @NonNull + public static ModelNotifier getModelNotifierForTable(Class table) { + return getDatabaseForTable(table).getModelNotifier(); + } + + @NonNull + public static TableNotifierRegister newRegisterForTable(Class table) { + return getModelNotifierForTable(table).newRegister(); + } + @Nullable private static ModelAdapter getModelAdapterOrNull(Class modelClass) { return FlowManager.getDatabaseForTable(modelClass).getModelAdapterForTable(modelClass); diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/BaseContentProvider.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/BaseContentProvider.java index 17c02a31d..3837adf76 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/BaseContentProvider.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/BaseContentProvider.java @@ -45,6 +45,8 @@ public boolean onCreate() { // framework has been initialized. if (moduleClass != null) { FlowManager.initModule(moduleClass); + } else if (getContext() != null) { + FlowManager.init(getContext()); } return true; diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/ContentResolverNotifier.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/ContentResolverNotifier.java index ae4c731af..516288f0e 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/ContentResolverNotifier.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/ContentResolverNotifier.java @@ -32,4 +32,57 @@ public void notifyTableChanged(@NonNull Class table, @NonNull BaseModel.A } } + @Override + public TableNotifierRegister newRegister() { + return new FlowContentTableNotifierRegister(); + } + + public static class FlowContentTableNotifierRegister implements TableNotifierRegister { + + private final FlowContentObserver flowContentObserver = new FlowContentObserver(); + + @Nullable + private OnTableChangedListener tableChangedListener; + + public FlowContentTableNotifierRegister() { + flowContentObserver.addOnTableChangedListener(internalContentChangeListener); + } + + @Override + public void register(Class tClass) { + flowContentObserver.registerForContentChanges(FlowManager.getContext(), tClass); + } + + @Override + public void unregister(Class tClass) { + flowContentObserver.unregisterForContentChanges(FlowManager.getContext()); + } + + @Override + public void unregisterAll() { + flowContentObserver.removeTableChangedListener(internalContentChangeListener); + this.tableChangedListener = null; + } + + @Override + public void setListener(OnTableChangedListener contentChangeListener) { + this.tableChangedListener = contentChangeListener; + } + + @Override + public boolean isSubscribed() { + return !flowContentObserver.isSubscribed(); + } + + private final OnTableChangedListener internalContentChangeListener + = new OnTableChangedListener() { + + @Override + public void onTableChanged(@Nullable Class tableChanged, @NonNull BaseModel.Action action) { + if (tableChangedListener != null) { + tableChangedListener.onTableChanged(tableChanged, action); + } + } + }; + } } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/DirectModelNotifier.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/DirectModelNotifier.java index 8fa81306e..0a9a25576 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/DirectModelNotifier.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/DirectModelNotifier.java @@ -7,8 +7,10 @@ import com.raizlabs.android.dbflow.structure.BaseModel; import com.raizlabs.android.dbflow.structure.ModelAdapter; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -28,14 +30,22 @@ public static DirectModelNotifier get() { return notifier; } - public interface ModelChangedListener { + public interface OnModelStateChangedListener { void onModelChanged(T model, BaseModel.Action action); - void onTableChanged(BaseModel.Action action); } - private final Map, Set> modelChangedListenerMap = new LinkedHashMap<>(); + public interface ModelChangedListener extends OnModelStateChangedListener, OnTableChangedListener { + + } + + private final Map, Set> modelChangedListenerMap = new LinkedHashMap<>(); + + private final Map, Set> tableChangedListenerMap = new LinkedHashMap<>(); + + + private final TableNotifierRegister singleRegister = new DirectTableNotifierRegister(); /** * Private constructor. Use shared {@link #get()} to ensure singular instance. @@ -49,9 +59,9 @@ private DirectModelNotifier() { @Override public void notifyModelChanged(@Nullable T model, @NonNull ModelAdapter adapter, @NonNull BaseModel.Action action) { - final Set listeners = modelChangedListenerMap.get(adapter.getModelClass()); + final Set listeners = modelChangedListenerMap.get(adapter.getModelClass()); if (listeners != null) { - for (ModelChangedListener listener : listeners) { + for (OnModelStateChangedListener listener : listeners) { if (listener != null) { listener.onModelChanged(model, action); } @@ -61,18 +71,28 @@ public void notifyModelChanged(@Nullable T model, @NonNull ModelAdapter a @Override public void notifyTableChanged(@NonNull Class table, @NonNull BaseModel.Action action) { - final Set listeners = modelChangedListenerMap.get(table); + final Set listeners = tableChangedListenerMap.get(table); if (listeners != null) { - for (ModelChangedListener listener : listeners) { + for (OnTableChangedListener listener : listeners) { if (listener != null) { - listener.onTableChanged(action); + listener.onTableChanged(table, action); } } } } + @Override + public TableNotifierRegister newRegister() { + return singleRegister; + } + public void registerForModelChanges(Class table, ModelChangedListener listener) { - Set listeners = modelChangedListenerMap.get(table); + registerForModelStateChanges(table, listener); + registerForTableChanges(table, listener); + } + + public void registerForModelStateChanges(Class table, OnModelStateChangedListener listener) { + Set listeners = modelChangedListenerMap.get(table); if (listeners == null) { listeners = new LinkedHashSet<>(); modelChangedListenerMap.put(table, listeners); @@ -80,10 +100,81 @@ public void registerForModelChanges(Class table, ModelChangedListener listeners.add(listener); } + public void registerForTableChanges(Class table, OnTableChangedListener listener) { + Set listeners = tableChangedListenerMap.get(table); + if (listeners == null) { + listeners = new LinkedHashSet<>(); + tableChangedListenerMap.put(table, listeners); + } + listeners.add(listener); + } + public void unregisterForModelChanges(Class table, ModelChangedListener listener) { - Set listeners = modelChangedListenerMap.get(table); + unregisterForModelStateChanges(table, listener); + unregisterForTableChanges(table, listener); + } + + + public void unregisterForModelStateChanges(Class table, OnModelStateChangedListener listener) { + Set listeners = modelChangedListenerMap.get(table); + if (listeners != null) { + listeners.remove(listener); + } + } + + public void unregisterForTableChanges(Class table, OnTableChangedListener listener) { + Set listeners = tableChangedListenerMap.get(table); if (listeners != null) { listeners.remove(listener); } } + + private class DirectTableNotifierRegister implements TableNotifierRegister { + private List registeredTables = new ArrayList<>(); + + @Nullable + private OnTableChangedListener modelChangedListener; + + @Override + public void register(Class tClass) { + registeredTables.add(tClass); + registerForTableChanges(tClass, internalChangeListener); + } + + @Override + public void unregister(Class tClass) { + registeredTables.remove(tClass); + unregisterForTableChanges(tClass, internalChangeListener); + } + + @Override + public void unregisterAll() { + for (Class table : registeredTables) { + unregisterForTableChanges(table, internalChangeListener); + } + this.modelChangedListener = null; + } + + @Override + public void setListener(OnTableChangedListener modelChangedListener) { + this.modelChangedListener = modelChangedListener; + } + + @Override + public boolean isSubscribed() { + return !registeredTables.isEmpty(); + } + + private final OnTableChangedListener internalChangeListener + = new OnTableChangedListener() { + + @Override + public void onTableChanged(@Nullable Class table, @NonNull BaseModel.Action action) { + if (modelChangedListener != null) { + modelChangedListener.onTableChanged(table, action); + } + } + }; + } + } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/FlowContentObserver.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/FlowContentObserver.java index 6fdb50681..d04a3b180 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/FlowContentObserver.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/FlowContentObserver.java @@ -83,19 +83,8 @@ public interface OnModelStateChangedListener { void onModelStateChanged(@Nullable Class table, Action action, @NonNull SQLOperator[] primaryKeyValues); } - /** - * Interface for when a generic change on a table occurs. - */ - public interface OnTableChangedListener { + public interface ContentChangeListener extends OnModelStateChangedListener, OnTableChangedListener { - /** - * Called when table changes. - * - * @param tableChanged The table that has changed. NULL unless version of app is {@link VERSION_CODES#JELLY_BEAN} - * or higher. - * @param action The action that occurred. - */ - void onTableChanged(@Nullable Class tableChanged, Action action); } private final Set modelChangeListeners = new CopyOnWriteArraySet<>(); @@ -191,6 +180,26 @@ public void removeTableChangedListener(OnTableChangedListener onTableChangedList onTableChangedListeners.remove(onTableChangedListener); } + /** + * Add a listener for model + table changes + * + * @param contentChangeListener Generic model change events from an {@link Action} + */ + public void addContentChangeListener(ContentChangeListener contentChangeListener) { + modelChangeListeners.add(contentChangeListener); + onTableChangedListeners.add(contentChangeListener); + } + + /** + * Removes a listener for model + table changes + * + * @param contentChangeListener Generic model change events from a {@link Action} + */ + public void removeContentChangeListener(ContentChangeListener contentChangeListener) { + modelChangeListeners.remove(contentChangeListener); + onTableChangedListeners.remove(contentChangeListener); + } + /** * Registers the observer for model change events for specific class. */ diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/ModelNotifier.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/ModelNotifier.java index 1c40b503a..5c5bfaa98 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/ModelNotifier.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/ModelNotifier.java @@ -16,4 +16,5 @@ void notifyModelChanged(@Nullable T model, @NonNull ModelAdapter adapter, void notifyTableChanged(@NonNull Class table, @NonNull BaseModel.Action action); + TableNotifierRegister newRegister(); } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/NotifyDistributor.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/NotifyDistributor.java index dc59ddb94..4af27928f 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/NotifyDistributor.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/NotifyDistributor.java @@ -21,12 +21,17 @@ public static NotifyDistributor get() { return distributor; } + @Override + public TableNotifierRegister newRegister() { + throw new RuntimeException("Cannot create a register from the distributor class"); + } + @Override public void notifyModelChanged(@Nullable TModel model, - @NonNull ModelAdapter modelAdapter, + @NonNull ModelAdapter adapter, @NonNull BaseModel.Action action) { - FlowManager.getDatabaseForTable(modelAdapter.getModelClass()) - .getModelNotifier().notifyModelChanged(model, modelAdapter, action); + FlowManager.getModelNotifierForTable(adapter.getModelClass()) + .notifyModelChanged(model, adapter, action); } /** @@ -35,7 +40,6 @@ public void notifyModelChanged(@Nullable TModel model, @Override public void notifyTableChanged(@NonNull Class table, @NonNull BaseModel.Action action) { - FlowManager.getDatabaseForTable(table) - .getModelNotifier().notifyTableChanged(table, action); + FlowManager.getModelNotifierForTable(table).notifyTableChanged(table, action); } } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/OnTableChangedListener.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/OnTableChangedListener.java new file mode 100644 index 000000000..bc0285541 --- /dev/null +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/OnTableChangedListener.java @@ -0,0 +1,22 @@ +package com.raizlabs.android.dbflow.runtime; + +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.raizlabs.android.dbflow.structure.BaseModel; + +/** + * Interface for when a generic change on a table occurs. + */ +public interface OnTableChangedListener { + + /** + * Called when table changes. + * + * @param tableChanged The table that has changed. NULL unless version of app is {@link Build.VERSION_CODES#JELLY_BEAN} + * or higher. + * @param action The action that occurred. + */ + void onTableChanged(@Nullable Class tableChanged, @NonNull BaseModel.Action action); +} diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/TableNotifierRegister.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/TableNotifierRegister.java new file mode 100644 index 000000000..01a82a121 --- /dev/null +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/runtime/TableNotifierRegister.java @@ -0,0 +1,17 @@ +package com.raizlabs.android.dbflow.runtime; + +/** + * Description: Defines how {@link ModelNotifier} registers listeners. Abstracts that away. + */ +public interface TableNotifierRegister { + + void register(Class tClass); + + void unregister(Class tClass); + + void unregisterAll(); + + void setListener(OnTableChangedListener listener); + + boolean isSubscribed(); +} diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/Operator.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/Operator.java index 7fa7448ff..054d49086 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/Operator.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/Operator.java @@ -372,21 +372,21 @@ public Between between(IConditional conditional) { @SuppressWarnings("unchecked") @Override public In in(IConditional firstConditional, IConditional... conditionals) { - return new In(this, firstConditional, true, conditionals); + return new In(this, firstConditional, true, (Object) conditionals); } @NonNull @SuppressWarnings("unchecked") @Override public In notIn(IConditional firstConditional, IConditional... conditionals) { - return new In(this, firstConditional, false, conditionals); + return new In(this, firstConditional, false, (Object) conditionals); } @NonNull @SuppressWarnings("unchecked") @Override public In notIn(BaseModelQueriable firstBaseModelQueriable, BaseModelQueriable[] baseModelQueriables) { - return new In(this, firstBaseModelQueriable, false, baseModelQueriables); + return new In(this, firstBaseModelQueriable, false, (Object) baseModelQueriables); } @NonNull diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/Update.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/Update.java index f51f25415..3ed5b56f8 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/Update.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/Update.java @@ -34,6 +34,11 @@ public Update conflictAction(ConflictAction conflictAction) { return this; } + @NonNull + public Update or(ConflictAction conflictAction) { + return conflictAction(conflictAction); + } + /** * @return This instance. * @see ConflictAction#ROLLBACK diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/property/Property.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/property/Property.java index 9d02fc40f..6faab5a00 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/property/Property.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/property/Property.java @@ -35,6 +35,8 @@ public String toString() { } }; + public static final Property WILDCARD = new Property(null, NameAlias.rawBuilder("?").build()); + final Class table; protected NameAlias nameAlias; diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/saveable/CacheableListModelSaver.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/saveable/CacheableListModelSaver.java index 1e5d69a21..c02c8ad74 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/saveable/CacheableListModelSaver.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/saveable/CacheableListModelSaver.java @@ -1,6 +1,5 @@ package com.raizlabs.android.dbflow.sql.saveable; -import android.content.ContentValues; import android.support.annotation.NonNull; import com.raizlabs.android.dbflow.structure.ModelAdapter; @@ -30,14 +29,15 @@ public synchronized void saveAll(@NonNull Collection tableCollection, ModelSaver modelSaver = getModelSaver(); ModelAdapter modelAdapter = modelSaver.getModelAdapter(); DatabaseStatement statement = modelAdapter.getInsertStatement(wrapper); - ContentValues contentValues = new ContentValues(); + DatabaseStatement updateStatement = modelAdapter.getUpdateStatement(wrapper); try { for (TModel model : tableCollection) { - if (modelSaver.save(model, wrapper, statement, contentValues)) { + if (modelSaver.save(model, wrapper, statement, updateStatement)) { modelAdapter.storeModelInCache(model); } } } finally { + updateStatement.close(); statement.close(); } } @@ -66,17 +66,37 @@ public synchronized void insertAll(@NonNull Collection tableCollection, @Override public synchronized void updateAll(@NonNull Collection tableCollection, - DatabaseWrapper wrapper) { + @NonNull DatabaseWrapper wrapper) { // skip if empty. if (tableCollection.isEmpty()) { return; } ModelSaver modelSaver = getModelSaver(); ModelAdapter modelAdapter = modelSaver.getModelAdapter(); - ContentValues contentValues = new ContentValues(); + DatabaseStatement statement = modelAdapter.getUpdateStatement(wrapper); + try { + for (TModel model : tableCollection) { + if (modelSaver.update(model, wrapper, statement)) { + modelAdapter.storeModelInCache(model); + } + } + } finally { + statement.close(); + } + } + + @Override + public synchronized void deleteAll(@NonNull Collection tableCollection, + @NonNull DatabaseWrapper wrapper) { + // skip if empty. + if (tableCollection.isEmpty()) { + return; + } + + ModelSaver modelSaver = getModelSaver(); for (TModel model : tableCollection) { - if (modelSaver.update(model, wrapper, contentValues)) { - modelAdapter.storeModelInCache(model); + if (modelSaver.delete(model, wrapper)) { + getModelSaver().getModelAdapter().removeModelFromCache(model); } } } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/saveable/ListModelSaver.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/saveable/ListModelSaver.java index 1a4a5a969..f7038f9cb 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/saveable/ListModelSaver.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/saveable/ListModelSaver.java @@ -1,6 +1,5 @@ package com.raizlabs.android.dbflow.sql.saveable; -import android.content.ContentValues; import android.support.annotation.NonNull; import com.raizlabs.android.dbflow.structure.database.DatabaseStatement; @@ -20,17 +19,18 @@ public synchronized void saveAll(@NonNull Collection tableCollection) { saveAll(tableCollection, modelSaver.getWritableDatabase()); } - public synchronized void saveAll(@NonNull Collection tableCollection, DatabaseWrapper wrapper) { + public synchronized void saveAll(@NonNull Collection tableCollection, + @NonNull DatabaseWrapper wrapper) { // skip if empty. if (tableCollection.isEmpty()) { return; } DatabaseStatement statement = modelSaver.getModelAdapter().getInsertStatement(wrapper); - ContentValues contentValues = new ContentValues(); + DatabaseStatement updateStatement = modelSaver.getModelAdapter().getUpdateStatement(wrapper); try { for (TModel model : tableCollection) { - modelSaver.save(model, wrapper, statement, contentValues); + modelSaver.save(model, wrapper, statement, updateStatement); } } finally { statement.close(); @@ -41,7 +41,8 @@ public synchronized void insertAll(@NonNull Collection tableCollection) insertAll(tableCollection, modelSaver.getWritableDatabase()); } - public synchronized void insertAll(@NonNull Collection tableCollection, DatabaseWrapper wrapper) { + public synchronized void insertAll(@NonNull Collection tableCollection, + @NonNull DatabaseWrapper wrapper) { // skip if empty. if (tableCollection.isEmpty()) { return; @@ -58,19 +59,23 @@ public synchronized void insertAll(@NonNull Collection tableCollection, } public synchronized void updateAll(@NonNull Collection tableCollection) { - saveAll(tableCollection, modelSaver.getWritableDatabase()); + updateAll(tableCollection, modelSaver.getWritableDatabase()); } public synchronized void updateAll(@NonNull Collection tableCollection, - DatabaseWrapper wrapper) { + @NonNull DatabaseWrapper wrapper) { // skip if empty. if (tableCollection.isEmpty()) { return; } - ContentValues contentValues = new ContentValues(); - for (TModel model : tableCollection) { - modelSaver.update(model, wrapper, contentValues); + DatabaseStatement updateStatement = modelSaver.getModelAdapter().getUpdateStatement(wrapper); + try { + for (TModel model : tableCollection) { + modelSaver.update(model, wrapper, updateStatement); + } + } finally { + updateStatement.close(); } } @@ -79,18 +84,23 @@ public synchronized void deleteAll(@NonNull Collection tableCollection) } public synchronized void deleteAll(@NonNull Collection tableCollection, - DatabaseWrapper wrapper) { + @NonNull DatabaseWrapper wrapper) { // skip if empty. if (tableCollection.isEmpty()) { return; } - for (TModel model : tableCollection) { - modelSaver.delete(model, wrapper); + DatabaseStatement deleteStatement = modelSaver.getModelAdapter().getDeleteStatement(wrapper); + try { + for (TModel model : tableCollection) { + modelSaver.delete(model, deleteStatement, wrapper); + } + } finally { + deleteStatement.close(); } } - + @NonNull public ModelSaver getModelSaver() { return modelSaver; } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/saveable/ModelSaver.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/saveable/ModelSaver.java index e18922fc8..9999e63ff 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/saveable/ModelSaver.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/saveable/ModelSaver.java @@ -6,15 +6,14 @@ import com.raizlabs.android.dbflow.annotation.ConflictAction; import com.raizlabs.android.dbflow.config.FlowManager; import com.raizlabs.android.dbflow.runtime.NotifyDistributor; -import com.raizlabs.android.dbflow.sql.language.Delete; import com.raizlabs.android.dbflow.structure.BaseModel; import com.raizlabs.android.dbflow.structure.ModelAdapter; import com.raizlabs.android.dbflow.structure.database.DatabaseStatement; import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper; /** - * Description: Defines how models get saved into the DB. It will bind values to {@link android.content.ContentValues} for - * an update, execute a {@link DatabaseStatement}, or delete an object via the {@link Delete} wrapper. + * Description: Defines how models get saved into the DB. It will bind values to {@link DatabaseStatement} + * for all CRUD operations as they are wildly faster and more efficient than {@link ContentValues}. */ public class ModelSaver { @@ -22,25 +21,29 @@ public class ModelSaver { private ModelAdapter modelAdapter; - public void setModelAdapter(ModelAdapter modelAdapter) { + public void setModelAdapter(@NonNull ModelAdapter modelAdapter) { this.modelAdapter = modelAdapter; } public synchronized boolean save(@NonNull TModel model) { - return save(model, getWritableDatabase(), modelAdapter.getInsertStatement(), new ContentValues()); + return save(model, getWritableDatabase(), modelAdapter.getInsertStatement(), + modelAdapter.getUpdateStatement()); } - public synchronized boolean save(@NonNull TModel model, DatabaseWrapper wrapper) { - return save(model, wrapper, modelAdapter.getInsertStatement(wrapper), new ContentValues()); + public synchronized boolean save(@NonNull TModel model, + @NonNull DatabaseWrapper wrapper) { + return save(model, wrapper, modelAdapter.getInsertStatement(wrapper), + modelAdapter.getUpdateStatement(wrapper)); } @SuppressWarnings("unchecked") public synchronized boolean save(@NonNull TModel model, DatabaseWrapper wrapper, - DatabaseStatement insertStatement, ContentValues contentValues) { + @NonNull DatabaseStatement insertStatement, + @NonNull DatabaseStatement updateStatement) { boolean exists = modelAdapter.exists(model, wrapper); if (exists) { - exists = update(model, wrapper, contentValues); + exists = update(model, wrapper, updateStatement); } if (!exists) { @@ -56,21 +59,27 @@ public synchronized boolean save(@NonNull TModel model, DatabaseWrapper wrapper, } public synchronized boolean update(@NonNull TModel model) { - return update(model, getWritableDatabase(), new ContentValues()); + return update(model, getWritableDatabase(), modelAdapter.getUpdateStatement()); } public synchronized boolean update(@NonNull TModel model, @NonNull DatabaseWrapper wrapper) { - return update(model, wrapper, new ContentValues()); + DatabaseStatement insertStatement = modelAdapter.getUpdateStatement(wrapper); + boolean success = false; + try { + success = update(model, wrapper, insertStatement); + } finally { + // since we generate an insert every time, we can safely close the statement here. + insertStatement.close(); + } + return success; } @SuppressWarnings("unchecked") public synchronized boolean update(@NonNull TModel model, @NonNull DatabaseWrapper wrapper, - @NonNull ContentValues contentValues) { + @NonNull DatabaseStatement databaseStatement) { modelAdapter.saveForeignKeys(model, wrapper); - modelAdapter.bindToContentValues(contentValues, model); - boolean successful = wrapper.updateWithOnConflict(modelAdapter.getTableName(), contentValues, - modelAdapter.getPrimaryConditionClause(model).getQuery(), null, - ConflictAction.getSQLiteDatabaseAlgorithmInt(modelAdapter.getUpdateOnConflictAction())) != 0; + modelAdapter.bindToUpdateStatement(databaseStatement, model); + boolean successful = databaseStatement.executeUpdateDelete() != 0; if (successful) { NotifyDistributor.get().notifyModelChanged(model, modelAdapter, BaseModel.Action.UPDATE); } @@ -96,8 +105,9 @@ public synchronized long insert(@NonNull TModel model, @NonNull DatabaseWrapper } @SuppressWarnings("unchecked") - public synchronized long insert(@NonNull TModel model, @NonNull DatabaseStatement insertStatement, - DatabaseWrapper wrapper) { + public synchronized long insert(@NonNull TModel model, + @NonNull DatabaseStatement insertStatement, + @NonNull DatabaseWrapper wrapper) { modelAdapter.saveForeignKeys(model, wrapper); modelAdapter.bindToInsertStatement(insertStatement, model); long id = insertStatement.executeInsert(); @@ -109,12 +119,12 @@ public synchronized long insert(@NonNull TModel model, @NonNull DatabaseStatemen } public synchronized boolean delete(@NonNull TModel model) { - return delete(model, getWritableDatabase()); + return delete(model, modelAdapter.getDeleteStatement(), getWritableDatabase()); } @SuppressWarnings("unchecked") public synchronized boolean delete(@NonNull TModel model, @NonNull DatabaseWrapper wrapper) { - DatabaseStatement deleteStatement = modelAdapter.getDeleteStatement(model, wrapper); + DatabaseStatement deleteStatement = modelAdapter.getDeleteStatement(wrapper); boolean success = false; try { success = delete(model, deleteStatement, wrapper); @@ -126,11 +136,13 @@ public synchronized boolean delete(@NonNull TModel model, @NonNull DatabaseWrapp } @SuppressWarnings("unchecked") - public synchronized boolean delete(@NonNull TModel model, @NonNull DatabaseStatement databaseStatement, + public synchronized boolean delete(@NonNull TModel model, + @NonNull DatabaseStatement deleteStatement, @NonNull DatabaseWrapper wrapper) { modelAdapter.deleteForeignKeys(model, wrapper); + modelAdapter.bindToDeleteStatement(deleteStatement, model); - boolean success = databaseStatement.executeUpdateDelete() != 0; + boolean success = deleteStatement.executeUpdateDelete() != 0; if (success) { NotifyDistributor.get().notifyModelChanged(model, modelAdapter, BaseModel.Action.DELETE); } @@ -142,9 +154,57 @@ protected DatabaseWrapper getWritableDatabase() { return FlowManager.getDatabaseForTable(modelAdapter.getModelClass()).getWritableDatabase(); } + @NonNull public ModelAdapter getModelAdapter() { return modelAdapter; } + /** + * Legacy save method. Uses {@link ContentValues} vs. the faster {@link DatabaseStatement} for updates. + * + * @see #save(Object, DatabaseWrapper, DatabaseStatement, DatabaseStatement) + */ + @Deprecated + @SuppressWarnings({"unchecked", "deprecation"}) + public synchronized boolean save(@NonNull TModel model, + @NonNull DatabaseWrapper wrapper, + @NonNull DatabaseStatement insertStatement, + @NonNull ContentValues contentValues) { + boolean exists = modelAdapter.exists(model, wrapper); + + if (exists) { + exists = update(model, wrapper, contentValues); + } + + if (!exists) { + exists = insert(model, insertStatement, wrapper) > INSERT_FAILED; + } + + if (exists) { + NotifyDistributor.get().notifyModelChanged(model, modelAdapter, BaseModel.Action.SAVE); + } + + // return successful store into db. + return exists; + } + + /** + * @see #update(Object, DatabaseWrapper, DatabaseStatement) + */ + @Deprecated + @SuppressWarnings("unchecked") + public synchronized boolean update(@NonNull TModel model, + @NonNull DatabaseWrapper wrapper, + @NonNull ContentValues contentValues) { + modelAdapter.saveForeignKeys(model, wrapper); + modelAdapter.bindToContentValues(contentValues, model); + boolean successful = wrapper.updateWithOnConflict(modelAdapter.getTableName(), contentValues, + modelAdapter.getPrimaryConditionClause(model).getQuery(), null, + ConflictAction.getSQLiteDatabaseAlgorithmInt(modelAdapter.getUpdateOnConflictAction())) != 0; + if (successful) { + NotifyDistributor.get().notifyModelChanged(model, modelAdapter, BaseModel.Action.UPDATE); + } + return successful; + } } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/InstanceAdapter.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/InstanceAdapter.java index c4ce0e0da..4be57ecb5 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/InstanceAdapter.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/InstanceAdapter.java @@ -1,14 +1,17 @@ package com.raizlabs.android.dbflow.structure; +import android.support.annotation.NonNull; + import com.raizlabs.android.dbflow.config.DatabaseDefinition; /** * Description: Provides a {@link #newInstance()} method to a {@link RetrievalAdapter} */ +@SuppressWarnings("NullableProblems") public abstract class InstanceAdapter - extends RetrievalAdapter { + extends RetrievalAdapter { - public InstanceAdapter(DatabaseDefinition databaseDefinition) { + public InstanceAdapter(@NonNull DatabaseDefinition databaseDefinition) { super(databaseDefinition); } @@ -16,5 +19,6 @@ public InstanceAdapter(DatabaseDefinition databaseDefinition) { * @return A new model using its default constructor. This is why default is required so that * we don't use reflection to create objects = faster. */ + @NonNull public abstract TModel newInstance(); } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/InternalAdapter.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/InternalAdapter.java index 89e08e4e8..ed0686d2d 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/InternalAdapter.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/InternalAdapter.java @@ -3,6 +3,7 @@ import android.content.ContentValues; import android.database.sqlite.SQLiteStatement; import android.support.annotation.IntRange; +import android.support.annotation.NonNull; import com.raizlabs.android.dbflow.annotation.PrimaryKey; import com.raizlabs.android.dbflow.structure.database.DatabaseStatement; @@ -13,11 +14,13 @@ /** * Description: Used for our internal Adapter classes such as generated {@link ModelAdapter}. */ +@SuppressWarnings("NullableProblems") public interface InternalAdapter { /** * @return The table name of this adapter. */ + @NonNull String getTableName(); /** @@ -25,7 +28,7 @@ public interface InternalAdapter { * * @param model The model to save/insert/update */ - boolean save(TModel model); + boolean save(@NonNull TModel model); /** * Saves the specified model to the DB. @@ -33,14 +36,14 @@ public interface InternalAdapter { * @param model The model to save/insert/update * @param databaseWrapper The manually specified wrapper. */ - boolean save(TModel model, DatabaseWrapper databaseWrapper); + boolean save(@NonNull TModel model, @NonNull DatabaseWrapper databaseWrapper); /** * Saves a {@link Collection} of models to the DB. * * @param models The {@link Collection} of models to save. */ - void saveAll(Collection models); + void saveAll(@NonNull Collection models); /** * Saves a {@link Collection} of models to the DB. @@ -48,28 +51,29 @@ public interface InternalAdapter { * @param models The {@link Collection} of models to save. * @param databaseWrapper The manually specified wrapper */ - void saveAll(Collection models, DatabaseWrapper databaseWrapper); + void saveAll(@NonNull Collection models, @NonNull DatabaseWrapper databaseWrapper); /** * Inserts the specified model into the DB. * * @param model The model to insert. */ - long insert(TModel model); + long insert(@NonNull TModel model); /** * Inserts the specified model into the DB. - * @param model The model to insert. + * + * @param model The model to insert. * @param databaseWrapper The manually specified wrapper. */ - long insert(TModel model, DatabaseWrapper databaseWrapper); + long insert(@NonNull TModel model, @NonNull DatabaseWrapper databaseWrapper); /** * Inserts a {@link Collection} of models into the DB. * * @param models The {@link Collection} of models to save. */ - void insertAll(Collection models); + void insertAll(@NonNull Collection models); /** * Inserts a {@link Collection} of models into the DB. @@ -77,14 +81,14 @@ public interface InternalAdapter { * @param models The {@link Collection} of models to save. * @param databaseWrapper The manually specified wrapper */ - void insertAll(Collection models, DatabaseWrapper databaseWrapper); + void insertAll(@NonNull Collection models, @NonNull DatabaseWrapper databaseWrapper); /** * Updates the specified model into the DB. * * @param model The model to update. */ - boolean update(TModel model); + boolean update(@NonNull TModel model); /** * Updates the specified model into the DB. @@ -92,14 +96,14 @@ public interface InternalAdapter { * @param model The model to update. * @param databaseWrapper The manually specified wrapper. */ - boolean update(TModel model, DatabaseWrapper databaseWrapper); + boolean update(@NonNull TModel model, @NonNull DatabaseWrapper databaseWrapper); /** * Updates a {@link Collection} of models in the DB. * * @param models The {@link Collection} of models to save. */ - void updateAll(Collection models); + void updateAll(@NonNull Collection models); /** * Updates a {@link Collection} of models in the DB. @@ -107,14 +111,14 @@ public interface InternalAdapter { * @param models The {@link Collection} of models to save. * @param databaseWrapper The manually specified wrapper */ - void updateAll(Collection models, DatabaseWrapper databaseWrapper); + void updateAll(@NonNull Collection models, @NonNull DatabaseWrapper databaseWrapper); /** * Deletes the model from the DB * * @param model The model to delete */ - boolean delete(TModel model); + boolean delete(@NonNull TModel model); /** * Deletes the model from the DB @@ -122,14 +126,14 @@ public interface InternalAdapter { * @param model The model to delete * @param databaseWrapper The manually specified wrapper. */ - boolean delete(TModel model, DatabaseWrapper databaseWrapper); + boolean delete(@NonNull TModel model, @NonNull DatabaseWrapper databaseWrapper); /** * Updates a {@link Collection} of models in the DB. * * @param models The {@link Collection} of models to save. */ - void deleteAll(Collection models); + void deleteAll(@NonNull Collection models); /** * Updates a {@link Collection} of models in the DB. @@ -137,14 +141,14 @@ public interface InternalAdapter { * @param models The {@link Collection} of models to save. * @param databaseWrapper The manually specified wrapper */ - void deleteAll(Collection models, DatabaseWrapper databaseWrapper); + void deleteAll(@NonNull Collection models, @NonNull DatabaseWrapper databaseWrapper); /** * Binds a {@link TModel} to the specified db statement * * @param sqLiteStatement The statement to fill */ - void bindToStatement(DatabaseStatement sqLiteStatement, TModel model); + void bindToStatement(@NonNull DatabaseStatement sqLiteStatement, @NonNull TModel model); /** * Provides common logic and a starting value for insert statements. So we can property compile @@ -154,7 +158,7 @@ public interface InternalAdapter { * @param model The model to retrieve data from. * @param start The starting index for this bind. */ - void bindToInsertStatement(DatabaseStatement sqLiteStatement, TModel model, + void bindToInsertStatement(@NonNull DatabaseStatement sqLiteStatement, @NonNull TModel model, @IntRange(from = 0, to = 1) int start); /** @@ -164,7 +168,7 @@ void bindToInsertStatement(DatabaseStatement sqLiteStatement, TModel model, * @param sqLiteStatement The statement to bind to. * @param model The model to read from. */ - void bindToInsertStatement(DatabaseStatement sqLiteStatement, TModel model); + void bindToInsertStatement(@NonNull DatabaseStatement sqLiteStatement, @NonNull TModel model); /** * Binds a {@link TModel} to the specified db statement @@ -172,7 +176,7 @@ void bindToInsertStatement(DatabaseStatement sqLiteStatement, TModel model, * @param contentValues The content values to fill. * @param model The model values to put on the contentvalues */ - void bindToContentValues(ContentValues contentValues, TModel model); + void bindToContentValues(@NonNull ContentValues contentValues, @NonNull TModel model); /** * Binds a {@link TModel} to the specified db statement, leaving out the {@link PrimaryKey#autoincrement()} @@ -181,22 +185,34 @@ void bindToInsertStatement(DatabaseStatement sqLiteStatement, TModel model, * @param contentValues The content values to fill. * @param model The model values to put on the content values. */ - void bindToInsertValues(ContentValues contentValues, TModel model); + void bindToInsertValues(@NonNull ContentValues contentValues, @NonNull TModel model); + + /** + * Binds values of the model to an update {@link DatabaseStatement}. It repeats each primary + * key binding twice to ensure proper update statements. + */ + void bindToUpdateStatement(@NonNull DatabaseStatement updateStatement, @NonNull TModel model); + + /** + * Binds values of the model to a delete {@link DatabaseStatement}. + */ + void bindToDeleteStatement(@NonNull DatabaseStatement deleteStatement, @NonNull TModel model); /** - * If a {@link com.raizlabs.android.dbflow.structure.Model} has an autoincrementing primary key, then + * If a model has an autoincrementing primary key, then * this method will be overridden. * * @param model The model object to store the key * @param id The key to store */ - void updateAutoIncrement(TModel model, Number id); + void updateAutoIncrement(@NonNull TModel model, @NonNull Number id); /** * @return The value for the {@link PrimaryKey#autoincrement()} * if it has the field. This method is overridden when its specified for the {@link TModel} */ - Number getAutoIncrementingId(TModel model); + @NonNull + Number getAutoIncrementingId(@NonNull TModel model); /** * @return true if the {@link InternalAdapter} can be cached. diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/ModelAdapter.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/ModelAdapter.java index 2a129391f..14133f452 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/ModelAdapter.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/ModelAdapter.java @@ -9,7 +9,6 @@ import com.raizlabs.android.dbflow.annotation.PrimaryKey; import com.raizlabs.android.dbflow.annotation.Table; import com.raizlabs.android.dbflow.config.DatabaseDefinition; -import com.raizlabs.android.dbflow.sql.language.SQLite; import com.raizlabs.android.dbflow.sql.language.property.IProperty; import com.raizlabs.android.dbflow.sql.language.property.Property; import com.raizlabs.android.dbflow.sql.saveable.ListModelSaver; @@ -28,17 +27,21 @@ /** * Description: Used for generated classes from the combination of {@link Table} and {@link Model}. */ +@SuppressWarnings("NullableProblems") public abstract class ModelAdapter extends InstanceAdapter implements InternalAdapter { private DatabaseStatement insertStatement; private DatabaseStatement compiledStatement; + private DatabaseStatement updateStatement; + private DatabaseStatement deleteStatement; + private String[] cachingColumns; private ModelCache modelCache; private ModelSaver modelSaver; private ListModelSaver listModelSaver; - public ModelAdapter(DatabaseDefinition databaseDefinition) { + public ModelAdapter(@NonNull DatabaseDefinition databaseDefinition) { super(databaseDefinition); if (getTableConfig() != null && getTableConfig().modelSaver() != null) { modelSaver = getTableConfig().modelSaver(); @@ -49,6 +52,7 @@ public ModelAdapter(DatabaseDefinition databaseDefinition) { /** * @return The pre-compiled insert statement for this table model adapter. This is reused and cached. */ + @NonNull public DatabaseStatement getInsertStatement() { if (insertStatement == null) { insertStatement = getInsertStatement(getWritableDatabaseForTable(getModelClass())); @@ -57,6 +61,30 @@ public DatabaseStatement getInsertStatement() { return insertStatement; } + /** + * @return The pre-compiled update statement for this table model adapter. This is reused and cached. + */ + @NonNull + public DatabaseStatement getUpdateStatement() { + if (updateStatement == null) { + updateStatement = getUpdateStatement(getWritableDatabaseForTable(getModelClass())); + } + + return updateStatement; + } + + /** + * @return The pre-compiled delete statement for this table model adapter. This is reused and cached. + */ + @NonNull + public DatabaseStatement getDeleteStatement() { + if (deleteStatement == null) { + deleteStatement = getDeleteStatement(getWritableDatabaseForTable(getModelClass())); + } + + return deleteStatement; + } + public void closeInsertStatement() { if (insertStatement == null) { return; @@ -70,19 +98,35 @@ public void closeInsertStatement() { * @return a new compiled {@link DatabaseStatement} representing insert. Not cached, always generated. * To bind values use {@link #bindToInsertStatement(DatabaseStatement, Object)}. */ - public DatabaseStatement getInsertStatement(DatabaseWrapper databaseWrapper) { + @NonNull + public DatabaseStatement getInsertStatement(@NonNull DatabaseWrapper databaseWrapper) { return databaseWrapper.compileStatement(getInsertStatementQuery()); } - public DatabaseStatement getDeleteStatement(TModel model, DatabaseWrapper databaseWrapper) { - return databaseWrapper.compileStatement(SQLite.delete().from(getModelClass()) - .where(getPrimaryConditionClause(model)).getQuery()); + /** + * @param databaseWrapper The database used to do an update statement. + * @return a new compiled {@link DatabaseStatement} representing update. Not cached, always generated. + * To bind values use {@link #bindToUpdateStatement(DatabaseStatement, Object)}. + */ + @NonNull + public DatabaseStatement getUpdateStatement(@NonNull DatabaseWrapper databaseWrapper) { + return databaseWrapper.compileStatement(getUpdateStatementQuery()); } + /** + * @param databaseWrapper The database used to do a delete statement. + * @return a new compiled {@link DatabaseStatement} representing delete. Not cached, always generated. + * To bind values use {@link #bindToDeleteStatement(DatabaseStatement, Object)}. + */ + @NonNull + public DatabaseStatement getDeleteStatement(@NonNull DatabaseWrapper databaseWrapper) { + return databaseWrapper.compileStatement(getDeleteStatementQuery()); + } /** * @return The precompiled full statement for this table model adapter */ + @NonNull public DatabaseStatement getCompiledStatement() { if (compiledStatement == null) { compiledStatement = getCompiledStatement(getWritableDatabaseForTable(getModelClass())); @@ -104,7 +148,7 @@ public void closeCompiledStatement() { * @return a new compiled {@link DatabaseStatement} representing insert. * To bind values use {@link #bindToInsertStatement(DatabaseStatement, Object)}. */ - public DatabaseStatement getCompiledStatement(DatabaseWrapper databaseWrapper) { + public DatabaseStatement getCompiledStatement(@NonNull DatabaseWrapper databaseWrapper) { return databaseWrapper.compileStatement(getCompiledStatementQuery()); } @@ -114,104 +158,104 @@ public DatabaseStatement getCompiledStatement(DatabaseWrapper databaseWrapper) { * @param cursor The cursor to load * @return A new {@link TModel} */ - public TModel loadFromCursor(FlowCursor cursor) { + public TModel loadFromCursor(@NonNull FlowCursor cursor) { TModel model = newInstance(); loadFromCursor(cursor, model); return model; } @Override - public boolean save(TModel model) { + public boolean save(@NonNull TModel model) { return getModelSaver().save(model); } @Override - public boolean save(TModel model, DatabaseWrapper databaseWrapper) { + public boolean save(@NonNull TModel model, @NonNull DatabaseWrapper databaseWrapper) { return getModelSaver().save(model, databaseWrapper); } @Override - public void saveAll(Collection models) { + public void saveAll(@NonNull Collection models) { getListModelSaver().saveAll(models); } @Override - public void saveAll(Collection models, DatabaseWrapper databaseWrapper) { + public void saveAll(@NonNull Collection models, @NonNull DatabaseWrapper databaseWrapper) { getListModelSaver().saveAll(models, databaseWrapper); } @Override - public long insert(TModel model) { + public long insert(@NonNull TModel model) { return getModelSaver().insert(model); } @Override - public long insert(TModel model, DatabaseWrapper databaseWrapper) { + public long insert(@NonNull TModel model, @NonNull DatabaseWrapper databaseWrapper) { return getModelSaver().insert(model, databaseWrapper); } @Override - public void insertAll(Collection models) { + public void insertAll(@NonNull Collection models) { getListModelSaver().insertAll(models); } @Override - public void insertAll(Collection models, DatabaseWrapper databaseWrapper) { + public void insertAll(@NonNull Collection models, @NonNull DatabaseWrapper databaseWrapper) { getListModelSaver().insertAll(models, databaseWrapper); } @Override - public boolean update(TModel model) { + public boolean update(@NonNull TModel model) { return getModelSaver().update(model); } @Override - public boolean update(TModel model, DatabaseWrapper databaseWrapper) { + public boolean update(@NonNull TModel model, @NonNull DatabaseWrapper databaseWrapper) { return getModelSaver().update(model, databaseWrapper); } @Override - public void updateAll(Collection models) { + public void updateAll(@NonNull Collection models) { getListModelSaver().updateAll(models); } @Override - public void updateAll(Collection models, DatabaseWrapper databaseWrapper) { + public void updateAll(@NonNull Collection models, @NonNull DatabaseWrapper databaseWrapper) { getListModelSaver().updateAll(models, databaseWrapper); } @Override - public boolean delete(TModel model) { + public boolean delete(@NonNull TModel model) { return getModelSaver().delete(model); } @Override - public boolean delete(TModel model, DatabaseWrapper databaseWrapper) { + public boolean delete(@NonNull TModel model, @NonNull DatabaseWrapper databaseWrapper) { return getModelSaver().delete(model, databaseWrapper); } @Override - public void deleteAll(Collection tModels, DatabaseWrapper databaseWrapper) { + public void deleteAll(@NonNull Collection tModels, @NonNull DatabaseWrapper databaseWrapper) { getListModelSaver().deleteAll(tModels, databaseWrapper); } @Override - public void deleteAll(Collection tModels) { + public void deleteAll(@NonNull Collection tModels) { getListModelSaver().deleteAll(tModels); } @Override - public void bindToInsertStatement(DatabaseStatement sqLiteStatement, TModel model) { + public void bindToInsertStatement(@NonNull DatabaseStatement sqLiteStatement, @NonNull TModel model) { bindToInsertStatement(sqLiteStatement, model, 0); } @Override - public void bindToContentValues(ContentValues contentValues, TModel tModel) { + public void bindToContentValues(@NonNull ContentValues contentValues, @NonNull TModel tModel) { bindToInsertValues(contentValues, tModel); } @Override - public void bindToStatement(DatabaseStatement sqLiteStatement, TModel tModel) { + public void bindToStatement(@NonNull DatabaseStatement sqLiteStatement, @NonNull TModel tModel) { bindToInsertStatement(sqLiteStatement, tModel, 0); } @@ -223,7 +267,7 @@ public void bindToStatement(DatabaseStatement sqLiteStatement, TModel tModel) { * @param id The key to store */ @Override - public void updateAutoIncrement(TModel model, Number id) { + public void updateAutoIncrement(@NonNull TModel model, @NonNull Number id) { } @@ -231,8 +275,9 @@ public void updateAutoIncrement(TModel model, Number id) { * @return The value for the {@link PrimaryKey#autoincrement()} * if it has the field. This method is overridden when its specified for the {@link TModel} */ + @NonNull @Override - public Number getAutoIncrementingId(TModel model) { + public Number getAutoIncrementingId(@NonNull TModel model) { throw new InvalidDBConfiguration( String.format("This method may have been called in error. The model class %1s must contain" + "a single primary key (if used in a ModelCache, this method may be called)", @@ -243,6 +288,7 @@ public Number getAutoIncrementingId(TModel model) { * @return The autoincrement column name for the {@link PrimaryKey#autoincrement()} * if it has the field. This method is overridden when its specified for the {@link TModel} */ + @NonNull public String getAutoIncrementingColumnName() { throw new InvalidDBConfiguration( String.format("This method may have been called in error. The model class %1s must contain " + @@ -254,7 +300,7 @@ public String getAutoIncrementingColumnName() { * Called when we want to save our {@link ForeignKey} objects. usually during insert + update. * This method is overridden when {@link ForeignKey} specified */ - public void saveForeignKeys(TModel model, DatabaseWrapper wrapper) { + public void saveForeignKeys(@NonNull TModel model, @NonNull DatabaseWrapper wrapper) { } @@ -262,13 +308,14 @@ public void saveForeignKeys(TModel model, DatabaseWrapper wrapper) { * Called when we want to delete our {@link ForeignKey} objects. During deletion {@link #delete(Object, DatabaseWrapper)} * This method is overridden when {@link ForeignKey} specified */ - public void deleteForeignKeys(TModel model, DatabaseWrapper wrapper) { + public void deleteForeignKeys(@NonNull TModel model, @NonNull DatabaseWrapper wrapper) { } /** * @return A set of columns that represent the caching columns. */ + @NonNull public String[] createCachingColumns() { return new String[]{getAutoIncrementingColumnName()}; } @@ -291,7 +338,8 @@ public String[] getCachingColumns() { * @param cursor The cursor to load from. * @return The populated set of values to load from cache. */ - public Object[] getCachingColumnValuesFromCursor(Object[] inValues, FlowCursor cursor) { + public Object[] getCachingColumnValuesFromCursor(@NonNull Object[] inValues, + @NonNull FlowCursor cursor) { throwCachingError(); return null; } @@ -300,7 +348,7 @@ public Object[] getCachingColumnValuesFromCursor(Object[] inValues, FlowCursor c * @param cursor The cursor to load caching id from. * @return The single cache column from cursor (if single). */ - public Object getCachingColumnValueFromCursor(FlowCursor cursor) { + public Object getCachingColumnValueFromCursor(@NonNull FlowCursor cursor) { throwSingleCachingError(); return null; } @@ -314,7 +362,7 @@ public Object getCachingColumnValueFromCursor(FlowCursor cursor) { * @param TModel The model to load from. * @return The populated set of values to load from cache. */ - public Object[] getCachingColumnValuesFromModel(Object[] inValues, TModel TModel) { + public Object[] getCachingColumnValuesFromModel(@NonNull Object[] inValues, @NonNull TModel TModel) { throwCachingError(); return null; } @@ -323,7 +371,7 @@ public Object[] getCachingColumnValuesFromModel(Object[] inValues, TModel TModel * @param model The model to load cache column data from. * @return The single cache column from model (if single). */ - public Object getCachingColumnValueFromModel(TModel model) { + public Object getCachingColumnValueFromModel(@NonNull TModel model) { throwSingleCachingError(); return null; } @@ -332,6 +380,10 @@ public void storeModelInCache(@NonNull TModel model) { getModelCache().addModel(getCachingId(model), model); } + public void removeModelFromCache(@NonNull TModel model) { + getModelCache().removeModel(getCachingId(model)); + } + public ModelCache getModelCache() { if (modelCache == null) { modelCache = createModelCache(); @@ -387,7 +439,7 @@ public void setModelSaver(ModelSaver modelSaver) { * * @param cursor The cursor to reload from. */ - public void reloadRelationships(@NonNull TModel model, FlowCursor cursor) { + public void reloadRelationships(@NonNull TModel model, @NonNull FlowCursor cursor) { if (!cachingEnabled()) { throwCachingError(); } @@ -443,6 +495,10 @@ protected String getInsertStatementQuery() { */ protected abstract String getCompiledStatementQuery(); + protected abstract String getUpdateStatementQuery(); + + protected abstract String getDeleteStatementQuery(); + /** * @return The conflict algorithm to use when updating a row in this table. */ diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/QueryModelAdapter.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/QueryModelAdapter.java index c6010be56..f00637fae 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/QueryModelAdapter.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/QueryModelAdapter.java @@ -1,5 +1,7 @@ package com.raizlabs.android.dbflow.structure; +import android.support.annotation.NonNull; + import com.raizlabs.android.dbflow.annotation.QueryModel; import com.raizlabs.android.dbflow.config.DatabaseDefinition; import com.raizlabs.android.dbflow.sql.language.OperatorGroup; @@ -17,17 +19,17 @@ public QueryModelAdapter(DatabaseDefinition databaseDefinition) { } @Override - public OperatorGroup getPrimaryConditionClause(TQueryModel model) { + public OperatorGroup getPrimaryConditionClause(@NonNull TQueryModel model) { throw new UnsupportedOperationException("QueryModels cannot check for existence"); } @Override - public boolean exists(TQueryModel model) { + public boolean exists(@NonNull TQueryModel model) { throw new UnsupportedOperationException("QueryModels cannot check for existence"); } @Override - public boolean exists(TQueryModel model, DatabaseWrapper databaseWrapper) { + public boolean exists(@NonNull TQueryModel model, @NonNull DatabaseWrapper databaseWrapper) { throw new UnsupportedOperationException("QueryModels cannot check for existence"); } } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/RetrievalAdapter.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/RetrievalAdapter.java index cc3cd6a41..0f803f6fd 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/RetrievalAdapter.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/RetrievalAdapter.java @@ -2,6 +2,7 @@ import android.database.Cursor; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import com.raizlabs.android.dbflow.config.DatabaseConfig; import com.raizlabs.android.dbflow.config.DatabaseDefinition; @@ -18,6 +19,7 @@ * Description: Provides a base retrieval class for all {@link Model} backed * adapters. */ +@SuppressWarnings("NullableProblems") public abstract class RetrievalAdapter { private SingleModelLoader singleModelLoader; @@ -25,7 +27,7 @@ public abstract class RetrievalAdapter { private TableConfig tableConfig; - public RetrievalAdapter(DatabaseDefinition databaseDefinition) { + public RetrievalAdapter(@NonNull DatabaseDefinition databaseDefinition) { DatabaseConfig databaseConfig = FlowManager.getConfig() .getConfigForDatabase(databaseDefinition.getAssociatedDatabaseClassFile()); if (databaseConfig != null) { @@ -42,11 +44,11 @@ public RetrievalAdapter(DatabaseDefinition databaseDefinition) { } } - public void load(TModel model) { + public void load(@NonNull TModel model) { load(model, FlowManager.getDatabaseForTable(getModelClass()).getWritableDatabase()); } - public void load(TModel model, DatabaseWrapper databaseWrapper) { + public void load(@NonNull TModel model, DatabaseWrapper databaseWrapper) { getSingleModelLoader().load(databaseWrapper, SQLite.select() .from(getModelClass()) @@ -60,13 +62,13 @@ public void load(TModel model, DatabaseWrapper databaseWrapper) { * @param model The model to assign cursor data to * @param cursor The cursor to load into the model */ - public abstract void loadFromCursor(FlowCursor cursor, TModel model); + public abstract void loadFromCursor(@NonNull FlowCursor cursor, @NonNull TModel model); /** * @param model The model to query values from * @return True if it exists as a row in the corresponding database table */ - public boolean exists(TModel model) { + public boolean exists(@NonNull TModel model) { return exists(model, FlowManager.getDatabaseForTable(getModelClass()).getWritableDatabase()); } @@ -74,19 +76,22 @@ public boolean exists(TModel model) { * @param model The model to query values from * @return True if it exists as a row in the corresponding database table */ - public abstract boolean exists(TModel model, DatabaseWrapper databaseWrapper); + public abstract boolean exists(@NonNull TModel model, + @NonNull DatabaseWrapper databaseWrapper); /** * @param model The primary condition clause. * @return The clause that contains necessary primary conditions for this table. */ - public abstract OperatorGroup getPrimaryConditionClause(TModel model); + public abstract OperatorGroup getPrimaryConditionClause(@NonNull TModel model); /** * @return the model class this adapter corresponds to */ + @NonNull public abstract Class getModelClass(); + @Nullable protected TableConfig getTableConfig() { return tableConfig; } @@ -94,6 +99,7 @@ protected TableConfig getTableConfig() { /** * @return A new {@link ListModelLoader}, caching will override this loader instance. */ + @NonNull public ListModelLoader getListModelLoader() { if (listModelLoader == null) { listModelLoader = createListModelLoader(); @@ -104,6 +110,7 @@ public ListModelLoader getListModelLoader() { /** * @return A new {@link ListModelLoader}, caching will override this loader instance. */ + @NonNull protected ListModelLoader createListModelLoader() { return new ListModelLoader<>(getModelClass()); } @@ -111,10 +118,12 @@ protected ListModelLoader createListModelLoader() { /** * @return A new {@link SingleModelLoader}, caching will override this loader instance. */ + @NonNull protected SingleModelLoader createSingleModelLoader() { return new SingleModelLoader<>(getModelClass()); } + @NonNull public SingleModelLoader getSingleModelLoader() { if (singleModelLoader == null) { singleModelLoader = createSingleModelLoader(); @@ -126,6 +135,7 @@ public SingleModelLoader getSingleModelLoader() { * @return A new instance of a {@link SingleModelLoader}. Subsequent calls do not cache * this object so it's recommended only calling this in bulk if possible. */ + @NonNull public SingleModelLoader getNonCacheableSingleModelLoader() { return new SingleModelLoader<>(getModelClass()); } @@ -134,6 +144,7 @@ public SingleModelLoader getNonCacheableSingleModelLoader() { * @return A new instance of a {@link ListModelLoader}. Subsequent calls do not cache * this object so it's recommended only calling this in bulk if possible. */ + @NonNull public ListModelLoader getNonCacheableListModelLoader() { return new ListModelLoader<>(getModelClass()); } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/transaction/FastStoreModelTransaction.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/transaction/FastStoreModelTransaction.java index 8e240738e..da5ba517c 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/transaction/FastStoreModelTransaction.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/transaction/FastStoreModelTransaction.java @@ -48,6 +48,16 @@ public void processModel(@NonNull List tModels, InternalAdapter }, internalAdapter); } + @NonNull + public static Builder deleteBuilder(@NonNull InternalAdapter internalAdapter) { + return new Builder<>(new ProcessModelList() { + @Override + public void processModel(@NonNull List tModels, InternalAdapter adapter, DatabaseWrapper wrapper) { + adapter.deleteAll(tModels, wrapper); + } + }, internalAdapter); + } + /** * Description: Simple interface for acting on a model in a Transaction or list of {@link Model} */ diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/listener/ContentValuesListener.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/listener/ContentValuesListener.java index 92ad4027c..813059056 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/listener/ContentValuesListener.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/listener/ContentValuesListener.java @@ -10,12 +10,19 @@ * Description: Called after the declared {@link ContentValues} are binded. It enables * us to listen and add custom behavior to the {@link ContentValues}. These must be * defined in a {@link Model} class to register properly. + *

+ * This class will no longer get called during updates unless explicit call to + * {@link ModelAdapter#bindToContentValues(ContentValues, Object)} + * or {@link ModelAdapter#bindToInsertValues(ContentValues, Object)} + * + * @see SQLiteStatementListener */ +@Deprecated public interface ContentValuesListener { /** * Called during an {@link Model#update()} and at the end of - * {@link ModelAdapter#bindToContentValues(ContentValues, Model)} + * {@link ModelAdapter#bindToContentValues(ContentValues, Object)} * . It enables you to customly change the values as necessary during update to the database. * * @param contentValues The content values to bind to for an update. @@ -24,7 +31,7 @@ public interface ContentValuesListener { /** * Called during an {@link Model#update()} and at the end of - * {@link ModelAdapter#bindToInsertValues(ContentValues, Model)}. + * {@link ModelAdapter#bindToInsertValues(ContentValues, Object)}. * It enables you to customly change the values as necessary during insert * to the database for a {@link ContentProvider}. * diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/listener/SQLiteStatementListener.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/listener/SQLiteStatementListener.java index 66f4f82b4..7792b7ef2 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/listener/SQLiteStatementListener.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/listener/SQLiteStatementListener.java @@ -12,7 +12,7 @@ public interface SQLiteStatementListener { /** - * Called at the end of {@link InternalAdapter#bindToStatement(DatabaseStatement, Model)} + * Called at the end of {@link InternalAdapter#bindToStatement(DatabaseStatement, Object)} * Perform a custom manipulation of the statement as willed. * * @param databaseStatement The statement from the {@link ModelAdapter} @@ -20,10 +20,20 @@ public interface SQLiteStatementListener { void onBindToStatement(DatabaseStatement databaseStatement); /** - * Called at the end of {@link InternalAdapter#bindToInsertStatement(DatabaseStatement, Model)} + * Called at the end of {@link InternalAdapter#bindToInsertStatement(DatabaseStatement, Object)} * Perform a custom manipulation of the statement as willed. * * @param databaseStatement The insert statement from the {@link ModelAdapter} */ void onBindToInsertStatement(DatabaseStatement databaseStatement); + + /** + * Called at the end of {@link InternalAdapter#bindToUpdateStatement(DatabaseStatement, Object)} + * Perform a custom manipulation of the statement as willed. + * + * @param databaseStatement The insert statement from the {@link ModelAdapter} + */ + void onBindToUpdateStatement(DatabaseStatement databaseStatement); + + void onBindToDeleteStatement(DatabaseStatement databaseStatement); } diff --git a/gradle.properties b/gradle.properties index b0db3814f..a40befee1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=4.0.1 +version=4.0.2 version_code=1 group=com.raizlabs.android bt_siteUrl=https://github.com/Raizlabs/DBFlow