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.annotationwrapper 17 18 import androidx.appsearch.compiler.ProcessingException 19 import com.squareup.javapoet.ClassName 20 import com.squareup.javapoet.TypeName 21 import javax.lang.model.element.ElementKind 22 import javax.lang.model.element.ExecutableElement 23 import javax.lang.model.element.Modifier 24 import javax.lang.model.element.TypeElement 25 import javax.lang.model.type.TypeMirror 26 27 /** 28 * Represents a class that can convert between some custom type and a property's actual type. 29 * 30 * @see androidx.appsearch.app.StringSerializer 31 * @see androidx.appsearch.app.LongSerializer 32 */ 33 data class SerializerClass( 34 /** The kind of serializer. */ 35 val kind: Kind, 36 37 /** The serializer class element. */ 38 val element: TypeElement, 39 40 /** The zero-param constructor. Present on every serializer class. */ 41 val defaultConstructor: ExecutableElement, 42 43 /** The custom type that can be serialized using the serializer class. */ 44 val customType: TypeMirror, 45 ) { 46 enum class Kind( 47 /** 48 * The actual type of the corresponding property within a `GenericDocument`. 49 * 50 * For example, a [.STRING_SERIALIZER] may only be used with a `@Document.StringProperty` 51 * which, in turn, boils down to a [String] within a `GenericDocument`. 52 */ 53 // TypeName is an immutable 3P type 54 val actualTypeInGenericDoc: TypeName 55 ) { 56 STRING_SERIALIZER(actualTypeInGenericDoc = ClassName.get(String::class.java)), 57 LONG_SERIALIZER(actualTypeInGenericDoc = TypeName.LONG), 58 } 59 60 companion object { 61 /** 62 * Creates a serializer class given its [TypeElement]. 63 * 64 * @throws ProcessingException If the `clazz` does not have a zero-param constructor. 65 */ 66 @Throws(ProcessingException::class) createnull67 fun create(clazz: TypeElement, kind: Kind): SerializerClass { 68 val deserializeMethod: ExecutableElement = findDeserializeMethod(clazz, kind) 69 return SerializerClass( 70 kind = kind, 71 element = clazz, 72 defaultConstructor = findDefaultConstructor(clazz), 73 customType = deserializeMethod.returnType, 74 ) 75 } 76 77 /** 78 * Returns the zero-param constructor in the `clazz`. 79 * 80 * @throws ProcessingException If no such constructor exists or it's private. 81 */ 82 @Throws(ProcessingException::class) findDefaultConstructornull83 private fun findDefaultConstructor(clazz: TypeElement): ExecutableElement { 84 val constructor: ExecutableElement = 85 clazz.enclosedElements 86 .stream() 87 .filter { it.kind == ElementKind.CONSTRUCTOR } 88 .map { it as ExecutableElement } 89 .filter { it.parameters.isEmpty() } 90 .findFirst() 91 .orElseThrow<ProcessingException> { 92 ProcessingException( 93 "Serializer ${clazz.qualifiedName} must have a zero-param " + 94 "constructor", 95 clazz 96 ) 97 } 98 if (constructor.modifiers.contains(Modifier.PRIVATE)) { 99 throw ProcessingException( 100 "The zero-param constructor of serializer ${clazz.qualifiedName} must not " + 101 "be private", 102 constructor 103 ) 104 } 105 return constructor 106 } 107 108 /** Returns the `T deserialize(PropertyType)` method. */ findDeserializeMethodnull109 private fun findDeserializeMethod(clazz: TypeElement, kind: Kind): ExecutableElement { 110 return clazz.enclosedElements 111 .stream() 112 .filter { it.kind == ElementKind.METHOD } 113 .map { it as ExecutableElement } 114 // The type-system enforces there is one method satisfying these constraints 115 .filter { 116 it.simpleName.contentEquals("deserialize") && 117 !it.modifiers.contains(Modifier.STATIC) && 118 // Direct equality check with the param's type should be sufficient. 119 // Don't need to allow for subtypes because mActualTypeInGenericDoc can 120 // only be a primitive type or String which is a final class. 121 hasSingleParamOfExactType(it, kind.actualTypeInGenericDoc) 122 } 123 .findFirst() 124 // Should never throw because param type is enforced by the type-system 125 .get() 126 } 127 hasSingleParamOfExactTypenull128 private fun hasSingleParamOfExactType( 129 method: ExecutableElement, 130 expectedType: TypeName 131 ): Boolean { 132 if (method.parameters.size != 1) { 133 return false 134 } 135 val firstParamType = TypeName.get(method.parameters.first().asType()) 136 return firstParamType == expectedType 137 } 138 } 139 } 140