1 /* 2 * Copyright 2023 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 package androidx.appsearch.compiler 17 18 import javax.lang.model.element.ElementKind 19 import javax.lang.model.element.ExecutableElement 20 import javax.lang.model.element.Modifier 21 import javax.lang.model.type.DeclaredType 22 23 /** 24 * A constructor or static method used to create a class annotated with `@Document` aka document 25 * class. 26 * 27 * Takes in N input params, each corresponding to a value for an [AnnotatedGetterOrField]. 28 * 29 * Moreover, may return the document class itself or a builder. All of the following are examples of 30 * valid creation methods: 31 * <pre> 32 * @Document 33 * class MyEntity { 34 * static MyEntity create(String id, String namespace, int someProp); 35 * // ^^^^^^ 36 * 37 * MyEntity() {...} 38 * // ^^^^^^^^ 39 * 40 * MyEntity(String id, String namespace, int someProp) {...} 41 * // ^^^^^^^^ 42 * 43 * @Document.BuilderProducer 44 * static Builder newBuilder() {...} 45 * // ^^^^^^^^^^ 46 * 47 * @Document.BuilderProducer 48 * static class Builder { 49 * Builder() {...} 50 * // ^^^^^^^ 51 * 52 * Builder(String id, String namespace, int someProp) {...} 53 * // ^^^^^^^ 54 * } 55 * } 56 * </pre> 57 */ 58 data class CreationMethod( 59 /** The constructor/static method element. */ 60 val element: ExecutableElement, 61 62 /** The [AnnotatedGetterOrField]s that each input param corresponds to (order sensitive). */ 63 val paramAssociations: List<AnnotatedGetterOrField>, 64 65 /** Whether the creation method returns the document class itself instead of a builder. */ 66 val returnsDocumentClass: Boolean, 67 ) { 68 companion object { 69 /** 70 * Infers which annotated getter/field each param corresponds to and creates a 71 * [CreationMethod]. 72 * 73 * @param method The creation method element. 74 * @param gettersAndFields The annotated getters/fields of the document class. 75 * @param returnsDocumentClass Whether the `method` returns the document class itself. If 76 * not, it is assumed that it returns a builder for the document class. 77 * @throws ProcessingException If the method is not invocable or the association for a param 78 * could not be deduced. 79 */ 80 @Throws(ProcessingException::class) inferParamAssociationsAndCreatenull81 fun inferParamAssociationsAndCreate( 82 method: ExecutableElement, 83 gettersAndFields: Collection<AnnotatedGetterOrField>, 84 returnsDocumentClass: Boolean 85 ): CreationMethod { 86 if (method.modifiers.contains(Modifier.PRIVATE)) { 87 throw ProcessingException( 88 ("Method cannot be used to create a " + 89 (if (returnsDocumentClass) "document class" else "builder") + 90 ": private visibility"), 91 method 92 ) 93 } 94 95 if ( 96 method.kind == ElementKind.CONSTRUCTOR && 97 method.enclosingElement.modifiers.contains(Modifier.ABSTRACT) 98 ) { 99 throw ProcessingException( 100 ("Method cannot be used to create a " + 101 (if (returnsDocumentClass) "document class" else "builder") + 102 ": abstract constructor"), 103 method 104 ) 105 } 106 107 val normalizedNameToGetterOrField = mutableMapOf<String, AnnotatedGetterOrField>() 108 for (getterOrField in gettersAndFields) { 109 normalizedNameToGetterOrField[getterOrField.normalizedName] = getterOrField 110 } 111 112 val paramAssociations = mutableListOf<AnnotatedGetterOrField>() 113 for (param in method.parameters) { 114 val paramName = param.simpleName.toString() 115 val correspondingGetterOrField: AnnotatedGetterOrField = 116 normalizedNameToGetterOrField[paramName] 117 ?: throw ProcessingException( 118 "Parameter \"$paramName\" is not an AppSearch parameter; " + 119 "don't know how to supply it.", 120 method 121 ) 122 paramAssociations.add(correspondingGetterOrField) 123 } 124 125 return CreationMethod(method, paramAssociations, returnsDocumentClass) 126 } 127 } 128 129 /** The enclosing class that the constructor/static method is a part of. */ 130 val enclosingClass: DeclaredType 131 get() = element.enclosingElement.asType() as DeclaredType 132 133 /** The static method's return type/constructor's enclosing class. */ 134 val returnType: DeclaredType 135 get() = 136 if (isConstructor) { 137 element.enclosingElement.asType() as DeclaredType 138 } else { 139 element.returnType as DeclaredType 140 } 141 142 /** The static method/constructor element's name. */ 143 val jvmName: String 144 get() = element.simpleName.toString() 145 146 /** Whether the creation method is a constructor. */ 147 val isConstructor: Boolean 148 get() = element.kind == ElementKind.CONSTRUCTOR 149 150 /** Whether the creation method returns a builder instead of the document class itself. */ 151 val returnsBuilder: Boolean 152 get() = !returnsDocumentClass 153 } 154