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
> juneau-common > 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
> juneau-common > 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<?> <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
> juneau-common > 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);
+ }
+}
+