1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17@file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
18
19package androidx.room.processor
20
21import androidx.annotation.VisibleForTesting
22import androidx.room.Insert
23import androidx.room.OnConflictStrategy.IGNORE
24import androidx.room.OnConflictStrategy.REPLACE
25import androidx.room.vo.InsertionMethod
26import androidx.room.vo.InsertionMethod.Type
27import androidx.room.vo.ShortcutQueryParameter
28import com.google.auto.common.MoreTypes
29import com.squareup.javapoet.TypeName
30import javax.lang.model.element.ExecutableElement
31import javax.lang.model.type.DeclaredType
32import javax.lang.model.type.TypeKind
33import javax.lang.model.type.TypeKind.LONG
34import javax.lang.model.type.TypeKind.VOID
35import javax.lang.model.type.TypeMirror
36
37class InsertionMethodProcessor(baseContext: Context,
38                               val containing: DeclaredType,
39                               val executableElement: ExecutableElement) {
40    val context = baseContext.fork(executableElement)
41    fun process(): InsertionMethod {
42        val delegate = ShortcutMethodProcessor(context, containing, executableElement)
43        val annotation = delegate.extractAnnotation(Insert::class,
44                ProcessorErrors.MISSING_INSERT_ANNOTATION)
45
46        val onConflict = OnConflictProcessor.extractFrom(annotation)
47        context.checker.check(onConflict <= IGNORE && onConflict >= REPLACE,
48                executableElement, ProcessorErrors.INVALID_ON_CONFLICT_VALUE)
49
50        val returnType = delegate.extractReturnType()
51        val returnTypeName = TypeName.get(returnType)
52        context.checker.notUnbound(returnTypeName, executableElement,
53                ProcessorErrors.CANNOT_USE_UNBOUND_GENERICS_IN_INSERTION_METHODS)
54
55        val (entities, params) = delegate.extractParams(
56                missingParamError = ProcessorErrors
57                        .INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT
58        )
59
60        // TODO we can support more types
61        var insertionType = getInsertionType(returnType)
62        context.checker.check(insertionType != null, executableElement,
63                ProcessorErrors.INVALID_INSERTION_METHOD_RETURN_TYPE)
64
65        if (insertionType != null) {
66            val acceptable = acceptableTypes(params)
67            if (insertionType !in acceptable) {
68                context.logger.e(executableElement,
69                        ProcessorErrors.insertionMethodReturnTypeMismatch(
70                                insertionType.returnTypeName,
71                                acceptable.map { it.returnTypeName }))
72                // clear it, no reason to generate code for it.
73                insertionType = null
74            }
75        }
76        return InsertionMethod(
77                element = executableElement,
78                name = executableElement.simpleName.toString(),
79                returnType = returnType,
80                entities = entities,
81                parameters = params,
82                onConflict = onConflict,
83                insertionType = insertionType
84        )
85    }
86
87    @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
88    private fun getInsertionType(returnType: TypeMirror): InsertionMethod.Type? {
89        // TODO we need to support more types here.
90        fun isLongPrimitiveType(typeMirror: TypeMirror) = typeMirror.kind == LONG
91
92        fun isLongBoxType(typeMirror: TypeMirror) =
93                MoreTypes.isType(typeMirror) &&
94                        MoreTypes.isTypeOf(java.lang.Long::class.java, typeMirror)
95
96        fun isLongType(typeMirror: TypeMirror) =
97                isLongPrimitiveType(typeMirror) || isLongBoxType(typeMirror)
98
99        return if (returnType.kind == VOID) {
100            Type.INSERT_VOID
101        } else if (returnType.kind == TypeKind.ARRAY) {
102            val arrayType = MoreTypes.asArray(returnType)
103            val param = arrayType.componentType
104            if (isLongPrimitiveType(param)) {
105                Type.INSERT_ID_ARRAY
106            } else if (isLongBoxType(param)) {
107                Type.INSERT_ID_ARRAY_BOX
108            } else {
109                null
110            }
111        } else if (MoreTypes.isType(returnType)
112                && MoreTypes.isTypeOf(List::class.java, returnType)) {
113            val declared = MoreTypes.asDeclared(returnType)
114            val param = declared.typeArguments.first()
115            if (isLongBoxType(param)) {
116                Type.INSERT_ID_LIST
117            } else {
118                null
119            }
120        } else if (isLongType(returnType)) {
121            Type.INSERT_SINGLE_ID
122        } else {
123            null
124        }
125    }
126
127    companion object {
128        @VisibleForTesting
129        val VOID_SET by lazy { setOf(Type.INSERT_VOID) }
130        @VisibleForTesting
131        val SINGLE_ITEM_SET by lazy { setOf(Type.INSERT_VOID, Type.INSERT_SINGLE_ID) }
132        @VisibleForTesting
133        val MULTIPLE_ITEM_SET by lazy {
134            setOf(Type.INSERT_VOID, Type.INSERT_ID_ARRAY, Type.INSERT_ID_ARRAY_BOX,
135                    Type.INSERT_ID_LIST)
136        }
137        fun acceptableTypes(params: List<ShortcutQueryParameter>): Set<InsertionMethod.Type> {
138            if (params.isEmpty()) {
139                return VOID_SET
140            }
141            if (params.size > 1) {
142                return VOID_SET
143            }
144            if (params.first().isMultiple) {
145                return MULTIPLE_ITEM_SET
146            } else {
147                return SINGLE_ITEM_SET
148            }
149        }
150    }
151}
152