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<AnnotationInfo<MyAnnotation>> <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> -> { 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<MyAnnotation> <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<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}