1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.juneau.commons.reflect;
18
19 import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
20 import static org.apache.juneau.commons.utils.AssertionUtils.*;
21 import static org.apache.juneau.commons.utils.CollectionUtils.*;
22 import static org.apache.juneau.commons.utils.ThrowableUtils.*;
23 import static org.apache.juneau.commons.utils.Utils.*;
24
25 import java.lang.annotation.*;
26 import java.util.*;
27 import java.util.function.*;
28
29 import org.apache.juneau.commons.annotation.*;
30 import org.apache.juneau.commons.collections.*;
31
32 /**
33 * Encapsulates information about an annotation instance and the element it's declared on.
34 *
35 * <p>
36 * This class provides a convenient wrapper around Java annotations that allows you to:
37 * <ul>
38 * <li>Access annotation values in a type-safe manner
39 * <li>Query annotation properties without reflection boilerplate
40 * <li>Track where the annotation was found (class, method, field, etc.)
41 * <li>Sort annotations by precedence using ranks
42 * </ul>
43 *
44 * <h5 class='section'>Example:</h5>
45 * <p class='bjava'>
46 * <jc>// Get annotation info from a class</jc>
47 * ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
48 * Optional<AnnotationInfo<MyAnnotation>> <jv>ai</jv> =
49 * <jv>ci</jv>.getAnnotations(MyAnnotation.<jk>class</jk>).findFirst();
50 *
51 * <jc>// Access annotation values</jc>
52 * <jv>ai</jv>.ifPresent(<jv>x</jv> -> {
53 * String <jv>value</jv> = <jv>x</jv>.getValue(String.<jk>class</jk>, <js>"value"</js>).orElse(<js>"default"</js>);
54 * <jk>int</jk> <jv>priority</jv> = <jv>x</jv>.getInt(<js>"priority"</js>).orElse(0);
55 * });
56 * </p>
57 *
58 * <h5 class='section'>See Also:</h5><ul>
59 * <li class='jc'>{@link ClassInfo}
60 * <li class='jc'>{@link MethodInfo}
61 * <li class='jc'>{@link FieldInfo}
62 * <li class='jc'>{@link ConstructorInfo}
63 * <li class='jc'>{@link ParameterInfo}
64 * <li class='jc'>{@link PackageInfo}
65 * </ul>
66 *
67 * @param <T> The annotation type.
68 */
69 public class AnnotationInfo<T extends Annotation> {
70
71 /**
72 * Creates a new annotation info object.
73 *
74 * <h5 class='section'>Example:</h5>
75 * <p class='bjava'>
76 * <jc>// Create annotation info for a class annotation</jc>
77 * ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
78 * MyAnnotation <jv>annotation</jv> = <jv>ci</jv>.inner().getAnnotation(MyAnnotation.<jk>class</jk>);
79 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = AnnotationInfo.<jsm>of</jsm>(<jv>ci</jv>, <jv>annotation</jv>);
80 * </p>
81 *
82 * @param <A> The annotation type.
83 * @param on The annotatable object where the annotation was found (class, method, field, constructor, parameter, or package).
84 * @param value The annotation instance. Must not be <jk>null</jk>.
85 * @return A new {@link AnnotationInfo} object wrapping the annotation.
86 */
87 public static <A extends Annotation> AnnotationInfo<A> of(Annotatable on, A value) {
88 return new AnnotationInfo<>(on, value);
89 }
90
91 private static int findRank(Object a) {
92 // @formatter:off
93 return ClassInfo.of(a).getAllMethods().stream()
94 .filter(m -> m.hasName("rank") && m.hasReturnType(int.class))
95 .findFirst()
96 .map(m -> safe(() -> (int)m.invoke(a)))
97 .orElse(0);
98 // @formatter:on
99 }
100
101 private final Annotatable annotatable;
102 final int rank;
103
104 private T a; // Effectively final
105
106 private final Supplier<List<MethodInfo>> methods = mem(() -> stream(a.annotationType().getMethods()).map(m -> MethodInfo.of(info(a.annotationType()), m)).toList());
107
108 /**
109 * Constructor.
110 *
111 * @param on The annotatable object where the annotation was found.
112 * @param a The annotation instance.
113 */
114 AnnotationInfo(Annotatable on, T a) {
115 this.annotatable = on; // TODO - Shouldn't allow null.
116 this.a = assertArgNotNull("a", a);
117 this.rank = findRank(a);
118 }
119
120 /**
121 * Returns the annotation type of this annotation.
122 *
123 * <p>
124 * Same as calling {@link Annotation#annotationType()}.
125 *
126 * <h5 class='section'>Example:</h5>
127 * <p class='bjava'>
128 * AnnotationInfo<Deprecated> <jv>ai</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getAnnotation(Deprecated.<jk>class</jk>);
129 * Class<? <jk>extends</jk> Annotation> <jv>type</jv> = <jv>ai</jv>.annotationType(); <jc>// Returns Deprecated.class</jc>
130 * </p>
131 *
132 * @return The annotation type of this annotation.
133 * @see Annotation#annotationType()
134 */
135 public Class<? extends Annotation> annotationType() {
136 return a.annotationType();
137 }
138
139 /**
140 * Casts this annotation info to a specific annotation type.
141 *
142 * <p>
143 * This is useful when you have an {@code AnnotationInfo<?>} and need to narrow it to a specific type.
144 *
145 * <h5 class='section'>Example:</h5>
146 * <p class='bjava'>
147 * AnnotationInfo<?> <jv>ai</jv> = ...;
148 *
149 * <jc>// Safe cast</jc>
150 * AnnotationInfo<MyAnnotation> <jv>myAi</jv> = <jv>ai</jv>.cast(MyAnnotation.<jk>class</jk>);
151 * <jk>if</jk> (<jv>myAi</jv> != <jk>null</jk>) {
152 * <jc>// Use strongly-typed annotation info</jc>
153 * }
154 * </p>
155 *
156 * @param <A> The annotation type to cast to.
157 * @param type The annotation type to cast to.
158 * @return This annotation info cast to the specified type, or <jk>null</jk> if the annotation is not of the specified type.
159 */
160 @SuppressWarnings("unchecked")
161 public <A extends Annotation> AnnotationInfo<A> cast(Class<A> type) {
162 return type.isInstance(a) ? (AnnotationInfo<A>)this : null;
163 }
164
165 /**
166 * Returns true if the specified object represents an annotation that is logically equivalent to this one.
167 *
168 * <p>
169 * Same as calling {@link Annotation#equals(Object)} on the wrapped annotation.
170 *
171 * <p>
172 * Two annotations are considered equal if:
173 * <ul>
174 * <li>They are of the same annotation type
175 * <li>All their corresponding member values are equal
176 * </ul>
177 *
178 * @param o The reference object with which to compare.
179 * @return <jk>true</jk> if the specified object is equal to this annotation.
180 * @see Annotation#equals(Object)
181 */
182 @Override /* Overridden from Object */
183 public boolean equals(Object o) {
184 if (o instanceof AnnotationInfo o2)
185 return a.equals(o2.a);
186 return a.equals(o);
187 }
188
189 /**
190 * Returns the value of the specified method on this annotation as a boolean.
191 *
192 * <h5 class='section'>Example:</h5>
193 * <p class='bjava'>
194 * <jc>// For annotation: @MyAnnotation(enabled=true)</jc>
195 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
196 * <jk>boolean</jk> <jv>enabled</jv> = <jv>ai</jv>.getBoolean(<js>"enabled"</js>).orElse(<jk>false</jk>); <jc>// Returns true</jc>
197 * </p>
198 *
199 * @param methodName The method name.
200 * @return An {@link Optional} containing the value as a boolean, or empty if not found or not a {@code boolean} type.
201 */
202 public Optional<Boolean> getBoolean(String methodName) {
203 return getMethod(methodName).filter(x -> x.hasReturnType(boolean.class)).map(x -> (Boolean)x.invoke(a));
204 }
205
206 /**
207 * Returns the value of the specified method on this annotation as a class array.
208 *
209 * <p>
210 * For type-safe access to an array of classes of a specific supertype, use {@link #getClassArray(String, Class)}.
211 *
212 * <h5 class='section'>Example:</h5>
213 * <p class='bjava'>
214 * <jc>// For annotation: @MyAnnotation(types={String.class, Integer.class})</jc>
215 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
216 * Class<?>[] <jv>types</jv> = <jv>ai</jv>.getClassArray(<js>"types"</js>).orElse(<jk>new</jk> Class[0]); <jc>// Returns [String.class, Integer.class]</jc>
217 * </p>
218 *
219 * @param methodName The method name.
220 * @return An {@link Optional} containing the class array value, or empty if not found or not a {@code Class[]} type.
221 */
222 @SuppressWarnings("unchecked")
223 public Optional<Class<?>[]> getClassArray(String methodName) {
224 return (Optional<Class<?>[]>)(Optional<?>)getMethod(methodName).filter(x -> x.hasReturnType(Class[].class)).map(x -> x.invoke(a));
225 }
226
227 /**
228 * Returns the value of the specified method on this annotation as a class array of a specific type.
229 *
230 * <h5 class='section'>Example:</h5>
231 * <p class='bjava'>
232 * <jc>// Get an array of serializer classes from an annotation</jc>
233 * Optional<Class<? <jk>extends</jk> Serializer>[]> <jv>serializerClasses</jv> =
234 * <jv>annotationInfo</jv>.getClassArray(<js>"serializers"</js>, Serializer.<jk>class</jk>);
235 * </p>
236 *
237 * @param <T> The expected supertype of the classes.
238 * @param methodName The method name.
239 * @param type The expected supertype of the class values.
240 * @return An optional containing the value of the specified method cast to the expected type,
241 * or empty if not found, not a class array, or any element is not assignable to the expected type.
242 */
243 @SuppressWarnings({ "unchecked", "hiding" })
244 public <T> Optional<Class<? extends T>[]> getClassArray(String methodName, Class<T> type) {
245 // @formatter:off
246 return getMethod(methodName)
247 .filter(x -> x.hasReturnType(Class[].class))
248 .map(x -> (Class<?>[])x.invoke(a))
249 .filter(arr -> {
250 for (var c : arr) {
251 if (!type.isAssignableFrom(c))
252 return false;
253 }
254 return true;
255 })
256 .map(x -> (Class<? extends T>[])x);
257 // @formatter:on
258 }
259
260 /**
261 * Returns the value of the specified method on this annotation as a class.
262 *
263 * <p>
264 * For type-safe access to a class of a specific supertype, use {@link #getClassValue(String, Class)}.
265 *
266 * <h5 class='section'>Example:</h5>
267 * <p class='bjava'>
268 * <jc>// For annotation: @MyAnnotation(type=String.class)</jc>
269 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
270 * Class<?> <jv>type</jv> = <jv>ai</jv>.getClassValue(<js>"type"</js>).orElse(<jk>null</jk>); <jc>// Returns String.class</jc>
271 * </p>
272 *
273 * @param methodName The method name.
274 * @return An {@link Optional} containing the class value, or empty if not found or not a {@link Class} type.
275 */
276 @SuppressWarnings("unchecked")
277 public Optional<Class<?>> getClassValue(String methodName) {
278 return (Optional<Class<?>>)(Optional<?>)getMethod(methodName).filter(x -> x.hasReturnType(Class.class)).map(x -> x.invoke(a));
279 }
280
281 /**
282 * Returns the value of the specified method on this annotation as a class of a specific type.
283 *
284 * <h5 class='section'>Example:</h5>
285 * <p class='bjava'>
286 * <jc>// Get a serializer class from an annotation</jc>
287 * Optional<Class<? <jk>extends</jk> Serializer>> <jv>serializerClass</jv> =
288 * <jv>annotationInfo</jv>.getClassValue(<js>"serializer"</js>, Serializer.<jk>class</jk>);
289 * </p>
290 *
291 * @param <T> The expected supertype of the class.
292 * @param methodName The method name.
293 * @param type The expected supertype of the class value.
294 * @return An optional containing the value of the specified method cast to the expected type,
295 * or empty if not found, not a class, or not assignable to the expected type.
296 */
297 @SuppressWarnings({ "unchecked", "hiding" })
298 public <T> Optional<Class<? extends T>> getClassValue(String methodName, Class<T> type) {
299 // @formatter:off
300 return getMethod(methodName)
301 .filter(x -> x.hasReturnType(Class.class))
302 .map(x -> (Class<?>)x.invoke(a))
303 .filter(type::isAssignableFrom)
304 .map(x -> (Class<? extends T>)x);
305 // @formatter:on
306 }
307
308 //-----------------------------------------------------------------------------------------------------------------
309 // Annotation interface methods
310 //-----------------------------------------------------------------------------------------------------------------
311
312 /**
313 * Returns the value of the specified method on this annotation as a double.
314 *
315 * <h5 class='section'>Example:</h5>
316 * <p class='bjava'>
317 * <jc>// For annotation: @MyAnnotation(threshold=0.95)</jc>
318 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
319 * <jk>double</jk> <jv>threshold</jv> = <jv>ai</jv>.getDouble(<js>"threshold"</js>).orElse(0.0); <jc>// Returns 0.95</jc>
320 * </p>
321 *
322 * @param methodName The method name.
323 * @return An {@link Optional} containing the value as a double, or empty if not found or not a {@code double} type.
324 */
325 public Optional<Double> getDouble(String methodName) {
326 return getMethod(methodName).filter(x -> x.hasReturnType(double.class)).map(x -> (Double)x.invoke(a));
327 }
328
329 /**
330 * Returns the value of the specified method on this annotation as a float.
331 *
332 * <h5 class='section'>Example:</h5>
333 * <p class='bjava'>
334 * <jc>// For annotation: @MyAnnotation(weight=0.5f)</jc>
335 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
336 * <jk>float</jk> <jv>weight</jv> = <jv>ai</jv>.getFloat(<js>"weight"</js>).orElse(0.0f); <jc>// Returns 0.5f</jc>
337 * </p>
338 *
339 * @param methodName The method name.
340 * @return An {@link Optional} containing the value as a float, or empty if not found or not a {@code float} type.
341 */
342 public Optional<Float> getFloat(String methodName) {
343 return getMethod(methodName).filter(x -> x.hasReturnType(float.class)).map(x -> (Float)x.invoke(a));
344 }
345
346 /**
347 * Returns the value of the specified method on this annotation as an integer.
348 *
349 * <h5 class='section'>Example:</h5>
350 * <p class='bjava'>
351 * <jc>// For annotation: @MyAnnotation(priority=5)</jc>
352 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
353 * <jk>int</jk> <jv>priority</jv> = <jv>ai</jv>.getInt(<js>"priority"</js>).orElse(0); <jc>// Returns 5</jc>
354 * </p>
355 *
356 * @param methodName The method name.
357 * @return An {@link Optional} containing the value as an integer, or empty if not found or not an {@code int} type.
358 */
359 public Optional<Integer> getInt(String methodName) {
360 return getMethod(methodName).filter(x -> x.hasReturnType(int.class)).map(x -> (Integer)x.invoke(a));
361 }
362
363 /**
364 * Returns the value of the specified method on this annotation as a long.
365 *
366 * <h5 class='section'>Example:</h5>
367 * <p class='bjava'>
368 * <jc>// For annotation: @MyAnnotation(timestamp=1234567890L)</jc>
369 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
370 * <jk>long</jk> <jv>timestamp</jv> = <jv>ai</jv>.getLong(<js>"timestamp"</js>).orElse(0L); <jc>// Returns 1234567890L</jc>
371 * </p>
372 *
373 * @param methodName The method name.
374 * @return An {@link Optional} containing the value as a long, or empty if not found or not a {@code long} type.
375 */
376 public Optional<Long> getLong(String methodName) {
377 return getMethod(methodName).filter(x -> x.hasReturnType(long.class)).map(x -> (Long)x.invoke(a));
378 }
379
380 /**
381 * Returns the method with the specified name on this annotation.
382 *
383 * <h5 class='section'>Example:</h5>
384 * <p class='bjava'>
385 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
386 * Optional<MethodInfo> <jv>method</jv> = <jv>ai</jv>.getMethod(<js>"value"</js>);
387 * <jv>method</jv>.ifPresent(<jv>m</jv> -> System.<jsf>out</jsf>.println(<jv>m</jv>.getReturnType()));
388 * </p>
389 *
390 * @param methodName The method name to look for.
391 * @return An {@link Optional} containing the method info, or empty if method not found.
392 */
393 public Optional<MethodInfo> getMethod(String methodName) {
394 return methods.get().stream().filter(x -> eq(methodName, x.getSimpleName())).findFirst();
395 }
396
397 //-----------------------------------------------------------------------------------------------------------------
398 // Private helper methods
399 //-----------------------------------------------------------------------------------------------------------------
400
401 /**
402 * Returns the simple class name of this annotation.
403 *
404 * <h5 class='section'>Example:</h5>
405 * <p class='bjava'>
406 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
407 * String <jv>name</jv> = <jv>ai</jv>.getName(); <jc>// Returns "MyAnnotation"</jc>
408 * </p>
409 *
410 * @return The simple class name of the annotation (e.g., {@code "Override"} for {@code @Override}).
411 */
412 public String getName() { return cns(a.annotationType()); }
413
414 /**
415 * Returns the rank of this annotation for sorting by precedence.
416 *
417 * <p>
418 * The rank is determined by checking if the annotation has a {@code rank()} method that returns an {@code int}.
419 * If found, that value is used; otherwise the rank defaults to {@code 0}.
420 *
421 * <p>
422 * Higher rank values indicate higher precedence when multiple annotations of the same type are present.
423 *
424 * <h5 class='section'>Example:</h5>
425 * <p class='bjava'>
426 * <jc>// Annotation with rank method</jc>
427 * <ja>@interface</ja> MyAnnotation {
428 * <jk>int</jk> rank() <jk>default</jk> 0;
429 * }
430 *
431 * <jc>// Get rank from annotation info</jc>
432 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
433 * <jk>int</jk> <jv>rank</jv> = <jv>ai</jv>.getRank(); <jc>// Returns value from rank() method</jc>
434 * </p>
435 *
436 * @return The rank of this annotation, or {@code 0} if no rank method exists.
437 */
438 public int getRank() { return rank; }
439
440 /**
441 * Returns the return type of the specified method on this annotation.
442 *
443 * <h5 class='section'>Example:</h5>
444 * <p class='bjava'>
445 * Optional<ClassInfo> <jv>returnType</jv> = <jv>annotationInfo</jv>.getReturnType(<js>"value"</js>);
446 * </p>
447 *
448 * @param methodName The method name.
449 * @return An optional containing the return type of the specified method, or empty if method not found.
450 */
451 public Optional<ClassInfo> getReturnType(String methodName) {
452 return getMethod(methodName).map(x -> x.getReturnType());
453 }
454
455 /**
456 * Returns the value of the specified method on this annotation as a string.
457 *
458 * <h5 class='section'>Example:</h5>
459 * <p class='bjava'>
460 * <jc>// For annotation: @MyAnnotation(name="John", age=30)</jc>
461 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
462 * String <jv>name</jv> = <jv>ai</jv>.getString(<js>"name"</js>).orElse(<js>"unknown"</js>); <jc>// Returns "John"</jc>
463 * </p>
464 *
465 * @param methodName The method name.
466 * @return An {@link Optional} containing the value as a string, or empty if not found or not a string type.
467 */
468 public Optional<String> getString(String methodName) {
469 return getMethod(methodName).filter(x -> x.hasReturnType(String.class)).map(x -> s(x.invoke(a)));
470 }
471
472 /**
473 * Returns the value of the specified method on this annotation as a string array.
474 *
475 * <h5 class='section'>Example:</h5>
476 * <p class='bjava'>
477 * <jc>// For annotation: @MyAnnotation(tags={"foo", "bar"})</jc>
478 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
479 * String[] <jv>tags</jv> = <jv>ai</jv>.getStringArray(<js>"tags"</js>).orElse(<jk>new</jk> String[0]); <jc>// Returns ["foo", "bar"]</jc>
480 * </p>
481 *
482 * @param methodName The method name.
483 * @return An {@link Optional} containing the string array value, or empty if not found or not a {@code String[]} type.
484 */
485 public Optional<String[]> getStringArray(String methodName) {
486 return getMethod(methodName).filter(x -> x.hasReturnType(String[].class)).map(x -> (String[])x.invoke(a));
487 }
488
489 /**
490 * Returns the value of the {@code value()} method on this annotation as a string.
491 *
492 * <p>
493 * This is a convenience method equivalent to calling {@link #getString(String) getString("value")}.
494 *
495 * <h5 class='section'>Example:</h5>
496 * <p class='bjava'>
497 * <jc>// For annotation: @MyAnnotation("foo")</jc>
498 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
499 * String <jv>value</jv> = <jv>ai</jv>.getValue().orElse(<js>"default"</js>); <jc>// Returns "foo"</jc>
500 * </p>
501 *
502 * @return An {@link Optional} containing the value of the {@code value()} method, or empty if not found or not a string.
503 */
504 public Optional<String> getValue() { return getString("value"); }
505
506 /**
507 * Returns the value of a specific annotation method.
508 *
509 * <p>
510 * This method provides type-safe access to annotation field values without requiring
511 * explicit reflection calls or casting.
512 *
513 * <h5 class='section'>Example:</h5>
514 * <p class='bjava'>
515 * <jc>// For annotation: @interface MyAnnotation { String value(); int priority(); }</jc>
516 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
517 *
518 * <jc>// Get string value</jc>
519 * Optional<String> <jv>value</jv> = <jv>ai</jv>.getValue(String.<jk>class</jk>, <js>"value"</js>);
520 *
521 * <jc>// Get int value</jc>
522 * Optional<Integer> <jv>priority</jv> = <jv>ai</jv>.getValue(Integer.<jk>class</jk>, <js>"priority"</js>);
523 * </p>
524 *
525 * @param <V> The expected type of the annotation field value.
526 * @param type The expected class of the annotation field value.
527 * @param name The name of the annotation method (field).
528 * @return An {@link Optional} containing the value if found and type matches, empty otherwise.
529 */
530 @SuppressWarnings("unchecked")
531 public <V> Optional<V> getValue(Class<V> type, String name) {
532 // @formatter:off
533 return methods.get().stream()
534 .filter(m -> eq(m.getName(), name) && eq(m.getReturnType().inner(), type))
535 .map(m -> safe(() -> (V)m.invoke(a)))
536 .findFirst();
537 // @formatter:on
538 }
539
540 /**
541 * Returns <jk>true</jk> if this annotation is itself annotated with the specified annotation.
542 *
543 * <p>
544 * This checks for meta-annotations on the annotation type.
545 *
546 * <h5 class='section'>Example:</h5>
547 * <p class='bjava'>
548 * <jc>// Check if @MyAnnotation is annotated with @Documented</jc>
549 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
550 * <jk>boolean</jk> <jv>isDocumented</jv> = <jv>ai</jv>.hasAnnotation(Documented.<jk>class</jk>);
551 * </p>
552 *
553 * @param <A> The meta-annotation type.
554 * @param type The meta-annotation to test for.
555 * @return <jk>true</jk> if this annotation is annotated with the specified annotation.
556 */
557 public <A extends Annotation> boolean hasAnnotation(Class<A> type) {
558 return nn(this.a.annotationType().getAnnotation(type));
559 }
560
561 /**
562 * Returns the hash code of this annotation.
563 *
564 * <p>
565 * Same as calling {@link Annotation#hashCode()} on the wrapped annotation.
566 *
567 * <p>
568 * The hash code of an annotation is the sum of the hash codes of its members (including those with default values).
569 *
570 * @return The hash code of this annotation.
571 * @see Annotation#hashCode()
572 */
573 @Override /* Overridden from Object */
574 public int hashCode() {
575 return a.hashCode();
576 }
577
578 /**
579 * Returns <jk>true</jk> if this annotation has the specified fully-qualified name.
580 *
581 * <h5 class='section'>Example:</h5>
582 * <p class='bjava'>
583 * <jk>boolean</jk> <jv>isName</jv> = <jv>annotationInfo</jv>.hasName(<js>"org.apache.juneau.annotation.Name"</js>);
584 * </p>
585 *
586 * @param value The fully-qualified name to check.
587 * @return <jk>true</jk> if this annotation has the specified fully-qualified name.
588 */
589 public boolean hasName(String value) {
590 return eq(value, a.annotationType().getName());
591 }
592
593 /**
594 * Returns <jk>true</jk> if this annotation has the specified simple name.
595 *
596 * <h5 class='section'>Example:</h5>
597 * <p class='bjava'>
598 * <jk>boolean</jk> <jv>isName</jv> = <jv>annotationInfo</jv>.hasSimpleName(<js>"Name"</js>);
599 * </p>
600 *
601 * @param value The simple name to check.
602 * @return <jk>true</jk> if this annotation has the specified simple name.
603 */
604 public boolean hasSimpleName(String value) {
605 return eq(value, a.annotationType().getSimpleName());
606 }
607
608 /**
609 * Returns the wrapped annotation instance.
610 *
611 * <h5 class='section'>Example:</h5>
612 * <p class='bjava'>
613 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
614 * MyAnnotation <jv>annotation</jv> = <jv>ai</jv>.inner();
615 *
616 * <jc>// Access annotation methods directly</jc>
617 * String <jv>value</jv> = <jv>annotation</jv>.value();
618 * </p>
619 *
620 * @return The wrapped annotation instance.
621 */
622 public T inner() {
623 return a;
624 }
625
626 /**
627 * Returns <jk>true</jk> if this annotation is in the specified {@link AnnotationGroup}.
628 *
629 * <p>
630 * Annotation groups are used to logically group related annotations together.
631 * This checks if the annotation is annotated with {@link AnnotationGroup} and if
632 * the group value matches the specified type.
633 *
634 * <h5 class='section'>Example:</h5>
635 * <p class='bjava'>
636 * <jc>// Define an annotation group</jc>
637 * <ja>@interface</ja> MyGroup {}
638 *
639 * <jc>// Annotation in the group</jc>
640 * <ja>@AnnotationGroup</ja>(MyGroup.<jk>class</jk>)
641 * <ja>@interface</ja> MyAnnotation {}
642 *
643 * <jc>// Check if annotation is in group</jc>
644 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
645 * <jk>boolean</jk> <jv>inGroup</jv> = <jv>ai</jv>.isInGroup(MyGroup.<jk>class</jk>); <jc>// Returns true</jc>
646 * </p>
647 *
648 * @param <A> The group annotation type.
649 * @param group The group annotation class to test for.
650 * @return <jk>true</jk> if this annotation is in the specified group.
651 * @see AnnotationGroup
652 */
653 public <A extends Annotation> boolean isInGroup(Class<A> group) {
654 var x = a.annotationType().getAnnotation(AnnotationGroup.class);
655 return (nn(x) && x.value().equals(group));
656 }
657
658 /**
659 * Returns <jk>true</jk> if this annotation is of the specified type.
660 *
661 * <h5 class='section'>Example:</h5>
662 * <p class='bjava'>
663 * AnnotationInfo<?> <jv>ai</jv> = ...;
664 *
665 * <jk>if</jk> (<jv>ai</jv>.isType(MyAnnotation.<jk>class</jk>)) {
666 * <jc>// Handle MyAnnotation specifically</jc>
667 * }
668 * </p>
669 *
670 * @param <A> The annotation type to test for.
671 * @param type The annotation type to test against.
672 * @return <jk>true</jk> if this annotation's type is exactly the specified type.
673 */
674 public <A extends Annotation> boolean isType(Class<A> type) {
675 return this.a.annotationType() == type;
676 }
677
678 /**
679 * Converts this annotation info to a map representation for debugging purposes.
680 *
681 * <p>
682 * The returned map contains:
683 * <ul>
684 * <li>The annotatable element's type and label (e.g., {@code "CLASS_TYPE" -> "com.example.MyClass"})
685 * <li>A nested map with the annotation's simple name as key and its non-default values
686 * </ul>
687 *
688 * <p>
689 * Only annotation values that differ from their default values are included.
690 *
691 * <h5 class='section'>Example:</h5>
692 * <p class='bjava'>
693 * AnnotationInfo<MyAnnotation> <jv>ai</jv> = ...;
694 * LinkedHashMap<String,Object> <jv>map</jv> = <jv>ai</jv>.toMap();
695 * <jc>// Returns: {"CLASS_TYPE": "MyClass", "@MyAnnotation": {"value": "foo", "priority": 5}}</jc>
696 * </p>
697 *
698 * @return A new map showing the attributes of this annotation info.
699 */
700 protected FluentMap<String,Object> properties() {
701 // @formatter:off
702 var ca = info(a.annotationType());
703 var ja = mapb().sorted().buildFluent(); // NOAI
704 ca.getDeclaredMethods().stream().forEach(x -> {
705 safeOptCatch(() -> {
706 var val = x.invoke(a);
707 var d = x.inner().getDefaultValue();
708 // Add values only if they're different from the default.
709 if (neq(val, d)) {
710 if (! (isArray(val) && length(val) == 0 && isArray(d) && length(d) == 0))
711 return val;
712 }
713 return null;
714 }, e -> lm(e)).ifPresent(v -> ja.a(x.getName(), v));
715 });
716 return filteredBeanPropertyMap()
717 .a(s(annotatable.getAnnotatableType()), annotatable.getLabel())
718 .a("@" + ca.getNameSimple(), ja);
719 // @formatter:on
720 }
721
722 /**
723 * Returns a simple string representation of this annotation showing the annotation type and location.
724 *
725 * <p>
726 * Format: {@code @AnnotationName(on=location)}
727 *
728 * <h5 class='section'>Examples:</h5>
729 * <ul>
730 * <li>{@code @Rest(on=MyClass)} - Annotation on a class
731 * <li>{@code @RestGet(on=MyClass.myMethod)} - Annotation on a method
732 * <li>{@code @Inject(on=MyClass.myField)} - Annotation on a field
733 * <li>{@code @PackageAnnotation(on=my.package)} - Annotation on a package
734 * </ul>
735 *
736 * @return A simple string representation of this annotation.
737 */
738 public String toSimpleString() {
739 return "@" + cns(a.annotationType()) + "(on=" + annotatable.getLabel() + ")";
740 }
741
742 /**
743 * Returns a string representation of this annotation.
744 *
745 * <p>
746 * Returns the map representation created by {@link #properties()}.
747 *
748 * @return A string representation of this annotation.
749 */
750 @Override /* Overridden from Object */
751 public String toString() {
752 return r(properties());
753 }
754 }