diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..c2e9a3e4a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "usage2"] + path = usage2 + url = https://git.gitbook.com/agrosner/dbflow.git diff --git a/README.md b/README.md index 1046c648a..20e7aad02 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ DBFlow is built from a collection of the best features of many database librarie Changes exist in the [releases tab](https://github.com/Raizlabs/DBFlow/releases). # Usage Docs -For more detailed usage, check out it out [here](/usage2/Intro.md) +For more detailed usage, check out it out [here](https://agrosner.gitbooks.io/dbflow/content/) # Including in your project @@ -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.0-beta7" + def dbflow_version = "4.0.0" // 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 0c784ed80..07002b362 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.1.1' + ext.kotlin_version = '1.1.2' repositories { jcenter() } diff --git a/dbflow-core/src/main/java/com/raizlabs/android/dbflow/annotation/ForeignKey.java b/dbflow-core/src/main/java/com/raizlabs/android/dbflow/annotation/ForeignKey.java index 8c96ca9c3..fb82d308c 100644 --- a/dbflow-core/src/main/java/com/raizlabs/android/dbflow/annotation/ForeignKey.java +++ b/dbflow-core/src/main/java/com/raizlabs/android/dbflow/annotation/ForeignKey.java @@ -43,6 +43,13 @@ */ boolean stubbedRelationship() default false; + /** + * @return If true, during a transaction, FK constraints are not violated immediately until the resulting transaction commits. + * This is useful for out of order foreign key operations. + * @see Deferred Foreign Key Constraints + */ + boolean deferred() default false; + /** * @return an optional table class that this reference points to. It's only used if the field * is NOT a Model class. diff --git a/dbflow-core/src/main/java/com/raizlabs/android/dbflow/annotation/OneToMany.java b/dbflow-core/src/main/java/com/raizlabs/android/dbflow/annotation/OneToMany.java index 73fe6ab51..e200b5dff 100644 --- a/dbflow-core/src/main/java/com/raizlabs/android/dbflow/annotation/OneToMany.java +++ b/dbflow-core/src/main/java/com/raizlabs/android/dbflow/annotation/OneToMany.java @@ -57,4 +57,10 @@ enum Method { * a setter for it. */ boolean isVariablePrivate() default false; + + /** + * @return If true, the code generated for this relationship done as efficiently as possible. + * It will not work on nested relationships, caching, and other code that requires overriding of BaseModel or Model operations. + */ + boolean efficientMethods() default true; } diff --git a/dbflow-core/src/main/java/com/raizlabs/android/dbflow/converter/CharConverter.java b/dbflow-core/src/main/java/com/raizlabs/android/dbflow/converter/CharConverter.java new file mode 100644 index 000000000..f7bf81692 --- /dev/null +++ b/dbflow-core/src/main/java/com/raizlabs/android/dbflow/converter/CharConverter.java @@ -0,0 +1,17 @@ +package com.raizlabs.android.dbflow.converter; + +/** + * Description: Converts a {@link Character} into a {@link String} for database storage. + */ +public class CharConverter extends TypeConverter { + + @Override + public String getDBValue(Character model) { + return model != null ? new String(new char[]{model}) : null; + } + + @Override + public Character getModelValue(String data) { + return data != null ? data.charAt(0) : null; + } +} diff --git a/dbflow-kotlinextensions/src/main/java/com/raizlabs/android/dbflow/kotlinextensions/PropertyMethodExtensions.kt b/dbflow-kotlinextensions/src/main/java/com/raizlabs/android/dbflow/kotlinextensions/PropertyMethodExtensions.kt index be223b744..37930eea1 100644 --- a/dbflow-kotlinextensions/src/main/java/com/raizlabs/android/dbflow/kotlinextensions/PropertyMethodExtensions.kt +++ b/dbflow-kotlinextensions/src/main/java/com/raizlabs/android/dbflow/kotlinextensions/PropertyMethodExtensions.kt @@ -5,7 +5,6 @@ import com.raizlabs.android.dbflow.sql.language.IConditional import com.raizlabs.android.dbflow.sql.language.Operator import com.raizlabs.android.dbflow.sql.language.Operator.Between import com.raizlabs.android.dbflow.sql.language.property.Property -import com.raizlabs.android.dbflow.structure.Model /** * Description: Provides property methods in via infix functions. @@ -93,36 +92,28 @@ infix fun IConditional.notIn(values: Array): Operator.In<*> { } } -infix fun IConditional.`is`(baseModelQueriable: BaseModelQueriable): Operator<*> = this.`is`(baseModelQueriable) +infix fun IConditional.`is`(baseModelQueriable: BaseModelQueriable): Operator<*> = this.`is`(baseModelQueriable) -infix fun IConditional.eq(baseModelQueriable: BaseModelQueriable): Operator<*> = this.eq(baseModelQueriable) +infix fun IConditional.eq(baseModelQueriable: BaseModelQueriable): Operator<*> = this.eq(baseModelQueriable) -infix fun IConditional.isNot(baseModelQueriable: BaseModelQueriable): Operator<*> = this.isNot(baseModelQueriable) +infix fun IConditional.isNot(baseModelQueriable: BaseModelQueriable): Operator<*> = this.isNot(baseModelQueriable) +infix fun IConditional.notEq(baseModelQueriable: BaseModelQueriable): Operator<*> = this.notEq(baseModelQueriable) +infix fun IConditional.like(baseModelQueriable: BaseModelQueriable): Operator<*> = this.like(baseModelQueriable) +infix fun IConditional.glob(baseModelQueriable: BaseModelQueriable): Operator<*> = this.glob(baseModelQueriable) +infix fun IConditional.greaterThan(baseModelQueriable: BaseModelQueriable): Operator<*> = this.greaterThan(baseModelQueriable) +infix fun IConditional.greaterThanOrEq(baseModelQueriable: BaseModelQueriable): Operator<*> = this.greaterThanOrEq(baseModelQueriable) +infix fun IConditional.lessThan(baseModelQueriable: BaseModelQueriable): Operator<*> = this.lessThan(baseModelQueriable) +infix fun IConditional.lessThanOrEq(baseModelQueriable: BaseModelQueriable): Operator<*> = this.lessThanOrEq(baseModelQueriable) +infix fun IConditional.between(baseModelQueriable: BaseModelQueriable): Between<*> = this.between(baseModelQueriable) -infix fun IConditional.notEq(baseModelQueriable: BaseModelQueriable): Operator<*> = this.notEq(baseModelQueriable) - -infix fun IConditional.like(baseModelQueriable: BaseModelQueriable): Operator<*> = this.like(baseModelQueriable) - -infix fun IConditional.glob(baseModelQueriable: BaseModelQueriable): Operator<*> = this.glob(baseModelQueriable) - -infix fun IConditional.greaterThan(baseModelQueriable: BaseModelQueriable): Operator<*> = this.greaterThan(baseModelQueriable) - -infix fun IConditional.greaterThanOrEq(baseModelQueriable: BaseModelQueriable): Operator<*> = this.greaterThanOrEq(baseModelQueriable) - -infix fun IConditional.lessThan(baseModelQueriable: BaseModelQueriable): Operator<*> = this.lessThan(baseModelQueriable) - -infix fun IConditional.lessThanOrEq(baseModelQueriable: BaseModelQueriable): Operator<*> = this.lessThanOrEq(baseModelQueriable) - -infix fun IConditional.between(baseModelQueriable: BaseModelQueriable): Between<*> = this.between(baseModelQueriable) - -infix fun IConditional.`in`(values: Array>): Operator.In<*> { +infix fun IConditional.`in`(values: Array>): Operator.In<*> { return when (values.size) { 1 -> `in`(values[0]) else -> this.`in`(values[0], *values.sliceArray(IntRange(1, values.size))) } } -infix fun IConditional.notIn(values: Array>): Operator.In<*> { +infix fun IConditional.notIn(values: Array>): Operator.In<*> { return when (values.size) { 1 -> notIn(values[0]) else -> this.notIn(values[0], *values.sliceArray(IntRange(1, values.size))) diff --git a/dbflow-kotlinextensions/src/main/java/com/raizlabs/android/dbflow/kotlinextensions/QueryExtensions.kt b/dbflow-kotlinextensions/src/main/java/com/raizlabs/android/dbflow/kotlinextensions/QueryExtensions.kt index 22edf7cca..fd5a43cb4 100644 --- a/dbflow-kotlinextensions/src/main/java/com/raizlabs/android/dbflow/kotlinextensions/QueryExtensions.kt +++ b/dbflow-kotlinextensions/src/main/java/com/raizlabs/android/dbflow/kotlinextensions/QueryExtensions.kt @@ -9,7 +9,7 @@ import com.raizlabs.android.dbflow.sql.queriable.AsyncQuery import com.raizlabs.android.dbflow.sql.queriable.ModelQueriable import com.raizlabs.android.dbflow.sql.queriable.Queriable import com.raizlabs.android.dbflow.structure.AsyncModel -import com.raizlabs.android.dbflow.structure.BaseModel +import com.raizlabs.android.dbflow.structure.Model import com.raizlabs.android.dbflow.structure.database.transaction.QueryTransaction import kotlin.reflect.KClass @@ -100,18 +100,18 @@ inline val ModelQueriable.async get() = async() infix inline fun AsyncQuery.list(crossinline callback: (QueryTransaction<*>, MutableList) -> Unit) - = queryListResultCallback { queryTransaction, mutableList -> callback(queryTransaction, mutableList) } - .execute() + = queryListResultCallback { queryTransaction, mutableList -> callback(queryTransaction, mutableList) } + .execute() infix inline fun AsyncQuery.result(crossinline callback: (QueryTransaction<*>, T?) -> Unit) - = querySingleResultCallback { queryTransaction, model -> callback(queryTransaction, model) } - .execute() + = querySingleResultCallback { queryTransaction, model -> callback(queryTransaction, model) } + .execute() infix inline fun AsyncQuery.cursorResult(crossinline callback: (QueryTransaction<*>, CursorResult) -> Unit) - = queryResultCallback { queryTransaction, cursorResult -> callback(queryTransaction, cursorResult) } - .execute() + = queryResultCallback { queryTransaction, cursorResult -> callback(queryTransaction, cursorResult) } + .execute() -inline val BaseModel.async: AsyncModel +inline val Model.async get() = async() infix inline fun AsyncModel.insert(crossinline listener: (T) -> Unit) = withListener { listener(it) }.insert() @@ -163,7 +163,7 @@ infix fun Update.set(sqlOperator: SQLOperator) = set(sqlOperator) inline fun delete() = SQLite.delete(T::class.java) inline fun delete(deleteClause: From.() -> BaseModelQueriable) - = deleteClause(SQLite.delete(T::class.java)) + = deleteClause(SQLite.delete(T::class.java)) // insert methods diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/ClassNames.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/ClassNames.kt index 23d377465..2889493fb 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/ClassNames.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/ClassNames.kt @@ -33,6 +33,7 @@ object ClassNames { val URI = ClassName.get("android.net", "Uri") val URI_MATCHER = ClassName.get("android.content", "UriMatcher") val CURSOR = ClassName.get("android.database", "Cursor") + val FLOW_CURSOR = ClassName.get(DATABASE, "FlowCursor") val DATABASE_UTILS = ClassName.get("android.database", "DatabaseUtils") val CONTENT_VALUES = ClassName.get("android.content", "ContentValues") val CONTENT_URIS = ClassName.get("android.content", "ContentUris") diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/Handlers.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/Handlers.kt index 54965487c..92a6a72ef 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/Handlers.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/Handlers.kt @@ -1,34 +1,15 @@ package com.raizlabs.android.dbflow.processor import com.google.common.collect.Sets -import com.raizlabs.android.dbflow.annotation.Database -import com.raizlabs.android.dbflow.annotation.ManyToMany -import com.raizlabs.android.dbflow.annotation.Migration -import com.raizlabs.android.dbflow.annotation.ModelView -import com.raizlabs.android.dbflow.annotation.MultipleManyToMany -import com.raizlabs.android.dbflow.annotation.QueryModel -import com.raizlabs.android.dbflow.annotation.Table +import com.raizlabs.android.dbflow.annotation.* import com.raizlabs.android.dbflow.annotation.TypeConverter import com.raizlabs.android.dbflow.annotation.provider.ContentProvider import com.raizlabs.android.dbflow.annotation.provider.TableEndpoint -import com.raizlabs.android.dbflow.converter.BigDecimalConverter -import com.raizlabs.android.dbflow.converter.BooleanConverter -import com.raizlabs.android.dbflow.converter.CalendarConverter -import com.raizlabs.android.dbflow.converter.DateConverter -import com.raizlabs.android.dbflow.converter.SqlDateConverter -import com.raizlabs.android.dbflow.converter.UUIDConverter -import com.raizlabs.android.dbflow.processor.definition.ContentProviderDefinition -import com.raizlabs.android.dbflow.processor.definition.DatabaseDefinition -import com.raizlabs.android.dbflow.processor.definition.ManyToManyDefinition -import com.raizlabs.android.dbflow.processor.definition.MigrationDefinition -import com.raizlabs.android.dbflow.processor.definition.ModelViewDefinition -import com.raizlabs.android.dbflow.processor.definition.QueryModelDefinition -import com.raizlabs.android.dbflow.processor.definition.TableDefinition -import com.raizlabs.android.dbflow.processor.definition.TableEndpointDefinition -import com.raizlabs.android.dbflow.processor.definition.TypeConverterDefinition +import com.raizlabs.android.dbflow.converter.* +import com.raizlabs.android.dbflow.processor.definition.* +import com.raizlabs.android.dbflow.processor.utils.annotation import javax.annotation.processing.RoundEnvironment import javax.lang.model.element.Element -import javax.lang.model.element.Modifier import javax.lang.model.element.PackageElement import javax.lang.model.element.TypeElement @@ -125,14 +106,14 @@ class TableHandler : BaseContainerHandler() { val tableDefinition = TableDefinition(processorManager, element) processorManager.addTableDefinition(tableDefinition) - if (element.getAnnotation(ManyToMany::class.java) != null) { + if (element.annotation() != null) { val manyToManyDefinition = ManyToManyDefinition(element, processorManager) processorManager.addManyToManyDefinition(manyToManyDefinition) } - if (element.getAnnotation(MultipleManyToMany::class.java) != null) { - val multipleManyToMany = element.getAnnotation(MultipleManyToMany::class.java) - multipleManyToMany.value.forEach { + if (element.annotation() != null) { + val multipleManyToMany = element.annotation() + multipleManyToMany?.value?.forEach { processorManager.addManyToManyDefinition(ManyToManyDefinition(element, processorManager, it)) } } @@ -154,7 +135,7 @@ class TypeConverterHandler : BaseContainerHandler() { override fun onProcessElement(processorManager: ProcessorManager, element: Element) { if (element is TypeElement) { - val className = ProcessorUtils.fromTypeMirror(element.asType(), processorManager) + val className = com.raizlabs.android.dbflow.processor.utils.fromTypeMirror(element.asType(), processorManager) val converterDefinition = className?.let { TypeConverterDefinition(it, element.asType(), processorManager, element) } converterDefinition?.let { if (VALIDATOR.validate(processorManager, converterDefinition)) { @@ -176,7 +157,8 @@ class TypeConverterHandler : BaseContainerHandler() { private val DEFAULT_TYPE_CONVERTERS = arrayOf>(CalendarConverter::class.java, BigDecimalConverter::class.java, DateConverter::class.java, SqlDateConverter::class.java, - BooleanConverter::class.java, UUIDConverter::class.java) + BooleanConverter::class.java, UUIDConverter::class.java, + CharConverter::class.java) } } @@ -232,7 +214,6 @@ class DatabaseHandler : BaseContainerHandler() { companion object { val TYPE_CONVERTER_MAP_FIELD_NAME = "typeConverters" - val METHOD_MODIFIERS: Set = Sets.newHashSet(Modifier.PUBLIC, Modifier.FINAL) val MODEL_ADAPTER_MAP_FIELD_NAME = "modelAdapters" val QUERY_MODEL_ADAPTER_MAP_FIELD_NAME = "queryModelAdapterMap" val MIGRATION_FIELD_NAME = "migrationMap" 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 9606f30f0..cfe84d855 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 @@ -29,23 +29,21 @@ class ProcessorManager internal constructor(val processingEnvironment: Processin lateinit var manager: ProcessorManager } - private val uniqueDatabases = Lists.newArrayList() - private val modelToDatabaseMap = Maps.newHashMap() - val typeConverters = Maps.newLinkedHashMap() - private val migrations = Maps.newHashMap>>() + private val uniqueDatabases = arrayListOf() + private val modelToDatabaseMap = hashMapOf() + val typeConverters = linkedMapOf() + private val migrations = hashMapOf>>() - private val databaseDefinitionMap = Maps.newHashMap() - private val handlers = ArrayList>() - private val providerMap = Maps.newHashMap() + private val databaseDefinitionMap = hashMapOf() + private val handlers = mutableSetOf>() + private val providerMap = hashMapOf() init { manager = this } fun addHandlers(vararg containerHandlers: BaseContainerHandler<*>) { - containerHandlers - .filterNot { handlers.contains(it) } - .forEach { handlers.add(it) } + containerHandlers.forEach { handlers.add(it) } } val messager: Messager = processingEnvironment.messager @@ -179,14 +177,7 @@ class ProcessorManager internal constructor(val processingEnvironment: Processin } } - fun getMigrationsForDatabase(databaseName: TypeName): Map> { - val migrationDefinitions = migrations[databaseName] - if (migrationDefinitions != null) { - return migrationDefinitions - } else { - return Maps.newHashMap>() - } - } + fun getMigrationsForDatabase(databaseName: TypeName) = migrations[databaseName] ?: hashMapOf>() fun addContentProviderDefinition(contentProviderDefinition: ContentProviderDefinition) { contentProviderDefinition.elementTypeName?.let { @@ -210,11 +201,9 @@ class ProcessorManager internal constructor(val processingEnvironment: Processin messager.printMessage(Diagnostic.Kind.ERROR, String.format("*==========*${callingClass ?: ""} :" + error?.trim() + "*==========*", *args)) var stackTraceElements = Thread.currentThread().stackTrace if (stackTraceElements.size > 8) { - stackTraceElements = Arrays.copyOf(stackTraceElements, 8) - } - for (stackTrace in stackTraceElements) { - messager.printMessage(Diagnostic.Kind.ERROR, stackTrace.toString()) + stackTraceElements = stackTraceElements.copyOf(8) } + stackTraceElements.forEach { messager.printMessage(Diagnostic.Kind.ERROR, it.toString()) } } fun logError(error: String?, vararg args: Any?) = logError(callingClass = null, error = error, args = args) 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 deleted file mode 100644 index 44a2fe433..000000000 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/ProcessorUtils.kt +++ /dev/null @@ -1,114 +0,0 @@ -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/SQLiteHelper.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/SQLiteHelper.kt index 4cf734644..b81baa40c 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/SQLiteHelper.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/SQLiteHelper.kt @@ -13,6 +13,9 @@ enum class SQLiteHelper { INTEGER { override val sqLiteStatementMethod = "Long" + + override val sqliteStatementWrapperMethod: String + get() = "Number" }, REAL { override val sqLiteStatementMethod = "Double" @@ -26,6 +29,9 @@ enum class SQLiteHelper { abstract val sqLiteStatementMethod: String + open val sqliteStatementWrapperMethod + get() = sqLiteStatementMethod + companion object { private val sTypeMap = hashMapOf(TypeName.BYTE to SQLiteHelper.INTEGER, @@ -52,7 +58,7 @@ enum class SQLiteHelper { private val sMethodMap = hashMapOf(ArrayTypeName.of(TypeName.BYTE) to "getBlob", ArrayTypeName.of(TypeName.BYTE.box()) to "getBlob", - TypeName.BOOLEAN to "getInt", + TypeName.BOOLEAN to "getBoolean", TypeName.BYTE to "getInt", TypeName.BYTE.box() to "getInt", TypeName.CHAR to "getString", diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/Validators.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/Validators.kt index 9095508a5..6ad56f0ad 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/Validators.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/Validators.kt @@ -39,18 +39,18 @@ class ColumnValidator : Validator { // validate getter and setters. if (validatorDefinition.columnAccessor is PrivateScopeColumnAccessor) { val privateColumnAccess = validatorDefinition.columnAccessor as PrivateScopeColumnAccessor - if (!validatorDefinition.baseTableDefinition.classElementLookUpMap.containsKey(privateColumnAccess.getGetterNameElement())) { + if (!validatorDefinition.baseTableDefinition.classElementLookUpMap.containsKey(privateColumnAccess.getterNameElement)) { processorManager.logError(ColumnValidator::class, "Could not find getter for private element: " + "\"%1s\" from table class: %1s. Consider adding a getter with name %1s or making it more accessible.", validatorDefinition.elementName, validatorDefinition.baseTableDefinition.elementName, - privateColumnAccess.getGetterNameElement()) + privateColumnAccess.getterNameElement) success = false } - if (!validatorDefinition.baseTableDefinition.classElementLookUpMap.containsKey(privateColumnAccess.getSetterNameElement())) { + if (!validatorDefinition.baseTableDefinition.classElementLookUpMap.containsKey(privateColumnAccess.setterNameElement)) { processorManager.logError(ColumnValidator::class, - "Could not find setter for private element: " + "\"%1s\" from table class: %1s. Consider adding a setter with name %1s or making it more accessible.", - validatorDefinition.elementName, validatorDefinition.baseTableDefinition.elementName, - privateColumnAccess.getSetterNameElement()) + "Could not find setter for private element: \"${validatorDefinition.elementName}\"" + + " from table class: ${validatorDefinition.baseTableDefinition.elementName}. " + + "Consider adding a setter with name ${privateColumnAccess.setterNameElement} or making it more accessible.") success = false } } @@ -74,23 +74,22 @@ class ColumnValidator : Validator { if (validatorDefinition.columnAccessor is EnumColumnAccessor) { if (validatorDefinition.isPrimaryKey) { success = false - processorManager.logError("Enums cannot be primary keys. Column: %1s and type: %1s", validatorDefinition.columnName, - validatorDefinition.elementTypeName) + processorManager.logError("Enums cannot be primary keys. Column: ${validatorDefinition.columnName}" + + " and type: ${validatorDefinition.elementTypeName}") } else if (validatorDefinition is ForeignKeyColumnDefinition) { success = false - processorManager.logError("Enums cannot be foreign keys. Column: %1s and type: %1s", validatorDefinition.columnName, - validatorDefinition.elementTypeName) + processorManager.logError("Enums cannot be foreign keys. Column: ${validatorDefinition.columnName}" + + " and type: ${validatorDefinition.elementTypeName}") } } if (validatorDefinition is ForeignKeyColumnDefinition) { validatorDefinition.column?.let { - if (it.name.length > 0) { + if (it.name.isNotEmpty()) { success = false - processorManager.logError("Foreign Key %1s cannot specify the column() field. " - + "Use a @ForeignKeyReference(columnName = {NAME} instead. Column: %1s and type: %1s", - validatorDefinition.elementName, validatorDefinition.columnName, - validatorDefinition.elementTypeName) + processorManager.logError("Foreign Key ${validatorDefinition.elementName} cannot specify the column() field. " + + "Use a @ForeignKeyReference(columnName = {NAME} instead. " + + "Column: ${validatorDefinition.columnName} and type: ${validatorDefinition.elementTypeName}") } } @@ -104,9 +103,8 @@ class ColumnValidator : Validator { if (autoIncrementingPrimaryKey == null) { autoIncrementingPrimaryKey = validatorDefinition } else if (autoIncrementingPrimaryKey != validatorDefinition) { - processorManager.logError( - "Only one autoincrementing primary key is allowed on a table. Found Column: %1s and type: %1s", - validatorDefinition.columnName, validatorDefinition.elementTypeName) + processorManager.logError("Only one auto-incrementing primary key is allowed on a table. " + + "Found Column: ${validatorDefinition.columnName} and type: ${validatorDefinition.elementTypeName}") success = false } } @@ -125,8 +123,8 @@ class ContentProviderValidator : Validator { var success = true if (validatorDefinition.endpointDefinitions.isEmpty()) { - processorManager.logError("The content provider %1s must have at least 1 @TableEndpoint associated with it", - validatorDefinition.element.simpleName) + processorManager.logError("The content provider ${validatorDefinition.element.simpleName} " + + "must have at least 1 @TableEndpoint associated with it") success = false } @@ -163,7 +161,8 @@ class TableEndpointValidator : Validator { var success = true if (validatorDefinition.contentUriDefinitions.isEmpty()) { - processorManager.logError("A table endpoint %1s must supply at least one @ContentUri", validatorDefinition.elementClassName) + processorManager.logError("A table endpoint ${validatorDefinition.elementClassName} " + + "must supply at least one @ContentUri") success = false } @@ -180,31 +179,35 @@ class TableValidator : Validator { var success = true if (!validatorDefinition.hasPrimaryConstructor) { - processorManager.logError(TableValidator::class, "Table ${ - validatorDefinition.elementClassName}" + + processorManager.logError(TableValidator::class, "Table ${validatorDefinition.elementClassName}" + " must provide a visible, default constructor.") success = false } if (validatorDefinition.columnDefinitions.isEmpty()) { - processorManager.logError(TableValidator::class, "Table %1s of %1s, %1s needs to define at least one column", validatorDefinition.tableName, - validatorDefinition.elementClassName, validatorDefinition.element.javaClass) + processorManager.logError(TableValidator::class, "Table ${validatorDefinition.tableName} " + + "of ${validatorDefinition.elementClassName}, ${validatorDefinition.element.javaClass} " + + "needs to define at least one column") success = false } - val hasTwoKinds = (validatorDefinition.hasAutoIncrement || validatorDefinition.hasRowID) && !validatorDefinition._primaryColumnDefinitions.isEmpty() + val hasTwoKinds = (validatorDefinition.hasAutoIncrement || validatorDefinition.hasRowID) + && !validatorDefinition._primaryColumnDefinitions.isEmpty() if (hasTwoKinds) { - processorManager.logError(TableValidator::class, "Table %1s cannot mix and match autoincrement and composite primary keys", - validatorDefinition.tableName) + processorManager.logError(TableValidator::class, "Table ${validatorDefinition.tableName}" + + " cannot mix and match autoincrement and composite primary keys") success = false } - val hasPrimary = (validatorDefinition.hasAutoIncrement || validatorDefinition.hasRowID) && validatorDefinition._primaryColumnDefinitions.isEmpty() - || !validatorDefinition.hasAutoIncrement && !validatorDefinition.hasRowID && !validatorDefinition._primaryColumnDefinitions.isEmpty() + val hasPrimary = (validatorDefinition.hasAutoIncrement || validatorDefinition.hasRowID) + && validatorDefinition._primaryColumnDefinitions.isEmpty() + || !validatorDefinition.hasAutoIncrement && !validatorDefinition.hasRowID + && !validatorDefinition._primaryColumnDefinitions.isEmpty() if (!hasPrimary) { - processorManager.logError(TableValidator::class, "Table %1s needs to define at least one primary key", validatorDefinition.tableName) + processorManager.logError(TableValidator::class, "Table ${validatorDefinition.tableName} " + + "needs to define at least one primary key") success = false } @@ -218,14 +221,14 @@ class TypeConverterValidator : Validator { var success = true if (validatorDefinition.modelTypeName == null) { - processorManager.logError("TypeConverter: " + validatorDefinition.className.toString() + - " uses an unsupported Model Element parameter. If it has type parameters, you must remove them or subclass it" + - "for proper usage.") + processorManager.logError("TypeConverter: ${validatorDefinition.className} uses an " + + "unsupported Model Element parameter. If it has type parameters, you must " + + "remove them or subclass it for proper usage.") success = false } else if (validatorDefinition.dbTypeName == null) { - processorManager.logError("TypeConverter: " + validatorDefinition.className.toString() + - " uses an unsupported DB Element parameter. If it has type parameters, you must remove them or subclass it " + - "for proper usage.") + processorManager.logError("TypeConverter: ${validatorDefinition.className} uses an " + + "unsupported DB Element parameter. If it has type parameters, you must remove" + + " them or subclass it for proper usage.") success = false } diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/BaseDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/BaseDefinition.kt index a54f10224..944637507 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/BaseDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/BaseDefinition.kt @@ -1,15 +1,14 @@ package com.raizlabs.android.dbflow.processor.definition +import com.grosner.kpoet.* import com.raizlabs.android.dbflow.processor.ProcessorManager import com.raizlabs.android.dbflow.processor.utils.ElementUtility import com.squareup.javapoet.ClassName import com.squareup.javapoet.ParameterizedTypeName import com.squareup.javapoet.TypeName import com.squareup.javapoet.TypeSpec -import java.util.* import javax.lang.model.element.Element import javax.lang.model.element.ExecutableElement -import javax.lang.model.element.Modifier import javax.lang.model.element.TypeElement import javax.lang.model.type.TypeMirror @@ -39,14 +38,14 @@ abstract class BaseDefinition : TypeDefinition { try { val typeMirror = element.asType() - elementTypeName = TypeName.get(typeMirror) + elementTypeName = typeMirror.typeName elementTypeName?.let { if (!it.isPrimitive) { elementClassName = getElementClassName(element) } } val erasedType = processorManager.typeUtils.erasure(typeMirror) - erasedTypeName = TypeName.get(erasedType) + erasedTypeName = erasedType.typeName } catch (e: Exception) { } @@ -60,16 +59,16 @@ abstract class BaseDefinition : TypeDefinition { val typeMirror: TypeMirror if (element is ExecutableElement) { typeMirror = element.returnType - elementTypeName = TypeName.get(typeMirror) + elementTypeName = typeMirror.typeName } else { typeMirror = element.asType() - elementTypeName = TypeName.get(typeMirror) + elementTypeName = typeMirror.typeName } val erasedType = processorManager.typeUtils.erasure(typeMirror) erasedTypeName = TypeName.get(erasedType) } catch (i: IllegalArgumentException) { - manager.logError("Found illegal type:" + element.asType() + " for " + element.simpleName.toString()) - manager.logError("Exception here:" + i.toString()) + manager.logError("Found illegal type: ${element.asType()} for ${element.simpleName}") + manager.logError("Exception here: $i") } elementName = element.simpleName.toString() @@ -87,7 +86,7 @@ abstract class BaseDefinition : TypeDefinition { this.typeElement = element this.element = element elementClassName = ClassName.get(typeElement) - elementTypeName = TypeName.get(element.asType()) + elementTypeName = element.asType().typeName elementName = element.simpleName.toString() packageName = manager.elements.getPackageOf(element)?.qualifiedName?.toString() ?: "" } @@ -124,16 +123,13 @@ abstract class BaseDefinition : TypeDefinition { override val typeSpec: TypeSpec get() { - val typeBuilder = TypeSpec.classBuilder(outputClassName?.simpleName()) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addSuperinterfaces(Arrays.asList(*implementsClasses)) - val extendsClass = extendsClass - if (extendsClass != null) { - typeBuilder.superclass(extendsClass) + return `public final class`(outputClassName?.simpleName() ?: "") { + extendsClass?.let { extends(it) } + implementsClasses.forEach { implements(it) } + javadoc("This is generated code. Please do not modify") + onWriteDefinition(this) + this } - typeBuilder.addJavadoc("This is generated code. Please do not modify") - onWriteDefinition(typeBuilder) - return typeBuilder.build() } protected open val extendsClass: TypeName? diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/BaseTableDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/BaseTableDefinition.kt index 003a635f8..5c62aaede 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/BaseTableDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/BaseTableDefinition.kt @@ -1,13 +1,13 @@ package com.raizlabs.android.dbflow.processor.definition import com.google.common.collect.Lists +import com.grosner.kpoet.* +import com.raizlabs.android.dbflow.processor.ClassNames import com.raizlabs.android.dbflow.processor.ProcessorManager import com.raizlabs.android.dbflow.processor.definition.column.ColumnDefinition import com.raizlabs.android.dbflow.processor.definition.column.ForeignKeyColumnDefinition import com.raizlabs.android.dbflow.processor.definition.column.PackagePrivateScopeColumnAccessor -import com.raizlabs.android.dbflow.processor.utils.ElementUtility -import com.raizlabs.android.dbflow.processor.utils.ModelUtils -import com.raizlabs.android.dbflow.processor.utils.capitalizeFirstLetter +import com.raizlabs.android.dbflow.processor.utils.* import com.squareup.javapoet.* import java.io.IOException import java.util.* @@ -34,18 +34,20 @@ abstract class BaseTableDefinition(typeElement: Element, processorManager: Proce var associatedTypeConverters: MutableMap> = HashMap() var globalTypeConverters: MutableMap> = HashMap() val packagePrivateList: MutableList = - Lists.newArrayList() + Lists.newArrayList() var orderedCursorLookUp: Boolean = false var assignDefaultValuesFromCursor = true var classElementLookUpMap: MutableMap = mutableMapOf() - val modelClassName: String + val modelClassName = typeElement.simpleName.toString() var databaseDefinition: DatabaseDefinition? = null + val hasGlobalTypeConverters + get() = globalTypeConverters.isNotEmpty() + init { - this.modelClassName = typeElement.simpleName.toString() columnDefinitions = ArrayList() } @@ -80,63 +82,76 @@ abstract class BaseTableDefinition(typeElement: Element, processorManager: Proce return "global_typeConverter" + typeConverterName.simpleName() } + fun writeConstructor(typeBuilder: TypeSpec.Builder) { + typeBuilder.apply { + val customTypeConverterPropertyMethod = CustomTypeConverterPropertyMethod(this@BaseTableDefinition) + customTypeConverterPropertyMethod.addToType(this) + + constructor { + if (hasGlobalTypeConverters) { + addParameter(param(ClassNames.DATABASE_HOLDER, "holder").build()) + } + addParameter(param(ClassNames.BASE_DATABASE_DEFINITION_CLASSNAME, "databaseDefinition").build()) + modifiers(public) + statement("super(databaseDefinition)") + code { + customTypeConverterPropertyMethod.addCode(this) + } + } + } + } + + fun writeGetModelClass(typeBuilder: TypeSpec.Builder, modelClassName: ClassName?) = typeBuilder.apply { + `override fun`(ParameterizedTypeName.get(ClassName.get(Class::class.java), modelClassName), "getModelClass") { + modifiers(public, final) + `return`("\$T.class", modelClassName) + } + } @Throws(IOException::class) fun writePackageHelper(processingEnvironment: ProcessingEnvironment) { var count = 0 if (!packagePrivateList.isEmpty()) { - val typeBuilder = TypeSpec.classBuilder(elementClassName?.simpleName() + - databaseDefinition?.classSeparator + "Helper") - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + val classSeparator = databaseDefinition?.classSeparator + val typeBuilder = TypeSpec.classBuilder("${elementClassName?.simpleName()}${classSeparator}Helper") + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) for (columnDefinition in packagePrivateList) { - var helperClassName = manager.elements.getPackageOf(columnDefinition.element).toString() + - "." + ClassName.get(columnDefinition.element.enclosingElement as TypeElement).simpleName() + - databaseDefinition?.classSeparator + "Helper" + var helperClassName = "${columnDefinition.element.getPackage()}.${columnDefinition.element.enclosingElement.toClassName().simpleName()}${classSeparator}Helper" if (columnDefinition is ForeignKeyColumnDefinition) { val tableDefinition: TableDefinition? = databaseDefinition?.objectHolder?.tableDefinitionMap?.get(columnDefinition.referencedTableClassName as TypeName) if (tableDefinition != null) { - helperClassName = manager.elements.getPackageOf(tableDefinition.element).toString() + - "." + ClassName.get(tableDefinition.element as TypeElement).simpleName() + - databaseDefinition?.classSeparator + "Helper" + helperClassName = "${tableDefinition.element.getPackage()}.${ClassName.get(tableDefinition.element as TypeElement).simpleName()}${classSeparator}Helper" } } val className = ElementUtility.getClassName(helperClassName, manager) if (className != null && PackagePrivateScopeColumnAccessor.containsColumn(className, columnDefinition.columnName)) { - - var method: MethodSpec.Builder = MethodSpec.methodBuilder("get" + columnDefinition.columnName.capitalizeFirstLetter()) - .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) - .addParameter(elementTypeName, ModelUtils.variable) - .returns(columnDefinition.elementTypeName) - val samePackage = ElementUtility.isInSamePackage(manager, columnDefinition.element, this.element) - - if (samePackage) { - method.addStatement("return \$L.\$L", ModelUtils.variable, columnDefinition.elementName) - } else { - method.addStatement("return \$T.get\$L(\$L)", className, - columnDefinition.columnName.capitalizeFirstLetter(), - ModelUtils.variable) + typeBuilder.apply { + val samePackage = ElementUtility.isInSamePackage(manager, columnDefinition.element, this@BaseTableDefinition.element) + val methodName = columnDefinition.columnName.capitalize() + + `public static final`(columnDefinition.elementTypeName!!, "get$methodName", + param(elementTypeName!!, ModelUtils.variable)) { + if (samePackage) { + `return`("${ModelUtils.variable}.${columnDefinition.elementName}") + } else { + `return`("\$T.get$methodName(${ModelUtils.variable})", className) + } + } + + `public static final`(TypeName.VOID, "set$methodName", + param(elementTypeName!!, ModelUtils.variable), + param(columnDefinition.elementTypeName!!, "var")) { + if (samePackage) { + statement("${ModelUtils.variable}.${columnDefinition.elementName} = var") + } else { + statement("\$T.set$methodName(${ModelUtils.variable}, var") + } + } } - typeBuilder.addMethod(method.build()) - - method = MethodSpec.methodBuilder("set" + columnDefinition.columnName.capitalizeFirstLetter()) - .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) - .addParameter(elementTypeName, ModelUtils.variable) - .addParameter(columnDefinition.elementTypeName, "var") - - if (samePackage) { - method.addStatement("\$L.\$L = \$L", ModelUtils.variable, - columnDefinition.elementName, "var") - } else { - - method.addStatement("\$T.set\$L(\$L, \$L)", className, - columnDefinition.columnName.capitalizeFirstLetter(), - ModelUtils.variable, "var") - } - typeBuilder.addMethod(method.build()) count++ } else if (className == null) { manager.logError(BaseTableDefinition::class, "Could not find classname for:" + helperClassName) diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/ContentProvider.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/ContentProvider.kt index 4190603cd..3e543ddb5 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/ContentProvider.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/ContentProvider.kt @@ -10,6 +10,7 @@ import com.raizlabs.android.dbflow.processor.ClassNames import com.raizlabs.android.dbflow.processor.ProcessorManager import com.raizlabs.android.dbflow.processor.TableEndpointValidator import com.raizlabs.android.dbflow.processor.utils.`override fun` +import com.raizlabs.android.dbflow.processor.utils.annotation import com.raizlabs.android.dbflow.processor.utils.controlFlow import com.raizlabs.android.dbflow.processor.utils.isNullOrEmpty import com.squareup.javapoet.* @@ -18,9 +19,9 @@ import javax.lang.model.type.MirroredTypeException internal fun appendDefault(code: CodeBlock.Builder) { code.beginControlFlow("default:") - .addStatement("throw new \$T(\$S + \$L)", - ClassName.get(IllegalArgumentException::class.java), "Unknown URI", Constants.PARAM_URI) - .endControlFlow() + .addStatement("throw new \$T(\$S + \$L)", + ClassName.get(IllegalArgumentException::class.java), "Unknown URI", Constants.PARAM_URI) + .endControlFlow() } object Constants { @@ -33,25 +34,24 @@ object Constants { * Get any code needed to use path segments. This should be called before creating the statement that uses * [.getSelectionAndSelectionArgs]. */ -internal fun ContentUriDefinition.getSegmentsPreparation(): CodeBlock { - if (segments.size == 0) { - return CodeBlock.builder().build() - } else { - return CodeBlock.builder().addStatement("\$T<\$T> segments = uri.getPathSegments()", - List::class.java, String::class.java).build() +internal fun ContentUriDefinition.getSegmentsPreparation() = code { + if (segments.isNotEmpty()) { + statement("\$T segments = uri.getPathSegments()", + parameterized(List::class)) } + this } /** * Get code which creates the `selection` and `selectionArgs` parameters separated by a comma. */ internal fun ContentUriDefinition.getSelectionAndSelectionArgs(): CodeBlock { - if (segments.size == 0) { + if (segments.isEmpty()) { return CodeBlock.builder().add("selection, selectionArgs").build() } else { val selectionBuilder = CodeBlock.builder().add("\$T.concatenateWhere(selection, \"", ClassNames.DATABASE_UTILS) val selectionArgsBuilder = CodeBlock.builder().add("\$T.appendSelectionArgs(selectionArgs, new \$T[] {", - ClassNames.DATABASE_UTILS, String::class.java) + ClassNames.DATABASE_UTILS, String::class.java) var isFirst = true for (segment in segments) { if (!isFirst) { @@ -84,21 +84,21 @@ class DeleteMethod(private val contentProviderDefinition: ContentProviderDefinit contentProviderDefinition.endpointDefinitions.forEach { it.contentUriDefinitions.forEach { uriDefinition -> if (uriDefinition.deleteEnabled) { - - code.beginControlFlow("case \$L:", uriDefinition.name) - - code.add(uriDefinition.getSegmentsPreparation()) - code.add("long count = \$T.getDatabase(\$S).getWritableDatabase().delete(\$S, ", - ClassNames.FLOW_MANAGER, - manager.getDatabaseName(contentProviderDefinition.databaseName), - it.tableName) - code.add(uriDefinition.getSelectionAndSelectionArgs()) - code.add(");\n") - - NotifyMethod(it, uriDefinition, Notify.Method.DELETE).addCode(code) - - code.addStatement("return (int) count") - code.endControlFlow() + code.apply { + case(uriDefinition.name.L) { + add(uriDefinition.getSegmentsPreparation()) + add("long count = \$T.getDatabase(\$S).getWritableDatabase().delete(\$S, ", + ClassNames.FLOW_MANAGER, + manager.getDatabaseName(contentProviderDefinition.databaseName), + it.tableName) + add(uriDefinition.getSelectionAndSelectionArgs()) + add(");\n") + + NotifyMethod(it, uriDefinition, Notify.Method.DELETE).addCode(this) + + `return`("(int) count") + } + } } } } @@ -107,12 +107,12 @@ class DeleteMethod(private val contentProviderDefinition: ContentProviderDefinit code.endControlFlow() return MethodSpec.methodBuilder("delete") - .addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addParameter(ClassNames.URI, PARAM_URI) - .addParameter(ClassName.get(String::class.java), PARAM_SELECTION) - .addParameter(ArrayTypeName.of(String::class.java), PARAM_SELECTION_ARGS) - .addCode(code.build()).returns(TypeName.INT).build() + .addAnnotation(Override::class.java) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addParameter(ClassNames.URI, PARAM_URI) + .addParameter(ClassName.get(String::class.java), PARAM_SELECTION) + .addParameter(ArrayTypeName.of(String::class.java), PARAM_SELECTION_ARGS) + .addCode(code.build()).returns(TypeName.INT).build() } companion object { @@ -141,12 +141,12 @@ class InsertMethod(private val contentProviderDefinition: ContentProviderDefinit code.apply { beginControlFlow("case \$L:", uriDefinition.name) addStatement("\$T adapter = \$T.getModelAdapter(\$T.getTableClassForName(\$S, \$S))", - ClassNames.MODEL_ADAPTER, ClassNames.FLOW_MANAGER, ClassNames.FLOW_MANAGER, - contentProviderDefinition.databaseNameString, it.tableName) + ClassNames.MODEL_ADAPTER, ClassNames.FLOW_MANAGER, ClassNames.FLOW_MANAGER, + contentProviderDefinition.databaseNameString, it.tableName) add("final long id = FlowManager.getDatabase(\$S).getWritableDatabase()", - contentProviderDefinition.databaseNameString).add(".insertWithOnConflict(\$S, null, values, " + "\$T.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction()));\n", it.tableName, - ClassNames.CONFLICT_ACTION) + contentProviderDefinition.databaseNameString).add(".insertWithOnConflict(\$S, null, values, " + "\$T.getSQLiteDatabaseAlgorithmInt(adapter.getInsertOnConflictAction()));\n", it.tableName, + ClassNames.CONFLICT_ACTION) NotifyMethod(it, uriDefinition, Notify.Method.INSERT).addCode(this) @@ -164,10 +164,10 @@ class InsertMethod(private val contentProviderDefinition: ContentProviderDefinit appendDefault(code) code.endControlFlow() return MethodSpec.methodBuilder(if (isBulk) "bulkInsert" else "insert") - .addAnnotation(Override::class.java).addParameter(ClassNames.URI, Constants.PARAM_URI) - .addParameter(ClassNames.CONTENT_VALUES, Constants.PARAM_CONTENT_VALUES) - .addModifiers(if (isBulk) Modifier.PROTECTED else Modifier.PUBLIC, Modifier.FINAL) - .addCode(code.build()).returns(if (isBulk) TypeName.INT else ClassNames.URI).build() + .addAnnotation(Override::class.java).addParameter(ClassNames.URI, Constants.PARAM_URI) + .addParameter(ClassNames.CONTENT_VALUES, Constants.PARAM_CONTENT_VALUES) + .addModifiers(if (isBulk) Modifier.PROTECTED else Modifier.PUBLIC, Modifier.FINAL) + .addCode(code.build()).returns(if (isBulk) TypeName.INT else ClassNames.URI).build() } } @@ -188,16 +188,16 @@ class NotifyMethod(private val tableEndpointDefinition: TableEndpointDefinition, val notifyDefinition = notifyDefinitionList[i] if (notifyDefinition.returnsArray) { code.addStatement("\$T[] notifyUris\$L = \$L.\$L(\$L)", ClassNames.URI, - notifyDefinition.methodName, notifyDefinition.parent, - notifyDefinition.methodName, notifyDefinition.params) + notifyDefinition.methodName, notifyDefinition.parent, + notifyDefinition.methodName, notifyDefinition.params) code.beginControlFlow("for (\$T notifyUri: notifyUris\$L)", ClassNames.URI, notifyDefinition.methodName) } else { code.addStatement("\$T notifyUri\$L = \$L.\$L(\$L)", ClassNames.URI, - notifyDefinition.methodName, notifyDefinition.parent, - notifyDefinition.methodName, notifyDefinition.params) + notifyDefinition.methodName, notifyDefinition.parent, + notifyDefinition.methodName, notifyDefinition.params) } code.addStatement("getContext().getContentResolver().notifyChange(notifyUri\$L, null)", - if (notifyDefinition.returnsArray) "" else notifyDefinition.methodName) + if (notifyDefinition.returnsArray) "" else notifyDefinition.methodName) if (notifyDefinition.returnsArray) { code.endControlFlow() } @@ -233,14 +233,14 @@ class QueryMethod(private val contentProviderDefinition: ContentProviderDefiniti override val methodSpec: MethodSpec? get() { val method = MethodSpec.methodBuilder("query") - .addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addParameter(ClassNames.URI, "uri") - .addParameter(ArrayTypeName.of(String::class.java), "projection") - .addParameter(ClassName.get(String::class.java), "selection") - .addParameter(ArrayTypeName.of(String::class.java), "selectionArgs") - .addParameter(ClassName.get(String::class.java), "sortOrder") - .returns(ClassNames.CURSOR) + .addAnnotation(Override::class.java) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addParameter(ClassNames.URI, "uri") + .addParameter(ArrayTypeName.of(String::class.java), "projection") + .addParameter(ClassName.get(String::class.java), "selection") + .addParameter(ArrayTypeName.of(String::class.java), "selectionArgs") + .addParameter(ClassName.get(String::class.java), "sortOrder") + .returns(ClassNames.CURSOR) method.addStatement("\$L cursor = null", ClassNames.CURSOR) method.beginControlFlow("switch(\$L.match(uri))", ContentProviderDefinition.URI_MATCHER) @@ -251,9 +251,9 @@ class QueryMethod(private val contentProviderDefinition: ContentProviderDefiniti beginControlFlow("case \$L:", uriDefinition.name) addCode(uriDefinition.getSegmentsPreparation()) addCode("cursor = \$T.getDatabase(\$S).getWritableDatabase().query(\$S, projection, ", - ClassNames.FLOW_MANAGER, - manager.getDatabaseName(contentProviderDefinition.databaseName), - tableEndpointDefinition.tableName) + ClassNames.FLOW_MANAGER, + manager.getDatabaseName(contentProviderDefinition.databaseName), + tableEndpointDefinition.tableName) addCode(uriDefinition.getSelectionAndSelectionArgs()) addCode(", null, null, sortOrder);\n") addStatement("break") @@ -282,13 +282,13 @@ class UpdateMethod(private val contentProviderDefinition: ContentProviderDefinit override val methodSpec: MethodSpec? get() { val method = MethodSpec.methodBuilder("update") - .addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC) - .addParameter(ClassNames.URI, Constants.PARAM_URI) - .addParameter(ClassNames.CONTENT_VALUES, Constants.PARAM_CONTENT_VALUES) - .addParameter(ClassName.get(String::class.java), "selection") - .addParameter(ArrayTypeName.of(String::class.java), "selectionArgs") - .returns(TypeName.INT) + .addAnnotation(Override::class.java) + .addModifiers(Modifier.PUBLIC) + .addParameter(ClassNames.URI, Constants.PARAM_URI) + .addParameter(ClassNames.CONTENT_VALUES, Constants.PARAM_CONTENT_VALUES) + .addParameter(ClassName.get(String::class.java), "selection") + .addParameter(ArrayTypeName.of(String::class.java), "selectionArgs") + .returns(TypeName.INT) method.beginControlFlow("switch(MATCHER.match(\$L))", Constants.PARAM_URI) for (tableEndpointDefinition in contentProviderDefinition.endpointDefinitions) { @@ -297,24 +297,24 @@ class UpdateMethod(private val contentProviderDefinition: ContentProviderDefinit method.apply { beginControlFlow("case \$L:", uriDefinition.name) addStatement("\$T adapter = \$T.getModelAdapter(\$T.getTableClassForName(\$S, \$S))", - ClassNames.MODEL_ADAPTER, ClassNames.FLOW_MANAGER, ClassNames.FLOW_MANAGER, - contentProviderDefinition.databaseNameString, - tableEndpointDefinition.tableName) + ClassNames.MODEL_ADAPTER, ClassNames.FLOW_MANAGER, ClassNames.FLOW_MANAGER, + contentProviderDefinition.databaseNameString, + tableEndpointDefinition.tableName) addCode(uriDefinition.getSegmentsPreparation()) addCode( - "long count = \$T.getDatabase(\$S).getWritableDatabase().updateWithOnConflict(\$S, \$L, ", - ClassNames.FLOW_MANAGER, - manager.getDatabaseName(contentProviderDefinition.databaseName), - tableEndpointDefinition.tableName, - Constants.PARAM_CONTENT_VALUES) + "long count = \$T.getDatabase(\$S).getWritableDatabase().updateWithOnConflict(\$S, \$L, ", + ClassNames.FLOW_MANAGER, + manager.getDatabaseName(contentProviderDefinition.databaseName), + tableEndpointDefinition.tableName, + Constants.PARAM_CONTENT_VALUES) addCode(uriDefinition.getSelectionAndSelectionArgs()) addCode( - ", \$T.getSQLiteDatabaseAlgorithmInt(adapter.getUpdateOnConflictAction()));\n", - ClassNames.CONFLICT_ACTION) + ", \$T.getSQLiteDatabaseAlgorithmInt(adapter.getUpdateOnConflictAction()));\n", + ClassNames.CONFLICT_ACTION) val code = CodeBlock.builder() NotifyMethod(tableEndpointDefinition, uriDefinition, - Notify.Method.UPDATE).addCode(code) + Notify.Method.UPDATE).addCode(code) addCode(code.build()) addStatement("return (int) count") @@ -349,14 +349,14 @@ class ContentProviderDefinition(typeElement: Element, processorManager: Processo var endpointDefinitions: MutableList = Lists.newArrayList() private val methods: Array = arrayOf(QueryMethod(this, manager), - InsertMethod(this, false), - InsertMethod(this, true), - DeleteMethod(this, manager), - UpdateMethod(this, manager)) + InsertMethod(this, false), + InsertMethod(this, true), + DeleteMethod(this, manager), + UpdateMethod(this, manager)) init { - val provider = element.getAnnotation(ContentProvider::class.java) + val provider = element.annotation() if (provider != null) { try { provider.database @@ -406,10 +406,10 @@ class ContentProviderDefinition(typeElement: Element, processorManager: Processo `override fun`(TypeName.BOOLEAN, "onCreate") { modifiers(public, final) addStatement("final \$T $AUTHORITY = \$L", String::class.java, - if (authority.contains("R.string.")) - "getContext().getString($authority)" - else - "\"$authority\"") + if (authority.contains("R.string.")) + "getContext().getString($authority)" + else + "\"$authority\"") for (endpointDefinition in endpointDefinitions) { endpointDefinition.contentUriDefinitions.forEach { @@ -418,7 +418,7 @@ class ContentProviderDefinition(typeElement: Element, processorManager: Processo path = "\"" + it.path + "\"" } else { path = CodeBlock.builder().add("\$L.\$L.getPath()", it.elementClassName, - it.name).build().toString() + it.name).build().toString() } addStatement("\$L.addURI(\$L, \$L, \$L)", URI_MATCHER, AUTHORITY, path, it.name) } @@ -438,12 +438,12 @@ class ContentProviderDefinition(typeElement: Element, processorManager: Processo statement("\$T type = null", ClassName.get(String::class.java)) controlFlow("switch(\$L.match(uri))", URI_MATCHER) { endpointDefinitions.flatMap { it.contentUriDefinitions } - .forEach { uri -> - controlFlow("case \$L:", uri.name) { - statement("type = \$S", uri.type) - `break`() + .forEach { uri -> + controlFlow("case \$L:", uri.name) { + statement("type = \$S", uri.type) + `break`() + } } - } appendDefault(this) } `return`("type") @@ -452,7 +452,7 @@ class ContentProviderDefinition(typeElement: Element, processorManager: Processo } methods.mapNotNull { it.methodSpec } - .forEach { typeBuilder.addMethod(it) } + .forEach { typeBuilder.addMethod(it) } } companion object { @@ -470,30 +470,32 @@ class ContentUriDefinition(typeElement: Element, processorManager: ProcessorMana var name = typeElement.enclosingElement.simpleName.toString() + "_" + typeElement.simpleName.toString() - var path: String + var path = "" + + var type = "" - var type: String + var queryEnabled = false - var queryEnabled: Boolean = false + var insertEnabled = false - var insertEnabled: Boolean = false + var deleteEnabled = false - var deleteEnabled: Boolean = false - var updateEnabled: Boolean = false + var updateEnabled = false - var segments: Array + var segments = arrayOf() init { - val contentUri = typeElement.getAnnotation(ContentUri::class.java) - path = contentUri.path - type = contentUri.type - queryEnabled = contentUri.queryEnabled - insertEnabled = contentUri.insertEnabled - deleteEnabled = contentUri.deleteEnabled - updateEnabled = contentUri.updateEnabled - - segments = contentUri.segments + typeElement.annotation()?.let { contentUri -> + path = contentUri.path + type = contentUri.type + queryEnabled = contentUri.queryEnabled + insertEnabled = contentUri.insertEnabled + deleteEnabled = contentUri.deleteEnabled + updateEnabled = contentUri.updateEnabled + + segments = contentUri.segments + } if (typeElement is VariableElement) { if (ClassNames.URI != elementTypeName) { diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/DatabaseDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/DatabaseDefinition.kt index 1c35410cb..7f305cbe4 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/DatabaseDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/DatabaseDefinition.kt @@ -3,8 +3,12 @@ package com.raizlabs.android.dbflow.processor.definition import com.grosner.kpoet.* import com.raizlabs.android.dbflow.annotation.ConflictAction import com.raizlabs.android.dbflow.annotation.Database -import com.raizlabs.android.dbflow.processor.* +import com.raizlabs.android.dbflow.processor.ClassNames +import com.raizlabs.android.dbflow.processor.ModelViewValidator +import com.raizlabs.android.dbflow.processor.ProcessorManager +import com.raizlabs.android.dbflow.processor.TableValidator import com.raizlabs.android.dbflow.processor.utils.`override fun` +import com.raizlabs.android.dbflow.processor.utils.annotation import com.raizlabs.android.dbflow.processor.utils.isNullOrEmpty import com.squareup.javapoet.* import java.util.* @@ -43,8 +47,7 @@ class DatabaseDefinition(manager: ProcessorManager, element: Element) : BaseDefi init { packageName = ClassNames.FLOW_MANAGER_PACKAGE - val database = element.getAnnotation(Database::class.java) - if (database != null) { + element.annotation()?.let { database -> databaseName = database.name databaseExtensionName = database.databaseExtension if (databaseName.isNullOrEmpty()) { @@ -86,41 +89,27 @@ class DatabaseDefinition(manager: ProcessorManager, element: Element) : BaseDefi private fun validateDefinitions() { elementClassName?.let { - // TODO: validate them here before preparing them val map = HashMap() val tableValidator = TableValidator() - for (tableDefinition in manager.getTableDefinitions(it)) { - if (tableValidator.validate(ProcessorManager.manager, tableDefinition)) { - tableDefinition.elementClassName?.let { className -> map.put(className, tableDefinition) } - } - } + manager.getTableDefinitions(it) + .filter { tableValidator.validate(ProcessorManager.manager, it) } + .forEach { it.elementClassName?.let { className -> map.put(className, it) } } manager.setTableDefinitions(map, it) val modelViewDefinitionMap = HashMap() val modelViewValidator = ModelViewValidator() - for (modelViewDefinition in manager.getModelViewDefinitions(it)) { - if (modelViewValidator.validate(ProcessorManager.manager, modelViewDefinition)) { - modelViewDefinition.elementClassName?.let { className -> modelViewDefinitionMap.put(className, modelViewDefinition) } - } - } + manager.getModelViewDefinitions(it) + .filter { modelViewValidator.validate(ProcessorManager.manager, it) } + .forEach { it.elementClassName?.let { className -> modelViewDefinitionMap.put(className, it) } } manager.setModelViewDefinitions(modelViewDefinitionMap, it) } - } private fun prepareDefinitions() { elementClassName?.let { - for (tableDefinition in manager.getTableDefinitions(it)) { - tableDefinition.prepareForWrite() - } - - for (modelViewDefinition in manager.getModelViewDefinitions(it)) { - modelViewDefinition.prepareForWrite() - } - - for (queryModelDefinition in manager.getQueryModelDefinitions(it)) { - queryModelDefinition.prepareForWrite() - } + manager.getTableDefinitions(it).forEach(TableDefinition::prepareForWrite) + manager.getModelViewDefinitions(it).forEach(ModelViewDefinition::prepareForWrite) + manager.getQueryModelDefinitions(it).forEach(QueryModelDefinition::prepareForWrite) } } @@ -128,25 +117,29 @@ class DatabaseDefinition(manager: ProcessorManager, element: Element) : BaseDefi builder.constructor(param(ClassNames.DATABASE_HOLDER, "holder")) { modifiers(public) - val elementClassName = this@DatabaseDefinition.elementClassName - if (elementClassName != null) { - for (tableDefinition in manager.getTableDefinitions(elementClassName)) { - statement("holder.putDatabaseForTable(\$T.class, this)", tableDefinition.elementClassName) - statement("\$L.put(\$S, \$T.class)", DatabaseHandler.MODEL_NAME_MAP, tableDefinition.tableName, tableDefinition.elementClassName) - statement("\$L.put(\$T.class, new \$T(holder, this))", DatabaseHandler.MODEL_ADAPTER_MAP_FIELD_NAME, - tableDefinition.elementClassName, tableDefinition.outputClassName) + this@DatabaseDefinition.elementClassName?.let { elementClassName -> + for (definition in manager.getTableDefinitions(elementClassName)) { + if (definition.hasGlobalTypeConverters) { + statement("addModelAdapter(new \$T(holder, this), holder)", definition.outputClassName) + } else { + statement("addModelAdapter(new \$T(this), holder)", definition.outputClassName) + } } - for (modelViewDefinition in manager.getModelViewDefinitions(elementClassName)) { - statement("holder.putDatabaseForTable(\$T.class, this)", modelViewDefinition.elementClassName) - statement("\$L.put(\$T.class, new \$T(holder, this))", DatabaseHandler.MODEL_VIEW_ADAPTER_MAP_FIELD_NAME, - modelViewDefinition.elementClassName, modelViewDefinition.outputClassName) + for (definition in manager.getModelViewDefinitions(elementClassName)) { + if (definition.hasGlobalTypeConverters) { + statement("addModelViewAdapter(new \$T(holder, this), holder)", definition.outputClassName) + } else { + statement("addModelViewAdapter(new \$T(this), holder)", definition.outputClassName) + } } - for (queryModelDefinition in manager.getQueryModelDefinitions(elementClassName)) { - statement("holder.putDatabaseForTable(\$T.class, this)", queryModelDefinition.elementClassName) - statement("\$L.put(\$T.class, new \$T(holder, this))", DatabaseHandler.QUERY_MODEL_ADAPTER_MAP_FIELD_NAME, - queryModelDefinition.elementClassName, queryModelDefinition.outputClassName) + for (definition in manager.getQueryModelDefinitions(elementClassName)) { + if (definition.hasGlobalTypeConverters) { + statement("addQueryModelAdapter(new \$T(holder, this), holder)", definition.outputClassName) + } else { + statement("addQueryModelAdapter(new \$T(this), holder)", definition.outputClassName) + } } val migrationDefinitionMap = manager.getMigrationsForDatabase(elementClassName) @@ -157,13 +150,8 @@ class DatabaseDefinition(manager: ProcessorManager, element: Element) : BaseDefi val migrationDefinitions = migrationDefinitionMap[version] migrationDefinitions?.let { Collections.sort(migrationDefinitions, { o1, o2 -> Integer.valueOf(o2.priority)!!.compareTo(o1.priority) }) - statement("\$T migrations\$L = new \$T()", ParameterizedTypeName.get(ClassName.get(List::class.java), ClassNames.MIGRATION), - version, ParameterizedTypeName.get(ClassName.get(ArrayList::class.java), ClassNames.MIGRATION)) - statement("\$L.put(\$L, migrations\$L)", DatabaseHandler.MIGRATION_FIELD_NAME, - version, version) for (migrationDefinition in migrationDefinitions) { - statement("migrations\$L.add(new \$T\$L)", version, migrationDefinition.elementClassName, - migrationDefinition.constructorName) + statement("addMigration($version, new \$T${migrationDefinition.constructorName})", migrationDefinition.elementClassName) } } } diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/DatabaseHolderDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/DatabaseHolderDefinition.kt index 232a721ab..fb55ff3f8 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/DatabaseHolderDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/DatabaseHolderDefinition.kt @@ -1,11 +1,10 @@ package com.raizlabs.android.dbflow.processor.definition +import com.grosner.kpoet.* import com.raizlabs.android.dbflow.processor.ClassNames import com.raizlabs.android.dbflow.processor.DatabaseHandler import com.raizlabs.android.dbflow.processor.ProcessorManager -import com.squareup.javapoet.MethodSpec import com.squareup.javapoet.TypeSpec -import javax.lang.model.element.Modifier /** * Description: Top-level writer that handles writing all [DatabaseDefinition] @@ -18,7 +17,6 @@ class DatabaseHolderDefinition(private val processorManager: ProcessorManager) : init { val options = this.processorManager.processingEnvironment.options - if (options.containsKey(OPTION_TARGET_MODULE_NAME)) { className = options[OPTION_TARGET_MODULE_NAME] ?: "" } @@ -26,41 +24,36 @@ class DatabaseHolderDefinition(private val processorManager: ProcessorManager) : className += ClassNames.DATABASE_HOLDER_STATIC_CLASS_NAME } - override val typeSpec: TypeSpec - get() { - val typeBuilder = TypeSpec.classBuilder(this.className) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .superclass(ClassNames.DATABASE_HOLDER) + override val typeSpec: TypeSpec = `public final class`(this.className) { + extends(ClassNames.DATABASE_HOLDER) - val constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC) + constructor { + modifiers(public) - processorManager.getTypeConverters().forEach { - constructor.addStatement("\$L.put(\$T.class, new \$T())", - DatabaseHandler.TYPE_CONVERTER_MAP_FIELD_NAME, it.modelTypeName, it.className) + processorManager.getTypeConverters().forEach { tc -> + statement("\$L.put(\$T.class, new \$T())", + DatabaseHandler.TYPE_CONVERTER_MAP_FIELD_NAME, tc.modelTypeName, tc.className) - if (it.allowedSubTypes?.isNotEmpty() ?: false) { - it.allowedSubTypes?.forEach { subType -> - constructor.addStatement("\$L.put(\$T.class, new \$T())", - DatabaseHandler.TYPE_CONVERTER_MAP_FIELD_NAME, subType, it.className) - } + tc.allowedSubTypes?.forEach { subType -> + statement("\$L.put(\$T.class, new \$T())", + DatabaseHandler.TYPE_CONVERTER_MAP_FIELD_NAME, subType, tc.className) } } - processorManager.getDatabaseHolderDefinitionList().sortedBy { it.databaseDefinition?.outputClassName?.simpleName() }.forEach { databaseDefinition -> - databaseDefinition.databaseDefinition?.let { constructor.addStatement("new \$T(this)", it.outputClassName) } - } - - typeBuilder.addMethod(constructor.build()) - - - return typeBuilder.build() + processorManager.getDatabaseHolderDefinitionList() + .mapNotNull { it.databaseDefinition?.outputClassName } + .sortedBy { it.simpleName() } + .forEach { statement("new \$T(this)", it) } + this } - - fun isGarbage(): Boolean { - val filter = processorManager.getDatabaseHolderDefinitionList().filter { it.databaseDefinition?.outputClassName != null } - return filter.isEmpty() } + /** + * If none of the database holder databases exist, don't generate a holder. + */ + fun isGarbage() = processorManager.getDatabaseHolderDefinitionList() + .filter { it.databaseDefinition?.outputClassName != null }.isEmpty() + companion object { @JvmField diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/IndexGroupsDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/IndexGroupsDefinition.kt index d486181aa..903523516 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/IndexGroupsDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/IndexGroupsDefinition.kt @@ -1,49 +1,38 @@ package com.raizlabs.android.dbflow.processor.definition +import com.grosner.kpoet.* import com.raizlabs.android.dbflow.annotation.IndexGroup import com.raizlabs.android.dbflow.processor.ClassNames import com.raizlabs.android.dbflow.processor.definition.column.ColumnDefinition -import com.squareup.javapoet.CodeBlock -import com.squareup.javapoet.FieldSpec import com.squareup.javapoet.ParameterizedTypeName import java.util.* import java.util.concurrent.atomic.AtomicInteger -import javax.lang.model.element.Modifier /** * Description: */ class IndexGroupsDefinition(private val tableDefinition: TableDefinition, indexGroup: IndexGroup) { - val indexName: String - val indexNumber: Int - val isUnique: Boolean + val indexName = indexGroup.name + val indexNumber = indexGroup.number + val isUnique = indexGroup.unique val columnDefinitionList: MutableList = ArrayList() - init { - this.indexName = indexGroup.name - this.indexNumber = indexGroup.number - this.isUnique = indexGroup.unique - } - - val fieldSpec: FieldSpec - get() { - val fieldBuilder = FieldSpec.builder(ParameterizedTypeName.get(ClassNames.INDEX_PROPERTY, tableDefinition.elementClassName), - "index_" + indexName, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) - val initializer = CodeBlock.builder().add("new \$T<>(\$S, \$L, \$T.class", ClassNames.INDEX_PROPERTY, - indexName, isUnique, tableDefinition.elementTypeName) + val fieldSpec = field(ParameterizedTypeName.get(ClassNames.INDEX_PROPERTY, tableDefinition.elementClassName), + "index_$indexName") { + addModifiers(public, static, final) + `=` { + add("new \$T<>(${indexName.S}, $isUnique, \$T.class", + ClassNames.INDEX_PROPERTY, tableDefinition.elementTypeName) if (columnDefinitionList.isNotEmpty()) { - initializer.add(",") + add(",") } val index = AtomicInteger(0) - columnDefinitionList.forEach { it.appendIndexInitializer(initializer, index) } - initializer.add(")") - - fieldBuilder.initializer(initializer.build()) - - return fieldBuilder.build() + columnDefinitionList.forEach { it.appendIndexInitializer(this, index) } + add(")") } + }.build()!! } 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 deleted file mode 100644 index 5df837639..000000000 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/InternalAdapterHelper.kt +++ /dev/null @@ -1,102 +0,0 @@ -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/ManyToManyDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/ManyToManyDefinition.kt index be541a3c5..256c1d112 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/ManyToManyDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/ManyToManyDefinition.kt @@ -7,7 +7,7 @@ import com.raizlabs.android.dbflow.annotation.PrimaryKey import com.raizlabs.android.dbflow.annotation.Table import com.raizlabs.android.dbflow.processor.ClassNames import com.raizlabs.android.dbflow.processor.ProcessorManager -import com.raizlabs.android.dbflow.processor.utils.capitalizeFirstLetter +import com.raizlabs.android.dbflow.processor.utils.annotation import com.raizlabs.android.dbflow.processor.utils.isNullOrEmpty import com.raizlabs.android.dbflow.processor.utils.lower import com.raizlabs.android.dbflow.processor.utils.toTypeElement @@ -21,8 +21,8 @@ import javax.lang.model.type.TypeMirror /** * Description: Generates the Model class that is used in a many to many. */ -class ManyToManyDefinition @JvmOverloads constructor(element: TypeElement, processorManager: ProcessorManager, - manyToMany: ManyToMany = element.getAnnotation(ManyToMany::class.java)) +class ManyToManyDefinition(element: TypeElement, processorManager: ProcessorManager, + manyToMany: ManyToMany = element.annotation()!!) : BaseDefinition(element, processorManager) { internal var referencedTable: TypeName @@ -48,23 +48,24 @@ class ManyToManyDefinition @JvmOverloads constructor(element: TypeElement, proce sameTableReferenced = referencedTable == elementTypeName - val table = element.getAnnotation(Table::class.java) - try { - table.database - } catch (mte: MirroredTypeException) { - databaseTypeName = TypeName.get(mte.typeMirror) + element.annotation
()?.let { table -> + try { + table.database + } catch (mte: MirroredTypeException) { + databaseTypeName = TypeName.get(mte.typeMirror) + } } if (!thisColumnName.isNullOrEmpty() && !referencedColumnName.isNullOrEmpty() - && thisColumnName == referencedColumnName) { - manager.logError(ManyToManyDefinition::class, "The thisTableColumnName and referenceTableColumnName" + "cannot be the same") + && thisColumnName == referencedColumnName) { + manager.logError(ManyToManyDefinition::class, "The thisTableColumnName and referenceTableColumnName cannot be the same") } } fun prepareForWrite() { val databaseDefinition = manager.getDatabaseHolderDefinition(databaseTypeName)?.databaseDefinition if (databaseDefinition == null) { - manager.logError("DatabaseDefinition was null for : " + elementName) + manager.logError("DatabaseDefinition was null for : $elementName") } else { if (generatedTableClassName.isNullOrEmpty()) { val referencedOutput = getElementClassName(referencedTable.toTypeElement(manager)) @@ -76,14 +77,14 @@ class ManyToManyDefinition @JvmOverloads constructor(element: TypeElement, proce } override fun onWriteDefinition(typeBuilder: TypeSpec.Builder) { - typeBuilder.addAnnotation(AnnotationSpec.builder(Table::class.java) - .addMember("database", "\$T.class", databaseTypeName).build()) + typeBuilder.apply { + addAnnotation(AnnotationSpec.builder(Table::class.java) + .addMember("database", "\$T.class", databaseTypeName).build()) - val referencedDefinition = manager.getTableDefinition(databaseTypeName, referencedTable) - val selfDefinition = manager.getTableDefinition(databaseTypeName, elementTypeName) + val referencedDefinition = manager.getTableDefinition(databaseTypeName, referencedTable) + val selfDefinition = manager.getTableDefinition(databaseTypeName, elementTypeName) - if (generateAutoIncrement) { - typeBuilder.apply { + if (generateAutoIncrement) { addField(field(`@`(PrimaryKey::class) { this["autoincrement"] = "true" }, TypeName.LONG, "_id").build()) `fun`(TypeName.LONG, "getId") { @@ -91,10 +92,10 @@ class ManyToManyDefinition @JvmOverloads constructor(element: TypeElement, proce `return`("_id") } } - } - referencedDefinition?.let { appendColumnDefinitions(typeBuilder, it, 0, referencedColumnName) } - selfDefinition?.let { appendColumnDefinitions(typeBuilder, it, 1, thisColumnName) } + referencedDefinition?.let { appendColumnDefinitions(this, it, 0, referencedColumnName) } + selfDefinition?.let { appendColumnDefinitions(this, it, 1, thisColumnName) } + } } override val extendsClass: TypeName? @@ -118,12 +119,12 @@ class ManyToManyDefinition @JvmOverloads constructor(element: TypeElement, proce } `@`(ForeignKey::class) { member("saveForeignKeyModel", saveForeignKeyModels.toString()) } } - `fun`(referencedDefinition.elementClassName!!, "get${fieldName.capitalizeFirstLetter()}") { + `fun`(referencedDefinition.elementClassName!!, "get${fieldName.capitalize()}") { modifiers(public, final) `return`(fieldName.L) } - `fun`(TypeName.VOID, "set${fieldName.capitalizeFirstLetter()}", - param(referencedDefinition.elementClassName!!, "param")) { + `fun`(TypeName.VOID, "set${fieldName.capitalize()}", + param(referencedDefinition.elementClassName!!, "param")) { modifiers(public, final) statement("$fieldName = param") } 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 fc6f89ea7..78d303808 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 @@ -1,7 +1,11 @@ package com.raizlabs.android.dbflow.processor.definition +import com.grosner.kpoet.* import com.raizlabs.android.dbflow.processor.ClassNames +import com.raizlabs.android.dbflow.processor.definition.column.wrapperCommaIfBaseModel import com.raizlabs.android.dbflow.processor.utils.ModelUtils +import com.raizlabs.android.dbflow.processor.utils.`override fun` +import com.raizlabs.android.dbflow.processor.utils.codeBlock import com.raizlabs.android.dbflow.processor.utils.isNullOrEmpty import com.raizlabs.android.dbflow.sql.QueryBuilder import com.squareup.javapoet.* @@ -81,7 +85,7 @@ class BindToContentValuesMethod(private val baseTableDefinition: BaseTableDefini */ class BindToStatementMethod(private val tableDefinition: TableDefinition, private val isInsert: Boolean) : MethodDefinition { - override val methodSpec: MethodSpec + override val methodSpec: MethodSpec? get() { val methodBuilder = MethodSpec.methodBuilder(if (isInsert) "bindToInsertStatement" else "bindToStatement") .addAnnotation(Override::class.java) @@ -102,8 +106,8 @@ class BindToStatementMethod(private val tableDefinition: TableDefinition, privat } if (tableDefinition.implementsSqlStatementListener) { - methodBuilder.addStatement("\$L.onBindTo\$LStatement(\$L)", - ModelUtils.variable, if (isInsert) "Insert" else "", PARAM_STATEMENT) + methodBuilder.addStatement("${ModelUtils.variable}.onBindTo\$LStatement($PARAM_STATEMENT)", + if (isInsert) "Insert" else "") } } else { var start = 0 @@ -113,12 +117,14 @@ class BindToStatementMethod(private val tableDefinition: TableDefinition, privat methodBuilder.addStatement("int start = 0") methodBuilder.addCode(it.getSQLiteStatementMethod(AtomicInteger(++start))) } - } - - methodBuilder.addStatement("bindToInsertStatement(\$L, \$L, \$L)", PARAM_STATEMENT, ModelUtils.variable, start) - if (tableDefinition.implementsSqlStatementListener) { - methodBuilder.addStatement("\$L.onBindTo\$LStatement(\$L)", - ModelUtils.variable, if (isInsert) "Insert" else "", PARAM_STATEMENT) + 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 } } @@ -138,50 +144,35 @@ class BindToStatementMethod(private val tableDefinition: TableDefinition, privat class CreationQueryMethod(private val tableDefinition: TableDefinition) : MethodDefinition { override val methodSpec: MethodSpec - get() { - val methodBuilder = MethodSpec.methodBuilder("getCreationQuery") - .addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .returns(ClassName.get(String::class.java)) + get() = `override fun`(String::class, "getCreationQuery") { + modifiers(public, final) - val creationBuilder = CodeBlock.builder().add("CREATE TABLE IF NOT EXISTS ") - .add(QueryBuilder.quote(tableDefinition.tableName)).add("(") + val foreignSize = tableDefinition.foreignKeyDefinitions.size - (0..tableDefinition.columnDefinitions.size - 1).forEach { i -> - if (i > 0) { - creationBuilder.add(",") + val creationBuilder = codeBlock { + add("CREATE TABLE IF NOT EXISTS ${QueryBuilder.quote(tableDefinition.tableName)}(") + add(tableDefinition.columnDefinitions.joinToString { it.creationName.toString() }) + tableDefinition.uniqueGroupsDefinitions.forEach { + if (!it.columnDefinitionList.isEmpty()) add(it.creationName) } - creationBuilder.add(tableDefinition.columnDefinitions[i].creationName) - } - tableDefinition.uniqueGroupsDefinitions.forEach { - if (!it.columnDefinitionList.isEmpty()) creationBuilder.add(it.creationName) - } - - if (!tableDefinition.hasAutoIncrement) { - val primarySize = tableDefinition.primaryColumnDefinitions.size - for (i in 0..primarySize - 1) { - if (i == 0) { - creationBuilder.add(", PRIMARY KEY(") - } - - if (i > 0) { - creationBuilder.add(",") - } - - val primaryDefinition = tableDefinition.primaryColumnDefinitions[i] - creationBuilder.add(primaryDefinition.primaryKeyName) - - if (i == primarySize - 1) { - creationBuilder.add(")") + if (!tableDefinition.hasAutoIncrement) { + val primarySize = tableDefinition.primaryColumnDefinitions.size + if (primarySize > 0) { + add(", PRIMARY KEY(${tableDefinition.primaryColumnDefinitions.joinToString { it.primaryKeyName.toString() }})") if (!tableDefinition.primaryKeyConflictActionName.isNullOrEmpty()) { - creationBuilder.add(" ON CONFLICT " + tableDefinition.primaryKeyConflictActionName) + add(" ON CONFLICT ${tableDefinition.primaryKeyConflictActionName}") } } } + if (foreignSize == 0) { + add(")") + } + this } - val foreignSize = tableDefinition.foreignKeyDefinitions.size + val codeBuilder = CodeBlock.builder() + .add("return ${creationBuilder.toString().S}") val foreignKeyBlocks = ArrayList() val tableNameBlocks = ArrayList() @@ -190,52 +181,36 @@ class CreationQueryMethod(private val tableDefinition: TableDefinition) : Method for (i in 0..foreignSize - 1) { val foreignKeyBuilder = CodeBlock.builder() val referenceBuilder = CodeBlock.builder() - val foreignKeyColumnDefinition = tableDefinition.foreignKeyDefinitions[i] - - foreignKeyBuilder.add(", FOREIGN KEY(") - - (0..foreignKeyColumnDefinition._foreignKeyReferenceDefinitionList.size - 1).forEach { j -> - if (j > 0) { - foreignKeyBuilder.add(",") + val fk = tableDefinition.foreignKeyDefinitions[i] + + foreignKeyBlocks.add(foreignKeyBuilder.apply { + add(", FOREIGN KEY(") + add(fk._foreignKeyReferenceDefinitionList.joinToString { QueryBuilder.quote(it.columnName) }) + add(") REFERENCES ") + }.build()) + + tableNameBlocks.add(codeBlock { add("\$T.getTableName(\$T.class)", ClassNames.FLOW_MANAGER, fk.referencedTableClassName) }) + + referenceKeyBlocks.add(referenceBuilder.apply { + add("(") + add(fk._foreignKeyReferenceDefinitionList.joinToString { QueryBuilder.quote(it.foreignColumnName) }) + add(") ON UPDATE ${fk.onUpdate.name.replace("_", " ")} ON DELETE ${fk.onDelete.name.replace("_", " ")}") + if (fk.deferred) { + add(" DEFERRABLE INITIALLY DEFERRED") } - val referenceDefinition = foreignKeyColumnDefinition._foreignKeyReferenceDefinitionList[j] - foreignKeyBuilder.add("\$L", QueryBuilder.quote(referenceDefinition.columnName)) - } - - - foreignKeyBuilder.add(") REFERENCES ") - - foreignKeyBlocks.add(foreignKeyBuilder.build()) - - tableNameBlocks.add(CodeBlock.builder().add("\$T.getTableName(\$T.class)", - ClassNames.FLOW_MANAGER, foreignKeyColumnDefinition.referencedTableClassName).build()) - - referenceBuilder.add("(") - for (j in 0..foreignKeyColumnDefinition._foreignKeyReferenceDefinitionList.size - 1) { - if (j > 0) { - referenceBuilder.add(", ") - } - val referenceDefinition = foreignKeyColumnDefinition._foreignKeyReferenceDefinitionList[j] - referenceBuilder.add("\$L", QueryBuilder.quote(referenceDefinition.foreignColumnName)) - } - referenceBuilder.add(") ON UPDATE \$L ON DELETE \$L", foreignKeyColumnDefinition.onUpdate.name.replace("_", " "), - foreignKeyColumnDefinition.onDelete.name.replace("_", " ")) - referenceKeyBlocks.add(referenceBuilder.build()) + }.build()) } - val codeBuilder = CodeBlock.builder().add("return \$S", creationBuilder.build().toString()) - if (foreignSize > 0) { for (i in 0..foreignSize - 1) { - codeBuilder.add("+ \$S + \$L + \$S", foreignKeyBlocks[i], tableNameBlocks[i], referenceKeyBlocks[i]) + codeBuilder.add("+ ${foreignKeyBlocks[i].S} + ${tableNameBlocks[i]} + ${referenceKeyBlocks[i].S}") } + codeBuilder.add(" + ${");".S};\n") + } else { + codeBuilder.add(";\n") } - codeBuilder.add(" + \$S", ");").add(";\n") - - methodBuilder.addCode(codeBuilder.build()) - - return methodBuilder.build() + addCode(codeBuilder.build()) } } @@ -245,21 +220,16 @@ class CreationQueryMethod(private val tableDefinition: TableDefinition) : Method class CustomTypeConverterPropertyMethod(private val baseTableDefinition: BaseTableDefinition) : TypeAdder, CodeAdder { - override fun addToType(typeBuilder: TypeSpec.Builder) { val customTypeConverters = baseTableDefinition.associatedTypeConverters.keys customTypeConverters.forEach { - typeBuilder.addField(FieldSpec.builder(it, "typeConverter" + it.simpleName(), - Modifier.PRIVATE, Modifier.FINAL).initializer("new \$T()", it).build()) + typeBuilder.`private final field`(it, "typeConverter${it.simpleName()}") { `=`("new \$T()", it) } } val globalTypeConverters = baseTableDefinition.globalTypeConverters.keys globalTypeConverters.forEach { - typeBuilder.addField(FieldSpec.builder(it, "global_typeConverter" + it.simpleName(), - Modifier.PRIVATE, Modifier.FINAL).build()) + typeBuilder.`private final field`(it, "global_typeConverter${it.simpleName()}") } - - } override fun addCode(code: CodeBlock.Builder): CodeBlock.Builder { @@ -269,8 +239,8 @@ class CustomTypeConverterPropertyMethod(private val baseTableDefinition: BaseTab val def = baseTableDefinition.globalTypeConverters[it] val firstDef = def?.get(0) firstDef?.typeConverterElementNames?.forEach { elementName -> - code.addStatement("global_typeConverter\$L = (\$T) \$L.getTypeConverterForClass(\$T.class)", - it.simpleName(), it, "holder", elementName).build() + code.statement("global_typeConverter${it.simpleName()} " + + "= (\$T) holder.getTypeConverterForClass(\$T.class)", it, elementName) } } return code @@ -282,23 +252,20 @@ class CustomTypeConverterPropertyMethod(private val baseTableDefinition: BaseTab */ class ExistenceMethod(private val tableDefinition: BaseTableDefinition) : MethodDefinition { - override val methodSpec: MethodSpec - get() { - val methodBuilder = MethodSpec.methodBuilder("exists") - .addAnnotation(Override::class.java) - .addParameter(tableDefinition.parameterClassName, ModelUtils.variable) - .addParameter(ClassNames.DATABASE_WRAPPER, "wrapper") - .addModifiers(Modifier.PUBLIC, Modifier.FINAL).returns(TypeName.BOOLEAN) - // only quick check if enabled. - var primaryColumn = tableDefinition.autoIncrementColumn - if (primaryColumn == null) { - primaryColumn = tableDefinition.primaryColumnDefinitions[0] + override val methodSpec + get() = `override fun`(TypeName.BOOLEAN, "exists", + param(tableDefinition.parameterClassName!!, ModelUtils.variable), + param(ClassNames.DATABASE_WRAPPER, "wrapper")) { + modifiers(public, final) + code { + // only quick check if enabled. + var primaryColumn = tableDefinition.autoIncrementColumn + if (primaryColumn == null) { + primaryColumn = tableDefinition.primaryColumnDefinitions[0] + } + primaryColumn.appendExistenceMethod(this) + this } - - val code = CodeBlock.builder() - primaryColumn.appendExistenceMethod(code) - methodBuilder.addCode(code.build()) - return methodBuilder.build() } } @@ -312,57 +279,38 @@ class InsertStatementQueryMethod(private val tableDefinition: TableDefinition, p if (isInsert && !tableDefinition.hasAutoIncrement) { return null // dont write method here because we reuse the compiled statement query method } - val methodBuilder = MethodSpec.methodBuilder(if (isInsert) "getInsertStatementQuery" else "getCompiledStatementQuery") - .addAnnotation(Override::class.java).addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .returns(ClassName.get(String::class.java)) - - val codeBuilder = CodeBlock.builder().add("INSERT ") - if (!tableDefinition.insertConflictActionName.isEmpty()) { - codeBuilder.add("OR \$L ", tableDefinition.insertConflictActionName) - } - codeBuilder.add("INTO ").add(QueryBuilder.quote(tableDefinition.tableName)) - - val isSingleAutoincrement = tableDefinition.hasAutoIncrement && tableDefinition.columnDefinitions.size == 1 - && isInsert - - codeBuilder.add("(") - - val columnSize = tableDefinition.columnDefinitions.size - var columnCount = 0 - tableDefinition.columnDefinitions.forEach { - if (!it.isPrimaryKeyAutoIncrement && !it.isRowId || !isInsert || isSingleAutoincrement) { - if (columnCount > 0) codeBuilder.add(",") - - codeBuilder.add(it.insertStatementColumnName) - columnCount++ - } - } - codeBuilder.add(")") - - codeBuilder.add(" VALUES (") - - columnCount = 0 - for (i in 0..columnSize - 1) { - val definition = tableDefinition.columnDefinitions[i] - if (!definition.isPrimaryKeyAutoIncrement && !definition.isRowId || !isInsert) { - if (columnCount > 0) { - codeBuilder.add(",") + return `override fun`(String::class, + if (isInsert) "getInsertStatementQuery" else "getCompiledStatementQuery") { + modifiers(public, final) + val isSingleAutoincrement = tableDefinition.hasAutoIncrement + && tableDefinition.columnDefinitions.size == 1 && isInsert + `return`(codeBlock { + add("INSERT ") + if (!tableDefinition.insertConflictActionName.isEmpty()) { + add("OR ${tableDefinition.insertConflictActionName} ") } + add("INTO ${QueryBuilder.quote(tableDefinition.tableName)}(") - codeBuilder.add(definition.insertStatementValuesString) - columnCount++ - } - } + tableDefinition.columnDefinitions.filter { + !it.isPrimaryKeyAutoIncrement && !it.isRowId || !isInsert || isSingleAutoincrement + }.forEachIndexed { index, columnDefinition -> + if (index > 0) add(",") + add(columnDefinition.insertStatementColumnName) - if (isSingleAutoincrement) { - codeBuilder.add("NULL") - } + } + add(") VALUES (") - codeBuilder.add(")") + tableDefinition.columnDefinitions.filter { !it.isPrimaryKeyAutoIncrement && !it.isRowId || !isInsert } + .forEachIndexed { index, columnDefinition -> + if (index > 0) add(",") + add(columnDefinition.insertStatementValuesString) + } - methodBuilder.addStatement("return \$S", codeBuilder.build().toString()) + if (isSingleAutoincrement) add("NULL") - return methodBuilder.build() + add(")") + }.S) + } } } @@ -372,35 +320,36 @@ class InsertStatementQueryMethod(private val tableDefinition: TableDefinition, p class LoadFromCursorMethod(private val baseTableDefinition: BaseTableDefinition) : MethodDefinition { override val methodSpec: MethodSpec - get() { - val methodBuilder = MethodSpec.methodBuilder("loadFromCursor").addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addParameter(ClassNames.CURSOR, PARAM_CURSOR) - .addParameter(baseTableDefinition.parameterClassName, - ModelUtils.variable).returns(TypeName.VOID) - + get() = `override fun`(TypeName.VOID, "loadFromCursor", + param(ClassNames.FLOW_CURSOR, PARAM_CURSOR), + param(baseTableDefinition.parameterClassName!!, ModelUtils.variable)) { + modifiers(public, final) val index = AtomicInteger(0) + val nameAllocator = NameAllocator() // unique names baseTableDefinition.columnDefinitions.forEach { - methodBuilder.addCode(it.getLoadFromCursorMethod(true, index)) + addCode(it.getLoadFromCursorMethod(true, index, nameAllocator)) index.incrementAndGet() } if (baseTableDefinition is TableDefinition) { - - val codeBuilder = CodeBlock.builder() - for (oneToMany in baseTableDefinition.oneToManyDefinitions) { - if (oneToMany.isLoad) oneToMany.writeLoad(codeBuilder) + val foundDef = baseTableDefinition.oneToManyDefinitions.find { it.hasWrapper } + foundDef?.let { foundDef.writeWrapperStatement(this) } + + code { + baseTableDefinition.oneToManyDefinitions + .filter { it.isLoad } + .forEach { it.writeLoad(this) } + this } - methodBuilder.addCode(codeBuilder.build()) } if (baseTableDefinition is TableDefinition && baseTableDefinition.implementsLoadFromCursorListener) { - methodBuilder.addStatement("\$L.onLoadFromCursor(\$L)", ModelUtils.variable, PARAM_CURSOR) + statement("${ModelUtils.variable}.onLoadFromCursor($PARAM_CURSOR)") } - - return methodBuilder.build() + this } + companion object { val PARAM_CURSOR = "cursor" @@ -415,39 +364,29 @@ class OneToManyDeleteMethod(private val tableDefinition: TableDefinition, override val methodSpec: MethodSpec? get() { - var shouldWrite = false - for (oneToManyDefinition in tableDefinition.oneToManyDefinitions) { - if (oneToManyDefinition.isDelete) { - shouldWrite = true - break - } - } - + val shouldWrite = tableDefinition.oneToManyDefinitions.any { it.isDelete } if (shouldWrite || tableDefinition.cachingEnabled) { + return `override fun`(TypeName.BOOLEAN, "delete", + param(tableDefinition.elementClassName!!, ModelUtils.variable)) { + modifiers(public, final) + if (useWrapper) { + addParameter(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper) + } + if (tableDefinition.cachingEnabled) { + statement("getModelCache().removeModel(getCachingId(${ModelUtils.variable}))") + } - val builder = CodeBlock.builder() - - if (tableDefinition.cachingEnabled) { - builder.addStatement("getModelCache().removeModel(getCachingId(\$L))", ModelUtils.variable) - } - - builder.addStatement("boolean successful = super.delete(\$L\$L)", ModelUtils.variable, - if (useWrapper) ", " + ModelUtils.wrapper else "") + statement("boolean successful = super.delete(${ModelUtils.variable}${wrapperCommaIfBaseModel(useWrapper)})") - tableDefinition.oneToManyDefinitions.forEach { it.writeDelete(builder, useWrapper) } + if (!useWrapper) { + val foundDef = tableDefinition.oneToManyDefinitions.find { it.hasWrapper } + foundDef?.let { foundDef.writeWrapperStatement(this) } + } - builder.addStatement("return successful") + tableDefinition.oneToManyDefinitions.forEach { it.writeDelete(this, useWrapper) } - val delete = MethodSpec.methodBuilder("delete") - .addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addParameter(tableDefinition.elementClassName, ModelUtils.variable) - .addCode(builder.build()) - .returns(TypeName.BOOLEAN) - if (useWrapper) { - delete.addParameter(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper) + `return`("successful") } - return delete.build() } return null } @@ -463,48 +402,52 @@ class OneToManySaveMethod(private val tableDefinition: TableDefinition, override val methodSpec: MethodSpec? get() { if (!tableDefinition.oneToManyDefinitions.isEmpty() || tableDefinition.cachingEnabled) { - val code = CodeBlock.builder() - - if (methodName == METHOD_INSERT) { - code.add("long rowId = ") - } else if (methodName == METHOD_UPDATE || methodName == METHOD_SAVE) { - code.add("boolean successful = ") + var retType = TypeName.BOOLEAN + var retStatement = "successful" + when (methodName) { + METHOD_INSERT -> { + retType = ClassName.LONG + retStatement = "rowId" + } } - code.addStatement("super.\$L(\$L\$L)", methodName, - ModelUtils.variable, - if (useWrapper) ", " + ModelUtils.wrapper else "") + return `override fun`(retType, methodName, + param(tableDefinition.elementClassName!!, ModelUtils.variable)) { + modifiers(public, final) - if (tableDefinition.cachingEnabled) { - code.addStatement("getModelCache().addModel(getCachingId(\$L), \$L)", ModelUtils.variable, - ModelUtils.variable) - } + if (useWrapper) { + addParameter(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper) + } + code { + if (methodName == METHOD_INSERT) { + add("long rowId = ") + } else if (methodName == METHOD_UPDATE || methodName == METHOD_SAVE) { + add("boolean successful = ") + } + statement("super.$methodName(${ModelUtils.variable}${wrapperCommaIfBaseModel(useWrapper)})") - for (oneToManyDefinition in tableDefinition.oneToManyDefinitions) { - when (methodName) { - METHOD_SAVE -> oneToManyDefinition.writeSave(code, useWrapper) - METHOD_UPDATE -> oneToManyDefinition.writeUpdate(code, useWrapper) - METHOD_INSERT -> oneToManyDefinition.writeInsert(code, useWrapper) + if (tableDefinition.cachingEnabled) { + statement("getModelCache().addModel(getCachingId(${ModelUtils.variable}), ${ModelUtils.variable})") + } + this } - } - val builder = MethodSpec.methodBuilder(methodName) - .addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addParameter(tableDefinition.elementClassName, ModelUtils.variable) - .addCode(code.build()) - if (methodName == METHOD_INSERT) { - builder.returns(ClassName.LONG) - builder.addStatement("return rowId") - } else if (methodName == METHOD_UPDATE || methodName == METHOD_SAVE) { - builder.returns(TypeName.BOOLEAN) - builder.addStatement("return successful") - } - if (useWrapper) { - builder.addParameter(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper) - } + // need wrapper access if we have wrapper param here. + if (!useWrapper) { + val foundDef = tableDefinition.oneToManyDefinitions.find { it.hasWrapper } + foundDef?.let { foundDef.writeWrapperStatement(this) } + } + + for (oneToManyDefinition in tableDefinition.oneToManyDefinitions) { + when (methodName) { + METHOD_SAVE -> oneToManyDefinition.writeSave(this, useWrapper) + METHOD_UPDATE -> oneToManyDefinition.writeUpdate(this, useWrapper) + METHOD_INSERT -> oneToManyDefinition.writeInsert(this, useWrapper) + } + } - return builder.build() + `return`(retStatement) + } } else { return null } @@ -524,21 +467,18 @@ class OneToManySaveMethod(private val tableDefinition: TableDefinition, class PrimaryConditionMethod(private val tableDefinition: BaseTableDefinition) : MethodDefinition { override val methodSpec: MethodSpec? - get() { - val methodBuilder = MethodSpec.methodBuilder("getPrimaryConditionClause") - .addAnnotation(Override::class.java) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .addParameter(tableDefinition.parameterClassName, - ModelUtils.variable).returns(ClassNames.OPERATOR_GROUP) - val code = CodeBlock.builder() - code.addStatement("\$T clause = \$T.clause()", ClassNames.OPERATOR_GROUP, ClassNames.OPERATOR_GROUP) - tableDefinition.primaryColumnDefinitions.forEach { - val codeBuilder = CodeBlock.builder() - it.appendPropertyComparisonAccessStatement(codeBuilder) - code.add(codeBuilder.build()) + get() = `override fun`(ClassNames.OPERATOR_GROUP, "getPrimaryConditionClause", + param(tableDefinition.parameterClassName!!, ModelUtils.variable)) { + modifiers(public, final) + code { + statement("\$T clause = \$T.clause()", ClassNames.OPERATOR_GROUP, ClassNames.OPERATOR_GROUP) + tableDefinition.primaryColumnDefinitions.forEach { + val codeBuilder = CodeBlock.builder() + it.appendPropertyComparisonAccessStatement(codeBuilder) + add(codeBuilder.build()) + } + this } - methodBuilder.addCode(code.build()) - methodBuilder.addStatement("return clause") - return methodBuilder.build() + `return`("clause") } } diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/MigrationDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/MigrationDefinition.kt index 99adf9f0d..14d521abf 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/MigrationDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/MigrationDefinition.kt @@ -1,7 +1,9 @@ package com.raizlabs.android.dbflow.processor.definition +import com.grosner.kpoet.typeName import com.raizlabs.android.dbflow.annotation.Migration import com.raizlabs.android.dbflow.processor.ProcessorManager +import com.raizlabs.android.dbflow.processor.utils.annotation import com.raizlabs.android.dbflow.processor.utils.isNullOrEmpty import com.squareup.javapoet.ClassName import com.squareup.javapoet.CodeBlock @@ -15,7 +17,7 @@ import javax.lang.model.type.MirroredTypeException * Description: Used in holding data about migration files. */ class MigrationDefinition(processorManager: ProcessorManager, typeElement: TypeElement) -: BaseDefinition(typeElement, processorManager) { + : BaseDefinition(typeElement, processorManager) { var databaseName: TypeName? = null @@ -29,22 +31,22 @@ class MigrationDefinition(processorManager: ProcessorManager, typeElement: TypeE init { setOutputClassName("") - val migration = typeElement.getAnnotation(Migration::class.java) + val migration = typeElement.annotation() if (migration == null) { processorManager.logError("Migration was null for:" + typeElement) } else { try { migration.database } catch (mte: MirroredTypeException) { - databaseName = TypeName.get(mte.typeMirror) + databaseName = mte.typeMirror.typeName } version = migration.version priority = migration.priority val elements = typeElement.enclosedElements - for (element in elements) { - if (element is ExecutableElement && element.getSimpleName().toString() == "") { + elements.forEach { element -> + if (element is ExecutableElement && element.simpleName.toString() == "") { if (!constructorName.isNullOrEmpty()) { manager.logError(MigrationDefinition::class, "Migrations cannot have more than one constructor. " + "They can only have an Empty() or single-parameter constructor Empty(Empty.class) that specifies " + @@ -57,12 +59,12 @@ class MigrationDefinition(processorManager: ProcessorManager, typeElement: TypeE val params = element.parameters val param = params[0] - val type = TypeName.get(param.asType()) + val type = param.asType().typeName if (type is ParameterizedTypeName && type.rawType == ClassName.get(Class::class.java)) { val containedType = type.typeArguments[0] - constructorName = CodeBlock.builder().add("(\$T.class)", containedType).build().toString() + constructorName = CodeBlock.of("(\$T.class)", containedType).toString() } else { - manager.logError(MigrationDefinition::class, "Wrong parameter type found for %1s. Found %1s" + "but required ModelClass.class", typeElement, type) + manager.logError(MigrationDefinition::class, "Wrong parameter type found for $typeElement. Found $type but required ModelClass.class") } } } diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/ModelViewDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/ModelViewDefinition.kt index 0287b63b2..f10baa3ac 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/ModelViewDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/ModelViewDefinition.kt @@ -7,13 +7,13 @@ import com.raizlabs.android.dbflow.annotation.ModelViewQuery import com.raizlabs.android.dbflow.processor.ClassNames import com.raizlabs.android.dbflow.processor.ColumnValidator import com.raizlabs.android.dbflow.processor.ProcessorManager -import com.raizlabs.android.dbflow.processor.ProcessorUtils import com.raizlabs.android.dbflow.processor.definition.column.ColumnDefinition import com.raizlabs.android.dbflow.processor.definition.column.ForeignKeyColumnDefinition import com.raizlabs.android.dbflow.processor.utils.* -import com.squareup.javapoet.* +import com.squareup.javapoet.ParameterizedTypeName +import com.squareup.javapoet.TypeName +import com.squareup.javapoet.TypeSpec import javax.lang.model.element.Element -import javax.lang.model.element.Modifier import javax.lang.model.element.TypeElement import javax.lang.model.type.MirroredTypeException @@ -31,7 +31,7 @@ class ModelViewDefinition(manager: ProcessorManager, element: Element) : BaseTab private var name: String? = null private val methods: Array = - arrayOf(LoadFromCursorMethod(this), ExistenceMethod(this), PrimaryConditionMethod(this)) + arrayOf(LoadFromCursorMethod(this), ExistenceMethod(this), PrimaryConditionMethod(this)) var allFields: Boolean = false @@ -39,8 +39,7 @@ class ModelViewDefinition(manager: ProcessorManager, element: Element) : BaseTab init { - val modelView = element.getAnnotation(ModelView::class.java) - if (modelView != null) { + element.annotation()?.let { modelView -> try { modelView.database } catch (mte: MirroredTypeException) { @@ -57,8 +56,8 @@ class ModelViewDefinition(manager: ProcessorManager, element: Element) : BaseTab } if (element is TypeElement) { - implementsLoadFromCursorListener = ProcessorUtils.implementsClass(manager.processingEnvironment, - ClassNames.LOAD_FROM_CURSOR_LISTENER.toString(), element) + implementsLoadFromCursorListener = element.implementsClass(manager.processingEnvironment, + ClassNames.LOAD_FROM_CURSOR_LISTENER) } else { implementsLoadFromCursorListener = false } @@ -73,11 +72,11 @@ class ModelViewDefinition(manager: ProcessorManager, element: Element) : BaseTab val modelView = element.getAnnotation(ModelView::class.java) if (modelView != null) { databaseDefinition = manager.getDatabaseHolderDefinition(databaseName)?.databaseDefinition - setOutputClassName(databaseDefinition?.classSeparator + DBFLOW_MODEL_VIEW_TAG) + setOutputClassName("${databaseDefinition?.classSeparator}ViewTable") typeElement?.let { createColumnDefinitions(it) } } else { - setOutputClassName(DBFLOW_MODEL_VIEW_TAG) + setOutputClassName("ViewTable") } } @@ -91,13 +90,13 @@ class ModelViewDefinition(manager: ProcessorManager, element: Element) : BaseTab val columnValidator = ColumnValidator() for (variableElement in variableElements) { - val isValidAllFields = ElementUtility.isValidAllFields(allFields, element) + val isValidAllFields = ElementUtility.isValidAllFields(allFields, variableElement) - if (variableElement.getAnnotation(Column::class.java) != null || isValidAllFields) { + if (variableElement.annotation() != null || isValidAllFields) { // package private, will generate helper - val isPackagePrivate = ElementUtility.isPackagePrivate(element) - val isPackagePrivateNotInSamePackage = isPackagePrivate && !ElementUtility.isInSamePackage(manager, element, this.element) + val isPackagePrivate = ElementUtility.isPackagePrivate(variableElement) + val isPackagePrivateNotInSamePackage = isPackagePrivate && !ElementUtility.isInSamePackage(manager, variableElement, this.element) val columnDefinition = ColumnDefinition(manager, variableElement, this, isPackagePrivateNotInSamePackage) if (columnValidator.validate(manager, columnDefinition)) { @@ -109,17 +108,17 @@ class ModelViewDefinition(manager: ProcessorManager, element: Element) : BaseTab } if (columnDefinition.isPrimaryKey || columnDefinition is ForeignKeyColumnDefinition - || columnDefinition.isPrimaryKeyAutoIncrement || columnDefinition.isRowId) { + || columnDefinition.isPrimaryKeyAutoIncrement || columnDefinition.isRowId) { manager.logError("ModelViews cannot have primary or foreign keys") } - } else if (variableElement.getAnnotation(ModelViewQuery::class.java) != null) { + } else if (variableElement.annotation() != null) { if (!queryFieldName.isNullOrEmpty()) { - manager.logError("Found duplicate ") + manager.logError("Found duplicate queryField name: $queryFieldName for $elementClassName") } - ProcessorUtils.ensureVisibleStatic(variableElement, typeElement, "ModelViewQuery") + ensureVisibleStatic(variableElement, typeElement, "ModelViewQuery") val element = variableElement.toTypeErasedElement() - if (!ProcessorUtils.implementsClass(manager.processingEnvironment, ClassNames.QUERY.toString(), element)) { + if (!element.implementsClass(manager.processingEnvironment, ClassNames.QUERY)) { manager.logError("The field ${variableElement.simpleName} must implement ${ClassNames.QUERY}") } @@ -128,7 +127,7 @@ class ModelViewDefinition(manager: ProcessorManager, element: Element) : BaseTab } if (queryFieldName.isNullOrEmpty()) { - manager.logError("%1s is missing the @ModelViewQuery field.", elementClassName) + manager.logError("$elementClassName is missing the @ModelViewQuery field.") } } @@ -139,34 +138,17 @@ class ModelViewDefinition(manager: ProcessorManager, element: Element) : BaseTab get() = ParameterizedTypeName.get(ClassNames.MODEL_VIEW_ADAPTER, elementClassName) override fun onWriteDefinition(typeBuilder: TypeSpec.Builder) { + typeBuilder.apply { + `public static final field`(String::class, "VIEW_NAME") { `=`(name.S) } - typeBuilder.addField(FieldSpec.builder(ClassName.get(String::class.java), - "VIEW_NAME", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("\$S", name!!).build()) - elementClassName?.let { - columnDefinitions.forEach { - columnDefinition -> - columnDefinition.addPropertyDefinition(typeBuilder, it) - } - } - - val customTypeConverterPropertyMethod = CustomTypeConverterPropertyMethod(this) - customTypeConverterPropertyMethod.addToType(typeBuilder) - - typeBuilder.constructor(param(ClassNames.DATABASE_HOLDER, "holder"), - param(ClassNames.BASE_DATABASE_DEFINITION_CLASSNAME, "databaseDefinition")) { - modifiers(public) - statement("super(databaseDefinition)") - code { - customTypeConverterPropertyMethod.addCode(this) + elementClassName?.let { elementClassName -> + columnDefinitions.forEach { it.addPropertyDefinition(typeBuilder, elementClassName) } } - } - methods.mapNotNull { it.methodSpec } - .forEach { typeBuilder.addMethod(it) } + writeConstructor(this) - InternalAdapterHelper.writeGetModelClass(typeBuilder, elementClassName) + writeGetModelClass(typeBuilder, elementClassName) - typeBuilder.apply { `override fun`(String::class, "getCreationQuery") { modifiers(public, final) `return`("\$T.\$L.getQuery()", elementClassName, queryFieldName) @@ -179,15 +161,14 @@ class ModelViewDefinition(manager: ProcessorManager, element: Element) : BaseTab modifiers(public, final) `return`("new \$T()", elementClassName) } + } + + methods.mapNotNull { it.methodSpec } + .forEach { typeBuilder.addMethod(it) } } override fun compareTo(other: ModelViewDefinition): Int { return Integer.valueOf(priority)!!.compareTo(other.priority) } - - companion object { - - private val DBFLOW_MODEL_VIEW_TAG = "ViewTable" - } -} +} \ No newline at end of file diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/NotifyDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/NotifyDefinition.kt index 972bc9123..dff447d93 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/NotifyDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/NotifyDefinition.kt @@ -3,6 +3,7 @@ package com.raizlabs.android.dbflow.processor.definition import com.raizlabs.android.dbflow.annotation.provider.Notify import com.raizlabs.android.dbflow.processor.ClassNames import com.raizlabs.android.dbflow.processor.ProcessorManager +import com.raizlabs.android.dbflow.processor.utils.annotation import com.squareup.javapoet.ClassName import javax.lang.model.element.Element import javax.lang.model.element.ExecutableElement @@ -14,8 +15,8 @@ import javax.lang.model.element.TypeElement class NotifyDefinition(typeElement: Element, processorManager: ProcessorManager) : BaseDefinition(typeElement, processorManager) { - var paths: Array - var method: Notify.Method + var paths = arrayOf() + var method = Notify.Method.DELETE val parent = (typeElement.enclosingElement as TypeElement).qualifiedName.toString() val methodName = typeElement.simpleName.toString() var params: String @@ -24,11 +25,10 @@ class NotifyDefinition(typeElement: Element, processorManager: ProcessorManager) init { - val notify = typeElement.getAnnotation(Notify::class.java) - - paths = notify.paths - - method = notify.method + typeElement.annotation()?.let { notify -> + paths = notify.paths + method = notify.method + } val executableElement = typeElement as ExecutableElement 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 78198fd99..6a8c702b5 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 @@ -1,35 +1,31 @@ package com.raizlabs.android.dbflow.processor.definition -import com.google.common.collect.Lists +import com.grosner.kpoet.* import com.raizlabs.android.dbflow.annotation.OneToMany import com.raizlabs.android.dbflow.processor.ClassNames import com.raizlabs.android.dbflow.processor.ProcessorManager -import com.raizlabs.android.dbflow.processor.ProcessorUtils import com.raizlabs.android.dbflow.processor.definition.column.* -import com.raizlabs.android.dbflow.processor.utils.ModelUtils -import com.raizlabs.android.dbflow.processor.utils.addStatement -import com.raizlabs.android.dbflow.processor.utils.controlFlow +import com.raizlabs.android.dbflow.processor.utils.* import com.squareup.javapoet.* -import java.util.* import javax.lang.model.element.ExecutableElement import javax.lang.model.element.TypeElement /** * Description: Represents the [OneToMany] annotation. */ -class OneToManyDefinition(typeElement: ExecutableElement, - processorManager: ProcessorManager) : BaseDefinition(typeElement, processorManager) { +class OneToManyDefinition(executableElement: ExecutableElement, + processorManager: ProcessorManager) : BaseDefinition(executableElement, processorManager) { private var _methodName: String private var _variableName: String - var methods: MutableList = Lists.newArrayList() + var methods = mutableListOf() - val isLoad: Boolean + val isLoad get() = isAll || methods.contains(OneToMany.Method.LOAD) - val isAll: Boolean + val isAll get() = methods.contains(OneToMany.Method.ALL) val isDelete: Boolean @@ -38,35 +34,54 @@ class OneToManyDefinition(typeElement: ExecutableElement, val isSave: Boolean get() = isAll || methods.contains(OneToMany.Method.SAVE) + var referencedTableType: TypeName? = null + var hasWrapper = false + private var columnAccessor: ColumnAccessor - private var extendsBaseModel: Boolean = false - private var extendsModel: Boolean = false - private var referencedTableType: TypeName? = null + private var extendsModel = false private var referencedType: TypeElement? = null + private var efficientCodeMethods = false + init { - val oneToMany = typeElement.getAnnotation(OneToMany::class.java) + val oneToMany = executableElement.annotation()!! - _methodName = typeElement.simpleName.toString() + efficientCodeMethods = oneToMany.efficientMethods + + _methodName = executableElement.simpleName.toString() _variableName = oneToMany.variableName if (_variableName.isEmpty()) { _variableName = _methodName.replace("get", "") _variableName = _variableName.substring(0, 1).toLowerCase() + _variableName.substring(1) } - methods.addAll(Arrays.asList(*oneToMany.methods)) + methods.addAll(oneToMany.methods) + + val parameters = executableElement.parameters + if (parameters.isNotEmpty()) { + if (parameters.size > 1) { + manager.logError(OneToManyDefinition::class, "OneToMany Methods can only have one parameter and that be the DatabaseWrapper.") + } else { + val param = parameters[0] + val name = param.asType().typeName + if (name == ClassNames.DATABASE_WRAPPER) { + hasWrapper = true + } else { + manager.logError(OneToManyDefinition::class, "OneToMany Methods can only specify a ${ClassNames.DATABASE_WRAPPER} as its parameter.") + } + } + } if (oneToMany.isVariablePrivate) { columnAccessor = PrivateScopeColumnAccessor(_variableName, object : GetterSetter { override val getterName: String = "" override val setterName: String = "" - }) + }, optionalGetterParam = if (hasWrapper) ModelUtils.wrapper else "") } else { columnAccessor = VisibleScopeColumnAccessor(_variableName) } - extendsBaseModel = false - val returnType = typeElement.returnType + val returnType = executableElement.returnType val typeName = TypeName.get(returnType) if (typeName is ParameterizedTypeName) { val typeArguments = typeName.typeArguments @@ -77,17 +92,19 @@ class OneToManyDefinition(typeElement: ExecutableElement, } referencedTableType = refTableType - referencedType = manager.elements.getTypeElement(referencedTableType?.toString()) - extendsBaseModel = ProcessorUtils.isSubclass(manager.processingEnvironment, - ClassNames.BASE_MODEL.toString(), referencedType) - extendsModel = ProcessorUtils.isSubclass(manager.processingEnvironment, - ClassNames.MODEL.toString(), referencedType) + referencedType = referencedTableType.toTypeElement(manager) + extendsModel = referencedType.isSubclass(manager.processingEnvironment, ClassNames.MODEL) } } + } - private val methodName = String.format("%1s.%1s()", ModelUtils.variable, _methodName) + private val methodName = "${ModelUtils.variable}.$_methodName(${wrapperIfBaseModel(hasWrapper)})" + fun writeWrapperStatement(method: MethodSpec.Builder) { + method.statement("\$T ${ModelUtils.wrapper} = \$T.getWritableDatabaseForTable(\$T.class)", + ClassNames.DATABASE_WRAPPER, ClassNames.FLOW_MANAGER, referencedTableType) + } /** * Writes the method to the specified builder for loading from DB. @@ -100,50 +117,51 @@ class OneToManyDefinition(typeElement: ExecutableElement, /** * Writes a delete method that will delete all related objects. - - * @param codeBuilder */ - fun writeDelete(codeBuilder: CodeBlock.Builder, useWrapper: Boolean) { + fun writeDelete(method: MethodSpec.Builder, useWrapper: Boolean) { if (isDelete) { - writeLoopWithMethod(codeBuilder, "delete", useWrapper && extendsBaseModel) - codeBuilder.addStatement(columnAccessor.set(CodeBlock.of("null"), modelBlock)) + writeLoopWithMethod(method, "delete", useWrapper) + method.statement(columnAccessor.set(CodeBlock.of("null"), modelBlock)) } } - fun writeSave(codeBuilder: CodeBlock.Builder, useWrapper: Boolean) { - if (isSave) writeLoopWithMethod(codeBuilder, "save", useWrapper && extendsBaseModel) + fun writeSave(codeBuilder: MethodSpec.Builder, useWrapper: Boolean) { + if (isSave) writeLoopWithMethod(codeBuilder, "save", useWrapper) } - fun writeUpdate(codeBuilder: CodeBlock.Builder, useWrapper: Boolean) { - if (isSave) writeLoopWithMethod(codeBuilder, "update", useWrapper && extendsBaseModel) + fun writeUpdate(codeBuilder: MethodSpec.Builder, useWrapper: Boolean) { + if (isSave) writeLoopWithMethod(codeBuilder, "update", useWrapper) } - fun writeInsert(codeBuilder: CodeBlock.Builder, useWrapper: Boolean) { - if (isSave) writeLoopWithMethod(codeBuilder, "insert", useWrapper && (extendsBaseModel || !extendsModel)) + fun writeInsert(codeBuilder: MethodSpec.Builder, useWrapper: Boolean) { + if (isSave) writeLoopWithMethod(codeBuilder, "insert", useWrapper) } - private fun writeLoopWithMethod(codeBuilder: CodeBlock.Builder, methodName: String, useWrapper: Boolean) { + private fun writeLoopWithMethod(codeBuilder: MethodSpec.Builder, methodName: String, useWrapper: Boolean) { val oneToManyMethodName = this@OneToManyDefinition.methodName codeBuilder.apply { - codeBuilder.controlFlow("if (\$L != null) ", oneToManyMethodName) { - val loopClass: ClassName? = if (extendsBaseModel) ClassNames.BASE_MODEL else ClassName.get(referencedType) - + `if`("$oneToManyMethodName != null") { // need to load adapter for non-model classes - if (!extendsModel) { - addStatement("\$T adapter = \$T.getModelAdapter(\$T.class)", + statement("\$T adapter = \$T.getModelAdapter(\$T.class)", ParameterizedTypeName.get(ClassNames.MODEL_ADAPTER, referencedTableType), ClassNames.FLOW_MANAGER, referencedTableType) + } - addStatement("adapter.\$LAll(\$L\$L)", methodName, oneToManyMethodName, - if (useWrapper) ", " + ModelUtils.wrapper else "") + if (efficientCodeMethods) { + statement("adapter.${methodName}All($oneToManyMethodName${wrapperCommaIfBaseModel(useWrapper)})") } else { - controlFlow("for (\$T value: \$L) ", loopClass, oneToManyMethodName) { - codeBuilder.addStatement("value.\$L(\$L)", methodName, if (useWrapper) ModelUtils.wrapper else "") + `for`("\$T value: $oneToManyMethodName", ClassName.get(referencedType)) { + if (!extendsModel) { + statement("adapter.$methodName(value${wrapperCommaIfBaseModel(useWrapper)})") + } else { + statement("value.$methodName(${wrapperIfBaseModel(useWrapper)})") + } + this } } } - } + }.end() } - } + diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/QueryModelDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/QueryModelDefinition.kt index 21591c6e1..c7e38409f 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/QueryModelDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/QueryModelDefinition.kt @@ -1,15 +1,19 @@ package com.raizlabs.android.dbflow.processor.definition -import com.grosner.kpoet.* +import com.grosner.kpoet.`return` +import com.grosner.kpoet.final +import com.grosner.kpoet.modifiers +import com.grosner.kpoet.public import com.raizlabs.android.dbflow.annotation.Column import com.raizlabs.android.dbflow.annotation.QueryModel import com.raizlabs.android.dbflow.processor.ClassNames import com.raizlabs.android.dbflow.processor.ColumnValidator import com.raizlabs.android.dbflow.processor.ProcessorManager -import com.raizlabs.android.dbflow.processor.ProcessorUtils import com.raizlabs.android.dbflow.processor.definition.column.ColumnDefinition import com.raizlabs.android.dbflow.processor.utils.ElementUtility import com.raizlabs.android.dbflow.processor.utils.`override fun` +import com.raizlabs.android.dbflow.processor.utils.annotation +import com.raizlabs.android.dbflow.processor.utils.implementsClass import com.squareup.javapoet.ParameterizedTypeName import com.squareup.javapoet.TypeName import com.squareup.javapoet.TypeSpec @@ -34,21 +38,19 @@ class QueryModelDefinition(typeElement: Element, processorManager: ProcessorMana init { - val queryModel = typeElement.getAnnotation(QueryModel::class.java) - if (queryModel != null) { + typeElement.annotation()?.let { queryModel -> try { queryModel.database } catch (mte: MirroredTypeException) { databaseTypeName = TypeName.get(mte.typeMirror) } - } elementClassName?.let { databaseTypeName?.let { it1 -> processorManager.addModelToDatabase(it, it1) } } if (element is TypeElement) { - implementsLoadFromCursorListener = ProcessorUtils.implementsClass(manager.processingEnvironment, ClassNames.LOAD_FROM_CURSOR_LISTENER.toString(), - element as TypeElement) + implementsLoadFromCursorListener = + (element as TypeElement).implementsClass(manager.processingEnvironment, ClassNames.LOAD_FROM_CURSOR_LISTENER) } @@ -61,10 +63,9 @@ class QueryModelDefinition(typeElement: Element, processorManager: ProcessorMana columnDefinitions.clear() packagePrivateList.clear() - val queryModel = typeElement?.getAnnotation(QueryModel::class.java) - if (queryModel != null) { + typeElement.annotation()?.let { queryModel -> databaseDefinition = manager.getDatabaseHolderDefinition(databaseTypeName)?.databaseDefinition - setOutputClassName(databaseDefinition?.classSeparator + DBFLOW_QUERY_MODEL_TAG) + setOutputClassName("${databaseDefinition?.classSeparator}QueryTable") allFields = queryModel.allFields typeElement?.let { createColumnDefinitions(it) } @@ -75,33 +76,21 @@ class QueryModelDefinition(typeElement: Element, processorManager: ProcessorMana get() = ParameterizedTypeName.get(ClassNames.QUERY_MODEL_ADAPTER, elementClassName) override fun onWriteDefinition(typeBuilder: TypeSpec.Builder) { - elementClassName?.let { className -> columnDefinitions.forEach { it.addPropertyDefinition(typeBuilder, className) } } - - val customTypeConverterPropertyMethod = CustomTypeConverterPropertyMethod(this) - customTypeConverterPropertyMethod.addToType(typeBuilder) - + typeBuilder.apply { + elementClassName?.let { className -> columnDefinitions.forEach { it.addPropertyDefinition(this, className) } } + writeGetModelClass(typeBuilder, elementClassName) - InternalAdapterHelper.writeGetModelClass(typeBuilder, elementClassName) + writeConstructor(this) - typeBuilder.constructor(param(ClassNames.DATABASE_HOLDER, "holder"), - param(ClassNames.BASE_DATABASE_DEFINITION_CLASSNAME, "databaseDefinition")) { - modifiers(public) - statement("super(databaseDefinition)") - code { - customTypeConverterPropertyMethod.addCode(this) - } - } - - methods.mapNotNull { it.methodSpec } - .forEach { typeBuilder.addMethod(it) } - - typeBuilder.apply { `override fun`(elementClassName!!, "newInstance") { modifiers(public, final) `return`("new \$T()", elementClassName) } } + + methods.mapNotNull { it.methodSpec } + .forEach { typeBuilder.addMethod(it) } } override fun createColumnDefinitions(typeElement: TypeElement) { @@ -115,12 +104,12 @@ class QueryModelDefinition(typeElement: Element, processorManager: ProcessorMana for (variableElement in variableElements) { // no private static or final fields - val isAllFields = ElementUtility.isValidAllFields(allFields, element) + val isAllFields = ElementUtility.isValidAllFields(allFields, variableElement) // package private, will generate helper - val isPackagePrivate = ElementUtility.isPackagePrivate(element) - val isPackagePrivateNotInSamePackage = isPackagePrivate && !ElementUtility.isInSamePackage(manager, element, this.element) + val isPackagePrivate = ElementUtility.isPackagePrivate(variableElement) + val isPackagePrivateNotInSamePackage = isPackagePrivate && !ElementUtility.isInSamePackage(manager, variableElement, this.element) - if (variableElement.getAnnotation(Column::class.java) != null || isAllFields) { + if (variableElement.annotation() != null || isAllFields) { val columnDefinition = ColumnDefinition(manager, variableElement, this, isPackagePrivateNotInSamePackage) if (columnValidator.validate(manager, columnDefinition)) { @@ -138,9 +127,4 @@ class QueryModelDefinition(typeElement: Element, processorManager: ProcessorMana val primaryColumnDefinitions: List get() = ArrayList() - companion object { - - private val DBFLOW_QUERY_MODEL_TAG = "QueryTable" - } - } 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 06759ebcf..883b41a28 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 @@ -1,11 +1,14 @@ package com.raizlabs.android.dbflow.processor.definition import com.google.common.collect.Lists -import com.google.common.collect.Maps import com.grosner.kpoet.* import com.raizlabs.android.dbflow.annotation.* -import com.raizlabs.android.dbflow.processor.* +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.column.ColumnDefinition +import com.raizlabs.android.dbflow.processor.definition.column.DefinitionUtils import com.raizlabs.android.dbflow.processor.definition.column.ForeignKeyColumnDefinition import com.raizlabs.android.dbflow.processor.utils.* import com.raizlabs.android.dbflow.sql.QueryBuilder @@ -32,10 +35,10 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab var primaryKeyConflictActionName: String = "" - var _primaryColumnDefinitions: MutableList - var foreignKeyDefinitions: MutableList - var uniqueGroupsDefinitions: MutableList - var indexGroupsDefinitions: MutableList + val _primaryColumnDefinitions = mutableListOf() + val foreignKeyDefinitions = mutableListOf() + val uniqueGroupsDefinitions = mutableListOf() + val indexGroupsDefinitions = mutableListOf() var implementsContentValuesListener = false @@ -53,28 +56,21 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab var allFields = false var useIsForPrivateBooleans: Boolean = false - val columnMap: MutableMap = Maps.newHashMap() + val columnMap = mutableMapOf() - var columnUniqueMap: MutableMap> - = Maps.newHashMap>() + var columnUniqueMap = mutableMapOf>() - var oneToManyDefinitions: MutableList = ArrayList() + var oneToManyDefinitions = mutableListOf() - var inheritedColumnMap: MutableMap = HashMap() - var inheritedFieldNameList: MutableList = ArrayList() - var inheritedPrimaryKeyMap: MutableMap = HashMap() + var inheritedColumnMap = hashMapOf() + var inheritedFieldNameList = mutableListOf() + var inheritedPrimaryKeyMap = hashMapOf() var hasPrimaryConstructor = false init { - _primaryColumnDefinitions = ArrayList() - foreignKeyDefinitions = ArrayList() - uniqueGroupsDefinitions = ArrayList() - indexGroupsDefinitions = ArrayList() - - val table = element.getAnnotation(Table::class.java) - if (table != null) { + element.annotation
()?.let { table -> this.tableName = table.name if (tableName == null || tableName!!.isEmpty()) { @@ -119,14 +115,14 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab inheritedPrimaryKeyMap.put(it.fieldName, it) } - implementsLoadFromCursorListener = ProcessorUtils.implementsClass(manager.processingEnvironment, - ClassNames.LOAD_FROM_CURSOR_LISTENER.toString(), element) + implementsLoadFromCursorListener = element.implementsClass(manager.processingEnvironment, + ClassNames.LOAD_FROM_CURSOR_LISTENER) - implementsContentValuesListener = ProcessorUtils.implementsClass(manager.processingEnvironment, - ClassNames.CONTENT_VALUES_LISTENER.toString(), element) + implementsContentValuesListener = element.implementsClass(manager.processingEnvironment, + ClassNames.CONTENT_VALUES_LISTENER) - implementsSqlStatementListener = ProcessorUtils.implementsClass(manager.processingEnvironment, - ClassNames.SQLITE_STATEMENT_LISTENER.toString(), element) + implementsSqlStatementListener = element.implementsClass(manager.processingEnvironment, + ClassNames.SQLITE_STATEMENT_LISTENER) } methods = arrayOf(BindToContentValuesMethod(this, true, implementsContentValuesListener), @@ -165,24 +161,23 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab } databaseDefinition?.let { - setOutputClassName(it.classSeparator + DBFLOW_TABLE_TAG) + setOutputClassName("${it.classSeparator}Table") // globular default - var insertConflict: ConflictAction? = table.insertConflict + var insertConflict = table.insertConflict if (insertConflict == ConflictAction.NONE && it.insertConflict != ConflictAction.NONE) { - insertConflict = it.insertConflict + insertConflict = it.insertConflict ?: ConflictAction.NONE } - var updateConflict: ConflictAction? = table.updateConflict - if (updateConflict == ConflictAction.NONE - && it.updateConflict != ConflictAction.NONE) { - updateConflict = it.updateConflict + var updateConflict = table.updateConflict + if (updateConflict == ConflictAction.NONE && it.updateConflict != ConflictAction.NONE) { + updateConflict = it.updateConflict ?: ConflictAction.NONE } val primaryKeyConflict = table.primaryKeyConflict - insertConflictActionName = if (insertConflict == ConflictAction.NONE) "" else insertConflict?.name ?: "" - updateConflictActionName = if (updateConflict == ConflictAction.NONE) "" else updateConflict?.name ?: "" + insertConflictActionName = if (insertConflict == ConflictAction.NONE) "" else insertConflict.name + updateConflictActionName = if (updateConflict == ConflictAction.NONE) "" else updateConflict.name primaryKeyConflictActionName = if (primaryKeyConflict == ConflictAction.NONE) "" else primaryKeyConflict.name } @@ -195,11 +190,8 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab manager.logError("A duplicate unique group with number %1s was found for %1s", uniqueGroup.groupNumber, tableName) } val definition = UniqueGroupsDefinition(uniqueGroup) - for (columnDefinition in columnDefinitions) { - if (columnDefinition.uniqueGroups.contains(definition.number)) { - definition.addColumnDefinition(columnDefinition) - } - } + columnDefinitions.filter { it.uniqueGroups.contains(definition.number) } + .forEach { definition.addColumnDefinition(it) } uniqueGroupsDefinitions.add(definition) uniqueNumbersSet.add(uniqueGroup.groupNumber) } @@ -211,11 +203,8 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab manager.logError(TableDefinition::class, "A duplicate unique index number %1s was found for %1s", indexGroup.number, elementName) } val definition = IndexGroupsDefinition(this, indexGroup) - columnDefinitions.forEach { - if (it.indexGroups.contains(definition.indexNumber)) { - definition.columnDefinitionList.add(it) - } - } + columnDefinitions.filter { it.indexGroups.contains(definition.indexNumber) } + .forEach { definition.columnDefinitionList.add(it) } indexGroupsDefinitions.add(definition) uniqueNumbersSet.add(indexGroup.number) } @@ -246,11 +235,11 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab val isPackagePrivate = ElementUtility.isPackagePrivate(element) val isPackagePrivateNotInSamePackage = isPackagePrivate && !ElementUtility.isInSamePackage(manager, element, this.element) - val isForeign = element.getAnnotation(ForeignKey::class.java) != null - val isPrimary = element.getAnnotation(PrimaryKey::class.java) != null + val isForeign = element.annotation() != null + val isPrimary = element.annotation() != null val isInherited = inheritedColumnMap.containsKey(element.simpleName.toString()) val isInheritedPrimaryKey = inheritedPrimaryKeyMap.containsKey(element.simpleName.toString()) - if (element.getAnnotation(Column::class.java) != null || isForeign || isPrimary + if (element.annotation() != null || isForeign || isPrimary || isAllFields || isInherited || isInheritedPrimaryKey) { val columnDefinition: ColumnDefinition @@ -290,14 +279,12 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab if (!columnDefinition.uniqueGroups.isEmpty()) { val groups = columnDefinition.uniqueGroups for (group in groups) { - var groupList: MutableList? = columnUniqueMap[group] + var groupList = columnUniqueMap[group] if (groupList == null) { - groupList = ArrayList() + groupList = mutableSetOf() columnUniqueMap.put(group, groupList) } - if (!groupList.contains(columnDefinition)) { - groupList.add(columnDefinition) - } + groupList.add(columnDefinition) } } @@ -305,20 +292,20 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab packagePrivateList.add(columnDefinition) } } - } else if (element.getAnnotation(OneToMany::class.java) != null) { + } else if (element.annotation() != null) { val oneToManyDefinition = OneToManyDefinition(element as ExecutableElement, manager) if (oneToManyValidator.validate(manager, oneToManyDefinition)) { oneToManyDefinitions.add(oneToManyDefinition) } - } else if (element.getAnnotation(ModelCacheField::class.java) != null) { - ProcessorUtils.ensureVisibleStatic(element, typeElement, "ModelCacheField") + } else if (element.annotation() != null) { + ensureVisibleStatic(element, typeElement, "ModelCacheField") if (!customCacheFieldName.isNullOrEmpty()) { manager.logError("ModelCacheField can only be declared once from: " + typeElement) } else { customCacheFieldName = element.simpleName.toString() } - } else if (element.getAnnotation(MultiCacheField::class.java) != null) { - ProcessorUtils.ensureVisibleStatic(element, typeElement, "MultiCacheField") + } else if (element.annotation() != null) { + ensureVisibleStatic(element, typeElement, "MultiCacheField") if (!customMultiCacheFieldName.isNullOrEmpty()) { manager.logError("MultiCacheField can only be declared once from: " + typeElement) } else { @@ -326,7 +313,6 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab } } } - } override val primaryColumnDefinitions: List @@ -340,57 +326,78 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab get() = ParameterizedTypeName.get(ClassNames.MODEL_ADAPTER, elementClassName) override fun onWriteDefinition(typeBuilder: TypeSpec.Builder) { + typeBuilder.apply { - InternalAdapterHelper.writeGetModelClass(typeBuilder, elementClassName) - InternalAdapterHelper.writeGetTableName(typeBuilder, tableName) + writeGetModelClass(this, elementClassName) + writeConstructor(this) - val getAllColumnPropertiesMethod = FieldSpec.builder( - ArrayTypeName.of(ClassNames.IPROPERTY), "ALL_COLUMN_PROPERTIES", - Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) - val getPropertiesBuilder = CodeBlock.builder() + `override fun`(String::class, "getTableName") { + modifiers(public, final) + `return`(QueryBuilder.quote(tableName).S) + } - val paramColumnName = "columnName" - val getPropertyForNameMethod = MethodSpec.methodBuilder("getProperty") - .addAnnotation(Override::class.java) - .addParameter(ClassName.get(String::class.java), paramColumnName) - .addModifiers(Modifier.PUBLIC, Modifier.FINAL) - .returns(ClassNames.PROPERTY) + `override fun`(elementClassName!!, "newInstance") { + modifiers(public, final) + `return`("new \$T()", elementClassName) + } - getPropertyForNameMethod.addStatement("\$L = \$T.quoteIfNeeded(\$L)", paramColumnName, - ClassName.get(QueryBuilder::class.java), paramColumnName) + if (updateConflictActionName.isNotEmpty()) { + `override fun`(ClassNames.CONFLICT_ACTION, "getUpdateOnConflictAction") { + modifiers(public, final) + `return`("\$T.$updateConflictActionName", ClassNames.CONFLICT_ACTION) + } + } - getPropertyForNameMethod.beginControlFlow("switch (\$L) ", paramColumnName) - columnDefinitions.indices.forEach { i -> - if (i > 0) { - getPropertiesBuilder.add(",") + if (insertConflictActionName.isNotEmpty()) { + `override fun`(ClassNames.CONFLICT_ACTION, "getInsertOnConflictAction") { + modifiers(public, final) + `return`("\$T.$insertConflictActionName", ClassNames.CONFLICT_ACTION) + } } - val columnDefinition = columnDefinitions[i] - elementClassName?.let { columnDefinition.addPropertyDefinition(typeBuilder, it) } - columnDefinition.addPropertyCase(getPropertyForNameMethod) - columnDefinition.addColumnName(getPropertiesBuilder) - } - getPropertyForNameMethod.controlFlow("default:") { - addStatement("throw new \$T(\$S)", IllegalArgumentException::class.java, - "Invalid column name passed. Ensure you are calling the correct table's column") - } - getPropertyForNameMethod.endControlFlow() - getAllColumnPropertiesMethod.initializer("new \$T[]{\$L}", ClassNames.IPROPERTY, getPropertiesBuilder.build().toString()) - typeBuilder.addField(getAllColumnPropertiesMethod.build()) + val paramColumnName = "columnName" + val getPropertiesBuilder = CodeBlock.builder() - // add index properties here - for (indexGroupsDefinition in indexGroupsDefinitions) { - typeBuilder.addField(indexGroupsDefinition.fieldSpec) - } + `override fun`(ClassNames.PROPERTY, "getProperty", + param(String::class, paramColumnName)) { + modifiers(public, final) + statement("$paramColumnName = \$T.quoteIfNeeded($paramColumnName)", ClassName.get(QueryBuilder::class.java)) - typeBuilder.addMethod(getPropertyForNameMethod.build()) + switch("($paramColumnName)") { + columnDefinitions.indices.forEach { i -> + if (i > 0) { + getPropertiesBuilder.add(",") + } + val columnDefinition = columnDefinitions[i] + elementClassName?.let { columnDefinition.addPropertyDefinition(typeBuilder, it) } + columnDefinition.addPropertyCase(this) + columnDefinition.addColumnName(getPropertiesBuilder) + } - if (hasAutoIncrement || hasRowID) { - val autoIncrement = autoIncrementColumn - autoIncrement?.let { - InternalAdapterHelper.writeUpdateAutoIncrement(typeBuilder, elementClassName, autoIncrement) + default { + `throw new`(IllegalArgumentException::class, "Invalid column name passed. Ensure you are calling the correct table's column") + } + } + } + + `public static final field`(ArrayTypeName.of(ClassNames.IPROPERTY), "ALL_COLUMN_PROPERTIES") { + `=`("new \$T[]{\$L}", ClassNames.IPROPERTY, getPropertiesBuilder.build().toString()) + } + + // add index properties here + for (indexGroupsDefinition in indexGroupsDefinitions) { + addField(indexGroupsDefinition.fieldSpec) + } + + if (hasAutoIncrement || hasRowID) { + val autoIncrement = autoIncrementColumn + autoIncrement?.let { + `override fun`(TypeName.VOID, "updateAutoIncrement", param(elementClassName!!, ModelUtils.variable), + param(Number::class, "id")) { + modifiers(public, final) + addCode(autoIncrement.updateAutoIncrementMethod) + } - typeBuilder.apply { `override fun`(Number::class, "getAutoIncrementingId", param(elementClassName!!, ModelUtils.variable)) { modifiers(public, final) addCode(autoIncrement.getSimpleAccessString()) @@ -401,49 +408,42 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab } } } - } - val saveForeignKeyFields = columnDefinitions.filter { (it is ForeignKeyColumnDefinition) && it.saveForeignKeyModel } - .map { it as ForeignKeyColumnDefinition } - if (saveForeignKeyFields.isNotEmpty()) { - val code = CodeBlock.builder() - saveForeignKeyFields.forEach { - it.appendSaveMethod(code) - } + val saveForeignKeyFields = columnDefinitions + .filter { (it is ForeignKeyColumnDefinition) && it.saveForeignKeyModel } + .map { it as ForeignKeyColumnDefinition } + if (saveForeignKeyFields.isNotEmpty()) { + val code = CodeBlock.builder() + saveForeignKeyFields.forEach { it.appendSaveMethod(code) } - typeBuilder.apply { `override fun`(TypeName.VOID, "saveForeignKeys", param(elementClassName!!, ModelUtils.variable), 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 } - if (deleteForeignKeyFields.isNotEmpty()) { - val code = CodeBlock.builder() - deleteForeignKeyFields.forEach { - it.appendDeleteMethod(code) + val deleteForeignKeyFields = columnDefinitions + .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)) { + modifiers(public, final) + addCode(code.build()) + } } - typeBuilder.`override fun`(TypeName.VOID, "deleteForeignKeys", param(elementClassName!!, ModelUtils.variable), - param(ClassNames.DATABASE_WRAPPER, ModelUtils.wrapper)) { + + `override fun`(ArrayTypeName.of(ClassNames.IPROPERTY), "getAllColumnProperties") { modifiers(public, final) - addCode(code.build()) + `return`("ALL_COLUMN_PROPERTIES") } - } - typeBuilder.`fun`(ArrayTypeName.of(ClassNames.IPROPERTY), "getAllColumnProperties") { - modifiers(public, final) - `return`("ALL_COLUMN_PROPERTIES") - } - - if (cachingEnabled) { + if (cachingEnabled) { - // TODO: pass in model cache loaders. - - typeBuilder.apply { val singlePrimaryKey = primaryColumnDefinitions.size == 1 `override fun`(ClassNames.SINGLE_MODEL_LOADER, "createSingleModelLoader") { @@ -472,21 +472,55 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab `return`(true.L) } - val primaries = primaryColumnDefinitions - InternalAdapterHelper.writeGetCachingId(this, elementClassName, primaries) + 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)) { + modifiers(public, final) + for (i in primaryColumns.indices) { + val column = primaryColumns[i] + addCode(column.getColumnAccessString(i)) + } - `override fun`(ArrayTypeName.of(ClassName.get(String::class.java)), "createCachingColumns") { - modifiers(public, final) - var columns = "return new String[]{" - primaries.indices.forEach { i -> - val column = primaries[i] - if (i > 0) { - columns += "," + `return`("inValues") + } + + `override fun`(ArrayTypeName.of(Any::class.java), "getCachingColumnValuesFromCursor", + 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}))") } - columns += "\"" + QueryBuilder.quoteIfNeeded(column.columnName) + "\"" + `return`("inValues") + } + } else { + // single primary key + `override fun`(Any::class, "getCachingColumnValueFromModel", + param(elementClassName!!, ModelUtils.variable)) { + modifiers(public, final) + addCode(primaryColumns[0].getSimpleAccessString()) + } + + `override fun`(Any::class, "getCachingColumnValueFromCursor", param(ClassNames.FLOW_CURSOR, "cursor")) { + modifiers(public, final) + val column = primaryColumns[0] + val method = DefinitionUtils.getLoadFromCursorMethodString(column.elementTypeName, column.wrapperTypeName) + `return`("${LoadFromCursorMethod.PARAM_CURSOR}.$method(${LoadFromCursorMethod.PARAM_CURSOR}.getColumnIndex(${column.columnName.S}))") + } + `override fun`(Any::class, "getCachingId", param(elementClassName!!, ModelUtils.variable)) { + modifiers(public, final) + `return`("getCachingColumnValueFromModel(${ModelUtils.variable})") } - columns += "}" - statement(columns) + } + + `override fun`(ArrayTypeName.of(ClassName.get(String::class.java)), "createCachingColumns") { + modifiers(public, final) + `return`("new String[]{${primaryColumns.joinToString { QueryBuilder.quoteIfNeeded(it.columnName).S }}}") } if (cacheSize != Table.DEFAULT_CACHE_SIZE) { @@ -500,7 +534,7 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab `override fun`(ParameterizedTypeName.get(ClassNames.MODEL_CACHE, elementClassName, WildcardTypeName.subtypeOf(Any::class.java)), "createModelCache") { modifiers(public, final) - `return`("\$T.\$L", elementClassName, customCacheFieldName) + `return`("\$T.$customCacheFieldName", elementClassName) } } @@ -508,65 +542,27 @@ class TableDefinition(manager: ProcessorManager, element: TypeElement) : BaseTab `override fun`(ParameterizedTypeName.get(ClassNames.MULTI_KEY_CACHE_CONVERTER, WildcardTypeName.subtypeOf(Any::class.java)), "getCacheConverter") { modifiers(public, final) - `return`("\$T.\$L", elementClassName, customMultiCacheFieldName) + `return`("\$T.$customMultiCacheFieldName", elementClassName) } } - `override fun`(TypeName.VOID, "reloadRelationships", - param(elementClassName!!, ModelUtils.variable), - param(ClassNames.CURSOR, LoadFromCursorMethod.PARAM_CURSOR)) { - modifiers(public, final) - code { - val noIndex = AtomicInteger(-1) - - foreignKeyDefinitions.forEach { - add(it.getLoadFromCursorMethod(false, noIndex)) + if (foreignKeyDefinitions.isNotEmpty()) { + `override fun`(TypeName.VOID, "reloadRelationships", + param(elementClassName!!, ModelUtils.variable), + param(ClassNames.FLOW_CURSOR, LoadFromCursorMethod.PARAM_CURSOR)) { + modifiers(public, final) + code { + val noIndex = AtomicInteger(-1) + val nameAllocator = NameAllocator() + foreignKeyDefinitions.forEach { add(it.getLoadFromCursorMethod(false, noIndex, nameAllocator)) } + this } - this } } } } - val customTypeConverterPropertyMethod = CustomTypeConverterPropertyMethod(this) - customTypeConverterPropertyMethod.addToType(typeBuilder) - - typeBuilder.apply { - constructor(param(ClassNames.DATABASE_HOLDER, "holder"), - param(ClassNames.BASE_DATABASE_DEFINITION_CLASSNAME, "databaseDefinition")) { - modifiers(public) - statement("super(databaseDefinition)") - code { - customTypeConverterPropertyMethod.addCode(this) - } - } - - `override fun`(elementClassName!!, "newInstance") { - modifiers(public, final) - `return`("new \$T()", elementClassName) - } - - if (!updateConflictActionName.isEmpty()) { - `override fun`(ClassNames.CONFLICT_ACTION, "getUpdateOnConflictAction") { - modifiers(public, final) - `return`("\$T.\$L", ClassNames.CONFLICT_ACTION, updateConflictActionName) - } - } - - if (!insertConflictActionName.isEmpty()) { - `override fun`(ClassNames.CONFLICT_ACTION, "getInsertOnConflictAction") { - modifiers(public, final) - `return`("\$T.\$L", ClassNames.CONFLICT_ACTION, insertConflictActionName) - } - } - } - methods.mapNotNull { it.methodSpec } .forEach { typeBuilder.addMethod(it) } } - - companion object { - - val DBFLOW_TABLE_TAG = "Table" - } } diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/TableEndpointDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/TableEndpointDefinition.kt index ca8f28d2f..3ade6f0cb 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/TableEndpointDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/TableEndpointDefinition.kt @@ -4,6 +4,7 @@ import com.raizlabs.android.dbflow.annotation.provider.ContentUri import com.raizlabs.android.dbflow.annotation.provider.Notify import com.raizlabs.android.dbflow.annotation.provider.TableEndpoint import com.raizlabs.android.dbflow.processor.ProcessorManager +import com.raizlabs.android.dbflow.processor.utils.annotation import com.squareup.javapoet.TypeName import javax.lang.model.element.Element import javax.lang.model.element.PackageElement @@ -14,7 +15,7 @@ import javax.lang.model.type.MirroredTypeException * Description: */ class TableEndpointDefinition(typeElement: Element, processorManager: ProcessorManager) -: BaseDefinition(typeElement, processorManager) { + : BaseDefinition(typeElement, processorManager) { var contentUriDefinitions: MutableList = mutableListOf() @@ -34,9 +35,7 @@ class TableEndpointDefinition(typeElement: Element, processorManager: ProcessorM init { - val endpoint = typeElement.getAnnotation(TableEndpoint::class.java) - if (endpoint != null) { - + typeElement.annotation()?.let { endpoint -> tableName = endpoint.name try { @@ -44,21 +43,20 @@ class TableEndpointDefinition(typeElement: Element, processorManager: ProcessorM } catch (mte: MirroredTypeException) { contentProviderName = TypeName.get(mte.typeMirror) } - } isTopLevel = typeElement.enclosingElement is PackageElement val elements = processorManager.elements.getAllMembers(typeElement as TypeElement) for (innerElement in elements) { - if (innerElement.getAnnotation(ContentUri::class.java) != null) { + if (innerElement.annotation() != null) { val contentUriDefinition = ContentUriDefinition(innerElement, processorManager) if (!pathValidationMap.containsKey(contentUriDefinition.path)) { contentUriDefinitions.add(contentUriDefinition) } else { processorManager.logError("There must be unique paths for the specified @ContentUri" + " %1s from %1s", contentUriDefinition.name, contentProviderName) } - } else if (innerElement.getAnnotation(Notify::class.java) != null) { + } else if (innerElement.annotation() != null) { val notifyDefinition = NotifyDefinition(innerElement, processorManager) for (path in notifyDefinition.paths) { diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/TypeConverterDefinition.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/TypeConverterDefinition.kt index 0b1955df5..0f4e698c0 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/TypeConverterDefinition.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/TypeConverterDefinition.kt @@ -3,6 +3,7 @@ package com.raizlabs.android.dbflow.processor.definition import com.raizlabs.android.dbflow.annotation.TypeConverter import com.raizlabs.android.dbflow.processor.ClassNames import com.raizlabs.android.dbflow.processor.ProcessorManager +import com.raizlabs.android.dbflow.processor.utils.annotation import com.squareup.javapoet.ClassName import com.squareup.javapoet.TypeName import javax.lang.model.element.TypeElement @@ -27,8 +28,7 @@ class TypeConverterDefinition(val className: ClassName, init { - val annotation = typeElement?.getAnnotation(TypeConverter::class.java) - if (annotation != null) { + typeElement.annotation()?.let { annotation -> val allowedSubTypes: MutableList = mutableListOf() try { annotation.allowedSubtypes; 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 c4870132d..9e6dd9688 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 @@ -1,13 +1,16 @@ package com.raizlabs.android.dbflow.processor.definition.column +import com.grosner.kpoet.* import com.raizlabs.android.dbflow.processor.ClassNames import com.raizlabs.android.dbflow.processor.SQLiteHelper import com.raizlabs.android.dbflow.processor.utils.ModelUtils -import com.raizlabs.android.dbflow.processor.utils.addStatement +import com.raizlabs.android.dbflow.processor.utils.catch import com.raizlabs.android.dbflow.processor.utils.isNullOrEmpty +import com.raizlabs.android.dbflow.processor.utils.statement import com.raizlabs.android.dbflow.sql.QueryBuilder import com.squareup.javapoet.ClassName import com.squareup.javapoet.CodeBlock +import com.squareup.javapoet.NameAllocator import com.squareup.javapoet.TypeName data class Combiner(val fieldLevelAccessor: ColumnAccessor, @@ -41,27 +44,20 @@ abstract class ColumnAccessCombiner(val combiner: Combiner) { return fieldAccess } - abstract fun addCode(code: CodeBlock.Builder, - columnRepresentation: String, defaultValue: CodeBlock? = null, - index: Int = -1, - modelBlock: CodeBlock = CodeBlock.of("model")) + abstract fun CodeBlock.Builder.addCode(columnRepresentation: String, defaultValue: CodeBlock? = null, + index: Int = -1, + modelBlock: CodeBlock = CodeBlock.of("model")) open fun addNull(code: CodeBlock.Builder, columnRepresentation: String, index: Int = -1) { } - - protected fun useStoredFieldRef(): Boolean { - return combiner.wrapperLevelAccessor == null && - combiner.fieldLevelAccessor !is VisibleScopeColumnAccessor - } - } class SimpleAccessCombiner(combiner: Combiner) : ColumnAccessCombiner(combiner) { - override fun addCode(code: CodeBlock.Builder, columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock) { - code.addStatement("return \$L", getFieldAccessBlock(code, modelBlock)) + override fun CodeBlock.Builder.addCode(columnRepresentation: String, + defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock) { + statement("return \$L", getFieldAccessBlock(this, modelBlock)) } } @@ -71,36 +67,38 @@ class ExistenceAccessCombiner(combiner: Combiner, val quickCheckPrimaryKey: Boolean, val tableClassName: ClassName) : ColumnAccessCombiner(combiner) { - override fun addCode(code: CodeBlock.Builder, columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock) { + override fun CodeBlock.Builder.addCode(columnRepresentation: String, + defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock) { combiner.apply { if (autoRowId) { - val access = getFieldAccessBlock(code, modelBlock) + val access = getFieldAccessBlock(this@addCode, modelBlock) - code.add("return ") + add("return ") if (!fieldTypeName.isPrimitive) { - code.add("(\$L != null && ", access) + add("(\$L != null && ", access) } - code.add("\$L > 0", access) + add("\$L > 0", access) if (!fieldTypeName.isPrimitive) { - code.add(" || \$L == null)", access) + add(" || \$L == null)", access) } } if (!autoRowId || !quickCheckPrimaryKey) { if (autoRowId) { - code.add("\n&& ") + add("\n&& ") } else { - code.add("return ") + add("return ") } - code.add("\$T.selectCountOf()\n.from(\$T.class)\n.where(getPrimaryConditionClause(\$L))\n.hasData(wrapper)", + add("\$T.selectCountOf()\n.from(\$T.class)\n" + + ".where(getPrimaryConditionClause(\$L))\n" + + ".hasData(wrapper)", ClassNames.SQLITE, tableClassName, modelBlock) } - code.add(";\n") + add(";\n") } } @@ -109,29 +107,24 @@ class ExistenceAccessCombiner(combiner: Combiner, class ContentValuesCombiner(combiner: Combiner) : ColumnAccessCombiner(combiner) { - override fun addCode(code: CodeBlock.Builder, columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, - modelBlock: CodeBlock) { + override fun CodeBlock.Builder.addCode(columnRepresentation: String, + defaultValue: CodeBlock?, index: Int, + modelBlock: CodeBlock) { combiner.apply { - val fieldAccess: CodeBlock = getFieldAccessBlock(code, modelBlock) + val fieldAccess: CodeBlock = getFieldAccessBlock(this@addCode, modelBlock) if (fieldTypeName.isPrimitive) { - code.addStatement("values.put(\$1S, \$2L)", QueryBuilder.quote(columnRepresentation), fieldAccess) + statement("values.put(\$1S, \$2L)", QueryBuilder.quote(columnRepresentation), fieldAccess) } else { if (defaultValue != null) { - var storedFieldAccess = CodeBlock.of("ref\$L", fieldLevelAccessor.propertyName) - if (useStoredFieldRef()) { - code.addStatement("\$T \$L = \$L", fieldTypeName, storedFieldAccess, fieldAccess) - } else { - storedFieldAccess = fieldAccess - } + val storedFieldAccess = fieldAccess var subWrapperFieldAccess = storedFieldAccess if (subWrapperAccessor != null) { subWrapperFieldAccess = subWrapperAccessor.get(storedFieldAccess) } - code.addStatement("values.put(\$S, \$L != null ? \$L : \$L)", + statement("values.put(\$S, \$L != null ? \$L : \$L)", QueryBuilder.quote(columnRepresentation), storedFieldAccess, subWrapperFieldAccess, defaultValue) } else { - code.addStatement("values.put(\$S, \$L)", + statement("values.put(\$S, \$L)", QueryBuilder.quote(columnRepresentation), fieldAccess) } } @@ -145,129 +138,118 @@ class ContentValuesCombiner(combiner: Combiner) class SqliteStatementAccessCombiner(combiner: Combiner) : ColumnAccessCombiner(combiner) { - override fun addCode(code: CodeBlock.Builder, columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, - modelBlock: CodeBlock) { + override fun CodeBlock.Builder.addCode(columnRepresentation: String, + defaultValue: CodeBlock?, index: Int, + modelBlock: CodeBlock) { combiner.apply { - val fieldAccess: CodeBlock = getFieldAccessBlock(code, modelBlock) + val fieldAccess: CodeBlock = getFieldAccessBlock(this@addCode, modelBlock) + val wrapperMethod = SQLiteHelper[wrapperFieldTypeName ?: fieldTypeName].sqliteStatementWrapperMethod + val statementMethod = SQLiteHelper[fieldTypeName].sqLiteStatementMethod if (fieldTypeName.isPrimitive) { - code.addStatement("statement.bind\$L(\$L + \$L, \$L)", - SQLiteHelper[fieldTypeName].sqLiteStatementMethod, - index, columnRepresentation, fieldAccess) + statement("statement.bind$statementMethod($index + $columnRepresentation, $fieldAccess)") } else { - if (defaultValue != null) { - var storedFieldAccess = CodeBlock.of("ref\$L", fieldLevelAccessor.propertyName) - - if (useStoredFieldRef()) { - code.addStatement("\$T \$L = \$L", fieldTypeName, storedFieldAccess, fieldAccess) - } else { - storedFieldAccess = fieldAccess + val subWrapperFieldAccess = subWrapperAccessor?.get(fieldAccess) ?: fieldAccess + if (!defaultValue.toString().isNullOrEmpty()) { + `if`("$fieldAccess != null") { + statement("statement.bind$wrapperMethod($index + $columnRepresentation," + + " $subWrapperFieldAccess)") + }.`else` { + statement("statement.bind$statementMethod($index + $columnRepresentation," + + " $defaultValue)") } - - var subWrapperFieldAccess = storedFieldAccess - if (subWrapperAccessor != null) { - subWrapperFieldAccess = subWrapperAccessor.get(storedFieldAccess) - } - code.beginControlFlow("if (\$L != null) ", storedFieldAccess) - .addStatement("statement.bind\$L(\$L + \$L, \$L)", - SQLiteHelper[wrapperFieldTypeName ?: fieldTypeName].sqLiteStatementMethod, - index, columnRepresentation, subWrapperFieldAccess) - .nextControlFlow("else") - if (!defaultValue.toString().isNullOrEmpty()) { - code.addStatement("statement.bind\$L(\$L + \$L, \$L)", - SQLiteHelper[wrapperFieldTypeName ?: fieldTypeName].sqLiteStatementMethod, - index, columnRepresentation, defaultValue) - } else { - code.addStatement("statement.bindNull(\$L + \$L)", index, columnRepresentation) - } - code.endControlFlow() } else { - code.addStatement("statement.bind\$L(\$L + \$L, \$L)", - SQLiteHelper[wrapperFieldTypeName ?: fieldTypeName].sqLiteStatementMethod, index, - columnRepresentation, fieldAccess) + statement("statement.bind${wrapperMethod}OrNull($index + $columnRepresentation," + + " $subWrapperFieldAccess)") } } } } override fun addNull(code: CodeBlock.Builder, columnRepresentation: String, index: Int) { - code.addStatement("statement.bindNull(\$L + \$L)", index, columnRepresentation) + code.addStatement("statement.bindNull($index + $columnRepresentation)") } } class LoadFromCursorAccessCombiner(combiner: Combiner, + val hasDefaultValue: Boolean, + val nameAllocator: NameAllocator, val orderedCursorLookup: Boolean = false, val assignDefaultValuesFromCursor: Boolean = true) : ColumnAccessCombiner(combiner) { - override fun addCode(code: CodeBlock.Builder, columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, - modelBlock: CodeBlock) { + override fun CodeBlock.Builder.addCode(columnRepresentation: String, + defaultValue: CodeBlock?, index: Int, + modelBlock: CodeBlock) { combiner.apply { - val indexName: CodeBlock - if (!orderedCursorLookup) { - indexName = CodeBlock.of("index_\$L", columnRepresentation) - code.addStatement("\$T \$L = cursor.getColumnIndex(\$S)", Int::class.java, indexName, - columnRepresentation) - code.beginControlFlow("if (\$1L != -1 && !cursor.isNull(\$1L))", indexName) + var indexName = if (!orderedCursorLookup) { + CodeBlock.of(columnRepresentation.S) } else { - indexName = CodeBlock.of(index.toString()) - code.beginControlFlow("if (!cursor.isNull(\$1L))", index) - } + CodeBlock.of(index.toString()) + }!! - val cursorAccess = CodeBlock.of("cursor.\$L(\$L)", - SQLiteHelper.getMethod(wrapperFieldTypeName ?: fieldTypeName), indexName) if (wrapperLevelAccessor != null) { + if (!orderedCursorLookup) { + indexName = CodeBlock.of(nameAllocator.newName("index_$columnRepresentation", columnRepresentation)) + statement("\$T \$L = cursor.getColumnIndex(\$S)", Int::class.java, indexName, + 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) // special case where we need to append try catch hack val isEnum = wrapperLevelAccessor is EnumColumnAccessor if (isEnum) { - code.beginControlFlow("try") + beginControlFlow("try") } if (subWrapperAccessor != null) { - code.addStatement(fieldLevelAccessor.set( + statement(fieldLevelAccessor.set( wrapperLevelAccessor.set(subWrapperAccessor.set(cursorAccess)), modelBlock)) } else { - code.addStatement(fieldLevelAccessor.set( + statement(fieldLevelAccessor.set( wrapperLevelAccessor.set(cursorAccess), modelBlock)) } if (isEnum) { - code.nextControlFlow("catch (\$T i)", IllegalArgumentException::class.java) - if (assignDefaultValuesFromCursor) { - code.addStatement(fieldLevelAccessor.set(wrapperLevelAccessor.set(defaultValue, - isDefault = true), modelBlock)) - } else { - code.addStatement(fieldLevelAccessor.set(defaultValue, modelBlock)) + catch(IllegalArgumentException::class) { + if (assignDefaultValuesFromCursor) { + statement(fieldLevelAccessor.set(wrapperLevelAccessor.set(defaultValue, + isDefault = true), modelBlock)) + } else { + statement(fieldLevelAccessor.set(defaultValue, modelBlock)) + } } - code.endControlFlow() } + if (assignDefaultValuesFromCursor) { + nextControlFlow("else") + statement(fieldLevelAccessor.set(wrapperLevelAccessor.set(defaultValue, + isDefault = true), modelBlock)) + } + endControlFlow() } else { - code.addStatement(fieldLevelAccessor.set(cursorAccess, modelBlock)) - } - if (assignDefaultValuesFromCursor) { - code.nextControlFlow("else") - if (wrapperLevelAccessor != null) { - code.addStatement(fieldLevelAccessor.set(wrapperLevelAccessor.set(defaultValue, - isDefault = true), modelBlock)) - } else { - code.addStatement(fieldLevelAccessor.set(defaultValue, modelBlock)) + var defaultValueBlock = defaultValue + if (!assignDefaultValuesFromCursor) { + defaultValueBlock = fieldLevelAccessor.get(modelBlock) } + val cursorAccess = CodeBlock.of("cursor.\$LOrDefault(\$L${if (hasDefaultValue) ", $defaultValueBlock" else ""})", + SQLiteHelper.getMethod(wrapperFieldTypeName ?: fieldTypeName), indexName) + statement(fieldLevelAccessor.set(cursorAccess, modelBlock)) } - code.endControlFlow() } } } class PrimaryReferenceAccessCombiner(combiner: Combiner) : ColumnAccessCombiner(combiner) { - override fun addCode(code: CodeBlock.Builder, columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, - modelBlock: CodeBlock) { - val wrapperLevelAccessor = this.combiner.wrapperLevelAccessor - code.addStatement("clause.and(\$L.\$Leq(\$L))", columnRepresentation, + override fun CodeBlock.Builder.addCode(columnRepresentation: String, + defaultValue: CodeBlock?, index: Int, + modelBlock: CodeBlock) { + val wrapperLevelAccessor = this@PrimaryReferenceAccessCombiner.combiner.wrapperLevelAccessor + statement("clause.and(\$L.\$Leq(\$L))", columnRepresentation, if (!wrapperLevelAccessor.isPrimitiveTarget()) "invertProperty()." else "", - getFieldAccessBlock(code, modelBlock, wrapperLevelAccessor !is BooleanColumnAccessor)) + getFieldAccessBlock(this, modelBlock, wrapperLevelAccessor !is BooleanColumnAccessor)) } override fun addNull(code: CodeBlock.Builder, columnRepresentation: String, index: Int) { @@ -278,15 +260,15 @@ class PrimaryReferenceAccessCombiner(combiner: Combiner) class UpdateAutoIncrementAccessCombiner(combiner: Combiner) : ColumnAccessCombiner(combiner) { - override fun addCode(code: CodeBlock.Builder, columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock) { + override fun CodeBlock.Builder.addCode(columnRepresentation: String, defaultValue: CodeBlock?, + index: Int, modelBlock: CodeBlock) { combiner.apply { var method = "" if (SQLiteHelper.containsNumberMethod(fieldTypeName.unbox())) { method = fieldTypeName.unbox().toString() } - code.addStatement(fieldLevelAccessor.set(CodeBlock.of("id.\$LValue()", method), modelBlock)) + statement(fieldLevelAccessor.set(CodeBlock.of("id.\$LValue()", method), modelBlock)) } } @@ -294,9 +276,9 @@ class UpdateAutoIncrementAccessCombiner(combiner: Combiner) class CachingIdAccessCombiner(combiner: Combiner) : ColumnAccessCombiner(combiner) { - override fun addCode(code: CodeBlock.Builder, columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock) { - code.addStatement("inValues[\$L] = \$L", index, getFieldAccessBlock(code, modelBlock)) + override fun CodeBlock.Builder.addCode(columnRepresentation: String, + defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock) { + statement("inValues[\$L] = \$L", index, getFieldAccessBlock(this, modelBlock)) } } @@ -305,19 +287,18 @@ class SaveModelAccessCombiner(combiner: Combiner, val implementsModel: Boolean, val extendsBaseModel: Boolean) : ColumnAccessCombiner(combiner) { - override fun addCode(code: CodeBlock.Builder, columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock) { + override fun CodeBlock.Builder.addCode(columnRepresentation: String, + defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock) { combiner.apply { - val access = getFieldAccessBlock(code, modelBlock) - code.beginControlFlow("if (\$L != null)", access) - if (implementsModel) { - code.addStatement("\$L.save(\$L)", access, - if (extendsBaseModel) ModelUtils.wrapper else "") - } else { - code.addStatement("\$T.getModelAdapter(\$T.class).save(\$L, \$L)", - ClassNames.FLOW_MANAGER, fieldTypeName, access, ModelUtils.wrapper) - } - code.endControlFlow() + val access = getFieldAccessBlock(this@addCode, modelBlock) + `if`("$access != null") { + if (implementsModel) { + statement("$access.save(${wrapperIfBaseModel(extendsBaseModel)})") + } else { + statement("\$T.getModelAdapter(\$T.class).save($access, ${ModelUtils.wrapper})", + ClassNames.FLOW_MANAGER, fieldTypeName) + } + }.end() } } @@ -327,20 +308,22 @@ class DeleteModelAccessCombiner(combiner: Combiner, val implementsModel: Boolean, val extendsBaseModel: Boolean) : ColumnAccessCombiner(combiner) { - override fun addCode(code: CodeBlock.Builder, columnRepresentation: String, - defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock) { + override fun CodeBlock.Builder.addCode(columnRepresentation: String, + defaultValue: CodeBlock?, index: Int, modelBlock: CodeBlock) { combiner.apply { - val access = getFieldAccessBlock(code, modelBlock) - code.beginControlFlow("if (\$L != null)", access) - if (implementsModel) { - code.addStatement("\$L.delete(\$L)", access, - if (extendsBaseModel) ModelUtils.wrapper else "") - } else { - code.addStatement("\$T.getModelAdapter(\$T.class).delete(\$L, \$L)", - ClassNames.FLOW_MANAGER, fieldTypeName, access, ModelUtils.wrapper) - } - code.endControlFlow() + val access = getFieldAccessBlock(this@addCode, modelBlock) + `if`("$access != null") { + if (implementsModel) { + statement("$access.delete(${wrapperIfBaseModel(extendsBaseModel)})") + } else { + statement("\$T.getModelAdapter(\$T.class).delete($access, ${ModelUtils.wrapper})", + ClassNames.FLOW_MANAGER, fieldTypeName) + } + }.end() } } -} \ No newline at end of file +} + +fun wrapperIfBaseModel(extendsBaseModel: Boolean) = if (extendsBaseModel) ModelUtils.wrapper else "" +fun wrapperCommaIfBaseModel(extendsBaseModel: Boolean) = if (extendsBaseModel) ", " + ModelUtils.wrapper else "" \ No newline at end of file diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ColumnAccessor.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ColumnAccessor.kt index fe8b08bec..6872ef07f 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ColumnAccessor.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/definition/column/ColumnAccessor.kt @@ -1,6 +1,7 @@ package com.raizlabs.android.dbflow.processor.definition.column import com.google.common.collect.Maps +import com.grosner.kpoet.code import com.raizlabs.android.dbflow.data.Blob import com.raizlabs.android.dbflow.processor.utils.capitalizeFirstLetter import com.raizlabs.android.dbflow.processor.utils.isNullOrEmpty @@ -61,68 +62,63 @@ class VisibleScopeColumnAccessor(propertyName: String) : ColumnAccessor(property val codeBlock: CodeBlock.Builder = CodeBlock.builder() baseVariableName?.let { codeBlock.add("\$L.", baseVariableName) } return codeBlock.add("\$L = \$L", propertyName, existingBlock) - .build() + .build() } override fun get(existingBlock: CodeBlock?): CodeBlock { val codeBlock: CodeBlock.Builder = CodeBlock.builder() existingBlock?.let { codeBlock.add("\$L.", existingBlock) } return codeBlock.add(propertyName) - .build() + .build() } } class PrivateScopeColumnAccessor(propertyName: String, getterSetter: GetterSetter? = null, - private val useIsForPrivateBooleans: Boolean = false) + private val useIsForPrivateBooleans: Boolean = false, + private val optionalGetterParam: String? = "") : ColumnAccessor(propertyName) { private var getterName: String = "" private var setterName: String = "" - override fun get(existingBlock: CodeBlock?): CodeBlock { - val codeBlock: CodeBlock.Builder = CodeBlock.builder() - existingBlock?.let { codeBlock.add("\$L.", existingBlock) } - return codeBlock.add("\$L()", getGetterNameElement()) - .build() + override fun get(existingBlock: CodeBlock?) = code { + existingBlock?.let { this.add("$existingBlock.") } + add("$getterNameElement($optionalGetterParam)") } override fun set(existingBlock: CodeBlock?, baseVariableName: CodeBlock?, - isDefault: Boolean): CodeBlock { - val codeBlock: CodeBlock.Builder = CodeBlock.builder() - baseVariableName?.let { codeBlock.add("\$L.", baseVariableName) } - return codeBlock.add("\$L(\$L)", getSetterNameElement(), existingBlock) - .build() + isDefault: Boolean) = code { + baseVariableName?.let { add("$baseVariableName.") } + add("$setterNameElement($existingBlock)") } - fun getGetterNameElement(): String { - return if (getterName.isNullOrEmpty()) { + val getterNameElement: String + get() = if (getterName.isNullOrEmpty()) { if (propertyName != null) { if (useIsForPrivateBooleans && !propertyName.startsWith("is", ignoreCase = true)) { - "is" + propertyName.capitalizeFirstLetter() + "is" + propertyName.capitalize() } else if (!useIsForPrivateBooleans && !propertyName.startsWith("get", ignoreCase = true)) { - "get" + propertyName.capitalizeFirstLetter() + "get" + propertyName.capitalize() } else propertyName.lower() } else { "" } } else getterName - } - fun getSetterNameElement(): String { - if (propertyName != null) { + val setterNameElement: String + get() = if (propertyName != null) { var setElementName = propertyName - return if (setterName.isNullOrEmpty()) { + if (setterName.isNullOrEmpty()) { if (!setElementName.startsWith("set", ignoreCase = true)) { if (useIsForPrivateBooleans && setElementName.startsWith("is")) { setElementName = setElementName.replaceFirst("is".toRegex(), "") } else if (useIsForPrivateBooleans && setElementName.startsWith("Is")) { setElementName = setElementName.replaceFirst("Is".toRegex(), "") } - "set" + setElementName.capitalizeFirstLetter() + "set" + setElementName.capitalize() } else setElementName.lower() } else setterName - } else return "" - } + } else "" init { getterSetter?.let { @@ -133,8 +129,8 @@ class PrivateScopeColumnAccessor(propertyName: String, getterSetter: GetterSette } class PackagePrivateScopeColumnAccessor( - propertyName: String, packageName: String, separator: String?, tableClassName: String) -: ColumnAccessor(propertyName) { + propertyName: String, packageName: String, separator: String?, tableClassName: String) + : ColumnAccessor(propertyName) { val helperClassName: ClassName val internalHelperClassName: ClassName @@ -146,16 +142,16 @@ class PackagePrivateScopeColumnAccessor( override fun get(existingBlock: CodeBlock?): CodeBlock { return CodeBlock.of("\$T.get\$L(\$L)", internalHelperClassName, - propertyName.capitalizeFirstLetter(), - existingBlock) + propertyName.capitalizeFirstLetter(), + existingBlock) } override fun set(existingBlock: CodeBlock?, baseVariableName: CodeBlock?, isDefault: Boolean): CodeBlock { return CodeBlock.of("\$T.set\$L(\$L, \$L)", helperClassName, - propertyName.capitalizeFirstLetter(), - baseVariableName, - existingBlock) + propertyName.capitalizeFirstLetter(), + baseVariableName, + existingBlock) } companion object { @@ -186,7 +182,7 @@ class PackagePrivateScopeColumnAccessor( class TypeConverterScopeColumnAccessor(val typeConverterFieldName: String, propertyName: String? = null) -: ColumnAccessor(propertyName) { + : ColumnAccessor(propertyName) { override fun get(existingBlock: CodeBlock?): CodeBlock { val codeBlock = CodeBlock.builder() @@ -209,7 +205,7 @@ class TypeConverterScopeColumnAccessor(val typeConverterFieldName: String, class EnumColumnAccessor(val propertyTypeName: TypeName, propertyName: String? = null) -: ColumnAccessor(propertyName) { + : ColumnAccessor(propertyName) { override fun get(existingBlock: CodeBlock?): CodeBlock { return appendAccess { add("\$L.name()", existingBlock) } @@ -253,7 +249,7 @@ class BooleanColumnAccessor(propertyName: String? = null) : ColumnAccessor(prope isDefault: Boolean): CodeBlock { return appendAccess { if (isDefault) add(existingBlock) - else add("\$L == 1 ? true : false", existingBlock) + else add("\$L", existingBlock) } } 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 742f54e87..fba1b4e14 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 @@ -1,14 +1,17 @@ package com.raizlabs.android.dbflow.processor.definition.column +import com.grosner.kpoet.code import com.raizlabs.android.dbflow.annotation.* import com.raizlabs.android.dbflow.data.Blob import com.raizlabs.android.dbflow.processor.ClassNames import com.raizlabs.android.dbflow.processor.ProcessorManager -import com.raizlabs.android.dbflow.processor.ProcessorUtils import com.raizlabs.android.dbflow.processor.definition.BaseDefinition import com.raizlabs.android.dbflow.processor.definition.BaseTableDefinition import com.raizlabs.android.dbflow.processor.definition.TableDefinition import com.raizlabs.android.dbflow.processor.definition.TypeConverterDefinition +import com.raizlabs.android.dbflow.processor.utils.annotation +import com.raizlabs.android.dbflow.processor.utils.fromTypeMirror +import com.raizlabs.android.dbflow.processor.utils.getTypeElement import com.raizlabs.android.dbflow.processor.utils.isNullOrEmpty import com.raizlabs.android.dbflow.sql.QueryBuilder import com.squareup.javapoet.* @@ -26,13 +29,14 @@ import javax.tools.Diagnostic open class ColumnDefinition @JvmOverloads constructor(processorManager: ProcessorManager, element: Element, var baseTableDefinition: BaseTableDefinition, isPackagePrivate: Boolean, - var column: Column? = element.getAnnotation(Column::class.java), - primaryKey: PrimaryKey? = element.getAnnotation(PrimaryKey::class.java)) + var column: Column? = element.annotation(), + primaryKey: PrimaryKey? = element.annotation()) : BaseDefinition(element, processorManager) { private val QUOTE_PATTERN = Pattern.compile("\".*\"") var columnName: String = "" + var propertyFieldName: String = "" var hasTypeConverter: Boolean = false var isPrimaryKey: Boolean = false @@ -78,8 +82,7 @@ constructor(processorManager: ProcessorManager, element: Element, get() = QueryBuilder.quote(columnName) init { - val notNullAnno = element.getAnnotation(NotNull::class.java) - if (notNullAnno != null) { + element.annotation()?.let { notNullAnno -> notNull = true onNullConflict = notNullAnno.onNullConflict } @@ -107,6 +110,9 @@ constructor(processorManager: ProcessorManager, element: Element, this.columnName = element.simpleName.toString() } + val nameAllocator = NameAllocator() + propertyFieldName = nameAllocator.newName(this.columnName) + if (isPackagePrivate) { columnAccessor = PackagePrivateScopeColumnAccessor(elementName, packageName, baseTableDefinition.databaseDefinition?.classSeparator, @@ -144,17 +150,15 @@ constructor(processorManager: ProcessorManager, element: Element, } } - val uniqueColumn = element.getAnnotation(Unique::class.java) - if (uniqueColumn != null) { + element.annotation()?.let { uniqueColumn -> unique = uniqueColumn.unique onUniqueConflict = uniqueColumn.onUniqueConflict uniqueColumn.uniqueGroups.forEach { uniqueGroups.add(it) } } - val index = element.getAnnotation(Index::class.java) - if (index != null) { + element.annotation()?.let { index -> // empty index, we assume generic - if (index.indexGroups.size == 0) { + if (index.indexGroups.isEmpty()) { indexGroups.add(IndexGroup.GENERIC) } else { index.indexGroups.forEach { indexGroups.add(it) } @@ -167,7 +171,7 @@ constructor(processorManager: ProcessorManager, element: Element, column?.typeConverter } catch (mte: MirroredTypeException) { typeMirror = mte.typeMirror - typeConverterClassName = ProcessorUtils.fromTypeMirror(typeMirror, manager) + typeConverterClassName = fromTypeMirror(typeMirror, manager) } hasCustomConverter = false @@ -178,7 +182,7 @@ constructor(processorManager: ProcessorManager, element: Element, } if (!hasCustomConverter) { - val typeElement = ProcessorUtils.getTypeElement(element) + val typeElement = getTypeElement(element) if (typeElement != null && typeElement.kind == ElementKind.ENUM) { wrapperAccessor = EnumColumnAccessor(elementTypeName!!) wrapperTypeName = ClassName.get(String::class.java) @@ -259,7 +263,7 @@ constructor(processorManager: ProcessorManager, element: Element, } val fieldBuilder = FieldSpec.builder(propParam, - columnName, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) + propertyFieldName, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) if (isNonPrimitiveTypeConverter) { val codeBlock = CodeBlock.builder() @@ -290,21 +294,22 @@ constructor(processorManager: ProcessorManager, element: Element, open fun addPropertyCase(methodBuilder: MethodSpec.Builder) { methodBuilder.apply { beginControlFlow("case \$S: ", QueryBuilder.quote(columnName)) - addStatement("return \$L", columnName) + addStatement("return \$L", propertyFieldName) endControlFlow() } } open fun addColumnName(codeBuilder: CodeBlock.Builder) { - codeBuilder.add(columnName) + codeBuilder.add(propertyFieldName) } open val contentValuesStatement: CodeBlock get() { val code = CodeBlock.builder() - ContentValuesCombiner(combiner) - .addCode(code, columnName, getDefaultValueBlock(), 0, modelBlock) + ContentValuesCombiner(combiner).apply { + code.addCode(columnName, getDefaultValueBlock(), 0, modelBlock) + } return code.build() } @@ -317,22 +322,22 @@ constructor(processorManager: ProcessorManager, element: Element, index.incrementAndGet() } - open fun getSQLiteStatementMethod(index: AtomicInteger): CodeBlock { - - val builder = CodeBlock.builder() - SqliteStatementAccessCombiner(combiner) - .addCode(builder, "start", getDefaultValueBlock(), index.get(), modelBlock) - return builder.build() + open fun getSQLiteStatementMethod(index: AtomicInteger) = code { + SqliteStatementAccessCombiner(combiner).apply { + addCode("start", getDefaultValueBlock(), index.get(), modelBlock) + } + this } - open fun getLoadFromCursorMethod(endNonPrimitiveIf: Boolean, index: AtomicInteger): CodeBlock { + open fun getLoadFromCursorMethod(endNonPrimitiveIf: Boolean, index: AtomicInteger, + nameAllocator: NameAllocator) = code { - val builder = CodeBlock.builder() - LoadFromCursorAccessCombiner(combiner, - baseTableDefinition.orderedCursorLookUp, - baseTableDefinition.assignDefaultValuesFromCursor) - .addCode(builder, columnName, getDefaultValueBlock(), index.get(), modelBlock) - return builder.build() + LoadFromCursorAccessCombiner(combiner, defaultValue != null, + nameAllocator, baseTableDefinition.orderedCursorLookUp, + baseTableDefinition.assignDefaultValuesFromCursor).apply { + addCode(columnName, getDefaultValueBlock(), index.get(), modelBlock) + } + this } /** @@ -340,39 +345,40 @@ constructor(processorManager: ProcessorManager, element: Element, * @return The statement to use. */ - val updateAutoIncrementMethod: CodeBlock - get() { - val code = CodeBlock.builder() - UpdateAutoIncrementAccessCombiner(combiner) - .addCode(code, columnName, getDefaultValueBlock(), - 0, modelBlock) - return code.build() + val updateAutoIncrementMethod + get() = code { + UpdateAutoIncrementAccessCombiner(combiner).apply { + addCode(columnName, getDefaultValueBlock(), 0, modelBlock) + } + this } - fun getColumnAccessString(index: Int): CodeBlock { - val codeBlock = CodeBlock.builder() - CachingIdAccessCombiner(combiner) - .addCode(codeBlock, columnName, getDefaultValueBlock(), index, modelBlock) - return codeBlock.build() + fun getColumnAccessString(index: Int) = code { + CachingIdAccessCombiner(combiner).apply { + addCode(columnName, getDefaultValueBlock(), index, modelBlock) + } + this } - fun getSimpleAccessString(): CodeBlock { - val codeBlock = CodeBlock.builder() - SimpleAccessCombiner(combiner) - .addCode(codeBlock, columnName, getDefaultValueBlock(), 0, modelBlock) - return codeBlock.build() + fun getSimpleAccessString() = code { + SimpleAccessCombiner(combiner).apply { + addCode(columnName, getDefaultValueBlock(), 0, modelBlock) + } + this } open fun appendExistenceMethod(codeBuilder: CodeBlock.Builder) { ExistenceAccessCombiner(combiner, isRowId || isPrimaryKeyAutoIncrement, isQuickCheckPrimaryKeyAutoIncrement, baseTableDefinition.elementClassName!!) - .addCode(codeBuilder, columnName, getDefaultValueBlock(), 0, modelBlock) + .apply { + codeBuilder.addCode(columnName, getDefaultValueBlock(), 0, modelBlock) + } } open fun appendPropertyComparisonAccessStatement(codeBuilder: CodeBlock.Builder) { - PrimaryReferenceAccessCombiner(combiner) - .addCode(codeBuilder, columnName, getDefaultValueBlock(), - 0, modelBlock) + PrimaryReferenceAccessCombiner(combiner).apply { + codeBuilder.addCode(propertyFieldName, getDefaultValueBlock(), 0, modelBlock) + } } open val creationName: CodeBlock 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 4860b96a3..d4011b8b2 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 @@ -2,8 +2,10 @@ package com.raizlabs.android.dbflow.processor.definition.column import com.raizlabs.android.dbflow.processor.ClassNames import com.raizlabs.android.dbflow.processor.SQLiteHelper -import com.raizlabs.android.dbflow.processor.utils.addStatement +import com.raizlabs.android.dbflow.processor.utils.statement +import com.squareup.javapoet.ClassName import com.squareup.javapoet.CodeBlock +import com.squareup.javapoet.NameAllocator import com.squareup.javapoet.TypeName import java.util.concurrent.atomic.AtomicInteger @@ -31,8 +33,8 @@ class ForeignKeyAccessCombiner(val fieldAccessor: ColumnAccessor) { } } code.nextControlFlow("else") - .add(nullAccessBlock.build().toString()) - .endControlFlow() + .add(nullAccessBlock.build().toString()) + .endControlFlow() } } @@ -42,8 +44,9 @@ class ForeignKeyAccessField(val columnRepresentation: String, fun addCode(code: CodeBlock.Builder, index: Int, modelAccessBlock: CodeBlock) { - columnAccessCombiner.addCode(code, columnRepresentation, defaultValue, index, - modelAccessBlock) + columnAccessCombiner.apply { + code.addCode(columnRepresentation, defaultValue, index, modelAccessBlock) + } } fun addNull(code: CodeBlock.Builder, index: Int) { @@ -54,7 +57,8 @@ class ForeignKeyAccessField(val columnRepresentation: String, class ForeignKeyLoadFromCursorCombiner(val fieldAccessor: ColumnAccessor, val referencedTypeName: TypeName, val referencedTableTypeName: TypeName, - val isStubbed: Boolean) { + val isStubbed: Boolean, + val nameAllocator: NameAllocator) { var fieldAccesses: List = arrayListOf() fun addCode(code: CodeBlock.Builder, index: AtomicInteger) { @@ -63,15 +67,16 @@ class ForeignKeyLoadFromCursorCombiner(val fieldAccessor: ColumnAccessor, if (!isStubbed) { setterBlock.add("\$T.select().from(\$T.class).where()", - ClassNames.SQLITE, referencedTypeName) + ClassNames.SQLITE, referencedTypeName) } else { - setterBlock.addStatement( - fieldAccessor.set(CodeBlock.of("new \$T()", referencedTypeName), modelBlock)) + setterBlock.statement( + fieldAccessor.set(CodeBlock.of("new \$T()", referencedTypeName), modelBlock)) } for ((i, it) in fieldAccesses.withIndex()) { - it.addRetrieval(setterBlock, index.get(), referencedTableTypeName, isStubbed, fieldAccessor) - it.addColumnIndex(code, index.get()) - it.addIndexCheckStatement(ifChecker, index.get(), i == fieldAccesses.size - 1) + 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) if (i < fieldAccesses.size - 1) { index.incrementAndGet() @@ -82,61 +87,74 @@ class ForeignKeyLoadFromCursorCombiner(val fieldAccessor: ColumnAccessor, code.beginControlFlow("if (\$L)", ifChecker.build()) if (!isStubbed) { - code.addStatement(fieldAccessor.set(setterBlock.build(), modelBlock)) + code.statement(fieldAccessor.set(setterBlock.build(), modelBlock)) } else { code.add(setterBlock.build()) } code.nextControlFlow("else") - .addStatement(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) { - - fun getIndexName(index: Int): CodeBlock { - return if (!orderedCursorLookup) { - CodeBlock.of("index_\$L", columnRepresentation) - } else { - CodeBlock.of(index.toString()) + 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 + + fun getIndexName(index: Int, nameAllocator: NameAllocator, referencedTypeName: TypeName): CodeBlock { + if (indexName == null) { + 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)) + } else { + CodeBlock.of(index.toString()) + } } + return indexName!! } fun addRetrieval(code: CodeBlock.Builder, index: Int, referencedTableTypeName: TypeName, - isStubbed: Boolean, parentAccessor: ColumnAccessor) { + isStubbed: Boolean, parentAccessor: ColumnAccessor, + nameAllocator: NameAllocator) { val cursorAccess = CodeBlock.of("cursor.\$L(\$L)", - SQLiteHelper.getMethod(subWrapperTypeName ?: fieldTypeName), getIndexName(index)) + 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.addStatement(fieldLevelAccessor.set(cursorAccess, parentAccessor.get(modelBlock))) + code.statement(fieldLevelAccessor.set(cursorAccess, parentAccessor.get(modelBlock))) } } - fun addColumnIndex(code: CodeBlock.Builder, index: Int) { + fun addColumnIndex(code: CodeBlock.Builder, index: Int, + referencedTableTypeName: TypeName, + nameAllocator: NameAllocator) { if (!orderedCursorLookup) { - code.addStatement(CodeBlock.of("int \$L = cursor.getColumnIndex(\$S)", - getIndexName(index), columnRepresentation)) + code.statement(CodeBlock.of("int \$L = cursor.getColumnIndex(\$S)", + getIndexName(index, nameAllocator, referencedTableTypeName), columnRepresentation)) } } fun addIndexCheckStatement(code: CodeBlock.Builder, index: Int, - isLast: Boolean) { - if (!orderedCursorLookup) code.add("\$L != -1 && ", getIndexName(index)) + referencedTableTypeName: TypeName, + isLast: Boolean, nameAllocator: NameAllocator) { + val indexName = getIndexName(index, nameAllocator, referencedTableTypeName) + if (!orderedCursorLookup) code.add("$indexName != -1 && ") - code.add("!cursor.isNull(\$L)", getIndexName(index)) + code.add("!cursor.isNull($indexName)") if (!isLast) code.add(" && ") } 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 a6b5719b7..7b342c4db 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 @@ -1,16 +1,16 @@ package com.raizlabs.android.dbflow.processor.definition.column +import com.grosner.kpoet.S +import com.grosner.kpoet.`return` +import com.grosner.kpoet.case import com.raizlabs.android.dbflow.annotation.ForeignKey import com.raizlabs.android.dbflow.annotation.ForeignKeyAction import com.raizlabs.android.dbflow.annotation.ForeignKeyReference import com.raizlabs.android.dbflow.annotation.Table import com.raizlabs.android.dbflow.processor.ClassNames import com.raizlabs.android.dbflow.processor.ProcessorManager -import com.raizlabs.android.dbflow.processor.ProcessorUtils import com.raizlabs.android.dbflow.processor.definition.TableDefinition -import com.raizlabs.android.dbflow.processor.utils.isNullOrEmpty -import com.raizlabs.android.dbflow.processor.utils.toTypeElement -import com.raizlabs.android.dbflow.processor.utils.toTypeErasedElement +import com.raizlabs.android.dbflow.processor.utils.* import com.raizlabs.android.dbflow.sql.QueryBuilder import com.squareup.javapoet.* import java.util.* @@ -30,14 +30,14 @@ class ForeignKeyColumnDefinition(manager: ProcessorManager, tableDefinition: Tab var referencedTableClassName: ClassName? = null - var onDelete: ForeignKeyAction - var onUpdate: ForeignKeyAction + var onDelete = ForeignKeyAction.NO_ACTION + var onUpdate = ForeignKeyAction.NO_ACTION var isStubbedRelationship: Boolean = false var isReferencingTableObject: Boolean = false - var implementsModel: Boolean - var extendsBaseModel: Boolean + var implementsModel = false + var extendsBaseModel = false var references: List? = null @@ -48,54 +48,57 @@ class ForeignKeyColumnDefinition(manager: ProcessorManager, tableDefinition: Tab var needsReferences = true + var deferred = false + override val typeConverterElementNames: List get() = _foreignKeyReferenceDefinitionList.filter { it.hasTypeConverter }.map { it.columnClassName } init { - val foreignKey = element.getAnnotation(ForeignKey::class.java) - onUpdate = foreignKey.onUpdate - onDelete = foreignKey.onDelete + element.annotation()?.let { foreignKey -> + onUpdate = foreignKey.onUpdate + onDelete = foreignKey.onDelete - isStubbedRelationship = foreignKey.stubbedRelationship + deferred = foreignKey.deferred + isStubbedRelationship = foreignKey.stubbedRelationship - try { - foreignKey.tableClass - } catch (mte: MirroredTypeException) { - referencedTableClassName = ProcessorUtils.fromTypeMirror(mte.typeMirror, manager) - } + try { + foreignKey.tableClass + } catch (mte: MirroredTypeException) { + referencedTableClassName = fromTypeMirror(mte.typeMirror, manager) + } - val erasedElement = element.toTypeErasedElement() + val erasedElement = element.toTypeErasedElement() - // hopefully intentionally left blank - if (referencedTableClassName == TypeName.OBJECT) { - if (elementTypeName is ParameterizedTypeName) { - val args = (elementTypeName as ParameterizedTypeName).typeArguments - if (args.size > 0) { - referencedTableClassName = ClassName.get(args[0].toTypeElement(manager)) - } - } else { - if (referencedTableClassName == null || referencedTableClassName == ClassName.OBJECT) { - referencedTableClassName = ClassName.get(elementTypeName.toTypeElement()) + // hopefully intentionally left blank + if (referencedTableClassName == TypeName.OBJECT) { + if (elementTypeName is ParameterizedTypeName) { + val args = (elementTypeName as ParameterizedTypeName).typeArguments + if (args.size > 0) { + referencedTableClassName = ClassName.get(args[0].toTypeElement(manager)) + } + } else { + if (referencedTableClassName == null || referencedTableClassName == ClassName.OBJECT) { + referencedTableClassName = ClassName.get(elementTypeName.toTypeElement()) + } } } - } - if (referencedTableClassName == null) { - manager.logError("Referenced was null for $element within $elementTypeName") - } + if (referencedTableClassName == null) { + manager.logError("Referenced was null for $element within $elementTypeName") + } - extendsBaseModel = ProcessorUtils.isSubclass(manager.processingEnvironment, - ClassNames.BASE_MODEL.toString(), erasedElement) - implementsModel = ProcessorUtils.implementsClass(manager.processingEnvironment, ClassNames.MODEL.toString(), erasedElement) - isReferencingTableObject = implementsModel || erasedElement?.getAnnotation(Table::class.java) != null + extendsBaseModel = erasedElement.isSubclass(manager.processingEnvironment, ClassNames.BASE_MODEL) + implementsModel = erasedElement.implementsClass(manager.processingEnvironment, ClassNames.MODEL) + isReferencingTableObject = implementsModel || erasedElement.annotation
() != null - nonModelColumn = !isReferencingTableObject + nonModelColumn = !isReferencingTableObject - saveForeignKeyModel = foreignKey.saveForeignKeyModel - deleteForeignKeyModel = foreignKey.deleteForeignKeyModel + saveForeignKeyModel = foreignKey.saveForeignKeyModel + deleteForeignKeyModel = foreignKey.deleteForeignKeyModel - references = foreignKey.references.asList() + references = foreignKey.references.asList() + } } override fun addPropertyDefinition(typeBuilder: TypeSpec.Builder, tableClass: TypeName) { @@ -108,21 +111,21 @@ class ForeignKeyColumnDefinition(manager: ProcessorManager, tableDefinition: Tab } if (it.columnName.isNullOrEmpty()) { manager.logError("Found empty reference name at ${it.foreignColumnName}" + - " from tablet ${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()) } } override fun addPropertyCase(methodBuilder: MethodSpec.Builder) { checkNeedsReferences() _foreignKeyReferenceDefinitionList.forEach { - methodBuilder.beginControlFlow("case \$S: ", QueryBuilder.quoteIfNeeded(it.columnName)) - methodBuilder.addStatement("return \$L", it.columnName) - methodBuilder.endControlFlow() + methodBuilder.case(QueryBuilder.quoteIfNeeded(it.columnName).S) { + `return`(it.columnName) + } } } @@ -161,7 +164,7 @@ class ForeignKeyColumnDefinition(manager: ProcessorManager, tableDefinition: Tab builder.add(",") } val referenceDefinition = _foreignKeyReferenceDefinitionList[i] - builder.add("\$L", QueryBuilder.quote(referenceDefinition.columnName)) + builder.add(QueryBuilder.quote(referenceDefinition.columnName)) } return builder.build() } @@ -240,9 +243,9 @@ class ForeignKeyColumnDefinition(manager: ProcessorManager, tableDefinition: Tab } } - override fun getLoadFromCursorMethod(endNonPrimitiveIf: Boolean, index: AtomicInteger): CodeBlock { + override fun getLoadFromCursorMethod(endNonPrimitiveIf: Boolean, index: AtomicInteger, nameAllocator: NameAllocator): CodeBlock { if (nonModelColumn) { - return super.getLoadFromCursorMethod(endNonPrimitiveIf, index) + return super.getLoadFromCursorMethod(endNonPrimitiveIf, index, nameAllocator) } else { checkNeedsReferences() @@ -250,11 +253,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) + referencedTableClassName, outputClassName, isStubbedRelationship, nameAllocator) _foreignKeyReferenceDefinitionList.forEach { foreignKeyCombiner.fieldAccesses += it.partialAccessor } @@ -284,8 +287,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) } } @@ -295,8 +298,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) } } @@ -309,18 +312,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 @@ -329,13 +332,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 bfa6a6ca3..3d0593ee7 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 @@ -49,18 +49,17 @@ class ForeignKeyReferenceDefinition(private val manager: ProcessorManager, foreignKeyFieldName: String, getterSetter: GetterSetter, name: String, packageName: String) { if (isReferencedFieldPrivate) { - val isBoolean = columnClassName?.box() == TypeName.BOOLEAN.box() columnAccessor = PrivateScopeColumnAccessor(foreignKeyFieldName, getterSetter, false) } else if (isReferencedFieldPackagePrivate) { - columnAccessor = PackagePrivateScopeColumnAccessor(foreignColumnName, packageName, - foreignKeyColumnDefinition.baseTableDefinition.databaseDefinition?.classSeparator, - name) + columnAccessor = PackagePrivateScopeColumnAccessor(foreignKeyFieldName, packageName, + foreignKeyColumnDefinition.baseTableDefinition.databaseDefinition?.classSeparator, + name) PackagePrivateScopeColumnAccessor.putElement( - (columnAccessor as PackagePrivateScopeColumnAccessor).helperClassName, - foreignColumnName) + (columnAccessor as PackagePrivateScopeColumnAccessor).helperClassName, + foreignKeyFieldName) } else { - columnAccessor = VisibleScopeColumnAccessor(foreignColumnName) + columnAccessor = VisibleScopeColumnAccessor(foreignKeyFieldName) } } @@ -70,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)) @@ -89,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 @@ -110,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 @@ -121,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 4d4ad9e92..669aaf242 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 @@ -1,7 +1,10 @@ package com.raizlabs.android.dbflow.processor.utils +import com.grosner.kpoet.end +import com.grosner.kpoet.nextControl import com.squareup.javapoet.CodeBlock import com.squareup.javapoet.MethodSpec +import kotlin.reflect.KClass /** * Description: Set of utility methods to save code @@ -25,10 +28,16 @@ fun MethodSpec.Builder.controlFlow(statement: String, vararg args: Any?, * * @author Andrew Grosner (fuzz) */ -fun CodeBlock.Builder.addStatement(codeBlock: CodeBlock?): CodeBlock.Builder - = this.addStatement("\$L", codeBlock) +fun CodeBlock.Builder.statement(codeBlock: CodeBlock?): CodeBlock.Builder + = this.addStatement("\$L", codeBlock) -fun MethodSpec.Builder.addStatement(codeBlock: CodeBlock?): MethodSpec.Builder +fun MethodSpec.Builder.statement(codeBlock: CodeBlock?): MethodSpec.Builder - = this.addStatement("\$L", codeBlock) + = this.addStatement("\$L", codeBlock) + +inline fun CodeBlock.Builder.catch(exception: KClass, + function: CodeBlock.Builder.() -> CodeBlock.Builder) + = nextControl("catch", statement = "\$T e", args = arrayOf(exception), function = function).end() + +fun codeBlock(function: CodeBlock.Builder.() -> CodeBlock.Builder) = CodeBlock.builder().function().build() diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/ElementExtensions.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/ElementExtensions.kt index 8ea9542db..e0c9151b2 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/ElementExtensions.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/ElementExtensions.kt @@ -1,6 +1,7 @@ package com.raizlabs.android.dbflow.processor.utils import com.raizlabs.android.dbflow.processor.ProcessorManager +import com.squareup.javapoet.ClassName import com.squareup.javapoet.TypeName import javax.lang.model.element.Element import javax.lang.model.element.TypeElement @@ -25,4 +26,10 @@ fun TypeMirror?.erasure(manager: ProcessorManager = ProcessorManager.manager): T // TypeName -fun TypeName?.toTypeElement(manager: ProcessorManager = ProcessorManager.manager): TypeElement? = manager.elements.getTypeElement(toString()) \ No newline at end of file +fun TypeName?.toTypeElement(manager: ProcessorManager = ProcessorManager.manager): TypeElement? = manager.elements.getTypeElement(toString()) + +inline fun Element?.annotation() = this?.getAnnotation(T::class.java) + +fun Element?.getPackage(manager: ProcessorManager = ProcessorManager.manager) = manager.elements.getPackageOf(this) + +fun Element?.toClassName() = ClassName.get(this as TypeElement) \ No newline at end of file diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/ElementUtility.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/ElementUtility.kt index cbfe28abb..c79b8aa50 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/ElementUtility.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/ElementUtility.kt @@ -45,7 +45,7 @@ object ElementUtility { return allFields && element.kind.isField && !element.modifiers.contains(Modifier.STATIC) && !element.modifiers.contains(Modifier.FINAL) && - element.getAnnotation(ColumnIgnore::class.java) == null && + element.annotation() == null && element.asType().toString() != ClassNames.MODEL_ADAPTER.toString() } diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/JavaPoetExtensions.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/JavaPoetExtensions.kt index fcef35b2c..aacfa2dbd 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/JavaPoetExtensions.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/JavaPoetExtensions.kt @@ -9,12 +9,24 @@ import kotlin.reflect.KClass fun TypeSpec.Builder.`override fun`(type: TypeName, name: String, vararg params: ParameterSpec.Builder, codeMethod: (MethodSpec.Builder.() -> MethodSpec.Builder) = { this }) - = addMethod(MethodSpec.methodBuilder(name).returns(type).addParameters(params.map { it.build() }.toList()) - .addAnnotation(Override::class.java) - .codeMethod().build())!! + = addMethod(MethodSpec.methodBuilder(name).returns(type).addParameters(params.map { it.build() }.toList()) + .addAnnotation(Override::class.java) + .codeMethod().build())!! fun TypeSpec.Builder.`override fun`(type: KClass<*>, name: String, vararg params: ParameterSpec.Builder, codeMethod: (MethodSpec.Builder.() -> MethodSpec.Builder) = { this }) - = addMethod(MethodSpec.methodBuilder(name).returns(type).addParameters(params.map { it.build() }.toList()) - .addAnnotation(Override::class.java) - .codeMethod().build())!! \ No newline at end of file + = addMethod(MethodSpec.methodBuilder(name).returns(type).addParameters(params.map { it.build() }.toList()) + .addAnnotation(Override::class.java) + .codeMethod().build())!! + +fun `override fun`(type: TypeName, name: String, vararg params: ParameterSpec.Builder, + codeMethod: (MethodSpec.Builder.() -> MethodSpec.Builder) = { this }) + = MethodSpec.methodBuilder(name).returns(type).addParameters(params.map { it.build() }.toList()) + .addAnnotation(Override::class.java) + .codeMethod().build()!! + +fun `override fun`(type: KClass<*>, name: String, vararg params: ParameterSpec.Builder, + codeMethod: (MethodSpec.Builder.() -> MethodSpec.Builder) = { this }) + = MethodSpec.methodBuilder(name).returns(type).addParameters(params.map { it.build() }.toList()) + .addAnnotation(Override::class.java) + .codeMethod().build()!! \ No newline at end of file diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/ProcessorUtils.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/ProcessorUtils.kt new file mode 100644 index 000000000..7354b0fec --- /dev/null +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/ProcessorUtils.kt @@ -0,0 +1,113 @@ +package com.raizlabs.android.dbflow.processor.utils + +import com.raizlabs.android.dbflow.processor.ProcessorManager +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 + +/** + * Whether the specified element implements the [ClassName] + */ +fun TypeElement?.implementsClass(processingEnvironment: ProcessingEnvironment + = manager.processingEnvironment, className: ClassName) + = implementsClass(processingEnvironment, className.toString()) + +/** + * 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 TypeElement?.implementsClass(processingEnvironment: ProcessingEnvironment, fqTn: String): 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 || this?.asType() == null) { + return false + } + val elementType = this.asType() + return elementType != null && (processingEnvironment.typeUtils.isAssignable(elementType, classMirror) || elementType == classMirror) + } +} + +/** + * Whether the specified element is assignable to the [className] parameter + */ +fun TypeElement?.isSubclass(processingEnvironment: ProcessingEnvironment + = manager.processingEnvironment, className: ClassName) + = isSubclass(processingEnvironment, className.toString()) + +/** + * Whether the specified element is assignable to the [fqTn] parameter + */ +fun TypeElement?.isSubclass(processingEnvironment: ProcessingEnvironment, fqTn: String): 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 && this != null && this.asType() != null && processingEnvironment.typeUtils.isSubtype(this.asType(), classMirror) + } +} + +fun fromTypeMirror(typeMirror: TypeMirror, processorManager: ProcessorManager): ClassName? { + val element = getTypeElement(typeMirror) + return if (element != null) { + ClassName.get(element) + } else { + ElementUtility.getClassName(typeMirror.toString(), processorManager) + } +} + +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 = 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/utils/StringUtils.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/StringUtils.kt index eae853ddc..f03f7fa61 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/StringUtils.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/StringUtils.kt @@ -4,19 +4,19 @@ package com.raizlabs.android.dbflow.processor.utils * Description: */ fun String?.isNullOrEmpty(): Boolean { - return this == null || this.trim { it <= ' ' }.length == 0 || this == "null" + return this == null || this.trim { it <= ' ' }.isEmpty() || this == "null" } fun String?.capitalizeFirstLetter(): String { - if (this == null || this.trim { it <= ' ' }.length == 0) { + if (this == null || this.trim { it <= ' ' }.isEmpty()) { return this ?: "" } - return this.substring(0, 1).toUpperCase() + this.substring(1) + return this.capitalize() } fun String?.lower(): String { - if (this == null || this.trim { it <= ' ' }.length == 0) { + if (this == null || this.trim { it <= ' ' }.isEmpty()) { return this ?: "" } diff --git a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/WriterUtils.kt b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/WriterUtils.kt index f32633539..281dedeca 100644 --- a/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/WriterUtils.kt +++ b/dbflow-processor/src/main/java/com/raizlabs/android/dbflow/processor/utils/WriterUtils.kt @@ -1,8 +1,8 @@ package com.raizlabs.android.dbflow.processor.utils +import com.grosner.kpoet.javaFile import com.raizlabs.android.dbflow.processor.ProcessorManager import com.raizlabs.android.dbflow.processor.definition.BaseDefinition -import com.squareup.javapoet.JavaFile import java.io.IOException /** @@ -13,13 +13,14 @@ object WriterUtils { fun writeBaseDefinition(baseDefinition: BaseDefinition, processorManager: ProcessorManager): Boolean { var success = false try { - val javaFile = JavaFile.builder(baseDefinition.packageName, baseDefinition.typeSpec).build() - javaFile.writeTo(processorManager.processingEnvironment.filer) + javaFile(baseDefinition.packageName) { baseDefinition.typeSpec } + .writeTo(processorManager.processingEnvironment.filer) success = true } catch (e: IOException) { // ignored } catch (i: IllegalStateException) { processorManager.logError(WriterUtils::class, "Found error for class:" + baseDefinition.elementName) + processorManager.logError(WriterUtils::class, i.message) } return success diff --git a/dbflow-sqlcipher/src/main/java/com/raizlabs/android/dbflow/sqlcipher/SQLCipherDatabase.java b/dbflow-sqlcipher/src/main/java/com/raizlabs/android/dbflow/sqlcipher/SQLCipherDatabase.java index 4f783bc08..5b4f71c56 100644 --- a/dbflow-sqlcipher/src/main/java/com/raizlabs/android/dbflow/sqlcipher/SQLCipherDatabase.java +++ b/dbflow-sqlcipher/src/main/java/com/raizlabs/android/dbflow/sqlcipher/SQLCipherDatabase.java @@ -1,12 +1,12 @@ package com.raizlabs.android.dbflow.sqlcipher; import android.content.ContentValues; -import android.database.Cursor; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.raizlabs.android.dbflow.structure.database.DatabaseStatement; import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; import net.sqlcipher.database.SQLiteDatabase; @@ -60,8 +60,8 @@ public SQLiteDatabase getDatabase() { } @Override - public Cursor rawQuery(String query, String[] selectionArgs) { - return database.rawQuery(query, selectionArgs); + public FlowCursor rawQuery(String query, String[] selectionArgs) { + return FlowCursor.from(database.rawQuery(query, selectionArgs)); } @Override @@ -75,15 +75,14 @@ public long insertWithOnConflict(String tableName, String nullColumnHack, Conten } @Override - public Cursor query( - @NonNull String tableName, - @Nullable String[] columns, - @Nullable String selection, - @Nullable String[] selectionArgs, - @Nullable String groupBy, - @Nullable String having, - @Nullable String orderBy) { - return database.query(tableName, columns, selection, selectionArgs, groupBy, having, orderBy); + public FlowCursor query(@NonNull String tableName, + @Nullable String[] columns, + @Nullable String selection, + @Nullable String[] selectionArgs, + @Nullable String groupBy, + @Nullable String having, + @Nullable String orderBy) { + return FlowCursor.from(database.query(tableName, columns, selection, selectionArgs, groupBy, having, orderBy)); } @Override diff --git a/dbflow-sqlcipher/src/main/java/com/raizlabs/android/dbflow/sqlcipher/SQLCipherStatement.java b/dbflow-sqlcipher/src/main/java/com/raizlabs/android/dbflow/sqlcipher/SQLCipherStatement.java index 1f5f094f9..b2d72267a 100644 --- a/dbflow-sqlcipher/src/main/java/com/raizlabs/android/dbflow/sqlcipher/SQLCipherStatement.java +++ b/dbflow-sqlcipher/src/main/java/com/raizlabs/android/dbflow/sqlcipher/SQLCipherStatement.java @@ -1,5 +1,6 @@ package com.raizlabs.android.dbflow.sqlcipher; +import com.raizlabs.android.dbflow.structure.database.BaseDatabaseStatement; import com.raizlabs.android.dbflow.structure.database.DatabaseStatement; import net.sqlcipher.database.SQLiteStatement; @@ -8,7 +9,7 @@ * Description: Implements the methods necessary for {@link DatabaseStatement}. Delegates calls to * the contained {@link SQLiteStatement}. */ -public class SQLCipherStatement implements DatabaseStatement { +public class SQLCipherStatement extends BaseDatabaseStatement { public static SQLCipherStatement from(SQLiteStatement statement) { return new SQLCipherStatement(statement); @@ -55,8 +56,8 @@ public long executeInsert() { } @Override - public void bindString(int index, String name) { - statement.bindString(index, name); + public void bindString(int index, String s) { + statement.bindString(index, s); } @Override diff --git a/dbflow-tests/build.gradle b/dbflow-tests/build.gradle index 77d4f829a..9a281dd3e 100644 --- a/dbflow-tests/build.gradle +++ b/dbflow-tests/build.gradle @@ -41,7 +41,7 @@ dependencies { kapt project("${dbflow_project_prefix}dbflow-processor") compile project(':dbflow') - compile 'com.android.support:appcompat-v7:25.3.0' + compile 'com.android.support:appcompat-v7:25.3.1' compile project("${dbflow_project_prefix}dbflow") compile project("${dbflow_project_prefix}dbflow-sqlcipher") compile project("${dbflow_project_prefix}dbflow-kotlinextensions") @@ -53,7 +53,6 @@ dependencies { kaptTest project("${dbflow_project_prefix}dbflow-processor") testCompile 'junit:junit:4.12' - testCompile 'com.jayway.awaitility:awaitility:1.6.5' testCompile "org.robolectric:robolectric:3.1.4" testCompile("com.nhaarman:mockito-kotlin:1.3.0") { exclude group: "org.jetbrains.kotlin" diff --git a/dbflow-tests/src/androidTest/java/com/raizlabs/android/dbflow/contentobserver/ContentObserverTest.kt b/dbflow-tests/src/androidTest/java/com/raizlabs/android/dbflow/contentobserver/ContentObserverTest.kt index 8d58835a7..aaf231ac4 100644 --- a/dbflow-tests/src/androidTest/java/com/raizlabs/android/dbflow/contentobserver/ContentObserverTest.kt +++ b/dbflow-tests/src/androidTest/java/com/raizlabs/android/dbflow/contentobserver/ContentObserverTest.kt @@ -59,7 +59,7 @@ class ContentObserverTest : BaseInstrumentedUnitTest() { @Test fun testSpecificUrlSave() { // insert on SAVE - assertProperConditions(BaseModel.Action.SAVE, { it.apply { age = 57 }.save() }) + assertProperConditions(BaseModel.Action.INSERT, { it.apply { age = 57 }.save() }) } @Test diff --git a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/TestDatabase.kt b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/TestDatabase.kt index 505f6b72c..78fe6a715 100644 --- a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/TestDatabase.kt +++ b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/TestDatabase.kt @@ -1,6 +1,9 @@ package com.raizlabs.android.dbflow import com.raizlabs.android.dbflow.annotation.Database +import com.raizlabs.android.dbflow.annotation.Migration +import com.raizlabs.android.dbflow.models.SimpleModel +import com.raizlabs.android.dbflow.sql.migration.UpdateTableMigration /** * Description: @@ -10,6 +13,13 @@ object TestDatabase { const val VERSION = 1 - const val NAME = "TestDatabase"; + const val NAME = "TestDatabase" + + @Migration(version = 1, database = TestDatabase::class) + class TestMigration : UpdateTableMigration(SimpleModel::class.java) { + override fun onPostMigrate() { + super.onPostMigrate() + } + } } \ No newline at end of file diff --git a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/ForeignKeyModels.kt b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/ForeignKeyModels.kt index 193f37484..641f0f36d 100644 --- a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/ForeignKeyModels.kt +++ b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/ForeignKeyModels.kt @@ -18,6 +18,13 @@ class Author(@PrimaryKey(autoincrement = true) var id: Int = 0, @Column(name = "first_name") var firstName: String = "", @Column(name = "last_name") var lastName: String = "") +/** + * Example of simple foreign key object with its [ForeignKey] deferred. + */ +@Table(database = TestDatabase::class) +class BlogDeferred(@PrimaryKey(autoincrement = true) var id: Int = 0, @Column var name: String = "", + @ForeignKey(deferred = true) var author: Author? = null) + /** * Class has example of single foreign key with [ForeignKeyReference] specified */ diff --git a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/OneToManyModelTest.kt b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/OneToManyModelTest.kt index a6c568832..338cf044b 100644 --- a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/OneToManyModelTest.kt +++ b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/OneToManyModelTest.kt @@ -23,6 +23,7 @@ class OneToManyModelTest : BaseUnitTest() { // assert loading works as expected. oneToManyModel = (select from OneToManyModel::class).result!! + val wrapper = writableDatabaseForTable() assertNotNull(oneToManyModel.getRelatedOrders()) assertTrue(!oneToManyModel.getRelatedOrders().isEmpty()) 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 32cb3b889..3c7374f75 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 @@ -5,7 +5,6 @@ import com.raizlabs.android.dbflow.annotation.OneToMany import com.raizlabs.android.dbflow.annotation.PrimaryKey import com.raizlabs.android.dbflow.annotation.Table import com.raizlabs.android.dbflow.kotlinextensions.from -import com.raizlabs.android.dbflow.kotlinextensions.list import com.raizlabs.android.dbflow.kotlinextensions.select import com.raizlabs.android.dbflow.kotlinextensions.where import com.raizlabs.android.dbflow.models.TwoColumnModel_Table.id @@ -16,11 +15,12 @@ class OneToManyModel(@PrimaryKey var name: String? = null) { var orders: List? = null @OneToMany(methods = arrayOf(OneToMany.Method.ALL), isVariablePrivate = true, - variableName = "orders") + variableName = "orders", efficientMethods = false) fun getRelatedOrders(): List { var localOrders = orders if (localOrders == null) { - localOrders = (select from TwoColumnModel::class where id.greaterThan(3)).list + localOrders = (select from TwoColumnModel::class where id.greaterThan(3)) + .queryList() } orders = localOrders return localOrders diff --git a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/QueryModelTest.kt b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/QueryModelTest.kt index f42ae731a..3ed55a318 100644 --- a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/QueryModelTest.kt +++ b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/QueryModelTest.kt @@ -26,9 +26,9 @@ class QueryModelTest : BaseUnitTest() { assert(blog.exists()) val result = (select(name.withTable().`as`("blogName"), id.withTable().`as`("authorId"), - Blog_Table.id.withTable().`as`("blogId")) from Blog::class innerJoin - Author::class on (author_id.withTable() eq id.withTable())) - .queryCustomSingle(AuthorNameQuery::class.java)!! + Blog_Table.id.withTable().`as`("blogId")) from Blog::class innerJoin + Author::class on (author_id.withTable() eq id.withTable())) + .queryCustomSingle(AuthorNameQuery::class.java)!! assertEquals(author.id, result.authorId) assertEquals(blog.id, result.blogId) } diff --git a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/QueryModels.kt b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/QueryModels.kt index 8c4c2fcac..86d61f072 100644 --- a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/QueryModels.kt +++ b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/models/QueryModels.kt @@ -23,4 +23,7 @@ class CustomBlobModel(@Column var myBlob: MyBlob? = null) { override fun getModelValue(data: Blob) = MyBlob(data.blob) } -} \ No newline at end of file +} + +@QueryModel(database = TestDatabase::class, allFields = true) +class AllFieldsQueryModel(var model: String? = null) \ 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 f0e5d98ba..cce4690c8 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 @@ -2,6 +2,8 @@ package com.raizlabs.android.dbflow.models 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 /** * Description: @@ -15,12 +17,15 @@ class SimpleCustomModel(@Column var name: String? = "") @Table(database = TestDatabase::class, insertConflict = ConflictAction.FAIL, updateConflict = ConflictAction.FAIL) class NumberModel(@PrimaryKey var id: Int = 0) +@Table(database = TestDatabase::class) +class CharModel(@PrimaryKey var id: Int = 0, @Column var char: Char? = null) + @Table(database = TestDatabase::class) class TwoColumnModel(@PrimaryKey var name: String? = "", @Column var id: Int = 0) @Table(database = TestDatabase::class, allFields = true) open class AllFieldsModel(@PrimaryKey var name: String? = null, - var count: Int = 0, + var count: Int? = 0, @Column(getterName = "getTruth") var truth: Boolean = false, internal val finalName: String = "", @@ -34,4 +39,36 @@ open class AllFieldsModel(@PrimaryKey var name: String? = null, } @Table(database = TestDatabase::class, allFields = true) -class SubclassAllFields(@Column var order: Int = 0) : AllFieldsModel() \ No newline at end of file +class SubclassAllFields(@Column var order: Int = 0) : AllFieldsModel() + +@Table(database = TestDatabase::class, assignDefaultValuesFromCursor = false) +class DontAssignDefaultModel(@PrimaryKey var name: String? = null, + @Column(getterName = "getNullableBool") var nullableBool: Boolean? = null, + @Column var index: Int = 0) + +@Table(database = TestDatabase::class, orderedCursorLookUp = true) +class OrderCursorModel(@PrimaryKey var id: Int = 0, @Column var name: String? = "", + @Column var age: Int = 0) + +@Table(database = TestDatabase::class) +class TypeConverterModel(@PrimaryKey var id: Int = 0, + @Column var blob: Blob? = null, + @Column(typeConverter = CustomTypeConverter::class) var customType: CustomType? = null) + +class CustomType(var name: String? = "") + +class CustomTypeConverter : TypeConverter() { + override fun getDBValue(model: CustomType?) = model?.name + + override fun getModelValue(data: String?) = if (data == null) { + null + } else { + CustomType(data) + } + +} + +@Table(database = TestDatabase::class) +class DefaultModel(@PrimaryKey @Column(defaultValue = "5") var id: Int? = 0, + @Column(defaultValue = "5.0") var location: Double? = 0.0, + @Column(defaultValue = "\"String\"") var name: String? = "") \ No newline at end of file diff --git a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/sql/language/FromTest.kt b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/sql/language/FromTest.kt index c469c20b1..95cc8d7ec 100644 --- a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/sql/language/FromTest.kt +++ b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/sql/language/FromTest.kt @@ -1,12 +1,13 @@ package com.raizlabs.android.dbflow.sql.language import com.raizlabs.android.dbflow.BaseUnitTest +import com.raizlabs.android.dbflow.contentobserver.User +import com.raizlabs.android.dbflow.kotlinextensions.* import com.raizlabs.android.dbflow.models.SimpleModel import com.raizlabs.android.dbflow.models.SimpleModel_Table.name import com.raizlabs.android.dbflow.models.TwoColumnModel import com.raizlabs.android.dbflow.models.TwoColumnModel_Table import com.raizlabs.android.dbflow.models.TwoColumnModel_Table.id -import com.raizlabs.android.dbflow.kotlinextensions.* import com.raizlabs.android.dbflow.sql.language.SQLite.select import junit.framework.Assert.assertEquals import junit.framework.Assert.assertTrue diff --git a/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/sql/migration/UpdateTableMigrationTest.kt b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/sql/migration/UpdateTableMigrationTest.kt new file mode 100644 index 000000000..767d35a84 --- /dev/null +++ b/dbflow-tests/src/test/java/com/raizlabs/android/dbflow/sql/migration/UpdateTableMigrationTest.kt @@ -0,0 +1,22 @@ +package com.raizlabs.android.dbflow.sql.migration + +import com.raizlabs.android.dbflow.BaseUnitTest +import com.raizlabs.android.dbflow.kotlinextensions.writableDatabaseForTable +import com.raizlabs.android.dbflow.models.SimpleModel +import com.raizlabs.android.dbflow.models.SimpleModel_Table +import org.junit.Test + +/** + * Description: + */ + +class UpdateTableMigrationTest : BaseUnitTest() { + + + @Test + fun testUpdateMigrationQuery() { + val update = UpdateTableMigration(SimpleModel::class.java) + update.set(SimpleModel_Table.name.eq("yes")) + update.migrate(writableDatabaseForTable()) + } +} 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 8c5570460..3a82771f0 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 @@ -36,15 +36,15 @@ */ public abstract class DatabaseDefinition { - final Map> migrationMap = new HashMap<>(); + private final Map> migrationMap = new HashMap<>(); - final Map, ModelAdapter> modelAdapters = new HashMap<>(); + private final Map, ModelAdapter> modelAdapters = new HashMap<>(); - final Map> modelTableNames = new HashMap<>(); + private final Map> modelTableNames = new HashMap<>(); - final Map, ModelViewAdapter> modelViewAdapterMap = new LinkedHashMap<>(); + private final Map, ModelViewAdapter> modelViewAdapterMap = new LinkedHashMap<>(); - final Map, QueryModelAdapter> queryModelAdapterMap = new LinkedHashMap<>(); + private final Map, QueryModelAdapter> queryModelAdapterMap = new LinkedHashMap<>(); /** * The helper that manages database changes and initialization @@ -103,6 +103,31 @@ public DatabaseDefinition() { } } + protected void addModelAdapter(ModelAdapter modelAdapter, DatabaseHolder holder) { + holder.putDatabaseForTable(modelAdapter.getModelClass(), this); + modelTableNames.put(modelAdapter.getTableName(), modelAdapter.getModelClass()); + modelAdapters.put(modelAdapter.getModelClass(), modelAdapter); + } + + protected void addModelViewAdapter(ModelViewAdapter modelViewAdapter, DatabaseHolder holder) { + holder.putDatabaseForTable(modelViewAdapter.getModelClass(), this); + modelViewAdapterMap.put(modelViewAdapter.getModelClass(), modelViewAdapter); + } + + protected void addQueryModelAdapter(QueryModelAdapter queryModelAdapter, DatabaseHolder holder) { + holder.putDatabaseForTable(queryModelAdapter.getModelClass(), this); + queryModelAdapterMap.put(queryModelAdapter.getModelClass(), queryModelAdapter); + } + + protected void addMigration(int version, Migration migration) { + List list = migrationMap.get(version); + if (list == null) { + list = new ArrayList<>(); + migrationMap.put(version, list); + } + list.add(migration); + } + /** * @return a list of all model classes in this database. */ 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 34712b3fc..605cabe67 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,7 @@ import com.raizlabs.android.dbflow.annotation.Table; import com.raizlabs.android.dbflow.converter.TypeConverter; +import com.raizlabs.android.dbflow.sql.QueryBuilder; import com.raizlabs.android.dbflow.sql.migration.Migration; import com.raizlabs.android.dbflow.structure.BaseModel; import com.raizlabs.android.dbflow.structure.BaseModelView; @@ -95,9 +96,12 @@ public static Class getTableClassForName(String databaseName, String tableNam DatabaseDefinition databaseDefinition = getDatabase(databaseName); Class modelClass = databaseDefinition.getModelClassForName(tableName); if (modelClass == null) { - throw new IllegalArgumentException(String.format("The specified table %1s was not found. " + - "Did you forget to add the @Table annotation and point it to %1s?", - tableName, databaseName)); + modelClass = databaseDefinition.getModelClassForName(QueryBuilder.quote(tableName)); + if (modelClass == null) { + throw new IllegalArgumentException(String.format("The specified table %1s was not found. " + + "Did you forget to add the @Table annotation and point it to %1s?", + tableName, databaseName)); + } } return modelClass; } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/list/FlowCursorList.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/list/FlowCursorList.java index 5eddd53b4..db7c0d637 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/list/FlowCursorList.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/list/FlowCursorList.java @@ -13,6 +13,7 @@ import com.raizlabs.android.dbflow.structure.ModelAdapter; import com.raizlabs.android.dbflow.structure.cache.ModelCache; import com.raizlabs.android.dbflow.structure.cache.ModelLruCache; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; import java.util.ArrayList; import java.util.HashSet; @@ -48,7 +49,7 @@ public interface OnCursorRefreshListener { public static final int MIN_CACHE_SIZE = 20; @Nullable - private Cursor cursor; + private FlowCursor cursor; private Class table; private ModelCache modelCache; @@ -301,7 +302,7 @@ public Builder newBuilder() { public static class Builder { private final Class modelClass; - private Cursor cursor; + private FlowCursor cursor; private ModelQueriable modelQueriable; private boolean cacheModels = true; private ModelCache modelCache; @@ -316,7 +317,7 @@ public Builder(@NonNull ModelQueriable modelQueriable) { } public Builder cursor(Cursor cursor) { - this.cursor = cursor; + this.cursor = FlowCursor.from(cursor); return this; } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/BaseAsyncObject.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/BaseAsyncObject.java index f7d2298ee..098e310bd 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/BaseAsyncObject.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/BaseAsyncObject.java @@ -1,5 +1,7 @@ package com.raizlabs.android.dbflow.sql; +import android.support.annotation.NonNull; + import com.raizlabs.android.dbflow.config.DatabaseDefinition; import com.raizlabs.android.dbflow.config.FlowManager; import com.raizlabs.android.dbflow.structure.database.transaction.ITransaction; @@ -13,10 +15,17 @@ public class BaseAsyncObject { private Transaction.Success successCallback; private Transaction.Error errorCallback; private Transaction currentTransaction; + + private final Class table; private final DatabaseDefinition databaseDefinition; - public BaseAsyncObject(Class modelClass) { - databaseDefinition = FlowManager.getDatabaseForTable(modelClass); + public BaseAsyncObject(Class table) { + this.table = table; + databaseDefinition = FlowManager.getDatabaseForTable(table); + } + + public Class getTable() { + return table; } /** @@ -46,21 +55,21 @@ public void cancel() { } } - protected void executeTransaction(ITransaction transaction) { + protected void executeTransaction(@NonNull ITransaction transaction) { cancel(); currentTransaction = databaseDefinition - .beginTransactionAsync(transaction) - .error(error) - .success(success) - .build(); + .beginTransactionAsync(transaction) + .error(error) + .success(success) + .build(); currentTransaction.execute(); } - protected void onError(Transaction transaction, Throwable error) { + protected void onError(@NonNull Transaction transaction, Throwable error) { } - protected void onSuccess(Transaction transaction) { + protected void onSuccess(@NonNull Transaction transaction) { } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/BaseQueriable.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/BaseQueriable.java index 886660671..e356774b9 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/BaseQueriable.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/BaseQueriable.java @@ -14,6 +14,7 @@ import com.raizlabs.android.dbflow.structure.database.DatabaseStatement; import com.raizlabs.android.dbflow.structure.database.DatabaseStatementWrapper; import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; /** * Description: Base implementation of something that can be queried from the database. @@ -39,6 +40,8 @@ public Class getTable() { * Execute a statement that returns a 1 by 1 table with a numeric value. * For example, SELECT COUNT(*) FROM table. * Please see {@link SQLiteStatement#simpleQueryForLong()}. + *

+ * catches a {@link SQLiteDoneException} if result is not found and returns 0. The error can safely be ignored. */ @Override public long count(DatabaseWrapper databaseWrapper) { @@ -48,7 +51,7 @@ public long count(DatabaseWrapper databaseWrapper) { return SqlUtils.longForQuery(databaseWrapper, query); } catch (SQLiteDoneException sde) { // catch exception here, log it but return 0; - FlowLog.log(FlowLog.Level.E, sde); + FlowLog.log(FlowLog.Level.W, sde); } return 0; } @@ -74,13 +77,13 @@ public boolean hasData(DatabaseWrapper databaseWrapper) { } @Override - public Cursor query() { + public FlowCursor query() { query(FlowManager.getWritableDatabaseForTable(table)); return null; } @Override - public Cursor query(DatabaseWrapper databaseWrapper) { + public FlowCursor query(DatabaseWrapper databaseWrapper) { if (getPrimaryAction().equals(BaseModel.Action.INSERT)) { // inserting, let's compile and insert DatabaseStatement databaseStatement = compileStatement(databaseWrapper); diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/BaseTransformable.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/BaseTransformable.java index 0e20dee43..1bb0f2b7a 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/BaseTransformable.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/BaseTransformable.java @@ -1,10 +1,10 @@ package com.raizlabs.android.dbflow.sql.language; -import android.database.Cursor; import android.support.annotation.NonNull; import com.raizlabs.android.dbflow.sql.language.property.IProperty; import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; import java.util.List; @@ -24,17 +24,17 @@ protected BaseTransformable(Class table) { } @NonNull - public Where where(SQLOperator... conditions) { + public Where where(@NonNull SQLOperator... conditions) { return new Where<>(this, conditions); } @Override - public Cursor query() { + public FlowCursor query() { return where().query(); } @Override - public Cursor query(DatabaseWrapper databaseWrapper) { + public FlowCursor query(DatabaseWrapper databaseWrapper) { return where().query(databaseWrapper); } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/CursorResult.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/CursorResult.java index d33f682ea..f53038135 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/CursorResult.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/CursorResult.java @@ -8,6 +8,7 @@ import com.raizlabs.android.dbflow.list.FlowCursorIterator; import com.raizlabs.android.dbflow.list.IFlowCursorIterator; import com.raizlabs.android.dbflow.structure.InstanceAdapter; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; import java.util.ArrayList; import java.util.List; @@ -21,18 +22,18 @@ public class CursorResult implements IFlowCursorIterator { private final InstanceAdapter retrievalAdapter; @Nullable - private Cursor cursor; + private FlowCursor cursor; @SuppressWarnings("unchecked") CursorResult(Class modelClass, @Nullable Cursor cursor) { - this.cursor = cursor; + this.cursor = FlowCursor.from(cursor); retrievalAdapter = FlowManager.getInstanceAdapter(modelClass); } /** * Swaps the current cursor and will close existing one. */ - public void swapCursor(@Nullable Cursor cursor) { + public void swapCursor(@Nullable FlowCursor cursor) { if (this.cursor != null) { if (!this.cursor.isClosed()) { this.cursor.close(); diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/Delete.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/Delete.java index 8684a5c83..2e020f03e 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/Delete.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/Delete.java @@ -18,7 +18,7 @@ public class Delete implements Query { * @param The class that implements {@link com.raizlabs.android.dbflow.structure.Model} */ public static void table(Class table, SQLOperator... conditions) { - new Delete().from(table).where(conditions).query(); + new Delete().from(table).where(conditions).executeUpdateDelete(); } /** @@ -48,7 +48,7 @@ public From from(Class table) { @Override public String getQuery() { return new QueryBuilder() - .append("DELETE") - .appendSpace().getQuery(); + .append("DELETE") + .appendSpace().getQuery(); } } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/OperatorGroup.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/OperatorGroup.java index c233b2087..7457c105c 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/OperatorGroup.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/OperatorGroup.java @@ -1,6 +1,7 @@ package com.raizlabs.android.dbflow.sql.language; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import com.raizlabs.android.dbflow.sql.Query; import com.raizlabs.android.dbflow.sql.QueryBuilder; @@ -164,10 +165,12 @@ public OperatorGroup orAll(Collection sqlOperators) { * Appends the {@link SQLOperator} with the specified operator string. */ @NonNull - private OperatorGroup operator(String operator, SQLOperator sqlOperator) { - setPreviousSeparator(operator); - conditionsList.add(sqlOperator); - isChanged = true; + private OperatorGroup operator(String operator, @Nullable SQLOperator sqlOperator) { + if (sqlOperator != null) { + setPreviousSeparator(operator); + conditionsList.add(sqlOperator); + isChanged = true; + } return this; } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/Where.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/Where.java index 5b2d02d56..5fd3d11fd 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/Where.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/Where.java @@ -10,6 +10,7 @@ import com.raizlabs.android.dbflow.sql.queriable.ModelQueriable; import com.raizlabs.android.dbflow.structure.BaseModel; import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; import java.util.ArrayList; import java.util.Collections; @@ -207,9 +208,9 @@ public String getQuery() { * @return the result of the query as a {@link Cursor}. */ @Override - public Cursor query(DatabaseWrapper wrapper) { + public FlowCursor query(DatabaseWrapper wrapper) { // Query the sql here - Cursor cursor; + FlowCursor cursor; if (whereBase.getQueryBuilderBase() instanceof Select) { cursor = wrapper.rawQuery(getQuery(), null); } else { @@ -220,7 +221,7 @@ public Cursor query(DatabaseWrapper wrapper) { } @Override - public Cursor query() { + public FlowCursor query() { return query(FlowManager.getDatabaseForTable(getTable()).getWritableDatabase()); } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/property/IProperty.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/property/IProperty.java index e41142514..94e417a31 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/property/IProperty.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/language/property/IProperty.java @@ -6,6 +6,7 @@ import com.raizlabs.android.dbflow.sql.language.Join; import com.raizlabs.android.dbflow.sql.language.Method; import com.raizlabs.android.dbflow.sql.language.NameAlias; +import com.raizlabs.android.dbflow.sql.language.OrderBy; import com.raizlabs.android.dbflow.structure.Model; /** @@ -117,4 +118,10 @@ public interface IProperty

extends Query { */ @NonNull Class getTable(); + + @NonNull + OrderBy asc(); + + @NonNull + OrderBy desc(); } 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 2ae98d26e..9d02fc40f 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 @@ -8,6 +8,7 @@ import com.raizlabs.android.dbflow.sql.language.IOperator; import com.raizlabs.android.dbflow.sql.language.NameAlias; import com.raizlabs.android.dbflow.sql.language.Operator; +import com.raizlabs.android.dbflow.sql.language.OrderBy; import java.util.Collection; @@ -505,6 +506,19 @@ public Operator rem(T value) { return getCondition().rem(value); } + + @Override + @NonNull + public OrderBy asc() { + return OrderBy.fromProperty(this).ascending(); + } + + @Override + @NonNull + public OrderBy desc() { + return OrderBy.fromProperty(this).descending(); + } + /** * @return helper method to construct it in a {@link #distinct()} call. */ diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/migration/UpdateTableMigration.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/migration/UpdateTableMigration.java index 33be7377a..6aa328552 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/migration/UpdateTableMigration.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/migration/UpdateTableMigration.java @@ -1,6 +1,7 @@ package com.raizlabs.android.dbflow.sql.migration; import android.support.annotation.CallSuper; +import android.support.annotation.Nullable; import com.raizlabs.android.dbflow.sql.language.BaseQueriable; import com.raizlabs.android.dbflow.sql.language.OperatorGroup; @@ -23,11 +24,13 @@ public class UpdateTableMigration extends BaseMigration { /** * Builds the conditions for the WHERE part of our query */ + @Nullable private OperatorGroup whereOperatorGroup; /** * The conditions to use to set fields in the update query */ + @Nullable private OperatorGroup setOperatorGroup; /** @@ -78,8 +81,8 @@ public void onPostMigrate() { public BaseQueriable getUpdateStatement() { return SQLite.update(table) - .set(setOperatorGroup) - .where(whereOperatorGroup); + .set(setOperatorGroup) + .where(whereOperatorGroup); } } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/CacheableListModelLoader.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/CacheableListModelLoader.java index ea8ad462a..4c8c96a64 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/CacheableListModelLoader.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/CacheableListModelLoader.java @@ -1,12 +1,12 @@ package com.raizlabs.android.dbflow.sql.queriable; -import android.database.Cursor; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.raizlabs.android.dbflow.annotation.Table; import com.raizlabs.android.dbflow.structure.ModelAdapter; import com.raizlabs.android.dbflow.structure.cache.ModelCache; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; import java.util.ArrayList; import java.util.List; @@ -51,7 +51,7 @@ public ModelAdapter getModelAdapter() { @NonNull @SuppressWarnings("unchecked") @Override - public List convertToData(@NonNull Cursor cursor, @Nullable List data) { + public List convertToData(@NonNull FlowCursor cursor, @Nullable List data) { if (data == null) { data = new ArrayList<>(); } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/CacheableModelLoader.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/CacheableModelLoader.java index 8a604222a..b3716c4a7 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/CacheableModelLoader.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/CacheableModelLoader.java @@ -1,12 +1,12 @@ package com.raizlabs.android.dbflow.sql.queriable; -import android.database.Cursor; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.raizlabs.android.dbflow.annotation.Table; import com.raizlabs.android.dbflow.structure.ModelAdapter; import com.raizlabs.android.dbflow.structure.cache.ModelCache; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; /** * Description: Loads model data that is backed by a {@link ModelCache}. Used when {@link Table#cachingEnabled()} @@ -51,7 +51,7 @@ public ModelAdapter getModelAdapter() { */ @Nullable @Override - public TModel convertToData(@NonNull Cursor cursor, @Nullable TModel data, boolean moveToFirst) { + public TModel convertToData(@NonNull FlowCursor cursor, @Nullable TModel data, boolean moveToFirst) { if (!moveToFirst || cursor.moveToFirst()) { Object[] values = getModelAdapter().getCachingColumnValuesFromCursor( new Object[getModelAdapter().getCachingColumns().length], cursor); diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/ListModelLoader.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/ListModelLoader.java index 37754f6b9..ea3bc4283 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/ListModelLoader.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/ListModelLoader.java @@ -1,10 +1,10 @@ package com.raizlabs.android.dbflow.sql.queriable; -import android.database.Cursor; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; import java.util.ArrayList; import java.util.List; @@ -46,13 +46,13 @@ public List load(@NonNull DatabaseWrapper databaseWrapper, String query, @NonNull @Override - public List load(@Nullable Cursor cursor) { + public List load(@Nullable FlowCursor cursor) { return super.load(cursor); } @NonNull @Override - public List load(@Nullable Cursor cursor, @Nullable List data) { + public List load(@Nullable FlowCursor cursor, @Nullable List data) { if (data == null) { data = new ArrayList<>(); } else { @@ -64,7 +64,7 @@ public List load(@Nullable Cursor cursor, @Nullable List data) { @SuppressWarnings("unchecked") @Override @NonNull - public List convertToData(@NonNull Cursor cursor, @Nullable List data) { + public List convertToData(@NonNull FlowCursor cursor, @Nullable List data) { if (data == null) { data = new ArrayList<>(); } else { diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/ModelLoader.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/ModelLoader.java index bb7de4b4a..5aa6d0d95 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/ModelLoader.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/ModelLoader.java @@ -9,6 +9,7 @@ import com.raizlabs.android.dbflow.config.FlowManager; import com.raizlabs.android.dbflow.structure.InstanceAdapter; import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; /** * Description: Represents how models load from DB. It will query a {@link SQLiteDatabase} @@ -54,17 +55,17 @@ public TReturn load(@NonNull DatabaseWrapper databaseWrapper, String query) { @Nullable public TReturn load(@NonNull DatabaseWrapper databaseWrapper, String query, @Nullable TReturn data) { - final Cursor cursor = databaseWrapper.rawQuery(query, null); + final FlowCursor cursor = databaseWrapper.rawQuery(query, null); return load(cursor, data); } @Nullable - public TReturn load(@Nullable Cursor cursor) { + public TReturn load(@Nullable FlowCursor cursor) { return load(cursor, null); } @Nullable - public TReturn load(@Nullable Cursor cursor, @Nullable TReturn data) { + public TReturn load(@Nullable FlowCursor cursor, @Nullable TReturn data) { if (cursor != null) { try { data = convertToData(cursor, data); @@ -103,5 +104,5 @@ public DatabaseDefinition getDatabaseDefinition() { * @return A new (or reused) instance that represents the {@link Cursor}. */ @Nullable - public abstract TReturn convertToData(@NonNull final Cursor cursor, @Nullable TReturn data); + public abstract TReturn convertToData(@NonNull final FlowCursor cursor, @Nullable TReturn data); } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/Queriable.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/Queriable.java index cc264ce1e..ab6f79e1c 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/Queriable.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/Queriable.java @@ -1,6 +1,5 @@ package com.raizlabs.android.dbflow.sql.queriable; -import android.database.Cursor; import android.support.annotation.Nullable; import com.raizlabs.android.dbflow.sql.Query; @@ -11,6 +10,7 @@ import com.raizlabs.android.dbflow.structure.Model; import com.raizlabs.android.dbflow.structure.database.DatabaseStatement; import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; /** * Description: The most basic interface that some of the classes such as {@link Insert}, {@link ModelQueriable}, @@ -22,7 +22,7 @@ public interface Queriable extends Query { * @return A cursor from the DB based on this query */ @Nullable - Cursor query(); + FlowCursor query(); /** * Allows you to pass in a {@link DatabaseWrapper} manually. @@ -31,7 +31,7 @@ public interface Queriable extends Query { * @return A cursor from the DB based on this query */ @Nullable - Cursor query(DatabaseWrapper databaseWrapper); + FlowCursor query(DatabaseWrapper databaseWrapper); /** diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/SingleKeyCacheableListModelLoader.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/SingleKeyCacheableListModelLoader.java index e735ab7c9..101eb8add 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/SingleKeyCacheableListModelLoader.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/SingleKeyCacheableListModelLoader.java @@ -1,9 +1,10 @@ package com.raizlabs.android.dbflow.sql.queriable; -import android.database.Cursor; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; + import java.util.ArrayList; import java.util.List; @@ -19,7 +20,7 @@ public SingleKeyCacheableListModelLoader(Class tModelClass) { @NonNull @SuppressWarnings("unchecked") @Override - public List convertToData(@NonNull Cursor cursor, @Nullable List data) { + public List convertToData(@NonNull FlowCursor cursor, @Nullable List data) { if (data == null) { data = new ArrayList<>(); } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/SingleKeyCacheableModelLoader.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/SingleKeyCacheableModelLoader.java index 9c746c76d..da4a2774f 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/SingleKeyCacheableModelLoader.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/SingleKeyCacheableModelLoader.java @@ -1,10 +1,10 @@ package com.raizlabs.android.dbflow.sql.queriable; -import android.database.Cursor; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.raizlabs.android.dbflow.structure.Model; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; /** * Description: More optimized version of {@link CacheableModelLoader} which assumes that the {@link Model} @@ -24,7 +24,7 @@ public SingleKeyCacheableModelLoader(Class modelClass) { */ @Nullable @Override - public TModel convertToData(@NonNull Cursor cursor, @Nullable TModel data, boolean moveToFirst) { + public TModel convertToData(@NonNull FlowCursor cursor, @Nullable TModel data, boolean moveToFirst) { if (!moveToFirst || cursor.moveToFirst()) { Object value = getModelAdapter().getCachingColumnValueFromCursor(cursor); TModel model = getModelCache().get(value); diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/SingleModelLoader.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/SingleModelLoader.java index f17502796..01dd7da1c 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/SingleModelLoader.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/SingleModelLoader.java @@ -1,9 +1,10 @@ package com.raizlabs.android.dbflow.sql.queriable; -import android.database.Cursor; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; + /** * Description: Responsible for loading data into a single object. */ @@ -15,7 +16,7 @@ public SingleModelLoader(Class modelClass) { @SuppressWarnings("unchecked") @Nullable - public TModel convertToData(@NonNull final Cursor cursor, @Nullable TModel data, boolean moveToFirst) { + public TModel convertToData(@NonNull final FlowCursor cursor, @Nullable TModel data, boolean moveToFirst) { if (!moveToFirst || cursor.moveToFirst()) { if (data == null) { data = getInstanceAdapter().newInstance(); @@ -26,7 +27,7 @@ public TModel convertToData(@NonNull final Cursor cursor, @Nullable TModel data, } @Override - public TModel convertToData(@NonNull final Cursor cursor, @Nullable TModel data) { + public TModel convertToData(@NonNull final FlowCursor cursor, @Nullable TModel data) { return convertToData(cursor, data, true); } } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/StringQuery.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/StringQuery.java index 7f03e04e2..efdb1b5bb 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/StringQuery.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/queriable/StringQuery.java @@ -1,6 +1,5 @@ package com.raizlabs.android.dbflow.sql.queriable; -import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import com.raizlabs.android.dbflow.config.FlowManager; @@ -9,6 +8,7 @@ import com.raizlabs.android.dbflow.sql.language.Delete; import com.raizlabs.android.dbflow.structure.BaseModel; import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; /** * Description: Provides a very basic query mechanism for strings. Allows you to easily perform custom SQL query string @@ -41,12 +41,12 @@ public String getQuery() { } @Override - public Cursor query() { + public FlowCursor query() { return query(FlowManager.getDatabaseForTable(getTable()).getWritableDatabase()); } @Override - public Cursor query(DatabaseWrapper databaseWrapper) { + public FlowCursor query(DatabaseWrapper databaseWrapper) { return databaseWrapper.rawQuery(query, args); } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/AsyncModel.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/AsyncModel.java index 40d819703..f2d15e5bd 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/AsyncModel.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/AsyncModel.java @@ -55,63 +55,93 @@ private ModelAdapter getModelAdapter() { return modelAdapter; } + @Override + public boolean save(@NonNull DatabaseWrapper wrapper) { + return save(); + } + @Override public boolean save() { executeTransaction(new ProcessModelTransaction.Builder<>( - new ProcessModelTransaction.ProcessModel() { - @Override - public void processModel(TModel model, DatabaseWrapper wrapper) { - getModelAdapter().save(model, wrapper); - } - }).add(model).build()); + new ProcessModelTransaction.ProcessModel() { + @Override + public void processModel(TModel model, DatabaseWrapper wrapper) { + getModelAdapter().save(model, wrapper); + } + }).add(model).build()); return false; } + @Override + public boolean delete(@NonNull DatabaseWrapper wrapper) { + return delete(); + } + @Override public boolean delete() { executeTransaction(new ProcessModelTransaction.Builder<>( - new ProcessModelTransaction.ProcessModel() { - @Override - public void processModel(TModel model, DatabaseWrapper wrapper) { - getModelAdapter().delete(model, wrapper); - } - }).add(model).build()); + new ProcessModelTransaction.ProcessModel() { + @Override + public void processModel(TModel model, DatabaseWrapper wrapper) { + getModelAdapter().delete(model, wrapper); + } + }).add(model).build()); return false; } + @Override + public boolean update(@NonNull DatabaseWrapper wrapper) { + return update(); + } + @Override public boolean update() { executeTransaction(new ProcessModelTransaction.Builder<>( - new ProcessModelTransaction.ProcessModel() { - @Override - public void processModel(TModel model, DatabaseWrapper wrapper) { - getModelAdapter().update(model, wrapper); - } - }).add(model).build()); + new ProcessModelTransaction.ProcessModel() { + @Override + public void processModel(TModel model, DatabaseWrapper wrapper) { + getModelAdapter().update(model, wrapper); + } + }).add(model).build()); return false; } + @Override + public long insert(DatabaseWrapper wrapper) { + return insert(); + } + @Override public long insert() { executeTransaction(new ProcessModelTransaction.Builder<>( - new ProcessModelTransaction.ProcessModel() { - @Override - public void processModel(TModel model, DatabaseWrapper wrapper) { - getModelAdapter().insert(model, wrapper); - } - }).add(model).build()); + new ProcessModelTransaction.ProcessModel() { + @Override + public void processModel(TModel model, DatabaseWrapper wrapper) { + getModelAdapter().insert(model, wrapper); + } + }).add(model).build()); return INVALID_ROW_ID; } + @Override + public void load(DatabaseWrapper wrapper) { + load(); + } + @Override public void load() { executeTransaction(new ProcessModelTransaction.Builder<>( - new ProcessModelTransaction.ProcessModel() { - @Override - public void processModel(TModel model, DatabaseWrapper wrapper) { - getModelAdapter().load(model, wrapper); - } - }).add(model).build()); + new ProcessModelTransaction.ProcessModel() { + @Override + public void processModel(TModel model, DatabaseWrapper wrapper) { + getModelAdapter().load(model, wrapper); + } + }).add(model).build()); + } + + @Override + public boolean exists(DatabaseWrapper wrapper) { + return exists(); } @Override @@ -119,8 +149,18 @@ public boolean exists() { return getModelAdapter().exists(model); } + /** + * @return Itself since it's already async. + */ + @NonNull + @Override + public AsyncModel async() { + //noinspection unchecked + return (AsyncModel) this; + } + @Override - protected void onSuccess(Transaction transaction) { + protected void onSuccess(@NonNull Transaction transaction) { if (onModelChangedListener != null && onModelChangedListener.get() != null) { onModelChangedListener.get().onModelChanged(model); } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/BaseModel.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/BaseModel.java index 411658f80..6bbbe80a5 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/BaseModel.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/BaseModel.java @@ -1,14 +1,16 @@ package com.raizlabs.android.dbflow.structure; +import android.support.annotation.NonNull; + import com.raizlabs.android.dbflow.annotation.ColumnIgnore; import com.raizlabs.android.dbflow.config.FlowManager; import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper; -import com.raizlabs.android.dbflow.structure.database.transaction.DefaultTransactionQueue; /** * Description: The base implementation of {@link Model}. It is recommended to use this class as * the base for your {@link Model}, but it is not required. */ +@SuppressWarnings("unchecked") public class BaseModel implements Model { /** @@ -45,76 +47,70 @@ public enum Action { @ColumnIgnore private transient ModelAdapter modelAdapter; - @SuppressWarnings("unchecked") @Override public void load() { getModelAdapter().load(this); } - @SuppressWarnings("unchecked") - public void load(DatabaseWrapper databaseWrapper) { - getModelAdapter().load(this, databaseWrapper); + @Override + public void load(DatabaseWrapper wrapper) { + getModelAdapter().load(this, wrapper); } - @SuppressWarnings("unchecked") @Override public boolean save() { return getModelAdapter().save(this); } - @SuppressWarnings("unchecked") - public boolean save(DatabaseWrapper databaseWrapper) { + + @Override + public boolean save(@NonNull DatabaseWrapper databaseWrapper) { return getModelAdapter().save(this, databaseWrapper); } - @SuppressWarnings("unchecked") @Override public boolean delete() { return getModelAdapter().delete(this); } - @SuppressWarnings("unchecked") - public boolean delete(DatabaseWrapper databaseWrapper) { + @Override + public boolean delete(@NonNull DatabaseWrapper databaseWrapper) { return getModelAdapter().delete(this, databaseWrapper); } - @SuppressWarnings("unchecked") @Override public boolean update() { return getModelAdapter().update(this); } - @SuppressWarnings("unchecked") - public void update(DatabaseWrapper databaseWrapper) { - getModelAdapter().update(this, databaseWrapper); + @Override + public boolean update(@NonNull DatabaseWrapper databaseWrapper) { + return getModelAdapter().update(this, databaseWrapper); } - @SuppressWarnings("unchecked") @Override public long insert() { return getModelAdapter().insert(this); } - @SuppressWarnings("unchecked") - public void insert(DatabaseWrapper databaseWrapper) { - getModelAdapter().insert(this, databaseWrapper); + @Override + public long insert(DatabaseWrapper databaseWrapper) { + return getModelAdapter().insert(this, databaseWrapper); } - @SuppressWarnings("unchecked") @Override public boolean exists() { return getModelAdapter().exists(this); } - @SuppressWarnings("unchecked") + @Override public boolean exists(DatabaseWrapper databaseWrapper) { return getModelAdapter().exists(this, databaseWrapper); } - /** - * @return An async instance of this model where all transactions are on the {@link DefaultTransactionQueue} - */ - public AsyncModel async() { + @NonNull + @Override + public AsyncModel async() { return new AsyncModel<>(this); } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/Model.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/Model.java index f52f54185..12ea1a6f3 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/Model.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/Model.java @@ -1,5 +1,10 @@ package com.raizlabs.android.dbflow.structure; +import android.support.annotation.NonNull; + +import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper; +import com.raizlabs.android.dbflow.structure.database.transaction.DefaultTransactionQueue; + public interface Model extends ReadOnlyModel { /** @@ -14,6 +19,13 @@ public interface Model extends ReadOnlyModel { */ boolean save(); + /** + * Saves the object in the DB. + * + * @return true if successful + */ + boolean save(@NonNull DatabaseWrapper wrapper); + /** * Deletes the object in the DB * @@ -21,6 +33,13 @@ public interface Model extends ReadOnlyModel { */ boolean delete(); + /** + * Deletes the object in the DB + * + * @return true if successful + */ + boolean delete(@NonNull DatabaseWrapper wrapper); + /** * Updates an object in the DB. Does not insert on failure. * @@ -28,6 +47,13 @@ public interface Model extends ReadOnlyModel { */ boolean update(); + /** + * Updates an object in the DB. Does not insert on failure. + * + * @return true if successful + */ + boolean update(@NonNull DatabaseWrapper wrapper); + /** * Inserts the object into the DB * @@ -35,4 +61,17 @@ public interface Model extends ReadOnlyModel { */ long insert(); + /** + * Inserts the object into the DB + * + * @return the count of the rows affected, should only be 1 here, or -1 if failed. + */ + long insert(DatabaseWrapper wrapper); + + /** + * @return An async instance of this model where all transactions are on the {@link DefaultTransactionQueue} + */ + @NonNull + AsyncModel async(); + } 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 b3a1889e8..2a129391f 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 @@ -1,7 +1,6 @@ package com.raizlabs.android.dbflow.structure; import android.content.ContentValues; -import android.database.Cursor; import android.database.sqlite.SQLiteStatement; import android.support.annotation.NonNull; @@ -10,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.config.FlowManager; 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; @@ -21,9 +19,12 @@ import com.raizlabs.android.dbflow.structure.cache.SimpleMapCache; import com.raizlabs.android.dbflow.structure.database.DatabaseStatement; import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; import java.util.Collection; +import static com.raizlabs.android.dbflow.config.FlowManager.getWritableDatabaseForTable; + /** * Description: Used for generated classes from the combination of {@link Table} and {@link Model}. */ @@ -50,8 +51,7 @@ public ModelAdapter(DatabaseDefinition databaseDefinition) { */ public DatabaseStatement getInsertStatement() { if (insertStatement == null) { - insertStatement = getInsertStatement( - FlowManager.getDatabaseForTable(getModelClass()).getWritableDatabase()); + insertStatement = getInsertStatement(getWritableDatabaseForTable(getModelClass())); } return insertStatement; @@ -85,8 +85,7 @@ public DatabaseStatement getDeleteStatement(TModel model, DatabaseWrapper databa */ public DatabaseStatement getCompiledStatement() { if (compiledStatement == null) { - compiledStatement = getCompiledStatement( - FlowManager.getDatabaseForTable(getModelClass()).getWritableDatabase()); + compiledStatement = getCompiledStatement(getWritableDatabaseForTable(getModelClass())); } return compiledStatement; @@ -115,7 +114,7 @@ public DatabaseStatement getCompiledStatement(DatabaseWrapper databaseWrapper) { * @param cursor The cursor to load * @return A new {@link TModel} */ - public TModel loadFromCursor(Cursor cursor) { + public TModel loadFromCursor(FlowCursor cursor) { TModel model = newInstance(); loadFromCursor(cursor, model); return model; @@ -134,12 +133,6 @@ public boolean save(TModel model, DatabaseWrapper databaseWrapper) { @Override public void saveAll(Collection models) { getListModelSaver().saveAll(models); - - if (cachingEnabled()) { - for (TModel model : models) { - getModelCache().addModel(getCachingId(model), model); - } - } } @Override @@ -217,6 +210,11 @@ public void bindToContentValues(ContentValues contentValues, TModel tModel) { bindToInsertValues(contentValues, tModel); } + @Override + public void bindToStatement(DatabaseStatement sqLiteStatement, TModel tModel) { + bindToInsertStatement(sqLiteStatement, tModel, 0); + } + /** * If a {@link Model} has an auto-incrementing primary key, then * this method will be overridden. @@ -286,14 +284,14 @@ public String[] getCachingColumns() { } /** - * Loads all primary keys from the {@link Cursor} into the inValues. The size of the array must + * Loads all primary keys from the {@link FlowCursor} into the inValues. The size of the array must * match all primary keys. This method gets generated when caching is enabled. * * @param inValues The reusable array of values to populate. * @param cursor The cursor to load from. * @return The populated set of values to load from cache. */ - public Object[] getCachingColumnValuesFromCursor(Object[] inValues, Cursor cursor) { + public Object[] getCachingColumnValuesFromCursor(Object[] inValues, FlowCursor cursor) { throwCachingError(); return null; } @@ -302,7 +300,7 @@ public Object[] getCachingColumnValuesFromCursor(Object[] inValues, Cursor curso * @param cursor The cursor to load caching id from. * @return The single cache column from cursor (if single). */ - public Object getCachingColumnValueFromCursor(Cursor cursor) { + public Object getCachingColumnValueFromCursor(FlowCursor cursor) { throwSingleCachingError(); return null; } @@ -384,13 +382,15 @@ public void setModelSaver(ModelSaver modelSaver) { } /** - * Reloads relationships when loading from {@link Cursor} in a model that's cacheable. By having + * Reloads relationships when loading from {@link FlowCursor} in a model that's cacheable. By having * relationships with cached models, the retrieval will be very fast. * * @param cursor The cursor to reload from. */ - public void reloadRelationships(@NonNull TModel model, Cursor cursor) { - throwCachingError(); + public void reloadRelationships(@NonNull TModel model, FlowCursor cursor) { + if (!cachingEnabled()) { + throwCachingError(); + } } @Override diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/NoModificationModel.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/NoModificationModel.java index 474fef898..eb6016224 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/NoModificationModel.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/NoModificationModel.java @@ -26,8 +26,8 @@ public void load() { } @SuppressWarnings("unchecked") - public void load(DatabaseWrapper databaseWrapper) { - getRetrievalAdapter().load(this, databaseWrapper); + public void load(DatabaseWrapper wrapper) { + getRetrievalAdapter().load(this, wrapper); } public RetrievalAdapter getRetrievalAdapter() { diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/ReadOnlyModel.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/ReadOnlyModel.java index 7e872ed6f..01d2c3c6d 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/ReadOnlyModel.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/ReadOnlyModel.java @@ -1,5 +1,8 @@ package com.raizlabs.android.dbflow.structure; +import com.raizlabs.android.dbflow.sql.migration.Migration; +import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper; + public interface ReadOnlyModel { /** @@ -7,10 +10,23 @@ public interface ReadOnlyModel { */ void load(); + /** + * Loads from the database the most recent version of the model based on it's primary keys. + * + * @param wrapper Database object to use. Useful for {@link Migration} classes. + */ + void load(DatabaseWrapper wrapper); + /** * @return true if this object exists in the DB. It combines all of it's primary key fields * into a SELECT query and checks to see if any results occur. */ boolean exists(); + /** + * @return true if this object exists in the DB. It combines all of it's primary key fields + * into a SELECT query and checks to see if any results occur. + * @param wrapper Database object to use. Useful for {@link Migration} classes. + */ + boolean exists(DatabaseWrapper wrapper); } 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 09cc2ce10..cc3cd6a41 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 @@ -12,6 +12,7 @@ import com.raizlabs.android.dbflow.sql.queriable.ListModelLoader; import com.raizlabs.android.dbflow.sql.queriable.SingleModelLoader; import com.raizlabs.android.dbflow.structure.database.DatabaseWrapper; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; /** * Description: Provides a base retrieval class for all {@link Model} backed @@ -26,7 +27,7 @@ public abstract class RetrievalAdapter { public RetrievalAdapter(DatabaseDefinition databaseDefinition) { DatabaseConfig databaseConfig = FlowManager.getConfig() - .getConfigForDatabase(databaseDefinition.getAssociatedDatabaseClassFile()); + .getConfigForDatabase(databaseDefinition.getAssociatedDatabaseClassFile()); if (databaseConfig != null) { tableConfig = databaseConfig.getTableConfigForTable(getModelClass()); if (tableConfig != null) { @@ -47,10 +48,10 @@ public void load(TModel model) { public void load(TModel model, DatabaseWrapper databaseWrapper) { getSingleModelLoader().load(databaseWrapper, - SQLite.select() - .from(getModelClass()) - .where(getPrimaryConditionClause(model)).getQuery(), - model); + SQLite.select() + .from(getModelClass()) + .where(getPrimaryConditionClause(model)).getQuery(), + model); } /** @@ -59,7 +60,7 @@ 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(Cursor cursor, TModel model); + public abstract void loadFromCursor(FlowCursor cursor, TModel model); /** * @param model The model to query values from diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/AndroidDatabase.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/AndroidDatabase.java index a73ee18cd..902245c65 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/AndroidDatabase.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/AndroidDatabase.java @@ -1,7 +1,6 @@ package com.raizlabs.android.dbflow.structure.database; import android.content.ContentValues; -import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Build; import android.support.annotation.NonNull; @@ -57,8 +56,8 @@ public DatabaseStatement compileStatement(String rawQuery) { } @Override - public Cursor rawQuery(String query, String[] selectionArgs) { - return database.rawQuery(query, selectionArgs); + public FlowCursor rawQuery(String query, String[] selectionArgs) { + return FlowCursor.from(database.rawQuery(query, selectionArgs)); } @Override @@ -84,15 +83,14 @@ public long insertWithOnConflict(String tableName, String nullColumnHack, Conten } @Override - public Cursor query( - @NonNull String tableName, - @Nullable String[] columns, - @Nullable String selection, - @Nullable String[] selectionArgs, - @Nullable String groupBy, - @Nullable String having, - @Nullable String orderBy) { - return database.query(tableName, columns, selection, selectionArgs, groupBy, having, orderBy); + public FlowCursor query(@NonNull String tableName, + @Nullable String[] columns, + @Nullable String selection, + @Nullable String[] selectionArgs, + @Nullable String groupBy, + @Nullable String having, + @Nullable String orderBy) { + return FlowCursor.from(database.query(tableName, columns, selection, selectionArgs, groupBy, having, orderBy)); } @Override diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/AndroidDatabaseStatement.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/AndroidDatabaseStatement.java index 4516e891f..739b8a187 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/AndroidDatabaseStatement.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/AndroidDatabaseStatement.java @@ -9,7 +9,7 @@ /** * Description: */ -public class AndroidDatabaseStatement implements DatabaseStatement { +public class AndroidDatabaseStatement extends BaseDatabaseStatement { public static AndroidDatabaseStatement from(SQLiteStatement sqLiteStatement, SQLiteDatabase database) { return new AndroidDatabaseStatement(sqLiteStatement, database); @@ -78,8 +78,8 @@ public long executeInsert() { } @Override - public void bindString(int index, String name) { - statement.bindString(index, name); + public void bindString(int index, String s) { + statement.bindString(index, s); } @Override diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/BaseDatabaseHelper.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/BaseDatabaseHelper.java index 0dedd1a24..35a9a3663 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/BaseDatabaseHelper.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/BaseDatabaseHelper.java @@ -57,6 +57,10 @@ public void onOpen(DatabaseWrapper db) { checkForeignKeySupport(db); } + public void onDowngrade(DatabaseWrapper db, int oldVersion, int newVersion) { + checkForeignKeySupport(db); + } + /** * If foreign keys are supported, we turn it on the DB specified. */ @@ -86,10 +90,10 @@ protected void executeCreations(final DatabaseWrapper database) { List modelViews = databaseDefinition.getModelViewAdapters(); for (ModelViewAdapter modelView : modelViews) { QueryBuilder queryBuilder = new QueryBuilder() - .append("CREATE VIEW IF NOT EXISTS") - .appendSpaceSeparated(modelView.getViewName()) - .append("AS ") - .append(modelView.getCreationQuery()); + .append("CREATE VIEW IF NOT EXISTS") + .appendSpaceSeparated(modelView.getViewName()) + .append("AS ") + .append(modelView.getCreationQuery()); try { database.execSQL(queryBuilder.getQuery()); } catch (SQLiteException e) { @@ -108,7 +112,7 @@ protected void executeMigrations(final DatabaseWrapper db, final int oldVersion, // will try migrations file or execute migrations from code try { final List files = Arrays.asList(FlowManager.getContext().getAssets().list( - MIGRATION_PATH + "/" + databaseDefinition.getDatabaseName())); + MIGRATION_PATH + "/" + databaseDefinition.getDatabaseName())); Collections.sort(files, new NaturalOrderComparator()); final Map> migrationFileMap = new HashMap<>(); diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/BaseDatabaseStatement.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/BaseDatabaseStatement.java new file mode 100644 index 000000000..ba888970d --- /dev/null +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/BaseDatabaseStatement.java @@ -0,0 +1,51 @@ +package com.raizlabs.android.dbflow.structure.database; + +import android.support.annotation.Nullable; + +/** + * Description: Default implementation for some {@link DatabaseStatement} methods. + */ +public abstract class BaseDatabaseStatement implements DatabaseStatement { + + @Override + public void bindStringOrNull(int index, @Nullable String s) { + if (s != null) { + bindString(index, s); + } else { + bindNull(index); + } + } + + @Override + public void bindNumber(int index, @Nullable Number number) { + bindNumberOrNull(index, number); + } + + @Override + public void bindNumberOrNull(int index, @Nullable Number number) { + if (number != null) { + bindLong(index, number.longValue()); + } else { + bindNull(index); + } + } + + @Override + public void bindDoubleOrNull(int index, @Nullable Double aDouble) { + if (aDouble != null) { + bindDouble(index, aDouble); + } else { + bindNull(index); + } + } + + @Override + public void bindBlobOrNull(int index, @Nullable byte[] bytes) { + if (bytes != null) { + bindBlob(index, bytes); + } else { + bindNull(index); + } + } + +} diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseHelperDelegate.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseHelperDelegate.java index 34a25cd3d..399624068 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseHelperDelegate.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseHelperDelegate.java @@ -42,12 +42,12 @@ public DatabaseHelperDelegate(DatabaseHelperListener databaseHelperListener, public void performRestoreFromBackup() { movePrepackagedDB(getDatabaseDefinition().getDatabaseFileName(), - getDatabaseDefinition().getDatabaseFileName()); + getDatabaseDefinition().getDatabaseFileName()); if (getDatabaseDefinition().backupEnabled()) { if (backupHelper == null) { throw new IllegalStateException("the passed backup helper was null, even though backup is enabled. " + - "Ensure that its passed in."); + "Ensure that its passed in."); } restoreDatabase(getTempDbFileName(), getDatabaseDefinition().getDatabaseFileName()); backupHelper.getDatabase(); @@ -86,6 +86,14 @@ public void onOpen(DatabaseWrapper db) { super.onOpen(db); } + @Override + public void onDowngrade(DatabaseWrapper db, int oldVersion, int newVersion) { + if (databaseHelperListener != null) { + databaseHelperListener.onDowngrade(db, oldVersion, newVersion); + } + super.onDowngrade(db, oldVersion, newVersion); + } + /** * @return the temporary database file name for when we have backups enabled * {@link DatabaseDefinition#backupEnabled()} @@ -106,8 +114,8 @@ public void movePrepackagedDB(String databaseName, String prepackagedName) { // If the database already exists, and is ok return if (dbPath.exists() && (!getDatabaseDefinition().areConsistencyChecksEnabled() || - (getDatabaseDefinition().areConsistencyChecksEnabled() - && isDatabaseIntegrityOk(getWritableDatabase())))) { + (getDatabaseDefinition().areConsistencyChecksEnabled() + && isDatabaseIntegrityOk(getWritableDatabase())))) { return; } @@ -121,7 +129,7 @@ && isDatabaseIntegrityOk(getWritableDatabase())))) { InputStream inputStream; // if it exists and the integrity is ok we use backup as the main DB is no longer valid if (existingDb.exists() && (!getDatabaseDefinition().backupEnabled() || getDatabaseDefinition().backupEnabled() - && backupHelper != null && isDatabaseIntegrityOk(backupHelper.getDatabase()))) { + && backupHelper != null && isDatabaseIntegrityOk(backupHelper.getDatabase()))) { inputStream = new FileInputStream(existingDb); } else { inputStream = FlowManager.getContext().getAssets().open(prepackagedName); @@ -161,7 +169,7 @@ public boolean isDatabaseIntegrityOk(DatabaseWrapper databaseWrapper) { if (!result.equalsIgnoreCase("ok")) { // integrity_checker failed on main or attached databases FlowLog.log(FlowLog.Level.E, "PRAGMA integrity_check on " + - getDatabaseDefinition().getDatabaseName() + " returned: " + result); + getDatabaseDefinition().getDatabaseName() + " returned: " + result); integrityOk = false; @@ -246,7 +254,7 @@ public void restoreDatabase(String databaseName, String prepackagedName) { InputStream inputStream; // if it exists and the integrity is ok if (existingDb.exists() && (getDatabaseDefinition().backupEnabled() - && backupHelper != null && isDatabaseIntegrityOk(backupHelper.getDatabase()))) { + && backupHelper != null && isDatabaseIntegrityOk(backupHelper.getDatabase()))) { inputStream = new FileInputStream(existingDb); } else { inputStream = FlowManager.getContext().getAssets().open(prepackagedName); @@ -265,7 +273,7 @@ public void restoreDatabase(String databaseName, String prepackagedName) { public void backupDB() { if (!getDatabaseDefinition().backupEnabled() || !getDatabaseDefinition().areConsistencyChecksEnabled()) { throw new IllegalStateException("Backups are not enabled for : " + getDatabaseDefinition().getDatabaseName() + ". Please consider adding " + - "both backupEnabled and consistency checks enabled to the Database annotation"); + "both backupEnabled and consistency checks enabled to the Database annotation"); } getDatabaseDefinition().beginTransactionAsync(new ITransaction() { diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseHelperListener.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseHelperListener.java index b00518226..d8de94442 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseHelperListener.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseHelperListener.java @@ -28,5 +28,12 @@ public interface DatabaseHelperListener { */ void onUpgrade(DatabaseWrapper database, int oldVersion, int newVersion); - + /** + * Called when DB is downgraded. Note that this may not be supported by all implementations of the DB. + * + * @param databaseWrapper The database downgraded. + * @param oldVersion The old. higher version. + * @param newVersion The new lower version. + */ + void onDowngrade(DatabaseWrapper databaseWrapper, int oldVersion, int newVersion); } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseStatement.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseStatement.java index effbd7385..5af8fdad0 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseStatement.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseStatement.java @@ -1,6 +1,7 @@ package com.raizlabs.android.dbflow.structure.database; import android.database.sqlite.SQLiteStatement; +import android.support.annotation.Nullable; /** * Description: Abstracts out a {@link SQLiteStatement}. @@ -19,13 +20,24 @@ public interface DatabaseStatement { long executeInsert(); - void bindString(int index, String name); + void bindString(int index, String s); + + void bindStringOrNull(int index, @Nullable String s); void bindNull(int index); void bindLong(int index, long aLong); + void bindNumber(int index, @Nullable Number number); + + void bindNumberOrNull(int index, @Nullable Number number); + void bindDouble(int index, double aDouble); + void bindDoubleOrNull(int index, @Nullable Double aDouble); + void bindBlob(int index, byte[] bytes); + + void bindBlobOrNull(int index, @Nullable byte[] bytes); + } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseStatementWrapper.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseStatementWrapper.java index bbca9caba..8a03d7f50 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseStatementWrapper.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseStatementWrapper.java @@ -9,7 +9,7 @@ * Description: Delegates all of its calls to the contained {@link DatabaseStatement}, while * providing notification methods for when operations occur. */ -public class DatabaseStatementWrapper implements DatabaseStatement { +public class DatabaseStatementWrapper extends BaseDatabaseStatement { @NonNull private final DatabaseStatement databaseStatement; @@ -62,8 +62,8 @@ public long executeInsert() { } @Override - public void bindString(int index, String name) { - databaseStatement.bindString(index, name); + public void bindString(int index, String s) { + databaseStatement.bindString(index, s); } @Override diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseWrapper.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseWrapper.java index 9233f9355..537916adf 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseWrapper.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/DatabaseWrapper.java @@ -1,7 +1,6 @@ package com.raizlabs.android.dbflow.structure.database; import android.content.ContentValues; -import android.database.Cursor; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -23,7 +22,7 @@ public interface DatabaseWrapper { DatabaseStatement compileStatement(String rawQuery); - Cursor rawQuery(String query, String[] selectionArgs); + FlowCursor rawQuery(String query, String[] selectionArgs); long updateWithOnConflict(String tableName, ContentValues contentValues, String where, String[] whereArgs, int conflictAlgorithm); @@ -31,9 +30,9 @@ long updateWithOnConflict(String tableName, ContentValues contentValues, String long insertWithOnConflict(String tableName, String nullColumnHack, ContentValues values, int sqLiteDatabaseAlgorithmInt); - Cursor query(@NonNull String tableName, @Nullable String[] columns, @Nullable String selection, - @Nullable String[] selectionArgs, @Nullable String groupBy, - @Nullable String having, @Nullable String orderBy); + FlowCursor query(@NonNull String tableName, @Nullable String[] columns, @Nullable String selection, + @Nullable String[] selectionArgs, @Nullable String groupBy, + @Nullable String having, @Nullable String orderBy); int delete(@NonNull String tableName, @Nullable String whereClause, @Nullable String[] whereArgs); } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/FlowCursor.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/FlowCursor.java new file mode 100644 index 000000000..715b95ed9 --- /dev/null +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/FlowCursor.java @@ -0,0 +1,280 @@ +package com.raizlabs.android.dbflow.structure.database; + +import android.database.Cursor; +import android.database.CursorWrapper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** + * Common {@link Cursor} class that wraps cursors we use in this library with convenience loading methods. + * This is used to help cut down on generated code size and potentially decrease method count. + */ +public class FlowCursor extends CursorWrapper { + + public static FlowCursor from(Cursor cursor) { + if (cursor instanceof FlowCursor) { + return (FlowCursor) cursor; + } else { + return new FlowCursor(cursor); + } + } + + private Cursor cursor; // compatibility reasons + + private FlowCursor(@NonNull Cursor cursor) { + super(cursor); + this.cursor = cursor; + } + + // compatibility + public Cursor getWrappedCursor() { + return cursor; + } + + public String getStringOrDefault(int index, String defValue) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getString(index); + } else { + return defValue; + } + } + + public String getStringOrDefault(String columnName) { + return getStringOrDefault(cursor.getColumnIndex(columnName)); + } + + @Nullable + public String getStringOrDefault(int index) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getString(index); + } else { + return null; + } + } + + public String getStringOrDefault(String columnName, String defValue) { + return getStringOrDefault(cursor.getColumnIndex(columnName), defValue); + } + + public int getIntOrDefault(String columnName) { + return getIntOrDefault(cursor.getColumnIndex(columnName)); + } + + public int getIntOrDefault(int index) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getInt(index); + } else { + return 0; + } + } + + public int getIntOrDefault(int index, int defValue) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getInt(index); + } else { + return defValue; + } + } + + public int getIntOrDefault(String columnName, int defValue) { + return getIntOrDefault(cursor.getColumnIndex(columnName), defValue); + } + + public Integer getIntOrDefault(int index, Integer defValue) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getInt(index); + } else { + return defValue; + } + } + + public Integer getIntOrDefault(String columnName, Integer defValue) { + return getIntOrDefault(cursor.getColumnIndex(columnName), defValue); + } + + public double getDoubleOrDefault(int index, double defValue) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getDouble(index); + } else { + return defValue; + } + } + + public double getDoubleOrDefault(String columnName) { + return getDoubleOrDefault(cursor.getColumnIndex(columnName)); + } + + public double getDoubleOrDefault(int index) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getDouble(index); + } else { + return 0; + } + } + + public double getDoubleOrDefault(String columnName, double defValue) { + return getDoubleOrDefault(cursor.getColumnIndex(columnName), defValue); + } + + public Double getDoubleOrDefault(int index, Double defValue) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getDouble(index); + } else { + return defValue; + } + } + + public Double getDoubleOrDefault(String columnName, Double defValue) { + return getDoubleOrDefault(cursor.getColumnIndex(columnName), defValue); + } + + public float getFloatOrDefault(int index, float defValue) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getFloat(index); + } else { + return defValue; + } + } + + public float getFloatOrDefault(String columnName) { + return getFloatOrDefault(cursor.getColumnIndex(columnName)); + } + + public float getFloatOrDefault(int index) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getFloat(index); + } else { + return 0; + } + } + + public float getFloatOrDefault(String columnName, float defValue) { + return getFloatOrDefault(cursor.getColumnIndex(columnName), defValue); + } + + public Float getFloatOrDefault(int index, Float defValue) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getFloat(index); + } else { + return defValue; + } + } + + public Float getFloatOrDefault(String columnName, Float defValue) { + return getFloatOrDefault(cursor.getColumnIndex(columnName), defValue); + } + + public long getLongOrDefault(int index, long defValue) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getLong(index); + } else { + return defValue; + } + } + + public long getLongOrDefault(String columnName) { + return getLongOrDefault(cursor.getColumnIndex(columnName)); + } + + public long getLongOrDefault(int index) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getLong(index); + } else { + return 0; + } + } + + public long getLongOrDefault(String columnName, long defValue) { + return getLongOrDefault(cursor.getColumnIndex(columnName), defValue); + } + + public Long getLongOrDefault(int index, Long defValue) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getLong(index); + } else { + return defValue; + } + } + + public Long getLongOrDefault(String columnName, Long defValue) { + return getLongOrDefault(cursor.getColumnIndex(columnName), defValue); + } + + public short getShortOrDefault(int index, short defValue) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getShort(index); + } else { + return defValue; + } + } + + public short getShortOrDefault(String columnName) { + return getShortOrDefault(cursor.getColumnIndex(columnName)); + } + + public short getShortOrDefault(int index) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getShort(index); + } else { + return 0; + } + } + + public short getShortOrDefault(String columnName, short defValue) { + return getShortOrDefault(cursor.getColumnIndex(columnName), defValue); + } + + public Short getShortOrDefault(int index, Short defValue) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getShort(index); + } else { + return defValue; + } + } + + public Short getShortOrDefault(String columnName, Short defValue) { + return getShortOrDefault(cursor.getColumnIndex(columnName), defValue); + } + + public byte[] getBlobOrDefault(int index, byte[] defValue) { + if (index != -1 && !cursor.isNull(index)) { + return cursor.getBlob(index); + } else { + return defValue; + } + } + + public byte[] getBlobOrDefault(String columnName, byte[] defValue) { + return getBlobOrDefault(cursor.getColumnIndex(columnName), defValue); + } + + public boolean getBooleanOrDefault(int index, boolean defValue) { + if (index != -1 && !cursor.isNull(index)) { + return getBoolean(index); + } else { + return defValue; + } + } + + public boolean getBooleanOrDefault(String columnName) { + return getBooleanOrDefault(cursor.getColumnIndex(columnName)); + } + + public boolean getBooleanOrDefault(int index) { + if (index != -1 && !cursor.isNull(index)) { + return getBoolean(index); + } else { + return false; + } + } + + public boolean getBooleanOrDefault(String columnName, boolean defValue) { + return getBooleanOrDefault(cursor.getColumnIndex(columnName), defValue); + } + + public boolean getBoolean(int index) { + return cursor.getInt(index) == 1; + } + +} + diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/FlowSQLiteOpenHelper.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/FlowSQLiteOpenHelper.java index 7d2b54231..bd9492b63 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/FlowSQLiteOpenHelper.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/database/FlowSQLiteOpenHelper.java @@ -81,6 +81,11 @@ public void onOpen(SQLiteDatabase db) { databaseHelperDelegate.onOpen(AndroidDatabase.from(db)); } + @Override + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + databaseHelperDelegate.onDowngrade(AndroidDatabase.from(db), oldVersion, newVersion); + } + @Override public void closeDB() { getDatabase(); @@ -145,6 +150,11 @@ public void onOpen(SQLiteDatabase db) { baseDatabaseHelper.onOpen(AndroidDatabase.from(db)); } + @Override + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + baseDatabaseHelper.onDowngrade(AndroidDatabase.from(db), oldVersion, newVersion); + } + @Override public void closeDB() { } diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/provider/BaseProviderModel.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/provider/BaseProviderModel.java index 9d88f7bdb..44437bd40 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/provider/BaseProviderModel.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/provider/BaseProviderModel.java @@ -7,6 +7,7 @@ import com.raizlabs.android.dbflow.sql.language.OperatorGroup; import com.raizlabs.android.dbflow.structure.BaseModel; import com.raizlabs.android.dbflow.structure.Model; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; /** * Description: Provides a base implementation of a {@link Model} backed @@ -15,7 +16,7 @@ * keep modifications locally from the {@link ContentProvider} */ public abstract class BaseProviderModel - extends BaseModel implements ModelProvider { + extends BaseModel implements ModelProvider { @Override public boolean delete() { @@ -52,7 +53,7 @@ public long insert() { @SuppressWarnings("unchecked") public boolean exists() { Cursor cursor = ContentUtils.query(FlowManager.getContext().getContentResolver(), - getQueryUri(), getModelAdapter().getPrimaryConditionClause(this), ""); + getQueryUri(), getModelAdapter().getPrimaryConditionClause(this), ""); boolean exists = (cursor != null && cursor.getCount() > 0); if (cursor != null) { cursor.close(); @@ -64,8 +65,8 @@ public boolean exists() { @SuppressWarnings("unchecked") public void load(OperatorGroup whereConditions, String orderBy, String... columns) { - Cursor cursor = ContentUtils.query(FlowManager.getContext().getContentResolver(), - getQueryUri(), whereConditions, orderBy, columns); + FlowCursor cursor = FlowCursor.from(ContentUtils.query(FlowManager.getContext().getContentResolver(), + getQueryUri(), whereConditions, orderBy, columns)); if (cursor != null && cursor.moveToFirst()) { getModelAdapter().loadFromCursor(cursor, this); cursor.close(); diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/provider/BaseSyncableProviderModel.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/provider/BaseSyncableProviderModel.java index ee5774887..5b4e4b991 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/provider/BaseSyncableProviderModel.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/provider/BaseSyncableProviderModel.java @@ -1,12 +1,12 @@ package com.raizlabs.android.dbflow.structure.provider; import android.content.ContentProvider; -import android.database.Cursor; import com.raizlabs.android.dbflow.config.FlowManager; import com.raizlabs.android.dbflow.sql.language.OperatorGroup; import com.raizlabs.android.dbflow.structure.BaseModel; import com.raizlabs.android.dbflow.structure.Model; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; /** * Description: Provides a base implementation of a {@link Model} backed @@ -44,7 +44,8 @@ public boolean update() { @SuppressWarnings("unchecked") public void load(OperatorGroup whereOperatorGroup, String orderBy, String... columns) { - Cursor cursor = ContentUtils.query(FlowManager.getContext().getContentResolver(), getQueryUri(), whereOperatorGroup, orderBy, columns); + FlowCursor cursor = FlowCursor.from(ContentUtils.query(FlowManager.getContext().getContentResolver(), + getQueryUri(), whereOperatorGroup, orderBy, columns)); if (cursor != null && cursor.moveToFirst()) { getModelAdapter().loadFromCursor(cursor, this); cursor.close(); diff --git a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/provider/ContentUtils.java b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/provider/ContentUtils.java index 3a2d40b47..31902791e 100644 --- a/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/provider/ContentUtils.java +++ b/dbflow/src/main/java/com/raizlabs/android/dbflow/structure/provider/ContentUtils.java @@ -10,8 +10,8 @@ import com.raizlabs.android.dbflow.config.FlowManager; import com.raizlabs.android.dbflow.sql.language.Operator; import com.raizlabs.android.dbflow.sql.language.OperatorGroup; -import com.raizlabs.android.dbflow.structure.Model; import com.raizlabs.android.dbflow.structure.ModelAdapter; +import com.raizlabs.android.dbflow.structure.database.FlowCursor; import java.util.ArrayList; import java.util.List; @@ -57,9 +57,8 @@ public static Uri buildUri(String baseContentUri, String authority, String... pa * Inserts the model into the {@link android.content.ContentResolver}. Uses the insertUri to resolve * the reference and the model to convert its data into {@link android.content.ContentValues} * - * @param insertUri A {@link android.net.Uri} from the {@link ContentProvider} class definition. - * @param model The model to insert. - * @param The class that implements {@link Model} + * @param insertUri A {@link android.net.Uri} from the {@link ContentProvider} class definition. + * @param model The model to insert. * @return A Uri of the inserted data. */ public static Uri insert(Uri insertUri, TableClass model) { @@ -73,7 +72,6 @@ public static Uri insert(Uri insertUri, TableClass model) { * @param contentResolver The content resolver to use (if different from {@link FlowManager#getContext()}) * @param insertUri A {@link android.net.Uri} from the {@link ContentProvider} class definition. * @param model The model to insert. - * @param The class that implements {@link Model} * @return The Uri of the inserted data. */ @SuppressWarnings("unchecked") @@ -97,7 +95,6 @@ public static Uri insert(ContentResolver contentResolver, Uri inser * @param bulkInsertUri The URI to bulk insert with * @param table The table to insert into * @param models The models to insert. - * @param The class that implements {@link Model} * @return The count of the rows affected by the insert. */ public static int bulkInsert(ContentResolver contentResolver, Uri bulkInsertUri, @@ -123,7 +120,6 @@ public static int bulkInsert(ContentResolver contentResolver, Uri b * @param bulkInsertUri The URI to bulk insert with * @param table The table to insert into * @param models The models to insert. - * @param The class that implements {@link Model} * @return The count of the rows affected by the insert. */ public static int bulkInsert(Uri bulkInsertUri, Class table, List models) { @@ -134,9 +130,8 @@ public static int bulkInsert(Uri bulkInsertUri, Class t * Updates the model through the {@link android.content.ContentResolver}. Uses the updateUri to * resolve the reference and the model to convert its data in {@link android.content.ContentValues} * - * @param updateUri A {@link android.net.Uri} from the {@link ContentProvider} - * @param model A model to update - * @param The class that implements {@link Model} + * @param updateUri A {@link android.net.Uri} from the {@link ContentProvider} + * @param model A model to update * @return The number of rows updated. */ public static int update(Uri updateUri, TableClass model) { @@ -150,7 +145,6 @@ public static int update(Uri updateUri, TableClass model) { * @param contentResolver The content resolver to use (if different from {@link FlowManager#getContext()}) * @param updateUri A {@link android.net.Uri} from the {@link ContentProvider} * @param model The model to update - * @param The class that implements {@link Model} * @return The number of rows updated. */ @SuppressWarnings("unchecked") @@ -168,11 +162,10 @@ public static int update(ContentResolver contentResolver, Uri updat /** * Deletes the specified model through the {@link android.content.ContentResolver}. Uses the deleteUri - * to resolve the reference and the model to {@link ModelAdapter#getPrimaryConditionClause(Model)} + * to resolve the reference and the model to {@link ModelAdapter#getPrimaryConditionClause(Object)}} * - * @param deleteUri A {@link android.net.Uri} from the {@link ContentProvider} - * @param model The model to delete - * @param The class that implements {@link Model} + * @param deleteUri A {@link android.net.Uri} from the {@link ContentProvider} + * @param model The model to delete * @return The number of rows deleted. */ @SuppressWarnings("unchecked") @@ -182,12 +175,11 @@ public static int delete(Uri deleteUri, TableClass model) { /** * Deletes the specified model through the {@link android.content.ContentResolver}. Uses the deleteUri - * to resolve the reference and the model to {@link ModelAdapter#getPrimaryConditionClause(Model)} + * to resolve the reference and the model to {@link ModelAdapter#getPrimaryConditionClause(Object)} * * @param contentResolver The content resolver to use (if different from {@link FlowManager#getContext()}) * @param deleteUri A {@link android.net.Uri} from the {@link ContentProvider} * @param model The model to delete - * @param The class that implements {@link Model} * @return The number of rows deleted. */ @SuppressWarnings("unchecked") @@ -231,7 +223,6 @@ public static Cursor query(ContentResolver contentResolver, Uri queryUri, * @param whereConditions The set of {@link Operator} to query the content provider. * @param orderBy The order by clause without the ORDER BY * @param columns The list of columns to query. - * @param The class that implements {@link Model} * @return A list of {@link TableClass} */ public static List queryList(Uri queryUri, Class table, @@ -251,19 +242,18 @@ public static List queryList(Uri queryUri, Class The class that implements {@link Model} * @return A list of {@link TableClass} */ - public static List queryList(ContentResolver contentResolver, Uri queryUri, Class table, + public static List queryList(ContentResolver contentResolver, Uri queryUri, + Class table, OperatorGroup whereConditions, String orderBy, String... columns) { - Cursor cursor = contentResolver.query(queryUri, columns, whereConditions.getQuery(), null, orderBy); + FlowCursor cursor = FlowCursor.from(contentResolver.query(queryUri, columns, whereConditions.getQuery(), null, orderBy)); if (cursor != null) { return FlowManager.getModelAdapter(table) - .getListModelLoader() - .load(cursor); + .getListModelLoader() + .load(cursor); } - return new ArrayList<>(); } @@ -276,7 +266,6 @@ public static List queryList(ContentResolver contentRes * @param whereConditions The set of {@link Operator} to query the content provider. * @param orderBy The order by clause without the ORDER BY * @param columns The list of columns to query. - * @param The class that implements {@link Model} * @return The first {@link TableClass} of the list query from the content provider. */ public static TableClass querySingle(Uri queryUri, Class table, @@ -295,7 +284,6 @@ public static TableClass querySingle(Uri queryUri, Class The class that implements {@link Model} * @return The first {@link TableClass} of the list query from the content provider. */ public static TableClass querySingle(ContentResolver contentResolver, Uri queryUri, Class table, diff --git a/gradle.properties b/gradle.properties index 8293f2161..c243e81ce 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -version=4.0.0-beta7 +version=4.0.0 version_code=1 group=com.raizlabs.android bt_siteUrl=https://github.com/Raizlabs/DBFlow diff --git a/usage2 b/usage2 new file mode 160000 index 000000000..99495e356 --- /dev/null +++ b/usage2 @@ -0,0 +1 @@ +Subproject commit 99495e3566e38785870f6c580caa3915709f8d1f diff --git a/usage2/Caching.md b/usage2/Caching.md deleted file mode 100644 index 834f88a74..000000000 --- a/usage2/Caching.md +++ /dev/null @@ -1,143 +0,0 @@ -# Caching - -DBFlow provides powerful caching mechanisms to speed up retrieval from the database -to enable high performance in our applications. - -Caching is not enabled by default, but it is very easy to enable. - -**Note**: caching functions correctly in only when we do CRUD operations on full model queries (i.e. Single table, no projections). - -Caching should be used when: - 1. Loading the same set of `Model` many times with slightly different selection. - 2. Operating on full `Model` objects (containing all `@PrimaryKey`) - -Caching should be avoided (or clear caches) when: - 1. Performing selection, complex queries, anything different than starting with `SQLite.select()` - 2. Using the wrapper modifications such as `Insert`, `Update`, or `Delete`. In this case you should clear the associated cache, post-operation. - -## Supported Caching Classes - -Caching is supported for: - 1. `SparseArray` via `SparseArrayBasedCache` (platform SparseArray) - 2. `Map` via `SimpleMapCache` - 3. `LruCache` via `ModelLruCache` (copy of `LruCache`, so dependency avoided) - 4. Custom Caching classes that implement `ModelCache` - -Cache sizes are not supported for `SimpleMapCache`. This is because `Map` can hold -arbitrary size of contents. - -## Enable Caching - -To enable caching on a single-primary key table, simply specify that it is enabled: - -```java - - -@Table(database = AppDatabase.class, cachingEnabled = true) -public class CacheableModel { - - @PrimaryKey(autoincrement = true) - long id; - - @Column - String name; -} - -``` - -to use caching on a table that uses multiple primary keys, [see](/usage2/Caching.md#multiple-primary-key-caching). - -By default we use a `SimpleMapCache`, which loads `Model` into a `Map`. The key is -either the primary key of the object or a combination of the two, but it should have -an associated `HashCode` and `equals()` value. - -## Loading from the DB - -When retrieving from the database, we still run a full query that returns a `Cursor`. -We skirt the expensive conversion process by checking the combination of Primary key on each row. -If an item exists with the same primary key combination, we return that object out of the cache. - -If you operate on a model object, that change gets reflected in the cache. But beware -modifying them in a separate thread might result in state state returned if the cache is not synchronized -properly. - -Any time a field on these objects are modified, you _should_ immediately save those -since we have a direct reference to the object from the cache. Otherwise, the DB -and cache could get into an inconsistent state. - -```java - -MyModel model = SQLite.select().from(MyModel.class).where(...).querySingle(); -model.setName("Name"); -model.save(); // save it to DB post any modifications to this object. - -``` - -## Disable Caching For Some Queries - -To disable caching on certain queries as you might want to project on only a few columns, -rather than the full dataset. Just call `disableCaching()`: - -```java - -select(My_Table.column, My_Table.column2) - .from(My.class) - .disableCaching() - .queryList(); - -``` - -## Advanced - -### Specifying cache Size - -To specify cache size, set `@Table(cacheSize = {size})`. Please note that not all -caches support sizing. It's up to each cache. - -### Custom Caches - -To specify a custom cache for a table, please define a `public static final` field: - -```java - -@ModelCacheField -public static ModelCache modelCache = new SimpleMapCache<>(); // replace with any cache you want. - -``` - -### Multiple Primary Key Caching - -This allows for tables that have multiple primary keys be used in caching. To use, -add a `@MultiCacheField` `public static final` field. -for example we have a `Coordinate` class: - - -```java - - -@Table(database = AppDatabase.class, cachingEnabled = true) -public class Coordinate { - - @MultiCacheField - public static final IMultiKeyCacheConverter multiKeyCacheModel = new IMultiKeyCacheConverter() { - - @Override - @NonNull - public String getCachingKey(@NonNull Object[] values) { - return "(" + values[0] + "," + values[1] + ")"; - } - }; - - @PrimaryKey - double latitude; - - @PrimaryKey - double longitude; - - -``` - -In this case we use the `IMultiKeyCacheConverter` class, which specifies a key type -that the object returns. The `getCachingKey` method returns an ordered set of `@PrimaryKey` -columns in declaration order. Also the value that is returned should have an `equals()` or `hashcode()` specified -especially when used in the `SimpleMapCache`. diff --git a/usage2/ContentProviderGeneration.md b/usage2/ContentProviderGeneration.md deleted file mode 100644 index 3bf2d5566..000000000 --- a/usage2/ContentProviderGeneration.md +++ /dev/null @@ -1,234 +0,0 @@ -# Content Provider Generation -This library includes a very fast, easy way to use `ContentProvider`! Using annotations, you can generate `ContentProvider` with ease. - -## Getting Started -This feature is largely based off of [schematic](https://github.com/SimonVT/schematic), - while leveraging DBFlow's power. - -### Placeholder ContentProvider -In order to define a `ContentProvider`, you must define it in a placeholder class: - -```java - -@ContentProvider(authority = TestContentProvider.AUTHORITY, - database = TestDatabase.class, - baseContentUri = TestContentProvider.BASE_CONTENT_URI) -public class TestContentProvider { - - public static final String AUTHORITY = "com.raizlabs.android.dbflow.test.provider"; - - public static final String BASE_CONTENT_URI = "content://"; - -} -``` - -or you can use the annotation in any class you wish. The recommended place would be in a `@Database` placeholder class. This is to simplify some of the declarations and keep it all in one place. - -```java - -@ContentProvider(authority = TestDatabase.AUTHORITY, - database = TestDatabase.class, - baseContentUri = TestDatabase.BASE_CONTENT_URI) -@Database(name = TestDatabase.NAME, version = TestDatabase.VERSION) -public class TestDatabase { - - public static final String NAME = "TestDatabase"; - - public static final int VERSION = "1"; - - public static final String AUTHORITY = "com.raizlabs.android.dbflow.test.provider"; - - public static final String BASE_CONTENT_URI = "content://"; - -} -``` - -### Adding To Manifest -In other applications or your current's `AndroidManifest.xml` add the **generated $Provider** class: - -```xml - - -``` - -`android:exported`: setting this to true, enables other applications to make use of it. - -**True** is recommended for outside application access. - -**Note you must have at least one `@TableEndpoint` for it to compile/pass error checking** - -### Adding endpoints into the data -There are two ways of defining `@TableEndpoint`: -1. Create an inner class within the `@ContentProvider` annotation. -2. Or Add the annotation to a `@Table` and specify the content provider class name (ex. TestContentProvider) - -`@TableEndpoint`: links up a query, insert, delete, and update to a specific table in the `ContentProvider` local database. - -Some recommendations: -1. (if inside a `@ContentProvider` class) Name the inner class same as the table it's referencing -2. Create a `public static final String ENDPOINT = "{tableName}"` field for reusability -3. Create `buildUri()` method (see below) to aid in creating other ones. - -To define one: - -```java - -@TableEndpoint(ContentProviderModel.ENDPOINT) -public static class ContentProviderModel { - - public static final String ENDPOINT = "ContentProviderModel"; - - private static Uri buildUri(String... paths) { - Uri.Builder builder = Uri.parse(BASE_CONTENT_URI + AUTHORITY).buildUpon(); - for (String path : paths) { - builder.appendPath(path); - } - return builder.build(); - } - - @ContentUri(path = ContentProviderModel.ENDPOINT, - type = ContentUri.ContentType.VND_MULTIPLE + ENDPOINT) - public static Uri CONTENT_URI = buildUri(ENDPOINT); - -} -``` - -or via the table it belongs to - -```java - - -@TableEndpoint(name = ContentProviderModel.NAME, contentProvider = ContentDatabase.class) -@Table(database = ContentDatabase.class, tableName = ContentProviderModel.NAME) -public class ContentProviderModel extends BaseProviderModel { - - public static final String NAME = "ContentProviderModel"; - - @ContentUri(path = NAME, type = ContentUri.ContentType.VND_MULTIPLE + NAME) - public static final Uri CONTENT_URI = ContentUtils.buildUriWithAuthority(ContentDatabase.AUTHORITY); - - @PrimaryKey(autoincrement = true) - long id; - - @Column - String notes; - - @Column - String title; - - @Override - public Uri getDeleteUri() { - return TestContentProvider.ContentProviderModel.CONTENT_URI; - } - - @Override - public Uri getInsertUri() { - return TestContentProvider.ContentProviderModel.CONTENT_URI; - } - - @Override - public Uri getUpdateUri() { - return TestContentProvider.ContentProviderModel.CONTENT_URI; - } - - @Override - public Uri getQueryUri() { - return TestContentProvider.ContentProviderModel.CONTENT_URI; - } -} -``` - -There are much more detailed usages of the `@ContentUri` annotation. Those will be in a later section. - -### Connect Model operations to the newly created ContentProvider -There are two kinds of `Model` that connect your application to a ContentProvider that was defined in your app, or another app. Extend these for convenience, however they are not required. - -`BaseProviderModel`: Overrides all `Model` methods and performs them on the `ContentProvider` - -`BaseSyncableProviderModel`: same as above, except it will synchronize the data changes with the local app database as well! - -#### Interacting with the Content Provider -You can use the `ContentUtils` methods: - -```java - -ContentProviderModel contentProviderModel = ...; // some instance - -int count = ContentUtils.update(getContentResolver(), ContentProviderModel.CONTENT_URI, contentProviderModel); - -Uri uri = ContentUtils.insert(getContentResolver(), ContentProviderModel.CONTENT_URI, contentProviderModel); - -int count = ContentUtils.delete(getContentResolver(), someContentUri, contentProviderModel); -``` - -**Recommended** usage is extending `BaseSyncableProviderModel` (for inter-app usage) so the local database contains the same data. Otherwise `BaseProviderModel` works just as well. - -```java - -MyModel model = new MyModel(); -model.id = 5; -model.load(); // queries the content provider - -model.someProp = "Hello" -model.update(false); // runs an update on the CP - -model.insert(false); // inserts the data into the CP -``` - -## Advanced Usage -### Notify Methods -You can define `@Notify` method to specify a custom interaction with the `ContentProvider` and return a custom `Uri[]` that notifies the contained `ContentResolver`. These methods can have any valid parameter from the `ContentProvider` methods. - -Supported kinds include: -1. Update -2. Insert -3. Delete - -#### Example - -```java - -@Notify(method = Notify.Method.UPDATE, -paths = {}) // specify paths that will call this method when specified. -public static Uri[] onUpdate(Context context, Uri uri) { - - return new Uri[] { - // return custom uris here - }; -} -``` - -### ContentUri Advanced -#### Path Segments -Path segments enable you to "filter" the uri query, update, insert, and deletion by a specific column and a value define by '#'. - -To specify one, this is an example `path` - -```java - -path = "Friends/#/#" -``` - -then match up the segments as: - -```java - -segments = {@PathSegment(segment = 1, column = "id"), - @PathSegment(segment = 2, column = "name")} -``` - -And to put it all together: - -```java - -@ContentUri(type = ContentType.VND_MULTIPLE, -path = "Friends/#/#", -segments = {@PathSegment(segment = 1, column = "id"), - @PathSegment(segment = 2, column = "name")}) -public static Uri withIdAndName(int id, String name) { - return buildUri(id, name); -} -``` diff --git a/usage2/Databases.md b/usage2/Databases.md deleted file mode 100644 index b61b8feb4..000000000 --- a/usage2/Databases.md +++ /dev/null @@ -1,131 +0,0 @@ -# Databases - -This section describes how databases are created in DBFlow and some more -advanced features. - -## Creating a Database - -In DBFlow, creating a database is as simple as only a few lines of code. DBFlow -supports any number of databases, however individual tables and other related files -can only be associated with one database. - -```java - -@Database(name = AppDatabase.NAME, version = AppDatabase.VERSION) -public class AppDatabase { - - public static final String NAME = "AppDatabase"; // we will add the .db extension - - public static final int VERSION = 1; -} - - -``` - -## Database Migrations - -Database migrations are run when upon open of the database connection, -the version number increases on an existing database. - -It is preferred that `Migration` files go in the same file as the database, for -organizational purposes. -An example migration: - -```java - -@Database(name = AppDatabase.NAME, version = AppDatabase.VERSION) -public class AppDatabase { - - public static final String NAME = "AppDatabase"; // we will add the .db extension - - public static final int VERSION = 2; - - @Migration(version = 2, database = MigrationDatabase.class) - public static class AddEmailToUserMigration extends AlterTableMigration { - - public AddEmailToUserMigration(Class table) { - super(table); - } - - @Override - public void onPreMigrate() { - addColumn(SQLiteType.TEXT, "email"); - } - } -} - -``` -This simple example adds a column to the `User` table named "email". In code, just add -the column to the `Model` class and this migration runs only on existing dbs. - To read more on migrations and more examples of different kinds, visit the [page](/usage2/Migrations.md). - -## Advanced Database features - -This section goes through features that are for more advanced use of a database, -and may be very useful. - -### Prepackaged Databases -To include a prepackaged database for your application, simply include the ".db" file in `src/main/assets/{databaseName}.db`. On creation of the database, we copy over the file into the application for usage. Since this is prepackaged within the APK, we cannot delete it once it's copied over, -which can bulk up your raw APK size. _Note_ this is only copied over on initial creation -of the database for the app. - -### Global Conflict Handling -In DBFlow when an INSERT or UPDATE are performed, by default, we use `NONE`. If you wish to configure this globally, you can define it to apply for all tables from a given database: - - -```java - -@Database(name = AppDatabase.NAME, version = AppDatabase.VERSION, insertConflict = ConflictAction.IGNORE, updateConflict= ConflictAction.REPLACE) -public class AppDatabase { -} - -``` - -These follow the SQLite standard [here](https://www.sqlite.org/conflict.html). - -### Integrity Checking - -Databases can get corrupted or in an invalid state at some point. If you specify -`consistencyChecksEnabled=true` It runs a `PRAGMA quick_check(1)` -whenever the database is opened. If it fails, you should provide a backup database -that it will copy over. If not, **we wipe the internal database**. Note that during this -time in case of failure we create a **third copy of the database** in case transfer fails. - -### Custom FlowSQLiteOpenHelper - -For variety of reasons, you may want to provide your own `FlowSQLiteOpenHelper` -to manage database interactions. To do so, you must implement `OpenHelper`, but -for convenience you should extend `FlowSQLiteOpenHelper` (for Android databases), -or `SQLCipherOpenHelper` for SQLCipher. Read more [here](/usage2/SQLCipherSupport.md) - -```java - - -public class CustomFlowSQliteOpenHelper extends FlowSQLiteOpenHelper { - - public CustomFlowSQliteOpenHelper(BaseDatabaseDefinition databaseDefinition, DatabaseHelperListener listener) { - super(databaseDefinition, listener); - } -} - - -``` - -Then in your `DatabaseConfig`: - -```java - -FlowManager.init(new FlowConfig.Builder(RuntimeEnvironment.application) - .addDatabaseConfig( - new DatabaseConfig.Builder(CipherDatabase.class) - .openHelper(new DatabaseConfig.OpenHelperCreator() { - @Override - public OpenHelper createHelper(DatabaseDefinition databaseDefinition, DatabaseHelperListener helperListener) { - return new CustomFlowSQliteOpenHelper(databaseDefinition, helperListener); - } - }) - .build()) - .build()); - - -``` diff --git a/usage2/GettingStarted.md b/usage2/GettingStarted.md deleted file mode 100644 index 9f40e7307..000000000 --- a/usage2/GettingStarted.md +++ /dev/null @@ -1,154 +0,0 @@ -# Getting Started - -This section describes how Models and tables are constructed via DBFlow. first -let's describe how to get a database up and running. - -## Creating a Database - -In DBFlow, creating a database is as simple as only a few lines of code. DBFlow -supports any number of databases, however individual tables and other related files -can only be associated with one database. - -```java - -@Database(name = AppDatabase.NAME, version = AppDatabase.VERSION) -public class AppDatabase { - - public static final String NAME = "AppDatabase"; // we will add the .db extension - - public static final int VERSION = 1; -} - - -``` - -Writing this file generates (by default) a `AppDatabaseAppDatabase_Database.java` -file, which contains tables, views, and more all tied to a specific database. This -class is automatically placed into the main `GeneratedDatabaseHolder`, which holds -potentially many databases. The name, `AppDatabaseAppDatabase_Database.java`, is generated -via {DatabaseClassName}{DatabaseFileName}{GeneratedClassSepator, default = "\_"}Database - -To learn more about what you can configure in a database, read [here](/usage2/Databases.md) - -## Initialize FlowManager - -DBFlow needs an instance of `Context` in order to use it for a few features such -as reading from assets, content observing, and generating `ContentProvider`. - -Initialize in your `Application` subclass. You can also initialize it from other -`Context` but we always grab the `Application` `Context` (this is done only once). - -```java -public class ExampleApplication extends Application { - - @Override - public void onCreate() { - super.onCreate(); - FlowManager.init(this); - } -} - -``` - -Finally, add the definition to the manifest (with the name that you chose for your custom application): -```xml - - -``` - -A database within DBFlow is only initialized once you call `FlowManager.getDatabase(SomeDatabase.class).getWritableDatabase()`. If you -don't want this behavior or prefer it to happen immediately, modify your `FlowConfig`: - -```java - -@Override -public void onCreate() { - super.onCreate(); - FlowManager.init(new FlowConfig.Builder(this) - .openDatabasesOnInit(true).build()); -} - -``` - -If you do not like the built-in `DefaultTransactionManager`, or just want to roll your own existing system: - -```java - -FlowManager.init(new FlowConfig.Builder(this) - .addDatabaseConfig( - new DatabaseConfig.Builder(AppDatabase.class) - .transactionManager(new CustomTransactionManager()) - .build())); - -``` -You can define different kinds for each database. -To read more on transactions and subclassing `BaseTransactionManager` go [here](/usage2/StoringData.md) - - -## Create Models - -Creating models are as simple as defining the model class, and adding the `@Table` annotation. -To read more on this, read [here](/usage2/Models.md). - -An example: - -```java - - -@Table(database = TestDatabase.class) -public class Currency { - - @PrimaryKey(autoincrement = true) - long id; // package-private recommended, not required - - @Column - @Unique - String symbol; - - @Column - String shortName; - - @Column - @Unique - private String name; // private with getters and setters - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } -} - -``` - -## Perform Some Queries - -DBFlow uses expressive builders to represent and translate to the SQLite language. - -A simple query in SQLite: - -```sqlite - -SELECT * FROM Currency WHERE symbol='$'; - -``` - -Can be represented by: - -```java - -SQLite.select() - .from(Currency.class) - .where(Currency_Table.symbol.eq("$")); - -``` - -We support many kinds of complex and complicated queries using the builder -language. To read more about this, see [the wrapper language docs](/usage2/SQLiteWrapperLanguage.md) - -There is much more you can do in DBFlow. Read through the other docs to -get a sense of the library. diff --git a/usage2/Indexing.md b/usage2/Indexing.md deleted file mode 100644 index 1e8129b55..000000000 --- a/usage2/Indexing.md +++ /dev/null @@ -1,78 +0,0 @@ -# Indexing - -In SQLite, an `Index` is a pointer to specific columns in a table that enable super-fast retrieval. - -__Note__: The database size can increase significantly, however if performance is more important, the tradeoff is worth it. - -Indexes are defined using the `indexGroups()` property of the `@Table` annotation. These operate similar to how `UniqueGroup` work: -1. specify an `@IndexGroup` -2. Add the `@Index` -3. Build and an `IndexProperty` gets generated. This allows super-easy access to the index so you can enable/disable it with ease. - -__Note__: `Index` are not explicitly enabled unless coupled with an `IndexMigration`. ([read here](/usage2/Migrations.md#index-migrations)). - -You can define as many `@IndexGroup` you want within a `@Table` as long as one field references the group. Also individual `@Column` can belong to any number of groups: - -```java - -@Table(database = TestDatabase.class, - indexGroups = { - @IndexGroup(number = 1, name = "firstIndex"), - @IndexGroup(number = 2, name = "secondIndex"), - @IndexGroup(number = 3, name = "thirdIndex") - }) -public class IndexModel2 { - - @Index(indexGroups = {1, 2, 3}) - @PrimaryKey - int id; - - @Index(indexGroups = 1) - @Column - String first_name; - - @Index(indexGroups = 2) - @Column - String last_name; - - @Index(indexGroups = {1, 3}) - @Column - Date created_date; - - @Index(indexGroups = {2, 3}) - @Column - boolean isPro; -} -``` - -By defining the index this way, we generate an `IndexProperty`, which makes it very -easy to enable, disable, and use it within queries: - -```java - -IndexModel2_Table.firstIndex.createIfNotExists(); - -SQLite.select() - .from(IndexModel2.class) - .indexedBy(IndexModel2_Table.firstIndex) - .where(...); // do a query here. - -IndexModel2_Table.firstIndex.drop(); // turn it off when no longer needed. -``` - -## SQLite Index Wrapper - -For flexibility, we also support the SQLite `Index` wrapper object, in which the `IndexProperty` -uses underneath. - -```java - -Index index = SQLite.index("MyIndex") - .on(SomeTable.class, SomeTable_Table.name, SomeTable_Table.othercolumn); -index.enable(); - -// do some operations - -index.disable(); // disable when no longer needed - -``` diff --git a/usage2/Intro.md b/usage2/Intro.md deleted file mode 100644 index cb3ae6b7b..000000000 --- a/usage2/Intro.md +++ /dev/null @@ -1,126 +0,0 @@ -# DBFlow - -DBFlow for Android lets you write very efficient database code while remaining -expressive and concise. - -```java - -@Table(database = AppDatabase.class) -public class Automobile extends BaseModel { // convenience, but not required to interact with db - - @PrimaryKey - String vin; - - @Column - String make; - - @Column - String model; - - @Column - int year; - -} - -Automobile venza = new Automobile(); -venza.vin = "499499449"; -venza.make = "Toyota"; -venza.model = "Venza"; -venza.year = 2013; -venza.save(); // inserts if not exists by primary key, updates if exists. - -// querying -// SELECT * FROM `Automobile` WHERE `year`=2001 AND `model`='Camry' -// we autogen a "_Table" class that contains convenience Properties which provide easy SQL ops. -SQLite().select() - .from(Automobile.class) - .where(Automobile_Table.year.is(2001)) - .and(Automobile_Table.model.is("Camry")) - .async() - .queryResultCallback(new QueryTransaction.QueryResultCallback() { - @Override - public void onQueryResult(QueryTransaction transaction, @NonNull CursorResult tResult) { - // called when query returns on UI thread - List autos = tResult.toListClose(); - // do something with results - } - }, new Transaction.Error() { - @Override - public void onError(Transaction transaction, Throwable error) { - // handle any errors - } - }).execute(); - -// run a transaction synchronous easily. -DatabaseDefinition database = FlowManager.getDatabase(AppDatabase.class); -database.executeTransaction(new ITransaction() { - @Override - public void execute(DatabaseWrapper databaseWrapper) { - // do something here - } -}); - -// run asynchronous transactions easily, with expressive builders -database.beginTransactionAsync(new ITransaction() { - @Override - public void execute(DatabaseWrapper databaseWrapper) { - // do something in BG - } - }).success(successCallback).error(errorCallback).build().execute(); - -``` - -## Proguard - -Since DBFlow uses annotation processing, which is run pre-proguard phase, -the configuration is highly minimal: - -``` --keep class * extends com.raizlabs.android.dbflow.config.DatabaseHolder { *; } -``` - -## Sections - -For migrating from 3.x to 4.0, read [here](/usage2/Migration4Guide.md) - -The list of documentation is listed here: - - [Getting Started](/usage2/GettingStarted.md) - - [Databases](/usage2/Databases.md) - - [Models](/usage2/Models.md) - - [Relationships](/usage2/Relationships.md) - - [Storing Data](/usage2/StoringData.md) - - [Retrieval](/usage2/Retrieval.md) - - [The SQLite Wrapper Language](/usage2/SQLiteWrapperLanguage.md) - - [Caching](/usage2/Caching.md) - - [List-Based Queries](/usage2/ListBasedQueries.md) - - [Migrations](/usage2/Migrations.md) - - [Observability](/usage2/Observability.md) - - [Type Converters](/usage2/TypeConverters.md) - -For advanced DBFlow usages: - - [Kotlin Support](/usage2/KotlinSupport.md) - - [RX Java Support](/usage2/RXSupport.md) - - [Multiple Modules](/usage2/MultipleModules.md) - - [Views](/usage2/ModelViews.md) - - [Query Models](/usage2/QueryModels.md) - - [Indexing](/usage2/Indexing.md) - - [SQLCipher](/usage2/SQLCipherSupport.md) diff --git a/usage2/KotlinSupport.md b/usage2/KotlinSupport.md deleted file mode 100644 index 51eb891eb..000000000 --- a/usage2/KotlinSupport.md +++ /dev/null @@ -1,231 +0,0 @@ -# Kotlin Support + Extensions - -DBFlow supports Kotlin out of the box and is fairly easily to use and implement. - -To add useful Kotlin-specific extensions: -``` -dependencies { - compile "com.github.Raizlabs.DBFlow:dbflow-kotlinextensions:${dbflow_version}@aar" -} - -``` -We also support [kotlin extensions for RX 1 + 2](/usage2/RXSupport.md)! - -## Classes - -DBFlow Classes are beautifully concise to write: - -```kotlin -@Table(database = KotlinDatabase::class) -class Person(@PrimaryKey var id: Int = 0, @Column var name: String? = null) -``` - -Also `data` classes are supported. - -```kotlin -@Table(database = KotlinTestDatabase::class) -data class Car(@PrimaryKey var id: Int = 0, @Column var name: String? = null) -``` - -In 4.0.0+, DBFlow contains a few extensions for Kotlin models which enable you -to keep your models acting like `BaseModel`, but do not have to explicitly extend -the class! - -```kotlin - -car.save() // extension method, optional databaseWrapper parameter. -car.insert() -car.update() -car.delete() -car.exists() - -``` - -## Query LINQ Syntax - -Kotlin has nice support for custim `infix` operators. Using this we can convert a regular, Plain old java query into a C#-like LINQ syntax. - -java: -``` - -List = SQLite.select() - .from(Result.class) - .where(Result_Table.column.eq(6)) - .and(Result_Table.column2.in("5", "6", "9")).queryList() - -``` - -kotlin: - -``` -val results = (select - from Result::class - where (column eq 6) - and (column2 `in`("5", "6", "9")) - groupBy column).list - // can call .result for single result - // .hasData if it has results - // .statement for a compiled statement -``` - -Enabling us to write code that is closer in syntax to SQLite! - -This supported for almost any SQLite operator that this library provides including: - 1. `Select` - 2. `Insert` - 3. `Update` - 4. `Delete` - -**Async Operations**: -With extensions we also support `async` operations on queries: - -```kotlin - -// easy async list query -(select - from Result::class - where (column eq 6)) -.async list { transaction, list -> - // do something here - updateUI(list) -} - -// easy single result query -(select - from Result::class - where (column eq 6)) -.async result { transaction, model -> - // do something here - updateUI(model) -} - -val model = Result() - -model.async save { - // completed, now do something with model -} - -``` - -### Property Extensions - -With Kotlin, we can define extension methods on pretty much any class. - -With this, we added methods to easily create `IProperty` from anything to make -queries a little more streamlined. In this query, we also make use of the extension -method for `from` to streamline the query even more. - -```kotlin - -var query = (select - from TestModel::class - where (5.property lessThan column) - and (clause(date.property between start_date) - and(end_date))) - - -``` - -### Query Extensions - -We can easily create nested `Operator` into `OperatorGroup` also fairly easily, also -other, random extensions: -```kotlin - -select from SomeTable::class where (name.eq("name") and id.eq(0)) - -"name".op() collate NOCASE - -"name".nameAlias - -"name".nameAlias `as` "My Name" - -// query sugar - -select from SomeTable::class where (name eq "name") or (id eq 0) - -``` - - -### Database Extensions - -#### Process Models Asynchronously - -In Java, we need to write something of the fashion: - -```java - -List items = SQLite.select() - .from(TestModel.class) - .queryList(); - - database.beginTransactionAsync(new ProcessModelTransaction.Builder<>( - new ProcessModel() { - @Override - public void processModel(TestModel model, DatabaseWrapper database) { - - } - }) - .success(successCallback) - .error(errorCallback).build() - .execute(); - -``` - -In Kotlin, we can use a combo of DSL and extension methods to: - -```kotlin - -var items = (select from TestModel1::class).list - - // easily delete all these items. - items.processInTransactionAsync { it, databaseWrapper -> it.delete(databaseWrapper) } - - // easily delete all these items with success - items.processInTransactionAsync({ it, databaseWrapper -> it.delete(databaseWrapper) }, - Transaction.Success { - // do something here - }) -// delete with all callbacks -items.processInTransactionAsync({ it, databaseWrapper -> it.delete(databaseWrapper) }, - Transaction.Success { - // do something here - }, - Transaction.Error { transaction, throwable -> - - }) - -``` - -The extension method on `Collection` allows you to perform this on all -collections from your Table! - -If you wish to easily do them _synchronously_ then use: - -```kotlin - -items.processInTransaction { it, databaseWrapper -> it.delete(databaseWrapper) } - -``` - -#### Class Extensions - -If you need access to the Database, ModelAdapter, etc for a specific class you -can now use the following (and more) reified global functions for easy access! - -```kotlin - -database() - -databaseForTable() - -writableDatabaseForTable() - -tableName() - -modelAdapter() - - -``` - -Which under-the-hood call their corresponding `FlowManager` methods. diff --git a/usage2/ListBasedQueries.md b/usage2/ListBasedQueries.md deleted file mode 100644 index 1e2949e01..000000000 --- a/usage2/ListBasedQueries.md +++ /dev/null @@ -1,157 +0,0 @@ -# List-Based Queries - -When we have large datasets from the database in our application, we wish to -display them in a `ListView`, `RecyclerView` or some other component that recycles -it's views. Instead of running a potentially very large query on the database, -converting it to a `List` and then keeping that chunk of memory active, we -can lazy-load each row from the query/table. - -DBFlow makes it easy using the `FlowCursorList`, for simple `BaseAdapter`-like methods, -or the `FlowQueryList`, which implements the `List` interface. - -Getting one of these lists is as simple as: - -```java - -FlowQueryList list = SQLite.select() - .from(MyTable.class) - .where(...) // some conditions - .flowQueryList(); -FlowCursorList list = SQLite.select() - .from(MyTable.class) - .where(...) // some conditions - .cursorList(); - - list.close(); // ensure you close these, as they utilize active cursors :) - -``` - -Any query method allows you to retrieve a default implementation of each. You -can also manually instantiate them: - -```java - -FlowQueryList list = new FlowQueryList.Builder<>(SQLite.select().from(MyTable.class)) - .cachingEnabled(false) // caching enabled by default - .build(); - -FlowCursorList list = new FlowCursorList.Builder<>(SQLite.select().from(MyTable.class)) - .cachingEnabled(true) - .modelCache(cache) // provide custom cache for this list - .build(); - -``` - -## Caching - -Both of these classes come with the ability to cache `Model` used in it's queries -so that loading only happens once and performance can remain high once loaded. The default -caching mechanism is a `ModelLruCache`, which provides an `LruCache` to manage -loading `Model`. - -They are done in almost the same way: - -```java - -FlowCursorList list = new FlowCursorList.Builder<>(SQLite.select().from(MyTable.class)) - .modelCache(cache) // provide custom cache for this list - .build(); -FlowQueryList list = new FlowQueryList.Builder<>(SQLite.select().from(MyTable.class)) - .modelCache(cache) - .build(); - -``` - -## FlowCursorList - -The `FlowCursorList` is simply a wrapper around a standard `Cursor`, giving it the -ability to cache `Model`, load items at specific position with conversion, and refresh -it's content easily. - -The `FlowCursorList` by default caches its results, for fast usage. The cache size is determined by the `ModelCache` you're using. Read on [here](/usage2/Caching.md). - -The `FlowCursorList` provides these methods: - - 1. `getItem(position)` - loads item from `Cursor` at specified position, caching and loading from cache (if enabled) - 2. `refresh()` - re-queries the underlying `Cursor`, clears out the cache, and reconstructs it. Use a `OnCursorRefreshListener` to get callbacks when this occurs. - 3. `getAll()` - returns a `List` of all items from the `Cursor`, no caching used - 4. `getCount()` - returns count of `Cursor` or 0 if `Cursor` is `null` - 5. `isEmpty()` - returns if count == 0 - 6. `clearCache()` - manually clears cache - -## Flow Query List - -This class is a much more powerful version of the `FlowCursorList`. It contains a `FlowCursorList`, -which backs it's retrieval operations. - -This class acts as `List` and can be used almost wherever a `List` is used. Also, it is a `FlowContentObserver` -see [Observability](/usage2/Observability.md), meaning other classes can listen -for its specific changes and it can auto-refresh itself when content changes. - -Feature rundown: - 1. `List` implementation of a Query - 2. `FlowContentObserver`, only for the table that it corresponds to in its initial `ModelQueriable` query statement. Mostly used for self refreshes. - 3. Transact changes to the query asynchronously (note that this refreshes itself every callback unless in a transaction state) - 5. Caching (almost same implementation as `FlowCursorList`) - -### List Implementation - -The `List` implementation is mostly for convenience. Please note that most of the modification -methods (`add`, `addAll` etc.) may not affect the query that you expect it to, unless the object you pass -objects that are valid for the query and you enable self refreshes. - -The retrieval methods are where the query works as you would expect. `get()` calls -`getItem()` on the internal `FlowCursorList`, `isEmpty()`, `getCount()`, etc all correspond -to the `Cursor` underneath. - -Both `FlowQueryList` and `FlowTableList` support `Iterator` and provide a very -efficient class: `FlowCursorIterator` that iterates through each row in a `Cursor` -and provides efficient operations. - -**Note**: any retrieval operation that turns it into another object (i.e. `subList()`, -`toArray`, etc) retrieves all objects contained in the query into memory, -and then converts it using the associated method on that returned `List`. - -### FlowContentObserver Implementation - -Using the `FlowContentObserver`, we can enable self-refreshes whenever a model changes -for the table this query points to. See [Observability](/usage2/Observability.md). - -To turn on self-refreshes, call `registerForContentChanges(context)`, which requeries -the data whenever it changes. - -We recommend placing this within a transaction on the `FlowQueryList`, so we only -refresh content the minimal amount of times: - -```java - -flowQueryList.beginTransaction(); - -// perform a bunch of modifications - -flowQueryList.endTransactionAndNotify(); - -``` - -To listen for `Cursor` refreshes register a `OnCursorRefreshListener`: - -```java - -modelList - .addOnCursorRefreshListener(new FlowCursorList.OnCursorRefreshListener() { - @Override - public void onCursorRefreshed(FlowCursorList cursorList) { - - } - }); - -``` - - -### Transact Changes Asynchronously - -If you want to pass or modify the `FlowQueryList` asynchronously, set `setTransact(true)`. -This will run all modifications in a `Transaction` and when completed, a `Cursor` refresh occurs. - -You can also register `Transaction.Error` and `Transaction.Success` callbacks for these modifications -on the `FlowQueryList` to handle when these `Transaction` finish. diff --git a/usage2/Migration3Guide.md b/usage2/Migration3Guide.md deleted file mode 100644 index dd2c606c6..000000000 --- a/usage2/Migration3Guide.md +++ /dev/null @@ -1,630 +0,0 @@ -# DBFlow 3.0 Migration Guide -DBFlow has undergone the most _significant_ changes in its lifetime in 3.0. This guide is meant to assist you in migrating from 2.1.x and above and _may not_ be fully inclusive of all changes. This doc will mention the most glaring and significant changes. If in doubt, consult the usage2 docs. - -A significant portion of the changes include the _complete_ overhaul of the underlying annotation processor, leading to wonderful improvements in maintainability of the code, readability, and stability of the generated code. Now it uses the updated [JavaPoet](https://github.com/square/javapoet) vs the outdated JavaWriter. The changes in this library alone _significantly_ helps out the stability of the generated code. - -_Some Changes to Note:_ -1. `update` no longer attempts to `insert` if it fails. -2. Package private fields from other packages are now automatically accessible via generated `_Helper` classes. The referenced fields must be annotated with `@Column`, `@PrimaryKey`, or `@ForeignKey`. if its a legacy `ForeignKeyReference`, `referendFieldIsPackagePrivate()` must be set to true. -3. `@Column` no longer required in conjunction with `@PrimaryKey` or `@ForeignKey` -4. Can now have DBFlow in multiple modules, libraries, etc via "Modules"! -5. `TransactionManager` has been replaced with a new per-database `BaseTransactionManager`. Each DB has its own `DBTransactionQueue` and you can replace the default with your own system. Also, no longer is this priority-based, but rather order-based. See more [here](/usage2/Transactions.md) - -This doc is to provide some basic examples of what has changed, but read all of the new usage docs! -Starting with [Intro](/usage2/Intro.md) - -## Table Of Contents - 1. [Initialization](/usage2/Migration3Guide.md#initialization) - 2. [Database + Table Structure](/usage2/Migration3Guide.md#database-and-table-structure) - 3. [Transactions Overhaul](/usage2/Migration3Guide.md#transactions-overhaul) - 4. [Properties](/usage2/Migration3Guide.md#properties) - 5. [ModelContainers](/usage2/Migration3Guide.md#modelcontainers) - 6. [ModelViews](/usage2/Migration3Guide.md#modelviews) - 7. [Caching](/usage2/Migration3Guide.md#caching) - 8. [Database Modules](/usage2/Migration3Guide.md#database-modules) - -## Initialization - -Previously DBFlow was intialized via: - -```java -public class ExampleApplication extends Application { - - @Override - public void onCreate() { - super.onCreate(); - FlowManager.init(this); - } -} - -``` - -Now we use the `FlowConfig.Builder`: - -```java -public class ExampleApplication extends Application { - - @Override - public void onCreate() { - super.onCreate(); - FlowManager.init(new FlowConfig.Builder(this).build()); - } -} - -``` - -See more of what you can customize [here](/usage2/GettingStarted.md) - -## Database And Table Structure -### Database changes -The default `generatedClassSeparator` is now `_` instead of `$` to play nice with Kotlin by default. A simple addition of: - -```java - -@Database(generatedClassSeparator = "$") -``` - -will keep your generated "Table" and other classes the same name. - -Globally, we no longer reference what `@Database` any database-specific element (Table, Migration, etc) by `String` name, but by `Class` now. - -Before: - -```java - -@Table(databaseName = AppDatabase.NAME) -@Migration(databaseName = AppDatabase.NAME) -``` - -After: - -```java - -@Table(database = AppDatabase.class) -@Migration(database = AppDatabase.class) -``` - -Why: We decided that referencing it directly by class name enforces type-safety and direct enforcement of the database placeholder class. Previously, - -```java - -@Table(databaseName = "AppDatabase") -``` - -was a valid specifier, which might lead to typos or errors. - -## Table Changes -`@Table` have some significant changes. - -Private boolean fields by default have changed. -`useIsForPrivateBooleans()` has changed to `useBooleanGetterSetters()`. By default -this is enabled, meaning `boolean` variables follow the convention: - -```java - -private boolean isEnabled; - -public boolean isEnabled() { - return isEnabled; -} - -public void setEnabled(boolean isEnabled) { - this.isEnabled = isEnabled; -} - -``` - -Instead of generating just `String` column name within a corresponding `$Table` class, it now generates `Property` fields. These fields are significantly smarter and more powerful. They considerably aid in the simplification of many complex queries and make the code in general more readable, type-safe, and just overall better. _NOTE: the properties are no longer capitalized, rather they match exact casing of the Column name._ - -Previously, when you defined a class as: - -```java - -@Table(databaseName = TestDatabase.NAME) -@ModelContainer -public class TestModel2 extends BaseModel { - - @Column - @PrimaryKey - String name; - - @Column(name = "model_order") - int order; -} -``` - -It generated a `TestModel2$Table` class: - -```java - -public final class TestModel2_Table { - - public static final String NAME = "name"; - - public static final String MODEL_ORDER = "model_order"; -} -``` - -Now when you define a class, it generates a definition as follows: - -```java -public final class TestModel2_Table { - public static final Property name = new Property(TestModel2.class, "name"); - - public static final IntProperty model_order = new IntProperty(TestModel2.class, "model_order"); - - public static final IProperty[] getAllColumnProperties() { - return new IProperty[]{name,model_order}; - } - - public static BaseProperty getProperty(String columnName) { - columnName = QueryBuilder.quoteIfNeeded(columnName); - switch (columnName) { - case "`name`": { - return name; - } - case "`model_order`": { - return model_order; - } - default: { - throw new IllegalArgumentException("Invalid column name passed. Ensure you are calling the correct table's column"); - } - } - } -} -``` - -Each `Property` now is used for each all references to a column in query statements. - -The `getProperty()` method allows to keep compatibility with the old format, solve some `ContentProvider` compatibility issues, or allow looking up `Property` by key. - -To read on how these properties interact read "Properties, Conditions, Queries, Replacement of ConditionQueryBuilder and more". - -### Index changes -Added was an `IndexGroup[]` of `indexGroups()`. - -Now we can generate `IndexProperty` (see properties for more information), which provide us a convenient generated `Index` to use for the table. This then is used in a queries that rely on indexes and make it dead simple to activate and deactivate indexes. - -A class written like: - -```java -@Table(database = TestDatabase.class, - indexGroups = { - @IndexGroup(number = 1, name = "firstIndex"), - @IndexGroup(number = 2, name = "secondIndex"), - @IndexGroup(number = 3, name = "thirdIndex") - }) -public class IndexModel2 extends BaseModel { - - @Index(indexGroups = {1, 2, 3}) - @PrimaryKey - int id; - - @Index(indexGroups = 1) - @Column - String first_name; - - @Index(indexGroups = 2) - @Column - String last_name; - - @Index(indexGroups = {1, 3}) - @Column - Date created_date; - - @Index(indexGroups = {2, 3}) - @Column - boolean isPro; -} -``` - -Generates in its "Table" class: - -```java -public final class IndexModel2_Table { - //...previous code omitted - - public static final IndexProperty index_firstIndex = new IndexProperty<>("firstIndex", false, IndexModel2.class, id, first_name, created_date); - - public static final IndexProperty index_secondIndex = new IndexProperty<>("secondIndex", false, IndexModel2.class, id, last_name, isPro); - - public static final IndexProperty index_thirdIndex = new IndexProperty<>("thirdIndex", false, IndexModel2.class, id, created_date, isPro); -``` - -### Foreign Key Changes -`@ForeignKey` fields no longer need to specify it's references or the `@Column` annotation!!! The old way still works, but is no longer necessary for `Model`-based ForeignKeys. The annotation processor takes the primary keys of the referenced table and generates a column with {fieldName}_{referencedColumnName} that represents the same SQLite Type of the field.
_Note: that is not backwards compatible_ with apps already with references. - -Going forward with new tables, you can leave them out. - -Previously: - -```java -@Table(database = TestDatabase.class) -public class ForeignInteractionModel extends TestModel1 { - - @Column - @ForeignKey( - onDelete = ForeignKeyAction.CASCADE, - onUpdate = ForeignKeyAction.CASCADE, - references = - {@ForeignKeyReference(columnName = "testmodel_id", - foreignColumnName = "name", - columnType = String.class), - @ForeignKeyReference(columnName = "testmodel_type", - foreignColumnName = "type", - columnType = String.class)}, - saveForeignKeyModel = false) - ForeignKeyContainer testModel1; -} -``` - -Now: - -```java -@Table(database = TestDatabase.class) -public class ForeignInteractionModel extends TestModel1 { - - @ForeignKey( - onDelete = ForeignKeyAction.CASCADE, - onUpdate = ForeignKeyAction.CASCADE, - saveForeignKeyModel = false) - ForeignKeyContainer testModel1; -} -``` - -The result is _significantly_ cleaner and less overhead to maintain. - -If you wish to keep old references, please keep in mind that `foreignColumnName` is now `foreignKeyColumnName`. - -## Transactions Overhaul - -In 3.0, Transactions got a serious facelift and should be easier to use and handle. -Also their logic and use are much more consolidated a focused. There is no longer -just one `TransactionManager`, rather each database has its own instance so that -operations between databases don't interfere. - -### Inserting Data -The format of how to declare them has changed: -Previously to run a transaction, you had to set it up as so: - -```java -ProcessModelInfo processModelInfo = ProcessModelInfo.withModels(models) - .result(resultReceiver) - .info(myInfo); -TransactionManager.getInstance().addTransaction(new SaveModelTransaction<>(processModelInfo)) -TransactionManager.getInstance().addTransaction(new UpdateModelListTransaction<>(processModelInfo)) -TransactionManager.getInstance().addTransaction(new DeleteModelListTransaction<>(processModelInfo)) - - -``` - -In 3.0, we have dropped the individual transaction types, use a new builder notation, -and with _every_ `Transaction` you get completion and error handling: - -```java - -FlowManager.getDatabase(AppDatabase.class) - .beginTransactionAsync(new ProcessModelTransaction.Builder<>( - new ProcessModelTransaction.ProcessModel() { - @Override - public void processModel(Model model) { - - } - }).build()) - .error(new Transaction.Error() { - @Override - public void onError(Transaction transaction, Throwable error) { - - } - }) - .success(new Transaction.Success() { - @Override - public void onSuccess(Transaction transaction) { - - } - }).build().execute(); - -``` - -One thing to note about the `Transaction.Error` is that if specified, _all_ exceptions -are caught and passed to the callback, otherwise any exception that happens in the Transaction system -gets thrown. - -You still can use the `DBBatchSaveQueue` for batch saves: - -Previously: - -```java - -TransactionManager.getInstance().saveOnSaveQueue(models); - -``` - -In 3.0: - -```java - -FlowManager.getDatabase(AppDatabase.class).getTransactionManager() - .getSaveQueue().addAll(models); - - -``` - -### Querying Data - -Previously when you queried data you have a few different classes that did almost same thing -such as `SelectListTransaction`, `BaseResultTransaction`, `QueryTransaction`, etc. -3.0 consolidates these into much simpler operations via: - -Previously: - -```java - -TransactionManager.getInstance().addTransaction(new SelectListTransaction<>(new TransactionListenerAdapter() { - @Override - public void onResultReceived(List testModels) { - - } - }, TestModel.class, condition1, condition2,..); - - -``` -In 3.0: - -```java - -database.beginTransactionAsync( - new QueryTransaction.Builder<>( - SQLite.select().from(TestModel1.class)) - .queryResult(new QueryTransaction.QueryResultCallback() { - @Override - public void onQueryResult(QueryTransaction transaction, @NonNull CursorResult result) { - - } - }).build()).build(); - -``` - -The `QueryResultCallback` gives back a `CursorResult`, which is a wrapper around abstract -`Cursor` that lets you retrieve easily `Models` from that cursor: - -```java -List models = result.toListClose(); -TestModel1 singleModel = result.toModelClose(); -``` - -Just ensure that you close the `Cursor`. - -### Callback Changes - -With 3.0, we modified the callback for a `Transaction`. Instead of having the -3 methods: - -```java -public interface TransactionListener { - - void onResultReceived(ResultClass result); - - boolean onReady(BaseTransaction transaction); - - boolean hasResult(BaseTransaction transaction, ResultClass result); - -} -``` - -Each `Transaction` automatically gives you ability to handle callbacks: - -```java - - -FlowManager.getDatabase(AppDatabase.class) - .beginTransactionAsync(new ITransaction() { - @Override - public void execute(DatabaseWrapper databaseWrapper) { - // do anything you want here. - } - }).build()) - .error(new Transaction.Error() { - @Override - public void onError(Transaction transaction, Throwable error) { - - } - }) - .success(new Transaction.Success() { - @Override - public void onSuccess(Transaction transaction) { - - } - }).build().execute(); - -``` - -For more usage on the new system, including the ability to roll your own `TransactionManager`, -visit [Transactions](/usage2/Transactions.md) - - -## Properties -Perhaps the most significant external change to this library is making queries, conditions, and interactions with the database much stricter and more type-safe. - -### Property -Properties replace `String` column names generated in the "$Table" classes. They also match exact case to the column name. They have methods that generate `Condition` that drastically simplify queries. (Please note the `Condition` class has moved to the `.language` package). - -Properties are represented by the interface `IProperty` which are subclassed into `Property`, `Method`, and the primitive properties (`IntProperty`, `CharProperty`, etc). - -Properties can also be represented by values via the `PropertyFactory` class, enabling values to appear first in queries: - -```java -PropertyFactory.from(5l) // generates LongProperty -PropertyFactory.from(5d) // generates DoubleProperty -PropertyFactory.from("Hello") // generates Property -PropertyFactory.from(Date.class, someDate) // generates Property -``` - -It will become apparent why this change was necessary with some examples: - -A non-simple query by SQLite standards: - -```sql -SELECT `name` AS `employee_name`, AVG(`salary`) AS `average_salary`, `order`, SUM(`salary`) as `sum_salary` - FROM `SomeTable` - WHERE `salary` > 150000 -``` - -Before: - -```java -List items = - new Select(ColumnAlias.column(SomeTable$Table.NAME).as("employee_name"), - ColumnAlias.columnsWithFunction("AVG", SomeTable$Table.SALARY).as("average_salary"), - SomeTable$Table.ORDER, - ColumnAlias.columnsWithFunction("SUM", SomeTable$Table.SALARY).as("sum_salary")) - .from(SomeTable.class) - .where(Condition.column(SomeTable$Table.SALARY).greaterThan(150000)) - .queryCustomList(SomeQueryTable.class); -``` - -Now (with static import on `SomeTable_Table` and `Method` ): - -```java -List items = - SQLite.select(name.as("employee_name"), - avg(salary).as("average_salary"), - order, - sum(salary).as("sum_salary")) - .from(SomeTable.class) - .where(salary.greaterThan(150000)) - .queryCustomList(SomeQueryTable.class); -``` - -The code instantly becomes cleaner, and reads more like an actual query. - -### Replacement of the ConditionQueryBuilder -ConditionQueryBuilder was fundamentally flawed. It represented a group of `Condition`, required a `Table`, yet extended `QueryBuilder`, meaning arbitrary `String` information could be appended to it, leading to potential for messy piece of query. - -It has been replaced with the `ConditionGroup` class. This class represents an arbitrary group of `SQLCondition` in which it's sole purpose is to group together `SQLCondition`. Even better a `ConditionGroup` itself is a `SQLCondition`, meaning it can _nest_ inside of other `ConditionGroup` to allow complicated and insane queries. - -Now you can take this: - -```sql -SELECT FROM `SomeTable` WHERE 0 < `latitude` AND (`longitude` > 50 OR `longitude` < 25) AND `name`='MyHome' -``` - -and turn it into: - -```java -SQLite.select() - .from(SomeTable.class) - .where(PropertyFactory.from(0).lessThan(SomeTable_Table.latitude)) - .and(ConditionGroup.clause() - .and(SomeTable_Table.longitude.greaterThan(50)) - .or(SomeTable_Table.longitude.lessThan(25))) - .and(SomeTable_Table.name.eq("MyHome")) -``` - -## ModelContainers -Now `ModelContainer` objects have a multitude of type-safe methods to ensure that they can convert their contained object's data into the field they associate with. What this means is that if our `Model` has a `long` field, while the data object for the `ModelContainer` has a `Integer` object. Previously, we would get a classcastexception. Now what it does is "coerce" the value into the type you need. Supported Types: -1. Integer/int -2. Double/Double -3. Boolean/boolean -4. Short/short -5. Long/long -6. Float/Float -7. String -8. Blob/byte[]/Byte[] -9. Byte/byte -10. Using TypeConverter to retrieve value safely. - -You can now `queryModelContainer` from the database to retrieve a single `Model` into `ModelContainer` format instead of into `Model` and then `ModelContainer`: - -```java - -JSONModel model = SQLite.select().from(SomeTable.class).where(SomeTable_Table.id.eq(5)).queryModelContainer(new JSONModel()); -JSONObject json = model.getData(); -// has data now -``` - -For the `toModel()` conversion/parse method from `ModelContainer` to `Model`, you can now: -1. Have `@Column` excluded from it via `excludeFromToModelMethod()` -2. include other fields in the method as well by adding the `@ContainerKey` annotation to them. - -## ModelViews -No longer do we need to specify the query for the `ModelView` in the annotation without ability to use the wrappper classes. We define a `@ModelViewQuery` field to use and then it simply becomes: - -```java -@ModelViewQuery - public static final Query QUERY = new Select(AModel_Table.time) - .from(AModel.class).where(AModel_Table.time.greaterThan(0l)); -``` - -What this means is that its easier than before to use Views. - -## Caching -I significantly revamped model caching in this release to make it easier, support more tables, and more consistent. Some of the significant changes: - -Previously you needed to extend `BaseCacheableModel` to enable model caching. No longer! The code that was there now generates in the corresponding `ModelAdapter` by setting `cachingEnabled = true` in the `@Table` annotation. - -Before - -```java -@Table(databaseName = TestDatabase.NAME) -public class CacheableModel extends BaseCacheableModel { - - @Column - @PrimaryKey(autoincrement = true) - long id; - - @Column - String name; - - @Override - public int getCacheSize() { - return 1000; - } -} -``` - -After: - -```java -@Table(database = TestDatabase.class, cachingEnabled = true, cacheSize = 1000) -public class CacheableModel extends BaseModel { - - @PrimaryKey(autoincrement = true) - long id; - - @Column - String name; -} -``` - -Also, you can now have caching objects with _multiple_ primary keys!!! - -Simply in your model class define a `@MultiCacheField` and now you can cache objects with multiple primary keys: - -```java -@Table(database = TestDatabase.class, cachingEnabled = true) -public class MultipleCacheableModel extends BaseModel { - - @MultiCacheField - public static IMultiKeyCacheConverter multiKeyCacheModel = new IMultiKeyCacheConverter() { - - @Override - @NonNull - public String getCachingKey(@NonNull Object[] values) { - return "(" + values[0] + "," + values[1] + ")"; - } - }; - - @PrimaryKey - double latitude; - - @PrimaryKey - double longitude; - -} -``` - -Please note that the field must be of type `IMultiKeyCacheConverter` in order to compile and convert correctly. You must provide one, otherwise caching will not work. Also the return caching key _must_ be unique, otherwise inconsistent results may occur from within the cache. - -## Database Modules -Now in DBFlow we have support for libraries, other subprojects, and more in general to all use DBFlow at the same time. The only requirement is that they specify an argument to `apt` in order to prevent clashes and the library loads the class during the initialization phase. To read on how to do this (fairly simply), please check it out here: ([Database Modules](https://github.com/Raizlabs/DBFlow/blob/master/usage/DatabaseModules.md)) diff --git a/usage2/Migration4Guide.md b/usage2/Migration4Guide.md deleted file mode 100644 index d90d65c75..000000000 --- a/usage2/Migration4Guide.md +++ /dev/null @@ -1,40 +0,0 @@ -# DBFlow 4.0 Migration guide - -In 4.0, DBFlow has greatly improved its internals and flexibility in this release. We have removed the `Model` restriction, rewritten the annotation processor completely in Kotlin, and more awesome improvements. - -_Major Changes In this release_ - -1. `PrimaryKey` can have `TypeConverters`, be table-based objects, and all kinds of objects. No real restrictions. - -2. `ForeignKey` have been revamped to allow `stubbedRelationship`. This replaces `ForeignKeyContainer`. - -3. `Model` interface now includes `load()` to enabled reloading very easily when fields change. - -4. All `ModelContainer` implementation + support has been removed. A few reasons pushed the removal, including implementation. Since removing support, the annotation processor is cleaner, easier to maintain, and more streamlined. Also the support for it was not up to par, and by removing it, we can focus on improving the quality of the other features. - -5. The annotation processor has been rewritten in Kotlin! By doing so, we reduced the code by ~13%. - -6. Removed the `Model` restriction on tables. If you leave out extending `BaseModel`, you _must_ interact with the `ModelAdapter`. - -7. We generate much less less code than 3.0. Combined the `_Table` + `_Adapter` into the singular `_Table` class, which contains both `Property` + all of the regular `ModelAdapter` methods. To ease the transition to 4.0, it is named `_Table` but extends `ModelAdapter`. So most use cases / interactions will not break. - -8. `Condition` are now `Operator`, this includes `SQLCondition` -> `SQLOperator`, `ConditionGroup` -> `OperatorGroup`. `Operator` are now typed and safer to use. - 1. `Operator` now also have `div`, `times`, `rem`, `plus` and `minus` methods. - -9. Property class changes: - 1. All primitive `Property` classes have been removed. We already boxed the values internally anyways so removing them cut down on method count and maintenance. - 2. `BaseProperty` no longer needs to exist, so all of it's methods now exist in `Property` - 3. `mod` method is now `rem` (remainder) method to match Kotlin 1.1's changes. - 4. `dividedBy` is now `div` to match Kotlin operators. - 5. `multipliedBy` is now `times` to match Kotlin operators. - -10. Rewrote all Unit tests to be more concise, better tested, and cleaner. - -11. A lot of bug fixes - -12. Kotlin: - 1. Added more Kotlin extensions. - 2. Most importantly you don't need to use `BaseModel`/`Model` at all anymore if you so choose. There are `Model`-like extension methods that supply the `Model` methods. - 3. Updated to version 1.1.1 - -13. RXJava1 and RXJava2 support! Can now write queries that return `Observable` and more. diff --git a/usage2/Migrations.md b/usage2/Migrations.md deleted file mode 100644 index ad73848fd..000000000 --- a/usage2/Migrations.md +++ /dev/null @@ -1,175 +0,0 @@ -# Migrations - -In this section we will discuss how migrations work, how each of the provided -migration classes work, and how to create your own custom one. - -There are two kinds of migrations that DBFlow supports: Script-based SQL files -and class annotation-based migrations. - -## How Migrations Work - -In SQL databases, migrations are used to modify or change existing database schema to adapt -to changing format or nature of stored data. In SQLite we have a limited ability -compared to SQL to modify tables and columns of an existing database. There are only -two kinds of modifications that exist: rename table and add a new column. - -In DBFlow migrations are not only used to modify the _structure_ of the database, but also other operations such as insert data into a database (for prepopulate), or add an index on a specific table. - -Migrations are only run on an existing database _except_ for the "0th" migration. Read [initial database setup](/usage2/Migrations.md#initial-database-setup) - -### Migration Classes - -We recommend placing any `Migration` inside an associated `@Database` class so it's apparent the migration is tied to it. -An example migration class: - -```java -@Database(version = 2, name = AppDatabase.NAME) -public class AppDatabase { - - public static final String NAME = "AppDatabase"; - - @Migration(version = 2, database = AppDatabase.class) - public static class Migration2 extends BaseMigration { - - @Override - public void migrate(DatabaseWrapper database) { - // run some code here - SQLite.update(Employee.class) - .set(Employee_Table.status.eq("Invalid")) - .where(Employee_Table.job.eq("Laid Off")) - .execute(database); // required inside a migration to pass the wrapper - } - } -} -``` - -The classes provide the ability to set a `priority` on the `Migration` so that an order is established. The higher the priority, that one will execute first. - -`Migration` have three methods: - 1. `onPreMigrate()` - called first, do setup, and construction here. - 2. `migrate()` -> called with the `DatabaseWrapper` specified, this is where the actual migration code should execute. - 3. `onPostMigrate()` -> perform some cleanup, or any notifications that it was executed. - -### Migration files - -DBFlow also supports `.sql` migration files. The rules on these follows must be followed: - 1. Place them in `assets/migrations/{DATABASE_NAME}/{versionNumber}.sql`. So that an example `AppDatabase` migration for version 2 resides in `assets/migrations/AppDatabase/2.sql` - 2. The file can contain any number of SQL statements - they are executed in order. Each statement must be on a single line or multiline and must end with `;` - 3. Comments are allowed as long as they appear on an individual file with standard SQLite comment syntax `--` - -### Prevent Recursive Access to the DB - -Since `Migration` occur when the database is opening, we cannot recursively access the database object in our models, SQLite wrapper statements, and other classes in DBFlow that are inside a `Migration`. - -To remedy that, DBFlow comes with support to pass the `DatabaseWrapper` into almost all places that require it: - 1. All query language `BaseQueriable` objects such as `Select`, `Insert`, `Update`, `Delete`, etc have methods that take in the `DatabaseWrapper` - 2. Any subclass of `BaseModel` (`Model` does not provide the methods for simplicity) - -### Initial Database Setup - -DBFlow supports `Migration` that run on version "0" of a database. When Android opens a `SQLiteDatabase` object, if the database is created, DBFlow calls on a `Migration` of -1 to 0th version. In this case, any `Migration` run at `version = 0` will get called. Once a database is created, this migration will not run again. So if you had an existent database at version 1, and changed version to 2, the "0th" `Migration` is not run because the old version the database would have been 1. - -## Provided Migration Classes - -In DBFlow we provide a few helper `Migration` subclasses -to provide default and easier implementation: - 1. `AlterTableMigration` - 2. `IndexMigration/IndexPropertyMigration` - 3. `UpdateTableMigration` - -### AlterTableMigration - -The _structural_ modification of a table is brought to a handy `Migration` subclass. - -It performs both of SQLite supported operations: - 1. Rename tables - 2. Add columns. - -For renaming tables, you should rename the `Model` class' `@Table(name = "{newName}")` before running -this `Migration`. The reason is that DBFlow will know -the new name only and the existing database will get caught up on it through this migration. Any new database created on a device will automatically have the new table name. - -For adding columns, we only support `SQLiteType` (all supported ones [here](https://www.sqlite.org/datatype3.html)) operations to add or remove columns. This is to enforce that the columns are created properly. If a column needs to be a `TypeConverter` column, use the database value from it. We map the associated type of the database field to a `SQLiteType` in [SQLiteType.java](/dbflow/src/main/java/com/raizlabs/android/dbflow/sql/SQLiteType.java). So if you have a `DateConverter` that specifies a `Date` column converted to `Long`, then you should look up `Long` in the `Map`. In this case `Long` converts to `INTEGER`. - -```java - - -@Migration(version = 2, database = AppDatabase.class) -public class Migration2 extends AlterTableMigration { - - public Migration2(Class table) { - super(table); - } - - @Override - public void onPreMigrate() { - addColumn(SQLiteType.TEXT, "myColumn"); - addColumn(SQLiteType.REAL, "anotherColumn"); - } -} - -``` - -### Index Migrations - -An `IndexMigration` (and `IndexPropertyMigration`) is used to structurally activate an `Index` on the database at a specific version. See [here](/usage2/Indexing.md) for information on creating them. - -`IndexMigration` does not require an `IndexProperty` to run, while `IndexPropertyMigration` makes use of the property to run. - -An `IndexMigration`: - -```java - -@Migration(version = 2, priority = 0, database = MigrationDatabase.class) -public static class IndexMigration2 extends IndexMigration { - - public IndexMigration2(@NonNull Class onTable) { - super(onTable); - } - - @NonNull - @Override - public String getName() { - return "TestIndex"; - } -} -``` - -An `IndexPropertyMigration`: - -```java - -@Migration(version = 2, priority = 1, database = MigrationDatabase.class) -public static class IndexPropertyMigration2 extends IndexPropertyMigration { - - @NonNull - @Override - public IndexProperty getIndexProperty() { - return IndexModel_Table.index_customIndex; - } -} - -``` - -### Update Table Migration - -A simple wrapper around `Update`, provides simply a default way to update data during a migration. - -```java - - -@Migration(version = 2, priority = 2, database = MigrationDatabase.class) -public static class UpdateMigration2 extends UpdateTableMigration { - - /** - * Creates an update migration. - * - * @param table The table to update - */ - public UpdateMigration2(Class table) { - super(table); - set(MigrationModel_Table.name.eq("New Name")); - } - -} - ``` diff --git a/usage2/ModelViews.md b/usage2/ModelViews.md deleted file mode 100644 index f9eb4dcbb..000000000 --- a/usage2/ModelViews.md +++ /dev/null @@ -1,47 +0,0 @@ -# ModelViews - -A `ModelView` is a SQLite representation of a `VIEW`. Read official SQLite docs -[here](https://www.sqlite.org/lang_createview.html) for more information. - -As with SQLite a `ModelView` cannot insert, update, or delete itself as it's -read-only. It is a virtual "view" placed on top of a regular table as a prepackaged -`Select` statement. In DBFlow using a `ModelView` should feel familiar and be very simple. - -```java - -@ModelView(database = TestDatabase.class) -public class TestModelView { - - @ModelViewQuery - public static final Query QUERY = SQLite.select().from(TestModel2.class) - .where(TestModel2_Table.model_order.greaterThan(5)); - - @Column - long model_order; -} - -``` - -To specify the query that a `ModelView` creates itself with, we _must_ define -a public static final field annotated with `@ModelViewQuery`. This tells DBFlow -what field is the query. This query is used only once when the database is created -(or updated) to create the view. - - -The full list of limitations/supported types are: - 1. Only `@Column` are allowed - 2. No `@PrimaryKey` or `@ForeignKey` - 3. Supports all fields, and accessibility modifiers that `Model` support - 4. Does not support `@InheritedField`, `@InheritedPrimaryKey` - 5. Basic, type-converted, non-model `@Column`. - 6. __Cannot__: update, insert, or delete - -`ModelView` are used identical to `Model` when retrieving from the database: - -```java - -SQLite.select() - .from(TestModelView.class) - .where(...) // ETC - -``` diff --git a/usage2/Models.md b/usage2/Models.md deleted file mode 100644 index f48853b45..000000000 --- a/usage2/Models.md +++ /dev/null @@ -1,163 +0,0 @@ -# Models - -In DBFlow we dont have any restrictions on what your table class is. We do, however recommend you subclass `BaseModel` on -your highest-order base-class, which provides a default implementation for you. - -When using regular models: -```java -FlowManager.getModelAdapter(MyTable.class).save(myTableObject); -``` - -When using `BaseModel`, it is much cleaner: - -```java -myTableObject.save(); -``` - -## Columns - -We, by default, lazily look for columns. This means that they all must contain either `@PrimaryKey`, `@Column`, or `@ForeignKey` to be used in tables. - -If you wish to make it simpler and include all fields in a class, set `@Table(allFields = true)`. -However this still requires you to specify at least one `@PrimaryKey` field. You -can then explicitly ignore fields via the `@ColumnIgnore` annotation. - -Columns can be `public`, package-private, or `private`. -`private` fields __must__ come with `public` java-bean-style getters and setters. - -Here is an example of a "nice" `private` field: - -```java -@Table(database = AppDatabase.class) -public class Dog { - - @PrimaryKey - private String name; - - public void setName(String name) { - this.name = name; - } - - public String getName() { - return name; - } - -} - -``` - -Columns have a wide-range of supported types in the `Model` classes: -**Supported Types**: - 1. all java primitives including `char`,`byte`, `short`, and `boolean`. - 2. All java boxed primitive classes - 3. String, Date, java.sql.Date, Calendar, Blob, Boolean - 4. Custom data types via a [TypeConverter](/usage2/TypeConverters.md) - 5. `Model` as fields, but only as `@PrimaryKey` and/or `@ForeignKey` - -**Unsupported Types**: - 1. `List` : List columns are not supported and not generally proper for a relational database. However, you can get away with a non-generic `List` column via a `TypeConverter`. But again, avoid this if you can. - 2. Anything that is generically typed (even with an associated `TypeConverter`). If you need to include the field, subclass the generic object and provide a `TypeConverter`. - -## Inherited Columns - -Since we don't require extension on `BaseModel` directly, tables can extend non-model classes and inherit their fields directly (given proper accessibility) via the `@InheritedColumn` annotation (or `@InheritedPrimaryKey` for primary keys): - -```java - -@Table(database = AppDatabase.class, - inheritedColumns = {@InheritedColumn(column = @Column, fieldName = "name"), - @InheritedColumn(column = @Column, fieldName = "number")}, - inheritedPrimaryKeys = {@InheritedPrimaryKey(column = @Column, - primaryKey = @PrimaryKey, - fieldName = "inherited_primary_key")}) -public class InheritorModel extends InheritedModel implements Model { - -``` - -## Primary Keys - -DBFlow supports multiple primary keys, right out of the box. Simply create a table with multiple `@PrimaryKey`: - -```java -@Table(database = AppDatabase.class) -public class Dog extends BaseModel { - - @PrimaryKey - String name; - - @PrimaryKey - String breed; - -} - -``` - -If we want an auto-incrementing key, you specify `@PrimaryKey(autoincrement = true)`, but only one of these kind can exist in a table and you cannot mix with regular primary keys. - -## Unique Columns - -DBFlow has support for SQLite `UNIQUE` constraint (here for documentation)[http://www.tutorialspoint.com/sqlite/sqlite_constraints.htm]. - -Add `@Unique` annotation to your existing `@Column` and DBFlow adds it as a constraint when -the database table is first created. This means that once it is created you should not change or modify this. - -We can _also_ support multiple unique clauses in order to ensure any combination of fields are unique. For example: - -To generate this in the creation query: -```sqlite -UNIQUE('name', 'number') ON CONFLICT FAIL, UNIQUE('name', 'address') ON CONFLICT ROLLBACK -``` -We declare the annotations as such: - -```java - -@Table(database = AppDatabase.class, - uniqueColumnGroups = {@UniqueGroup(groupNumber = 1, uniqueConflict = ConflictAction.FAIL), - @UniqueGroup(groupNumber = 2, uniqueConflict = ConflictAction.ROLLBACK)) -public class UniqueModel { - - @PrimaryKey - @Unique(unique = false, uniqueGroups = {1,2}) - String name; - - @Column - @Unique(unique = false, uniqueGroups = 1) - String number; - - @Column - @Unique(unique = false, uniqueGroups = 2) - String address; - -} - -``` - -The `groupNumber` within each defined `uniqueColumnGroups` with an associated `@Unique` column. We need to specify `unique=false` for any column used in a group so we expect the column to be part of a group. If true as well, the column will _also_ alone be unique. - -## Default Values - -DBFlow supports default values in a slighty different way that SQLite does. Since we do not know -exactly the intention of missing data when saving a `Model`, since we group all fields, `defaultValue` specifies -a value that we replace when saving to the database when the value of the field is `null`. - -This feature only works on Boxed primitive and the `DataClass` equivalent of objects (such as from TypeConverter), such as String, Integer, Long, Double, etc. -__Note__: If the `DataClass` is a `Blob`, unfortunately this will not work. -For `Boolean` classes, use "1" for true, "0" for false. - -```java - -@Column(defaultValue = "55") -Integer count; - -@Column(defaultValue = "\"this is\"") -String test; - -@Column(defaultValue = "1000L") -Date date; - -@Column(defaultValue = "1") -Boolean aBoolean; - -``` - -DBFlow inserts it's literal value into the `ModelAdapter` for the table so any `String` must be escaped. diff --git a/usage2/MultipleModules.md b/usage2/MultipleModules.md deleted file mode 100644 index 69c963f83..000000000 --- a/usage2/MultipleModules.md +++ /dev/null @@ -1,48 +0,0 @@ -# Multiple Database Modules - -In apps that want to share DBFlow across multiple modules or when developing a library -module that uses DBFlow, we have to provide a little extra configuration to properly -ensure that all database classes are accounted for. - -It's directly related to the fact that annotation processors are isolated between projects -and are not shared. - -In order to add support for multiple modules, in each and every library/subproject that uses -a DBFlow instance, you must add an APT argument (using the [android-apt plugin](https://bitbucket.org/hvisser/android-apt)) to its `build.gradle`: - -```java -apt { - arguments { - targetModuleName 'SomeUniqueModuleName' - } -} -``` - -or for if you use Kotlin, KAPT: - -```java -kapt { - generateStubs = true - arguments { - arg("targetModuleName", "SomeUniqueModuleName") - } -} -``` - - -By passing the targetModuleName, we append that to the `GeneratedDatabaseHolder` class name to create the `{targetModuleName}GeneratedDatabaseHolder` module. __Note__: Specifying this in code means -you need to specify the module when initializing DBFlow: - -From previous sample code, we recommend initializing the specific module inside your library, -to prevent developer error. __Note__: Multiple calls to `FlowManager` will not adversely -affect DBFlow. If DBFlow is already initialized, we append the module to DBFlow if and only if it does not already exist. - -```java - -public void initialize(Context context) { - FlowManager.init(new FlowConfig.Builder(context) - .addDatabaseHolder(SomeUniqueModuleNameGeneratedDatabaseHolder.class) - .build()); -} - -``` diff --git a/usage2/Observability.md b/usage2/Observability.md deleted file mode 100644 index 9655381f8..000000000 --- a/usage2/Observability.md +++ /dev/null @@ -1,154 +0,0 @@ -# Observability - -DBFlow provides a flexible way to observe changes on models and tables in this library. - -By default, DBFlow utilizes the [`ContentResolver`](https://developer.android.com/reference/android/content/ContentResolver.html) -to send changes through the android system. We then can utilize [`ContentObserver`](http://developer.android.com/reference/android/database/ContentObserver.html) to listen for these changes via the `FlowContentObserver`. - -Also, DBFlow also supports direct [model notification](/usage2/Observability.md#direct-changes) via a custom `ModelNotifier`. - -## FlowContentObserver - -The content observer converts each model passed to it into `Uri` format that describes the `Action`, primary keys, and table of the class that changed. - -A model: -```kotlin - -@Table(database = AppDatabase.class) -class User(@PrimaryKey var id: Int = 0, @Column var name: String = "") - -``` - -with data: -```kotlin - -User(55, "Andrew Grosner").delete() - -``` - -converts to: - -``` -dbflow://%60User%60?%2560id%2560=55#DELETE -``` - -Then after we register a `FlowContentObserver`: - -```java - -FlowContentObserver observer = new FlowContentObserver(); - -observer.registerForContentChanges(context, User.class); - -// do something here -// unregister when done -observer.unregisterForContentChanges(context); - -``` - -## Model Changes - -It will now receive the `Uri` for that table. Once we have that, we can register for model changes on that content: - -```java - -observer.addModelChangeListener(new OnModelStateChangedListener() { - @Override - public void onModelStateChanged(@Nullable Class table, BaseModel.Action action, @NonNull SQLOperator[] primaryKeyValues) { - // do something here - } -}); - - -``` -The method will return the `Action` which is one of: - 1. `SAVE` (will call `INSERT` or `UPDATE` as well if that operation was used) - 2. `INSERT` - 3. `UPDATE` - 4. `DELETE` - -The `SQLOperator[]` passed back specify the primary column and value pairs that were changed for the model. - -If we want to get less granular and just get notifications when generally a table changes, read on. - -## Register for Table Changes - -Table change events are similar to `OnModelStateChangedListener`, except that they only specify the table and action taken. These get called for any action on a table, including granular model changes. We recommend batching those events together, which we describe in the next section. - -```java - -addOnTableChangedListener(new OnTableChangedListener() { - @Override - public void onTableChanged(@Nullable Class tableChanged, BaseModel.Action action) { - // perform an action. May get called many times! Use batch transactions to combine them. - } -}); - -``` - -## Batch Up Many Events - -Sometimes we're modifying tens or hundreds of items at the same time and we do not wish to get notified for _every_ one but only once for each _kind_ of change that occurs. - -To batch up the notifications so that they fire all at once, we use batch transactions: - -```java - -FlowContentObserver observer = new FlowContentObserver(); - -observer.beginTransaction(); - -// save, modify models here -for(User user: users) { - users.save(); -} - -observer.endTransactionAndNotify(); // callback batched - -``` - -Batch interactions will store up all unique `Uri` for each action (these include `@Primary` key of the `Model` changed). When `endTransactionAndNotify()` is called, -all those `Uri` are called in the `onChange()` method from the `FlowContentObserver` as expected. - -If we are using `OnTableChangedListener` callbacks, then by default we will receive one callback per `Action` per table. If we wish to only receive a single callback, set `setNotifyAllUris(false)`, which will make the `Uri` all only specify `CHANGE`. - -# Direct Changes - -DBFlow also supports direct observability on model changes rather than convert those models into `Uri` and have to decipher what has changed. - -To set up direct changes we override the default `ModelNotifier`: - -```java - -FlowManager.init(FlowConfig.Builder(context) - .addDatabaseConfig(DatabaseConfig.Builder(TestDatabase.class) - .modelNotifier(DirectModelNotifier.get()) - .build()).build()); - -``` - -We must use the shared instance of the `DirectModelNotifier` since if we do not, your listeners will not receive callbacks. - -Next register for changes on the `DirectModelNotifier`: -```java - -DirectModelNotifier.get().registerForModelChanges(User.class, new ModelChangedListener() { - @Override - public void onModelChanged(User model, BaseModel.Action action) { - // react to model changes - } - - @Override - public void onTableChanged(BaseModel.Action action) { - // react to table changes. - } - };) - -``` - -Then unregister your model change listener when you don't need it anymore (to prevent memory leaks): - -```java -DirectModelNotifier.get().unregisterForModelChanges(Userr.class, modelChangedListener); - -``` diff --git a/usage2/QueryModels.md b/usage2/QueryModels.md deleted file mode 100644 index 8fd081423..000000000 --- a/usage2/QueryModels.md +++ /dev/null @@ -1,107 +0,0 @@ -# Query Models - -A `QueryModel` is purely an ORM object that maps rows from a `Cursor` into -a `Model` such that when loading from the DB, we can easily use the data from it. - -We use a different annotation, `@QueryModel`, to define it separately. These -do not allow for modifications in the DB, rather act as a marshal agent out of the DB. - -## Define a QueryModel - -For this example, we have a list of employees that we want to gather the average salary -for each position in each department from our company. - -We defined an `Employee` table: - -```java - -@Table(database = AppDatabase.class) -public class EmployeeModel { - - @PrimaryKey - String uid; - - @Column - long salary; - - @Column - String name; - - @Column - String title; - - @Column - String department; -} - -``` - -We need someway to retrieve the results of this query, since we want to avoid -dealing with the `Cursor` directly. We can use a SQLite query with our existing models, but -we have no way to map it currently to our tables, since the query returns new Columns -that do not represent any existing table: - -```java - -SQLite.select(EmployeeModel_Table.department, - Method.avg(EmployeeModel_Table.salary.as("average_salary")), - EmployeeModel_Table.title) - .from(EmployeeModel.class) - .groupBy(EmployeeModel_Table.department, EmployeeModel_Table.title); - -``` - -So we must define a `QueryModel`, representing the results of the query: - -```java -@QueryModel(database = AppDatabase.class) -public class AverageSalary { - - @Column - String title; - - @Column - long average_salary; - - @Column - String department; -} -``` - -And adjust our query to handle the new output: - -```java - -SQLite.select(EmployeeModel_Table.department, - Method.avg(EmployeeModel_Table.salary.as("average_salary")), - EmployeeModel_Table.title) - .from(EmployeeModel.class) - .groupBy(EmployeeModel_Table.department, EmployeeModel_Table.title) - .async() - .queryResultCallback(new QueryTransaction.QueryResultCallback() { - @Override - public void onQueryResult(QueryTransaction transaction, @NonNull CursorResult tResult) { - List queryModels = tResult.toCustomListClose(AverageSalary.class); - - // do something with the result - } - }).execute(); - -``` - -## Query Model Support - -`QueryModel` support only a limited subset of `Model` features. - -If you use the optional base class of `BaseQueryModel`, - Modifications such as `insert()`, `update()`, `save()`, and `delete()` will throw - an `InvalidSqlViewOperationException`. Otherwise, `RetrievalAdapter` do not - contain modification methods. - -They support `allFields` and inheritance and visibility modifiers as defined by [Models](/usage2/Models.md). - -`QueryModel` **do not** support: - 1. `InheritedField`/`InheritedPrimaryKey` - 2. `@PrimaryKey`/`@ForeignKey` - 3. caching - 4. changing "useBooleanGetterSetters" for private boolean fields. diff --git a/usage2/RXSupport.md b/usage2/RXSupport.md deleted file mode 100644 index c9e365377..000000000 --- a/usage2/RXSupport.md +++ /dev/null @@ -1,170 +0,0 @@ -# RXJava Support - -RXJava support in DBFlow is an _incubating_ feature and likely to change over time. We support both RX1 and RX2 and have made the extensions + DBFlow compatibility almost identical - save for the changes and where it makes sense in each version. - -Currently it supports - 1. `Insert`, `Update`, `Delete`, `Set`, `Join`, and all wrapper query mechanisms by wrapping them in `rx()` - 2. Single + `List` model `save()`, `insert()`, `update()`, and `delete()`. - 3. Streaming a set of results from a query - 4. Observing on table changes for specific `ModelQueriable` and providing ability to query from that set repeatedly as needed. - 5. Kotlin extension methods in a separate artifact that enhance the conversion. - -## Getting Started - -Add the separate packages to your project: -```groovy - -dependencies { - // RXJava1 - compile "com.github.Raizlabs.DBFlow:dbflow-rx:${dbflow_version}" - - // optional, for use with Kotlin as a nice companion. - compile "com.github.Raizlabs.DBFlow:dbflow-rx-kotlinextensions:${dbflow_version}" - - // RXJava2 - compile "com.github.Raizlabs.DBFlow:dbflow-rx2:${dbflow_version}" - - // optional, for use with Kotlin as a nice companion. - compile "com.github.Raizlabs.DBFlow:dbflow-rx2-kotlinextensions:${dbflow_version}" -} - -``` - -## Wrapper Language -Using the classes is as easy as wrapping all SQL wrapper calls with `RXSQLite.rx()` (Kotlin we supply extension method): - -Before: -```java - -List list = SQLite.select() - .from(MyTable.class) - .queryList(); - -``` - -After: - -```java - -RXSQLite.rx( - SQLite.select().from(MyTable.class)) - .queryList() - .subscribe((list) -> { - - }); - -``` - -or with Kotlin + extension methods: -```kotlin - - select.from(MyTable::class.java) - .rx() - .list { list -> - - } - -``` - -## Model operations -To make the transition as smoothest as possible, we've provided a `BaseRXModel` which replaces `BaseModel` for convenience in the RX space. - -```kotlin - -class Person(@PrimaryKey var id: Int = 0, @Column var name: String? = "") : BaseRXModel - -``` - -Operations are as easy as: -```java - -new Person(5, "Andrew Grosner") - .insert() - .subscribe((rowId) -> { - - }); - -``` - -or with Kotlin+extensions: -```kotlin - -Person(5, "Andrew Grosner") - .insert { rowId -> - - } - -``` - -## Query Stream - -We can use RX to stream the result set, one at a time from the `ModelQueriable` using -the method `queryStreamResults()`: - -```java - -RXSQLite.rx( - SQLite.select() - .from(TestModel1.class)) - .queryStreamResults() - .subscribe((model) -> { - - }); - -``` - -## Kotlin Support - -Most of the support mirrors [kotlin support](/usage2/KotlinSupport.md) with a few -minor changes. - -Extension properties/methods include: - 1. `rx()` extension method making it super easy to integrate RX. - 2. `RXModelQueriable.streamResults` - stream results one at time to a `Subscription` - 3. `list`, `result`,`streamResults`, `cursorResult`,`statement`, `hasData`, `cursor`, and `count` all provide a method lambda that is called within a `Subscription`. - -```kotlin - -select from MyTable::class - where (MyTable.name `is` "Good") - list { list -> // - - } - -``` - -which is the same with RX as: - -```kotlin - -(select.from(MyTable::class.java) - .where(MyTable.name `is` "Good")) - .rx() - .list { list -> - - } - -``` - - -Or if we want to get pretty with `BaseRXModel` + extensions: - -```kotlin - -Person("Somebody").save { success -> - // do something -} - -Person("Somebody").update { success -> - // do something -} - -Person("Somebody").insert { rowId -> - // do something -} - -Person("Somebody").delete { success -> - // do something -} - -``` diff --git a/usage2/Relationships.md b/usage2/Relationships.md deleted file mode 100644 index befd11a6e..000000000 --- a/usage2/Relationships.md +++ /dev/null @@ -1,214 +0,0 @@ -# Relationships - -We can link `@Table` in DBFlow via 1-1, 1-many, or many-to-many. For 1-1 we use -`@PrimaryKey`, for 1-many we use `@OneToMany`, and for many-to-many we use the `@ManyToMany` annotation. - - -## One To One - -DBFlow supports multiple `@ForeignKey` right out of the box as well (and for the most part, they can also be `@PrimaryKey`). - -```java -@Table(database = AppDatabase.class) -public class Dog extends BaseModel { - - @PrimaryKey - String name; - - @ForeignKey(tableClass = Breed.class) - @PrimaryKey - String breed; - - @ForeignKey - Owner owner; -} - -``` - -`@ForeignKey` can only be a subset of types: - 1. `Model` - 2. Any field not requiring a `TypeConverter`. If not a `Model` or a table class, you _must_ specify the `tableClass` it points to. - 3. Cannot inherit `@ForeignKey` from non-model classes (see [Inherited Columns](/usage2/Models.md#inherited-columns)) - -If you create a circular reference (i.e. two tables with strong references to `Model` as `@ForeignKey` to each other), read on. - -## Stubbed Relationships - -For efficiency reasons we recommend specifying `@ForeignKey(stubbedRelationship = true)`. What this will do is only _preset_ the primary key references into a table object. All other fields will not be set. If you need to access the full object, you will have to call `load()` for `Model`, or use the `ModelAdapter` to load the object from the DB. - -From our previous example of `Dog`, instead of using a `String` field for **breed** -we recommended by using a `Breed`. It is nearly identical, but the difference being -we would then only need to call `load()` on the reference and it would query the `Breed` -table for a row with the `breed` id. This also makes it easier if the table you -reference has multiple primary keys, since DBFlow will handle the work for you. - -Multiple calls to `load()` will query the DB every time, so call when needed. Also if you don't specify `@Database(foreignKeyConstraintsEnforced = true)`, calling `load()` may not have any effect. Essentially without enforcing `@ForeignKey` at a SQLite level, you can end up with floating key references that do not exist in the referenced table. - -In normal circumstances, for every load of a `Dog` object from the database, -we would also do a load of related `Owner`. This means that even if multiple `Dog` say (50) -all point to same owner we end up doing 2x retrievals for every load of `Dog`. Replacing -that model field of `Owner` with a stubbed relationship prevents the extra N lookup time, -leading to much faster loads of `Dog`. - -__Note__: using stubbed relationships also helps to prevent circular references that can -get you in a `StackOverFlowError` if two tables strongly reference each other in `@ForeignKey`. - -Our modified example now looks like this: - -```java -@Table(database = AppDatabase.class) -public class Dog extends BaseModel { - - @PrimaryKey - String name; - - @ForeignKey(stubbedRelationship = true) - @PrimaryKey - Breed breed; // tableClass only needed for single-field refs that are not Model. - - @ForeignKey(stubbedRelationship = true) - Owner owner; -} - -``` - -## One To Many - -In DBFlow, `@OneToMany` is an annotation that you provide to a method in your `Model` class that will allow management of those objects during CRUD operations. -This can allow you to combine a relationship of objects to a single `Model` to happen together on load, save, insert, update, and deletion. - -```java - -@Table(database = ColonyDatabase.class) -public class Queen extends BaseModel { - - @PrimaryKey(autoincrement = true) - long id; - - @Column - String name; - - @ForeignKey(saveForeignKeyModel = false) - Colony colony; - - List ants; - - @OneToMany(methods = {OneToMany.Method.ALL}, _variableName = "ants") - public List getMyAnts() { - if (ants == null || ants.isEmpty()) { - ants = SQLite.select() - .from(Ant.class) - .where(Ant_Table.queenForeignKeyContainer_id.eq(id)) - .queryList(); - } - return ants; - } -} - -``` - -## Many To Many - - -In DBFlow many to many is done via source-gen. A simple table: - -```java - -@Table(database = TestDatabase.class) -@ManyToMany(referencedTable = Follower.class) -public class User extends BaseModel { - - @PrimaryKey - String name; - - @PrimaryKey - int id; - -} - -``` - -Generates a `@Table` class named `User_Follower`, which DBFlow treats as if you -coded the class yourself!: - -```java - -@Table( - database = TestDatabase.class -) -public final class User_Follower extends BaseModel { - @PrimaryKey( - autoincrement = true - ) - long _id; - - @ForeignKey( - saveForeignKeyModel = false - ) - Follower follower; - - @ForeignKey( - saveForeignKeyModel = false - ) - User user; - - public final long getId() { - return _id; - } - - public final Followers getFollower() { - return follower; - } - - public final void setFollower(Follower param) { - follower = param; - } - - public final Users getUser() { - return user; - } - - public final void setUser(User param) { - user = param; - } -} - -``` - -This annotation makes it very easy to generate "join" tables for you to use in the app for a ManyToMany relationship. It only generates the table you need. To use it you must reference it in code as normal. - -_Note_: This annotation is only a helper to generate tables that otherwise you -would have to write yourself. It is expected that management still is done by you, the developer. - -### Custom Column Names - -You can change the name of the columns that are generated. By default they are simply -lower case first letter version of the table name. - -`referencedTableColumnName` -> Refers to the referenced table. -`thisTableColumnName` -> Refers to the table that is creating the reference. - -### Multiple ManyToMany - -You can also specify `@MultipleManyToMany` which enables you to define more -than a single `@ManyToMany` relationship on the table. - -A class can use both: - -```java -@Table(database = TestDatabase.class) -@ManyToMany(referencedTable = TestModel1.class) -@MultipleManyToMany({@ManyToMany(referencedTable = TestModel2.class), - @ManyToMany(referencedTable = com.raizlabs.android.dbflow.test.sql.TestModel3.class)}) -public class ManyToManyModel extends BaseModel { - - @PrimaryKey - String name; - - @PrimaryKey - int id; - - @Column - char anotherColumn; -} -``` diff --git a/usage2/Retrieval.md b/usage2/Retrieval.md deleted file mode 100644 index e398e82ec..000000000 --- a/usage2/Retrieval.md +++ /dev/null @@ -1,116 +0,0 @@ -# Retrieval - -DBFlow provides a few ways to retrieve information from the database. Through -the `Model` classes we can map this information to easy-to-use objects. - -DBFlow provides a few different ways to retrieve information from the database. We -can retrieve synchronously or asynchronous (preferred). - -We can also use `ModelView` ([read here](/usage2/ModelViews.md)) and `@Index` ([read here](/usage2/Indexing.md)) to perform faster retrieval on a set of data constantly queried. - -## Synchronous Retrieval - -Using the [SQLite query language](/usage2/SQLiteWrapperLanguage.md) we can retrieve -data easily and expressively. To perform it synchronously: - - -```java - -// list -List employees = SQLite.select() - .from(Employee.class) - .queryList(); - -// single result, we apply a limit(1) automatically to get the result even faster. -Employee employee = SQLite.select() - .from(Employee.class) - .where(Employee_Table.name.eq("Andrew Grosner")) - .querySingle(); - -// get a custom list -List employees = SQLite.select() - .from(Employee.class) - .queryCustomList(AnotherTable.class); - -// custom object -AnotherTable anotherObject = SQLite.select() - .from(Employee.class) - .where(Employee_Table.name.eq("Andrew Grosner")) - .queryCustomSingle(AnotherTable.class); - -``` - -To query custom objects or lists, see how to do so in [QueryModel](/usage2/QueryModel.md). - -Also you can query a `FlowCursorList`/`FlowTableList` from a query easily -via `queryCursorList()` and the `queryTableList()` methods. To see more on these, -go to [Flow Lists](/usage2/FlowLists.md). - - -## Asynchronous Retrieval - -DBFlow provides the very-handy `Transaction` system that allows you to place all -calls to the DB in a queue. Using this system, we recommend placing retrieval queries -on this queue to help prevent locking and threading issues when using a database. - -A quick sample of retrieving data asyncly: - -```java - -SQLite.select() - .from(TestModel1.class) - .where(TestModel1_Table.name.is("Async")) - .async() - .queryResultCallback(new QueryTransaction.QueryResultCallback() { - @Override - public void onQueryResult(QueryTransaction transaction, @NonNull CursorResult tResult) { - - } - }).execute(); - -``` - -This is fundamentally equal to: - -```java - - -FlowManager.getDatabaseForTable(TestModel1.class) - .beginTransactionAsync(new QueryTransaction.Builder<>( - SQLite.select() - .from(TestModel1.class) - .where(TestModel1_Table.name.is("Async"))) - .queryResult(new QueryTransaction.QueryResultCallback() { - @Override - public void onQueryResult(QueryTransaction transaction, @NonNull CursorResult tResult) { - - } - }).build()) -.build().execute(); - -``` - -The first example in this section is more of a convenience for (2). - -By default the library uses the `DefaultTransactionManager` which utilizes -a `DefaultTransactionQueue`. This queue is essentially an ordered queue that -executes FIFO (first-in-first-out) and blocks itself until new `Transaction` are added. - -If you wish to customize and provide a different queue (or map it to an existing system), read up on [Transactions](/usage2/StoringData.md). - - -Compared to pre-3.0 DBFlow, this is a breaking change from the old, priority-based -queue system. The reason for this change was to simplify the queuing system and -allow other systems to exist without confusing loss of functionality. To keep the old -system read [Transactions](/usage2/StoringData.md). - -## Faster Retrieval - -In an effort to squeeze out more speed at the potential cost of flexibility, DBFlow provides a -couple ways to optimize loads from the DB. If you do not wish to use caching but wish -to speed conversion from `Cursor` to `Model`, read on. - - If you simply retrieve a `List` of `Model` -without any projection from your DB, you can take advantage of 2 features: - 1. `@Table(orderedCursorLookUp = true)` -> We do not call `Cursor.getColumnIndex()` and assume that the `Cursor` is ordered by column declarations in the class. - 2. `@Table(assignDefaultValuesFromCursor = false)` -> We do not expect to reuse an object from the DB (or care) if the corresponding fields aren't assigned a value when missing from the `Cursor`. diff --git a/usage2/SQLCipherSupport.md b/usage2/SQLCipherSupport.md deleted file mode 100644 index 83bfee79e..000000000 --- a/usage2/SQLCipherSupport.md +++ /dev/null @@ -1,57 +0,0 @@ -# SQLCipher Support - -As of 3.0.0-beta2+, DBFlow now supports [SQLCipher](https://www.zetetic.net/sqlcipher/) fairly easily. - -To add the library add the library to your `build.gradle` with same version you are using with the rest of the library. - -```groovy -dependencies { - compile "com.github.Raizlabs.DBFlow:dbflow-sqlcipher:${version}" - compile "net.zetetic:android-database-sqlcipher:${sqlcipher_version}@aar" - -} -``` - -You also need to add the Proguard rule: -``` --keep class net.sqlcipher.** { *; } --dontwarn net.sqlcipher.** -``` - -Next, you need to subclass the provided `SQLCipherOpenHelper` (taken from test files): - -```java -public class SQLCipherHelperImpl extends SQLCipherOpenHelper { - - public SQLCipherHelperImpl(DatabaseDefinition databaseDefinition, DatabaseHelperListener listener) { - super(databaseDefinition, listener); - } - - @Override - protected String getCipherSecret() { - return "dbflow-rules"; - } -} -``` - -_Note:_ that the constructor with `DatabaseDefinition` and `DatabaseHelperListener` is required. - -Then in your application class when initializing DBFlow: - -```java - -FlowManager.init(new FlowConfig.Builder(this) - .addDatabaseConfig( - new DatabaseConfig.Builder(CipherDatabase.class) - .openHelper(new DatabaseConfig.OpenHelperCreator() { - @Override - public OpenHelper createHelper(DatabaseDefinition databaseDefinition, DatabaseHelperListener helperListener) { - return new SQLCipherHelperImpl(databaseDefinition, helperListener); - } - }) - .build()) - .build()); - -``` - -And that's it. You're all set to start using SQLCipher! diff --git a/usage2/SQLiteWrapperLanguage.md b/usage2/SQLiteWrapperLanguage.md deleted file mode 100644 index fb24b014b..000000000 --- a/usage2/SQLiteWrapperLanguage.md +++ /dev/null @@ -1,384 +0,0 @@ -# SQLite Wrapper Language - -DBFlow's SQLite wrapper language attempts to make it as easy as possible to -write queries, execute statements, and more. - -We will attempt to make this doc comprehensive, but reference the SQLite language -for how to formulate queries, as DBFlow follows it as much as possible. - -## SELECT - -The way to query data, `SELECT` are started by: - -```java - -SQLite.select().from(SomeTable.class) - -``` - -### Projections - -By default if no parameters are specified in the `select()` query, we use the `*` wildcard qualifier, -meaning all columns are returned in the results. - -To specify individual columns, you _must_ use `Property` variables. -These get generated when you annotate your `Model` with columns, or created manually. - -```java - -SQLite.select(Player_Table.name, Player_Table.position) - .from(Player.class) - -``` - -To specify methods such as `COUNT()` or `SUM()` (static import on `Method`): - - -```java - -SQLite.select(count(Employee_Table.name), sum(Employee_Table.salary)) - .from(Employee.class) - -``` - -Translates to: - -```sqlite - -SELECT COUNT(`name`), SUM(`salary`) FROM `Employee`; - -``` - -There are more handy methods in `Method`. - -### Operators - -DBFlow supports many kinds of operations. They are formulated into a `OperatorGroup`, -which represent a set of `SQLOperator` subclasses combined into a SQLite conditional piece. -`Property` translate themselves into `SQLOperator` via their conditional methods such as -`eq()`, `lessThan()`, `greaterThan()`, `between()`, `in()`, etc. - -They make it very easy to construct concise and meaningful queries: - -```java - -int taxBracketCount = SQLite.select(count(Employee_Table.name)) - .from(Employee.class) - .where(Employee_Table.salary.lessThan(150000)) - .and(Employee_Table.salary.greaterThan(80000)) - .count(); - -``` - -Translates to: - -```sqlite - -SELECT COUNT(`name`) FROM `Employee` WHERE `salary`<150000 AND `salary`>80000; - -``` - -DBFlow supports `IN`/`NOT IN` and `BETWEEN` as well. - -A more comprehensive list of operations DBFlow supports and what they translate to: - - 1. is(), eq() -> = - 2. isNot(), notEq() -> != - 3. isNull() -> IS NULL / isNotNull() -> IS NOT NULL - 4. like(), glob() - 5. greaterThan(), greaterThanOrEqual(), lessThan(), lessThanOrEqual() - 6. between() -> BETWEEN - 7. in(), notIn() - -#### Nested Conditions - -To create nested conditions (in parenthesis more often than not), just include -an `OperatorGroup` as a `SQLOperator` in a query: - - -```java - -SQLite.select() - .from(Location.class) - .where(Location_Table.latitude.eq(home.getLatitude())) - .and(OperatorGroup.clause() - .and(Location_Table.latitude - .minus(PropertyFactory.from(home.getLatitude()) - .eq(1000L)))) - -``` - -Translates to: - -```sqlite - -SELECT * FROM `Location` WHERE `latitude`=45.05 AND (`latitude` - 45.05) = 1000 - -``` - -#### Nested Queries - -To create a nested query simply include it as a `Property` via `PropertyFactory.from(BaseQueriable)`: - -```java - -.where(PropertyFactory.from(SQLite.select().from(...).where(...)) - -``` - -This appends a `WHERE (SELECT * FROM {table} )` to the query. - -### JOINS - -For reference, ([JOIN examples](http://www.tutorialspoint.com/sqlite/sqlite_using_joins.htm)). - -`JOIN` statements are great for combining many-to-many relationships. -If your query returns non-table fields and cannot map to an existing object, -see about [query models](/usage2/QueryModels.md) - -For example we have a table named `Customer` and another named `Reservations`. - -```SQL -SELECT FROM `Customer` AS `C` INNER JOIN `Reservations` AS `R` ON `C`.`customerId`=`R`.`customerId` -``` - -```java -// use the different QueryModel (instead of Table) if the result cannot be applied to existing Model classes. -List customers = new Select() - .from(Customer.class).as("C") - .join(Reservations.class, JoinType.INNER).as("R") - .on(Customer_Table.customerId - .withTable(NameAlias.builder("C").build()) - .eq(Reservations_Table.customerId.withTable("R")) - .queryCustomList(CustomTable.class); -``` - -The `IProperty.withTable()` method will prepend a `NameAlias` or the `Table` alias to the `IProperty` in the query, convenient for JOIN queries: - -```sqlite -SELECT EMP_ID, NAME, DEPT FROM COMPANY LEFT OUTER JOIN DEPARTMENT - ON COMPANY.ID = DEPARTMENT.EMP_ID -``` - -in DBFlow: - -```java -SQLite.select(Company_Table.EMP_ID, Company_Table.DEPT) - .from(Company.class) - .leftOuterJoin(Department.class) - .on(Company_Table.ID.withTable().eq(Department_Table.EMP_ID.withTable())) -``` - -### Order By - -```java - -// true for 'ASC', false for 'DESC' -SQLite.select() - .from(table) - .where() - .orderBy(Customer_Table.customer_id, true) - - SQLite.select() - .from(table) - .where() - .orderBy(Customer_Table.customer_id, true) - .orderBy(Customer_Table.name, false) -``` - -### Group By - -```java -SQLite.select() - .from(table) - .groupBy(Customer_Table.customer_id, Customer_Table.customer_name) -``` - -### HAVING - -```java -SQLite.select() - .from(table) - .groupBy(Customer_Table.customer_id, Customer_Table.customer_name)) - .having(Customer_Table.customer_id.greaterThan(2)) -``` - -### LIMIT + OFFSET - -```java -SQLite.select() - .from(table) - .limit(3) - .offset(2) -``` - -## UPDATE - -DBFlow supports two kind of UPDATE: - 1. `Model.update()` - 2. `SQLite.update()` - -For simple `UPDATE` for a single or few, concrete set of `Model` stick with (1). -For powerful multiple `Model` update that can span many rows, use (2). In this -section we speak on (2). **Note:** if using model caching, you'll need to clear it out -post an operation from (2). - - -```sql - -UPDATE Ant SET type = 'other' WHERE male = 1 AND type = 'worker'; -``` - -Using DBFlow: - -```java - -// Native SQL wrapper -SQLite.update(Ant.class) - .set(Ant_Table.type.eq("other")) - .where(Ant_Table.type.is("worker")) - .and(Ant_Table.isMale.is(true)) - .async() - .execute(); // non-UI blocking -``` - -The `Set` part of the `Update` supports different kinds of values: - 1. `ContentValues` -> converts to key/value as a `SQLOperator` of `is()`/`eq()` - 2. `SQLOperator`, which are grouped together as part of the `SET` statement. - -## DELETE - -`DELETE` queries in DBFlow are similiar to `Update` in that we have two kinds: - - 1. `Model.delete()` - 2. `SQLite.delete()` - -For simple `DELETE` for a single or few, concrete set of `Model` stick with (1). -For powerful multiple `Model` deletion that can span many rows, use (2). In this -section we speak on (2). **Note:** if using model caching, you'll need to clear it out -post an operation from (2). - - -```java - -// Delete a whole table -Delete.table(MyTable.class); - -// Delete multiple instantly -Delete.tables(MyTable1.class, MyTable2.class); - -// Delete using query -SQLite.delete(MyTable.class) - .where(DeviceObject_Table.carrier.is("T-MOBILE")) - .and(DeviceObject_Table.device.is("Samsung-Galaxy-S5")) - .async() - .execute(); -``` - -## INSERT - -`INSERT` queries in DBFlow are also similiar to `Update` and `Delete` in that we -have two kinds: - - 1. `Model.insert()` - 2. `SQLite.insert()` - -For simple `INSERT` for a single or few, concrete set of `Model` stick with (1). -For powerful multiple `Model` insertion that can span many rows, use (2). In this -section we speak on (2). **Note:** using model caching, you'll need to clear it out -post an operation from (2). - -```java - -// columns + values separately -SQLite.insert(SomeTable.class) - .columns(SomeTable_Table.name, SomeTable_Table.phoneNumber) - .values("Default", "5555555") - .async() - .execute() - -// or combine into Operators - SQLite.insert(SomeTable.class) - .columnValues(SomeTable_Table.name.eq("Default"), - SomeTable_Table.phoneNumber.eq("5555555")) - .async() - .execute() - -``` - -`INSERT` supports inserting multiple rows as well. - -```java - -// columns + values separately -SQLite.insert(SomeTable.class) - .columns(SomeTable_Table.name, SomeTable_Table.phoneNumber) - .values("Default1", "5555555") - .values("Default2", "6666666") - .async() - .execute() - -// or combine into Operators - SQLite.insert(SomeTable.class) - .columnValues(SomeTable_Table.name.eq("Default1"), - SomeTable_Table.phoneNumber.eq("5555555")) - .columnValues(SomeTable_Table.name.eq("Default2"), - SomeTable_Table.phoneNumber.eq("6666666")) - .async() - .execute() - -``` - -## Trigger - -Triggers enable SQLite-level listener operations that perform some operation, modification, -or action to run when a specific database event occurs. [See](https://www.sqlite.org/lang_createtrigger.html) for more documentation on its usage. - -```java - -Trigger.create("SomeTrigger") - .after().insert(ConditionModel.class).begin(new Update<>(TestUpdateModel.class) - .set(TestUpdateModel_Table.value.is("Fired"))).enable(); // enables the trigger if it does not exist, so subsequent calls are OK - - -``` - -## Case - -The SQLite `CASE` operator is very useful to evaluate a set of conditions and "map" them -to a certain value that returns in a SELECT query. - -We have two kinds of case: -1. Simple -2. Searched - -The simple CASE query in DBFlow: - -```java - -SQLite.select(CaseModel_Table.customerId, - CaseModel_Table.firstName, - CaseModel_Table.lastName, - SQLite._case(CaseModel_Table.country) - .when("USA").then("Domestic") - ._else("Foreign") - .end("CustomerGroup")).from(CaseModel.class) - -``` - -The CASE is returned as `CustomerGroup` with the valyes of "Domestic" if the country is from -the 'USA' otherwise we mark the value as "Foreign". These appear alongside the results -set from the SELECT. - -The search CASE is a little more complicated in that each `when()` statement -represents a `SQLOperator`, which return a `boolean` expression: - -```java - -SQLite.select(CaseModel_Table.customerId, - CaseModel_Table.firstName, - CaseModel_Table.lastName, - SQLite.caseWhen(CaseModel_Table.country.eq("USA")) - .then("Domestic") - ._else("Foreign").end("CustomerGroup")).from(CaseModel.class); -``` diff --git a/usage2/StoringData.md b/usage2/StoringData.md deleted file mode 100644 index 692451c3f..000000000 --- a/usage2/StoringData.md +++ /dev/null @@ -1,313 +0,0 @@ -# Storing Data - -DBFlow provide a few mechanisms by which we store data to the database. The difference of options -should not provide confusion but rather allow flexibility in what you decide is the best way -to store information. - -## Synchronous Storage - -While generally saving data synchronous should be avoided, for small amounts of data -it has little effect. - -```java - -FlowManager.getModelAdapter(SomeTable.class).save(model); - -FlowManager.getModelAdapter(SomeTable.class).insert(model); - -FlowManager.getModelAdapter(SomeTable.class).update(model); - -model.insert(); // inserts -model.update(); // updates -model.save(); // checks if exists, if true update, else insert. - -``` - -Code (without running in a transaction) like this should be avoided: - -```java - -for (int i = 0; i < models.size(), i++) { - models.get(i).save(); -} - -``` - -Doing operations on the main thread can block it if you read and write to the DB on a different thread while accessing DB on the main. - -## Synchronous Transactions - -A simple database transaction can be wrapped in a call: - -```java - -FlowManager.getDatabase(AppDatabase.class).executeTransaction(new ITransaction() { - @Override - public void execute(DatabaseWrapper databaseWrapper) { - // something here - Player player = new Player("Andrew", "Grosner"); - player.save(databaseWrapper); // use wrapper (from BaseModel) - } -}); - -``` - -Even though DBFlow is ridiculously fast, this should be put on a separate thread - outside of the UI, so that your UI remains responsive on all devices. - - Instead we should move onto `Transaction` (the preferred method). - -### Async Transactions - -## Transactions - -Transactions are ACID in SQLite, meaning they either occur completely or not at all. -Using transactions significantly speed up the time it takes to store. So recommendation -you should use transactions whenever you can. - -Async is the preferred method. Transactions, using the `DefaultTransactionManager`, - occur on one thread per-database (to prevent flooding from other DB in your app) - and receive callbacks on the UI. You can override this behavior and roll your own - or hook into an existing system, read [here](/usage2/StoringData.md#custom-transactionmanager). - -Also to use the legacy, priority-based system, read [here](/usage2/StoringData.md#priority-queue). - - A basic transaction: - - ```java - -DatabaseDefinition database = FlowManager.getDatabase(AppDatabase.class); -Transaction transaction = database.beginTransactionAsync(new ITransaction() { - @Override - public void execute(DatabaseWrapper databaseWrapper) { - called.set(true); - } - }).build(); -transaction.execute(); // execute - -transaction.cancel(); - // attempt to cancel before its run. If it's already ran, this call has no effect. - - ``` - - `Transaction` have callbacks to allow you to "listen" for success and errors. - - ```java - - - transaction - .success(new Transaction.Success() { - @Override - public void onSuccess(Transaction transaction) { - // called post-execution on the UI thread. - } - }) - .error(new Transaction.Error() { - @Override - public void onError(Transaction transaction, Throwable error) { - // call if any errors occur in the transaction. - } - }); - - - ``` - - The `Success` callback runs post-transaction on the UI thread. - The `Error` callback is called on the UI thread if and only if it is specified and an exception occurs, - otherwise it is thrown in the `Transaction` as a `RuntimeException`. **Note**: - all exceptions are caught when specifying the callback. Ensure you handle all - errors, otherwise you might miss some problems. - -### ProcessModelTransaction - -`ProcessModelTransaction` allows for more flexibility and for you to easily operate on a set of `Model` in a -`Transaction` easily. It holds a list of `Model` by which you provide the modification -method in the `Builder`. You can listen for when each are processed inside a normal -`Transaction`. - -It is a convenient way to operate on them: - -```java - -ProcessModelTransaction processModelTransaction = - new ProcessModelTransaction.Builder<>(new ProcessModelTransaction.ProcessModel() { - @Override - public void processModel(TestModel1 model) { - // call some operation on model here - model.save(); - model.insert(); // or - model.delete(); // or - } - }).processListener(new ProcessModelTransaction.OnModelProcessListener() { - @Override - public void onModelProcessed(long current, long total, TestModel1 modifiedModel) { - modelProcessedCount.incrementAndGet(); - } - }).addAll(items).build(); -Transaction transaction = database.beginTransactionAsync(processModelTransaction).build(); -transaction.execute(); - -``` - -In Kotlin (with `dbflow-kotlinextensions`), we can drastically simplify: - -```java - -items.processInTransactionAsync({ it, databaseWrapper -> it.delete(databaseWrapper) }, - ProcessModelTransaction.OnModelProcessListener { current, size, model -> - modelProcessedCount.incrementAndGet(); - }) - -``` -You can listen to when operations complete for each model via the `OnModelProcessListener`. -These callbacks occur on the UI thread. If you wish to run them on same thread (great for tests), -set `runProcessListenerOnSameThread()` to `true`. - -### FastStoreModelTransaction - -The `FastStoreModelTransaction` is the quickest, lightest way to store a `List` of -`Model` into the database through a `Transaction`. It comes with some restrictions when compared to `ProcessModelTransaction`: - 1. All `Model` must be from same Table/Model Class. - 2. No progress listening - 3. Can only `save`, `insert`, or `update` the whole list entirely. - -```java - -FastStoreModelTransaction - .insertBuilder(FlowManager.getModelAdapter(TestModel2.class)) - .addAll(modelList) - .build() - - // updateBuilder + saveBuilder also available. - -``` - -What it provides: - 1. Reuses `ContentValues`, `DatabaseStatement`, and other classes where possible. - 2. Opens and closes own `DatabaseStatement` per total execution. - 3. Significant speed bump over `ProcessModelTransaction` at the expense of flexibility. - -### Custom TransactionManager - -If you prefer to roll your own thread-management system or have an existing -system you can override the default system included. - - -To begin you must implement a `ITransactionQueue`: - -```java - -public class CustomQueue implements ITransactionQueue { - - @Override - public void add(Transaction transaction) { - - } - - @Override - public void cancel(Transaction transaction) { - - } - - @Override - public void startIfNotAlive() { - - } - - @Override - public void cancel(String name) { - - } - - @Override - public void quit() { - - } -} - -``` - -You must provide ways to `add()`, `cancel(Transaction)`, and `startIfNotAlive()`. -The other two methods are optional, but recommended. - -`startIfNotAlive()` in the `DefaultTransactionQueue` will start itself (since it's -a thread). - - Next you can override the `BaseTransactionManager` (not required, see later): - -```java - -public class CustomTransactionManager extends BaseTransactionManager { - - public CustomTransactionManager(DatabaseDefinition databaseDefinition) { - super(new CustomTransactionQueue(), databaseDefinition); - } - -} - -``` - -To register it with DBFlow, in your `FlowConfig`, you must: - -```java - -FlowManager.init(builder - .addDatabaseConfig(new DatabaseConfig.Builder(AppDatabase.class) - .transactionManagerCreator(new DatabaseConfig.TransactionManagerCreator() { - @Override - public BaseTransactionManager createManager(DatabaseDefinition databaseDefinition) { - // this will be called once database modules are loaded and created. - return new CustomTransactionManager(databaseDefinition); - - // or you can: - //return new DefaultTransactionManager(new CustomTransactionQueue(), databaseDefinition); - } - }) - .build()) - .build()); - -``` - -### Priority Queue - -In versions pre-3.0, DBFlow utilized a `PriorityBlockingQueue` to manage the asynchronous -dispatch of `Transaction`. As of 3.0, it has switched to simply a FIFO queue. To -keep the legacy way, a `PriorityTransactionQueue` was created. - -As seen in [Custom Transaction Managers](/usage2/StoringData.md#custom-transactionmanager), -we provide a custom instance of the `DefaultTransactionManager` with the `PriorityTransactionQueue` specified: - -```java - -FlowManager.init(builder - .addDatabaseConfig(new DatabaseConfig.Builder(AppDatabase.class) - .transactionManagerCreator(new DatabaseConfig.TransactionManagerCreator() { - @Override - public BaseTransactionManager createManager(DatabaseDefinition databaseDefinition) { - // this will be called once database modules are loaded and created. - return new DefaultTransactionManager( - new PriorityTransactionQueue("DBFlow Priority Queue"), - databaseDefinition); - } - }) - .build()) - .build()); - -``` - -What this does is for the specified database (in this case `AppDatabase`), -now require each `ITransaction` specified for the database should wrap itself around -the `PriorityTransactionWrapper`. Otherwise an the `PriorityTransactionQueue` -wraps the existing `Transaction` in a `PriorityTransactionWrapper` with normal priority. - - -To specify a priority: - -```java - -FlowManager.getDatabase(AppDatabase.class) - .beginTransactionAsync(new PriorityTransactionWrapper.Builder(myTransaction) - .priority(PriorityTransactionWrapper.PRIORITY_HIGH).build()) - .build().execute(); - -``` diff --git a/usage2/TypeConverters.md b/usage2/TypeConverters.md deleted file mode 100644 index 6593b83aa..000000000 --- a/usage2/TypeConverters.md +++ /dev/null @@ -1,58 +0,0 @@ -# Type conversion - -When building out `Model` classes, you may wish to provide a different type of `@Column` that from the standard supported column types. To recap the standard column types include: - 1. `String`, `char`, `Character` - 2. All numbers types (primitive + boxed) - 3. `byte[]`/`Byte` - 4. `Blob` (DBFlow's version) - 5. `Date`/`java.sql.Date` - 6. Bools - 7. `Model` as `@ForeignKey` - 8. `Calendar` - 9. `BigDecimal` - 10. `UUID` - -`TypeConverter` do _not_ support: - 1. Any Parameterized fields. - 2. `List`, `Map`, etc. Best way to fix this is to create a separate table [relationship](/usage2/Relationships.md) - 3. Conversion from one type-converter to another (i.e `JSONObject` to `Date`). The first parameter of `TypeConverter` is the value of the type as if it was a primitive/boxed type. - 4. Conversion from custom type to `Model`, or `Model` to a supported type. - 5. The custom class _must_ map to a non-complex field such as `String`, numbers, `char`/`Character` or `Blob` - -## Define a TypeConverter - -Defining a `TypeConverter` is quick and easy. - -This example creates a `TypeConverter` for a field that is `JSONObject` and converts it to a `String` representation: - -```java - -@com.raizlabs.android.dbflow.annotation.TypeConverter -public class JSONConverter extends TypeConverter { - - @Override - public String getDBValue(JSONObject model) { - return model == null ? null : model.toString(); - } - - @Override - public JSONObject getModelValue(String data) { - JSONObject jsonObject = null; - try { - jsonObject = new JSONObject(data); - } catch (JSONException e) { - // you should consider logging or throwing exception. - } - return jsonObject; - } -} - -``` - -Once this is defined, by using the annotation `@TypeConverter`, it is registered automatically accross all databases. - -There are cases where you wish to provide multiple `TypeConverter` for same kind of field (i.e. `Date` with different date formats stored in a DB). - -## TypeConverter for specific `@Column` - -In DBFlow, specifying a `TypeConverter` for a `@Column` is as easy as `@Column(typeConverter = JSONConverter.class)`. What it will do is create the converter once for use only when that column is used.