001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.reflect;
018
019import static org.apache.juneau.commons.function.Suppliers.*;
020import static org.apache.juneau.commons.reflect.ClassArrayFormat.*;
021import static org.apache.juneau.commons.reflect.ClassNameFormat.*;
022import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
023import static org.apache.juneau.commons.utils.ClassUtils.*;
024import static org.apache.juneau.commons.utils.CollectionUtils.*;
025import static org.apache.juneau.commons.utils.Shorts.*;
026import static org.apache.juneau.commons.utils.ThrowableUtils.*;
027
028import java.lang.annotation.*;
029import java.util.*;
030import java.util.function.*;
031
032import org.apache.juneau.commons.*;
033import org.apache.juneau.commons.collections.*;
034
035/**
036 * Encapsulates information about an annotation instance and the element it's declared on.
037 *
038 * <p>
039 * This class provides a convenient wrapper around Java annotations that allows you to:
040 * <ul>
041 *    <li>Access annotation values in a type-safe manner
042 *    <li>Query annotation properties without reflection boilerplate
043 *    <li>Track where the annotation was found (class, method, field, etc.)
044 *    <li>Sort annotations by precedence using ranks
045 * </ul>
046 *
047 * <h5 class='section'>Example:</h5>
048 * <p class='bjava'>
049 *    <jc>// Get annotation info from a class</jc>
050 *    ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
051 *    Optional&lt;AnnotationInfo&lt;MyAnnotation&gt;&gt; <jv>ai</jv> =
052 *       <jv>ci</jv>.getAnnotations(MyAnnotation.<jk>class</jk>).findFirst();
053 *
054 *    <jc>// Access annotation values</jc>
055 *    <jv>ai</jv>.ifPresent(<jv>x</jv> -&gt; {
056 *       String <jv>value</jv> = <jv>x</jv>.getValue(String.<jk>class</jk>, <js>"value"</js>).orElse(<js>"default"</js>);
057 *       <jk>int</jk> <jv>priority</jv> = <jv>x</jv>.getInt(<js>"priority"</js>).orElse(0);
058 *    });
059 * </p>
060 *
061 * <h5 class='section'>See Also:</h5><ul>
062 *    <li class='jc'>{@link ClassInfo}
063 *    <li class='jc'>{@link MethodInfo}
064 *    <li class='jc'>{@link FieldInfo}
065 *    <li class='jc'>{@link ConstructorInfo}
066 *    <li class='jc'>{@link ParameterInfo}
067 *    <li class='jc'>{@link PackageInfo}
068 * </ul>
069 *
070 * @param <T> The annotation type.
071 */
072@SuppressWarnings({
073   "hiding", // Field/variable hiding intentional for specific scoping
074   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
075   "java:S3740", // Raw Annotation type used intentionally for unparameterized annotation introspection
076   "unchecked" // Casts in cast() and the Class/Class[] value accessors follow isInstance/hasReturnType checks on the annotation member.
077})
078public class AnnotationInfo<T extends Annotation> {
079
080   /**
081    * Creates a new annotation info object.
082    *
083    * <h5 class='section'>Example:</h5>
084    * <p class='bjava'>
085    *    <jc>// Create annotation info for a class annotation</jc>
086    *    ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
087    *    MyAnnotation <jv>annotation</jv> = <jv>ci</jv>.inner().getAnnotation(MyAnnotation.<jk>class</jk>);
088    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = AnnotationInfo.<jsm>of</jsm>(<jv>ci</jv>, <jv>annotation</jv>);
089    * </p>
090    *
091    * @param <A> The annotation type.
092    * @param on The annotatable object where the annotation was found (class, method, field, constructor, parameter, or package). Must not be <jk>null</jk>.
093    * @param value The annotation instance. Must not be <jk>null</jk>.
094    * @return A new {@link AnnotationInfo} object wrapping the annotation.
095    */
096   public static <A extends Annotation> AnnotationInfo<A> of(Annotatable on, A value) {
097      return new AnnotationInfo<>(on, value);
098   }
099
100   private static int findRank(Object a) {
101      // @formatter:off
102      return ClassInfo.of(a).getAllMethods().stream()
103         .filter(m -> m.hasName("rank") && m.hasReturnType(int.class))
104         .findFirst()
105         .map(m -> safe(() -> (int)m.invoke(a)))
106         .orElse(0);
107      // @formatter:on
108   }
109
110   private final Annotatable annotatable;
111   final int rank;
112
113   private final T a;
114
115   private final Supplier<List<MethodInfo>> methods;
116   private final Supplier<String> toString;  // String representation with annotation type, location, and values.
117
118   /**
119    * Constructor.
120    *
121    * @param on The annotatable object where the annotation was found.
122    * @param a The annotation instance.
123    */
124   AnnotationInfo(Annotatable on, T a) {
125      this.annotatable = reqnn("on", on);
126      this.a = reqnn("a", a);
127      this.methods = memoize(() -> stream(a.annotationType().getMethods()).map(m -> MethodInfo.of(info(a.annotationType()), m)).toList());
128      this.rank = findRank(a);
129      this.toString = memoize(this::findToString);
130   }
131
132   /**
133    * Returns the annotatable object (class, method, field, etc.) where this annotation was found.
134    *
135    * @return The annotatable object where this annotation was found.
136    */
137   public Annotatable getAnnotatable() {
138      return annotatable;
139   }
140
141   /**
142    * Returns the annotatable object cast to the specified type.
143    *
144    * @param <A> The annotatable type.
145    * @param type The class to cast to.
146    * @return The annotatable element cast to the specified type.
147    * @throws ClassCastException If the annotatable element is not of the specified type.
148    */
149   public <A extends Annotatable> A getAnnotatable(Class<A> type) {
150      return type.cast(annotatable);
151   }
152
153   /**
154    * Returns the annotation type of this annotation.
155    *
156    * <p>
157    * Same as calling {@link Annotation#annotationType()}.
158    *
159    * <h5 class='section'>Example:</h5>
160    * <p class='bjava'>
161    *    AnnotationInfo&lt;Deprecated&gt; <jv>ai</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getAnnotation(Deprecated.<jk>class</jk>);
162    *    Class&lt;? <jk>extends</jk> Annotation&gt; <jv>type</jv> = <jv>ai</jv>.annotationType();  <jc>// Returns Deprecated.class</jc>
163    * </p>
164    *
165    * @return The annotation type of this annotation.
166    * @see Annotation#annotationType()
167    */
168   public Class<? extends Annotation> annotationType() {
169      return a.annotationType();
170   }
171
172   /**
173    * Casts this annotation info to a specific annotation type.
174    *
175    * <p>
176    * This is useful when you have an {@code AnnotationInfo<?>} and need to narrow it to a specific type.
177    *
178    * <h5 class='section'>Example:</h5>
179    * <p class='bjava'>
180    *    AnnotationInfo&lt;?&gt; <jv>ai</jv> = ...;
181    *
182    *    <jc>// Safe cast</jc>
183    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>myAi</jv> = <jv>ai</jv>.cast(MyAnnotation.<jk>class</jk>);
184    *    <jk>if</jk> (<jv>myAi</jv> != <jk>null</jk>) {
185    *       <jc>// Use strongly-typed annotation info</jc>
186    *    }
187    * </p>
188    *
189    * @param <A> The annotation type to cast to.
190    * @param type The annotation type to cast to.
191    * @return This annotation info cast to the specified type, or <jk>null</jk> if the annotation is not of the specified type.
192    */
193   public <A extends Annotation> AnnotationInfo<A> cast(Class<A> type) {
194      return type.isInstance(a) ? (AnnotationInfo<A>)this : null;
195   }
196
197   /**
198    * Returns true if the specified object represents an annotation that is logically equivalent to this one.
199    *
200    * <p>
201    * Same as calling {@link Annotation#equals(Object)} on the wrapped annotation.
202    *
203    * <p>
204    * Two annotations are considered equal if:
205    * <ul>
206    *    <li>They are of the same annotation type
207    *    <li>All their corresponding member values are equal
208    * </ul>
209    *
210    * @param o The reference object with which to compare.
211    * @return <jk>true</jk> if the specified object is equal to this annotation.
212    * @see Annotation#equals(Object)
213    */
214   @Override /* Overridden from Object */
215   public boolean equals(Object o) {
216      if (o instanceof AnnotationInfo o2)
217         return a.equals(o2.a);
218      return a.equals(o);
219   }
220
221   /**
222    * Returns the value of the specified method on this annotation as a boolean.
223    *
224    * <h5 class='section'>Example:</h5>
225    * <p class='bjava'>
226    *    <jc>// For annotation: @MyAnnotation(enabled=true)</jc>
227    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
228    *    <jk>boolean</jk> <jv>enabled</jv> = <jv>ai</jv>.getBoolean(<js>"enabled"</js>).orElse(<jk>false</jk>);  <jc>// Returns true</jc>
229    * </p>
230    *
231    * @param methodName The method name.
232    * @return An {@link Optional} containing the value as a boolean, or empty if not found or not a {@code boolean} type.
233    */
234   public Optional<Boolean> getBoolean(String methodName) {
235      return getMethod(methodName).filter(x -> x.hasReturnType(boolean.class)).map(x -> (Boolean)x.invoke(a));
236   }
237
238   /**
239    * Returns the value of the specified method on this annotation as a class array.
240    *
241    * <p>
242    * For type-safe access to an array of classes of a specific supertype, use {@link #getClassArray(String, Class)}.
243    *
244    * <h5 class='section'>Example:</h5>
245    * <p class='bjava'>
246    *    <jc>// For annotation: @MyAnnotation(types={String.class, Integer.class})</jc>
247    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
248    *    Class&lt;?&gt;[] <jv>types</jv> = <jv>ai</jv>.getClassArray(<js>"types"</js>).orElse(<jk>new</jk> Class[0]);  <jc>// Returns [String.class, Integer.class]</jc>
249    * </p>
250    *
251    * @param methodName The method name.
252    * @return An {@link Optional} containing the class array value, or empty if not found or not a {@code Class[]} type.
253    */
254   public Optional<Class<?>[]> getClassArray(String methodName) {
255      return (Optional<Class<?>[]>)(Optional<?>)getMethod(methodName).filter(x -> x.hasReturnType(Class[].class)).map(x -> x.invoke(a));
256   }
257
258   /**
259    * Returns the value of the specified method on this annotation as a class array of a specific type.
260    *
261    * <h5 class='section'>Example:</h5>
262    * <p class='bjava'>
263    *    <jc>// Get an array of serializer classes from an annotation</jc>
264    *    Optional&lt;Class&lt;? <jk>extends</jk> Serializer&gt;[]&gt; <jv>serializerClasses</jv> =
265    *       <jv>annotationInfo</jv>.getClassArray(<js>"serializers"</js>, Serializer.<jk>class</jk>);
266    * </p>
267    *
268    * @param <T> The expected supertype of the classes.
269    * @param methodName The method name.
270    * @param type The expected supertype of the class values.
271    * @return An optional containing the value of the specified method cast to the expected type,
272    *         or empty if not found, not a class array, or any element is not assignable to the expected type.
273    */
274   public <T> Optional<Class<? extends T>[]> getClassArray(String methodName, Class<T> type) {
275      // @formatter:off
276      return getMethod(methodName)
277         .filter(x -> x.hasReturnType(Class[].class))
278         .map(x -> (Class<?>[])x.invoke(a))
279         .filter(arr -> {
280            for (var c : arr) {
281               if (!type.isAssignableFrom(c))
282                  return false;
283            }
284            return true;
285         })
286         .map(x -> (Class<? extends T>[])x);
287      // @formatter:on
288   }
289
290   /**
291    * Returns the value of the specified method on this annotation as a class.
292    *
293    * <p>
294    * For type-safe access to a class of a specific supertype, use {@link #getClassValue(String, Class)}.
295    *
296    * <h5 class='section'>Example:</h5>
297    * <p class='bjava'>
298    *    <jc>// For annotation: @MyAnnotation(type=String.class)</jc>
299    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
300    *    Class&lt;?&gt; <jv>type</jv> = <jv>ai</jv>.getClassValue(<js>"type"</js>).orElse(<jk>null</jk>);  <jc>// Returns String.class</jc>
301    * </p>
302    *
303    * @param methodName The method name.
304    * @return An {@link Optional} containing the class value, or empty if not found or not a {@link Class} type.
305    */
306   public Optional<Class<?>> getClassValue(String methodName) {
307      return (Optional<Class<?>>)(Optional<?>)getMethod(methodName).filter(x -> x.hasReturnType(Class.class)).map(x -> x.invoke(a));
308   }
309
310   /**
311    * Returns the value of the specified method on this annotation as a class of a specific type.
312    *
313    * <h5 class='section'>Example:</h5>
314    * <p class='bjava'>
315    *    <jc>// Get a serializer class from an annotation</jc>
316    *    Optional&lt;Class&lt;? <jk>extends</jk> Serializer&gt;&gt; <jv>serializerClass</jv> =
317    *       <jv>annotationInfo</jv>.getClassValue(<js>"serializer"</js>, Serializer.<jk>class</jk>);
318    * </p>
319    *
320    * @param <T> The expected supertype of the class.
321    * @param methodName The method name.
322    * @param type The expected supertype of the class value.
323    * @return An optional containing the value of the specified method cast to the expected type,
324    *         or empty if not found, not a class, or not assignable to the expected type.
325    */
326   public <T> Optional<Class<? extends T>> getClassValue(String methodName, Class<T> type) {
327      // @formatter:off
328      return getMethod(methodName)
329         .filter(x -> x.hasReturnType(Class.class))
330         .map(x -> (Class<?>)x.invoke(a))
331         .filter(type::isAssignableFrom)
332         .map(x -> (Class<? extends T>)x);
333      // @formatter:on
334   }
335
336   //-----------------------------------------------------------------------------------------------------------------
337   // Annotation interface methods
338   //-----------------------------------------------------------------------------------------------------------------
339
340   /**
341    * Returns the value of the specified method on this annotation as a double.
342    *
343    * <h5 class='section'>Example:</h5>
344    * <p class='bjava'>
345    *    <jc>// For annotation: @MyAnnotation(threshold=0.95)</jc>
346    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
347    *    <jk>double</jk> <jv>threshold</jv> = <jv>ai</jv>.getDouble(<js>"threshold"</js>).orElse(0.0);  <jc>// Returns 0.95</jc>
348    * </p>
349    *
350    * @param methodName The method name.
351    * @return An {@link Optional} containing the value as a double, or empty if not found or not a {@code double} type.
352    */
353   public Optional<Double> getDouble(String methodName) {
354      return getMethod(methodName).filter(x -> x.hasReturnType(double.class)).map(x -> (Double)x.invoke(a));
355   }
356
357   /**
358    * Returns the value of the specified method on this annotation as a float.
359    *
360    * <h5 class='section'>Example:</h5>
361    * <p class='bjava'>
362    *    <jc>// For annotation: @MyAnnotation(weight=0.5f)</jc>
363    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
364    *    <jk>float</jk> <jv>weight</jv> = <jv>ai</jv>.getFloat(<js>"weight"</js>).orElse(0.0f);  <jc>// Returns 0.5f</jc>
365    * </p>
366    *
367    * @param methodName The method name.
368    * @return An {@link Optional} containing the value as a float, or empty if not found or not a {@code float} type.
369    */
370   public Optional<Float> getFloat(String methodName) {
371      return getMethod(methodName).filter(x -> x.hasReturnType(float.class)).map(x -> (Float)x.invoke(a));
372   }
373
374   /**
375    * Returns the value of the specified method on this annotation as an integer.
376    *
377    * <h5 class='section'>Example:</h5>
378    * <p class='bjava'>
379    *    <jc>// For annotation: @MyAnnotation(priority=5)</jc>
380    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
381    *    <jk>int</jk> <jv>priority</jv> = <jv>ai</jv>.getInt(<js>"priority"</js>).orElse(0);  <jc>// Returns 5</jc>
382    * </p>
383    *
384    * @param methodName The method name.
385    * @return An {@link Optional} containing the value as an integer, or empty if not found or not an {@code int} type.
386    */
387   public Optional<Integer> getInt(String methodName) {
388      return getMethod(methodName).filter(x -> x.hasReturnType(int.class)).map(x -> (Integer)x.invoke(a));
389   }
390
391   /**
392    * Returns the value of the specified method on this annotation as a long.
393    *
394    * <h5 class='section'>Example:</h5>
395    * <p class='bjava'>
396    *    <jc>// For annotation: @MyAnnotation(timestamp=1234567890L)</jc>
397    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
398    *    <jk>long</jk> <jv>timestamp</jv> = <jv>ai</jv>.getLong(<js>"timestamp"</js>).orElse(0L);  <jc>// Returns 1234567890L</jc>
399    * </p>
400    *
401    * @param methodName The method name.
402    * @return An {@link Optional} containing the value as a long, or empty if not found or not a {@code long} type.
403    */
404   public Optional<Long> getLong(String methodName) {
405      return getMethod(methodName).filter(x -> x.hasReturnType(long.class)).map(x -> (Long)x.invoke(a));
406   }
407
408   /**
409    * Returns the method with the specified name on this annotation.
410    *
411    * <h5 class='section'>Example:</h5>
412    * <p class='bjava'>
413    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
414    *    Optional&lt;MethodInfo&gt; <jv>method</jv> = <jv>ai</jv>.getMethod(<js>"value"</js>);
415    *    <jv>method</jv>.ifPresent(<jv>m</jv> -&gt; System.<jsf>out</jsf>.println(<jv>m</jv>.getReturnType()));
416    * </p>
417    *
418    * @param methodName The method name to look for.
419    * @return An {@link Optional} containing the method info, or empty if method not found.
420    */
421   public Optional<MethodInfo> getMethod(String methodName) {
422      return methods.get().stream().filter(x -> eq(methodName, x.getNameSimple())).findFirst();
423   }
424
425   //-----------------------------------------------------------------------------------------------------------------
426   // Private helper methods
427   //-----------------------------------------------------------------------------------------------------------------
428
429   /**
430    * Returns the class name of this annotation.
431    *
432    * <h5 class='section'>Example:</h5>
433    * <p class='bjava'>
434    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
435    *    String <jv>name</jv> = <jv>ai</jv>.getName();  <jc>// Returns "mypackage.MyAnnotation"</jc>
436    * </p>
437    *
438    * @return The simple class name of the annotation (e.g., {@code "Override"} for {@code @Override}).
439    */
440   public String getName() { return cn(a.annotationType()); }
441
442   /**
443    * Returns the simple class name of this annotation.
444    *
445    * <h5 class='section'>Example:</h5>
446    * <p class='bjava'>
447    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
448    *    String <jv>name</jv> = <jv>ai</jv>.getNameSimple();  <jc>// Returns "MyAnnotation"</jc>
449    * </p>
450    *
451    * @return The simple class name of the annotation (e.g., {@code "Override"} for {@code @Override}).
452    * @see #getName()
453    */
454   public String getNameSimple() { return cns(a.annotationType()); }
455
456   /**
457    * Returns the rank of this annotation for sorting by precedence.
458    *
459    * <p>
460    * The rank is determined by checking if the annotation has a {@code rank()} method that returns an {@code int}.
461    * If found, that value is used; otherwise the rank defaults to {@code 0}.
462    *
463    * <p>
464    * Higher rank values indicate higher precedence when multiple annotations of the same type are present.
465    *
466    * <h5 class='section'>Example:</h5>
467    * <p class='bjava'>
468    *    <jc>// Annotation with rank method</jc>
469    *    <ja>@interface</ja> MyAnnotation {
470    *       <jk>int</jk> rank() <jk>default</jk> 0;
471    *    }
472    *
473    *    <jc>// Get rank from annotation info</jc>
474    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
475    *    <jk>int</jk> <jv>rank</jv> = <jv>ai</jv>.getRank();  <jc>// Returns value from rank() method</jc>
476    * </p>
477    *
478    * @return The rank of this annotation, or {@code 0} if no rank method exists.
479    */
480   public int getRank() { return rank; }
481
482   /**
483    * Returns the return type of the specified method on this annotation.
484    *
485    * <h5 class='section'>Example:</h5>
486    * <p class='bjava'>
487    *    Optional&lt;ClassInfo&gt; <jv>returnType</jv> = <jv>annotationInfo</jv>.getReturnType(<js>"value"</js>);
488    * </p>
489    *
490    * @param methodName The method name.
491    * @return An optional containing the return type of the specified method, or empty if method not found.
492    */
493   public Optional<ClassInfo> getReturnType(String methodName) {
494      return getMethod(methodName).map(x -> x.getReturnType());
495   }
496
497   /**
498    * Returns the value of the specified method on this annotation as a string.
499    *
500    * <h5 class='section'>Example:</h5>
501    * <p class='bjava'>
502    *    <jc>// For annotation: @MyAnnotation(name="John", age=30)</jc>
503    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
504    *    String <jv>name</jv> = <jv>ai</jv>.getString(<js>"name"</js>).orElse(<js>"unknown"</js>);  <jc>// Returns "John"</jc>
505    * </p>
506    *
507    * @param methodName The method name.
508    * @return An {@link Optional} containing the value as a string, or empty if not found or not a string type.
509    */
510   public Optional<String> getString(String methodName) {
511      return getMethod(methodName).filter(x -> x.hasReturnType(String.class)).map(x -> s(x.invoke(a)));
512   }
513
514   /**
515    * Returns the value of the specified method on this annotation as a string array.
516    *
517    * <h5 class='section'>Example:</h5>
518    * <p class='bjava'>
519    *    <jc>// For annotation: @MyAnnotation(tags={"foo", "bar"})</jc>
520    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
521    *    String[] <jv>tags</jv> = <jv>ai</jv>.getStringArray(<js>"tags"</js>).orElse(<jk>new</jk> String[0]);  <jc>// Returns ["foo", "bar"]</jc>
522    * </p>
523    *
524    * @param methodName The method name.
525    * @return An {@link Optional} containing the string array value, or empty if not found or not a {@code String[]} type.
526    */
527   public Optional<String[]> getStringArray(String methodName) {
528      return getMethod(methodName).filter(x -> x.hasReturnType(String[].class)).map(x -> (String[])x.invoke(a));
529   }
530
531   /**
532    * Returns the value of the {@code value()} method on this annotation as a string.
533    *
534    * <p>
535    * This is a convenience method equivalent to calling {@link #getString(String) getString("value")}.
536    *
537    * <h5 class='section'>Example:</h5>
538    * <p class='bjava'>
539    *    <jc>// For annotation: @MyAnnotation("foo")</jc>
540    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
541    *    String <jv>value</jv> = <jv>ai</jv>.getValue().orElse(<js>"default"</js>);  <jc>// Returns "foo"</jc>
542    * </p>
543    *
544    * @return An {@link Optional} containing the value of the {@code value()} method, or empty if not found or not a string.
545    */
546   public Optional<String> getValue() { return getString("value"); }
547
548   /**
549    * Returns the value of a specific annotation method.
550    *
551    * <p>
552    * This method provides type-safe access to annotation field values without requiring
553    * explicit reflection calls or casting.
554    *
555    * <h5 class='section'>Example:</h5>
556    * <p class='bjava'>
557    *    <jc>// For annotation: @interface MyAnnotation { String value(); int priority(); }</jc>
558    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
559    *
560    *    <jc>// Get string value</jc>
561    *    Optional&lt;String&gt; <jv>value</jv> = <jv>ai</jv>.getValue(String.<jk>class</jk>, <js>"value"</js>);
562    *
563    *    <jc>// Get int value</jc>
564    *    Optional&lt;Integer&gt; <jv>priority</jv> = <jv>ai</jv>.getValue(Integer.<jk>class</jk>, <js>"priority"</js>);
565    * </p>
566    *
567    * @param <V> The expected type of the annotation field value.
568    * @param type The expected class of the annotation field value.
569    * @param name The name of the annotation method (field).
570    * @return An {@link Optional} containing the value if found and type matches, empty otherwise.
571    */
572   public <V> Optional<V> getValue(Class<V> type, String name) {
573      // @formatter:off
574      return methods.get().stream()
575         .filter(m -> eq(m.getName(), name) && eq(m.getReturnType().inner(), type))
576         .map(m -> safe(() -> (V)m.invoke(a)))
577         .findFirst();
578      // @formatter:on
579   }
580
581   /**
582    * Returns <jk>true</jk> if this annotation is itself annotated with the specified annotation.
583    *
584    * <p>
585    * This checks for meta-annotations on the annotation type.
586    *
587    * <h5 class='section'>Example:</h5>
588    * <p class='bjava'>
589    *    <jc>// Check if @MyAnnotation is annotated with @Documented</jc>
590    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
591    *    <jk>boolean</jk> <jv>isDocumented</jv> = <jv>ai</jv>.hasAnnotation(Documented.<jk>class</jk>);
592    * </p>
593    *
594    * @param <A> The meta-annotation type.
595    * @param type The meta-annotation to test for.
596    * @return <jk>true</jk> if this annotation is annotated with the specified annotation.
597    */
598   public <A extends Annotation> boolean hasAnnotation(Class<A> type) {
599      return nn(this.a.annotationType().getAnnotation(type));
600   }
601
602   /**
603    * Returns the hash code of this annotation.
604    *
605    * <p>
606    * Same as calling {@link Annotation#hashCode()} on the wrapped annotation.
607    *
608    * <p>
609    * The hash code of an annotation is the sum of the hash codes of its members (including those with default values).
610    *
611    * @return The hash code of this annotation.
612    * @see Annotation#hashCode()
613    */
614   @Override /* Overridden from Object */
615   public int hashCode() {
616      return a.hashCode();
617   }
618
619   /**
620    * Returns <jk>true</jk> if this annotation has the specified fully-qualified name.
621    *
622    * <h5 class='section'>Example:</h5>
623    * <p class='bjava'>
624    *    <jk>boolean</jk> <jv>isName</jv> = <jv>annotationInfo</jv>.hasName(<js>"org.apache.juneau.commons.bean.Name"</js>);
625    * </p>
626    *
627    * @param value The fully-qualified name to check.
628    * @return <jk>true</jk> if this annotation has the specified fully-qualified name.
629    */
630   public boolean hasName(String value) {
631      return eq(value, getName());
632   }
633
634   /**
635    * Returns <jk>true</jk> if this annotation has the specified simple name.
636    *
637    * <h5 class='section'>Example:</h5>
638    * <p class='bjava'>
639    *    <jk>boolean</jk> <jv>isName</jv> = <jv>annotationInfo</jv>.hasSimpleName(<js>"Name"</js>);
640    * </p>
641    *
642    * @param value The simple name to check.
643    * @return <jk>true</jk> if this annotation has the specified simple name.
644    */
645   public boolean hasNameSimple(String value) {
646      return eq(value, getNameSimple());
647   }
648
649   /**
650    * Returns the wrapped annotation instance.
651    *
652    * <h5 class='section'>Example:</h5>
653    * <p class='bjava'>
654    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
655    *    MyAnnotation <jv>annotation</jv> = <jv>ai</jv>.inner();
656    *
657    *    <jc>// Access annotation methods directly</jc>
658    *    String <jv>value</jv> = <jv>annotation</jv>.value();
659    * </p>
660    *
661    * @return The wrapped annotation instance.
662    */
663   public T inner() {
664      return a;
665   }
666
667   /**
668    * Returns <jk>true</jk> if this annotation is in the specified {@link AnnotationGroup}.
669    *
670    * <p>
671    * Annotation groups are used to logically group related annotations together.
672    * This checks if the annotation is annotated with {@link AnnotationGroup} and if
673    * the group value matches the specified type.
674    *
675    * <h5 class='section'>Example:</h5>
676    * <p class='bjava'>
677    *    <jc>// Define an annotation group</jc>
678    *    <ja>@interface</ja> MyGroup {}
679    *
680    *    <jc>// Annotation in the group</jc>
681    *    <ja>@AnnotationGroup</ja>(MyGroup.<jk>class</jk>)
682    *    <ja>@interface</ja> MyAnnotation {}
683    *
684    *    <jc>// Check if annotation is in group</jc>
685    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
686    *    <jk>boolean</jk> <jv>inGroup</jv> = <jv>ai</jv>.isInGroup(MyGroup.<jk>class</jk>);  <jc>// Returns true</jc>
687    * </p>
688    *
689    * @param <A> The group annotation type.
690    * @param group The group annotation class to test for.
691    * @return <jk>true</jk> if this annotation is in the specified group.
692    * @see AnnotationGroup
693    */
694   public <A extends Annotation> boolean isInGroup(Class<A> group) {
695      var x = a.annotationType().getAnnotation(AnnotationGroup.class);
696      return (nn(x) && x.value().equals(group));
697   }
698
699   /**
700    * Returns <jk>true</jk> if this annotation is of the specified type.
701    *
702    * <h5 class='section'>Example:</h5>
703    * <p class='bjava'>
704    *    AnnotationInfo&lt;?&gt; <jv>ai</jv> = ...;
705    *
706    *    <jk>if</jk> (<jv>ai</jv>.isType(MyAnnotation.<jk>class</jk>)) {
707    *       <jc>// Handle MyAnnotation specifically</jc>
708    *    }
709    * </p>
710    *
711    * @param <A> The annotation type to test for.
712    * @param type The annotation type to test against.
713    * @return <jk>true</jk> if this annotation's type is exactly the specified type.
714    */
715   public <A extends Annotation> boolean isType(Class<A> type) {
716      return this.a.annotationType() == type;
717   }
718
719   /**
720    * Converts this annotation info to a map representation for debugging purposes.
721    *
722    * <p>
723    * The returned map contains:
724    * <ul>
725    *    <li>The annotatable element's type and label (e.g., {@code "CLASS_TYPE" -> "com.example.MyClass"})
726    *    <li>A nested map with the annotation's simple name as key and its non-default values
727    * </ul>
728    *
729    * <p>
730    * Only annotation values that differ from their default values are included.
731    *
732    * <h5 class='section'>Example:</h5>
733    * <p class='bjava'>
734    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
735    *    LinkedHashMap&lt;String,Object&gt; <jv>map</jv> = <jv>ai</jv>.toMap();
736    *    <jc>// Returns: {"CLASS_TYPE": "MyClass", "@MyAnnotation": {"value": "foo", "priority": 5}}</jc>
737    * </p>
738    *
739    * @return A new map showing the attributes of this annotation info.
740    */
741   public FluentMap<String,Object> properties() {
742      // @formatter:off
743      var ca = info(a.annotationType());
744      var ja = mapBuilder().sorted().buildFluent();  // NOAI
745      ca.getDeclaredMethods().forEach(x -> safeOptCatch(() -> {
746         var val = x.invoke(a);
747         var d = x.inner().getDefaultValue();
748         // Add values only if they're different from the default.
749         if (neq(val, d) && ! (isArray(val) && length(val) == 0 && isArray(d) && length(d) == 0))
750            return val;
751         return null;
752      }, e -> localizedMessage(e)).ifPresent(v -> ja.a(x.getName(), v)));
753      return filteredBeanPropertyMap()
754         .a(s(annotatable.getAnnotatableType()), annotatable.getLabel())
755         .a("@" + ca.getNameSimple(), ja);
756      // @formatter:on
757   }
758
759   /**
760    * Returns a simple string representation of this annotation showing the annotation type and location.
761    *
762    * <p>
763    * Format: {@code @AnnotationName(on=location)}
764    *
765    * <h5 class='section'>Examples:</h5>
766    * <ul>
767    *    <li>{@code @Marshalled(on=MyClass)} - Annotation on a class
768    *    <li>{@code @Inject(on=MyClass.myField)} - Annotation on a field
769    *    <li>{@code @PackageAnnotation(on=my.package)} - Annotation on a package
770    * </ul>
771    *
772    * @return A simple string representation of this annotation.
773    */
774   public String toSimpleString() {
775      return "@" + cns(a.annotationType()) + "(on=" + annotatable.getLabel() + ")";
776   }
777
778   /**
779    * Returns a detailed string representation of this annotation.
780    *
781    * <p>
782    * The returned string includes:
783    * <ul>
784    *    <li>Annotation type (fully qualified name with @ prefix)
785    *    <li>Location where the annotation is declared (on=...)
786    *    <li>Annotation values (non-default values only, in key=value format)
787    * </ul>
788    *
789    * <h5 class='section'>Examples:</h5>
790    * <p class='bjava'>
791    *    <jc>// Simple annotation on a class</jc>
792    *    AnnotationInfo <jv>ai</jv> = ...;
793    *    <jv>ai</jv>.toString();
794    *    <jc>// Returns: "@java.lang.Deprecated(on=org.example.MyClass)"</jc>
795    *
796    *    <jc>// Annotation with value</jc>
797    *    <jc>// Returns: "@org.example.MyAnnotation(value=foo, on=org.example.MyClass)"</jc>
798    *
799    *    <jc>// Annotation on a method</jc>
800    *    <jc>// Returns: "@org.example.RestGet(path=/api, on=org.example.MyClass.myMethod)"</jc>
801    *
802    *    <jc>// Annotation on a field</jc>
803    *    <jc>// Returns: "@org.example.Inject(name=myBean, on=org.example.MyClass.myField)"</jc>
804    * </p>
805    *
806    * <h5 class='section'>Comparison with Other Methods:</h5>
807    * <ul class='spaced-list'>
808    *    <li>{@link #toSimpleString()} - Returns simple format: "@AnnotationName(on=location)"
809    *    <li>{@link #properties()} - Returns map representation for debugging
810    * </ul>
811    *
812    * @return A detailed string representation including annotation type, location, and values.
813    */
814   @Override /* Overridden from Object */
815   public String toString() {
816      return toString.get();
817   }
818
819   private String findToString() {
820      var sb = new StringBuilder(256);
821
822      // Annotation type (with @ prefix and full package name)
823      sb.append("@");
824      var annotationType = a.annotationType();
825      ClassInfo.of(annotationType).appendNameFormatted(sb, FULL, true, '$', BRACKETS);
826
827      // Get annotation values (non-default only)
828      var ca = info(annotationType);
829      var values = new ArrayList<String>();
830      ca.getDeclaredMethods().forEach(m -> safeOptCatch(() -> {
831         var val = m.invoke(a);
832         var d = m.inner().getDefaultValue();
833         // Add values only if they're different from the default
834         if (neq(val, d) && ! (isArray(val) && length(val) == 0 && isArray(d) && length(d) == 0)) {
835            var valueStr = formatAnnotationValue(val);
836            values.add(m.getName() + "=" + valueStr);
837         }
838         return null;
839      }, e -> null));
840
841      // Build the string: @AnnotationType(key1=value1, key2=value2, on=location)
842      if (! values.isEmpty()) {
843         sb.append("(");
844         sb.append(String.join(", ", values));
845         sb.append(", on=");
846      } else {
847         sb.append("(on=");
848      }
849      sb.append(annotatable.getLabel());
850      sb.append(")");
851
852      return sb.toString();
853   }
854
855   private String formatAnnotationValue(Object value) {
856      if (value == null)
857         return "null";
858      if (value instanceof String)
859         return "\"" + value + "\"";
860      if (isArray(value)) {
861         var array = toList(value, Object.class);
862         if (array.isEmpty())
863            return "{}";
864         var elements = array.stream()
865            .map(this::formatAnnotationValue)
866            .collect(java.util.stream.Collectors.joining(", "));
867         return "{" + elements + "}";
868      }
869      if (value instanceof Class<?> value2) {
870         var sb = new StringBuilder();
871         ClassInfo.of(value2).appendNameFormatted(sb, FULL, true, '$', BRACKETS);
872         return sb.toString() + ".class";
873      }
874      if (value instanceof Enum<?> value3) {
875         return value3.getDeclaringClass().getSimpleName() + "." + value3.name();
876      }
877      if (value instanceof Annotation value2) {
878         // For nested annotations, use simple format
879         return "@" + ClassInfo.of(value2.annotationType()).getNameSimple() + "(...)";
880      }
881      return String.valueOf(value);
882   }
883}