| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.room.processor |
| |
| import androidx.room.Delete |
| import androidx.room.Insert |
| import androidx.room.Query |
| import androidx.room.RawQuery |
| import androidx.room.SkipQueryVerification |
| import androidx.room.Transaction |
| import androidx.room.Update |
| import androidx.room.processing.XConstructorElement |
| import androidx.room.processing.XDeclaredType |
| import androidx.room.processing.XTypeElement |
| import androidx.room.verifier.DatabaseVerifier |
| import androidx.room.vo.Dao |
| import androidx.room.vo.KotlinDefaultMethodDelegate |
| |
| class DaoProcessor( |
| baseContext: Context, |
| val element: XTypeElement, |
| val dbType: XDeclaredType, |
| val dbVerifier: DatabaseVerifier? |
| ) { |
| val context = baseContext.fork(element) |
| |
| companion object { |
| val PROCESSED_ANNOTATIONS = listOf(Insert::class, Delete::class, Query::class, |
| Update::class, RawQuery::class) |
| } |
| |
| fun process(): Dao { |
| context.checker.hasAnnotation(element, androidx.room.Dao::class, |
| ProcessorErrors.DAO_MUST_BE_ANNOTATED_WITH_DAO) |
| context.checker.check(element.isAbstract() || element.isInterface(), |
| element, ProcessorErrors.DAO_MUST_BE_AN_ABSTRACT_CLASS_OR_AN_INTERFACE) |
| |
| val declaredType = element.asDeclaredType() |
| val allMethods = element.getAllMethods() |
| val methods = allMethods |
| .filter { |
| it.isAbstract() && |
| it.findKotlinDefaultImpl() == null |
| }.groupBy { method -> |
| context.checker.check( |
| PROCESSED_ANNOTATIONS.count { method.hasAnnotation(it) } == 1, method, |
| ProcessorErrors.INVALID_ANNOTATION_COUNT_IN_DAO_METHOD |
| ) |
| if (method.hasAnnotation(Query::class)) { |
| Query::class |
| } else if (method.hasAnnotation(Insert::class)) { |
| Insert::class |
| } else if (method.hasAnnotation(Delete::class)) { |
| Delete::class |
| } else if (method.hasAnnotation(Update::class)) { |
| Update::class |
| } else if (method.hasAnnotation(RawQuery::class)) { |
| RawQuery::class |
| } else { |
| Any::class |
| } |
| } |
| val processorVerifier = if (element.hasAnnotation(SkipQueryVerification::class) || |
| element.hasAnnotation(RawQuery::class)) { |
| null |
| } else { |
| dbVerifier |
| } |
| |
| val queryMethods = methods[Query::class]?.map { |
| QueryMethodProcessor( |
| baseContext = context, |
| containing = declaredType, |
| executableElement = it, |
| dbVerifier = processorVerifier).process() |
| } ?: emptyList() |
| |
| val rawQueryMethods = methods[RawQuery::class]?.map { |
| RawQueryMethodProcessor( |
| baseContext = context, |
| containing = declaredType, |
| executableElement = it |
| ).process() |
| } ?: emptyList() |
| |
| val insertionMethods = methods[Insert::class]?.map { |
| InsertionMethodProcessor( |
| baseContext = context, |
| containing = declaredType, |
| executableElement = it).process() |
| } ?: emptyList() |
| |
| val deletionMethods = methods[Delete::class]?.map { |
| DeletionMethodProcessor( |
| baseContext = context, |
| containing = declaredType, |
| executableElement = it).process() |
| } ?: emptyList() |
| |
| val updateMethods = methods[Update::class]?.map { |
| UpdateMethodProcessor( |
| baseContext = context, |
| containing = declaredType, |
| executableElement = it).process() |
| } ?: emptyList() |
| |
| val transactionMethods = allMethods.filter { member -> |
| member.hasAnnotation(Transaction::class) && |
| PROCESSED_ANNOTATIONS.none { member.hasAnnotation(it) } |
| }.map { |
| TransactionMethodProcessor( |
| baseContext = context, |
| containing = declaredType, |
| executableElement = it).process() |
| } |
| |
| val kotlinDefaultMethodDelegates = if (element.isInterface()) { |
| val allProcessedMethods = |
| methods.values.flatten() + transactionMethods.map { it.element } |
| allMethods.filterNot { |
| allProcessedMethods.contains(it) |
| }.mapNotNull { method -> |
| method.findKotlinDefaultImpl()?.let { delegate -> |
| KotlinDefaultMethodDelegate( |
| element = method, |
| delegateElement = delegate |
| ) |
| } |
| } |
| } else { |
| emptyList() |
| } |
| |
| val constructors = element.getConstructors() |
| val goodConstructor = constructors.firstOrNull { |
| it.parameters.size == 1 && |
| it.parameters[0].type.isAssignableFrom(dbType) |
| } |
| val constructorParamType = if (goodConstructor != null) { |
| goodConstructor.parameters[0].type.typeName |
| } else { |
| validateEmptyConstructor(constructors) |
| null |
| } |
| |
| context.checker.check(methods[Any::class] == null, element, |
| ProcessorErrors.ABSTRACT_METHOD_IN_DAO_MISSING_ANY_ANNOTATION) |
| |
| val type = declaredType.typeName |
| context.checker.notUnbound(type, element, |
| ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_DAO_CLASSES) |
| |
| return Dao(element = element, |
| type = declaredType, |
| queryMethods = queryMethods, |
| rawQueryMethods = rawQueryMethods, |
| insertionMethods = insertionMethods, |
| deletionMethods = deletionMethods, |
| updateMethods = updateMethods, |
| transactionMethods = transactionMethods, |
| kotlinDefaultMethodDelegates = kotlinDefaultMethodDelegates, |
| constructorParamType = constructorParamType) |
| } |
| |
| private fun validateEmptyConstructor(constructors: List<XConstructorElement>) { |
| if (constructors.isNotEmpty() && constructors.all { it.parameters.isNotEmpty() }) { |
| context.logger.e(element, ProcessorErrors.daoMustHaveMatchingConstructor( |
| element.toString(), dbType.toString())) |
| } |
| } |
| } |