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 9c7f8b02b8 Utility class modernization
9c7f8b02b8 is described below

commit 9c7f8b02b83b7fbfe601758a1b36b9b87d201e36
Author: James Bognar <[email protected]>
AuthorDate: Wed Nov 5 13:34:06 2025 -0500

    Utility class modernization
---
 .../juneau/common/reflect/AnnotationProvider2.java | 268 ++++++++++-
 .../apache/juneau/common/reflect/ClassInfo.java    |  64 ---
 .../juneau/common/reflect/ReflectionMap2.java      | 523 +++++++++++++++++++++
 .../java/org/apache/juneau/BeanPropertyMeta.java   |   6 +-
 .../src/main/java/org/apache/juneau/ClassMeta.java |  21 -
 .../src/main/java/org/apache/juneau/Context.java   | 129 +----
 .../java/org/apache/juneau/ClassMeta_Test.java     |  10 -
 .../juneau/common/reflect/ClassInfo_Test.java      | 150 +++---
 8 files changed, 887 insertions(+), 284 deletions(-)

diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider2.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider2.java
index d243e9c0c1..55c0ffab97 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider2.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider2.java
@@ -41,6 +41,18 @@ import org.apache.juneau.common.collections.*;
  *     <li>Caches results for performance
  * </ul>
  *
+ * <h5 class='section'>Usage:</h5>
+ * <p class='bjava'>
+ *     <jc>// Create with default settings</jc>
+ *     AnnotationProvider2 <jv>provider</jv> = 
AnnotationProvider2.<jsm>create</jsm>().build();
+ *
+ *     <jc>// Create with caching disabled</jc>
+ *     AnnotationProvider2 <jv>provider</jv> = AnnotationProvider2
+ *             .<jsm>create</jsm>()
+ *             .disableCaching()
+ *             .build();
+ * </p>
+ *
  * <h5 class='section'>See Also:</h5>
  * <ul>
  *     <li class='jc'>{@link AnnotationProvider}
@@ -57,12 +69,163 @@ public class AnnotationProvider2 {
        /**
         * Default instance.
         */
-       public static final AnnotationProvider2 INSTANCE = new 
AnnotationProvider2();
+       public static final AnnotationProvider2 INSTANCE = new 
AnnotationProvider2(create());
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Builder
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Builder for creating configured {@link AnnotationProvider2} 
instances.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      AnnotationProvider2 <jv>provider</jv> = AnnotationProvider2
+        *              .<jsm>create</jsm>()
+        *              .disableCaching()
+        *              .build();
+        * </p>
+        *
+        * <h5 class='section'>See Also:</h5>
+        * <ul>
+        *      <li class='jm'>{@link AnnotationProvider2#create()}
+        * </ul>
+        */
+       public static class Builder {
+               boolean disableCaching;
+               ReflectionMap2.Builder<Annotation> runtimeAnnotations = 
ReflectionMap2.create(Annotation.class);
+
+               Builder() {
+                       disableCaching = DISABLE_ANNOTATION_CACHING;
+               }
+
+               /**
+                * Builds a new {@link AnnotationProvider2} instance with the 
configured settings.
+                *
+                * @return A new immutable {@link AnnotationProvider2} instance.
+                */
+               public AnnotationProvider2 build() {
+                       return new AnnotationProvider2(this);
+               }
+
+               /**
+                * Disables annotation caching entirely.
+                *
+                * <p>
+                * When disabled, annotation lookups will always perform fresh 
searches without caching results.
+                *
+                * @return This object for method chaining.
+                */
+               public Builder disableCaching() {
+                       disableCaching = true;
+                       return this;
+               }
+
+               /**
+                * Conditionally disables or enables annotation caching.
+                *
+                * @param value Whether to disable caching.
+                * @return This object for method chaining.
+                */
+               public Builder disableCaching(boolean value) {
+                       disableCaching = value;
+                       return this;
+               }
+
+               /**
+                * Adds runtime annotations to be applied to classes and 
methods.
+                *
+                * <p>
+                * Annotations must define either an {@code onClass()} method 
that returns a {@code Class[]} array,
+                * or an {@code on()} method that returns a {@code String[]} 
array to specify the targets.
+                *
+                * @param annotations The annotations to add.
+                * @return This object for method chaining.
+                * @throws BeanRuntimeException If the annotations are invalid.
+                */
+               public Builder addRuntimeAnnotations(List<Annotation> 
annotations) {
+
+                       for (var a : annotations) {
+                               try {
+                                       var ci = ClassInfo.of(a.getClass());
+
+                                       MethodInfo mi = ci.getPublicMethod(x -> 
x.hasName("onClass"));
+                                       if (nn(mi)) {
+                                               if (! 
mi.getReturnType().is(Class[].class))
+                                                       throw new 
BeanRuntimeException("Invalid annotation @{0} used in runtime annotations.  
Annotation must define an onClass() method that returns a Class array.", 
scn(a));
+                                               for (var c : 
(Class<?>[])mi.accessible().invoke(a))
+                                                       
runtimeAnnotations.append(c.getName(), a);
+                                       }
+
+                                       mi = ci.getPublicMethod(x -> 
x.hasName("on"));
+                                       if (nn(mi)) {
+                                               if (! 
mi.getReturnType().is(String[].class))
+                                                       throw new 
BeanRuntimeException("Invalid annotation @{0} used in runtime annotations.  
Annotation must define an on() method that returns a String array.", scn(a));
+                                               for (var s : 
(String[])mi.accessible().invoke(a))
+                                                       
runtimeAnnotations.append(s, a);
+                                       }
+
+                               } catch (BeanRuntimeException e) {
+                                       throw e;
+                               } catch (Exception e) {
+                                       throw new BeanRuntimeException(e, null, 
"Invalid annotation @{0} used in runtime annotations.", cn(a));
+                               }
+                       }
+                       return this;
+               }
+
+               public Builder addRuntimeAnnotations(Annotation...annotations) {
+                       return addRuntimeAnnotations(l(annotations));
+               }
+       }
+
+       /**
+        * Creates a new {@link Builder} for constructing an annotation 
provider.
+        *
+        * @return A new builder for configuring the annotation provider.
+        */
+       public static Builder create() {
+               return new Builder();
+       }
 
        // @formatter:off
-       private final Cache<Class<?>,List<AnnotationInfo<Annotation>>> 
classAnnotationsInfo = 
Cache.<Class<?>,List<AnnotationInfo<Annotation>>>create().supplier(this::findClassAnnotations).disableCaching(DISABLE_ANNOTATION_CACHING).build();
+       private final Cache<Class<?>,List<AnnotationInfo<Annotation>>> 
classAnnotations;
+       private final Cache<Class<?>,List<AnnotationInfo<Annotation>>> 
classDeclaredAnnotations;
+       private final Cache<Method,List<AnnotationInfo<Annotation>>> 
methodAnnotations;
+       private final Cache<Field,List<AnnotationInfo<Annotation>>> 
fieldAnnotations;
+       private final Cache<Constructor<?>,List<AnnotationInfo<Annotation>>> 
constructorAnnotations;
+       private final ReflectionMap2<Annotation> runtimeAnnotations;
        // @formatter:on
 
+       /**
+        * Constructor.
+        *
+        * @param builder The builder containing configuration settings.
+        */
+       protected AnnotationProvider2(Builder builder) {
+               this.classAnnotations = 
Cache.<Class<?>,List<AnnotationInfo<Annotation>>>create()
+                       .supplier(this::findClassAnnotations)
+                       .disableCaching(builder.disableCaching)
+                       .build();
+               this.classDeclaredAnnotations = 
Cache.<Class<?>,List<AnnotationInfo<Annotation>>>create()
+                       .supplier(this::findClassDeclaredAnnotations)
+                       .disableCaching(builder.disableCaching)
+                       .build();
+               this.methodAnnotations = 
Cache.<Method,List<AnnotationInfo<Annotation>>>create()
+                       .supplier(this::findMethodAnnotations)
+                       .disableCaching(builder.disableCaching)
+                       .build();
+               this.fieldAnnotations = 
Cache.<Field,List<AnnotationInfo<Annotation>>>create()
+                       .supplier(this::findFieldAnnotations)
+                       .disableCaching(builder.disableCaching)
+                       .build();
+               this.constructorAnnotations = 
Cache.<Constructor<?>,List<AnnotationInfo<Annotation>>>create()
+                       .supplier(this::findConstructorAnnotations)
+                       .disableCaching(builder.disableCaching)
+                       .build();
+               this.runtimeAnnotations = builder.runtimeAnnotations.build();
+       }
+
 
        
//-----------------------------------------------------------------------------------------------------------------
        // Public API
@@ -80,7 +243,7 @@ public class AnnotationProvider2 {
         */
        public List<AnnotationInfo<Annotation>> find(Class<?> onClass) {
                assertArgNotNull("onClass", onClass);
-               return classAnnotationsInfo.get(onClass);
+               return classAnnotations.get(onClass);
        }
 
        /**
@@ -104,6 +267,61 @@ public class AnnotationProvider2 {
                        .map(a -> (AnnotationInfo<A>)a);
        }
 
+       public List<AnnotationInfo<Annotation>> findDeclared(Class<?> onClass) {
+               assertArgNotNull("onClass", onClass);
+               return classDeclaredAnnotations.get(onClass);
+       }
+
+       public <A extends Annotation> Stream<AnnotationInfo<A>> 
findDeclared(Class<A> type, Class<?> onClass) {
+               assertArgNotNull("type", type);
+               assertArgNotNull("onClass", onClass);
+               return findDeclared(onClass).stream()
+                       .filter(a -> a.isType(type))
+                       .map(a -> (AnnotationInfo<A>)a);
+       }
+
+       public List<AnnotationInfo<Annotation>> find(Method onMethod) {
+               assertArgNotNull("onMethod", onMethod);
+               return methodAnnotations.get(onMethod);
+       }
+
+       @SuppressWarnings("unchecked")
+       public <A extends Annotation> Stream<AnnotationInfo<A>> find(Class<A> 
type, Method onMethod) {
+               assertArgNotNull("type", type);
+               assertArgNotNull("onMethod", onMethod);
+               return find(onMethod).stream()
+                       .filter(a -> a.isType(type))
+                       .map(a -> (AnnotationInfo<A>)a);
+       }
+
+       public List<AnnotationInfo<Annotation>> find(Field onField) {
+               assertArgNotNull("onField", onField);
+               return fieldAnnotations.get(onField);
+       }
+
+       @SuppressWarnings("unchecked")
+       public <A extends Annotation> Stream<AnnotationInfo<A>> find(Class<A> 
type, Field onField) {
+               assertArgNotNull("type", type);
+               assertArgNotNull("onField", onField);
+               return find(onField).stream()
+                       .filter(a -> a.isType(type))
+                       .map(a -> (AnnotationInfo<A>)a);
+       }
+
+       public List<AnnotationInfo<Annotation>> find(Constructor<?> 
onConstructor) {
+               assertArgNotNull("onConstructor", onConstructor);
+               return constructorAnnotations.get(onConstructor);
+       }
+
+       @SuppressWarnings("unchecked")
+       public <A extends Annotation> Stream<AnnotationInfo<A>> find(Class<A> 
type, Constructor<?> onConstructor) {
+               assertArgNotNull("type", type);
+               assertArgNotNull("onConstructor", onConstructor);
+               return find(onConstructor).stream()
+                       .filter(a -> a.isType(type))
+                       .map(a -> (AnnotationInfo<A>)a);
+       }
+
        
//-----------------------------------------------------------------------------------------------------------------
        // Private implementation
        
//-----------------------------------------------------------------------------------------------------------------
@@ -148,6 +366,46 @@ public class AnnotationProvider2 {
                return u(list);
        }
 
+       private List<AnnotationInfo<Annotation>> 
findClassDeclaredAnnotations(Class<?> forClass) {
+               var list = new ArrayList<AnnotationInfo<Annotation>>();
+
+               // On this class
+               findDeclaredAnnotations(list, forClass);
+
+               return u(list);
+       }
+
+       private List<AnnotationInfo<Annotation>> findMethodAnnotations(Method 
forMethod) {
+               var list = new ArrayList<AnnotationInfo<Annotation>>();
+
+               MethodInfo.of(forMethod).getMatchingMethods().forEach(m -> {
+                       runtimeAnnotations.findMatching(m.inner()).forEach(a -> 
list.add(AnnotationInfo.of(m, a)));
+                       list.addAll(m.getDeclaredAnnotationInfos());
+               });
+
+               return u(list);
+       }
+
+       private List<AnnotationInfo<Annotation>> findFieldAnnotations(Field 
forField) {
+               var list = new ArrayList<AnnotationInfo<Annotation>>();
+
+               FieldInfo fi = FieldInfo.of(forField);
+               runtimeAnnotations.findMatching(forField).forEach(a -> 
list.add(AnnotationInfo.of(fi, a)));
+               list.addAll(fi.getAnnotationInfos());
+
+               return u(list);
+       }
+
+       private List<AnnotationInfo<Annotation>> 
findConstructorAnnotations(Constructor<?> forConstructor) {
+               var list = new ArrayList<AnnotationInfo<Annotation>>();
+
+               ConstructorInfo ci = ConstructorInfo.of(forConstructor);
+               runtimeAnnotations.findMatching(forConstructor).forEach(a -> 
list.add(AnnotationInfo.of(ci, a)));
+               list.addAll(ci.getDeclaredAnnotationInfos());
+
+               return u(list);
+       }
+
        /**
         * Finds all declared annotations on the specified class and appends 
them to the list.
         *
@@ -156,6 +414,7 @@ public class AnnotationProvider2 {
         */
        private void findDeclaredAnnotations(List<AnnotationInfo<Annotation>> 
appendTo, Class<?> forClass) {
                var ci = ClassInfo.of(forClass);
+               runtimeAnnotations.findMatching(forClass).forEach(x -> 
appendTo.add(AnnotationInfo.of(ClassInfo.of(forClass), x)));
                for (var a : forClass.getDeclaredAnnotations())
                        for (var a2 : splitRepeated(a))
                                appendTo.add(AnnotationInfo.of(ci, a2));
@@ -173,5 +432,4 @@ public class AnnotationProvider2 {
                        for (var a2 : splitRepeated(a))
                                appendTo.add(AnnotationInfo.of(pi, a2));
        }
-}
-
+}
\ No newline at end of file
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ClassInfo.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ClassInfo.java
index b885b98b92..d064767dcc 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ClassInfo.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ClassInfo.java
@@ -313,70 +313,6 @@ public class ClassInfo extends ElementInfo implements 
Annotatable {
                return (o instanceof ClassInfo o2) && eq(this, o2, (x, y) -> 
eq(x.t, y.t));
        }
 
-       /**
-        * Returns the first matching annotation on this class and 
superclasses/interfaces.
-        *
-        * <p>
-        * Annotations are searched in the following orders:
-        * <ol>
-        *      <li>On the package of this class.
-        *      <li>On interfaces ordered parent-to-child.
-        *      <li>On parent classes ordered parent-to-child.
-        *      <li>On this class.
-        * </ol>
-        *
-        * @param <A> The annotation type to look for.
-        * @param annotationProvider The annotation provider.
-        * @param type The annotation to look for.
-        * @param filter A predicate to apply to the entries to determine if 
annotation should be returned.  Can be <jk>null</jk>.
-        * @return This object.
-        */
-       public <A extends Annotation> A firstAnnotation(AnnotationProvider 
annotationProvider, Class<A> type, Predicate<A> filter) {
-               if (annotationProvider == null)
-                       annotationProvider = AnnotationProvider.DEFAULT;
-               A x = null;
-               x = getPackageAnnotation(type);
-               if (nn(x) && test(filter, x))
-                       return x;
-               var interfaces2 = interfaces.get();
-               for (int i = interfaces2.size() - 1; i >= 0; i--) {
-                       x = annotationProvider.firstAnnotation(type, 
interfaces2.get(i).inner(), filter);
-                       if (nn(x))
-                               return x;
-               }
-               var parents2 = parents.get();
-               for (int i = parents2.size() - 1; i >= 0; i--) {
-                       x = annotationProvider.firstAnnotation(type, 
parents2.get(i).inner(), filter);
-                       if (nn(x))
-                               return x;
-               }
-               x = annotationProvider.firstAnnotation(type, inner(), filter);
-               if (nn(x) && test(filter, x))
-                       return x;
-               return null;
-       }
-
-       /**
-        * Returns the first matching annotation on this class and 
superclasses/interfaces.
-        *
-        * <p>
-        * Annotations are searched in the following orders:
-        * <ol>
-        *      <li>On the package of this class.
-        *      <li>On interfaces ordered parent-to-child.
-        *      <li>On parent classes ordered parent-to-child.
-        *      <li>On this class.
-        * </ol>
-        *
-        * @param <A> The annotation type to look for.
-        * @param type The annotation to look for.
-        * @param filter A predicate to apply to the entries to determine if 
annotation should be returned.  Can be <jk>null</jk>.
-        * @return This object.
-        */
-       public <A extends Annotation> A firstAnnotation(Class<A> type, 
Predicate<A> filter) {
-               return firstAnnotation(null, type, filter);
-       }
-
        /**
         * Performs an action on all matching fields on this class and all 
parent classes.
         *
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ReflectionMap2.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ReflectionMap2.java
new file mode 100644
index 0000000000..42bc351f17
--- /dev/null
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ReflectionMap2.java
@@ -0,0 +1,523 @@
+/*
+ * 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.reflect;
+
+import static java.lang.Character.*;
+import static org.apache.juneau.common.utils.CollectionUtils.*;
+import static org.apache.juneau.common.utils.StringUtils.*;
+import static org.apache.juneau.common.utils.ThrowableUtils.*;
+import static org.apache.juneau.common.utils.Utils.*;
+
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
+
+import org.apache.juneau.common.utils.*;
+
+/**
+ * Allows arbitrary objects to be mapped to classes and methods base on 
class/method name keys.
+ *
+ * <p>
+ * The valid pattern matches are:
+ * <ul class='spaced-list'>
+ *  <li>Classes:
+ *             <ul>
+ *                     <li>Fully qualified:
+ *                             <ul>
+ *                                     <li><js>"com.foo.MyClass"</js>
+ *                             </ul>
+ *                     <li>Fully qualified inner class:
+ *                             <ul>
+ *                                     
<li><js>"com.foo.MyClass$Inner1$Inner2"</js>
+ *                             </ul>
+ *                     <li>Simple:
+ *                             <ul>
+ *                                     <li><js>"MyClass"</js>
+ *                             </ul>
+ *                     <li>Simple inner:
+ *                             <ul>
+ *                                     <li><js>"MyClass$Inner1$Inner2"</js>
+ *                                     <li><js>"Inner1$Inner2"</js>
+ *                                     <li><js>"Inner2"</js>
+ *                             </ul>
+ *             </ul>
+ *     <li>Methods:
+ *             <ul>
+ *                     <li>Fully qualified with args:
+ *                             <ul>
+ *                                     
<li><js>"com.foo.MyClass.myMethod(String,int)"</js>
+ *                                     
<li><js>"com.foo.MyClass.myMethod(java.lang.String,int)"</js>
+ *                                     
<li><js>"com.foo.MyClass.myMethod()"</js>
+ *                             </ul>
+ *                     <li>Fully qualified:
+ *                             <ul>
+ *                                     <li><js>"com.foo.MyClass.myMethod"</js>
+ *                             </ul>
+ *                     <li>Simple with args:
+ *                             <ul>
+ *                                     
<li><js>"MyClass.myMethod(String,int)"</js>
+ *                                     
<li><js>"MyClass.myMethod(java.lang.String,int)"</js>
+ *                                     <li><js>"MyClass.myMethod()"</js>
+ *                             </ul>
+ *                     <li>Simple:
+ *                             <ul>
+ *                                     <li><js>"MyClass.myMethod"</js>
+ *                             </ul>
+ *                     <li>Simple inner class:
+ *                             <ul>
+ *                                     
<li><js>"MyClass$Inner1$Inner2.myMethod"</js>
+ *                                     <li><js>"Inner1$Inner2.myMethod"</js>
+ *                                     <li><js>"Inner2.myMethod"</js>
+ *                             </ul>
+ *             </ul>
+ *     <li>Fields:
+ *             <ul>
+ *                     <li>Fully qualified:
+ *                             <ul>
+ *                                     <li><js>"com.foo.MyClass.myField"</js>
+ *                             </ul>
+ *                     <li>Simple:
+ *                             <ul>
+ *                                     <li><js>"MyClass.myField"</js>
+ *                             </ul>
+ *                     <li>Simple inner class:
+ *                             <ul>
+ *                                     
<li><js>"MyClass$Inner1$Inner2.myField"</js>
+ *                                     <li><js>"Inner1$Inner2.myField"</js>
+ *                                     <li><js>"Inner2.myField"</js>
+ *                             </ul>
+ *             </ul>
+ *     <li>Constructors:
+ *             <ul>
+ *                     <li>Fully qualified with args:
+ *                             <ul>
+ *                                     
<li><js>"com.foo.MyClass(String,int)"</js>
+ *                                     
<li><js>"com.foo.MyClass(java.lang.String,int)"</js>
+ *                                     <li><js>"com.foo.MyClass()"</js>
+ *                             </ul>
+ *                     <li>Simple with args:
+ *                             <ul>
+ *                                     <li><js>"MyClass(String,int)"</js>
+ *                                     
<li><js>"MyClass(java.lang.String,int)"</js>
+ *                                     <li><js>"MyClass()"</js>
+ *                             </ul>
+ *                     <li>Simple inner class:
+ *                             <ul>
+ *                                     <li><js>"MyClass$Inner1$Inner2()"</js>
+ *                                     <li><js>"Inner1$Inner2()"</js>
+ *                                     <li><js>"Inner2()"</js>
+ *                             </ul>
+ *             </ul>
+ *     <li>A comma-delimited list of anything on this list.
+ * </ul>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * </ul>
+ *
+ * @param <V> The type of object in this map.
+ */
+public class ReflectionMap2<V> {
+
+       /**
+        * Builder class.
+        * @param <V> The type of object in this map.
+        */
+       public static class Builder<V> {
+               final List<ClassEntry<V>> classEntries;
+               final List<MethodEntry<V>> methodEntries;
+               final List<FieldEntry<V>> fieldEntries;
+               final List<ConstructorEntry<V>> constructorEntries;
+
+               /**
+                * Constructor.
+                */
+               protected Builder() {
+                       classEntries = list();
+                       methodEntries = list();
+                       fieldEntries = list();
+                       constructorEntries = list();
+               }
+
+               /**
+                * Copy constructor.
+                *
+                * @param copyFrom The builder being copied.
+                */
+               protected Builder(Builder<V> copyFrom) {
+                       classEntries = copyOf(copyFrom.classEntries);
+                       methodEntries = copyOf(copyFrom.methodEntries);
+                       fieldEntries = copyOf(copyFrom.fieldEntries);
+                       constructorEntries = 
copyOf(copyFrom.constructorEntries);
+               }
+
+               /**
+                * Adds a mapping to this builder.
+                *
+                * @param key
+                *      The mapping key.
+                *      <br>Can be any of the following:
+                *      <ul>
+                *              <li>Full class name (e.g. 
<js>"com.foo.MyClass"</js>).
+                *              <li>Simple class name (e.g. <js>"MyClass"</js>).
+                *              <li>All classes (e.g. <js>"*"</js>).
+                *              <li>Full method name (e.g. 
<js>"com.foo.MyClass.myMethod"</js>).
+                *              <li>Simple method name (e.g. 
<js>"MyClass.myMethod"</js>).
+                *              <li>A comma-delimited list of anything on this 
list.
+                *      </ul>
+                * @param value The value for this mapping.
+                * @return This object.
+                */
+               public Builder<V> append(String key, V value) {
+                       if (StringUtils.isEmpty(key))
+                               throw runtimeException("Invalid reflection 
signature: [{0}]", key);
+                       try {
+                               splitNames(key, k -> {
+                                       if (k.endsWith(")")) {
+                                               int i = k.substring(0, 
k.indexOf('(')).lastIndexOf('.');
+                                               if (i == -1 || 
isUpperCase(k.charAt(i + 1))) {
+                                                       
constructorEntries.add(new ConstructorEntry<>(k, value));
+                                               } else {
+                                                       methodEntries.add(new 
MethodEntry<>(k, value));
+                                               }
+                                       } else {
+                                               int i = k.lastIndexOf('.');
+                                               if (i == -1) {
+                                                       classEntries.add(new 
ClassEntry<>(k, value));
+                                               } else if 
(isUpperCase(k.charAt(i + 1))) {
+                                                       classEntries.add(new 
ClassEntry<>(k, value));
+                                                       fieldEntries.add(new 
FieldEntry<>(k, value));
+                                               } else {
+                                                       methodEntries.add(new 
MethodEntry<>(k, value));
+                                                       fieldEntries.add(new 
FieldEntry<>(k, value));
+                                               }
+                                       }
+                               });
+                       } catch (@SuppressWarnings("unused") 
IndexOutOfBoundsException e) {
+                               throw runtimeException("Invalid reflection 
signature: [{0}]", key);
+                       }
+
+                       return this;
+               }
+
+               /**
+                * Create new instance of {@link ReflectionMap2} based on the 
contents of this builder.
+                *
+                * @return A new {@link ReflectionMap2} object.
+                */
+               public ReflectionMap2<V> build() {
+                       return new ReflectionMap2<>(this);
+               }
+
+               /**
+                * Creates a copy of this builder.
+                *
+                * @return A copy of this builder.
+                */
+               public Builder<V> copy() {
+                       return new Builder<>(this);
+               }
+       }
+
+       static class ClassEntry<V> {
+               final String simpleName, fullName;
+               final V value;
+
+               ClassEntry(String name, V value) {
+                       this.simpleName = simpleClassName(name);
+                       this.fullName = name;
+                       this.value = value;
+               }
+
+               public boolean matches(Class<?> c) {
+                       if (c == null)
+                               return false;
+                       return classMatches(simpleName, fullName, c);
+               }
+
+               @Override
+               public String toString() {
+                       // @formatter:off
+                       return mapb().filtered()
+                               .add("simpleName", simpleName)
+                               .add("fullName", fullName)
+                               .add("value", value)
+                               .toString();
+                       // @formatter:on
+               }
+       }
+
+       static class ConstructorEntry<V> {
+               String simpleClassName, fullClassName, args[];
+               V value;
+
+               ConstructorEntry(String name, V value) {
+                       int i = name.indexOf('(');
+                       this.args = splita(name.substring(i + 1, name.length() 
- 1));
+                       name = name.substring(0, i).trim();
+                       this.simpleClassName = simpleClassName(name);
+                       this.fullClassName = name;
+                       this.value = value;
+               }
+
+               public boolean matches(Constructor<?> m) {
+                       if (m == null)
+                               return false;
+                       var c = m.getDeclaringClass();
+                       return classMatches(simpleClassName, fullClassName, c) 
&& (argsMatch(args, m.getParameterTypes()));
+               }
+
+               @Override
+               public String toString() {
+                       // @formatter:off
+                       return mapb().filtered()
+                               .add("simpleClassName", simpleClassName)
+                               .add("fullClassName", fullClassName)
+                               .add("args", args)
+                               .add("value", value)
+                               .toString();
+                       // @formatter:on
+               }
+       }
+
+       static class FieldEntry<V> {
+               String simpleClassName, fullClassName, fieldName;
+               V value;
+
+               FieldEntry(String name, V value) {
+                       int i = name.lastIndexOf('.');
+                       var s1 = name.substring(0, i);
+                       var s2 = name.substring(i + 1);
+                       this.simpleClassName = simpleClassName(s1);
+                       this.fullClassName = s1;
+                       this.fieldName = s2;
+                       this.value = value;
+               }
+
+               public boolean matches(Field f) {
+                       if (f == null)
+                               return false;
+                       var c = f.getDeclaringClass();
+                       return classMatches(simpleClassName, fullClassName, c) 
&& (eq(f.getName(), fieldName));
+               }
+
+               @Override
+               public String toString() {
+                       // @formatter:off
+                       return mapb().filtered()
+                               .add("simpleClassName", simpleClassName)
+                               .add("fullClassName", fullClassName)
+                               .add("fieldName", fieldName)
+                               .add("value", value)
+                               .toString();
+                       // @formatter:on
+               }
+       }
+
+       static class MethodEntry<V> {
+               String simpleClassName, fullClassName, methodName, args[];
+               V value;
+
+               MethodEntry(String name, V value) {
+                       int i = name.indexOf('(');
+                       this.args = i == -1 ? null : 
splitMethodArgs(name.substring(i + 1, name.length() - 1));
+                       if (nn(args)) {
+                               for (int j = 0; j < args.length; j++) {
+
+                                       // Strip off generic parameters.
+                                       int k = args[j].indexOf('<');
+                                       if (k > 0)
+                                               args[j] = args[j].substring(0, 
k);
+
+                                       // Convert from xxx[][] to [[Lxxx; 
notation.
+                                       if (args[j].endsWith("[]")) {
+                                               int l = 0;
+                                               while (args[j].endsWith("[]")) {
+                                                       l++;
+                                                       args[j] = 
args[j].substring(0, args[j].length() - 2);
+                                               }
+                                               var sb = new 
StringBuilder(args[j].length() + l + 2);
+                                               for (int m = 0; m < l; m++)
+                                                       sb.append('[');
+                                               
sb.append('L').append(args[j]).append(';');
+                                               args[j] = sb.toString();
+                                       }
+                               }
+                       }
+                       name = i == -1 ? name : name.substring(0, i);
+                       i = name.lastIndexOf('.');
+                       var s1 = name.substring(0, i).trim();
+                       var s2 = name.substring(i + 1).trim();
+                       this.simpleClassName = simpleClassName(s1);
+                       this.fullClassName = s1;
+                       this.methodName = s2;
+                       this.value = value;
+               }
+
+               public boolean matches(Method m) {
+                       if (m == null)
+                               return false;
+                       var c = m.getDeclaringClass();
+                       // @formatter:off
+                       return
+                               classMatches(simpleClassName, fullClassName, c)
+                               && (eq(m.getName(), methodName))
+                               && (argsMatch(args, m.getParameterTypes()));
+                       // @formatter:on
+               }
+
+               @Override
+               public String toString() {
+                       // @formatter:off
+                       return mapb().filtered()
+                               .add("simpleClassName", simpleClassName)
+                               .add("fullClassName", fullClassName)
+                               .add("methodName", methodName)
+                               .add("args", args)
+                               .add("value", value)
+                               .toString();
+                       // @formatter:on
+               }
+       }
+
+       /**
+        * Static builder creator.
+        *
+        * @param <V> The type of object in this map.
+        * @param c The type of object in this map.
+        * @return A new instance of this object.
+        */
+       public static <V> Builder<V> create(Class<V> c) {
+               return new Builder<>();
+       }
+
+       static boolean argsMatch(String[] names, Class<?>[] args) {
+               if (names == null)
+                       return true;
+               if (names.length != args.length)
+                       return false;
+               for (int i = 0; i < args.length; i++) {
+                       var n = names[i];
+                       var a = args[i];
+                       if (! (eq(n, a.getSimpleName()) || eq(n, a.getName())))
+                               return false;
+               }
+               return true;
+       }
+
+       static boolean classMatches(String simpleName, String fullName, 
Class<?> c) {
+               // For class 
org.apache.juneau.a.rttests.RountTripBeansWithBuilders$Ac$Builder
+               // c.getSimpleName() == "Builder"
+               // c.getFullName() == 
"org.apache.juneau.a.rttests.RountTripBeansWithBuilders$Ac$Builder"
+               // c.getPackage() == "org.apache.juneau.a.rttests"
+               var cSimple = scn(c);
+               var cFull = cn(c);
+               if (eq(simpleName, cSimple) || eq(fullName, cFull) || 
"*".equals(simpleName))
+                       return true;
+               if (cFull.indexOf('$') != -1) {
+                       var p = c.getPackage();
+                       if (nn(p))
+                               cFull = cFull.substring(p.getName().length() + 
1);
+                       if (eq(simpleName, cFull))
+                               return true;
+                       int i = cFull.indexOf('$');
+                       while (i != -1) {
+                               cFull = cFull.substring(i + 1);
+                               if (eq(simpleName, cFull))
+                                       return true;
+                               i = cFull.indexOf('$');
+                       }
+               }
+               return false;
+       }
+
+       static String simpleClassName(String name) {
+               int i = name.indexOf('.');
+               if (i == -1)
+                       return name;
+               return null;
+       }
+
+       static void splitNames(String key, Consumer<String> consumer) {
+               if (key.indexOf(',') == -1) {
+                       consumer.accept(key);
+               } else {
+                       int m = 0;
+                       boolean escaped = false;
+                       for (int i = 0; i < key.length(); i++) {
+                               char c = key.charAt(i);
+                               if (c == '(')
+                                       escaped = true;
+                               else if (c == ')')
+                                       escaped = false;
+                               else if (c == ',' && ! escaped) {
+                                       consumer.accept(key.substring(m, 
i).trim());
+                                       m = i + 1;
+                               }
+                       }
+                       consumer.accept(key.substring(m).trim());
+               }
+       }
+
+       final List<ClassEntry<V>> classEntries;
+
+       final List<MethodEntry<V>> methodEntries;
+
+       final List<FieldEntry<V>> fieldEntries;
+
+       final List<ConstructorEntry<V>> constructorEntries;
+
+       /**
+        * Constructor.
+        *
+        * @param b Initializer object.
+        */
+       protected ReflectionMap2(Builder<V> b) {
+               this.classEntries = u(copyOf(b.classEntries));
+               this.methodEntries = u(copyOf(b.methodEntries));
+               this.fieldEntries =  u(copyOf(b.fieldEntries));
+               this.constructorEntries = u(copyOf(b.constructorEntries));
+       }
+
+       public Stream<V> findMatching(Class<?> c) {
+               return classEntries.stream().filter(x -> x.matches(c)).map(x -> 
x.value);
+       }
+
+       public Stream<V> findMatching(Method m) {
+               return methodEntries.stream().filter(x -> x.matches(m)).map(x 
-> x.value);
+       }
+
+       public Stream<V> findMatching(Field f) {
+               return fieldEntries.stream().filter(x -> x.matches(f)).map(x -> 
x.value);
+       }
+
+       public Stream<V> findMatching(Constructor c) {
+               return constructorEntries.stream().filter(x -> 
x.matches(c)).map(x -> x.value);
+       }
+
+       @Override /* Overridden from Object */
+       public String toString() {
+               // @formatter:off
+               return mapb().filtered()
+                       .add("classEntries", classEntries)
+                       .add("methodEntries", methodEntries)
+                       .add("fieldEntries", fieldEntries)
+                       .add("constructorEntries", constructorEntries)
+                       .toString();
+               // @formatter:on
+       }
+}
\ No newline at end of file
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
index 0c1d9468e2..d6874cbf8d 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
@@ -635,9 +635,9 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
        public <A extends Annotation> BeanPropertyMeta 
forEachAnnotation(Class<A> a, Predicate<A> filter, Consumer<A> action) {
                BeanContext bc = beanContext;
                if (nn(a)) {
-                       bc.forEachAnnotation(a, field, filter, action);
-                       bc.forEachAnnotation(a, getter, filter, action);
-                       bc.forEachAnnotation(a, setter, filter, action);
+                       if (nn(field)) bc.forEachAnnotation(a, field, filter, 
action);
+                       if (nn(getter)) bc.forEachAnnotation(a, getter, filter, 
action);
+                       if (nn(setter)) bc.forEachAnnotation(a, setter, filter, 
action);
                }
                return this;
        }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
index 0e87ae4a69..8b0dec2190 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ClassMeta.java
@@ -851,27 +851,6 @@ public class ClassMeta<T> implements Type {
                return (o instanceof ClassMeta<?> o2) && eq(this, o2, (x, y) -> 
eq(x.innerClass, y.innerClass));
        }
 
-       /**
-        * Returns the first matching annotation on this class or parent 
classes/interfaces in parent-to-child order.
-        *
-        * @param <A> The annotation type to look for.
-        * @param type The annotation to search for.
-        * @param filter A predicate to apply to the entries to determine if 
annotation should be used.  Can be <jk>null</jk>.
-        * @return This object.
-        */
-       public <A extends Annotation> Optional<A> firstAnnotation(Class<A> 
type, Predicate<A> filter) {
-               A[] array = annotationArray(type);
-               if (array == null) {
-                       if (beanContext == null)
-                               return 
Optional.ofNullable(info.firstAnnotation(BeanContext.DEFAULT, type, filter));
-                       return Optional.empty();
-               }
-               for (var a : array)
-                       if (test(filter, a))
-                               return Optional.of(a);
-               return Optional.empty();
-       }
-
        /**
         * Performs an action on all matching annotations of the specified type 
defined on this class or parent classes/interfaces in parent-to-child order.
         *
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Context.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Context.java
index 2011d50a82..7d8330f0e9 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Context.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Context.java
@@ -759,11 +759,8 @@ public abstract class Context implements 
AnnotationProvider {
        final boolean debug;
 
        private final ReflectionMap<Annotation> annotationMap;
-       private final Cache2<Class<?>,Class<? extends Annotation>,Annotation[]> 
classAnnotationCache;
        private final Cache2<Class<?>,Class<? extends Annotation>,Annotation[]> 
declaredClassAnnotationCache;
-       private final Cache2<Method,Class<? extends Annotation>,Annotation[]> 
methodAnnotationCache;
-       private final Cache2<Field,Class<? extends Annotation>,Annotation[]> 
fieldAnnotationCache;
-       private final Cache2<Constructor<?>,Class<? extends 
Annotation>,Annotation[]> constructorAnnotationCache;
+       private final AnnotationProvider2 annotationProvider;
 
        /**
         * Constructor for this class.
@@ -804,11 +801,9 @@ public abstract class Context implements 
AnnotationProvider {
                });
                this.annotationMap = rmb.build();
                var disabled = 
Boolean.getBoolean("juneau.disableAnnotationCaching");
-               classAnnotationCache = (Cache2)Cache2.of(Class.class, 
Class.class, Annotation[].class).disableCaching(disabled).supplier((k1, k2) -> 
annotationMap.appendAll(k1, k2, k1.getAnnotationsByType(k2))).build();
                declaredClassAnnotationCache = (Cache2)Cache2.of(Class.class, 
Class.class, Annotation[].class).disableCaching(disabled).supplier((k1, k2) -> 
annotationMap.appendAll(k1, k2, k1.getDeclaredAnnotationsByType(k2))).build();
-               methodAnnotationCache = (Cache2)Cache2.of(Method.class, 
Class.class, Annotation[].class).disableCaching(disabled).supplier((k1, k2) -> 
annotationMap.appendAll(k1, k2, k1.getAnnotationsByType(k2))).build();
-               fieldAnnotationCache = (Cache2)Cache2.of(Field.class, 
Class.class, Annotation[].class).disableCaching(disabled).supplier((k1, k2) -> 
annotationMap.appendAll(k1, k2, k1.getAnnotationsByType(k2))).build();
-               constructorAnnotationCache = 
(Cache2)Cache2.of(Constructor.class, Class.class, 
Annotation[].class).disableCaching(disabled).supplier((k1, k2) -> 
annotationMap.appendAll(k1, k2, k1.getAnnotationsByType(k2))).build();
+
+               annotationProvider = 
AnnotationProvider2.create().addRuntimeAnnotations(annotations).build();
        }
 
        /**
@@ -820,11 +815,12 @@ public abstract class Context implements 
AnnotationProvider {
                annotationMap = copyFrom.annotationMap;
                annotations = copyFrom.annotations;
                debug = copyFrom.debug;
-               classAnnotationCache = copyFrom.classAnnotationCache;
                declaredClassAnnotationCache = 
copyFrom.declaredClassAnnotationCache;
-               methodAnnotationCache = copyFrom.methodAnnotationCache;
-               fieldAnnotationCache = copyFrom.fieldAnnotationCache;
-               constructorAnnotationCache = 
copyFrom.constructorAnnotationCache;
+               annotationProvider = copyFrom.annotationProvider;
+       }
+
+       public AnnotationProvider2 getAnnotationProvider() {
+               return annotationProvider;
        }
 
        /**
@@ -854,75 +850,47 @@ public abstract class Context implements 
AnnotationProvider {
 
        @Override /* Overridden from MetaProvider */
        public <A extends Annotation> A firstAnnotation(Class<A> type, Class<?> 
onClass, Predicate<A> filter) {
-               if (nn(type) && nn(onClass))
-                       for (var a : annotations(type, onClass))
-                               if (test(filter, a))
-                                       return a;
-               return null;
+               return getAnnotationProvider().find(type, onClass).map(x -> 
x.inner()).filter(x -> filter.test(x)).findFirst().orElse(null);
        }
 
        @Override /* Overridden from MetaProvider */
        public <A extends Annotation> A firstAnnotation(Class<A> type, 
Constructor<?> onConstructor, Predicate<A> filter) {
-               if (nn(type) && nn(onConstructor))
-                       for (var a : annotations(type, onConstructor))
-                               if (test(filter, a))
-                                       return a;
-               return null;
+               return getAnnotationProvider().find(type, onConstructor).map(x 
-> x.inner()).filter(x -> filter.test(x)).findFirst().orElse(null);
        }
 
        @Override /* Overridden from MetaProvider */
        public <A extends Annotation> A firstAnnotation(Class<A> type, Field 
onField, Predicate<A> filter) {
-               if (nn(type) && nn(onField))
-                       for (var a : annotations(type, onField))
-                               if (test(filter, a))
-                                       return a;
-               return null;
+               return getAnnotationProvider().find(type, onField).map(x -> 
x.inner()).filter(x -> filter.test(x)).findFirst().orElse(null);
        }
 
        @Override /* Overridden from MetaProvider */
        public <A extends Annotation> A firstAnnotation(Class<A> type, Method 
onMethod, Predicate<A> filter) {
-               if (nn(type) && nn(onMethod))
-                       for (var a : annotations(type, onMethod))
-                               if (test(filter, a))
-                                       return a;
-               return null;
+               return getAnnotationProvider().find(type, onMethod).map(x -> 
x.inner()).filter(x -> filter.test(x)).findFirst().orElse(null);
        }
 
        @Override /* Overridden from MetaProvider */
        public <A extends Annotation> A firstDeclaredAnnotation(Class<A> type, 
Class<?> onClass, Predicate<A> filter) {
-               if (nn(type) && nn(onClass))
-                       for (var a : declaredAnnotations(type, onClass))
-                               if (test(filter, a))
-                                       return a;
-               return null;
+               return getAnnotationProvider().findDeclared(type, 
onClass).map(x -> x.inner()).filter(x -> 
filter.test(x)).findFirst().orElse(null);
        }
 
        @Override /* Overridden from MetaProvider */
        public <A extends Annotation> void forEachAnnotation(Class<A> type, 
Class<?> onClass, Predicate<A> filter, Consumer<A> action) {
-               if (nn(type) && nn(onClass))
-                       for (var a : annotations(type, onClass))
-                               consumeIf(filter, action, a);
+               getAnnotationProvider().find(type, onClass).map(x -> 
x.inner()).filter(x -> filter.test(x)).forEach(x -> action.accept(x));
        }
 
        @Override /* Overridden from MetaProvider */
        public <A extends Annotation> void forEachAnnotation(Class<A> type, 
Constructor<?> onConstructor, Predicate<A> filter, Consumer<A> action) {
-               if (nn(type) && nn(onConstructor))
-                       for (var a : annotations(type, onConstructor))
-                               consumeIf(filter, action, a);
+               getAnnotationProvider().find(type, onConstructor).map(x -> 
x.inner()).filter(x -> filter.test(x)).forEach(x -> action.accept(x));
        }
 
        @Override /* Overridden from MetaProvider */
        public <A extends Annotation> void forEachAnnotation(Class<A> type, 
Field onField, Predicate<A> filter, Consumer<A> action) {
-               if (nn(type) && nn(onField))
-                       for (var a : annotations(type, onField))
-                               consumeIf(filter, action, a);
+               getAnnotationProvider().find(type, onField).map(x -> 
x.inner()).filter(x -> filter.test(x)).forEach(x -> action.accept(x));
        }
 
        @Override /* Overridden from MetaProvider */
        public <A extends Annotation> void forEachAnnotation(Class<A> type, 
Method onMethod, Predicate<A> filter, Consumer<A> action) {
-               if (nn(type) && nn(onMethod))
-                       for (var a : annotations(type, onMethod))
-                               consumeIf(filter, action, a);
+               getAnnotationProvider().find(type, onMethod).map(x -> 
x.inner()).filter(x -> filter.test(x)).forEach(x -> action.accept(x));
        }
 
        @Override /* Overridden from MetaProvider */
@@ -951,7 +919,7 @@ public abstract class Context implements AnnotationProvider 
{
         * @return <jk>true</jk> if the annotation exists on the specified 
class.
         */
        public <A extends Annotation> boolean hasAnnotation(Class<A> type, 
Class<?> onClass) {
-               return annotations(type, onClass).length > 0;
+               return getAnnotationProvider().find(type, onClass).map(x -> 
x.inner()).findFirst().isPresent();
        }
 
        /**
@@ -963,7 +931,7 @@ public abstract class Context implements AnnotationProvider 
{
         * @return <jk>true</jk> if the annotation exists on the specified 
field.
         */
        public <A extends Annotation> boolean hasAnnotation(Class<A> type, 
Constructor<?> onConstructor) {
-               return annotations(type, onConstructor).length > 0;
+               return getAnnotationProvider().find(type, onConstructor).map(x 
-> x.inner()).findFirst().isPresent();
        }
 
        /**
@@ -975,7 +943,7 @@ public abstract class Context implements AnnotationProvider 
{
         * @return <jk>true</jk> if the annotation exists on the specified 
field.
         */
        public <A extends Annotation> boolean hasAnnotation(Class<A> type, 
Field onField) {
-               return annotations(type, onField).length > 0;
+               return getAnnotationProvider().find(type, onField).map(x -> 
x.inner()).findFirst().isPresent();
        }
 
        /**
@@ -987,7 +955,7 @@ public abstract class Context implements AnnotationProvider 
{
         * @return <jk>true</jk> if the annotation exists on the specified 
method.
         */
        public <A extends Annotation> boolean hasAnnotation(Class<A> type, 
Method onMethod) {
-               return annotations(type, onMethod).length > 0;
+               return getAnnotationProvider().find(type, onMethod).map(x -> 
x.inner()).findFirst().isPresent();
        }
 
        /**
@@ -1001,52 +969,27 @@ public abstract class Context implements 
AnnotationProvider {
 
        @Override /* Overridden from MetaProvider */
        public <A extends Annotation> A lastAnnotation(Class<A> type, Class<?> 
onClass, Predicate<A> filter) {
-               A x = null;
-               if (nn(type) && nn(onClass))
-                       for (var a : annotations(type, onClass))
-                               if (test(filter, a))
-                                       x = a;
-               return x;
+               return getAnnotationProvider().find(type, onClass).map(x -> 
x.inner()).filter(x -> filter.test(x)).findFirst().orElse(null);
        }
 
        @Override /* Overridden from MetaProvider */
        public <A extends Annotation> A lastAnnotation(Class<A> type, 
Constructor<?> onConstructor, Predicate<A> filter) {
-               A x = null;
-               if (nn(type) && nn(onConstructor))
-                       for (var a : annotations(type, onConstructor))
-                               if (test(filter, a))
-                                       x = a;
-               return x;
+               return getAnnotationProvider().find(type, onConstructor).map(x 
-> x.inner()).filter(x -> filter.test(x)).findFirst().orElse(null);
        }
 
        @Override /* Overridden from MetaProvider */
        public <A extends Annotation> A lastAnnotation(Class<A> type, Field 
onField, Predicate<A> filter) {
-               A x = null;
-               if (nn(type) && nn(onField))
-                       for (var a : annotations(type, onField))
-                               if (test(filter, a))
-                                       x = a;
-               return x;
+               return getAnnotationProvider().find(type, onField).map(x -> 
x.inner()).filter(x -> filter.test(x)).findFirst().orElse(null);
        }
 
        @Override /* Overridden from MetaProvider */
        public <A extends Annotation> A lastAnnotation(Class<A> type, Method 
onMethod, Predicate<A> filter) {
-               A x = null;
-               if (nn(type) && nn(onMethod))
-                       for (var a : annotations(type, onMethod))
-                               if (test(filter, a))
-                                       x = a;
-               return x;
+               return getAnnotationProvider().find(type, onMethod).map(x -> 
x.inner()).filter(x -> filter.test(x)).findFirst().orElse(null);
        }
 
        @Override /* Overridden from MetaProvider */
        public <A extends Annotation> A lastDeclaredAnnotation(Class<A> type, 
Class<?> onClass, Predicate<A> filter) {
-               A x = null;
-               if (nn(type) && nn(onClass))
-                       for (var a : declaredAnnotations(type, onClass))
-                               if (test(filter, a))
-                                       x = a;
-               return x;
+               return getAnnotationProvider().findDeclared(type, 
onClass).map(x -> x.inner()).filter(x -> 
filter.test(x)).findFirst().orElse(null);
        }
 
        @Override /* Overridden from Object */
@@ -1054,26 +997,6 @@ public abstract class Context implements 
AnnotationProvider {
                return Utils2.toPropertyMap(this).asReadableString();
        }
 
-       @SuppressWarnings("unchecked")
-       private <A extends Annotation> A[] annotations(Class<A> type, Class<?> 
onClass) {
-               return (A[])classAnnotationCache.get(onClass, type);
-       }
-
-       @SuppressWarnings("unchecked")
-       private <A extends Annotation> A[] annotations(Class<A> type, 
Constructor<?> onConstructor) {
-               return (A[])constructorAnnotationCache.get(onConstructor, type);
-       }
-
-       @SuppressWarnings("unchecked")
-       private <A extends Annotation> A[] annotations(Class<A> type, Field 
onField) {
-               return (A[])fieldAnnotationCache.get(onField, type);
-       }
-
-       @SuppressWarnings("unchecked")
-       private <A extends Annotation> A[] annotations(Class<A> type, Method 
onMethod) {
-               return (A[])methodAnnotationCache.get(onMethod, type);
-       }
-
        @SuppressWarnings("unchecked")
        private <A extends Annotation> A[] declaredAnnotations(Class<A> type, 
Class<?> onClass) {
                return (A[])declaredClassAnnotationCache.get(onClass, type);
diff --git a/juneau-utest/src/test/java/org/apache/juneau/ClassMeta_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/ClassMeta_Test.java
index a6ee7539ef..dbef394671 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/ClassMeta_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/ClassMeta_Test.java
@@ -307,16 +307,6 @@ class ClassMeta_Test extends TestBase {
                assertList(l4, "5");
        }
 
-       @Test void firstAnnotation() {
-               var c3 = bc.getClassMeta(C3.class);
-               var c4 = bc.getClassMeta(C4.class);
-               var c5 = bc.getClassMeta(C5.class);
-               assertEquals(2, c3.firstAnnotation(A.class, 
null).get().value());
-               assertEquals(2, c4.firstAnnotation(A.class, 
null).get().value());
-               assertEquals(3, c5.firstAnnotation(A.class, 
null).get().value());
-               assertEquals(5, c3.firstAnnotation(A.class, x -> x.value() == 
5).get().value());
-       }
-
        @Test void lastAnnotation() {
                var c3 = bc.getClassMeta(C3.class);
                var c4 = bc.getClassMeta(C4.class);
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ClassInfo_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ClassInfo_Test.java
index a5ed60ae10..02a32663e9 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ClassInfo_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/ClassInfo_Test.java
@@ -595,12 +595,6 @@ public class ClassInfo_Test extends TestBase {
                assertList(l4, "5");
        }
 
-       @Test void firstAnnotation() {
-               assertEquals(2, g3.firstAnnotation(A.class, null).value());
-               assertEquals(2, g4.firstAnnotation(A.class, null).value());
-               assertEquals(3, g5.firstAnnotation(A.class, null).value());
-               assertEquals(5, g3.firstAnnotation(A.class, x -> x.value() == 
5).value());
-       }
        @Test void lastAnnotation() {
                assertEquals(7, g3.lastAnnotation(A.class, null).value());
                assertEquals(7, g4.lastAnnotation(A.class, null).value());
@@ -1624,111 +1618,111 @@ public class ClassInfo_Test extends TestBase {
                @Test
                void a01_simple() {
                        ClassInfo ci = ClassInfo.of(String.class);
-                       
+
                        // SIMPLE format
-                       assertEquals("String", 
+                       assertEquals("String",
                                ci.getNameFormatted(SIMPLE, false, '$', 
BRACKETS));
-                       assertEquals("String", 
+                       assertEquals("String",
                                ci.getNameFormatted(SIMPLE, false, '.', 
BRACKETS));
-                       assertEquals("String", 
+                       assertEquals("String",
                                ci.getNameFormatted(SIMPLE, true, '$', 
BRACKETS));
                }
 
                @Test
                void a02_short() {
                        ClassInfo ci = ClassInfo.of(String.class);
-                       
+
                        // SHORT format
-                       assertEquals("String", 
+                       assertEquals("String",
                                ci.getNameFormatted(SHORT, false, '$', 
BRACKETS));
-                       assertEquals("String", 
+                       assertEquals("String",
                                ci.getNameFormatted(SHORT, true, '$', 
BRACKETS));
                }
 
                @Test
                void a03_full() {
                        ClassInfo ci = ClassInfo.of(String.class);
-                       
+
                        // FULL format
-                       assertEquals("java.lang.String", 
+                       assertEquals("java.lang.String",
                                ci.getNameFormatted(FULL, false, '$', 
BRACKETS));
-                       assertEquals("java.lang.String", 
+                       assertEquals("java.lang.String",
                                ci.getNameFormatted(FULL, true, '$', BRACKETS));
                }
 
                @Test
                void a04_innerClass() {
                        ClassInfo ci = ClassInfo.of(Map.Entry.class);
-                       
+
                        // SIMPLE - only innermost name
-                       assertEquals("Entry", 
+                       assertEquals("Entry",
                                ci.getNameFormatted(SIMPLE, false, '$', 
BRACKETS));
-                       
+
                        // SHORT - outer class + inner class
-                       assertEquals("Map$Entry", 
+                       assertEquals("Map$Entry",
                                ci.getNameFormatted(SHORT, false, '$', 
BRACKETS));
-                       assertEquals("Map.Entry", 
+                       assertEquals("Map.Entry",
                                ci.getNameFormatted(SHORT, false, '.', 
BRACKETS));
-                       
+
                        // FULL - package + outer + inner
-                       assertEquals("java.util.Map$Entry", 
+                       assertEquals("java.util.Map$Entry",
                                ci.getNameFormatted(FULL, false, '$', 
BRACKETS));
-                       assertEquals("java.util.Map.Entry", 
+                       assertEquals("java.util.Map.Entry",
                                ci.getNameFormatted(FULL, false, '.', 
BRACKETS));
                }
 
                @Test
                void a05_arrays() {
                        ClassInfo ci = ClassInfo.of(String[].class);
-                       
+
                        // BRACKETS format
-                       assertEquals("String[]", 
+                       assertEquals("String[]",
                                ci.getNameFormatted(SIMPLE, false, '$', 
BRACKETS));
-                       assertEquals("String[]", 
+                       assertEquals("String[]",
                                ci.getNameFormatted(SHORT, false, '$', 
BRACKETS));
-                       assertEquals("java.lang.String[]", 
+                       assertEquals("java.lang.String[]",
                                ci.getNameFormatted(FULL, false, '$', 
BRACKETS));
-                       
+
                        // WORD format
-                       assertEquals("StringArray", 
+                       assertEquals("StringArray",
                                ci.getNameFormatted(SIMPLE, false, '$', WORD));
-                       assertEquals("StringArray", 
+                       assertEquals("StringArray",
                                ci.getNameFormatted(SHORT, false, '$', WORD));
-                       assertEquals("java.lang.StringArray", 
+                       assertEquals("java.lang.StringArray",
                                ci.getNameFormatted(FULL, false, '$', WORD));
                }
 
                @Test
                void a06_multiDimensionalArrays() {
                        ClassInfo ci = ClassInfo.of(String[][].class);
-                       
+
                        // BRACKETS format
-                       assertEquals("String[][]", 
+                       assertEquals("String[][]",
                                ci.getNameFormatted(SIMPLE, false, '$', 
BRACKETS));
-                       assertEquals("java.lang.String[][]", 
+                       assertEquals("java.lang.String[][]",
                                ci.getNameFormatted(FULL, false, '$', 
BRACKETS));
-                       
+
                        // WORD format
-                       assertEquals("StringArrayArray", 
+                       assertEquals("StringArrayArray",
                                ci.getNameFormatted(SIMPLE, false, '$', WORD));
-                       assertEquals("java.lang.StringArrayArray", 
+                       assertEquals("java.lang.StringArrayArray",
                                ci.getNameFormatted(FULL, false, '$', WORD));
                }
 
                @Test
                void a07_primitiveArrays() {
                        ClassInfo ci = ClassInfo.of(int[].class);
-                       
+
                        // BRACKETS format
-                       assertEquals("int[]", 
+                       assertEquals("int[]",
                                ci.getNameFormatted(SIMPLE, false, '$', 
BRACKETS));
-                       assertEquals("int[]", 
+                       assertEquals("int[]",
                                ci.getNameFormatted(FULL, false, '$', 
BRACKETS));
-                       
+
                        // WORD format
-                       assertEquals("intArray", 
+                       assertEquals("intArray",
                                ci.getNameFormatted(SIMPLE, false, '$', WORD));
-                       assertEquals("intArray", 
+                       assertEquals("intArray",
                                ci.getNameFormatted(FULL, false, '$', WORD));
                }
 
@@ -1738,23 +1732,23 @@ public class ClassInfo_Test extends TestBase {
                        Field f = 
GenericsTestClass.class.getDeclaredField("hashMap");
                        Type t = f.getGenericType();
                        ClassInfo ci = ClassInfo.of(t);
-                       
+
                        // Without type params
-                       assertEquals("HashMap", 
+                       assertEquals("HashMap",
                                ci.getNameFormatted(SIMPLE, false, '$', 
BRACKETS));
-                       assertEquals("java.util.HashMap", 
+                       assertEquals("java.util.HashMap",
                                ci.getNameFormatted(FULL, false, '$', 
BRACKETS));
-                       
+
                        // With type params - SIMPLE
-                       assertEquals("HashMap<String,Integer>", 
+                       assertEquals("HashMap<String,Integer>",
                                ci.getNameFormatted(SIMPLE, true, '$', 
BRACKETS));
-                       
+
                        // With type params - SHORT
-                       assertEquals("HashMap<String,Integer>", 
+                       assertEquals("HashMap<String,Integer>",
                                ci.getNameFormatted(SHORT, true, '$', 
BRACKETS));
-                       
+
                        // With type params - FULL
-                       
assertEquals("java.util.HashMap<java.lang.String,java.lang.Integer>", 
+                       
assertEquals("java.util.HashMap<java.lang.String,java.lang.Integer>",
                                ci.getNameFormatted(FULL, true, '$', BRACKETS));
                }
 
@@ -1764,13 +1758,13 @@ public class ClassInfo_Test extends TestBase {
                        Field f = 
GenericsTestClass.class.getDeclaredField("nestedMap");
                        Type t = f.getGenericType();
                        ClassInfo ci = ClassInfo.of(t);
-                       
+
                        // With type params - SIMPLE
-                       assertEquals("HashMap<String,ArrayList<Integer>>", 
+                       assertEquals("HashMap<String,ArrayList<Integer>>",
                                ci.getNameFormatted(SIMPLE, true, '$', 
BRACKETS));
-                       
+
                        // With type params - FULL
-                       
assertEquals("java.util.HashMap<java.lang.String,java.util.ArrayList<java.lang.Integer>>",
 
+                       
assertEquals("java.util.HashMap<java.lang.String,java.util.ArrayList<java.lang.Integer>>",
                                ci.getNameFormatted(FULL, true, '$', BRACKETS));
                }
 
@@ -1780,15 +1774,15 @@ public class ClassInfo_Test extends TestBase {
                        Field f = 
GenericsTestClass.class.getDeclaredField("listArray");
                        Type t = f.getGenericType();
                        ClassInfo ci = ClassInfo.of(t);
-                       
+
                        // BRACKETS format
-                       assertEquals("ArrayList<String>[]", 
+                       assertEquals("ArrayList<String>[]",
                                ci.getNameFormatted(SIMPLE, true, '$', 
BRACKETS));
-                       assertEquals("java.util.ArrayList<java.lang.String>[]", 
+                       assertEquals("java.util.ArrayList<java.lang.String>[]",
                                ci.getNameFormatted(FULL, true, '$', BRACKETS));
-                       
+
                        // WORD format
-                       assertEquals("ArrayList<String>Array", 
+                       assertEquals("ArrayList<String>Array",
                                ci.getNameFormatted(SIMPLE, true, '$', WORD));
                }
 
@@ -1798,7 +1792,7 @@ public class ClassInfo_Test extends TestBase {
                        StringBuilder sb = new StringBuilder("Type: ");
                        ci.appendNameFormatted(sb, FULL, false, '$', BRACKETS);
                        assertEquals("Type: java.lang.String", sb.toString());
-                       
+
                        // Verify it returns the same StringBuilder for chaining
                        StringBuilder result = ci.appendNameFormatted(sb, 
SIMPLE, false, '$', BRACKETS);
                        assertSame(sb, result);
@@ -1808,40 +1802,40 @@ public class ClassInfo_Test extends TestBase {
                @Test
                void a12_equivalentMethods() {
                        ClassInfo ci = ClassInfo.of(String.class);
-                       
+
                        // getName() equivalent
-                       assertEquals(ci.getName(), 
+                       assertEquals(ci.getName(),
                                ci.getNameFormatted(FULL, false, '$', 
BRACKETS));
-                       
+
                        // getSimpleName() equivalent
-                       assertEquals(ci.getNameSimple(), 
+                       assertEquals(ci.getNameSimple(),
                                ci.getNameFormatted(SIMPLE, false, '$', 
BRACKETS));
                }
 
                @Test
                void a13_innerClassArrays() {
                        ClassInfo ci = ClassInfo.of(Map.Entry[].class);
-                       
+
                        // SIMPLE
-                       assertEquals("Entry[]", 
+                       assertEquals("Entry[]",
                                ci.getNameFormatted(SIMPLE, false, '$', 
BRACKETS));
-                       assertEquals("EntryArray", 
+                       assertEquals("EntryArray",
                                ci.getNameFormatted(SIMPLE, false, '$', WORD));
-                       
+
                        // SHORT
-                       assertEquals("Map$Entry[]", 
+                       assertEquals("Map$Entry[]",
                                ci.getNameFormatted(SHORT, false, '$', 
BRACKETS));
-                       assertEquals("Map.Entry[]", 
+                       assertEquals("Map.Entry[]",
                                ci.getNameFormatted(SHORT, false, '.', 
BRACKETS));
-                       assertEquals("Map$EntryArray", 
+                       assertEquals("Map$EntryArray",
                                ci.getNameFormatted(SHORT, false, '$', WORD));
-                       
+
                        // FULL
-                       assertEquals("java.util.Map$Entry[]", 
+                       assertEquals("java.util.Map$Entry[]",
                                ci.getNameFormatted(FULL, false, '$', 
BRACKETS));
-                       assertEquals("java.util.Map.Entry[]", 
+                       assertEquals("java.util.Map.Entry[]",
                                ci.getNameFormatted(FULL, false, '.', 
BRACKETS));
-                       assertEquals("java.util.Map$EntryArray", 
+                       assertEquals("java.util.Map$EntryArray",
                                ci.getNameFormatted(FULL, false, '$', WORD));
                }
 

Reply via email to