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 77b058b6d8 org.apache.juneau.common.reflect API improvements
77b058b6d8 is described below

commit 77b058b6d80e7ac2df9eed1c7031533c30defcad
Author: James Bognar <[email protected]>
AuthorDate: Thu Nov 20 12:02:32 2025 -0500

    org.apache.juneau.common.reflect API improvements
---
 .../apache/juneau/common/collections/Cache.java    | 112 +++++++++++++++--
 .../common/collections/ConcurrentHashMap1Key.java  | 134 +++++++++++++++++++++
 .../common/collections/ConcurrentHashMap2Key.java  |  29 +++--
 .../common/collections/ConcurrentHashMap3Key.java  |  25 ++--
 .../common/collections/ConcurrentHashMap4Key.java  |  27 +++--
 .../common/collections/ConcurrentHashMap5Key.java  |  19 +--
 .../org/apache/juneau/common/function/Tuple1.java  |  99 +++++++++++++++
 .../org/apache/juneau/common/function/Tuple2.java  |  22 ++++
 .../org/apache/juneau/common/function/Tuple3.java  |  14 +++
 .../org/apache/juneau/common/function/Tuple4.java  |  14 +++
 .../org/apache/juneau/common/function/Tuple5.java  |  14 +++
 .../juneau/common/reflect/AnnotationProvider.java  |  81 ++++++++++++-
 .../org/apache/juneau/common/utils/HashCode.java   |  30 ++++-
 .../juneau/common/reflect/MethodInfo_Test.java     |   2 +-
 14 files changed, 574 insertions(+), 48 deletions(-)

diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache.java
index e287b902ab..36cca1c56c 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache.java
@@ -24,12 +24,14 @@ import java.util.concurrent.*;
 import java.util.concurrent.atomic.*;
 import java.util.function.*;
 
+import org.apache.juneau.common.function.*;
+
 /**
  * Simple in-memory cache for storing and retrieving objects by key.
  *
  * <h5 class='section'>Overview:</h5>
  * <p>
- * This class extends {@link ConcurrentHashMap} to provide a thread-safe 
caching layer with automatic
+ * This class uses {@link ConcurrentHashMap1Key} internally to provide a 
thread-safe caching layer with automatic
  * value computation, cache eviction, and statistics tracking. It's designed 
for caching expensive-to-compute
  * or frequently-accessed objects to improve performance.
  *
@@ -42,6 +44,7 @@ import java.util.function.*;
  *     <li>Built-in hit/miss statistics tracking
  *     <li>Optional logging of cache statistics on JVM shutdown
  *     <li>Can be disabled entirely via builder or system property
+ *     <li><b>Array Support:</b> Arrays can be used as keys with proper 
content-based hashing and equality
  * </ul>
  *
  * <h5 class='section'>Usage:</h5>
@@ -60,6 +63,18 @@ import java.util.function.*;
  *     Pattern <jv>pattern2</jv> = 
<jv>patternCache</jv>.get(<js>"[0-9]+"</js>, () -&gt; 
Pattern.compile(<js>"[0-9]+"</js>, Pattern.CASE_INSENSITIVE));
  * </p>
  *
+ * <h5 class='section'>Array Support:</h5>
+ * <p>
+ * Unlike standard {@link java.util.HashMap} which uses identity-based 
equality for array keys,
+ * this class properly handles arrays using content-based comparison via 
{@link ConcurrentHashMap1Key}:
+ *
+ * <p class='bjava'>
+ *     <jc>// Arrays work correctly as keys</jc>
+ *     Cache&lt;String[],Result&gt; <jv>cache</jv> = 
Cache.<jsm>of</jsm>(String[].<jk>class</jk>, Result.<jk>class</jk>).build();
+ *     <jv>cache</jv>.get(<jk>new</jk> String[]{<js>"a"</js>, <js>"b"</js>}, 
() -&gt; computeResult());
+ *     Result <jv>r</jv> = <jv>cache</jv>.get(<jk>new</jk> 
String[]{<js>"a"</js>, <js>"b"</js>}, () -&gt; computeResult());  <jc>// Cache 
hit!</jc>
+ * </p>
+ *
  * <h5 class='section'>Cache Behavior:</h5>
  * <ul class='spaced-list'>
  *     <li>When a key is requested:
@@ -96,6 +111,7 @@ import java.util.function.*;
  *             to minimize redundant computation in concurrent scenarios
  *     <li>When max size is exceeded, the entire cache is cleared in a single 
operation
  *     <li>Statistics tracking uses {@link AtomicInteger} for thread-safe 
counting without locking
+ *     <li>For arrays, content-based hashing via {@link 
java.util.Arrays#hashCode(Object[])} ensures proper cache hits
  * </ul>
  *
  * <h5 class='section'>Examples:</h5>
@@ -125,15 +141,16 @@ import java.util.function.*;
  *
  * <h5 class='section'>See Also:</h5>
  * <ul>
+ *     <li class='jc'>{@link ConcurrentHashMap1Key}
  *     <li class='link'><a class="doclink" 
href="../../../../../index.html#juneau-common">Overview &gt; juneau-common</a>
  * </ul>
  *
- * @param <K> The key type.
+ * @param <K> The key type. Can be an array type for content-based key 
matching.
  * @param <V> The value type.
  */
-public class Cache<K,V> extends ConcurrentHashMap<K,V> {
+public class Cache<K,V> implements java.util.Map<K,V> {
 
-       private static final long serialVersionUID = 1L;
+       private final ConcurrentHashMap1Key<K,V> map = new 
ConcurrentHashMap1Key<>();
 
        /**
         * Builder for creating configured {@link Cache} instances.
@@ -421,7 +438,7 @@ public class Cache<K,V> extends ConcurrentHashMap<K,V> {
         * @throws IllegalArgumentException if key is <jk>null</jk>.
         */
        @SuppressWarnings("unchecked")
-       @Override /* ConcurrentHashMap */
+       @Override /* Map */
        public V get(Object key) {
                assertArgNotNull("key", key);
                return get((K)key, () -> supplier.apply((K)key));
@@ -446,6 +463,7 @@ public class Cache<K,V> extends ConcurrentHashMap<K,V> {
         *      <li>Thread-safe: Multiple threads can safely call this method 
concurrently
         *      <li>The supplier may be called multiple times for the same key 
in concurrent scenarios
         *              (due to {@link ConcurrentHashMap#putIfAbsent(Object, 
Object)} semantics)
+        *      <li><b>Array Keys:</b> Arrays are matched by content, not 
identity
         * </ul>
         *
         * <h5 class='section'>Example:</h5>
@@ -470,7 +488,7 @@ public class Cache<K,V> extends ConcurrentHashMap<K,V> {
                assertArgNotNull("key", key);
                if (disableCaching)
                        return supplier.get();
-               V v = super.get(key);
+               V v = map.getKey(key);
                if (v == null) {
                        if (size() > maxSize)
                                clear();
@@ -478,7 +496,7 @@ public class Cache<K,V> extends ConcurrentHashMap<K,V> {
                        if (v == null)
                                remove(key);
                        else
-                               putIfAbsent(key, v);
+                               map.put(Tuple1.of(key), v);
                } else {
                        cacheHits.incrementAndGet();
                }
@@ -501,10 +519,9 @@ public class Cache<K,V> extends ConcurrentHashMap<K,V> {
         * @return The previous value associated with the key, or <jk>null</jk> 
if there was no mapping for the key.
         * @throws IllegalArgumentException if key is <jk>null</jk>.
         */
-       @Override /* ConcurrentHashMap */
        public V put(K key, V value) {
                assertArgNotNull("key", key);
-               return super.put(key, value);
+               return map.putKey(key, value);
        }
 
        /**
@@ -534,4 +551,81 @@ public class Cache<K,V> extends ConcurrentHashMap<K,V> {
         * @return The total number of cache hits since creation.
         */
        public int getCacheHits() { return cacheHits.get(); }
+
+       /**
+        * Returns the number of entries currently in the cache.
+        *
+        * @return The number of cached entries.
+        */
+       public int size() {
+               return map.size();
+       }
+
+       /**
+        * Returns a collection view of the values contained in this cache.
+        *
+        * @return A collection view of the values.
+        */
+       public java.util.Collection<V> values() {
+               return map.values();
+       }
+
+       /**
+        * Returns <jk>true</jk> if this cache contains no entries.
+        *
+        * @return <jk>true</jk> if this cache contains no entries.
+        */
+       public boolean isEmpty() {
+               return map.isEmpty();
+       }
+
+       /**
+        * Removes all entries from the cache.
+        *
+        * <p>
+        * Note: This does not reset the cache hit counter.
+        */
+       public void clear() {
+               map.clear();
+       }
+
+       /**
+        * Removes the specified key from the cache.
+        *
+        * @param key The key to remove. Must not be <jk>null</jk>.
+        * @return The previous value associated with the key, or <jk>null</jk> 
if there was no mapping.
+        * @throws IllegalArgumentException if key is <jk>null</jk>.
+        */
+       @Override /* Map */
+       public V remove(Object key) {
+               assertArgNotNull("key", key);
+               return map.remove(Tuple1.of(key));
+       }
+
+       @Override /* Map */
+       public boolean containsKey(Object key) {
+               return map.containsKey(Tuple1.of(key));
+       }
+
+       @Override /* Map */
+       public boolean containsValue(Object value) {
+               return map.containsValue(value);
+       }
+
+       @Override /* Map */
+       public void putAll(java.util.Map<? extends K, ? extends V> m) {
+               m.forEach((k, v) -> put(k, v));
+       }
+
+       @Override /* Map */
+       public java.util.Set<K> keySet() {
+               return 
map.keySet().stream().map(Tuple1::getA).collect(java.util.stream.Collectors.toSet());
+       }
+
+       @Override /* Map */
+       public java.util.Set<java.util.Map.Entry<K, V>> entrySet() {
+               return map.entrySet().stream()
+                       .map(e -> new 
java.util.AbstractMap.SimpleEntry<>(e.getKey().getA(), e.getValue()))
+                       .collect(java.util.stream.Collectors.toSet());
+       }
 }
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap1Key.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap1Key.java
new file mode 100644
index 0000000000..a08e8c4b10
--- /dev/null
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap1Key.java
@@ -0,0 +1,134 @@
+/*
+ * 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.collections;
+
+import static org.apache.juneau.common.utils.AssertionUtils.*;
+
+import java.util.concurrent.*;
+
+import org.apache.juneau.common.function.*;
+
+/**
+ * A thread-safe concurrent hash map that uses a single-value wrapper key for 
lookups.
+ *
+ * <p>
+ * This class extends {@link ConcurrentHashMap} to provide efficient storage 
and retrieval of values
+ * indexed by a single key component wrapped in a {@link Tuple1}. The primary 
use case is when you need
+ * content-based equality for keys that would otherwise use identity-based 
equality (such as arrays).
+ *
+ * <h5 class='section'>Features:</h5>
+ * <ul class='spaced-list'>
+ *     <li><b>Content-Based Keys:</b> Keys use content-based equality via 
{@link Tuple1}
+ *     <li><b>Array Support:</b> Arrays can be used as keys with proper 
content-based hashing and equality
+ *     <li><b>Thread-Safe:</b> Inherits all thread-safety guarantees from 
{@link ConcurrentHashMap}
+ *     <li><b>Null Keys Supported:</b> Key can be <jk>null</jk>
+ * </ul>
+ *
+ * <h5 class='section'>Usage:</h5>
+ * <p class='bjava'>
+ *     <jc>// Create a map for caching results indexed by array keys</jc>
+ *     ConcurrentHashMap1Key&lt;String[],Result&gt; <jv>cache</jv> =
+ *             <jk>new</jk> ConcurrentHashMap1Key&lt;&gt;();
+ *
+ *     <jc>// Store a value</jc>
+ *     <jv>cache</jv>.put(<jk>new</jk> String[]{<js>"a"</js>, <js>"b"</js>}, 
<jv>result1</jv>);
+ *
+ *     <jc>// Retrieve a value (works with different array instance but same 
content)</jc>
+ *     Result <jv>cached</jv> = <jv>cache</jv>.get(<jk>new</jk> 
String[]{<js>"a"</js>, <js>"b"</js>});  <jc>// Returns result1</jc>
+ * </p>
+ *
+ * <h5 class='section'>Array Support:</h5>
+ * <p>
+ * Unlike standard {@link java.util.HashMap} which uses identity-based 
equality for array keys,
+ * this class properly handles arrays using content-based comparison:
+ *
+ * <p class='bjava'>
+ *     <jc>// Arrays work correctly as keys</jc>
+ *     ConcurrentHashMap1Key&lt;int[],String&gt; <jv>map</jv> = <jk>new</jk> 
ConcurrentHashMap1Key&lt;&gt;();
+ *     <jv>map</jv>.put(<jk>new</jk> <jk>int</jk>[]{1,2,3}, <js>"foo"</js>);
+ *     String <jv>result</jv> = <jv>map</jv>.get(<jk>new</jk> 
<jk>int</jk>[]{1,2,3});  <jc>// Returns "foo"</jc>
+ * </p>
+ *
+ * <h5 class='section'>Common Use Cases:</h5>
+ * <ul class='spaced-list'>
+ *     <li>Caching results indexed by array keys (e.g., annotation traversal 
patterns)
+ *     <li>Storing configuration indexed by enum arrays
+ *     <li>Mapping results by method parameter arrays
+ *     <li>Any scenario requiring content-based equality for otherwise 
identity-based types
+ * </ul>
+ *
+ * <h5 class='section'>Key Hashing:</h5>
+ * <p>
+ * Keys are wrapped in {@link Tuple1} which provides content-based hashing.
+ * For arrays, {@link java.util.Arrays#hashCode(Object[])} is used to ensure
+ * consistent hashing based on array contents rather than identity.
+ *
+ * <h5 class='section'>Thread Safety:</h5>
+ * <p>
+ * This class inherits all thread-safety properties from {@link 
ConcurrentHashMap}. Multiple threads
+ * can safely read and write to the map concurrently without external 
synchronization.
+ *
+ * <h5 class='section'>Performance:</h5>
+ * <ul class='spaced-list'>
+ *     <li>Average O(1) lookup and insertion time (inherited from {@link 
ConcurrentHashMap})
+ *     <li>Minimal object allocation: tuple keys are created only during 
put/get operations
+ *     <li>Lock-free reads for existing entries (inherited from {@link 
ConcurrentHashMap})
+ * </ul>
+ *
+ * <h5 class='section'>See Also:</h5>
+ * <ul>
+ *     <li class='jc'>{@link ConcurrentHashMap2Key}
+ *     <li class='jc'>{@link ConcurrentHashMap3Key}
+ *     <li class='jc'>{@link ConcurrentHashMap4Key}
+ *     <li class='jc'>{@link ConcurrentHashMap5Key}
+ *     <li class='link'><a class="doclink" 
href="../../../../../index.html#juneau-common">Overview &gt; juneau-common</a>
+ * </ul>
+ *
+ * @param <K> The key type.
+ * @param <V> The value type.
+ * @serial exclude
+ */
+public class ConcurrentHashMap1Key<K,V> extends ConcurrentHashMap<Tuple1<K>,V> 
{
+
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * Retrieves the value associated with the specified key.
+        *
+        * @param key Key component. Must not be <jk>null</jk>.
+        * @return The value associated with the key, or <jk>null</jk> if not 
found.
+        * @throws IllegalArgumentException if key is <jk>null</jk>.
+        */
+       public V getKey(K key) {
+               assertArgNotNull("key", key);
+               return super.get(Tuple1.of(key));
+       }
+
+       /**
+        * Associates the specified value with the specified key in this map.
+        *
+        * @param key Key component. Must not be <jk>null</jk>.
+        * @param value The value to associate with the key.
+        * @return The previous value associated with the key, or <jk>null</jk> 
if there was no mapping.
+        * @throws IllegalArgumentException if key is <jk>null</jk>.
+        */
+       public V putKey(K key, V value) {
+               assertArgNotNull("key", key);
+               return super.put(Tuple1.of(key), value);
+       }
+}
+
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap2Key.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap2Key.java
index ad4c73c946..5e172bc27f 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap2Key.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap2Key.java
@@ -33,6 +33,8 @@ import org.apache.juneau.common.function.*;
  * <h5 class='section'>Features:</h5>
  * <ul class='spaced-list'>
  *     <li><b>Two-Part Keys:</b> Values are indexed by a pair of keys (K1, K2) 
instead of a single key
+ *     <li><b>Content-Based Keys:</b> Keys use content-based equality via 
{@link Tuple2}
+ *     <li><b>Array Support:</b> Arrays can be used as key components with 
proper content-based hashing and equality
  *     <li><b>Thread-Safe:</b> Inherits all thread-safety guarantees from 
{@link ConcurrentHashMap}
  *     <li><b>Null Keys Supported:</b> Both key parts can be <jk>null</jk>
  * </ul>
@@ -60,15 +62,24 @@ import org.apache.juneau.common.function.*;
  *     <li>Tracking statistics indexed by date and metric name
  * </ul>
  *
- * <h5 class='section'>Key Hashing:</h5>
+ * <h5 class='section'>Array Support:</h5>
  * <p>
- * The composite key is hashed using the formula:
+ * Unlike standard {@link java.util.HashMap} which uses identity-based 
equality for array keys,
+ * this class properly handles arrays using content-based comparison via 
{@link Tuple2}:
+ *
  * <p class='bjava'>
- *     hash = 31 * k1.hashCode() + k2.hashCode()
+ *     <jc>// Arrays work correctly as key components</jc>
+ *     ConcurrentHashMap2Key&lt;String,int[],Result&gt; <jv>map</jv> = 
<jk>new</jk> ConcurrentHashMap2Key&lt;&gt;();
+ *     <jv>map</jv>.put(<js>"key"</js>, <jk>new</jk> <jk>int</jk>[]{1,2,3}, 
<jv>result1</jv>);
+ *     Result <jv>r</jv> = <jv>map</jv>.get(<js>"key"</js>, <jk>new</jk> 
<jk>int</jk>[]{1,2,3});  <jc>// Returns result1</jc>
  * </p>
+ *
+ * <h5 class='section'>Key Hashing:</h5>
  * <p>
- * Null keys are treated as having a hash code of 0. Keys are considered equal 
if both components
- * are equal according to {@link Object#equals(Object)}.
+ * Keys are wrapped in {@link Tuple2} which provides content-based hashing.
+ * For arrays, {@link java.util.Arrays#hashCode(Object[])} is used to ensure
+ * consistent hashing based on array contents rather than identity. Keys are 
considered equal if both components
+ * are equal according to content-based comparison (via {@link 
org.apache.juneau.common.utils.Utils#eq(Object, Object)}).
  *
  * <h5 class='section'>Thread Safety:</h5>
  * <p>
@@ -84,11 +95,15 @@ import org.apache.juneau.common.function.*;
  *
  * <h5 class='section'>See Also:</h5>
  * <ul>
+ *     <li class='jc'>{@link ConcurrentHashMap1Key}
+ *     <li class='jc'>{@link ConcurrentHashMap3Key}
+ *     <li class='jc'>{@link ConcurrentHashMap4Key}
+ *     <li class='jc'>{@link ConcurrentHashMap5Key}
  *     <li class='link'><a class="doclink" 
href="../../../../../index.html#juneau-common">Overview &gt; juneau-common</a>
  * </ul>
  *
- * @param <K1> The first key component type.
- * @param <K2> The second key component type.
+ * @param <K1> The first key component type. Can be an array type for 
content-based key matching.
+ * @param <K2> The second key component type. Can be an array type for 
content-based key matching.
  * @param <V> The value type.
  * @serial exclude
  */
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap3Key.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap3Key.java
index 64660e1e77..1fa97ff43a 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap3Key.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap3Key.java
@@ -33,6 +33,8 @@ import org.apache.juneau.common.function.*;
  * <h5 class='section'>Features:</h5>
  * <ul class='spaced-list'>
  *     <li><b>Three-Part Keys:</b> Values are indexed by a triplet of keys 
(K1, K2, K3) instead of a single key
+ *     <li><b>Content-Based Keys:</b> Keys use content-based equality via 
{@link Tuple3}
+ *     <li><b>Array Support:</b> Arrays can be used as key components with 
proper content-based hashing and equality
  *     <li><b>Thread-Safe:</b> Inherits all thread-safety guarantees from 
{@link ConcurrentHashMap}
  * </ul>
  *
@@ -89,15 +91,17 @@ import org.apache.juneau.common.function.*;
  *     <li>Tracking permissions by user, resource, and action
  * </ul>
  *
- * <h5 class='section'>Key Hashing:</h5>
+ * <h5 class='section'>Array Support:</h5>
  * <p>
- * The composite key is hashed using the formula:
- * <p class='bjava'>
- *     hash = 31 * (31 * k1.hashCode() + k2.hashCode()) + k3.hashCode()
- * </p>
+ * Unlike standard {@link java.util.HashMap} which uses identity-based 
equality for array keys,
+ * this class properly handles arrays using content-based comparison via 
{@link Tuple3}.
+ *
+ * <h5 class='section'>Key Hashing:</h5>
  * <p>
- * Null keys are treated as having a hash code of 0. Keys are considered equal 
if all components
- * are equal according to {@link Object#equals(Object)}.
+ * Keys are wrapped in {@link Tuple3} which provides content-based hashing.
+ * For arrays, {@link java.util.Arrays#hashCode(Object[])} is used to ensure
+ * consistent hashing based on array contents rather than identity. Keys are 
considered equal if all components
+ * are equal according to content-based comparison (via {@link 
org.apache.juneau.common.utils.Utils#eq(Object, Object)}).
  *
  * <h5 class='section'>Thread Safety:</h5>
  * <p>
@@ -113,15 +117,16 @@ import org.apache.juneau.common.function.*;
  *
  * <h5 class='section'>See Also:</h5>
  * <ul>
+ *     <li class='jc'>{@link ConcurrentHashMap1Key}
  *     <li class='jc'>{@link ConcurrentHashMap2Key}
  *     <li class='jc'>{@link ConcurrentHashMap4Key}
  *     <li class='jc'>{@link ConcurrentHashMap5Key}
  *     <li class='link'><a class="doclink" 
href="../../../../../index.html#juneau-common">Overview &gt; juneau-common</a>
  * </ul>
  *
- * @param <K1> The first key component type.
- * @param <K2> The second key component type.
- * @param <K3> The third key component type.
+ * @param <K1> The first key component type. Can be an array type for 
content-based key matching.
+ * @param <K2> The second key component type. Can be an array type for 
content-based key matching.
+ * @param <K3> The third key component type. Can be an array type for 
content-based key matching.
  * @param <V> The value type.
  * @serial exclude
  */
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap4Key.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap4Key.java
index 74d24f16e6..c155fd6e5f 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap4Key.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap4Key.java
@@ -33,6 +33,8 @@ import org.apache.juneau.common.function.*;
  * <h5 class='section'>Features:</h5>
  * <ul class='spaced-list'>
  *     <li><b>Four-Part Keys:</b> Values are indexed by four keys (K1, K2, K3, 
K4) instead of a single key
+ *     <li><b>Content-Based Keys:</b> Keys use content-based equality via 
{@link Tuple4}
+ *     <li><b>Array Support:</b> Arrays can be used as key components with 
proper content-based hashing and equality
  *     <li><b>Thread-Safe:</b> Inherits all thread-safety guarantees from 
{@link ConcurrentHashMap}
  *     <li><b>Null Keys Supported:</b> All key parts can be <jk>null</jk>
  *     <li><b>Optional Caching:</b> Can be disabled to always invoke a 
supplier function instead of caching
@@ -92,15 +94,17 @@ import org.apache.juneau.common.function.*;
  *     <li>Caching query results indexed by database, schema, table, and query 
type
  * </ul>
  *
- * <h5 class='section'>Key Hashing:</h5>
+ * <h5 class='section'>Array Support:</h5>
  * <p>
- * The composite key is hashed using the formula:
- * <p class='bjava'>
- *     hash = 31 * (31 * (31 * k1.hashCode() + k2.hashCode()) + k3.hashCode()) 
+ k4.hashCode()
- * </p>
+ * Unlike standard {@link java.util.HashMap} which uses identity-based 
equality for array keys,
+ * this class properly handles arrays using content-based comparison via 
{@link Tuple4}.
+ *
+ * <h5 class='section'>Key Hashing:</h5>
  * <p>
- * Null keys are treated as having a hash code of 0. Keys are considered equal 
if all components
- * are equal according to {@link Object#equals(Object)}.
+ * Keys are wrapped in {@link Tuple4} which provides content-based hashing.
+ * For arrays, {@link java.util.Arrays#hashCode(Object[])} is used to ensure
+ * consistent hashing based on array contents rather than identity. Keys are 
considered equal if all components
+ * are equal according to content-based comparison (via {@link 
org.apache.juneau.common.utils.Utils#eq(Object, Object)}).
  *
  * <h5 class='section'>Thread Safety:</h5>
  * <p>
@@ -116,16 +120,17 @@ import org.apache.juneau.common.function.*;
  *
  * <h5 class='section'>See Also:</h5>
  * <ul>
+ *     <li class='jc'>{@link ConcurrentHashMap1Key}
  *     <li class='jc'>{@link ConcurrentHashMap2Key}
  *     <li class='jc'>{@link ConcurrentHashMap3Key}
  *     <li class='jc'>{@link ConcurrentHashMap5Key}
  *     <li class='link'><a class="doclink" 
href="../../../../../index.html#juneau-common">Overview &gt; juneau-common</a>
  * </ul>
  *
- * @param <K1> The first key component type.
- * @param <K2> The second key component type.
- * @param <K3> The third key component type.
- * @param <K4> The fourth key component type.
+ * @param <K1> The first key component type. Can be an array type for 
content-based key matching.
+ * @param <K2> The second key component type. Can be an array type for 
content-based key matching.
+ * @param <K3> The third key component type. Can be an array type for 
content-based key matching.
+ * @param <K4> The fourth key component type. Can be an array type for 
content-based key matching.
  * @param <V> The value type.
  * @serial exclude
  */
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap5Key.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap5Key.java
index 9a591491ad..2995bd6597 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap5Key.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/ConcurrentHashMap5Key.java
@@ -33,6 +33,8 @@ import org.apache.juneau.common.function.*;
  * <h5 class='section'>Features:</h5>
  * <ul class='spaced-list'>
  *     <li><b>Five-Part Keys:</b> Values are indexed by five keys (K1, K2, K3, 
K4, K5) instead of a single key
+ *     <li><b>Content-Based Keys:</b> Keys use content-based equality via 
{@link Tuple5}
+ *     <li><b>Array Support:</b> Arrays can be used as key components with 
proper content-based hashing and equality
  *     <li><b>Thread-Safe:</b> Inherits all thread-safety guarantees from 
{@link ConcurrentHashMap}
  *     <li><b>Null Keys Supported:</b> All key parts can be <jk>null</jk>
  *     <li><b>Optional Caching:</b> Can be disabled to always invoke a 
supplier function instead of caching
@@ -93,17 +95,18 @@ import org.apache.juneau.common.function.*;
  *
  * <h5 class='section'>See Also:</h5>
  * <ul>
- *     <li class='jc'>{@link ConcurrentTwoKeyHashMap}
- *     <li class='jc'>{@link ConcurrentThreeKeyHashMap}
- *     <li class='jc'>{@link ConcurrentFourKeyHashMap}
+ *     <li class='jc'>{@link ConcurrentHashMap1Key}
+ *     <li class='jc'>{@link ConcurrentHashMap2Key}
+ *     <li class='jc'>{@link ConcurrentHashMap3Key}
+ *     <li class='jc'>{@link ConcurrentHashMap4Key}
  *     <li class='link'><a class="doclink" 
href="../../../../../index.html#juneau-common">Overview &gt; juneau-common</a>
  * </ul>
  *
- * @param <K1> The first key component type.
- * @param <K2> The second key component type.
- * @param <K3> The third key component type.
- * @param <K4> The fourth key component type.
- * @param <K5> The fifth key component type.
+ * @param <K1> The first key component type. Can be an array type for 
content-based key matching.
+ * @param <K2> The second key component type. Can be an array type for 
content-based key matching.
+ * @param <K3> The third key component type. Can be an array type for 
content-based key matching.
+ * @param <K4> The fourth key component type. Can be an array type for 
content-based key matching.
+ * @param <K5> The fifth key component type. Can be an array type for 
content-based key matching.
  * @param <V> The value type.
  * @serial exclude
  */
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple1.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple1.java
new file mode 100644
index 0000000000..be51c8ca8f
--- /dev/null
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple1.java
@@ -0,0 +1,99 @@
+/*
+ * 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.function;
+
+import static org.apache.juneau.common.utils.Utils.*;
+
+import org.apache.juneau.common.utils.*;
+
+/**
+ * Represents a simple tuple of 1 object.
+ *
+ * <p>
+ * This class is useful when you need a single-value wrapper that properly 
implements
+ * {@link #equals(Object)} and {@link #hashCode()} based on content rather 
than identity.
+ * This is particularly useful for HashMap keys when you want to use an array 
or need
+ * a wrapper that uses content-based equality.
+ *
+ * <h5 class='section'>Array Support:</h5>
+ * <p>
+ * Unlike using arrays directly as HashMap keys, this class properly handles 
arrays by using
+ * content-based equality and hashing via {@link HashCode#of(Object...)} which 
internally uses
+ * {@link java.util.Arrays#hashCode(Object[])} for arrays.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *     <jc>// Using arrays as keys via Tuple1</jc>
+ *     Map&lt;Tuple1&lt;String[]&gt;,Integer&gt; <jv>map</jv> = <jk>new</jk> 
HashMap&lt;&gt;();
+ *     <jv>map</jv>.put(Tuple1.<jsm>of</jsm>(<jk>new</jk> 
String[]{<js>"a"</js>,<js>"b"</js>}), 1);
+ *     <jv>map</jv>.put(Tuple1.<jsm>of</jsm>(<jk>new</jk> 
String[]{<js>"a"</js>,<js>"b"</js>}), 2);  <jc>// Replaces first entry</jc>
+ *     System.<jsf>out</jsf>.println(<jv>map</jv>.size());  <jc>// Prints 
"1"</jc>
+ * </p>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='jc'>{@link Tuple2}
+ *     <li class='jc'>{@link Tuple3}
+ *     <li class='jc'>{@link Tuple4}
+ *     <li class='jc'>{@link Tuple5}
+ * </ul>
+ *
+ * @param <A> Object type.
+ */
+public class Tuple1<A> {
+
+       /**
+        * Static creator.
+        *
+        * @param <A> Object type.
+        * @param a Object value.
+        * @return A new tuple object.
+        */
+       public static <A> Tuple1<A> of(A a) {
+               return new Tuple1<>(a);
+       }
+
+       private final A a;
+       private final int hashCode;
+
+       /**
+        * Constructor.
+        *
+        * @param a Object value.
+        */
+       public Tuple1(A a) {
+               this.a = a;
+               this.hashCode = HashCode.of(a);
+       }
+
+       @Override /* Overridden from Object */
+       public boolean equals(Object o) {
+               return o instanceof Tuple1<?> o2 && eq(this, o2, (x, y) -> 
eq(x.a, y.a));
+       }
+
+       /**
+        * Returns the object in this tuple.
+        *
+        * @return The object in this tuple.
+        */
+       public A getA() { return a; }
+
+       @Override /* Overridden from Object */
+       public int hashCode() {
+               return hashCode;
+       }
+}
+
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple2.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple2.java
index 5661328420..3b54502ea9 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple2.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple2.java
@@ -23,7 +23,29 @@ import org.apache.juneau.common.utils.*;
 /**
  * Represents a simple tuple of 2 objects.
  *
+ * <p>
+ * This class is useful when you need a two-value composite key for HashMap 
lookups that properly implements
+ * {@link #equals(Object)} and {@link #hashCode()} based on content rather 
than identity.
+ *
+ * <h5 class='section'>Array Support:</h5>
+ * <p>
+ * Unlike using arrays directly as HashMap keys, this class properly handles 
arrays by using
+ * content-based equality and hashing via {@link HashCode#of(Object...)} which 
internally uses
+ * {@link java.util.Arrays#hashCode(Object[])} for arrays.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ *     <jc>// Using arrays in composite keys</jc>
+ *     Map&lt;Tuple2&lt;String,int[]&gt;,Result&gt; <jv>map</jv> = 
<jk>new</jk> HashMap&lt;&gt;();
+ *     <jv>map</jv>.put(Tuple2.<jsm>of</jsm>(<js>"key"</js>, <jk>new</jk> 
<jk>int</jk>[]{1,2,3}), <jv>result1</jv>);
+ *     Result <jv>r</jv> = 
<jv>map</jv>.get(Tuple2.<jsm>of</jsm>(<js>"key"</js>, <jk>new</jk> 
<jk>int</jk>[]{1,2,3}));  <jc>// Works correctly!</jc>
+ * </p>
+ *
  * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='jc'>{@link Tuple1}
+ *     <li class='jc'>{@link Tuple3}
+ *     <li class='jc'>{@link Tuple4}
+ *     <li class='jc'>{@link Tuple5}
  * </ul>
  *
  * @param <A> Object 1 type.
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple3.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple3.java
index 3990c3531a..10e1e69b93 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple3.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple3.java
@@ -23,7 +23,21 @@ import org.apache.juneau.common.utils.*;
 /**
  * Represents a simple tuple of 3 objects.
  *
+ * <p>
+ * This class is useful when you need a three-value composite key for HashMap 
lookups that properly implements
+ * {@link #equals(Object)} and {@link #hashCode()} based on content rather 
than identity.
+ *
+ * <h5 class='section'>Array Support:</h5>
+ * <p>
+ * Unlike using arrays directly as HashMap keys, this class properly handles 
arrays by using
+ * content-based equality and hashing via {@link HashCode#of(Object...)} which 
internally uses
+ * {@link java.util.Arrays#hashCode(Object[])} for arrays.
+ *
  * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='jc'>{@link Tuple1}
+ *     <li class='jc'>{@link Tuple2}
+ *     <li class='jc'>{@link Tuple4}
+ *     <li class='jc'>{@link Tuple5}
  * </ul>
  *
  * @param <A> Object 1 type.
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple4.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple4.java
index b372ee06b5..2db39418e4 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple4.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple4.java
@@ -23,7 +23,21 @@ import org.apache.juneau.common.utils.*;
 /**
  * Represents a simple tuple of 4 objects.
  *
+ * <p>
+ * This class is useful when you need a four-value composite key for HashMap 
lookups that properly implements
+ * {@link #equals(Object)} and {@link #hashCode()} based on content rather 
than identity.
+ *
+ * <h5 class='section'>Array Support:</h5>
+ * <p>
+ * Unlike using arrays directly as HashMap keys, this class properly handles 
arrays by using
+ * content-based equality and hashing via {@link HashCode#of(Object...)} which 
internally uses
+ * {@link java.util.Arrays#hashCode(Object[])} for arrays.
+ *
  * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='jc'>{@link Tuple1}
+ *     <li class='jc'>{@link Tuple2}
+ *     <li class='jc'>{@link Tuple3}
+ *     <li class='jc'>{@link Tuple5}
  * </ul>
  *
  * @param <A> Object 1 type.
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple5.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple5.java
index 3be21a81cf..e80420b8df 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple5.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/function/Tuple5.java
@@ -23,7 +23,21 @@ import org.apache.juneau.common.utils.*;
 /**
  * Represents a simple tuple of 5 objects.
  *
+ * <p>
+ * This class is useful when you need a five-value composite key for HashMap 
lookups that properly implements
+ * {@link #equals(Object)} and {@link #hashCode()} based on content rather 
than identity.
+ *
+ * <h5 class='section'>Array Support:</h5>
+ * <p>
+ * Unlike using arrays directly as HashMap keys, this class properly handles 
arrays by using
+ * content-based equality and hashing via {@link HashCode#of(Object...)} which 
internally uses
+ * {@link java.util.Arrays#hashCode(Object[])} for arrays.
+ *
  * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='jc'>{@link Tuple1}
+ *     <li class='jc'>{@link Tuple2}
+ *     <li class='jc'>{@link Tuple3}
+ *     <li class='jc'>{@link Tuple4}
  * </ul>
  *
  * @param <A> Object 1 type.
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
index 2c8c34236e..4b1a356fc1 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
@@ -189,6 +189,46 @@ public class AnnotationProvider {
         */
        public static final AnnotationProvider INSTANCE = new 
AnnotationProvider(create());
 
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Performance instrumentation
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       private static final Map<String, Long> methodCallCounts = new 
java.util.concurrent.ConcurrentHashMap<>();
+       private static final boolean ENABLE_INSTRUMENTATION = 
Boolean.getBoolean("juneau.instrumentAnnotationProvider");
+       private static final boolean ENABLE_NEW_CODE = 
Boolean.getBoolean("juneau.annotationProvider.enableNewCode");
+
+       static {
+               if (ENABLE_INSTRUMENTATION) {
+                       Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+                               try {
+                                       java.io.PrintWriter pw = new 
java.io.PrintWriter(new 
java.io.FileWriter("/tmp/annotation-provider-stats.txt"));
+                                       pw.println("\n=== AnnotationProvider 
Method Call Statistics ===");
+                                       pw.println(String.format("%-65s %12s", 
"Method", "Calls"));
+                                       pw.println("=".repeat(80));
+                                       
+                                       methodCallCounts.entrySet().stream()
+                                               .sorted(Map.Entry.<String, 
Long>comparingByValue().reversed())
+                                               .forEach(e -> 
pw.println(String.format("%-65s %,12d", e.getKey(), e.getValue())));
+                                       
+                                       long totalCalls = 
methodCallCounts.values().stream().mapToLong(Long::longValue).sum();
+                                       pw.println("=".repeat(80));
+                                       pw.println(String.format("%-65s %,12d", 
"TOTAL", totalCalls));
+                                       pw.println("=== Total methods 
instrumented: " + methodCallCounts.size() + " ===\n");
+                                       pw.close();
+                                       System.err.println("\n=== 
AnnotationProvider statistics written to /tmp/annotation-provider-stats.txt 
===\n");
+                               } catch (Exception e) {
+                                       e.printStackTrace();
+                               }
+                       }));
+               }
+       }
+
+       private static void trackCall(String methodSignature) {
+               if (ENABLE_INSTRUMENTATION) {
+                       methodCallCounts.merge(methodSignature, 1L, Long::sum);
+               }
+       }
+
        
//-----------------------------------------------------------------------------------------------------------------
        // Builder
        
//-----------------------------------------------------------------------------------------------------------------
@@ -490,6 +530,10 @@ public class AnnotationProvider {
         */
        @SuppressWarnings("unchecked")
        public <A extends Annotation> Stream<AnnotationInfo<A>> find(Class<A> 
type, ClassInfo clazz, AnnotationTraversal... traversals) {
+               trackCall("find(Class, ClassInfo, AnnotationTraversal...)");
+               if (ENABLE_NEW_CODE)
+                       return findNew(type, clazz, traversals).stream();
+               
                assertArgNotNull("type", type);
                assertArgNotNull("clazz", clazz);
                if (traversals.length == 0)
@@ -518,6 +562,17 @@ public class AnnotationProvider {
                        });
        }
 
+       /**
+        * New optimized implementation of find(Class, ClassInfo, 
AnnotationTraversal...).
+        * Returns a List for better performance.
+        * Enable with -Djuneau.annotationProvider.enableNewCode=true
+        */
+       @SuppressWarnings("unchecked")
+       private <A extends Annotation> List<AnnotationInfo<A>> findNew(Class<A> 
type, ClassInfo clazz, AnnotationTraversal... traversals) {
+               // TODO: Implement optimized version
+               throw new UnsupportedOperationException("New implementation not 
yet available");
+       }
+
        /**
         * Streams all annotations from a class using configurable traversal 
options, without filtering by annotation type.
         *
@@ -542,6 +597,7 @@ public class AnnotationProvider {
         */
        @SuppressWarnings("unchecked")
        public Stream<AnnotationInfo<? extends Annotation>> find(ClassInfo 
clazz, AnnotationTraversal... traversals) {
+               trackCall("find(ClassInfo, AnnotationTraversal...)");
                assertArgNotNull("clazz", clazz);
                if (traversals.length == 0)
                        traversals = a(PARENTS, PACKAGE);
@@ -582,6 +638,7 @@ public class AnnotationProvider {
         * @return A stream of {@link AnnotationInfo} objects in parent-first 
order. Never <jk>null</jk>.
         */
        public <A extends Annotation> Stream<AnnotationInfo<A>> 
findTopDown(Class<A> type, ClassInfo clazz, AnnotationTraversal... traversals) {
+               trackCall("findTopDown(Class, ClassInfo, 
AnnotationTraversal...)");
                return rstream(find(type, clazz, traversals).toList());
        }
 
@@ -597,6 +654,7 @@ public class AnnotationProvider {
         * @return A stream of {@link AnnotationInfo} objects in parent-first 
order. Never <jk>null</jk>.
         */
        public Stream<AnnotationInfo<? extends Annotation>> 
findTopDown(ClassInfo clazz, AnnotationTraversal... traversals) {
+               trackCall("findTopDown(ClassInfo, AnnotationTraversal...)");
                return rstream(find(clazz, traversals).toList());
        }
 
@@ -638,6 +696,7 @@ public class AnnotationProvider {
         * @return <jk>true</jk> if the annotation is found, <jk>false</jk> 
otherwise.
         */
        public <A extends Annotation> boolean has(Class<A> type, ClassInfo 
clazz, AnnotationTraversal... traversals) {
+               trackCall("has(Class, ClassInfo, AnnotationTraversal...)");
                return find(type, clazz, traversals).findFirst().isPresent();
        }
 
@@ -666,6 +725,7 @@ public class AnnotationProvider {
         */
        @SuppressWarnings("unchecked")
        public <A extends Annotation> Stream<AnnotationInfo<A>> find(Class<A> 
type, MethodInfo method, AnnotationTraversal... traversals) {
+               trackCall("find(Class, MethodInfo, AnnotationTraversal...)");
                assertArgNotNull("type", type);
                assertArgNotNull("method", method);
                if (traversals.length == 0)
@@ -720,6 +780,7 @@ public class AnnotationProvider {
         * @return A stream of {@link AnnotationInfo} objects. Never 
<jk>null</jk>.
         */
        public Stream<AnnotationInfo<? extends Annotation>> find(MethodInfo 
method, AnnotationTraversal... traversals) {
+               trackCall("find(MethodInfo, AnnotationTraversal...)");
                assertArgNotNull("method", method);
                if (traversals.length == 0)
                        traversals = a(SELF, MATCHING_METHODS, DECLARING_CLASS, 
RETURN_TYPE, PACKAGE);
@@ -764,6 +825,7 @@ public class AnnotationProvider {
         * @return A stream of {@link AnnotationInfo} objects in parent-first 
order. Never <jk>null</jk>.
         */
        public <A extends Annotation> Stream<AnnotationInfo<A>> 
findTopDown(Class<A> type, MethodInfo method, AnnotationTraversal... 
traversals) {
+               trackCall("findTopDown(Class, MethodInfo, 
AnnotationTraversal...)");
                return rstream(find(type, method, traversals).toList());
        }
 
@@ -779,6 +841,7 @@ public class AnnotationProvider {
         * @return A stream of {@link AnnotationInfo} objects in parent-first 
order. Never <jk>null</jk>.
         */
        public Stream<AnnotationInfo<? extends Annotation>> 
findTopDown(MethodInfo method, AnnotationTraversal... traversals) {
+               trackCall("findTopDown(MethodInfo, AnnotationTraversal...)");
                return rstream(find(method, traversals).toList());
        }
 
@@ -820,6 +883,7 @@ public class AnnotationProvider {
         * @return <jk>true</jk> if the annotation is found, <jk>false</jk> 
otherwise.
         */
        public <A extends Annotation> boolean has(Class<A> type, MethodInfo 
method, AnnotationTraversal... traversals) {
+               trackCall("has(Class, MethodInfo, AnnotationTraversal...)");
                return find(type, method, traversals).findFirst().isPresent();
        }
 
@@ -863,6 +927,7 @@ public class AnnotationProvider {
         * @return A stream of {@link AnnotationInfo} objects in 
child-to-parent order. Never <jk>null</jk>.
         */
        public <A extends Annotation> Stream<AnnotationInfo<A>> find(Class<A> 
type, ParameterInfo parameter, AnnotationTraversal... traversals) {
+               trackCall("find(Class, ParameterInfo, AnnotationTraversal...)");
                assertArgNotNull("type", type);
                assertArgNotNull("parameter", parameter);
                if (traversals.length == 0)
@@ -918,6 +983,7 @@ public class AnnotationProvider {
         * @return A stream of {@link AnnotationInfo} objects in 
parent-to-child order. Never <jk>null</jk>.
         */
        public <A extends Annotation> Stream<AnnotationInfo<A>> 
findTopDown(Class<A> type, ParameterInfo parameter, AnnotationTraversal... 
traversals) {
+               trackCall("findTopDown(Class, ParameterInfo, 
AnnotationTraversal...)");
                return rstream(find(type, parameter, traversals).toList());
        }
 
@@ -956,6 +1022,7 @@ public class AnnotationProvider {
         * @return A stream of {@link AnnotationInfo} objects in 
child-to-parent order. Never <jk>null</jk>.
         */
        public Stream<AnnotationInfo<? extends Annotation>> find(ParameterInfo 
parameter, AnnotationTraversal... traversals) {
+               trackCall("find(ParameterInfo, AnnotationTraversal...)");
                assertArgNotNull("parameter", parameter);
                if (traversals.length == 0)
                        traversals = a(SELF, MATCHING_PARAMETERS, 
PARAMETER_TYPE);
@@ -988,6 +1055,7 @@ public class AnnotationProvider {
         * @return A stream of {@link AnnotationInfo} objects in 
parent-to-child order. Never <jk>null</jk>.
         */
        public Stream<AnnotationInfo<? extends Annotation>> 
findTopDown(ParameterInfo parameter, AnnotationTraversal... traversals) {
+               trackCall("findTopDown(ParameterInfo, AnnotationTraversal...)");
                return rstream(find(parameter, traversals).toList());
        }
 
@@ -1028,6 +1096,7 @@ public class AnnotationProvider {
         * @return <jk>true</jk> if the annotation is found, <jk>false</jk> 
otherwise.
         */
        public <A extends Annotation> boolean has(Class<A> type, ParameterInfo 
parameter, AnnotationTraversal... traversals) {
+               trackCall("has(Class, ParameterInfo, AnnotationTraversal...)");
                return find(type, parameter, 
traversals).findFirst().isPresent();
        }
 
@@ -1052,6 +1121,7 @@ public class AnnotationProvider {
         */
        @SuppressWarnings("unchecked")
        public <A extends Annotation> Stream<AnnotationInfo<A>> find(Class<A> 
type, FieldInfo field, AnnotationTraversal... traversals) {
+               trackCall("find(Class, FieldInfo, AnnotationTraversal...)");
                assertArgNotNull("type", type);
                assertArgNotNull("field", field);
                if (traversals.length == 0)
@@ -1089,6 +1159,7 @@ public class AnnotationProvider {
         * @return A stream of {@link AnnotationInfo} objects. Never 
<jk>null</jk>.
         */
        public Stream<AnnotationInfo<? extends Annotation>> find(FieldInfo 
field, AnnotationTraversal... traversals) {
+               trackCall("find(FieldInfo, AnnotationTraversal...)");
                assertArgNotNull("field", field);
                if (traversals.length == 0)
                        traversals = a(SELF);
@@ -1120,6 +1191,7 @@ public class AnnotationProvider {
         * @return A stream of {@link AnnotationInfo} objects in parent-first 
order. Never <jk>null</jk>.
         */
        public <A extends Annotation> Stream<AnnotationInfo<A>> 
findTopDown(Class<A> type, FieldInfo field, AnnotationTraversal... traversals) {
+               trackCall("findTopDown(Class, FieldInfo, 
AnnotationTraversal...)");
                return rstream(find(type, field, traversals).toList());
        }
 
@@ -1135,6 +1207,7 @@ public class AnnotationProvider {
         * @return A stream of {@link AnnotationInfo} objects in parent-first 
order. Never <jk>null</jk>.
         */
        public Stream<AnnotationInfo<? extends Annotation>> 
findTopDown(FieldInfo field, AnnotationTraversal... traversals) {
+               trackCall("findTopDown(FieldInfo, AnnotationTraversal...)");
                return rstream(find(field, traversals).toList());
        }
 
@@ -1170,6 +1243,7 @@ public class AnnotationProvider {
         * @return <jk>true</jk> if the annotation is found, <jk>false</jk> 
otherwise.
         */
        public <A extends Annotation> boolean has(Class<A> type, FieldInfo 
field, AnnotationTraversal... traversals) {
+               trackCall("has(Class, FieldInfo, AnnotationTraversal...)");
                return find(type, field, traversals).findFirst().isPresent();
        }
 
@@ -1194,6 +1268,7 @@ public class AnnotationProvider {
         */
        @SuppressWarnings("unchecked")
        public <A extends Annotation> Stream<AnnotationInfo<A>> find(Class<A> 
type, ConstructorInfo constructor, AnnotationTraversal... traversals) {
+               trackCall("find(Class, ConstructorInfo, 
AnnotationTraversal...)");
                assertArgNotNull("type", type);
                assertArgNotNull("constructor", constructor);
                if (traversals.length == 0)
@@ -1231,6 +1306,7 @@ public class AnnotationProvider {
         * @return A stream of {@link AnnotationInfo} objects. Never 
<jk>null</jk>.
         */
        public Stream<AnnotationInfo<? extends Annotation>> 
find(ConstructorInfo constructor, AnnotationTraversal... traversals) {
+               trackCall("find(ConstructorInfo, AnnotationTraversal...)");
                assertArgNotNull("constructor", constructor);
                if (traversals.length == 0)
                        traversals = a(SELF);
@@ -1262,6 +1338,7 @@ public class AnnotationProvider {
         * @return A stream of {@link AnnotationInfo} objects in parent-first 
order. Never <jk>null</jk>.
         */
        public <A extends Annotation> Stream<AnnotationInfo<A>> 
findTopDown(Class<A> type, ConstructorInfo constructor, AnnotationTraversal... 
traversals) {
+               trackCall("findTopDown(Class, ConstructorInfo, 
AnnotationTraversal...)");
                return rstream(find(type, constructor, traversals).toList());
        }
 
@@ -1277,6 +1354,7 @@ public class AnnotationProvider {
         * @return A stream of {@link AnnotationInfo} objects in parent-first 
order. Never <jk>null</jk>.
         */
        public Stream<AnnotationInfo<? extends Annotation>> 
findTopDown(ConstructorInfo constructor, AnnotationTraversal... traversals) {
+               trackCall("findTopDown(ConstructorInfo, 
AnnotationTraversal...)");
                return rstream(find(constructor, traversals).toList());
        }
 
@@ -1312,9 +1390,10 @@ public class AnnotationProvider {
         * @return <jk>true</jk> if the annotation is found, <jk>false</jk> 
otherwise.
         */
        public <A extends Annotation> boolean has(Class<A> type, 
ConstructorInfo constructor, AnnotationTraversal... traversals) {
+               trackCall("has(Class, ConstructorInfo, 
AnnotationTraversal...)");
                return find(type, constructor, 
traversals).findFirst().isPresent();
        }
-
+       
        
//-----------------------------------------------------------------------------------------------------------------
        // Helper methods
        
//-----------------------------------------------------------------------------------------------------------------
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/HashCode.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/HashCode.java
index c46aab3fcb..ee08047988 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/HashCode.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/utils/HashCode.java
@@ -71,12 +71,40 @@ public class HashCode {
        /**
         * Hashes the hashcode of the specified object into this object.
         *
+        * <p>
+        * Arrays are handled specially to use content-based hashing via {@link 
java.util.Arrays#hashCode(Object[])}
+        * instead of identity-based hashing.
+        *
         * @param o The object whose hashcode will be hashed with this object.
         * @return This object.
         */
        public HashCode add(Object o) {
                o = unswap(o);
-               add(o == null ? 0 : o.hashCode());
+               if (o == null) {
+                       add(0);
+               } else if (o.getClass().isArray()) {
+                       // Use content-based hashcode for arrays
+                       if (o instanceof Object[])
+                               add(java.util.Arrays.hashCode((Object[])o));
+                       else if (o instanceof int[])
+                               add(java.util.Arrays.hashCode((int[])o));
+                       else if (o instanceof long[])
+                               add(java.util.Arrays.hashCode((long[])o));
+                       else if (o instanceof short[])
+                               add(java.util.Arrays.hashCode((short[])o));
+                       else if (o instanceof byte[])
+                               add(java.util.Arrays.hashCode((byte[])o));
+                       else if (o instanceof char[])
+                               add(java.util.Arrays.hashCode((char[])o));
+                       else if (o instanceof boolean[])
+                               add(java.util.Arrays.hashCode((boolean[])o));
+                       else if (o instanceof float[])
+                               add(java.util.Arrays.hashCode((float[])o));
+                       else if (o instanceof double[])
+                               add(java.util.Arrays.hashCode((double[])o));
+               } else {
+                       add(o.hashCode());
+               }
                return this;
        }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/MethodInfo_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/MethodInfo_Test.java
index 0b553bbc71..c20b07a0ff 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/common/reflect/MethodInfo_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/common/reflect/MethodInfo_Test.java
@@ -352,7 +352,7 @@ class MethodInfo_Test extends TestBase {
        }
 
        private static List<A> annotations(MethodInfo mi, Class<? extends 
Annotation> a) {
-               return AnnotationProvider.INSTANCE.findTopDown(a, 
mi).map(AnnotationInfo::inner).map(PredicateUtils.peek()).map(x -> 
(A)x).collect(Collectors.toList());
+               return AnnotationProvider.INSTANCE.findTopDown(a, 
mi).map(AnnotationInfo::inner).map(x -> (A)x).collect(Collectors.toList());
        }
 
        @Test void getAnnotationAny() {

Reply via email to