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>, () ->
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<String[],Result> <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>},
() -> computeResult());
+ * Result <jv>r</jv> = <jv>cache</jv>.get(<jk>new</jk>
String[]{<js>"a"</js>, <js>"b"</js>}, () -> 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 > 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<String[],Result> <jv>cache</jv> =
+ * <jk>new</jk> ConcurrentHashMap1Key<>();
+ *
+ * <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<int[],String> <jv>map</jv> = <jk>new</jk>
ConcurrentHashMap1Key<>();
+ * <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 > 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<String,int[],Result> <jv>map</jv> =
<jk>new</jk> ConcurrentHashMap2Key<>();
+ * <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 > 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 > 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 > 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 > 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<Tuple1<String[]>,Integer> <jv>map</jv> = <jk>new</jk>
HashMap<>();
+ * <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<Tuple2<String,int[]>,Result> <jv>map</jv> =
<jk>new</jk> HashMap<>();
+ * <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() {