-
Notifications
You must be signed in to change notification settings - Fork 348
/
AnnotationUtils.java
948 lines (885 loc) · 39.2 KB
/
AnnotationUtils.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
package org.checkerframework.javacutil;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.model.JavacElements;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.util.ElementFilter;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.signature.qual.DotSeparatedIdentifiers;
import org.checkerframework.javacutil.AnnotationBuilder.CheckerFrameworkAnnotationMirror;
/** A utility class for working with annotations. */
public class AnnotationUtils {
// Class cannot be instantiated.
private AnnotationUtils() {
throw new AssertionError("Class AnnotationUtils cannot be instantiated.");
}
/** Clear the static caches. */
// TODO: hack to clear out static state.
public static void clear() {
annotationClassNames.clear();
}
// **********************************************************************
// Factory Methods to create instances of AnnotationMirror
// **********************************************************************
private static final int ANNOTATION_CACHE_SIZE = 500;
/** Maps classes representing AnnotationMirrors to their names. */
private static final Map<Class<? extends Annotation>, String> annotationClassNames =
Collections.synchronizedMap(CollectionUtils.createLRUCache(ANNOTATION_CACHE_SIZE));
// **********************************************************************
// Helper methods to handle annotations. mainly workaround
// AnnotationMirror.equals undesired property
// (I think the undesired property is that it's reference equality.)
// **********************************************************************
/**
* @param annotation the annotation whose name to return
* @return the fully-qualified name of an annotation as a String
*/
public static final String annotationName(AnnotationMirror annotation) {
if (annotation instanceof AnnotationBuilder.CheckerFrameworkAnnotationMirror) {
return ((AnnotationBuilder.CheckerFrameworkAnnotationMirror) annotation).annotationName;
}
final DeclaredType annoType = annotation.getAnnotationType();
final TypeElement elm = (TypeElement) annoType.asElement();
String name = elm.getQualifiedName().toString();
return name;
}
/**
* Returns true iff both annotations are of the same type and have the same annotation values.
*
* <p>This behavior differs from {@code AnnotationMirror.equals(Object)}. The equals method
* returns true iff both annotations are the same and annotate the same annotation target (e.g.
* field, variable, etc) -- that is, if its arguments are the same annotation instance.
*
* @param a1 the first AnnotationMirror to compare
* @param a2 the second AnnotationMirror to compare
* @return true iff a1 and a2 are the same annotation
*/
public static boolean areSame(AnnotationMirror a1, AnnotationMirror a2) {
if (a1 == a2) {
return true;
}
if (!areSameByName(a1, a2)) {
return false;
}
// This commented implementation is less efficient. It is also wrong: it requires a
// particular order for fields, and it distinguishes the long constants "33" and "33L".
// Map<? extends ExecutableElement, ? extends AnnotationValue> elval1 =
// getElementValuesWithDefaults(a1);
// Map<? extends ExecutableElement, ? extends AnnotationValue> elval2 =
// getElementValuesWithDefaults(a2);
// return elval1.toString().equals(elval2.toString());
return sameElementValues(a1, a2);
}
/**
* Return true iff a1 and a2 have the same annotation type.
*
* @param a1 the first AnnotationMirror to compare
* @param a2 the second AnnotationMirror to compare
* @return true iff a1 and a2 have the same annotation name
* @see #areSame(AnnotationMirror, AnnotationMirror)
* @return true iff a1 and a2 have the same annotation name
*/
public static boolean areSameByName(AnnotationMirror a1, AnnotationMirror a2) {
if (a1 == a2) {
return true;
}
if (a1 == null) {
throw new BugInCF("Unexpected null first argument to areSameByName");
}
if (a2 == null) {
throw new BugInCF("Unexpected null second argument to areSameByName");
}
if (a1 instanceof CheckerFrameworkAnnotationMirror
&& a2 instanceof CheckerFrameworkAnnotationMirror) {
return ((CheckerFrameworkAnnotationMirror) a1).annotationName
== ((CheckerFrameworkAnnotationMirror) a2).annotationName;
}
return annotationName(a1).equals(annotationName(a2));
}
/**
* Checks that the annotation {@code am} has the name {@code aname} (a fully-qualified type
* name). Values are ignored.
*
* @param am the AnnotationMirror whose name to compare
* @param aname the string to compare
* @return true if aname is the name of am
*/
public static boolean areSameByName(AnnotationMirror am, String aname) {
return aname.equals(annotationName(am));
}
/**
* Checks that the annotation {@code am} has the name of {@code annoClass}. Values are ignored.
*
* @param am the AnnotationMirror whose class to compare
* @param annoClass the class to compare
* @return true if annoclass is the class of am
*/
public static boolean areSameByClass(
AnnotationMirror am, Class<? extends Annotation> annoClass) {
String canonicalName = annotationClassNames.get(annoClass);
if (canonicalName == null) {
canonicalName = annoClass.getCanonicalName();
assert canonicalName != null : "@AssumeAssertion(nullness): assumption";
annotationClassNames.put(annoClass, canonicalName);
}
return areSameByName(am, canonicalName);
}
/**
* Checks that two collections contain the same annotations.
*
* @param c1 the first collection to compare
* @param c2 the second collection to compare
* @return true iff c1 and c2 contain the same annotations, according to {@link
* #areSame(AnnotationMirror, AnnotationMirror)}
*/
public static boolean areSame(
Collection<? extends AnnotationMirror> c1, Collection<? extends AnnotationMirror> c2) {
if (c1.size() != c2.size()) {
return false;
}
if (c1.size() == 1) {
return areSame(c1.iterator().next(), c2.iterator().next());
}
// while loop depends on SortedSet implementation.
SortedSet<AnnotationMirror> s1 = createAnnotationSet();
SortedSet<AnnotationMirror> s2 = createAnnotationSet();
s1.addAll(c1);
s2.addAll(c2);
Iterator<AnnotationMirror> iter1 = s1.iterator();
Iterator<AnnotationMirror> iter2 = s2.iterator();
while (iter1.hasNext()) {
AnnotationMirror anno1 = iter1.next();
AnnotationMirror anno2 = iter2.next();
if (!areSame(anno1, anno2)) {
return false;
}
}
return true;
}
/**
* Checks that the collection contains the annotation. Using Collection.contains does not always
* work, because it does not use areSame for comparison.
*
* @param c a collection of AnnotationMirrors
* @param anno the AnnotationMirror to search for in c
* @return true iff c contains anno, according to areSame
*/
public static boolean containsSame(
Collection<? extends AnnotationMirror> c, AnnotationMirror anno) {
return getSame(c, anno) != null;
}
/**
* Returns the AnnotationMirror in {@code c} that is the same annotation as {@code anno}.
*
* @param c a collection of AnnotationMirrors
* @param anno the AnnotationMirror to search for in c
* @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according
* to areSame; otherwise, {@code null}
*/
public static @Nullable AnnotationMirror getSame(
Collection<? extends AnnotationMirror> c, AnnotationMirror anno) {
for (AnnotationMirror an : c) {
if (AnnotationUtils.areSame(an, anno)) {
return an;
}
}
return null;
}
/**
* Checks that the collection contains the annotation. Using Collection.contains does not always
* work, because it does not use areSame for comparison.
*
* @param c a collection of AnnotationMirrors
* @param anno the annotation class to search for in c
* @return true iff c contains anno, according to areSameByClass
*/
public static boolean containsSameByClass(
Collection<? extends AnnotationMirror> c, Class<? extends Annotation> anno) {
return getAnnotationByClass(c, anno) != null;
}
/**
* Returns the AnnotationMirror in {@code c} that has the same class as {@code anno}.
*
* @param c a collection of AnnotationMirrors
* @param anno the class to search for in c
* @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according
* to areSameByClass; otherwise, {@code null}
*/
public static @Nullable AnnotationMirror getAnnotationByClass(
Collection<? extends AnnotationMirror> c, Class<? extends Annotation> anno) {
for (AnnotationMirror an : c) {
if (AnnotationUtils.areSameByClass(an, anno)) {
return an;
}
}
return null;
}
/**
* Checks that the collection contains an annotation of the given name. Differs from using
* Collection.contains, which does not use areSameByName for comparison.
*
* @param c a collection of AnnotationMirrors
* @param anno the name to search for in c
* @return true iff c contains anno, according to areSameByName
*/
public static boolean containsSameByName(
Collection<? extends AnnotationMirror> c, String anno) {
return getAnnotationByName(c, anno) != null;
}
/**
* Returns the AnnotationMirror in {@code c} that has the same name as {@code anno}.
*
* @param c a collection of AnnotationMirrors
* @param anno the name to search for in c
* @return AnnotationMirror with the same name as {@code anno} iff c contains anno, according to
* areSameByName; otherwise, {@code null}
*/
public static @Nullable AnnotationMirror getAnnotationByName(
Collection<? extends AnnotationMirror> c, String anno) {
for (AnnotationMirror an : c) {
if (AnnotationUtils.areSameByName(an, anno)) {
return an;
}
}
return null;
}
/**
* Checks that the collection contains an annotation of the given name. Differs from using
* Collection.contains, which does not use areSameByName for comparison.
*
* @param c a collection of AnnotationMirrors
* @param anno the annotation whose name to search for in c
* @return true iff c contains anno, according to areSameByName
*/
public static boolean containsSameByName(
Collection<? extends AnnotationMirror> c, AnnotationMirror anno) {
return getSameByName(c, anno) != null;
}
/**
* Returns the AnnotationMirror in {@code c} that is the same annotation as {@code anno}
* ignoring values.
*
* @param c a collection of AnnotationMirrors
* @param anno the annotation whose name to search for in c
* @return AnnotationMirror with the same class as {@code anno} iff c contains anno, according
* to areSameByName; otherwise, {@code null}
*/
public static @Nullable AnnotationMirror getSameByName(
Collection<? extends AnnotationMirror> c, AnnotationMirror anno) {
for (AnnotationMirror an : c) {
if (AnnotationUtils.areSameByName(an, anno)) {
return an;
}
}
return null;
}
/**
* Provide ordering for {@link AnnotationMirror}s. AnnotationMirrors are first compared by their
* fully-qualified names, then by their element values in order of the name of the element.
*
* @param a1 the first annotation
* @param a2 the second annotation
* @return an ordering over AnnotationMirrors based on their name and values
*/
public static int compareAnnotationMirrors(AnnotationMirror a1, AnnotationMirror a2) {
if (!AnnotationUtils.areSameByName(a1, a2)) {
return annotationName(a1).compareTo(annotationName(a2));
}
// The annotations have the same name, but different values, so compare values.
Map<? extends ExecutableElement, ? extends AnnotationValue> vals1 = a1.getElementValues();
Map<? extends ExecutableElement, ? extends AnnotationValue> vals2 = a2.getElementValues();
Set<ExecutableElement> sortedElements =
new TreeSet<>(Comparator.comparing(ElementUtils::getSimpleName));
sortedElements.addAll(
ElementFilter.methodsIn(a1.getAnnotationType().asElement().getEnclosedElements()));
for (ExecutableElement meth : sortedElements) {
AnnotationValue aval1 = vals1.get(meth);
AnnotationValue aval2 = vals2.get(meth);
if (aval1 == null) {
aval1 = meth.getDefaultValue();
}
if (aval2 == null) {
aval2 = meth.getDefaultValue();
}
int result = compareAnnotationValue(aval1, aval2);
if (result != 0) {
return result;
}
}
return 0;
}
/**
* Return 0 iff the two AnnotationValue objects are the same.
*
* @param av1 the first AnnotationValue to compare
* @param av2 the second AnnotationValue to compare
* @return 0 if if the two annotation values are the same
*/
private static int compareAnnotationValue(AnnotationValue av1, AnnotationValue av2) {
if (av1 == av2) {
return 0;
} else if (av1 == null) {
return -1;
} else if (av2 == null) {
return 1;
}
return compareAnnotationValueValue(av1.getValue(), av2.getValue());
}
/**
* Return 0 if the two annotation values are the same.
*
* @param val1 a value returned by {@code AnnotationValue.getValue()}
* @param val2 a value returned by {@code AnnotationValue.getValue()}
*/
private static int compareAnnotationValueValue(@Nullable Object val1, @Nullable Object val2) {
if (val1 == val2) {
return 0;
} else if (val1 == null) {
return -1;
} else if (val2 == null) {
return 1;
}
// Can't use deepEquals() to compare val1 and val2, because they might have mismatched
// AnnotationValue vs. CheckerFrameworkAnnotationValue, and AnnotationValue doesn't override
// equals(). So, write my own version of deepEquals().
if ((val1 instanceof List<?>) && (val2 instanceof List<?>)) {
List<?> list1 = (List<?>) val1;
List<?> list2 = (List<?>) val2;
if (list1.size() != list2.size()) {
return list1.size() - list2.size();
}
// Don't compare setwise, because order can matter. These mean different things:
// @LTLengthOf(value={"a1","a2"}, offest={"0", "1"})
// @LTLengthOf(value={"a2","a1"}, offest={"0", "1"})
for (int i = 0; i < list1.size(); i++) {
Object v1 = list1.get(i);
Object v2 = list2.get(i);
int result = compareAnnotationValueValue(v1, v2);
if (result != 0) {
return result;
}
}
return 0;
} else if ((val1 instanceof AnnotationMirror) && (val2 instanceof AnnotationMirror)) {
return compareAnnotationMirrors((AnnotationMirror) val1, (AnnotationMirror) val2);
} else if ((val1 instanceof AnnotationValue) && (val2 instanceof AnnotationValue)) {
// This case occurs because of the recursive call when comparing arrays of
// annotation values.
return compareAnnotationValue((AnnotationValue) val1, (AnnotationValue) val2);
}
if ((val1 instanceof Type.ClassType) && (val2 instanceof Type.ClassType)) {
// Type.ClassType does not override equals
if (TypesUtils.areSameDeclaredTypes((Type.ClassType) val1, (Type.ClassType) val2)) {
return 0;
}
}
if (Objects.equals(val1, val2)) {
return 0;
}
int result = val1.toString().compareTo(val2.toString());
if (result == 0) {
result = -1;
}
return result;
}
/**
* Provide ordering for {@link AnnotationMirror} based on their fully qualified name. The
* ordering ignores annotation values when ordering.
*
* <p>The ordering is meant to be used as {@link TreeSet} or {@link TreeMap} ordering. A {@link
* Set} should not contain two annotations that only differ in values.
*
* @return an ordering over AnnotationMirrors based on their name
* @deprecated Use the method reference {@code AnnotationUtils::compareAnnotationMirrors}
* instead
*/
@Deprecated
public static Comparator<AnnotationMirror> annotationOrdering() {
return AnnotationUtils::compareAnnotationMirrors;
}
/**
* Create a map suitable for storing {@link AnnotationMirror} as keys.
*
* <p>It can store one instance of {@link AnnotationMirror} of a given declared type, regardless
* of the annotation element values.
*
* @param <V> the value of the map
* @return a new map with {@link AnnotationMirror} as key
*/
public static <V> Map<AnnotationMirror, V> createAnnotationMap() {
return new TreeMap<>(AnnotationUtils::compareAnnotationMirrors);
}
/**
* Constructs a {@link Set} for storing {@link AnnotationMirror}s.
*
* <p>It stores at most once instance of {@link AnnotationMirror} of a given type, regardless of
* the annotation element values.
*
* @return a sorted new set to store {@link AnnotationMirror} as element
*/
public static SortedSet<AnnotationMirror> createAnnotationSet() {
return new TreeSet<>(AnnotationUtils::compareAnnotationMirrors);
}
/**
* Returns true if the given annotation has a @Inherited meta-annotation.
*
* @param anno the annotation to check for an @Inherited meta-annotation
* @return true if the given annotation has a @Inherited meta-annotation
*/
public static boolean hasInheritedMeta(AnnotationMirror anno) {
return anno.getAnnotationType().asElement().getAnnotation(Inherited.class) != null;
}
/**
* @param target a location where an annotation can be written
* @return the set of {@link ElementKind}s to which {@code target} applies, ignoring TYPE_USE
*/
public static EnumSet<ElementKind> getElementKindsForTarget(@Nullable Target target) {
if (target == null) {
// A missing @Target implies that the annotation can be written everywhere.
return EnumSet.allOf(ElementKind.class);
}
EnumSet<ElementKind> eleKinds = EnumSet.noneOf(ElementKind.class);
for (ElementType elementType : target.value()) {
eleKinds.addAll(getElementKindsForElementType(elementType));
}
return eleKinds;
}
/**
* Returns the set of {@link ElementKind}s corresponding to {@code elementType}. If the element
* type is TYPE_USE, then ElementKinds returned should be the same as those returned for TYPE
* and TYPE_PARAMETER, but this method returns the empty set instead.
*
* <p>If the Element is MODULE, the empty set is returned. This is so that this method can
* compile with Java 8.
*
* @param elementType the elementType to find ElementKinds for
* @return the set of {@link ElementKind}s corresponding to {@code elementType}
*/
public static EnumSet<ElementKind> getElementKindsForElementType(ElementType elementType) {
switch (elementType) {
case TYPE:
return EnumSet.copyOf(ElementUtils.classElementKinds());
case FIELD:
return EnumSet.of(ElementKind.FIELD, ElementKind.ENUM_CONSTANT);
case METHOD:
return EnumSet.of(ElementKind.METHOD);
case PARAMETER:
return EnumSet.of(ElementKind.PARAMETER);
case CONSTRUCTOR:
return EnumSet.of(ElementKind.CONSTRUCTOR);
case LOCAL_VARIABLE:
return EnumSet.of(
ElementKind.LOCAL_VARIABLE,
ElementKind.RESOURCE_VARIABLE,
ElementKind.EXCEPTION_PARAMETER);
case ANNOTATION_TYPE:
return EnumSet.of(ElementKind.ANNOTATION_TYPE);
case PACKAGE:
return EnumSet.of(ElementKind.PACKAGE);
case TYPE_PARAMETER:
return EnumSet.of(ElementKind.TYPE_PARAMETER);
case TYPE_USE:
return EnumSet.noneOf(ElementKind.class);
default:
// TODO: Add actual case to check for the enum constant and return Set containing
// ElementKind.MODULE. (Java 11)
if (elementType.name().contentEquals("MODULE")) {
return EnumSet.noneOf(ElementKind.class);
}
if (elementType.name().equals("RECORD_COMPONENT")) {
return EnumSet.of(ElementKind.valueOf("RECORD_COMPONENT"));
}
throw new BugInCF("Unrecognized ElementType: " + elementType);
}
}
// **********************************************************************
// Extractors for annotation values
// **********************************************************************
/**
* Returns the values of an annotation's elements, including defaults. The method with the same
* name in JavacElements cannot be used directly, because it includes a cast to
* Attribute.Compound, which doesn't hold for annotations generated by the Checker Framework.
*
* @see AnnotationMirror#getElementValues()
* @see JavacElements#getElementValuesWithDefaults(AnnotationMirror)
* @param ad annotation to examine
* @return the values of the annotation's elements, including defaults
*/
public static Map<? extends ExecutableElement, ? extends AnnotationValue>
getElementValuesWithDefaults(AnnotationMirror ad) {
Map<ExecutableElement, AnnotationValue> valMap = new HashMap<>();
if (ad.getElementValues() != null) {
valMap.putAll(ad.getElementValues());
}
for (ExecutableElement meth :
ElementFilter.methodsIn(ad.getAnnotationType().asElement().getEnclosedElements())) {
AnnotationValue defaultValue = meth.getDefaultValue();
if (defaultValue != null && !valMap.containsKey(meth)) {
valMap.put(meth, defaultValue);
}
}
return valMap;
}
/**
* Returns true if the two annotations have the same elements (fields). The arguments {@code
* am1} and {@code am2} must be the same type of annotation.
*
* @param am1 the first AnnotationMirror to compare
* @param am2 the second AnnotationMirror to compare
* @return true if if the two annotations have the same elements (fields)
*/
public static boolean sameElementValues(AnnotationMirror am1, AnnotationMirror am2) {
if (am1 == am2) {
return true;
}
Map<? extends ExecutableElement, ? extends AnnotationValue> vals1 = am1.getElementValues();
Map<? extends ExecutableElement, ? extends AnnotationValue> vals2 = am2.getElementValues();
for (ExecutableElement meth :
ElementFilter.methodsIn(
am1.getAnnotationType().asElement().getEnclosedElements())) {
AnnotationValue aval1 = vals1.get(meth);
AnnotationValue aval2 = vals2.get(meth);
if (aval1 == null) {
aval1 = meth.getDefaultValue();
}
if (aval2 == null) {
aval2 = meth.getDefaultValue();
}
if (!sameAnnotationValue(aval1, aval2)) {
return false;
}
}
return true;
}
/**
* Return true iff the two AnnotationValue objects are the same. Use this instead of
* CheckerFrameworkAnnotationValue.equals, which wouldn't get called if the receiver is some
* AnnotationValue other than CheckerFrameworkAnnotationValue.
*
* @param av1 the first AnnotationValue to compare
* @param av2 the second AnnotationValue to compare
* @return true if if the two annotation values are the same
*/
public static boolean sameAnnotationValue(AnnotationValue av1, AnnotationValue av2) {
return compareAnnotationValue(av1, av2) == 0;
}
/**
* Verify whether the element with the name {@code elementName} exists in the annotation {@code
* anno}.
*
* @param anno the annotation to examine
* @param elementName the name of the element
* @return whether the element exists in anno
*/
public static boolean hasElementValue(AnnotationMirror anno, CharSequence elementName) {
Map<? extends ExecutableElement, ? extends AnnotationValue> valmap =
anno.getElementValues();
for (ExecutableElement elem : valmap.keySet()) {
if (elem.getSimpleName().contentEquals(elementName)) {
return true;
}
}
return false;
}
/**
* Get the element with the name {@code elementName} of the annotation {@code anno}. The result
* is expected to have type {@code expectedType}.
*
* <p>For elements of array type, use {@code getElementValueArray} instead.
*
* <p>For elements of enum type, use {@code getElementValueEnum} instead.
*
* @param anno the annotation whose element to access
* @param elementName the name of the element to access
* @param expectedType the expected type of the element
* @param <T> the class of the expected type
* @param useDefaults whether to apply default values to the element
* @return the value of the element with the given name
*/
public static <T> T getElementValue(
AnnotationMirror anno,
CharSequence elementName,
Class<T> expectedType,
boolean useDefaults) {
Map<? extends ExecutableElement, ? extends AnnotationValue> valmap;
if (useDefaults) {
valmap = getElementValuesWithDefaults(anno);
} else {
valmap = anno.getElementValues();
}
for (ExecutableElement elem : valmap.keySet()) {
if (elem.getSimpleName().contentEquals(elementName)) {
AnnotationValue val = valmap.get(elem);
return expectedType.cast(val.getValue());
}
}
throw new NoSuchElementException(
String.format(
"No element with name \'%s\' in annotation %s; useDefaults=%s, valmap.keySet()=%s",
elementName, anno, useDefaults, valmap.keySet()));
}
/** Differentiates NoSuchElementException from other BugInCF. */
@SuppressWarnings("serial")
private static class NoSuchElementException extends BugInCF {
/**
* Constructs a new NoSuchElementException.
*
* @param message the detail message
*/
public NoSuchElementException(String message) {
super(message);
}
}
/**
* Get the element with the name {@code elementName} of the annotation {@code anno}, or return
* null if no such element exists.
*
* @param anno the annotation whose element to access
* @param elementName the name of the element to access
* @param expectedType the expected type of the element
* @param <T> the class of the expected type
* @param useDefaults whether to apply default values to the element
* @return the value of the element with the given name, or null
*/
public static <T> @Nullable T getElementValueOrNull(
AnnotationMirror anno,
CharSequence elementName,
Class<T> expectedType,
boolean useDefaults) {
// This implementation permits getElementValue a more detailed error message than if
// getElementValue called getElementValueOrNull and threw an error if the result was null.
try {
return getElementValue(anno, elementName, expectedType, useDefaults);
} catch (NoSuchElementException e) {
return null;
}
}
/**
* Get the element with the name {@code name} of the annotation {@code anno}. The result is an
* enum of type {@code T}.
*
* @param anno the annotation to disassemble
* @param elementName the name of the element to access
* @param expectedType the expected type used to cast the return type, an enum
* @param <T> the class of the expected type
* @param useDefaults whether to apply default values to the element
* @return the value of the element with the given name
*/
public static <T extends Enum<T>> T getElementValueEnum(
AnnotationMirror anno,
CharSequence elementName,
Class<T> expectedType,
boolean useDefaults) {
VarSymbol vs = getElementValue(anno, elementName, VarSymbol.class, useDefaults);
T value = Enum.valueOf(expectedType, vs.getSimpleName().toString());
return value;
}
/**
* Get the element with the name {@code elementName} of the annotation {@code anno}, where the
* element has an array type. One element of the result is expected to have type {@code
* expectedType}.
*
* <p>Parameter useDefaults is used to determine whether default values should be used for
* annotation values. Finding defaults requires more computation, so should be false when no
* defaulting is needed.
*
* @param anno the annotation to disassemble
* @param elementName the name of the element to access
* @param expectedType the expected type used to cast the return type
* @param <T> the class of the expected type
* @param useDefaults whether to apply default values to the element
* @return the value of the element with the given name
*/
public static <T> List<T> getElementValueArray(
AnnotationMirror anno,
CharSequence elementName,
Class<T> expectedType,
boolean useDefaults) {
@SuppressWarnings("unchecked")
List<AnnotationValue> la = getElementValue(anno, elementName, List.class, useDefaults);
List<T> result = new ArrayList<>(la.size());
for (AnnotationValue a : la) {
result.add(expectedType.cast(a.getValue()));
}
return result;
}
/**
* Get the element with the name {@code elementName} of the annotation {@code anno}, or the
* default value if no element is present explicitly, where the element has an array type and
* the elements are {@code Enum}s. One element of the result is expected to have type {@code
* expectedType}.
*
* @param anno the annotation to disassemble
* @param elementName the name of the element to access
* @param expectedType the expected type used to cast the return type
* @param <T> the class of the expected type
* @param useDefaults whether to apply default values to the element
* @return the value of the element with the given name
*/
public static <T extends Enum<T>> List<T> getElementValueEnumArray(
AnnotationMirror anno,
CharSequence elementName,
Class<T> expectedType,
boolean useDefaults) {
@SuppressWarnings("unchecked")
List<AnnotationValue> la = getElementValue(anno, elementName, List.class, useDefaults);
List<T> result = new ArrayList<>(la.size());
for (AnnotationValue a : la) {
T value = Enum.valueOf(expectedType, a.getValue().toString());
result.add(value);
}
return result;
}
/**
* Get the Name of the class that is referenced by element {@code elementName}.
*
* <p>This is a convenience method for the most common use-case. It is like {@code
* getElementValue(anno, elementName, ClassType.class).getQualifiedName()}, but this method
* ensures consistent use of the qualified name.
*
* @param anno the annotation to disassemble
* @param elementName the name of the element to access
* @param useDefaults whether to apply default values to the element
* @return the name of the class that is referenced by element with the given name
*/
@SuppressWarnings("signature") // https://tinyurl.com/cfissue/658 for getQualifiedName
public static @DotSeparatedIdentifiers Name getElementValueClassName(
AnnotationMirror anno, CharSequence elementName, boolean useDefaults) {
Type.ClassType ct = getElementValue(anno, elementName, Type.ClassType.class, useDefaults);
// TODO: Is it a problem that this returns the type parameters too? Should I cut them off?
return ct.asElement().getQualifiedName();
}
/**
* Get the list of Names of the classes that are referenced by element {@code elementName}. It
* fails if the class wasn't found. Like {@link #getElementValueClassNames}, but returns classes
* rather than names.
*
* @param anno the annotation whose field to access
* @param annoElement the element/field of {@code anno} whose content is a list of classes
* @param useDefaults whether to apply default values to the element
* @return the names of classes in {@code anno.annoElement}
*/
public static List<Name> getElementValueClassNames(
AnnotationMirror anno, CharSequence annoElement, boolean useDefaults) {
List<Type.ClassType> la =
getElementValueArray(anno, annoElement, Type.ClassType.class, useDefaults);
List<Name> names = new ArrayList<>();
for (Type.ClassType classType : la) {
names.add(classType.asElement().getQualifiedName());
}
return names;
}
// The Javadoc doesn't use @link because framework is a different project than this one
// (javacutil).
/**
* See
* org.checkerframework.framework.type.QualifierHierarchy#updateMappingToMutableSet(QualifierHierarchy,
* Map, Object, AnnotationMirror).
*/
public static <T> void updateMappingToImmutableSet(
Map<T, Set<AnnotationMirror>> map, T key, Set<AnnotationMirror> newQual) {
Set<AnnotationMirror> result = AnnotationUtils.createAnnotationSet();
// TODO: if T is also an AnnotationMirror, should we use areSame?
if (!map.containsKey(key)) {
result.addAll(newQual);
} else {
result.addAll(map.get(key));
result.addAll(newQual);
}
map.put(key, Collections.unmodifiableSet(result));
}
/**
* Returns the annotations explicitly written on a constructor result. Callers should check that
* {@code constructorDeclaration} is in fact a declaration of a constructor.
*
* @param constructorDeclaration declaration tree of constructor
* @return set of annotations explicit on the resulting type of the constructor
*/
public static Set<AnnotationMirror> getExplicitAnnotationsOnConstructorResult(
MethodTree constructorDeclaration) {
Set<AnnotationMirror> annotationSet = AnnotationUtils.createAnnotationSet();
ModifiersTree modifiersTree = constructorDeclaration.getModifiers();
if (modifiersTree != null) {
List<? extends AnnotationTree> annotationTrees = modifiersTree.getAnnotations();
annotationSet.addAll(TreeUtils.annotationsFromTypeAnnotationTrees(annotationTrees));
}
return annotationSet;
}
/**
* Returns true if anno is a declaration annotation. In other words, returns true if anno cannot
* be written on uses of types.
*
* @param anno the AnnotationMirror
* @return true if anno is a declaration annotation.
*/
public static boolean isDeclarationAnnotation(AnnotationMirror anno) {
TypeElement elem = (TypeElement) anno.getAnnotationType().asElement();
Target t = elem.getAnnotation(Target.class);
if (t == null) {
return true;
}
for (ElementType elementType : t.value()) {
if (elementType == ElementType.TYPE_USE) {
return false;
}
}
return true;
}
/**
* Returns true if the given array contains {@link ElementType#TYPE_USE}, false otherwise.
*
* @param elements an array of {@link ElementType} values
* @param cls the annotation class being tested; used for diagnostic messages only
* @return true iff the give array contains {@link ElementType#TYPE_USE}
* @throws RuntimeException if the array contains both {@link ElementType#TYPE_USE} and
* something besides {@link ElementType#TYPE_PARAMETER}
*/
public static boolean hasTypeQualifierElementTypes(ElementType[] elements, Class<?> cls) {
// True if the array contains TYPE_USE
boolean hasTypeUse = false;
// Non-null if the array contains an element other than TYPE_USE or TYPE_PARAMETER
ElementType otherElementType = null;
for (ElementType element : elements) {
if (element == ElementType.TYPE_USE) {
hasTypeUse = true;
} else if (element != ElementType.TYPE_PARAMETER) {
otherElementType = element;
}
if (hasTypeUse && otherElementType != null) {
throw new BugInCF(
"@Target meta-annotation should not contain both TYPE_USE and "
+ otherElementType
+ ", for annotation "
+ cls.getName());
}
}
return hasTypeUse;
}
}