1 /* 2 * 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 static androidx.appsearch.compiler.IntrospectionHelper.generateClassHierarchy; 19 import static androidx.appsearch.compiler.IntrospectionHelper.getDocumentAnnotation; 20 21 import static java.util.Objects.requireNonNull; 22 import static java.util.stream.Collectors.groupingBy; 23 24 import androidx.annotation.RestrictTo; 25 import androidx.appsearch.compiler.annotationwrapper.DataPropertyAnnotation; 26 import androidx.appsearch.compiler.annotationwrapper.MetadataPropertyAnnotation; 27 import androidx.appsearch.compiler.annotationwrapper.PropertyAnnotation; 28 29 import org.jspecify.annotations.NonNull; 30 import org.jspecify.annotations.Nullable; 31 32 import java.util.Collection; 33 import java.util.HashMap; 34 import java.util.LinkedHashMap; 35 import java.util.LinkedHashSet; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Set; 39 import java.util.function.Predicate; 40 41 import javax.annotation.processing.ProcessingEnvironment; 42 import javax.lang.model.element.AnnotationMirror; 43 import javax.lang.model.element.Element; 44 import javax.lang.model.element.ExecutableElement; 45 import javax.lang.model.element.Modifier; 46 import javax.lang.model.element.TypeElement; 47 import javax.lang.model.util.Elements; 48 49 /** 50 * Processes @Document annotations. 51 * 52 * @see AnnotatedGetterAndFieldAccumulator for the DocumentModel's invariants with regards to its 53 * getter and field definitions. 54 * 55 * @exportToFramework:hide 56 */ 57 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) 58 class DocumentModel { 59 private static final String CLASS_SUFFIX = ".class"; 60 61 private final IntrospectionHelper mHelper; 62 63 private final Elements mElementUtil; 64 65 private final TypeElement mClass; 66 67 // The name of the original class annotated with @Document 68 private final String mQualifiedDocumentClassName; 69 70 private final String mSchemaName; 71 72 private final LinkedHashSet<TypeElement> mParentTypes; 73 74 private final LinkedHashSet<AnnotatedGetterOrField> mAnnotatedGettersAndFields; 75 76 private final @NonNull AnnotatedGetterOrField mIdAnnotatedGetterOrField; 77 78 private final @NonNull AnnotatedGetterOrField mNamespaceAnnotatedGetterOrField; 79 80 private final @NonNull Map<AnnotatedGetterOrField, PropertyAccessor> mAccessors; 81 82 private final @NonNull DocumentClassCreationInfo mDocumentClassCreationInfo; 83 DocumentModel( @onNull ProcessingEnvironment env, @NonNull TypeElement clazz, @Nullable TypeElement generatedAutoValueElement)84 private DocumentModel( 85 @NonNull ProcessingEnvironment env, 86 @NonNull TypeElement clazz, 87 @Nullable TypeElement generatedAutoValueElement) 88 throws ProcessingException { 89 if (clazz.getModifiers().contains(Modifier.PRIVATE)) { 90 throw new ProcessingException("@Document annotated class is private", clazz); 91 } 92 93 mHelper = new IntrospectionHelper(env); 94 mElementUtil = env.getElementUtils(); 95 mClass = clazz; 96 mQualifiedDocumentClassName = generatedAutoValueElement != null 97 ? generatedAutoValueElement.getQualifiedName().toString() 98 : clazz.getQualifiedName().toString(); 99 mParentTypes = getParentSchemaTypes(clazz); 100 101 List<TypeElement> classHierarchy = generateClassHierarchy(clazz); 102 mSchemaName = computeSchemaName(classHierarchy); 103 mAnnotatedGettersAndFields = scanAnnotatedGettersAndFields(classHierarchy, env); 104 105 requireNoDuplicateMetadataProperties(); 106 mIdAnnotatedGetterOrField = requireGetterOrFieldMatchingPredicate( 107 getterOrField -> getterOrField.getAnnotation() == MetadataPropertyAnnotation.ID, 108 /* errorMessage= */"All @Document classes must have exactly one field annotated " 109 + "with @Id"); 110 mNamespaceAnnotatedGetterOrField = requireGetterOrFieldMatchingPredicate( 111 getterOrField -> 112 getterOrField.getAnnotation() == MetadataPropertyAnnotation.NAMESPACE, 113 /* errorMessage= */"All @Document classes must have exactly one field annotated " 114 + "with @Namespace"); 115 116 LinkedHashSet<ExecutableElement> allMethods = mHelper.getAllMethods(clazz); 117 mAccessors = inferPropertyAccessors(mAnnotatedGettersAndFields, allMethods, mHelper); 118 mDocumentClassCreationInfo = 119 DocumentClassCreationInfo.infer(clazz, mAnnotatedGettersAndFields, mHelper); 120 } 121 scanAnnotatedGettersAndFields( @onNull List<TypeElement> hierarchy, @NonNull ProcessingEnvironment env)122 private static LinkedHashSet<AnnotatedGetterOrField> scanAnnotatedGettersAndFields( 123 @NonNull List<TypeElement> hierarchy, 124 @NonNull ProcessingEnvironment env) throws ProcessingException { 125 AnnotatedGetterAndFieldAccumulator accumulator = new AnnotatedGetterAndFieldAccumulator(); 126 for (TypeElement type : hierarchy) { 127 for (Element enclosedElement : type.getEnclosedElements()) { 128 AnnotatedGetterOrField getterOrField = 129 AnnotatedGetterOrField.tryCreateFor(enclosedElement, env); 130 if (getterOrField == null) { 131 continue; 132 } 133 accumulator.add(getterOrField); 134 } 135 } 136 return accumulator.getAccumulatedGettersAndFields(); 137 } 138 139 /** 140 * Makes sure {@link #mAnnotatedGettersAndFields} does not contain two getters/fields 141 * annotated with the same metadata annotation e.g. it doesn't make sense for a document to 142 * have two {@code @Document.Id}s. 143 */ requireNoDuplicateMetadataProperties()144 private void requireNoDuplicateMetadataProperties() throws ProcessingException { 145 Map<MetadataPropertyAnnotation, List<AnnotatedGetterOrField>> annotationToGettersAndFields = 146 mAnnotatedGettersAndFields.stream() 147 .filter(getterOrField -> 148 getterOrField.getAnnotation().getPropertyKind() 149 == PropertyAnnotation.Kind.METADATA_PROPERTY) 150 .collect(groupingBy((getterOrField) -> 151 (MetadataPropertyAnnotation) getterOrField.getAnnotation())); 152 for (Map.Entry<MetadataPropertyAnnotation, List<AnnotatedGetterOrField>> entry : 153 annotationToGettersAndFields.entrySet()) { 154 MetadataPropertyAnnotation annotation = entry.getKey(); 155 List<AnnotatedGetterOrField> gettersAndFields = entry.getValue(); 156 if (gettersAndFields.size() > 1) { 157 // Can show the error on any of the duplicates. Just pick the first first. 158 throw new ProcessingException( 159 "Duplicate member annotated with @" 160 + annotation.getClassName().simpleName(), 161 gettersAndFields.get(0).getElement()); 162 } 163 } 164 } 165 166 /** 167 * Makes sure {@link #mAnnotatedGettersAndFields} contains a getter/field that matches the 168 * predicate. 169 * 170 * @return The matched getter/field. 171 * @throws ProcessingException with the error message if no match. 172 */ requireGetterOrFieldMatchingPredicate( @onNull Predicate<AnnotatedGetterOrField> predicate, @NonNull String errorMessage)173 private @NonNull AnnotatedGetterOrField requireGetterOrFieldMatchingPredicate( 174 @NonNull Predicate<AnnotatedGetterOrField> predicate, 175 @NonNull String errorMessage) throws ProcessingException { 176 return mAnnotatedGettersAndFields.stream() 177 .filter(predicate) 178 .findFirst() 179 .orElseThrow(() -> new ProcessingException(errorMessage, mClass)); 180 } 181 182 /** 183 * Tries to create an {@link DocumentModel} from the given {@link Element}. 184 * 185 * @throws ProcessingException if the @{@code Document}-annotated class is invalid. 186 */ createPojoModel( @onNull ProcessingEnvironment env, @NonNull TypeElement clazz)187 public static DocumentModel createPojoModel( 188 @NonNull ProcessingEnvironment env, @NonNull TypeElement clazz) 189 throws ProcessingException { 190 return new DocumentModel(env, clazz, null); 191 } 192 193 /** 194 * Tries to create an {@link DocumentModel} from the given AutoValue {@link Element} and 195 * corresponding generated class. 196 * 197 * @throws ProcessingException if the @{@code Document}-annotated class is invalid. 198 */ createAutoValueModel( @onNull ProcessingEnvironment env, @NonNull TypeElement clazz, @NonNull TypeElement generatedAutoValueElement)199 public static DocumentModel createAutoValueModel( 200 @NonNull ProcessingEnvironment env, @NonNull TypeElement clazz, 201 @NonNull TypeElement generatedAutoValueElement) 202 throws ProcessingException { 203 return new DocumentModel(env, clazz, generatedAutoValueElement); 204 } 205 getClassElement()206 public @NonNull TypeElement getClassElement() { 207 return mClass; 208 } 209 210 /** 211 * The name of the original class annotated with @Document 212 * 213 * @return the class name 214 */ getQualifiedDocumentClassName()215 public @NonNull String getQualifiedDocumentClassName() { 216 return mQualifiedDocumentClassName; 217 } 218 getSchemaName()219 public @NonNull String getSchemaName() { 220 return mSchemaName; 221 } 222 223 /** 224 * Returns the set of parent classes specified in @Document via the "parent" parameter. 225 */ getParentTypes()226 public @NonNull Set<TypeElement> getParentTypes() { 227 return mParentTypes; 228 } 229 230 /** 231 * Returns all getters/fields (declared or inherited) annotated with some 232 * {@link PropertyAnnotation}. 233 */ getAnnotatedGettersAndFields()234 public @NonNull Set<AnnotatedGetterOrField> getAnnotatedGettersAndFields() { 235 return mAnnotatedGettersAndFields; 236 } 237 238 /** 239 * Returns the getter/field annotated with {@code @Document.Id}. 240 */ getIdAnnotatedGetterOrField()241 public @NonNull AnnotatedGetterOrField getIdAnnotatedGetterOrField() { 242 return mIdAnnotatedGetterOrField; 243 } 244 245 /** 246 * Returns the getter/field annotated with {@code @Document.Namespace}. 247 */ getNamespaceAnnotatedGetterOrField()248 public @NonNull AnnotatedGetterOrField getNamespaceAnnotatedGetterOrField() { 249 return mNamespaceAnnotatedGetterOrField; 250 } 251 252 /** 253 * Returns the public/package-private accessor for an annotated getter/field (may be private). 254 */ getAccessor(@onNull AnnotatedGetterOrField getterOrField)255 public @NonNull PropertyAccessor getAccessor(@NonNull AnnotatedGetterOrField getterOrField) { 256 PropertyAccessor accessor = mAccessors.get(getterOrField); 257 if (accessor == null) { 258 throw new IllegalArgumentException( 259 "No such getter/field belongs to this DocumentModel: " + getterOrField); 260 } 261 return accessor; 262 } 263 getDocumentClassCreationInfo()264 public @NonNull DocumentClassCreationInfo getDocumentClassCreationInfo() { 265 return mDocumentClassCreationInfo; 266 } 267 268 /** 269 * Infers the {@link PropertyAccessor} for each of the {@link AnnotatedGetterOrField}. 270 * 271 * <p>Each accessor may be the {@link AnnotatedGetterOrField} itself or some other non-private 272 * getter. 273 */ inferPropertyAccessors( @onNull Collection<AnnotatedGetterOrField> annotatedGettersAndFields, @NonNull Collection<ExecutableElement> allMethods, @NonNull IntrospectionHelper helper)274 private static @NonNull Map<AnnotatedGetterOrField, PropertyAccessor> inferPropertyAccessors( 275 @NonNull Collection<AnnotatedGetterOrField> annotatedGettersAndFields, 276 @NonNull Collection<ExecutableElement> allMethods, 277 @NonNull IntrospectionHelper helper) throws ProcessingException { 278 Map<AnnotatedGetterOrField, PropertyAccessor> accessors = new HashMap<>(); 279 for (AnnotatedGetterOrField getterOrField : annotatedGettersAndFields) { 280 accessors.put( 281 getterOrField, 282 PropertyAccessor.infer(getterOrField, allMethods, helper)); 283 } 284 return accessors; 285 } 286 287 /** 288 * Returns the parent types mentioned within the {@code @Document} annotation. 289 */ getParentSchemaTypes( @onNull TypeElement documentClass)290 private @NonNull LinkedHashSet<TypeElement> getParentSchemaTypes( 291 @NonNull TypeElement documentClass) throws ProcessingException { 292 AnnotationMirror documentAnnotation = requireNonNull(getDocumentAnnotation(documentClass)); 293 Map<String, Object> params = mHelper.getAnnotationParams(documentAnnotation); 294 LinkedHashSet<TypeElement> parentsSchemaTypes = new LinkedHashSet<>(); 295 Object parentsParam = params.get("parent"); 296 if (parentsParam instanceof List) { 297 for (Object parent : (List<?>) parentsParam) { 298 String parentClassName = parent.toString(); 299 parentClassName = parentClassName.substring(0, 300 parentClassName.length() - CLASS_SUFFIX.length()); 301 parentsSchemaTypes.add(mElementUtil.getTypeElement(parentClassName)); 302 } 303 } 304 if (!parentsSchemaTypes.isEmpty() && params.get("name").toString().isEmpty()) { 305 throw new ProcessingException( 306 "All @Document classes with a parent must explicitly provide a name", 307 mClass); 308 } 309 return parentsSchemaTypes; 310 } 311 312 /** 313 * Computes the schema name for a Document class given its hierarchy of parent @Document 314 * classes. 315 * 316 * <p>The schema name will be the most specific Document class that has an explicit schema name, 317 * to allow the schema name to be manually set with the "name" annotation. If no such Document 318 * class exists, use the name of the root Document class, so that performing a query on the base 319 * \@Document class will also return child @Document classes. 320 * 321 * @param hierarchy List of classes annotated with \@Document, with the root class at the 322 * beginning and the final class at the end 323 * @return the final schema name for the class at the end of the hierarchy 324 */ computeSchemaName(List<TypeElement> hierarchy)325 private @NonNull String computeSchemaName(List<TypeElement> hierarchy) { 326 for (int i = hierarchy.size() - 1; i >= 0; i--) { 327 AnnotationMirror documentAnnotation = getDocumentAnnotation(hierarchy.get(i)); 328 if (documentAnnotation == null) { 329 continue; 330 } 331 Map<String, Object> params = mHelper.getAnnotationParams(documentAnnotation); 332 String name = params.get("name").toString(); 333 if (!name.isEmpty()) { 334 return name; 335 } 336 } 337 // Nobody had a name annotation -- use the class name of the root document in the hierarchy 338 TypeElement rootDocumentClass = hierarchy.get(0); 339 AnnotationMirror rootDocumentAnnotation = getDocumentAnnotation(rootDocumentClass); 340 if (rootDocumentAnnotation == null) { 341 return mClass.getSimpleName().toString(); 342 } 343 // Documents don't need an explicit name annotation, can use the class name 344 return rootDocumentClass.getSimpleName().toString(); 345 } 346 347 /** 348 * Accumulates and de-duplicates {@link AnnotatedGetterOrField}s within a class hierarchy and 349 * ensures all of the following: 350 * 351 * <ol> 352 * <li> 353 * The same getter/field doesn't appear in the class hierarchy with different 354 * annotation types e.g. 355 * 356 * <pre> 357 * {@code 358 * @Document 359 * class Parent { 360 * @Document.StringProperty 361 * public String getProp(); 362 * } 363 * 364 * @Document 365 * class Child extends Parent { 366 * @Document.Id 367 * public String getProp(); 368 * } 369 * } 370 * </pre> 371 * </li> 372 * <li> 373 * The same getter/field doesn't appear twice with different serialized names e.g. 374 * 375 * <pre> 376 * {@code 377 * @Document 378 * class Parent { 379 * @Document.StringProperty("foo") 380 * public String getProp(); 381 * } 382 * 383 * @Document 384 * class Child extends Parent { 385 * @Document.StringProperty("bar") 386 * public String getProp(); 387 * } 388 * } 389 * </pre> 390 * </li> 391 * <li> 392 * The same serialized name doesn't appear on two separate getters/fields e.g. 393 * 394 * <pre> 395 * {@code 396 * @Document 397 * class Gift { 398 * @Document.StringProperty("foo") 399 * String mField; 400 * 401 * @Document.LongProperty("foo") 402 * Long getProp(); 403 * } 404 * } 405 * </pre> 406 * </li> 407 * <li> 408 * Two annotated element do not have the same normalized name because this hinders with 409 * downstream logic that tries to infer {@link CreationMethod}s e.g. 410 * 411 * <pre> 412 * {@code 413 * @Document 414 * class Gift { 415 * @Document.StringProperty 416 * String mFoo; 417 * 418 * @Document.StringProperty 419 * String getFoo() {...} 420 * void setFoo(String value) {...} 421 * } 422 * } 423 * </pre> 424 * </li> 425 * </ol> 426 * 427 * @see CreationMethod#inferParamAssociationsAndCreate 428 */ 429 private static final class AnnotatedGetterAndFieldAccumulator { 430 private final Map<String, AnnotatedGetterOrField> mJvmNameToGetterOrField = 431 new LinkedHashMap<>(); 432 private final Map<String, AnnotatedGetterOrField> mSerializedNameToGetterOrField = 433 new HashMap<>(); 434 private final Map<String, AnnotatedGetterOrField> mNormalizedNameToGetterOrField = 435 new HashMap<>(); 436 AnnotatedGetterAndFieldAccumulator()437 AnnotatedGetterAndFieldAccumulator() { 438 } 439 440 /** 441 * Adds the {@link AnnotatedGetterOrField} to the accumulator. 442 * 443 * <p>{@link AnnotatedGetterOrField} that appear again are considered to be overridden 444 * versions and replace the older ones. 445 * 446 * <p>Hence, this method should be called with {@link AnnotatedGetterOrField}s from the 447 * least specific types to the most specific type. 448 */ add(@onNull AnnotatedGetterOrField getterOrField)449 void add(@NonNull AnnotatedGetterOrField getterOrField) throws ProcessingException { 450 String jvmName = getterOrField.getJvmName(); 451 AnnotatedGetterOrField existingGetterOrField = mJvmNameToGetterOrField.get(jvmName); 452 453 if (existingGetterOrField == null) { 454 // First time we're seeing this getter or field 455 mJvmNameToGetterOrField.put(jvmName, getterOrField); 456 457 requireUniqueNormalizedName(getterOrField); 458 mNormalizedNameToGetterOrField.put( 459 getterOrField.getNormalizedName(), getterOrField); 460 461 if (hasDataPropertyAnnotation(getterOrField)) { 462 requireSerializedNameNeverSeenBefore(getterOrField); 463 mSerializedNameToGetterOrField.put( 464 getSerializedName(getterOrField), getterOrField); 465 } 466 } else { 467 // Seen this getter or field before. It showed up again because of overriding. 468 requireAnnotationTypeIsConsistent(existingGetterOrField, getterOrField); 469 // Replace the old entries 470 mJvmNameToGetterOrField.put(jvmName, getterOrField); 471 mNormalizedNameToGetterOrField.put( 472 getterOrField.getNormalizedName(), getterOrField); 473 474 if (hasDataPropertyAnnotation(getterOrField)) { 475 requireSerializedNameIsConsistent(existingGetterOrField, getterOrField); 476 // Replace the old entry 477 mSerializedNameToGetterOrField.put( 478 getSerializedName(getterOrField), getterOrField); 479 } 480 } 481 } 482 getAccumulatedGettersAndFields()483 @NonNull LinkedHashSet<AnnotatedGetterOrField> getAccumulatedGettersAndFields() { 484 return new LinkedHashSet<>(mJvmNameToGetterOrField.values()); 485 } 486 487 /** 488 * Makes sure the getter/field's normalized name either never appeared before, or if it did, 489 * did so for the same getter/field and re-appeared likely because of overriding. 490 */ requireUniqueNormalizedName( @onNull AnnotatedGetterOrField getterOrField)491 private void requireUniqueNormalizedName( 492 @NonNull AnnotatedGetterOrField getterOrField) throws ProcessingException { 493 AnnotatedGetterOrField existingGetterOrField = 494 mNormalizedNameToGetterOrField.get(getterOrField.getNormalizedName()); 495 if (existingGetterOrField == null) { 496 // Never seen this normalized name before 497 return; 498 } 499 if (existingGetterOrField.getJvmName().equals(getterOrField.getJvmName())) { 500 // Same getter/field appeared again (likely because of overriding). Ok. 501 return; 502 } 503 throw new ProcessingException( 504 ("Normalized name \"%s\" is already taken up by pre-existing %s. " 505 + "Please rename this getter/field to something else.").formatted( 506 getterOrField.getNormalizedName(), 507 createSignatureString(existingGetterOrField)), 508 getterOrField.getElement()); 509 } 510 511 /** 512 * Makes sure a new getter/field is never annotated with a serialized name that is 513 * already given to some other getter/field. 514 * 515 * <p>Assumes the getter/field is annotated with a {@link DataPropertyAnnotation}. 516 */ requireSerializedNameNeverSeenBefore( @onNull AnnotatedGetterOrField getterOrField)517 private void requireSerializedNameNeverSeenBefore( 518 @NonNull AnnotatedGetterOrField getterOrField) throws ProcessingException { 519 String serializedName = getSerializedName(getterOrField); 520 AnnotatedGetterOrField existingGetterOrField = 521 mSerializedNameToGetterOrField.get(serializedName); 522 if (existingGetterOrField != null) { 523 throw new ProcessingException( 524 "Cannot give property the name '%s' because it is already used for %s" 525 .formatted(serializedName, existingGetterOrField.getJvmName()), 526 getterOrField.getElement()); 527 } 528 } 529 530 /** 531 * Returns the serialized name that should be used for the property in the database. 532 * 533 * <p>Assumes the getter/field is annotated with a {@link DataPropertyAnnotation}. 534 */ getSerializedName( @onNull AnnotatedGetterOrField getterOrField)535 private static @NonNull String getSerializedName( 536 @NonNull AnnotatedGetterOrField getterOrField) { 537 DataPropertyAnnotation annotation = 538 (DataPropertyAnnotation) getterOrField.getAnnotation(); 539 return annotation.getName(); 540 } 541 hasDataPropertyAnnotation( @onNull AnnotatedGetterOrField getterOrField)542 private static boolean hasDataPropertyAnnotation( 543 @NonNull AnnotatedGetterOrField getterOrField) { 544 PropertyAnnotation annotation = getterOrField.getAnnotation(); 545 return annotation.getPropertyKind() == PropertyAnnotation.Kind.DATA_PROPERTY; 546 } 547 548 /** 549 * Makes sure the annotation type didn't change when overriding e.g. 550 * {@code @StringProperty -> @Id}. 551 */ requireAnnotationTypeIsConsistent( @onNull AnnotatedGetterOrField existingGetterOrField, @NonNull AnnotatedGetterOrField overriddenGetterOfField)552 private static void requireAnnotationTypeIsConsistent( 553 @NonNull AnnotatedGetterOrField existingGetterOrField, 554 @NonNull AnnotatedGetterOrField overriddenGetterOfField) 555 throws ProcessingException { 556 PropertyAnnotation existingAnnotation = existingGetterOrField.getAnnotation(); 557 PropertyAnnotation overriddenAnnotation = overriddenGetterOfField.getAnnotation(); 558 if (!existingAnnotation.getClassName().equals(overriddenAnnotation.getClassName())) { 559 throw new ProcessingException( 560 ("Property type must stay consistent when overriding annotated members " 561 + "but changed from @%s -> @%s").formatted( 562 existingAnnotation.getClassName().simpleName(), 563 overriddenAnnotation.getClassName().simpleName()), 564 overriddenGetterOfField.getElement()); 565 } 566 } 567 568 /** 569 * Makes sure the serialized name didn't change when overriding. 570 * 571 * <p>Assumes the getter/field is annotated with a {@link DataPropertyAnnotation}. 572 */ requireSerializedNameIsConsistent( @onNull AnnotatedGetterOrField existingGetterOrField, @NonNull AnnotatedGetterOrField overriddenGetterOrField)573 private static void requireSerializedNameIsConsistent( 574 @NonNull AnnotatedGetterOrField existingGetterOrField, 575 @NonNull AnnotatedGetterOrField overriddenGetterOrField) 576 throws ProcessingException { 577 String existingSerializedName = getSerializedName(existingGetterOrField); 578 String overriddenSerializedName = getSerializedName(overriddenGetterOrField); 579 if (!existingSerializedName.equals(overriddenSerializedName)) { 580 throw new ProcessingException( 581 ("Property name within the annotation must stay consistent when overriding " 582 + "annotated members but changed from '%s' -> '%s'".formatted( 583 existingSerializedName, overriddenSerializedName)), 584 overriddenGetterOrField.getElement()); 585 } 586 } 587 createSignatureString( @onNull AnnotatedGetterOrField getterOrField)588 private static @NonNull String createSignatureString( 589 @NonNull AnnotatedGetterOrField getterOrField) { 590 return getterOrField.getJvmType() 591 + " " 592 + getterOrField.getElement().getEnclosingElement().getSimpleName() 593 + "#" 594 + getterOrField.getJvmName() 595 + (getterOrField.isGetter() ? "()" : ""); 596 } 597 } 598 } 599