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.reflect.ReflectionUtils.*;
020import static org.apache.juneau.commons.utils.AssertionUtils.*;
021import static org.apache.juneau.commons.utils.CollectionUtils.*;
022import static org.apache.juneau.commons.utils.ThrowableUtils.*;
023import static org.apache.juneau.commons.utils.Utils.*;
024
025import java.lang.annotation.*;
026import java.util.*;
027import java.util.function.*;
028
029import org.apache.juneau.commons.annotation.*;
030import org.apache.juneau.commons.collections.*;
031
032/**
033 * Encapsulates information about an annotation instance and the element it's declared on.
034 *
035 * <p>
036 * This class provides a convenient wrapper around Java annotations that allows you to:
037 * <ul>
038 *    <li>Access annotation values in a type-safe manner
039 *    <li>Query annotation properties without reflection boilerplate
040 *    <li>Track where the annotation was found (class, method, field, etc.)
041 *    <li>Sort annotations by precedence using ranks
042 * </ul>
043 *
044 * <h5 class='section'>Example:</h5>
045 * <p class='bjava'>
046 *    <jc>// Get annotation info from a class</jc>
047 *    ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
048 *    Optional&lt;AnnotationInfo&lt;MyAnnotation&gt;&gt; <jv>ai</jv> =
049 *       <jv>ci</jv>.getAnnotations(MyAnnotation.<jk>class</jk>).findFirst();
050 *
051 *    <jc>// Access annotation values</jc>
052 *    <jv>ai</jv>.ifPresent(<jv>x</jv> -&gt; {
053 *       String <jv>value</jv> = <jv>x</jv>.getValue(String.<jk>class</jk>, <js>"value"</js>).orElse(<js>"default"</js>);
054 *       <jk>int</jk> <jv>priority</jv> = <jv>x</jv>.getInt(<js>"priority"</js>).orElse(0);
055 *    });
056 * </p>
057 *
058 * <h5 class='section'>See Also:</h5><ul>
059 *    <li class='jc'>{@link ClassInfo}
060 *    <li class='jc'>{@link MethodInfo}
061 *    <li class='jc'>{@link FieldInfo}
062 *    <li class='jc'>{@link ConstructorInfo}
063 *    <li class='jc'>{@link ParameterInfo}
064 *    <li class='jc'>{@link PackageInfo}
065 * </ul>
066 *
067 * @param <T> The annotation type.
068 */
069public class AnnotationInfo<T extends Annotation> {
070
071   /**
072    * Creates a new annotation info object.
073    *
074    * <h5 class='section'>Example:</h5>
075    * <p class='bjava'>
076    *    <jc>// Create annotation info for a class annotation</jc>
077    *    ClassInfo <jv>ci</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
078    *    MyAnnotation <jv>annotation</jv> = <jv>ci</jv>.inner().getAnnotation(MyAnnotation.<jk>class</jk>);
079    *    AnnotationInfo&lt;MyAnnotation&gt; <jv>ai</jv> = AnnotationInfo.<jsm>of</jsm>(<jv>ci</jv>, <jv>annotation</jv>);
080    * </p>
081    *
082    * @param <A> The annotation type.
083    * @param on The annotatable object where the annotation was found (class, method, field, constructor, parameter, or package).
084    * @param value The annotation instance. Must not be <jk>null</jk>.
085    * @return A new {@link AnnotationInfo} object wrapping the annotation.
086    */
087   public static <A extends Annotation> AnnotationInfo<A> of(Annotatable on, A value) {
088      return new AnnotationInfo<>(on, value);
089   }
090
091   private static int findRank(Object a) {
092      // @formatter:off
093      return ClassInfo.of(a).getAllMethods().stream()
094         .filter(m -> m.hasName("rank") && m.hasReturnType(int.class))
095         .findFirst()
096         .map(m -> safe(() -> (int)m.invoke(a)))
097         .orElse(0);
098      // @formatter:on
099   }
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&lt;Deprecated&gt; <jv>ai</jv> = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>).getAnnotation(Deprecated.<jk>class</jk>);
129    *    Class&lt;? <jk>extends</jk> Annotation&gt; <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&lt;?&gt; <jv>ai</jv> = ...;
148    *
149    *    <jc>// Safe cast</jc>
150    *    AnnotationInfo&lt;MyAnnotation&gt; <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&lt;MyAnnotation&gt; <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&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
216    *    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>
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&lt;Class&lt;? <jk>extends</jk> Serializer&gt;[]&gt; <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&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
270    *    Class&lt;?&gt; <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&lt;Class&lt;? <jk>extends</jk> Serializer&gt;&gt; <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&lt;MyAnnotation&gt; <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&lt;MyAnnotation&gt; <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&lt;MyAnnotation&gt; <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&lt;MyAnnotation&gt; <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&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
386    *    Optional&lt;MethodInfo&gt; <jv>method</jv> = <jv>ai</jv>.getMethod(<js>"value"</js>);
387    *    <jv>method</jv>.ifPresent(<jv>m</jv> -&gt; 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&lt;MyAnnotation&gt; <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&lt;MyAnnotation&gt; <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&lt;ClassInfo&gt; <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&lt;MyAnnotation&gt; <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&lt;MyAnnotation&gt; <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&lt;MyAnnotation&gt; <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&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
517    *
518    *    <jc>// Get string value</jc>
519    *    Optional&lt;String&gt; <jv>value</jv> = <jv>ai</jv>.getValue(String.<jk>class</jk>, <js>"value"</js>);
520    *
521    *    <jc>// Get int value</jc>
522    *    Optional&lt;Integer&gt; <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&lt;MyAnnotation&gt; <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&lt;MyAnnotation&gt; <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&lt;MyAnnotation&gt; <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&lt;?&gt; <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&lt;MyAnnotation&gt; <jv>ai</jv> = ...;
694    *    LinkedHashMap&lt;String,Object&gt; <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}