1 /* 2 * Copyright 2019 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 17 package android.processor.view.inspector; 18 19 import android.processor.view.inspector.InspectableClassModel.Accessor; 20 import android.processor.view.inspector.InspectableClassModel.IntEnumEntry; 21 import android.processor.view.inspector.InspectableClassModel.IntFlagEntry; 22 import android.processor.view.inspector.InspectableClassModel.Property; 23 24 import androidx.annotation.NonNull; 25 26 import java.util.ArrayList; 27 import java.util.List; 28 import java.util.Optional; 29 import java.util.Set; 30 import java.util.regex.Pattern; 31 32 import javax.annotation.processing.ProcessingEnvironment; 33 import javax.lang.model.element.AnnotationMirror; 34 import javax.lang.model.element.AnnotationValue; 35 import javax.lang.model.element.Element; 36 import javax.lang.model.element.ElementKind; 37 import javax.lang.model.element.ExecutableElement; 38 import javax.lang.model.element.Modifier; 39 import javax.lang.model.element.TypeElement; 40 import javax.lang.model.type.NoType; 41 import javax.lang.model.type.TypeKind; 42 import javax.lang.model.type.TypeMirror; 43 44 /** 45 * Process {@code @InspectableProperty} annotations. 46 * 47 * @see android.view.inspector.InspectableProperty 48 */ 49 public final class InspectablePropertyProcessor { 50 private final @NonNull String mQualifiedName; 51 private final @NonNull ProcessingEnvironment mProcessingEnv; 52 private final @NonNull AnnotationUtils mAnnotationUtils; 53 54 /** 55 * Regex that matches methods names of the form {@code #getValue()}. 56 */ 57 private static final Pattern GETTER_GET_PREFIX = Pattern.compile("\\Aget[A-Z]"); 58 59 /** 60 * Regex that matches method name of the form {@code #isPredicate()}. 61 */ 62 private static final Pattern GETTER_IS_PREFIX = Pattern.compile("\\Ais[A-Z]"); 63 64 /** 65 * Set of android and androidx annotation qualified names for colors packed into {@code int}. 66 * 67 * @see android.annotation.ColorInt 68 */ 69 private static final String[] COLOR_INT_ANNOTATION_NAMES = { 70 "android.annotation.ColorInt", 71 "androidx.annotation.ColorInt"}; 72 73 /** 74 * Set of android and androidx annotation qualified names for colors packed into {@code long}. 75 * 76 * @see android.annotation.ColorLong 77 */ 78 private static final String[] COLOR_LONG_ANNOTATION_NAMES = { 79 "android.annotation.ColorLong", 80 "androidx.annotation.ColorLong"}; 81 82 /** 83 * Set of android and androidx annotation qualified names of resource ID annotations. 84 */ 85 private static final String[] RESOURCE_ID_ANNOTATION_NAMES = { 86 "android.annotation.AnimatorRes", 87 "android.annotation.AnimRes", 88 "android.annotation.AnyRes", 89 "android.annotation.ArrayRes", 90 "android.annotation.BoolRes", 91 "android.annotation.DimenRes", 92 "android.annotation.DrawableRes", 93 "android.annotation.FontRes", 94 "android.annotation.IdRes", 95 "android.annotation.IntegerRes", 96 "android.annotation.InterpolatorRes", 97 "android.annotation.LayoutRes", 98 "android.annotation.MenuRes", 99 "android.annotation.NavigationRes", 100 "android.annotation.PluralsRes", 101 "android.annotation.RawRes", 102 "android.annotation.StringRes", 103 "android.annotation.StyleableRes", 104 "android.annotation.StyleRes", 105 "android.annotation.TransitionRes", 106 "android.annotation.XmlRes", 107 "androidx.annotation.AnimatorRes", 108 "androidx.annotation.AnimRes", 109 "androidx.annotation.AnyRes", 110 "androidx.annotation.ArrayRes", 111 "androidx.annotation.BoolRes", 112 "androidx.annotation.DimenRes", 113 "androidx.annotation.DrawableRes", 114 "androidx.annotation.FontRes", 115 "androidx.annotation.IdRes", 116 "androidx.annotation.IntegerRes", 117 "androidx.annotation.InterpolatorRes", 118 "androidx.annotation.LayoutRes", 119 "androidx.annotation.MenuRes", 120 "androidx.annotation.NavigationRes", 121 "androidx.annotation.PluralsRes", 122 "androidx.annotation.RawRes", 123 "androidx.annotation.StringRes", 124 "androidx.annotation.StyleableRes", 125 "androidx.annotation.StyleRes", 126 "androidx.annotation.TransitionRes", 127 "androidx.annotation.XmlRes" 128 }; 129 130 /** 131 * @param annotationQualifiedName The qualified name of the annotation to process 132 * @param processingEnv The processing environment from the parent processor 133 */ InspectablePropertyProcessor( @onNull String annotationQualifiedName, @NonNull ProcessingEnvironment processingEnv)134 public InspectablePropertyProcessor( 135 @NonNull String annotationQualifiedName, 136 @NonNull ProcessingEnvironment processingEnv) { 137 mQualifiedName = annotationQualifiedName; 138 mProcessingEnv = processingEnv; 139 mAnnotationUtils = new AnnotationUtils(processingEnv); 140 } 141 process(@onNull Element element, @NonNull InspectableClassModel model)142 public void process(@NonNull Element element, @NonNull InspectableClassModel model) { 143 try { 144 final AnnotationMirror annotation = 145 mAnnotationUtils.exactlyOneMirror(mQualifiedName, element); 146 final Property property = buildProperty(element, annotation); 147 148 model.getProperty(property.getName()).ifPresent(p -> { 149 throw new ProcessingException( 150 String.format( 151 "Property \"%s\" is already defined on #%s.", 152 p.getName(), 153 p.getAccessor().invocation()), 154 element, 155 annotation); 156 }); 157 158 model.putProperty(property); 159 } catch (ProcessingException processingException) { 160 processingException.print(mProcessingEnv.getMessager()); 161 } 162 } 163 164 165 /** 166 * Build a {@link Property} from a getter and an inspectable property annotation. 167 * 168 * @param accessor An element representing the getter or public field to build from 169 * @param annotation A mirror of an inspectable property-shaped annotation 170 * @return A property for the getter and annotation 171 * @throws ProcessingException If the supplied data is invalid and a property cannot be modeled 172 */ 173 @NonNull buildProperty( @onNull Element accessor, @NonNull AnnotationMirror annotation)174 private Property buildProperty( 175 @NonNull Element accessor, 176 @NonNull AnnotationMirror annotation) { 177 final Property property; 178 final Optional<String> nameFromAnnotation = mAnnotationUtils 179 .typedValueByName("name", String.class, accessor, annotation); 180 181 validateModifiers(accessor); 182 183 switch (accessor.getKind()) { 184 case FIELD: 185 property = new Property( 186 nameFromAnnotation.orElseGet(() -> accessor.getSimpleName().toString()), 187 Accessor.ofField(accessor.getSimpleName().toString()), 188 determinePropertyType(accessor, annotation)); 189 break; 190 case METHOD: 191 final ExecutableElement getter = ensureGetter(accessor); 192 193 property = new Property( 194 nameFromAnnotation.orElseGet(() -> inferPropertyNameFromGetter(getter)), 195 Accessor.ofGetter(getter.getSimpleName().toString()), 196 determinePropertyType(getter, annotation)); 197 break; 198 default: 199 throw new ProcessingException( 200 String.format( 201 "Property must either be a getter method or a field, got %s.", 202 accessor.getKind() 203 ), 204 accessor, 205 annotation); 206 } 207 208 mAnnotationUtils 209 .typedValueByName("hasAttributeId", Boolean.class, accessor, annotation) 210 .ifPresent(property::setAttributeIdInferrableFromR); 211 212 mAnnotationUtils 213 .typedValueByName("attributeId", Integer.class, accessor, annotation) 214 .ifPresent(property::setAttributeId); 215 216 switch (property.getType()) { 217 case INT_ENUM: 218 property.setIntEnumEntries(processEnumMapping(accessor, annotation)); 219 break; 220 case INT_FLAG: 221 property.setIntFlagEntries(processFlagMapping(accessor, annotation)); 222 break; 223 } 224 225 return property; 226 } 227 228 /** 229 * Validates that an element is public, concrete, and non-static. 230 * 231 * @param element The element to check 232 * @throws ProcessingException If the element's modifiers are invalid 233 */ validateModifiers(@onNull Element element)234 private void validateModifiers(@NonNull Element element) { 235 final Set<Modifier> modifiers = element.getModifiers(); 236 237 if (!modifiers.contains(Modifier.PUBLIC)) { 238 throw new ProcessingException( 239 "Property getter methods and fields must be public.", 240 element); 241 } 242 243 if (modifiers.contains(Modifier.ABSTRACT)) { 244 throw new ProcessingException( 245 "Property getter methods must not be abstract.", 246 element); 247 } 248 249 if (modifiers.contains(Modifier.STATIC)) { 250 throw new ProcessingException( 251 "Property getter methods and fields must not be static.", 252 element); 253 } 254 } 255 256 /** 257 * Check that an element is shaped like a getter. 258 * 259 * @param element An element that hopefully represents a getter 260 * @return An {@link ExecutableElement} that represents a getter method. 261 * @throws ProcessingException if the element isn't a getter 262 */ 263 @NonNull ensureGetter(@onNull Element element)264 private ExecutableElement ensureGetter(@NonNull Element element) { 265 if (element.getKind() != ElementKind.METHOD) { 266 throw new ProcessingException( 267 String.format("Expected a method, got a %s", element.getKind()), 268 element); 269 } 270 271 final ExecutableElement method = (ExecutableElement) element; 272 273 274 if (!method.getParameters().isEmpty()) { 275 throw new ProcessingException( 276 String.format( 277 "Expected a getter method to take no parameters, " 278 + "but got %d parameters.", 279 method.getParameters().size()), 280 element); 281 } 282 283 if (method.isVarArgs()) { 284 throw new ProcessingException( 285 "Expected a getter method to take no arguments, but got a var args method.", 286 element); 287 } 288 289 if (method.getReturnType() instanceof NoType) { 290 throw new ProcessingException( 291 "Expected a getter to have a return type, got void.", 292 element); 293 } 294 295 return method; 296 } 297 298 299 /** 300 * Determine the property type from the annotation, return type, or context clues. 301 * 302 * @param accessor An element representing the getter or field to determine the type of 303 * @param annotation A mirror of an inspectable property-shaped annotation 304 * @return The resolved property type 305 * @throws ProcessingException If the property type cannot be resolved or is invalid 306 * @see android.view.inspector.InspectableProperty#valueType() 307 */ 308 @NonNull determinePropertyType( @onNull Element accessor, @NonNull AnnotationMirror annotation)309 private Property.Type determinePropertyType( 310 @NonNull Element accessor, 311 @NonNull AnnotationMirror annotation) { 312 final String valueType = mAnnotationUtils 313 .untypedValueByName("valueType", accessor, annotation) 314 .map(Object::toString) 315 .orElse("INFERRED"); 316 317 final Property.Type accessorType = 318 convertTypeMirrorToPropertyType(extractReturnOrFieldType(accessor), accessor); 319 320 final Optional<AnnotationValue> enumMapping = 321 mAnnotationUtils.valueByName("enumMapping", annotation); 322 final Optional<AnnotationValue> flagMapping = 323 mAnnotationUtils.valueByName("flagMapping", annotation); 324 325 if (accessorType != Property.Type.INT) { 326 enumMapping.ifPresent(value -> { 327 throw new ProcessingException( 328 String.format( 329 "Can only use enumMapping on int types, got %s.", 330 accessorType.toString().toLowerCase()), 331 accessor, 332 annotation, 333 value); 334 }); 335 flagMapping.ifPresent(value -> { 336 throw new ProcessingException( 337 String.format( 338 "Can only use flagMapping on int types, got %s.", 339 accessorType.toString().toLowerCase()), 340 accessor, 341 annotation, 342 value); 343 }); 344 } 345 346 347 switch (valueType) { 348 case "INFERRED": 349 final boolean hasColor = hasColorAnnotation(accessor); 350 final boolean hasResourceId = hasResourceIdAnnotation(accessor); 351 352 if (hasColor) { 353 enumMapping.ifPresent(value -> { 354 throw new ProcessingException( 355 "Cannot use enumMapping on a color type.", 356 accessor, 357 annotation, 358 value); 359 }); 360 flagMapping.ifPresent(value -> { 361 throw new ProcessingException( 362 "Cannot use flagMapping on a color type.", 363 accessor, 364 annotation, 365 value); 366 }); 367 if (hasResourceId) { 368 throw new ProcessingException( 369 "Cannot infer type, both color and resource ID annotations " 370 + "are present.", 371 accessor, 372 annotation); 373 } 374 return Property.Type.COLOR; 375 } else if (hasResourceId) { 376 enumMapping.ifPresent(value -> { 377 throw new ProcessingException( 378 "Cannot use enumMapping on a resource ID type.", 379 accessor, 380 annotation, 381 value); 382 }); 383 flagMapping.ifPresent(value -> { 384 throw new ProcessingException( 385 "Cannot use flagMapping on a resource ID type.", 386 accessor, 387 annotation, 388 value); 389 }); 390 return Property.Type.RESOURCE_ID; 391 } else if (enumMapping.isPresent()) { 392 flagMapping.ifPresent(value -> { 393 throw new ProcessingException( 394 "Cannot use flagMapping and enumMapping simultaneously.", 395 accessor, 396 annotation, 397 value); 398 }); 399 return Property.Type.INT_ENUM; 400 } else if (flagMapping.isPresent()) { 401 return Property.Type.INT_FLAG; 402 } else { 403 return accessorType; 404 } 405 case "NONE": 406 return accessorType; 407 case "COLOR": 408 switch (accessorType) { 409 case COLOR: 410 case INT: 411 case LONG: 412 return Property.Type.COLOR; 413 default: 414 throw new ProcessingException( 415 "Color must be a long, integer, or android.graphics.Color", 416 accessor, 417 annotation); 418 } 419 case "GRAVITY": 420 requirePackedIntToBeInt("Gravity", accessorType, accessor, annotation); 421 return Property.Type.GRAVITY; 422 case "INT_ENUM": 423 requirePackedIntToBeInt("IntEnum", accessorType, accessor, annotation); 424 return Property.Type.INT_ENUM; 425 case "INT_FLAG": 426 requirePackedIntToBeInt("IntFlag", accessorType, accessor, annotation); 427 return Property.Type.INT_FLAG; 428 case "RESOURCE_ID": 429 return Property.Type.RESOURCE_ID; 430 default: 431 throw new ProcessingException( 432 String.format("Unknown value type enumeration value: %s", valueType), 433 accessor, 434 annotation); 435 } 436 } 437 438 /** 439 * Get the type of a field or the return type of a method. 440 * 441 * @param element The element to extract a {@link TypeMirror} from 442 * @return The return or field type of the element 443 * @throws ProcessingException If the element is not a field or a method 444 */ 445 @NonNull extractReturnOrFieldType(@onNull Element element)446 private TypeMirror extractReturnOrFieldType(@NonNull Element element) { 447 switch (element.getKind()) { 448 case FIELD: 449 return element.asType(); 450 case METHOD: 451 return ((ExecutableElement) element).getReturnType(); 452 default: 453 throw new ProcessingException( 454 String.format( 455 "Unable to determine the type of a %s.", 456 element.getKind()), 457 element); 458 } 459 } 460 461 /** 462 * Get a property type from a type mirror 463 * 464 * @param typeMirror The type mirror to convert to a property type 465 * @param element The element to be used for exceptions 466 * @return The property type returned by the getter 467 * @throws ProcessingException If the return type is not a primitive or an object 468 */ 469 @NonNull convertTypeMirrorToPropertyType( @onNull TypeMirror typeMirror, @NonNull Element element)470 private Property.Type convertTypeMirrorToPropertyType( 471 @NonNull TypeMirror typeMirror, 472 @NonNull Element element) { 473 switch (unboxType(typeMirror)) { 474 case BOOLEAN: 475 return Property.Type.BOOLEAN; 476 case BYTE: 477 return Property.Type.BYTE; 478 case CHAR: 479 return Property.Type.CHAR; 480 case DOUBLE: 481 return Property.Type.DOUBLE; 482 case FLOAT: 483 return Property.Type.FLOAT; 484 case INT: 485 return Property.Type.INT; 486 case LONG: 487 return Property.Type.LONG; 488 case SHORT: 489 return Property.Type.SHORT; 490 case DECLARED: 491 if (isColorType(typeMirror)) { 492 return Property.Type.COLOR; 493 } else { 494 return Property.Type.OBJECT; 495 } 496 case ARRAY: 497 return Property.Type.OBJECT; 498 default: 499 throw new ProcessingException( 500 String.format("Unsupported property type %s.", typeMirror), 501 element); 502 } 503 } 504 505 /** 506 * Require that a value type packed into an integer be on a getter that returns an int. 507 * 508 * @param typeName The name of the type to use in the exception 509 * @param returnType The return type of the getter to check 510 * @param accessor The getter, to use in the exception 511 * @param annotation The annotation, to use in the exception 512 * @throws ProcessingException If the return type is not an int 513 */ requirePackedIntToBeInt( @onNull String typeName, @NonNull Property.Type returnType, @NonNull Element accessor, @NonNull AnnotationMirror annotation)514 private static void requirePackedIntToBeInt( 515 @NonNull String typeName, 516 @NonNull Property.Type returnType, 517 @NonNull Element accessor, 518 @NonNull AnnotationMirror annotation) { 519 if (returnType != Property.Type.INT) { 520 throw new ProcessingException( 521 String.format( 522 "%s can only be defined on a method that returns int, got %s.", 523 typeName, 524 returnType.toString().toLowerCase()), 525 accessor, 526 annotation); 527 } 528 } 529 530 /** 531 * Determine if a getter is annotated with color annotation matching its return type. 532 * 533 * Note that an {@code int} return value annotated with {@link android.annotation.ColorLong} is 534 * not considered to be annotated, nor is a {@code long} annotated with 535 * {@link android.annotation.ColorInt}. 536 * 537 * @param accessor The getter or field to query 538 * @return True if the getter has a color annotation, false otherwise 539 */ hasColorAnnotation(@onNull Element accessor)540 private boolean hasColorAnnotation(@NonNull Element accessor) { 541 switch (unboxType(extractReturnOrFieldType(accessor))) { 542 case INT: 543 for (String name : COLOR_INT_ANNOTATION_NAMES) { 544 if (mAnnotationUtils.hasAnnotation(accessor, name)) { 545 return true; 546 } 547 } 548 return false; 549 case LONG: 550 for (String name : COLOR_LONG_ANNOTATION_NAMES) { 551 if (mAnnotationUtils.hasAnnotation(accessor, name)) { 552 return true; 553 } 554 } 555 return false; 556 default: 557 return false; 558 } 559 } 560 561 /** 562 * Determine if a getter or a field is annotated with a resource ID annotation. 563 * 564 * @param accessor The getter or field to query 565 * @return True if the accessor is an integer and has a resource ID annotation, false otherwise 566 */ hasResourceIdAnnotation(@onNull Element accessor)567 private boolean hasResourceIdAnnotation(@NonNull Element accessor) { 568 if (unboxType(extractReturnOrFieldType(accessor)) == TypeKind.INT) { 569 for (String name : RESOURCE_ID_ANNOTATION_NAMES) { 570 if (mAnnotationUtils.hasAnnotation(accessor, name)) { 571 return true; 572 } 573 } 574 } 575 576 return false; 577 } 578 579 /** 580 * Infer a property name from a getter method. 581 * 582 * If the method is prefixed with {@code get}, the prefix will be stripped, and the 583 * capitalization fixed. E.g.: {@code getSomeProperty} to {@code someProperty}. 584 * 585 * Additionally, if the method's return type is a boolean, an {@code is} prefix will also be 586 * stripped. E.g.: {@code isPropertyEnabled} to {@code propertyEnabled}. 587 * 588 * Failing that, this method will just return the full name of the getter. 589 * 590 * @param getter An element representing a getter 591 * @return A string property name 592 */ 593 @NonNull inferPropertyNameFromGetter(@onNull ExecutableElement getter)594 private String inferPropertyNameFromGetter(@NonNull ExecutableElement getter) { 595 final String name = getter.getSimpleName().toString(); 596 597 if (GETTER_GET_PREFIX.matcher(name).find()) { 598 return name.substring(3, 4).toLowerCase() + name.substring(4); 599 } else if (isBoolean(getter.getReturnType()) && GETTER_IS_PREFIX.matcher(name).find()) { 600 return name.substring(2, 3).toLowerCase() + name.substring(3); 601 } else { 602 return name; 603 } 604 } 605 606 /** 607 * Build a model of an {@code int} enumeration mapping from annotation values. 608 * 609 * This method only handles the one-to-one mapping of mirrors of 610 * {@link android.view.inspector.InspectableProperty.EnumEntry} annotations into 611 * {@link IntEnumEntry} objects. Further validation should be handled elsewhere 612 * 613 * @see android.view.inspector.InspectableProperty#enumMapping() 614 * @param accessor The accessor of the property, used for exceptions 615 * @param annotation The {@link android.view.inspector.InspectableProperty} annotation to 616 * extract enum mapping values from. 617 * @return A list of int enum entries, in the order specified in source 618 * @throws ProcessingException if mapping doesn't exist or is invalid 619 */ 620 @NonNull processEnumMapping( @onNull Element accessor, @NonNull AnnotationMirror annotation)621 private List<IntEnumEntry> processEnumMapping( 622 @NonNull Element accessor, 623 @NonNull AnnotationMirror annotation) { 624 List<AnnotationMirror> enumAnnotations = mAnnotationUtils.typedArrayValuesByName( 625 "enumMapping", AnnotationMirror.class, accessor, annotation); 626 List<IntEnumEntry> enumEntries = new ArrayList<>(enumAnnotations.size()); 627 628 if (enumAnnotations.isEmpty()) { 629 throw new ProcessingException( 630 "Encountered an empty array for enumMapping", accessor, annotation); 631 } 632 633 for (AnnotationMirror enumAnnotation : enumAnnotations) { 634 final String name = mAnnotationUtils.typedValueByName( 635 "name", String.class, accessor, enumAnnotation) 636 .orElseThrow(() -> new ProcessingException( 637 "Name is required for @EnumEntry", 638 accessor, 639 enumAnnotation)); 640 641 final int value = mAnnotationUtils.typedValueByName( 642 "value", Integer.class, accessor, enumAnnotation) 643 .orElseThrow(() -> new ProcessingException( 644 "Value is required for @EnumEntry", 645 accessor, 646 enumAnnotation)); 647 648 enumEntries.add(new IntEnumEntry(value, name)); 649 } 650 651 return enumEntries; 652 } 653 654 /** 655 * Build a model of an {@code int} flag mapping from annotation values. 656 * 657 * This method only handles the one-to-one mapping of mirrors of 658 * {@link android.view.inspector.InspectableProperty.FlagEntry} annotations into 659 * {@link IntFlagEntry} objects. Further validation should be handled elsewhere 660 * 661 * @see android.view.inspector.IntFlagMapping 662 * @see android.view.inspector.InspectableProperty#flagMapping() 663 * @param accessor The accessor of the property, used for exceptions 664 * @param annotation The {@link android.view.inspector.InspectableProperty} annotation to 665 * extract flag mapping values from. 666 * @return A list of int flags entries, in the order specified in source 667 * @throws ProcessingException if mapping doesn't exist or is invalid 668 */ 669 @NonNull processFlagMapping( @onNull Element accessor, @NonNull AnnotationMirror annotation)670 private List<IntFlagEntry> processFlagMapping( 671 @NonNull Element accessor, 672 @NonNull AnnotationMirror annotation) { 673 List<AnnotationMirror> flagAnnotations = mAnnotationUtils.typedArrayValuesByName( 674 "flagMapping", AnnotationMirror.class, accessor, annotation); 675 List<IntFlagEntry> flagEntries = new ArrayList<>(flagAnnotations.size()); 676 677 if (flagAnnotations.isEmpty()) { 678 throw new ProcessingException( 679 "Encountered an empty array for flagMapping", accessor, annotation); 680 } 681 682 for (AnnotationMirror flagAnnotation : flagAnnotations) { 683 final String name = mAnnotationUtils.typedValueByName( 684 "name", String.class, accessor, flagAnnotation) 685 .orElseThrow(() -> new ProcessingException( 686 "Name is required for @FlagEntry", 687 accessor, 688 flagAnnotation)); 689 690 final int target = mAnnotationUtils.typedValueByName( 691 "target", Integer.class, accessor, flagAnnotation) 692 .orElseThrow(() -> new ProcessingException( 693 "Target is required for @FlagEntry", 694 accessor, 695 flagAnnotation)); 696 697 final Optional<Integer> mask = mAnnotationUtils.typedValueByName( 698 "mask", Integer.class, accessor, flagAnnotation); 699 700 if (mask.isPresent()) { 701 flagEntries.add(new IntFlagEntry(mask.get(), target, name)); 702 } else { 703 flagEntries.add(new IntFlagEntry(target, name)); 704 } 705 } 706 707 return flagEntries; 708 } 709 710 /** 711 * Determine if a {@link TypeMirror} is a boxed or unboxed boolean. 712 * 713 * @param type The type mirror to check 714 * @return True if the type is a boolean 715 */ isBoolean(@onNull TypeMirror type)716 private boolean isBoolean(@NonNull TypeMirror type) { 717 if (type.getKind() == TypeKind.DECLARED) { 718 return mProcessingEnv.getTypeUtils().unboxedType(type).getKind() == TypeKind.BOOLEAN; 719 } else { 720 return type.getKind() == TypeKind.BOOLEAN; 721 } 722 } 723 724 /** 725 * Unbox a type mirror if it represents a boxed type, otherwise pass it through. 726 * 727 * @param typeMirror The type mirror to unbox 728 * @return The same type mirror, or an unboxed primitive version 729 */ 730 @NonNull unboxType(@onNull TypeMirror typeMirror)731 private TypeKind unboxType(@NonNull TypeMirror typeMirror) { 732 final TypeKind typeKind = typeMirror.getKind(); 733 734 if (typeKind.isPrimitive()) { 735 return typeKind; 736 } else if (typeKind == TypeKind.DECLARED) { 737 try { 738 return mProcessingEnv.getTypeUtils().unboxedType(typeMirror).getKind(); 739 } catch (IllegalArgumentException e) { 740 return typeKind; 741 } 742 } else { 743 return typeKind; 744 } 745 } 746 747 /** 748 * Determine if a type mirror represents a subtype of {@link android.graphics.Color}. 749 * 750 * @param typeMirror The type mirror to test 751 * @return True if it represents a subclass of color, false otherwise 752 */ isColorType(@onNull TypeMirror typeMirror)753 private boolean isColorType(@NonNull TypeMirror typeMirror) { 754 final TypeElement colorType = mProcessingEnv 755 .getElementUtils() 756 .getTypeElement("android.graphics.Color"); 757 758 if (colorType == null) { 759 return false; 760 } else { 761 return mProcessingEnv.getTypeUtils().isSubtype(typeMirror, colorType.asType()); 762 } 763 } 764 } 765