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