1 /* <lambda>null2 * 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 androidx.appsearch.compiler.annotationwrapper.DataPropertyAnnotation 19 import androidx.appsearch.compiler.annotationwrapper.DataPropertyAnnotation.Companion.tryParse 20 import androidx.appsearch.compiler.annotationwrapper.LongPropertyAnnotation 21 import androidx.appsearch.compiler.annotationwrapper.MetadataPropertyAnnotation 22 import androidx.appsearch.compiler.annotationwrapper.PropertyAnnotation 23 import androidx.appsearch.compiler.annotationwrapper.SerializerClass 24 import androidx.appsearch.compiler.annotationwrapper.StringPropertyAnnotation 25 import java.util.Locale 26 import java.util.stream.Collectors 27 import javax.annotation.processing.ProcessingEnvironment 28 import javax.lang.model.element.AnnotationMirror 29 import javax.lang.model.element.Element 30 import javax.lang.model.element.ElementKind 31 import javax.lang.model.element.ExecutableElement 32 import javax.lang.model.type.ArrayType 33 import javax.lang.model.type.DeclaredType 34 import javax.lang.model.type.TypeKind 35 import javax.lang.model.type.TypeMirror 36 37 /** 38 * A getter or field annotated with a [PropertyAnnotation] annotation. For example, 39 * <pre> 40 * @Document("MyEntity") 41 * public final class Entity { 42 * @Document.Id 43 * public String mId; 44 * // ^^^ 45 * 46 * // OR 47 * 48 * @Document.StringProperty 49 * public String getName() {...} 50 * // ^^^^^^^ 51 * } 52 * </pre> 53 */ 54 data class AnnotatedGetterOrField( 55 /** The annotation that the getter or field is annotated with. */ 56 val annotation: PropertyAnnotation, 57 58 /** The annotated getter or field. */ 59 val element: Element, 60 61 /** 62 * The type-category of the getter or field. 63 * 64 * Note: `byte[]` as treated specially as documented in [ElementTypeCategory]. 65 */ 66 val elementTypeCategory: ElementTypeCategory, 67 68 /** 69 * The field/getter's return type if non-repeated, else the underlying element type. 70 * 71 * For example, `String` for a field `String mName` and `int` for a field `int[] mNums`. 72 * 73 * The one exception to this is `byte[]` where: 74 * <pre> 75 * @BytesProperty bytes[] mField; // componentType: byte[] 76 * @BytesProperty bytes[][] mField; // componentType: byte[] 77 * @BytesProperty List<bytes[]> mField; // componentType: byte[] 78 * </pre> 79 */ 80 val componentType: TypeMirror, 81 82 /** 83 * The normalized/stemmed [.getJvmName]. 84 * 85 * For example, 86 * <pre> 87 * getName -> name 88 * mName -> name 89 * _name -> name 90 * name_ -> name 91 * isAwesome -> awesome 92 * </pre> 93 */ 94 val normalizedName: String, 95 ) { 96 /** 97 * Specifies whether the getter/field is assigned a collection or array or a single type. 98 * 99 * Note: `byte[]` are treated specially such that 100 * * `byte[]` is a primitive in icing and is treated as [.SINGLE]. 101 * * `Collection<byte[]>` is treated as a [.COLLECTION]. 102 * * `byte[][]` is treated as an [.ARRAY]. 103 * 104 * The boxed [Byte] type is not supported by the AppSearch compiler at all. 105 */ 106 enum class ElementTypeCategory { 107 SINGLE, 108 COLLECTION, 109 ARRAY 110 } 111 112 companion object { 113 /** 114 * Creates a [AnnotatedGetterOrField] if the element is annotated with some 115 * [PropertyAnnotation]. Otherwise returns null. 116 */ 117 @Throws(ProcessingException::class) 118 @JvmStatic 119 fun tryCreateFor(element: Element, env: ProcessingEnvironment): AnnotatedGetterOrField? { 120 val annotation: AnnotationMirror = getSingleAppSearchAnnotation(element) ?: return null 121 122 val metadataPropertyAnnotation = MetadataPropertyAnnotation.tryParse(annotation) 123 if (metadataPropertyAnnotation != null) { 124 return create(metadataPropertyAnnotation, element, env) 125 } 126 127 val normalizedName: String = inferNormalizedName(element, env) // e.g. mField -> field 128 val dataPropertyAnnotation = 129 tryParse(annotation, /* defaultName= */ normalizedName, IntrospectionHelper(env)) 130 if (dataPropertyAnnotation != null) { 131 return create(dataPropertyAnnotation, element, env) 132 } 133 134 return null 135 } 136 137 /** 138 * Creates a [AnnotatedGetterOrField] for a `getterOrField` annotated with the specified 139 * `annotation`. 140 */ 141 @Throws(ProcessingException::class) 142 private fun create( 143 annotation: PropertyAnnotation, 144 getterOrField: Element, 145 env: ProcessingEnvironment 146 ): AnnotatedGetterOrField { 147 requireIsGetterOrField(getterOrField) 148 149 val typeCategory: ElementTypeCategory = inferTypeCategory(getterOrField, env) 150 val annotatedGetterOrField = 151 AnnotatedGetterOrField( 152 annotation = annotation, 153 element = getterOrField, 154 elementTypeCategory = typeCategory, 155 componentType = inferComponentType(getterOrField, typeCategory), 156 normalizedName = inferNormalizedName(getterOrField, env), 157 ) 158 159 requireTypeMatchesAnnotation(annotatedGetterOrField, env) 160 161 return annotatedGetterOrField 162 } 163 164 @Throws(ProcessingException::class) 165 private fun requireIsGetterOrField(element: Element) { 166 when (element.kind) { 167 ElementKind.FIELD -> return 168 ElementKind.METHOD -> { 169 val method = element as ExecutableElement 170 val errors = IntrospectionHelper.validateIsGetter(method) 171 if (errors.isNotEmpty()) { 172 val err = 173 ProcessingException( 174 "Failed to find a suitable getter for element " + 175 "\"${method.simpleName}\"", 176 method 177 ) 178 err.addWarnings(errors) 179 throw err 180 } 181 } 182 else -> {} 183 } 184 } 185 186 /** 187 * Infers whether the getter/field returns a collection or array or neither. 188 * 189 * Note: `byte[]` are treated specially as documented in [ElementTypeCategory]. 190 */ 191 private fun inferTypeCategory( 192 getterOrField: Element, 193 env: ProcessingEnvironment 194 ): ElementTypeCategory { 195 val jvmType = IntrospectionHelper.getPropertyType(getterOrField) 196 val typeUtils = env.typeUtils 197 val helper = IntrospectionHelper(env) 198 return if (typeUtils.isAssignable(typeUtils.erasure(jvmType), helper.collectionType)) { 199 ElementTypeCategory.COLLECTION 200 } else if ( 201 jvmType.kind == TypeKind.ARRAY && 202 !typeUtils.isSameType(jvmType, helper.bytePrimitiveArrayType) && 203 !typeUtils.isSameType(jvmType, helper.byteBoxArrayType) 204 ) { 205 // byte[] has a native representation in Icing and should be considered a 206 // primitive by itself. 207 // 208 // byte[][], however, is considered repeated (ARRAY). 209 ElementTypeCategory.ARRAY 210 } else { 211 ElementTypeCategory.SINGLE 212 } 213 } 214 215 /** 216 * Infers the getter/field's return type if non-repeated, else the underlying element type. 217 * 218 * For example, `String mField -> String` and `List<String> mField -> String`. 219 */ 220 @Throws(ProcessingException::class) 221 private fun inferComponentType( 222 getterOrField: Element, 223 typeCategory: ElementTypeCategory 224 ): TypeMirror { 225 val jvmType = IntrospectionHelper.getPropertyType(getterOrField) 226 return when (typeCategory) { 227 ElementTypeCategory.SINGLE -> jvmType 228 ElementTypeCategory.COLLECTION -> { 229 // e.g. List<T> 230 // ^ 231 val typeArguments = (jvmType as DeclaredType).typeArguments 232 if (typeArguments.isEmpty()) { 233 throw ProcessingException( 234 "Property is repeated but has no generic rawType", 235 getterOrField 236 ) 237 } 238 typeArguments.first() 239 } 240 ElementTypeCategory.ARRAY -> (jvmType as ArrayType).componentType 241 } 242 } 243 244 private fun inferNormalizedName(element: Element, env: ProcessingEnvironment): String { 245 return if (element.kind == ElementKind.METHOD) { 246 inferNormalizedMethodName(element, env) 247 } else { 248 inferNormalizedFieldName(element) 249 } 250 } 251 252 /** 253 * Makes sure the getter/field's JVM type matches the type expected by the 254 * [PropertyAnnotation]. 255 */ 256 @Throws(ProcessingException::class) 257 private fun requireTypeMatchesAnnotation( 258 getterOrField: AnnotatedGetterOrField, 259 env: ProcessingEnvironment 260 ) { 261 val annotation = getterOrField.annotation 262 when (annotation.propertyKind) { 263 PropertyAnnotation.Kind.METADATA_PROPERTY -> 264 requireTypeMatchesMetadataPropertyAnnotation( 265 getterOrField, 266 annotation as MetadataPropertyAnnotation, 267 env 268 ) 269 PropertyAnnotation.Kind.DATA_PROPERTY -> 270 requireTypeMatchesDataPropertyAnnotation( 271 getterOrField, 272 annotation as DataPropertyAnnotation, 273 env 274 ) 275 } 276 } 277 278 /** 279 * Returns the only `@Document.*` annotation on the element e.g. `@Document.StringProperty`. 280 * 281 * Returns null if no such annotation exists on the element. 282 * 283 * @throws ProcessingException If the element is annotated with more than one of such 284 * annotations. 285 */ 286 @Throws(ProcessingException::class) 287 private fun getSingleAppSearchAnnotation(element: Element): AnnotationMirror? { 288 // @Document.* annotation 289 val annotations = 290 element.annotationMirrors 291 .stream() 292 .filter { 293 it.annotationType 294 .toString() 295 .startsWith( 296 IntrospectionHelper.DOCUMENT_ANNOTATION_CLASS.canonicalName() 297 ) 298 } 299 .toList() 300 if (annotations.isEmpty()) { 301 return null 302 } 303 if (annotations.size > 1) { 304 throw ProcessingException("Cannot use multiple @Document.* annotations", element) 305 } 306 return annotations.first() 307 } 308 309 private fun inferNormalizedMethodName(method: Element, env: ProcessingEnvironment): String { 310 val methodName = method.simpleName.toString() 311 val helper = IntrospectionHelper(env) 312 // String getName() -> name 313 if ( 314 methodName.startsWith("get") && 315 methodName.length > 3 && 316 Character.isUpperCase(methodName[3]) 317 ) { 318 return methodName.substring(3, 4).lowercase(Locale.getDefault()) + 319 methodName.substring(4) 320 } 321 322 // String isAwesome() -> awesome 323 if ( 324 helper.isFieldOfBooleanType(method) && 325 methodName.startsWith("is") && 326 methodName.length > 2 327 ) { 328 return methodName.substring(2, 3).lowercase(Locale.getDefault()) + 329 methodName.substring(3) 330 } 331 332 // Assume the method's name is the normalized name as well: String name() -> name 333 return methodName 334 } 335 336 private fun inferNormalizedFieldName(field: Element): String { 337 val fieldName = field.simpleName.toString() 338 if (fieldName.length < 2) { 339 return fieldName 340 } 341 342 // String mName -> name 343 if (fieldName[0] == 'm' && Character.isUpperCase(fieldName[1])) { 344 return fieldName.substring(1, 2).lowercase(Locale.getDefault()) + 345 fieldName.substring(2) 346 } 347 348 // String _name -> name 349 if (fieldName[0] == '_' && fieldName[1] != '_' && Character.isLowerCase(fieldName[1])) { 350 return fieldName.substring(1) 351 } 352 353 // String name_ -> name 354 if (fieldName[fieldName.length - 1] == '_' && fieldName[fieldName.length - 2] != '_') { 355 return fieldName.substring(0, fieldName.length - 1) 356 } 357 358 // Assume the field's name is the normalize name as well: String name -> name 359 return fieldName 360 } 361 362 /** 363 * Makes sure the getter/field's JVM type matches the type expected by the 364 * [MetadataPropertyAnnotation]. 365 * 366 * For example, fields annotated with `@Document.Score` must be of type `int` or [Integer]. 367 */ 368 @Throws(ProcessingException::class) 369 private fun requireTypeMatchesMetadataPropertyAnnotation( 370 getterOrField: AnnotatedGetterOrField, 371 annotation: MetadataPropertyAnnotation, 372 env: ProcessingEnvironment 373 ) { 374 val helper = IntrospectionHelper(env) 375 when (annotation) { 376 MetadataPropertyAnnotation.ID, 377 MetadataPropertyAnnotation.NAMESPACE -> 378 requireTypeIsOneOf( 379 getterOrField, 380 listOf(helper.stringType), 381 env, 382 allowRepeated = false 383 ) 384 MetadataPropertyAnnotation.TTL_MILLIS, 385 MetadataPropertyAnnotation.CREATION_TIMESTAMP_MILLIS -> 386 requireTypeIsOneOf( 387 getterOrField, 388 listOf( 389 helper.longPrimitiveType, 390 helper.intPrimitiveType, 391 helper.longBoxType, 392 helper.integerBoxType 393 ), 394 env, 395 allowRepeated = false 396 ) 397 MetadataPropertyAnnotation.SCORE -> 398 requireTypeIsOneOf( 399 getterOrField, 400 listOf(helper.intPrimitiveType, helper.integerBoxType), 401 env, 402 allowRepeated = false 403 ) 404 } 405 } 406 407 /** 408 * Makes sure the getter/field's JVM type matches the type expected by the 409 * [DataPropertyAnnotation]. 410 * 411 * For example, fields annotated with [StringPropertyAnnotation] must be of type [String] or 412 * a collection or array of [String]s. 413 */ 414 @Throws(ProcessingException::class) 415 private fun requireTypeMatchesDataPropertyAnnotation( 416 getterOrField: AnnotatedGetterOrField, 417 annotation: DataPropertyAnnotation, 418 env: ProcessingEnvironment 419 ) { 420 val helper = IntrospectionHelper(env) 421 when (annotation.dataPropertyKind) { 422 DataPropertyAnnotation.Kind.STRING_PROPERTY -> { 423 val stringSerializer = (annotation as StringPropertyAnnotation).customSerializer 424 if (stringSerializer != null) { 425 requireComponentTypeMatchesWithSerializer( 426 getterOrField, 427 stringSerializer, 428 env 429 ) 430 } else { 431 requireTypeIsOneOf( 432 getterOrField, 433 listOf(helper.stringType), 434 env, 435 allowRepeated = true 436 ) 437 } 438 } 439 DataPropertyAnnotation.Kind.DOCUMENT_PROPERTY -> 440 requireTypeIsSomeDocumentClass(getterOrField, env) 441 DataPropertyAnnotation.Kind.LONG_PROPERTY -> { 442 val longSerializer = (annotation as LongPropertyAnnotation).customSerializer 443 if (longSerializer != null) { 444 requireComponentTypeMatchesWithSerializer( 445 getterOrField, 446 longSerializer, 447 env 448 ) 449 } else { 450 requireTypeIsOneOf( 451 getterOrField, 452 listOf( 453 helper.longPrimitiveType, 454 helper.intPrimitiveType, 455 helper.longBoxType, 456 helper.integerBoxType 457 ), 458 env, 459 allowRepeated = true 460 ) 461 } 462 } 463 DataPropertyAnnotation.Kind.DOUBLE_PROPERTY -> 464 requireTypeIsOneOf( 465 getterOrField, 466 listOf( 467 helper.doublePrimitiveType, 468 helper.floatPrimitiveType, 469 helper.doubleBoxType, 470 helper.floatBoxType 471 ), 472 env, 473 allowRepeated = true 474 ) 475 DataPropertyAnnotation.Kind.BOOLEAN_PROPERTY -> 476 requireTypeIsOneOf( 477 getterOrField, 478 listOf(helper.booleanPrimitiveType, helper.booleanBoxType), 479 env, 480 allowRepeated = true 481 ) 482 DataPropertyAnnotation.Kind.BYTES_PROPERTY -> 483 requireTypeIsOneOf( 484 getterOrField, 485 listOf(helper.bytePrimitiveArrayType), 486 env, 487 allowRepeated = true 488 ) 489 DataPropertyAnnotation.Kind.EMBEDDING_PROPERTY -> 490 requireTypeIsOneOf( 491 getterOrField, 492 listOf(helper.embeddingType), 493 env, 494 allowRepeated = true 495 ) 496 DataPropertyAnnotation.Kind.BLOB_HANDLE_PROPERTY -> 497 requireTypeIsOneOf( 498 getterOrField, 499 listOf(helper.blobHandleType), 500 env, 501 allowRepeated = true 502 ) 503 } 504 } 505 506 /** 507 * Makes sure the getter/field's type is one of the expected types. 508 * 509 * If `allowRepeated` is true, also allows the getter/field's type to be an array or 510 * collection of any of the expected types. 511 */ 512 @Throws(ProcessingException::class) 513 private fun requireTypeIsOneOf( 514 getterOrField: AnnotatedGetterOrField, 515 expectedTypes: Collection<TypeMirror>, 516 env: ProcessingEnvironment, 517 allowRepeated: Boolean 518 ) { 519 val typeUtils = env.typeUtils 520 val target = if (allowRepeated) getterOrField.componentType else getterOrField.jvmType 521 val isValid = 522 expectedTypes.stream().anyMatch { expectedType: TypeMirror -> 523 typeUtils.isSameType(expectedType, target) 524 } 525 if (!isValid) { 526 val error = 527 ("@${getterOrField.annotation.className.simpleName()}" + 528 " must only be placed on a getter/field of type " + 529 (if (allowRepeated) "or array or collection of " else "") + 530 expectedTypes 531 .stream() 532 .map(TypeMirror::toString) 533 .collect(Collectors.joining("|"))) 534 throw ProcessingException(error, getterOrField.element) 535 } 536 } 537 538 /** 539 * Makes sure the getter/field's component type is consistent with the serializer class. 540 * 541 * @throws ProcessingException If the getter/field is of a different type than what the 542 * serializer class serializes to/from. 543 */ 544 @Throws(ProcessingException::class) 545 private fun requireComponentTypeMatchesWithSerializer( 546 getterOrField: AnnotatedGetterOrField, 547 serializerClass: SerializerClass, 548 env: ProcessingEnvironment 549 ) { 550 // The component type must exactly match the type for which we have a serializer. 551 // Subtypes do not work e.g. 552 // @StringProperty(serializer = ParentSerializer.class) Child mField; 553 // because ParentSerializer.deserialize(String) would return a Parent, which we won't be 554 // able to assign to mField. 555 if ( 556 !env.typeUtils.isSameType(getterOrField.componentType, serializerClass.customType) 557 ) { 558 throw ProcessingException( 559 ("@%s with serializer = %s must only be placed on a getter/field of type or " + 560 "array or collection of %s") 561 .format( 562 getterOrField.annotation.className.simpleName(), 563 serializerClass.element.simpleName, 564 serializerClass.customType 565 ), 566 getterOrField.element 567 ) 568 } 569 } 570 571 /** 572 * Makes sure the getter/field is assigned a type annotated with `@Document`. 573 * 574 * Allows for arrays and collections of such a type as well. 575 */ 576 @Throws(ProcessingException::class) 577 private fun requireTypeIsSomeDocumentClass( 578 annotatedGetterOrField: AnnotatedGetterOrField, 579 env: ProcessingEnvironment 580 ) { 581 val componentType = annotatedGetterOrField.componentType 582 if (componentType.kind == TypeKind.DECLARED) { 583 val element = env.typeUtils.asElement(componentType) 584 if (IntrospectionHelper.getDocumentAnnotation(element) != null) { 585 return 586 } 587 } 588 throw ProcessingException( 589 "Invalid type for @DocumentProperty. Must be another class " + 590 "annotated with @Document (or collection or array of)", 591 annotatedGetterOrField.element 592 ) 593 } 594 } 595 596 /** The field/getter's return type. */ 597 val jvmType: TypeMirror 598 get() = 599 if (isGetter) { 600 (element as ExecutableElement).returnType 601 } else { 602 element.asType() 603 } 604 605 /** The getter/field's jvm name e.g. `mId` or `getName`. */ 606 val jvmName: String 607 get() = element.simpleName.toString() 608 609 /** Whether the [.getElement] is a getter. */ 610 val isGetter: Boolean 611 get() = element.kind == ElementKind.METHOD 612 613 /** Whether the [.getElement] is a field. */ 614 val isField: Boolean 615 get() = element.kind == ElementKind.FIELD 616 } 617