1 /*
<lambda>null2  * Copyright 2018 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 androidx.appsearch.compiler.AnnotatedGetterOrField.Companion.tryCreateFor
19 import androidx.appsearch.compiler.annotationwrapper.DataPropertyAnnotation
20 import androidx.appsearch.compiler.annotationwrapper.DocumentPropertyAnnotation
21 import androidx.appsearch.compiler.annotationwrapper.PropertyAnnotation
22 import com.google.auto.common.MoreTypes
23 import com.google.auto.value.AutoValue
24 import com.squareup.javapoet.ClassName
25 import java.util.ArrayDeque
26 import java.util.Deque
27 import java.util.WeakHashMap
28 import java.util.stream.Collectors
29 import java.util.stream.Stream
30 import javax.annotation.processing.ProcessingEnvironment
31 import javax.lang.model.element.AnnotationMirror
32 import javax.lang.model.element.Element
33 import javax.lang.model.element.ElementKind
34 import javax.lang.model.element.ExecutableElement
35 import javax.lang.model.element.Modifier
36 import javax.lang.model.element.TypeElement
37 import javax.lang.model.type.ArrayType
38 import javax.lang.model.type.DeclaredType
39 import javax.lang.model.type.ExecutableType
40 import javax.lang.model.type.TypeKind
41 import javax.lang.model.type.TypeMirror
42 import javax.lang.model.util.Elements
43 import javax.lang.model.util.Types
44 import kotlin.metadata.isNullable
45 import kotlin.metadata.jvm.KotlinClassMetadata
46 
47 /** Utilities for working with data structures representing parsed Java code. */
48 class IntrospectionHelper internal constructor(private val env: ProcessingEnvironment) {
49     private val typeUtils: Types = env.typeUtils
50     private val elementUtils: Elements = env.elementUtils
51 
52     // Non-boxable objects
53     val blobHandleType: TypeMirror =
54         elementUtils.getTypeElement(APPSEARCH_BLOB_HANDLE_CLASS.canonicalName()).asType()
55     val collectionType: TypeMirror =
56         elementUtils.getTypeElement(java.util.Collection::class.java.name).asType()
57     val embeddingType: TypeMirror =
58         elementUtils.getTypeElement(EMBEDDING_VECTOR_CLASS.canonicalName()).asType()
59     val genericDocumentType: TypeMirror =
60         elementUtils.getTypeElement(GENERIC_DOCUMENT_CLASS.canonicalName()).asType()
61     val listType: TypeMirror = elementUtils.getTypeElement(java.util.List::class.java.name).asType()
62     @JvmField
63     val stringType: TypeMirror =
64         elementUtils.getTypeElement(java.lang.String::class.java.name).asType()
65 
66     // Boxable objects
67     val booleanBoxType: TypeMirror =
68         elementUtils.getTypeElement(java.lang.Boolean::class.java.name).asType()
69     val byteBoxType: TypeMirror =
70         elementUtils.getTypeElement(java.lang.Byte::class.java.name).asType()
71     val byteBoxArrayType: TypeMirror = typeUtils.getArrayType(byteBoxType)
72     val doubleBoxType: TypeMirror =
73         elementUtils.getTypeElement(java.lang.Double::class.java.name).asType()
74     val floatBoxType: TypeMirror =
75         elementUtils.getTypeElement(java.lang.Float::class.java.name).asType()
76     val integerBoxType: TypeMirror =
77         elementUtils.getTypeElement(java.lang.Integer::class.java.name).asType()
78     val longBoxType: TypeMirror =
79         elementUtils.getTypeElement(java.lang.Long::class.java.name).asType()
80 
81     // Primitive versions of boxable objects
82     @JvmField val booleanPrimitiveType: TypeMirror = typeUtils.unboxedType(booleanBoxType)
83     val bytePrimitiveType: TypeMirror = typeUtils.unboxedType(byteBoxType)
84     @JvmField val bytePrimitiveArrayType: TypeMirror = typeUtils.getArrayType(bytePrimitiveType)
85     @JvmField val doublePrimitiveType: TypeMirror = typeUtils.unboxedType(doubleBoxType)
86     val floatPrimitiveType: TypeMirror = typeUtils.unboxedType(floatBoxType)
87     @JvmField val intPrimitiveType: TypeMirror = typeUtils.unboxedType(integerBoxType)
88     @JvmField val longPrimitiveType: TypeMirror = typeUtils.unboxedType(longBoxType)
89 
90     private val allMethodsCache = WeakHashMap<TypeElement, LinkedHashSet<ExecutableElement>>()
91 
92     companion object {
93         const val GEN_CLASS_PREFIX: String = "$\$__AppSearch__"
94         const val APPSEARCH_PKG: String = "androidx.appsearch.app"
95 
96         @JvmField
97         val APPSEARCH_SCHEMA_CLASS: ClassName = ClassName.get(APPSEARCH_PKG, "AppSearchSchema")
98 
99         @JvmField
100         val PROPERTY_CONFIG_CLASS: ClassName = APPSEARCH_SCHEMA_CLASS.nestedClass("PropertyConfig")
101 
102         const val APPSEARCH_EXCEPTION_PKG: String = "androidx.appsearch.exceptions"
103 
104         @JvmField
105         val APPSEARCH_EXCEPTION_CLASS: ClassName =
106             ClassName.get(APPSEARCH_EXCEPTION_PKG, "AppSearchException")
107 
108         const val APPSEARCH_ANNOTATION_PKG: String = "androidx.appsearch.annotation"
109 
110         const val DOCUMENT_ANNOTATION_SIMPLE_CLASS_NAME: String = "Document"
111 
112         @JvmField
113         val DOCUMENT_ANNOTATION_CLASS: ClassName =
114             ClassName.get(APPSEARCH_ANNOTATION_PKG, DOCUMENT_ANNOTATION_SIMPLE_CLASS_NAME)
115 
116         @JvmField
117         val GENERIC_DOCUMENT_CLASS: ClassName = ClassName.get(APPSEARCH_PKG, "GenericDocument")
118 
119         val EMBEDDING_VECTOR_CLASS: ClassName = ClassName.get(APPSEARCH_PKG, "EmbeddingVector")
120 
121         val APPSEARCH_BLOB_HANDLE_CLASS: ClassName =
122             ClassName.get(APPSEARCH_PKG, "AppSearchBlobHandle")
123 
124         val BUILDER_PRODUCER_CLASS: ClassName =
125             DOCUMENT_ANNOTATION_CLASS.nestedClass("BuilderProducer")
126 
127         @JvmField
128         val DOCUMENT_CLASS_FACTORY_CLASS: ClassName =
129             ClassName.get(APPSEARCH_PKG, "DocumentClassFactory")
130 
131         @JvmField
132         val RESTRICT_TO_ANNOTATION_CLASS: ClassName =
133             ClassName.get("androidx.annotation", "RestrictTo")
134 
135         @JvmField
136         val RESTRICT_TO_SCOPE_CLASS: ClassName = RESTRICT_TO_ANNOTATION_CLASS.nestedClass("Scope")
137 
138         @JvmField
139         val DOCUMENT_CLASS_MAPPING_CONTEXT_CLASS: ClassName =
140             ClassName.get(APPSEARCH_PKG, "DocumentClassMappingContext")
141 
142         /**
143          * Returns `androidx.appsearch.annotation.Document` annotation element from the input
144          * element's annotations. Returns null if no such annotation is found.
145          */
146         @JvmStatic
147         fun getDocumentAnnotation(element: Element): AnnotationMirror? {
148             val annotations: List<AnnotationMirror> =
149                 getAnnotations(element, DOCUMENT_ANNOTATION_CLASS)
150             return annotations.firstOrNull()
151         }
152 
153         /**
154          * Returns a list of annotations of a given kind from the input element's annotations,
155          * specified by the annotation's class name. Returns null if no annotation of such kind is
156          * found.
157          */
158         fun getAnnotations(element: Element, className: ClassName): List<AnnotationMirror> {
159             return element.annotationMirrors
160                 .stream()
161                 .filter { it.annotationType.toString() == className.canonicalName() }
162                 .toList()
163         }
164 
165         /**
166          * Returns the property type of the given property. Properties are represented by an
167          * annotated Java element that is either a Java field or a getter method.
168          */
169         fun getPropertyType(property: Element): TypeMirror {
170             var propertyType = property.asType()
171             if (property.kind == ElementKind.METHOD) {
172                 propertyType = (propertyType as ExecutableType).returnType
173             }
174             return propertyType
175         }
176 
177         /**
178          * Creates the name of output class. $$__AppSearch__Foo for Foo, $$__AppSearch__Foo$$__Bar
179          * for inner class Foo.Bar.
180          */
181         @JvmStatic
182         fun getDocumentClassFactoryForClass(pkg: String, className: String): ClassName {
183             val genClassName: String = GEN_CLASS_PREFIX + className.replace(".", "$\$__")
184             return ClassName.get(pkg, genClassName)
185         }
186 
187         /**
188          * Creates the name of output class. $$__AppSearch__Foo for Foo, $$__AppSearch__Foo$$__Bar
189          * for inner class Foo.Bar.
190          */
191         @JvmStatic
192         fun getDocumentClassFactoryForClass(clazz: ClassName): ClassName {
193             val className = clazz.canonicalName().substring(clazz.packageName().length + 1)
194             return getDocumentClassFactoryForClass(clazz.packageName(), className)
195         }
196 
197         /**
198          * Get a list of super classes of element annotated with @Document, in order starting with
199          * the class at the top of the hierarchy and descending down the class hierarchy. Note that
200          * this ordering is important because super classes must appear first in the list than child
201          * classes to make property overrides work.
202          */
203         @Throws(ProcessingException::class)
204         @JvmStatic
205         fun generateClassHierarchy(element: TypeElement): List<TypeElement> {
206             val hierarchy: Deque<TypeElement> = ArrayDeque<TypeElement>()
207             if (element.getAnnotation(AutoValue::class.java) != null) {
208                 // We don't allow classes annotated with both Document and AutoValue to extend
209                 // classes.
210                 // Because of how AutoValue is set up, there is no way to add a constructor to
211                 // populate fields of super classes.
212                 // There should just be the generated class and the original annotated class
213                 val superClass = MoreTypes.asTypeElement(element.superclass)
214                 if (
215                     !superClass.qualifiedName.contentEquals(
216                         java.lang.Object::class.java.canonicalName
217                     )
218                 ) {
219                     throw ProcessingException(
220                         "A class annotated with AutoValue and Document cannot have a superclass",
221                         element
222                     )
223                 }
224                 hierarchy.add(element)
225             } else {
226                 val visited = mutableSetOf<TypeElement>()
227                 generateClassHierarchyHelper(element, element, hierarchy, visited)
228             }
229             return hierarchy.toList()
230         }
231 
232         /**
233          * Checks if a method is a valid getter and returns any errors.
234          *
235          * Returns an empty list if no errors i.e. the method is a valid getter.
236          */
237         fun validateIsGetter(method: ExecutableElement): MutableList<ProcessingException> {
238             val errors = mutableListOf<ProcessingException>()
239             if (method.parameters.isNotEmpty()) {
240                 errors.add(
241                     ProcessingException("Getter cannot be used: should take no parameters", method)
242                 )
243             }
244             if (method.modifiers.contains(Modifier.PRIVATE)) {
245                 errors.add(ProcessingException("Getter cannot be used: private visibility", method))
246             }
247             if (method.modifiers.contains(Modifier.STATIC)) {
248                 errors.add(ProcessingException("Getter cannot be used: must not be static", method))
249             }
250             return errors
251         }
252 
253         @Throws(ProcessingException::class)
254         private fun generateClassHierarchyHelper(
255             leafElement: TypeElement,
256             currentClass: TypeElement,
257             hierarchy: Deque<TypeElement>,
258             visited: MutableSet<TypeElement>
259         ) {
260             if (
261                 currentClass.qualifiedName.contentEquals(java.lang.Object::class.java.canonicalName)
262             ) {
263                 return
264             }
265             // If you inherit from an AutoValue class, you have to implement the static methods.
266             // That defeats the purpose of AutoValue
267             if (currentClass.getAnnotation(AutoValue::class.java) != null) {
268                 throw ProcessingException(
269                     "A class annotated with Document cannot inherit from a class " +
270                         "annotated with AutoValue",
271                     leafElement
272                 )
273             }
274 
275             // It's possible to revisit the same interface more than once, so this check exists to
276             // catch that.
277             if (visited.contains(currentClass)) {
278                 return
279             }
280             visited.add(currentClass)
281 
282             if (getDocumentAnnotation(currentClass) != null) {
283                 hierarchy.addFirst(currentClass)
284             }
285             val superclass = currentClass.superclass
286             // If currentClass is an interface, then superclass could be NONE.
287             if (superclass.kind != TypeKind.NONE) {
288                 generateClassHierarchyHelper(
289                     leafElement,
290                     MoreTypes.asTypeElement(superclass),
291                     hierarchy,
292                     visited
293                 )
294             }
295             for (implementedInterface in currentClass.interfaces) {
296                 generateClassHierarchyHelper(
297                     leafElement,
298                     MoreTypes.asTypeElement(implementedInterface),
299                     hierarchy,
300                     visited
301                 )
302             }
303         }
304 
305         /**
306          * Determines if a field is from Kotlin and is NonNull by checking for a Metadata
307          * annotation.
308          */
309         @JvmStatic
310         fun isNonNullKotlinField(getterOrField: AnnotatedGetterOrField): Boolean {
311             val metadata =
312                 getterOrField.element.enclosingElement.getAnnotation(Metadata::class.java)
313             if (metadata != null) {
314                 val kotlinMetadata: KotlinClassMetadata = KotlinClassMetadata.readStrict(metadata)
315                 if (kotlinMetadata is KotlinClassMetadata.Class) {
316                     val kmClass = kotlinMetadata.kmClass
317                     val properties = kmClass.properties
318                     for (property in properties) {
319                         if (property.name == getterOrField.jvmName) {
320                             return !property.returnType.isNullable
321                         }
322                     }
323                 }
324             }
325             // It is not a kotlin property.
326             return false
327         }
328     }
329 
330     /**
331      * Returns the document property annotation that matches the given property name from a given
332      * class or interface element.
333      *
334      * Returns null if the property cannot be found in the class or interface, or if the property
335      * matching the property name is not a document property.
336      */
337     @Throws(ProcessingException::class)
338     fun getDocumentPropertyAnnotation(
339         clazz: TypeElement,
340         propertyName: String
341     ): DocumentPropertyAnnotation? {
342         for (enclosedElement in clazz.enclosedElements) {
343             val getterOrField = tryCreateFor(enclosedElement, env)
344             if (
345                 getterOrField == null ||
346                     getterOrField.annotation.propertyKind != PropertyAnnotation.Kind.DATA_PROPERTY
347             ) {
348                 continue
349             }
350             if (
351                 (getterOrField.annotation as DataPropertyAnnotation).dataPropertyKind ==
352                     DataPropertyAnnotation.Kind.DOCUMENT_PROPERTY
353             ) {
354                 val documentPropertyAnnotation =
355                     getterOrField.annotation as DocumentPropertyAnnotation
356                 if (documentPropertyAnnotation.name == propertyName) {
357                     return documentPropertyAnnotation
358                 }
359             }
360         }
361         return null
362     }
363 
364     /** Checks whether the property data type is one of the valid types. */
365     fun isFieldOfExactType(property: Element, vararg validTypes: TypeMirror): Boolean {
366         val propertyType: TypeMirror = getPropertyType(property)
367         for (validType in validTypes) {
368             if (propertyType.kind == TypeKind.ARRAY) {
369                 if (typeUtils.isSameType((propertyType as ArrayType).componentType, validType)) {
370                     return true
371                 }
372             } else if (typeUtils.isAssignable(typeUtils.erasure(propertyType), collectionType)) {
373                 if (
374                     typeUtils.isSameType(
375                         (propertyType as DeclaredType).typeArguments.first(),
376                         validType
377                     )
378                 ) {
379                     return true
380                 }
381             } else if (typeUtils.isSameType(propertyType, validType)) {
382                 return true
383             }
384         }
385         return false
386     }
387 
388     /** Checks whether the property data type is of boolean type. */
389     fun isFieldOfBooleanType(property: Element): Boolean {
390         return isFieldOfExactType(property, booleanBoxType, booleanPrimitiveType)
391     }
392 
393     /** Returns the annotation's params as a map. Includes the default values. */
394     fun getAnnotationParams(annotation: AnnotationMirror): Map<String, Any?> {
395         val values = env.elementUtils.getElementValuesWithDefaults(annotation)
396         val ret = mutableMapOf<String, Any?>()
397         for (entry in values.entries) {
398             val key = entry.key.simpleName.toString()
399             ret[key] = entry.value.value
400         }
401         return ret
402     }
403 
404     /**
405      * Returns all the methods within a class, whether inherited or declared directly.
406      *
407      * Caches results internally, so it is cheap to call subsequently for the same input.
408      */
409     fun getAllMethods(clazz: TypeElement): LinkedHashSet<ExecutableElement> {
410         return allMethodsCache.computeIfAbsent(clazz) { type: TypeElement ->
411             env.elementUtils
412                 .getAllMembers(type)
413                 .stream()
414                 .filter { it.kind == ElementKind.METHOD }
415                 .map { it as ExecutableElement }
416                 .collect(Collectors.toCollection { LinkedHashSet() })
417         }
418     }
419 
420     /** Whether a type is the same as `long[]`. */
421     fun isPrimitiveLongArray(type: TypeMirror): Boolean {
422         return isArrayOf(type, longPrimitiveType)
423     }
424 
425     /** Whether a type is the same as `double[]`. */
426     fun isPrimitiveDoubleArray(type: TypeMirror): Boolean {
427         return isArrayOf(type, doublePrimitiveType)
428     }
429 
430     /** Whether a type is the same as `boolean[]`. */
431     fun isPrimitiveBooleanArray(type: TypeMirror): Boolean {
432         return isArrayOf(type, booleanPrimitiveType)
433     }
434 
435     private fun isArrayOf(type: TypeMirror, arrayComponentType: TypeMirror): Boolean {
436         return typeUtils.isSameType(type, typeUtils.getArrayType(arrayComponentType))
437     }
438 
439     /**
440      * Same as [.validateIsGetter] but additionally verifies that the getter returns the specified
441      * type.
442      */
443     fun validateIsGetterThatReturns(
444         method: ExecutableElement,
445         expectedReturnType: TypeMirror
446     ): MutableList<ProcessingException> {
447         val errors: MutableList<ProcessingException> = validateIsGetter(method)
448         if (!typeUtils.isAssignable(method.returnType, expectedReturnType)) {
449             errors.add(
450                 ProcessingException(
451                     "Getter cannot be used: Does not return $expectedReturnType",
452                     method
453                 )
454             )
455         }
456         return errors
457     }
458 
459     /**
460      * A method's type and element (i.e. declaration).
461      *
462      * Note: The parameter and return types may differ between the type and the element. For
463      * example,
464      * <pre>
465      * public class StringSet implements Set<String> {...}
466      * </pre>
467      *
468      * Here, the type of `StringSet.add()` is `(String) -> boolean` and the element points to the
469      * generic declaration within `Set<T>` with a return type of `boolean` and a single parameter of
470      * type `T`.
471      */
472     class MethodTypeAndElement(val type: ExecutableType, val element: ExecutableElement)
473 
474     /**
475      * Returns a stream of all the methods (including inherited) within a [DeclaredType].
476      *
477      * Does not include constructors.
478      */
479     fun getAllMethods(type: DeclaredType): Stream<MethodTypeAndElement> {
480         return elementUtils
481             .getAllMembers(type.asElement() as TypeElement)
482             .stream()
483             .filter { it.kind == ElementKind.METHOD }
484             .map {
485                 MethodTypeAndElement(
486                     typeUtils.asMemberOf(type, it) as ExecutableType,
487                     it as ExecutableElement
488                 )
489             }
490     }
491 
492     /** Whether the method returns the specified type (or subtype). */
493     fun isReturnTypeMatching(method: ExecutableType, type: TypeMirror): Boolean {
494         return typeUtils.isAssignable(method.returnType, type)
495     }
496 
497     /**
498      * Returns a type that the source type should be casted to coerce it to the target type.
499      *
500      * Handles the following cases:
501      * <pre>
502      * long|Long -> int|Integer = (int) ...
503      * double|Double -> float|Float = (float) ...
504      * </pre>
505      *
506      * Returns null if no cast is necessary.
507      */
508     fun getNarrowingCastType(sourceType: TypeMirror, targetType: TypeMirror): TypeMirror? {
509         if (
510             typeUtils.isSameType(targetType, intPrimitiveType) ||
511                 typeUtils.isSameType(targetType, integerBoxType)
512         ) {
513             if (
514                 typeUtils.isSameType(sourceType, longPrimitiveType) ||
515                     typeUtils.isSameType(sourceType, longBoxType)
516             ) {
517                 return intPrimitiveType
518             }
519         }
520         if (
521             typeUtils.isSameType(targetType, floatPrimitiveType) ||
522                 typeUtils.isSameType(targetType, floatBoxType)
523         ) {
524             if (
525                 typeUtils.isSameType(sourceType, doublePrimitiveType) ||
526                     typeUtils.isSameType(sourceType, doubleBoxType)
527             ) {
528                 return floatPrimitiveType
529             }
530         }
531         return null
532     }
533 
534     /** Whether the element is a static method that returns the class it's enclosed within. */
535     fun isStaticFactoryMethod(element: Element): Boolean {
536         if (element.kind != ElementKind.METHOD || !element.modifiers.contains(Modifier.STATIC)) {
537             return false
538         }
539         val method = element as ExecutableElement
540         val enclosingType = method.enclosingElement.asType()
541         return typeUtils.isSameType(method.returnType, enclosingType)
542     }
543 }
544