This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new a0f667b608 org.apache.juneau.common.reflect API improvements
a0f667b608 is described below

commit a0f667b608f9b39014cd7c5360db0e2e77661ac9
Author: James Bognar <[email protected]>
AuthorDate: Mon Nov 24 12:31:31 2025 -0500

    org.apache.juneau.common.reflect API improvements
---
 .../juneau/common/annotation/AnnotationObject.java | 106 +++++-
 .../common/annotation/AppliedAnnotationObject.java | 240 +++++++++++-
 .../annotation/AppliedOnClassAnnotationObject.java |  62 ++-
 .../common/annotation/AnnotationObject_Test.java   | 423 +++++++++++++++++++++
 4 files changed, 824 insertions(+), 7 deletions(-)

diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/annotation/AnnotationObject.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/annotation/AnnotationObject.java
index 0612031424..738232a7b6 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/annotation/AnnotationObject.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/annotation/AnnotationObject.java
@@ -27,16 +27,112 @@ import java.util.*;
 import org.apache.juneau.common.utils.*;
 
 /**
- * A concrete implementation of an annotation.
+ * A concrete implementation of a Java annotation that can be created 
programmatically at runtime.
  *
  * <p>
- * Follows the standard Java conventions for equality and hashcode calculation 
for annotations.
- * Equivalent annotations defined programmatically and declaratively should 
match for equality and hashcode calculation.
+ * This class provides a base for creating annotation instances without 
requiring them to be declared on
+ * program elements at compile-time. It allows annotations to be constructed 
using a builder pattern and
+ * follows all standard Java annotation semantics for equality, hashcode, and 
string representation.
  *
+ * <h5 class='section'>Overview:</h5>
  * <p>
- * For performance reasons, the hashcode is calculated one time and cached at 
the end of object creation.
- * Constructors must call the {@link #postConstruct()} method after all fields 
have been set to trigger this calculation.
+ * Java annotations are typically declared statically on classes, methods, 
fields, etc. at compile-time:
+ * <p class='bjava'>
+ *     <ja>@Bean</ja>(sort=<jk>true</jk>)
+ *     <jk>public class</jk> MyClass {...}
+ * </p>
  *
+ * <p>
+ * This class allows you to create those same annotations programmatically:
+ * <p class='bjava'>
+ *     Bean <jv>annotation</jv> = BeanAnnotation
+ *             .<jsm>create</jsm>()
+ *             .sort(<jk>true</jk>)
+ *             .build();
+ * </p>
+ *
+ * <h5 class='section'>Equality and Hashcode:</h5>
+ * <p>
+ * Follows the standard Java conventions for annotation equality and hashcode 
calculation as defined in
+ * {@link Annotation#equals(Object)} and {@link Annotation#hashCode()}. This 
ensures that programmatically-created
+ * annotations are equivalent to compile-time declared annotations if they 
have the same type and properties.
+ *
+ * <p class='bjava'>
+ *     <jc>// These two annotations are equal:</jc>
+ *     <ja>@Bean</ja>(sort=<jk>true</jk>)
+ *     <jk>class</jk> MyClass {}
+ *
+ *     Bean <jv>declared</jv> = 
MyClass.<jk>class</jk>.getAnnotation(Bean.<jk>class</jk>);
+ *     Bean <jv>programmatic</jv> = 
BeanAnnotation.<jsm>create</jsm>().sort(<jk>true</jk>).build();
+ *
+ *     <jsm>assertEquals</jsm>(<jv>declared</jv>, <jv>programmatic</jv>);  
<jc>// true</jc>
+ *     <jsm>assertEquals</jsm>(<jv>declared</jv>.hashCode(), 
<jv>programmatic</jv>.hashCode());  <jc>// true</jc>
+ * </p>
+ *
+ * <h5 class='section'>Hashcode Caching:</h5>
+ * <p>
+ * For performance reasons, the hashcode is calculated once and cached at the 
end of object construction.
+ * Subclass constructors <b>must</b> call {@link #postConstruct()} after all 
fields have been set to trigger
+ * this calculation.
+ *
+ * <p class='bjava'>
+ *     <jk>public</jk> MyAnnotation(Builder <jv>builder</jv>) {
+ *             <jk>super</jk>(<jv>builder</jv>);
+ *             <jk>this</jk>.<jf>myField</jf> = 
<jv>builder</jv>.<jf>myField</jf>;
+ *             postConstruct();  <jc>// Must be called!</jc>
+ *     }
+ * </p>
+ *
+ * <h5 class='section'>Builder Pattern:</h5>
+ * <p>
+ * Subclasses should provide a nested {@link Builder} class that extends 
{@link AnnotationObject.Builder}
+ * to construct instances using a fluent builder pattern. The builder should:
+ * <ul class='spaced-list'>
+ *     <li>Provide setter methods for each annotation property
+ *     <li>Return <c>this</c> (or the builder type) from each setter for 
method chaining
+ *     <li>Provide a {@code build()} method that constructs the final 
annotation object
+ * </ul>
+ *
+ * <h5 class='section'>Example Implementation:</h5>
+ * <p class='bjava'>
+ *     <jk>public class</jk> MyAnnotationObject <jk>extends</jk> 
AnnotationObject <jk>implements</jk> MyAnnotation {
+ *
+ *             <jk>private final</jk> String <jf>value</jf>;
+ *
+ *             <jk>public static class</jk> Builder <jk>extends</jk> 
AnnotationObject.Builder {
+ *                     String <jf>value</jf> = <js>""</js>;
+ *
+ *                     <jk>public</jk> Builder() {
+ *                             <jk>super</jk>(MyAnnotation.<jk>class</jk>);
+ *                     }
+ *
+ *                     <jk>public</jk> Builder value(String <jv>value</jv>) {
+ *                             <jk>this</jk>.<jf>value</jf> = <jv>value</jv>;
+ *                             <jk>return this</jk>;
+ *                     }
+ *
+ *                     <jk>public</jk> MyAnnotation build() {
+ *                             <jk>return new</jk> 
MyAnnotationObject(<jk>this</jk>);
+ *                     }
+ *             }
+ *
+ *             <jk>public</jk> MyAnnotationObject(Builder <jv>builder</jv>) {
+ *                     <jk>super</jk>(<jv>builder</jv>);
+ *                     <jk>this</jk>.<jf>value</jf> = 
<jv>builder</jv>.<jf>value</jf>;
+ *                     postConstruct();
+ *             }
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public</jk> String value() {
+ *                     <jk>return</jk> <jf>value</jf>;
+ *             }
+ *     }
+ * </p>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='jc'>{@link AppliedAnnotationObject} - For annotations with 
dynamic targeting support
+ *     <li class='link'><a class="doclink" 
href="../../../../../overview-summary.html#juneau-common.Annotations">Overview 
&gt; juneau-common &gt; Annotations</a>
+ * </ul>
  */
 public class AnnotationObject implements Annotation {
 
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/annotation/AppliedAnnotationObject.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/annotation/AppliedAnnotationObject.java
index 24e144a3a3..5505e3d84c 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/annotation/AppliedAnnotationObject.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/annotation/AppliedAnnotationObject.java
@@ -25,8 +25,97 @@ import java.lang.reflect.*;
 import org.apache.juneau.common.reflect.*;
 
 /**
- * An implementation of an annotation that has an <code>on</code> value 
targeting classes/methods/fields/constructors.
+ * An implementation of an annotation that can be dynamically applied to 
classes, methods, fields, and constructors at runtime.
  *
+ * <p>
+ * This class extends {@link AnnotationObject} to provide support for 
annotations that have an <c>on</c> property which
+ * allows them to be targeted to specific program elements based on their 
fully-qualified names.
+ *
+ * <h5 class='section'>Overview:</h5>
+ * <p>
+ * Applied annotations allow you to define annotation properties in external 
configuration or programmatically at runtime,
+ * rather than requiring them to be declared directly on classes, methods, 
fields, or constructors at compile-time.
+ *
+ * <p>
+ * This is particularly useful for:
+ * <ul class='spaced-list'>
+ *     <li>Configuration-driven behavior - Define annotation properties in 
config files without modifying source code
+ *     <li>Dynamic application - Apply annotations to third-party classes 
where you can't modify the source
+ *     <li>Runtime customization - Change annotation properties based on 
runtime conditions
+ *     <li>Centralized configuration - Define annotation properties for 
multiple classes in one place
+ * </ul>
+ *
+ * <h5 class='section'>Targeting with <c>on</c>:</h5>
+ * <p>
+ * The base {@link Builder#on(String...)} method accepts fully-qualified names 
in the following formats:
+ * <ul class='spaced-list'>
+ *     <li><js>"com.example.MyClass"</js> - Target a specific class
+ *     <li><js>"com.example.MyClass.myMethod"</js> - Target a specific method
+ *     <li><js>"com.example.MyClass.myField"</js> - Target a specific field
+ *     <li><js>"com.example.MyClass(java.lang.String,int)"</js> - Target a 
specific constructor
+ * </ul>
+ *
+ * <h5 class='section'>Builder Variants:</h5>
+ * <p>
+ * This class provides specialized builder variants for different targeting 
scenarios:
+ * <ul class='javatree'>
+ *     <li class='jc'>{@link Builder} - Base builder with string-based 
targeting via {@link Builder#on(String...)}
+ *     <li class='jc'>{@link BuilderT} - For targeting classes
+ *             <ul>
+ *                     <li class='jm'>{@link BuilderT#on(Class...) 
on(Class...)} - Target by class, stored as strings
+ *                     <li class='jm'>{@link BuilderT#on(ClassInfo...) 
on(ClassInfo...)} - Target by ClassInfo, stored as strings
+ *                     <li class='jm'>{@link BuilderT#onClass(Class...) 
onClass(Class...)} - Target by class, stored as Class objects
+ *                     <li class='jm'>{@link BuilderT#onClass(ClassInfo...) 
onClass(ClassInfo...)} - Target by ClassInfo, stored as Class objects
+ *             </ul>
+ *     <li class='jc'>{@link BuilderM} - For targeting methods
+ *             <ul>
+ *                     <li class='jm'>{@link BuilderM#on(Method...) 
on(Method...)} - Target by Method object
+ *                     <li class='jm'>{@link BuilderM#on(MethodInfo...) 
on(MethodInfo...)} - Target by MethodInfo object
+ *             </ul>
+ *     <li class='jc'>{@link BuilderC} - For targeting constructors
+ *             <ul>
+ *                     <li class='jm'>{@link BuilderC#on(Constructor...) 
on(Constructor...)} - Target by Constructor object
+ *                     <li class='jm'>{@link BuilderC#on(ConstructorInfo...) 
on(ConstructorInfo...)} - Target by ConstructorInfo object
+ *             </ul>
+ *     <li class='jc'>{@link BuilderMF} - For targeting methods and fields
+ *             <ul>
+ *                     <li class='jm'>{@link BuilderMF#on(Method...) 
on(Method...)} - Target by Method object
+ *                     <li class='jm'>{@link BuilderMF#on(MethodInfo...) 
on(MethodInfo...)} - Target by MethodInfo object
+ *                     <li class='jm'>{@link BuilderMF#on(Field...) 
on(Field...)} - Target by Field object
+ *                     <li class='jm'>{@link BuilderMF#on(FieldInfo...) 
on(FieldInfo...)} - Target by FieldInfo object
+ *             </ul>
+ *     <li class='jc'>{@link BuilderTM} - For targeting classes and methods
+ *     <li class='jc'>{@link BuilderTMF} - For targeting classes, methods, and 
fields
+ *     <li class='jc'>{@link BuilderTMFC} - For targeting classes, methods, 
fields, and constructors
+ * </ul>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *     <jc>// Create a Bean annotation that applies to MyClass</jc>
+ *     BeanAnnotation <jv>annotation</jv> = BeanAnnotation
+ *             .<jsm>create</jsm>(MyClass.<jk>class</jk>)
+ *             .sort(<jk>true</jk>)
+ *             .build();
+ *
+ *     <jc>// Or target by string name</jc>
+ *     BeanAnnotation <jv>annotation2</jv> = BeanAnnotation
+ *             .<jsm>create</jsm>()
+ *             .on(<js>"com.example.MyClass"</js>)
+ *             .sort(<jk>true</jk>)
+ *             .build();
+ * </p>
+ *
+ * <h5 class='section'>Notes:</h5>
+ * <ul class='spaced-list'>
+ *     <li>The {@link #on()} method returns the string-based targets
+ *     <li>Subclasses may provide additional {@code onClass()}, {@code 
onMethod()}, etc. methods for type-safe access
+ *     <li>All builder methods return the builder for method chaining
+ *     <li>Use {@link #postConstruct()} after setting all properties to 
finalize hashcode calculation
+ * </ul>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='link'><a class="doclink" 
href="../../../../../overview-summary.html#juneau-common.Annotations">Overview 
&gt; juneau-common &gt; Annotations</a>
+ * </ul>
  */
 public class AppliedAnnotationObject extends AnnotationObject {
 
@@ -36,6 +125,26 @@ public class AppliedAnnotationObject extends 
AnnotationObject {
 
        /**
         * Builder for {@link AppliedAnnotationObject} objects.
+        *
+        * <p>
+        * Provides string-based targeting via the {@link #on(String...)} 
method.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Target specific classes and methods by name</jc>
+        *      MyAnnotation <jv>annotation</jv> = MyAnnotation
+        *              .<jsm>create</jsm>()
+        *              .on(<js>"com.example.MyClass"</js>)
+        *              .on(<js>"com.example.MyClass.myMethod"</js>)
+        *              .build();
+        * </p>
+        *
+        * <h5 class='section'>See Also:</h5><ul>
+        *      <li class='jc'>{@link BuilderT} - For type-safe class targeting
+        *      <li class='jc'>{@link BuilderM} - For method targeting
+        *      <li class='jc'>{@link BuilderC} - For constructor targeting
+        *      <li class='jc'>{@link BuilderMF} - For method and field 
targeting
+        * </ul>
         */
        public static class Builder extends AnnotationObject.Builder {
 
@@ -65,6 +174,32 @@ public class AppliedAnnotationObject extends 
AnnotationObject {
 
        /**
         * Builder for applied annotations targeting classes.
+        *
+        * <p>
+        * Adds type-safe class targeting methods to the base builder:
+        * <ul class='javatree'>
+        *      <li class='jm'>{@link #on(Class...) on(Class...)} - Target by 
class, stored as strings in {@link AppliedAnnotationObject#on()}
+        *      <li class='jm'>{@link #on(ClassInfo...) on(ClassInfo...)} - 
Target by ClassInfo, stored as strings
+        *      <li class='jm'>{@link #onClass(Class...) onClass(Class...)} - 
Target by class, stored as Class objects in {@link 
AppliedOnClassAnnotationObject#onClass()}
+        *      <li class='jm'>{@link #onClass(ClassInfo...) 
onClass(ClassInfo...)} - Target by ClassInfo, stored as Class objects
+        * </ul>
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Target classes using type-safe references</jc>
+        *      MyAnnotation <jv>annotation</jv> = MyAnnotation
+        *              .<jsm>create</jsm>()
+        *              .on(MyClass.<jk>class</jk>, 
MyOtherClass.<jk>class</jk>)  <jc>// Stored as strings</jc>
+        *              .onClass(ThirdClass.<jk>class</jk>)  <jc>// Stored as 
Class object</jc>
+        *              .build();
+        * </p>
+        *
+        * <h5 class='section'>Notes:</h5>
+        * <ul class='spaced-list'>
+        *      <li>Use {@link #on(Class...)} when you want string-based 
matching (e.g., for configuration)
+        *      <li>Use {@link #onClass(Class...)} when you need direct Class 
object references
+        *      <li>Both can be used together on the same builder
+        * </ul>
         */
        public static class BuilderT extends Builder {
 
@@ -132,6 +267,25 @@ public class AppliedAnnotationObject extends 
AnnotationObject {
 
        /**
         * Builder for applied annotations targeting methods.
+        *
+        * <p>
+        * Adds method targeting capabilities to the base builder:
+        * <ul class='javatree'>
+        *      <li class='jm'>{@link #on(Method...) on(Method...)} - Target by 
Method reflection object
+        *      <li class='jm'>{@link #on(MethodInfo...) on(MethodInfo...)} - 
Target by MethodInfo wrapper
+        * </ul>
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Target specific methods</jc>
+        *      Method <jv>method1</jv> = 
MyClass.<jk>class</jk>.getMethod(<js>"myMethod"</js>);
+        *      Method <jv>method2</jv> = 
MyClass.<jk>class</jk>.getMethod(<js>"otherMethod"</js>);
+        *
+        *      MyAnnotation <jv>annotation</jv> = MyAnnotation
+        *              .<jsm>create</jsm>()
+        *              .on(<jv>method1</jv>, <jv>method2</jv>)
+        *              .build();
+        * </p>
         */
        public static class BuilderM extends Builder {
 
@@ -171,6 +325,24 @@ public class AppliedAnnotationObject extends 
AnnotationObject {
 
        /**
         * Builder for applied annotations targeting constructors.
+        *
+        * <p>
+        * Adds constructor targeting capabilities to the base builder:
+        * <ul class='javatree'>
+        *      <li class='jm'>{@link #on(Constructor...) on(Constructor...)} - 
Target by Constructor reflection object
+        *      <li class='jm'>{@link #on(ConstructorInfo...) 
on(ConstructorInfo...)} - Target by ConstructorInfo wrapper
+        * </ul>
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Target specific constructors</jc>
+        *      Constructor&lt;?&gt; <jv>ctor</jv> = 
MyClass.<jk>class</jk>.getConstructor(String.<jk>class</jk>, 
<jk>int</jk>.<jk>class</jk>);
+        *
+        *      MyAnnotation <jv>annotation</jv> = MyAnnotation
+        *              .<jsm>create</jsm>()
+        *              .on(<jv>ctor</jv>)
+        *              .build();
+        * </p>
         */
        public static class BuilderC extends Builder {
 
@@ -210,6 +382,28 @@ public class AppliedAnnotationObject extends 
AnnotationObject {
 
        /**
         * Builder for applied annotations targeting methods and fields.
+        *
+        * <p>
+        * Adds method and field targeting capabilities to the base builder:
+        * <ul class='javatree'>
+        *      <li class='jm'>{@link #on(Method...) on(Method...)} - Target by 
Method reflection object
+        *      <li class='jm'>{@link #on(MethodInfo...) on(MethodInfo...)} - 
Target by MethodInfo wrapper
+        *      <li class='jm'>{@link #on(Field...) on(Field...)} - Target by 
Field reflection object
+        *      <li class='jm'>{@link #on(FieldInfo...) on(FieldInfo...)} - 
Target by FieldInfo wrapper
+        * </ul>
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Target specific methods and fields</jc>
+        *      Method <jv>method</jv> = 
MyClass.<jk>class</jk>.getMethod(<js>"myMethod"</js>);
+        *      Field <jv>field</jv> = 
MyClass.<jk>class</jk>.getField(<js>"myField"</js>);
+        *
+        *      MyAnnotation <jv>annotation</jv> = MyAnnotation
+        *              .<jsm>create</jsm>()
+        *              .on(<jv>method</jv>)
+        *              .on(<jv>field</jv>)
+        *              .build();
+        * </p>
         */
        public static class BuilderMF extends Builder {
 
@@ -273,6 +467,19 @@ public class AppliedAnnotationObject extends 
AnnotationObject {
 
        /**
         * Builder for applied annotations targeting classes and methods.
+        *
+        * <p>
+        * Combines the capabilities of {@link BuilderT} and {@link BuilderM}, 
providing targeting for both classes and methods.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Target both classes and methods</jc>
+        *      MyAnnotation <jv>annotation</jv> = MyAnnotation
+        *              .<jsm>create</jsm>()
+        *              .on(MyClass.<jk>class</jk>)  <jc>// Target class</jc>
+        *              
.on(MyClass.<jk>class</jk>.getMethod(<js>"myMethod"</js>))  <jc>// Target 
method</jc>
+        *              .build();
+        * </p>
         */
        public static class BuilderTM extends BuilderT {
 
@@ -324,6 +531,21 @@ public class AppliedAnnotationObject extends 
AnnotationObject {
 
        /**
         * Builder for applied annotations targeting classes, methods, and 
fields.
+        *
+        * <p>
+        * Combines the capabilities of {@link BuilderT}, {@link BuilderM}, and 
field targeting,
+        * providing comprehensive targeting for classes, methods, and fields.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Target classes, methods, and fields</jc>
+        *      MyAnnotation <jv>annotation</jv> = MyAnnotation
+        *              .<jsm>create</jsm>()
+        *              .on(MyClass.<jk>class</jk>)  <jc>// Target class</jc>
+        *              
.on(MyClass.<jk>class</jk>.getMethod(<js>"myMethod"</js>))  <jc>// Target 
method</jc>
+        *              
.on(MyClass.<jk>class</jk>.getField(<js>"myField"</js>))  <jc>// Target 
field</jc>
+        *              .build();
+        * </p>
         */
        public static class BuilderTMF extends BuilderT {
 
@@ -387,6 +609,22 @@ public class AppliedAnnotationObject extends 
AnnotationObject {
 
        /**
         * Builder for applied annotations targeting classes, methods, fields, 
and constructors.
+        *
+        * <p>
+        * The most comprehensive builder variant, combining all targeting 
capabilities from {@link BuilderT}, {@link BuilderM},
+        * field targeting, and {@link BuilderC}, providing complete targeting 
for all program elements.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Target any program element</jc>
+        *      MyAnnotation <jv>annotation</jv> = MyAnnotation
+        *              .<jsm>create</jsm>()
+        *              .on(MyClass.<jk>class</jk>)  <jc>// Target class</jc>
+        *              
.on(MyClass.<jk>class</jk>.getMethod(<js>"myMethod"</js>))  <jc>// Target 
method</jc>
+        *              
.on(MyClass.<jk>class</jk>.getField(<js>"myField"</js>))  <jc>// Target 
field</jc>
+        *              .on(MyClass.<jk>class</jk>.getConstructor())  <jc>// 
Target constructor</jc>
+        *              .build();
+        * </p>
         */
        public static class BuilderTMFC extends BuilderTMF {
 
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/annotation/AppliedOnClassAnnotationObject.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/annotation/AppliedOnClassAnnotationObject.java
index 290052077b..50f163a878 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/annotation/AppliedOnClassAnnotationObject.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/annotation/AppliedOnClassAnnotationObject.java
@@ -19,8 +19,68 @@ package org.apache.juneau.common.annotation;
 import static org.apache.juneau.common.utils.CollectionUtils.*;
 
 /**
- * An implementation of an annotation that has an <code>on</code> value 
targeting classes/methods/fields/constructors.
+ * An implementation of an annotation that can be dynamically applied to 
classes, methods, fields, and constructors,
+ * with additional support for type-safe class targeting via the {@link 
#onClass()} property.
  *
+ * <p>
+ * This class extends {@link AppliedAnnotationObject} to provide both 
string-based targeting (via {@link #on()})
+ * and type-safe class-based targeting (via {@link #onClass()}).
+ *
+ * <h5 class='section'>Difference between <c>on</c> and <c>onClass</c>:</h5>
+ * <ul class='spaced-list'>
+ *     <li><b>{@link #on()}</b> - Returns string-based targets (e.g., 
<js>"com.example.MyClass"</js>)
+ *             <br>Useful for:
+ *             <ul>
+ *                     <li>Configuration files where class references aren't 
available
+ *                     <li>Targeting classes that may not be loaded yet
+ *                     <li>Pattern matching or wildcard targeting
+ *             </ul>
+ *     <li><b>{@link #onClass()}</b> - Returns Class object targets (e.g., 
<c>MyClass.<jk>class</jk></c>)
+ *             <br>Useful for:
+ *             <ul>
+ *                     <li>Type-safe programmatic configuration
+ *                     <li>Direct class references in code
+ *                     <li>Avoiding string-based name matching
+ *             </ul>
+ * </ul>
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *     <jc>// Using onClass() for type-safe targeting</jc>
+ *     BeanAnnotation <jv>annotation</jv> = BeanAnnotation
+ *             .<jsm>create</jsm>()
+ *             .onClass(MyClass.<jk>class</jk>, MyOtherClass.<jk>class</jk>)
+ *             .sort(<jk>true</jk>)
+ *             .build();
+ *
+ *     <jc>// Using on() for string-based targeting</jc>
+ *     BeanAnnotation <jv>annotation2</jv> = BeanAnnotation
+ *             .<jsm>create</jsm>()
+ *             .on(<js>"com.example.MyClass"</js>, 
<js>"com.example.MyOtherClass"</js>)
+ *             .sort(<jk>true</jk>)
+ *             .build();
+ *
+ *     <jc>// Can use both together</jc>
+ *     BeanAnnotation <jv>annotation3</jv> = BeanAnnotation
+ *             .<jsm>create</jsm>()
+ *             .on(<js>"com.example.MyClass"</js>)  <jc>// String-based</jc>
+ *             .onClass(MyOtherClass.<jk>class</jk>)  <jc>// Type-safe</jc>
+ *             .sort(<jk>true</jk>)
+ *             .build();
+ * </p>
+ *
+ * <h5 class='section'>Notes:</h5>
+ * <ul class='spaced-list'>
+ *     <li>The {@link #on()} method returns string representations of ALL 
targets (both string-based and class-based)
+ *     <li>The {@link #onClass()} method returns only the Class object targets
+ *     <li>When using {@link AppliedAnnotationObject.BuilderT#on(Class...) 
BuilderT.on(Class...)}, classes are converted to strings and stored in {@link 
#on()}
+ *     <li>When using {@link 
AppliedAnnotationObject.BuilderT#onClass(Class...) BuilderT.onClass(Class...)}, 
classes are stored as Class objects in {@link #onClass()}
+ * </ul>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='jc'>{@link AppliedAnnotationObject} - Parent class 
documentation
+ *     <li class='link'><a class="doclink" 
href="../../../../../overview-summary.html#juneau-common.Annotations">Overview 
&gt; juneau-common &gt; Annotations</a>
+ * </ul>
  */
 public class AppliedOnClassAnnotationObject extends AppliedAnnotationObject {
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/common/annotation/AnnotationObject_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/common/annotation/AnnotationObject_Test.java
new file mode 100644
index 0000000000..d1066cc5f1
--- /dev/null
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/common/annotation/AnnotationObject_Test.java
@@ -0,0 +1,423 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.common.annotation;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.lang.annotation.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+class AnnotationObject_Test extends TestBase {
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // Test annotation for testing purposes
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Target({ElementType.TYPE, ElementType.METHOD})
+       @Retention(RetentionPolicy.RUNTIME)
+       public @interface TestAnnotation {
+               String value() default "";
+               int number() default 0;
+               boolean flag() default false;
+               String[] array() default {};
+       }
+
+       /**
+        * Implementation of TestAnnotation using AnnotationObject
+        */
+       public static class TestAnnotationObject extends AnnotationObject 
implements TestAnnotation {
+
+               private final String value;
+               private final int number;
+               private final boolean flag;
+               private final String[] array;
+
+               public static class Builder extends AnnotationObject.Builder {
+                       String value = "";
+                       int number = 0;
+                       boolean flag = false;
+                       String[] array = {};
+
+                       public Builder() {
+                               super(TestAnnotation.class);
+                       }
+
+                       public Builder value(String value) {
+                               this.value = value;
+                               return this;
+                       }
+
+                       public Builder number(int number) {
+                               this.number = number;
+                               return this;
+                       }
+
+                       public Builder flag(boolean flag) {
+                               this.flag = flag;
+                               return this;
+                       }
+
+                       public Builder array(String...array) {
+                               this.array = array;
+                               return this;
+                       }
+
+                       public TestAnnotation build() {
+                               return new TestAnnotationObject(this);
+                       }
+               }
+
+               public static Builder create() {
+                       return new Builder();
+               }
+
+               public TestAnnotationObject(Builder b) {
+                       super(b);
+                       this.value = b.value;
+                       this.number = b.number;
+                       this.flag = b.flag;
+                       this.array = Arrays.copyOf(b.array, b.array.length);
+                       postConstruct();
+               }
+
+               @Override
+               public String value() {
+                       return value;
+               }
+
+               @Override
+               public int number() {
+                       return number;
+               }
+
+               @Override
+               public boolean flag() {
+                       return flag;
+               }
+
+               @Override
+               public String[] array() {
+                       return array;
+               }
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // Basic tests
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Test
+       void a01_basic_defaultValues() {
+               TestAnnotation a = TestAnnotationObject.create().build();
+               assertEquals("", a.value());
+               assertEquals(0, a.number());
+               assertEquals(false, a.flag());
+               assertArrayEquals(new String[0], a.array());
+       }
+
+       @Test
+       void a02_basic_customValues() {
+               TestAnnotation a = TestAnnotationObject.create()
+                       .value("test")
+                       .number(42)
+                       .flag(true)
+                       .array("a", "b", "c")
+                       .build();
+
+               assertEquals("test", a.value());
+               assertEquals(42, a.number());
+               assertEquals(true, a.flag());
+               assertArrayEquals(new String[]{"a", "b", "c"}, a.array());
+       }
+
+       @Test
+       void a03_basic_annotationType() {
+               TestAnnotation a = TestAnnotationObject.create().build();
+               assertEquals(TestAnnotation.class, a.annotationType());
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // Equality and hashcode tests
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Test
+       void b01_equality_identical() {
+               TestAnnotation a1 = TestAnnotationObject.create()
+                       .value("test")
+                       .number(42)
+                       .flag(true)
+                       .array("a", "b")
+                       .build();
+
+               TestAnnotation a2 = TestAnnotationObject.create()
+                       .value("test")
+                       .number(42)
+                       .flag(true)
+                       .array("a", "b")
+                       .build();
+
+               assertEquals(a1, a2);
+               assertEquals(a1.hashCode(), a2.hashCode());
+       }
+
+       @Test
+       void b02_equality_different() {
+               TestAnnotation a1 = TestAnnotationObject.create()
+                       .value("test1")
+                       .build();
+
+               TestAnnotation a2 = TestAnnotationObject.create()
+                       .value("test2")
+                       .build();
+
+               assertNotEquals(a1, a2);
+       }
+
+       @Test
+       void b03_equality_withDeclaredAnnotation() {
+               @TestAnnotation(value="test", number=42, flag=true, array={"a", 
"b"})
+               class TestClass {}
+
+               TestAnnotation declared = 
TestClass.class.getAnnotation(TestAnnotation.class);
+               TestAnnotation programmatic = TestAnnotationObject.create()
+                       .value("test")
+                       .number(42)
+                       .flag(true)
+                       .array("a", "b")
+                       .build();
+
+               assertEquals(declared, programmatic);
+               assertEquals(declared.hashCode(), programmatic.hashCode());
+       }
+
+       @Test
+       void b04_hashCode_consistency() {
+               TestAnnotation a = TestAnnotationObject.create()
+                       .value("test")
+                       .number(42)
+                       .build();
+
+               int hash1 = a.hashCode();
+               int hash2 = a.hashCode();
+               assertEquals(hash1, hash2, "hashCode should be consistent");
+       }
+
+       @Test
+       void b05_hashCode_notNegativeOne() {
+               TestAnnotation a = TestAnnotationObject.create().build();
+               assertNotEquals(-1, a.hashCode(), "hashCode should not be -1 
after postConstruct");
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // toMap() tests
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Test
+       void c01_toMap_defaultValues() {
+               TestAnnotation a = TestAnnotationObject.create().build();
+               Map<String, Object> map = ((TestAnnotationObject)a).toMap();
+
+               assertNotNull(map);
+               assertEquals("", map.get("value"));
+               assertEquals(0, map.get("number"));
+               assertEquals(false, map.get("flag"));
+               assertArrayEquals(new String[0], (String[])map.get("array"));
+       }
+
+       @Test
+       void c02_toMap_customValues() {
+               TestAnnotation a = TestAnnotationObject.create()
+                       .value("test")
+                       .number(42)
+                       .flag(true)
+                       .array("a", "b")
+                       .build();
+               Map<String, Object> map = ((TestAnnotationObject)a).toMap();
+
+               assertEquals("test", map.get("value"));
+               assertEquals(42, map.get("number"));
+               assertEquals(true, map.get("flag"));
+               assertArrayEquals(new String[]{"a", "b"}, 
(String[])map.get("array"));
+       }
+
+       @Test
+       void c03_toMap_keySorted() {
+               TestAnnotation a = TestAnnotationObject.create().build();
+               Map<String, Object> map = ((TestAnnotationObject)a).toMap();
+
+               // Map should be ordered by key name
+               List<String> keys = new ArrayList<>(map.keySet());
+               assertEquals("array", keys.get(0));
+               assertEquals("flag", keys.get(1));
+               assertEquals("number", keys.get(2));
+               assertEquals("value", keys.get(3));
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // toString() tests
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Test
+       void d01_toString_notNull() {
+               TestAnnotation a = TestAnnotationObject.create().build();
+               String str = a.toString();
+               assertNotNull(str);
+               assertFalse(str.isEmpty());
+       }
+
+       @Test
+       void d02_toString_containsValues() {
+               TestAnnotation a = TestAnnotationObject.create()
+                       .value("test")
+                       .number(42)
+                       .build();
+               String str = a.toString();
+
+               assertTrue(str.contains("test"));
+               assertTrue(str.contains("42"));
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // postConstruct() requirement tests
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Test
+       void e01_postConstruct_required() {
+               // Test that hashCode() throws exception if postConstruct() was 
not called
+               AnnotationObject obj = new AnnotationObject(new 
AnnotationObject.Builder(TestAnnotation.class)) {
+                       // Intentionally not calling postConstruct()
+               };
+
+               Exception e = assertThrows(IllegalArgumentException.class, () 
-> {
+                       obj.hashCode();
+               });
+
+               assertTrue(e.getMessage().contains("postConstruct"));
+       }
+
+       @Test
+       void e02_postConstruct_called() {
+               // Test that hashCode() works correctly when postConstruct() is 
called
+               AnnotationObject obj = new AnnotationObject(new 
AnnotationObject.Builder(TestAnnotation.class)) {
+                       {
+                               postConstruct();
+                       }
+               };
+
+               assertNotEquals(-1, obj.hashCode());
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // Builder pattern tests
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Test
+       void f01_builder_fluentApi() {
+               TestAnnotation a = TestAnnotationObject.create()
+                       .value("test")
+                       .number(42)
+                       .flag(true)
+                       .array("a", "b")
+                       .build();
+
+               assertNotNull(a);
+               assertEquals("test", a.value());
+       }
+
+       @Test
+       void f02_builder_multipleBuilds() {
+               TestAnnotationObject.Builder builder = 
TestAnnotationObject.create()
+                       .value("test")
+                       .number(42);
+
+               TestAnnotation a1 = builder.build();
+               TestAnnotation a2 = builder.build();
+
+               // Different instances but equal
+               assertNotSame(a1, a2);
+               assertEquals(a1, a2);
+       }
+
+       @Test
+       void f03_builder_getAnnotationType() {
+               TestAnnotationObject.Builder builder = 
TestAnnotationObject.create();
+               assertEquals(TestAnnotation.class, builder.getAnnotationType());
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // Edge cases
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Test
+       void g01_arrayEquality_emptyVsNull() {
+               // Empty array and null should be handled consistently
+               TestAnnotation a1 = TestAnnotationObject.create()
+                       .array()
+                       .build();
+
+               TestAnnotation a2 = TestAnnotationObject.create()
+                       .array(new String[0])
+                       .build();
+
+               assertEquals(a1, a2);
+       }
+
+       @Test
+       void g02_arrayEquality_deepEquals() {
+               // Arrays with same content should be equal
+               TestAnnotation a1 = TestAnnotationObject.create()
+                       .array("a", "b", "c")
+                       .build();
+
+               TestAnnotation a2 = TestAnnotationObject.create()
+                       .array(new String[]{"a", "b", "c"})
+                       .build();
+
+               assertEquals(a1, a2);
+       }
+
+       @Test
+       void g03_arrayEquality_differentOrder() {
+               // Arrays with different order should not be equal
+               TestAnnotation a1 = TestAnnotationObject.create()
+                       .array("a", "b", "c")
+                       .build();
+
+               TestAnnotation a2 = TestAnnotationObject.create()
+                       .array("c", "b", "a")
+                       .build();
+
+               assertNotEquals(a1, a2);
+       }
+
+       @Test
+       void g04_equality_differentType() {
+               TestAnnotation a = TestAnnotationObject.create().build();
+               Object other = new Object();
+
+               assertNotEquals(a, other);
+       }
+
+       @Test
+       void g05_equality_null() {
+               TestAnnotation a = TestAnnotationObject.create().build();
+               assertNotEquals(a, null);
+       }
+}
+


Reply via email to