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

commit 38732083a636c3b2b761cadfbde33876586b0c1d
Author: James Bognar <[email protected]>
AuthorDate: Tue Nov 25 09:43:23 2025 -0500

    org.apache.juneau.common.reflect API improvements
---
 .../apache/juneau/common/collections/Cache.java    |  11 +-
 .../apache/juneau/common/collections/Cache2.java   |   3 +
 .../apache/juneau/common/collections/Cache3.java   |   3 +
 .../apache/juneau/common/collections/Cache4.java   |   3 +
 .../apache/juneau/common/collections/Cache5.java   |   3 +
 .../juneau/common/collections/BidiMap_Test.java    | 123 +++++++++++++++++++++
 .../juneau/common/collections/Cache2_Test.java     |  92 +++++++++++++++
 .../juneau/common/collections/Cache3_Test.java     |  54 +++++++++
 .../juneau/common/collections/Cache4_Test.java     |  54 +++++++++
 .../juneau/common/collections/Cache5_Test.java     |  54 +++++++++
 .../juneau/common/collections/Cache_Test.java      |  91 +++++++++++++++
 .../juneau/common/collections/SimpleMap_Test.java  |  63 ++++++++++-
 12 files changed, 548 insertions(+), 6 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 72e45466cb..a417db4489 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
@@ -529,8 +529,12 @@ public class Cache<K,V> {
         * @return The previous value associated with the key, or <jk>null</jk> 
if there was no mapping.
         */
        public V put(K key, V value) {
-               if (value == null)
-                       return map.remove(wrap(key));
+               if (value == null) {
+                       Tuple1<K> wrapped = wrap(key);
+                       V result = map.remove(wrapped);
+                       wrapperCache.remove(key); // Clean up wrapper cache
+                       return result;
+               }
                return map.put(wrap(key), value);
        }
 
@@ -595,6 +599,9 @@ public class Cache<K,V> {
         * @return <jk>true</jk> if the cache contains the value.
         */
        public boolean containsValue(V value) {
+               // ConcurrentHashMap doesn't allow null values, so null can 
never be in the cache
+               if (value == null)
+                       return false;
                return map.containsValue(value);
        }
 
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache2.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache2.java
index d0448bc6de..da1f85619f 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache2.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache2.java
@@ -505,6 +505,9 @@ public class Cache2<K1,K2,V> {
         * @return <jk>true</jk> if the cache contains the value.
         */
        public boolean containsValue(V value) {
+               // ConcurrentHashMap doesn't allow null values, so null can 
never be in the cache
+               if (value == null)
+                       return false;
                return map.containsValue(value);
        }
 
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache3.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache3.java
index 3cdd0a7f21..468775c69a 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache3.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache3.java
@@ -331,6 +331,9 @@ public class Cache3<K1,K2,K3,V> {
         * @return <jk>true</jk> if the cache contains the value.
         */
        public boolean containsValue(V value) {
+               // ConcurrentHashMap doesn't allow null values, so null can 
never be in the cache
+               if (value == null)
+                       return false;
                return map.containsValue(value);
        }
 
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache4.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache4.java
index 0b1f12e48d..7161d0896b 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache4.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache4.java
@@ -330,6 +330,9 @@ public class Cache4<K1,K2,K3,K4,V> {
         * @return <jk>true</jk> if the cache contains the value.
         */
        public boolean containsValue(V value) {
+               // ConcurrentHashMap doesn't allow null values, so null can 
never be in the cache
+               if (value == null)
+                       return false;
                return map.containsValue(value);
        }
 
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache5.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache5.java
index f794ad6ebf..5c04504991 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache5.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/collections/Cache5.java
@@ -341,6 +341,9 @@ public class Cache5<K1,K2,K3,K4,K5,V> {
         * @return <jk>true</jk> if the cache contains the value.
         */
        public boolean containsValue(V value) {
+               // ConcurrentHashMap doesn't allow null values, so null can 
never be in the cache
+               if (value == null)
+                       return false;
                return map.containsValue(value);
        }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/common/collections/BidiMap_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/common/collections/BidiMap_Test.java
index 7f25e3e492..a263709ee4 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/common/collections/BidiMap_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/common/collections/BidiMap_Test.java
@@ -346,6 +346,22 @@ class BidiMap_Test extends TestBase {
                assertNull(map.getKey(1));
        }
 
+       @Test void a22b_overwriteInBuilder_differentValue() {
+               // Test line 123: overwriting a key with a different value
+               // This should remove the old value from tracking
+               var map = BidiMap.<String,Integer>create()
+                       .add("key1", 100)
+                       .add("key2", 200)
+                       .add("key1", 300)  // Overwrite key1 with different 
value (line 123)
+                       .build();
+
+               assertSize(2, map);
+               assertEquals(300, map.get("key1"));
+               assertEquals(200, map.get("key2"));
+               assertEquals("key1", map.getKey(300));
+               assertNull(map.getKey(100)); // Old value should be removed
+       }
+
        @Test void a23_duplicateValuesInBuilder() {
                // Duplicate values are not allowed
                assertThrows(IllegalArgumentException.class, () ->
@@ -356,6 +372,113 @@ class BidiMap_Test extends TestBase {
                );
        }
 
+       @Test void a23b_duplicateValuesInBuilder_overwritingKey() {
+               // Test line 127: trying to add a value that's already mapped 
to a different key
+               // This should throw IllegalArgumentException
+               // The condition is: values.contains(value) && ! 
value.equals(existingValue)
+               // where existingValue is the value currently mapped to the key 
being added
+               assertThrows(IllegalArgumentException.class, () ->
+                       BidiMap.<String,Integer>create()
+                               .add("key1", 100)
+                               .add("key2", 200)
+                               .add("key1", 200)  // Try to map key1 to 200, 
but 200 is already mapped to key2
+                               .build()
+               );
+       }
+
+       @Test void a23c_overwriteKeyWithSameValue() {
+               // Test line 127: overwriting a key with the same value should 
not throw
+               // The condition is: values.contains(value) && ! 
value.equals(existingValue)
+               // When value.equals(existingValue), the condition is false, so 
assertArg passes
+               var map = BidiMap.<String,Integer>create()
+                       .add("key1", 100)
+                       .add("key2", 200)
+                       .add("key1", 100)  // Overwrite key1 with the same 
value (should be allowed)
+                       .build();
+
+               assertSize(2, map);
+               assertEquals(100, map.get("key1"));
+               assertEquals(200, map.get("key2"));
+       }
+
+       @Test void a23d_overwriteKeyWithNewValue_notInValues() {
+               // Test line 127: overwriting a key with a new value not in the 
values set
+               // The condition is: values.contains(value) && ! 
value.equals(existingValue)
+               // When !values.contains(value), the condition is false, so 
assertArg passes
+               var map = BidiMap.<String,Integer>create()
+                       .add("key1", 100)
+                       .add("key2", 200)
+                       .add("key1", 300)  // Overwrite key1 with a new value 
not in values set
+                       .build();
+
+               assertSize(2, map);
+               assertEquals(300, map.get("key1"));
+               assertEquals(200, map.get("key2"));
+       }
+
+       
//====================================================================================================
+       // Constructor coverage - toMap merge function (lines 185-186)
+       
//====================================================================================================
+
+       @Test void a24_constructorMergeFunction_duplicateKeysInBuilder() {
+               // Test lines 185-186: The toMap merge function (a, b) -> b
+               // Note: Since the builder uses LinkedHashMap, duplicate keys 
are overwritten before
+               // reaching the constructor, so the merge function may not be 
called in practice.
+               // However, this test verifies that the constructor correctly 
processes entries
+               // and that the merge function is present as a safety measure.
+               var map = BidiMap.<String,Integer>create()
+                       .add("key1", 100)
+                       .add("key1", 200)  // Overwrite - LinkedHashMap handles 
this
+                       .add("key1", 300)  // Overwrite again
+                       .build();
+
+               // The last value should win (merge function behavior: (a, b) 
-> b)
+               assertSize(1, map);
+               assertEquals(300, map.get("key1"));
+               assertEquals("key1", map.getKey(300));
+       }
+
+       @Test void a25_constructorMergeFunction_duplicateValuesInBuilder() {
+               // Test line 186: The reverse map's toMap merge function (a, b) 
-> b
+               // Note: The builder prevents duplicate values, so the merge 
function may not be called.
+               // However, this test verifies that the constructor correctly 
processes entries
+               // and handles value overwrites correctly.
+               var map = BidiMap.<String,Integer>create()
+                       .add("key1", 100)
+                       .add("key2", 200)
+                       .add("key1", 300)  // Overwrite key1, removing 100 from 
reverse map
+                       .build();
+
+               // Verify the reverse map correctly reflects the overwrite
+               assertSize(2, map);
+               assertEquals(300, map.get("key1"));
+               assertEquals(200, map.get("key2"));
+               assertEquals("key1", map.getKey(300));
+               assertEquals("key2", map.getKey(200));
+               assertNull(map.getKey(100)); // Old value should be removed
+       }
+
+       @Test void a26_constructorWithNullEntries_filteredCorrectly() {
+               // Test lines 185-186: Verify that null keys and values are 
filtered correctly
+               // in both forward and reverse maps during construction
+               var map = BidiMap.<String,Integer>create()
+                       .add("key1", 1)
+                       .add(null, 2)      // Null key - should be filtered
+                       .add("key3", null) // Null value - should be filtered
+                       .add(null, null)   // Both null - should be filtered
+                       .add("key5", 5)
+                       .build();
+
+               // Only non-null entries should be present
+               assertSize(2, map);
+               assertEquals(1, map.get("key1"));
+               assertEquals(5, map.get("key5"));
+               assertEquals("key1", map.getKey(1));
+               assertEquals("key5", map.getKey(5));
+               assertNull(map.get(null));
+               assertNull(map.getKey(null));
+       }
+
        @Test void a24_sizeAfterOperations() {
                var map = BidiMap.<String,Integer>create().build();
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache2_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache2_Test.java
index 05405caccc..1ecff25f97 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache2_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache2_Test.java
@@ -414,6 +414,24 @@ class Cache2_Test extends TestBase {
                assertEquals("value2", x.get("user", 123, () -> "should not be 
called"));
        }
 
+       @Test
+       void i03_put_withNullValue() {
+               var x = Cache2.of(String.class, Integer.class, 
String.class).build();
+               x.put("user", 123, "value1");
+               var previous = x.put("user", 123, null);
+               assertEquals("value1", previous);
+               assertFalse(x.containsKey("user", 123));
+       }
+
+       @Test
+       void i04_put_withNullValue_newKey() {
+               var x = Cache2.of(String.class, Integer.class, 
String.class).build();
+               var previous = x.put("user", 123, null);
+               assertNull(previous);
+               assertFalse(x.containsKey("user", 123));
+               assertTrue(x.isEmpty());
+       }
+
        
//====================================================================================================
        // j - isEmpty() method
        
//====================================================================================================
@@ -466,6 +484,80 @@ class Cache2_Test extends TestBase {
                assertTrue(x.containsKey("user", 123));
        }
 
+       
//====================================================================================================
+       // l - remove() method
+       
//====================================================================================================
+
+       @Test
+       void l02_remove_existingKey() {
+               var x = Cache2.of(String.class, Integer.class, 
String.class).build();
+               x.put("user", 123, "value1");
+               var removed = x.remove("user", 123);
+               assertEquals("value1", removed);
+               assertFalse(x.containsKey("user", 123));
+       }
+
+       @Test
+       void l03_remove_nonExistentKey() {
+               var x = Cache2.of(String.class, Integer.class, 
String.class).build();
+               var removed = x.remove("user", 123);
+               assertNull(removed);
+       }
+
+       
//====================================================================================================
+       // m - containsValue() method
+       
//====================================================================================================
+
+       @Test
+       void m02_containsValue_present() {
+               var x = Cache2.of(String.class, Integer.class, 
String.class).build();
+               x.put("user", 123, "value1");
+               x.put("admin", 456, "value2");
+               assertTrue(x.containsValue("value1"));
+               assertTrue(x.containsValue("value2"));
+               assertFalse(x.containsValue("value3"));
+       }
+
+       @Test
+       void m03_containsValue_notPresent() {
+               var x = Cache2.of(String.class, Integer.class, 
String.class).build();
+               assertFalse(x.containsValue("value1"));
+       }
+
+       
//====================================================================================================
+       // n - logOnExit() builder methods
+       
//====================================================================================================
+
+       @Test
+       void n02_logOnExit_withStringId() {
+               var x = Cache2.of(String.class, Integer.class, String.class)
+                       .logOnExit("TestCache2")
+                       .supplier((k1, k2) -> k1 + ":" + k2)
+                       .build();
+               x.get("user", 123);
+               assertSize(1, x);
+       }
+
+       @Test
+       void n03_logOnExit_withBooleanTrue() {
+               var x = Cache2.of(String.class, Integer.class, String.class)
+                       .logOnExit(true, "MyCache2")
+                       .supplier((k1, k2) -> k1 + ":" + k2)
+                       .build();
+               x.get("user", 123);
+               assertSize(1, x);
+       }
+
+       @Test
+       void n04_logOnExit_withBooleanFalse() {
+               var x = Cache2.of(String.class, Integer.class, String.class)
+                       .logOnExit(false, "DisabledCache2")
+                       .supplier((k1, k2) -> k1 + ":" + k2)
+                       .build();
+               x.get("user", 123);
+               assertSize(1, x);
+       }
+
        
//====================================================================================================
        // l - create() static method
        
//====================================================================================================
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache3_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache3_Test.java
index 774f79e749..2651ee6efa 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache3_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache3_Test.java
@@ -223,6 +223,15 @@ class Cache3_Test extends TestBase {
                assertEquals("value", x.get("en", "US", 1, () -> "should not be 
called"));
        }
 
+       @Test
+       void b01b_put_withNullValue() {
+               var x = Cache3.of(String.class, String.class, Integer.class, 
String.class).build();
+               x.put("en", "US", 1, "value1");
+               var previous = x.put("en", "US", 1, null);
+               assertEquals("value1", previous);
+               assertFalse(x.containsKey("en", "US", 1));
+       }
+
        @Test
        void b02_isEmpty() {
                var x = Cache3.of(String.class, String.class, Integer.class, 
String.class).build();
@@ -286,5 +295,50 @@ class Cache3_Test extends TestBase {
                assertEquals(2, callCount.get());
                assertTrue(x.isEmpty());
        }
+
+       
//====================================================================================================
+       // d - remove() and containsValue()
+       
//====================================================================================================
+
+       @Test
+       void d01_remove() {
+               var x = Cache3.of(String.class, String.class, Integer.class, 
String.class).build();
+               x.put("en", "US", 1, "value1");
+               var removed = x.remove("en", "US", 1);
+               assertEquals("value1", removed);
+               assertFalse(x.containsKey("en", "US", 1));
+       }
+
+       @Test
+       void d02_containsValue() {
+               var x = Cache3.of(String.class, String.class, Integer.class, 
String.class).build();
+               x.put("en", "US", 1, "value1");
+               assertTrue(x.containsValue("value1"));
+               assertFalse(x.containsValue("value2"));
+       }
+
+       
//====================================================================================================
+       // e - logOnExit() builder methods
+       
//====================================================================================================
+
+       @Test
+       void e01_logOnExit_withStringId() {
+               var x = Cache3.of(String.class, String.class, Integer.class, 
String.class)
+                       .logOnExit("TestCache3")
+                       .supplier((k1, k2, k3) -> k1 + ":" + k2 + ":" + k3)
+                       .build();
+               x.get("en", "US", 1);
+               assertSize(1, x);
+       }
+
+       @Test
+       void e02_logOnExit_withBoolean() {
+               var x = Cache3.of(String.class, String.class, Integer.class, 
String.class)
+                       .logOnExit(true, "MyCache3")
+                       .supplier((k1, k2, k3) -> k1 + ":" + k2 + ":" + k3)
+                       .build();
+               x.get("en", "US", 1);
+               assertSize(1, x);
+       }
 }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache4_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache4_Test.java
index 2c687792e7..e2138cfaf9 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache4_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache4_Test.java
@@ -224,6 +224,15 @@ class Cache4_Test extends TestBase {
                assertEquals("value", x.get("en", "US", "formal", 1, () -> 
"should not be called"));
        }
 
+       @Test
+       void b01b_put_withNullValue() {
+               var x = Cache4.of(String.class, String.class, String.class, 
Integer.class, String.class).build();
+               x.put("en", "US", "formal", 1, "value1");
+               var previous = x.put("en", "US", "formal", 1, null);
+               assertEquals("value1", previous);
+               assertFalse(x.containsKey("en", "US", "formal", 1));
+       }
+
        @Test
        void b02_isEmpty() {
                var x = Cache4.of(String.class, String.class, String.class, 
Integer.class, String.class).build();
@@ -286,5 +295,50 @@ class Cache4_Test extends TestBase {
                assertEquals(2, callCount.get());
                assertTrue(x.isEmpty());
        }
+
+       
//====================================================================================================
+       // d - remove() and containsValue()
+       
//====================================================================================================
+
+       @Test
+       void d01_remove() {
+               var x = Cache4.of(String.class, String.class, String.class, 
Integer.class, String.class).build();
+               x.put("en", "US", "formal", 1, "value1");
+               var removed = x.remove("en", "US", "formal", 1);
+               assertEquals("value1", removed);
+               assertFalse(x.containsKey("en", "US", "formal", 1));
+       }
+
+       @Test
+       void d02_containsValue() {
+               var x = Cache4.of(String.class, String.class, String.class, 
Integer.class, String.class).build();
+               x.put("en", "US", "formal", 1, "value1");
+               assertTrue(x.containsValue("value1"));
+               assertFalse(x.containsValue("value2"));
+       }
+
+       
//====================================================================================================
+       // e - logOnExit() builder methods
+       
//====================================================================================================
+
+       @Test
+       void e01_logOnExit_withStringId() {
+               var x = Cache4.of(String.class, String.class, String.class, 
Integer.class, String.class)
+                       .logOnExit("TestCache4")
+                       .supplier((k1, k2, k3, k4) -> k1 + ":" + k2 + ":" + k3 
+ ":" + k4)
+                       .build();
+               x.get("en", "US", "formal", 1);
+               assertSize(1, x);
+       }
+
+       @Test
+       void e02_logOnExit_withBoolean() {
+               var x = Cache4.of(String.class, String.class, String.class, 
Integer.class, String.class)
+                       .logOnExit(true, "MyCache4")
+                       .supplier((k1, k2, k3, k4) -> k1 + ":" + k2 + ":" + k3 
+ ":" + k4)
+                       .build();
+               x.get("en", "US", "formal", 1);
+               assertSize(1, x);
+       }
 }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache5_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache5_Test.java
index 6f04a77f20..9a1c9871b9 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache5_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache5_Test.java
@@ -225,6 +225,15 @@ class Cache5_Test extends TestBase {
                assertEquals("value", x.get("en", "US", "west", "formal", 1, () 
-> "should not be called"));
        }
 
+       @Test
+       void b01b_put_withNullValue() {
+               var x = Cache5.of(String.class, String.class, String.class, 
String.class, Integer.class, String.class).build();
+               x.put("en", "US", "west", "formal", 1, "value1");
+               var previous = x.put("en", "US", "west", "formal", 1, null);
+               assertEquals("value1", previous);
+               assertFalse(x.containsKey("en", "US", "west", "formal", 1));
+       }
+
        @Test
        void b02_isEmpty() {
                var x = Cache5.of(String.class, String.class, String.class, 
String.class, Integer.class, String.class).build();
@@ -287,5 +296,50 @@ class Cache5_Test extends TestBase {
                assertEquals(2, callCount.get());
                assertTrue(x.isEmpty());
        }
+
+       
//====================================================================================================
+       // d - remove() and containsValue()
+       
//====================================================================================================
+
+       @Test
+       void d01_remove() {
+               var x = Cache5.of(String.class, String.class, String.class, 
String.class, Integer.class, String.class).build();
+               x.put("en", "US", "west", "formal", 1, "value1");
+               var removed = x.remove("en", "US", "west", "formal", 1);
+               assertEquals("value1", removed);
+               assertFalse(x.containsKey("en", "US", "west", "formal", 1));
+       }
+
+       @Test
+       void d02_containsValue() {
+               var x = Cache5.of(String.class, String.class, String.class, 
String.class, Integer.class, String.class).build();
+               x.put("en", "US", "west", "formal", 1, "value1");
+               assertTrue(x.containsValue("value1"));
+               assertFalse(x.containsValue("value2"));
+       }
+
+       
//====================================================================================================
+       // e - logOnExit() builder methods
+       
//====================================================================================================
+
+       @Test
+       void e01_logOnExit_withStringId() {
+               var x = Cache5.of(String.class, String.class, String.class, 
String.class, Integer.class, String.class)
+                       .logOnExit("TestCache5")
+                       .supplier((k1, k2, k3, k4, k5) -> k1 + ":" + k2 + ":" + 
k3 + ":" + k4 + ":" + k5)
+                       .build();
+               x.get("en", "US", "west", "formal", 1);
+               assertSize(1, x);
+       }
+
+       @Test
+       void e02_logOnExit_withBoolean() {
+               var x = Cache5.of(String.class, String.class, String.class, 
String.class, Integer.class, String.class)
+                       .logOnExit(true, "MyCache5")
+                       .supplier((k1, k2, k3, k4, k5) -> k1 + ":" + k2 + ":" + 
k3 + ":" + k4 + ":" + k5)
+                       .build();
+               x.get("en", "US", "west", "formal", 1);
+               assertSize(1, x);
+       }
 }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache_Test.java
index 007742587d..e23e63eaf2 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/common/collections/Cache_Test.java
@@ -620,6 +620,8 @@ class Cache_Test extends TestBase {
                cache.put("key1", "value1");
                var previous = cache.put("key1", null);
                assertEquals("value1", previous);
+               // Null values are not cached, so key should be removed
+               assertFalse(cache.containsKey("key1"), "Key should be removed 
when null value is put");
                // Null values are not cached, so get() will call supplier
                var callCount = new AtomicInteger();
                var result = cache.get("key1", () -> {
@@ -628,6 +630,17 @@ class Cache_Test extends TestBase {
                });
                assertEquals("supplied", result);
                assertEquals(1, callCount.get());
+               // After get() with non-null supplier, key is now in cache again
+               assertTrue(cache.containsKey("key1"), "Key should be in cache 
after get() with non-null supplier");
+       }
+
+       @Test void a35b_put_withNullValue_newKey() {
+               var cache = Cache.of(String.class, String.class).build();
+               // Putting null for a new key should return null and not add 
anything
+               var previous = cache.put("key1", null);
+               assertNull(previous);
+               assertFalse(cache.containsKey("key1"));
+               assertTrue(cache.isEmpty());
        }
 
        
//====================================================================================================
@@ -705,6 +718,84 @@ class Cache_Test extends TestBase {
                assertTrue(cache.containsKey(null));
        }
 
+       
//====================================================================================================
+       // remove() method
+       
//====================================================================================================
+
+       @Test void a46_remove_existingKey() {
+               var cache = Cache.of(String.class, String.class).build();
+               cache.put("key1", "value1");
+               var removed = cache.remove("key1");
+               assertEquals("value1", removed);
+               assertFalse(cache.containsKey("key1"));
+               assertTrue(cache.isEmpty());
+       }
+
+       @Test void a47_remove_nonExistentKey() {
+               var cache = Cache.of(String.class, String.class).build();
+               var removed = cache.remove("key1");
+               assertNull(removed);
+       }
+
+       @Test void a48_remove_afterGet() {
+               var cache = Cache.of(String.class, String.class).build();
+               cache.get("key1", () -> "value1");
+               var removed = cache.remove("key1");
+               assertEquals("value1", removed);
+               assertFalse(cache.containsKey("key1"));
+       }
+
+       @Test void a49_remove_nullKey() {
+               var cache = Cache.of(String.class, String.class).build();
+               cache.put(null, "value1");
+               var removed = cache.remove(null);
+               assertEquals("value1", removed);
+               assertFalse(cache.containsKey(null));
+       }
+
+       
//====================================================================================================
+       // containsValue() method
+       
//====================================================================================================
+
+       @Test void a50_containsValue_present() {
+               var cache = Cache.of(String.class, String.class).build();
+               cache.put("key1", "value1");
+               cache.put("key2", "value2");
+               assertTrue(cache.containsValue("value1"));
+               assertTrue(cache.containsValue("value2"));
+               assertFalse(cache.containsValue("value3"));
+       }
+
+       @Test void a51_containsValue_notPresent() {
+               var cache = Cache.of(String.class, String.class).build();
+               assertFalse(cache.containsValue("value1"));
+       }
+
+       @Test void a52_containsValue_afterRemove() {
+               var cache = Cache.of(String.class, String.class).build();
+               cache.put("key1", "value1");
+               assertTrue(cache.containsValue("value1"));
+               cache.remove("key1");
+               assertFalse(cache.containsValue("value1"));
+       }
+
+       @Test void a53_containsValue_afterClear() {
+               var cache = Cache.of(String.class, String.class).build();
+               cache.put("key1", "value1");
+               cache.put("key2", "value2");
+               assertTrue(cache.containsValue("value1"));
+               cache.clear();
+               assertFalse(cache.containsValue("value1"));
+               assertFalse(cache.containsValue("value2"));
+       }
+
+       @Test void a54_containsValue_nullValue() {
+               var cache = Cache.of(String.class, String.class).build();
+               // Null values can't be cached, so containsValue(null) should 
return false
+               cache.get("key1", () -> null);
+               assertFalse(cache.containsValue(null));
+       }
+
        
//====================================================================================================
        // Array key support
        
//====================================================================================================
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/common/collections/SimpleMap_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/common/collections/SimpleMap_Test.java
index 6b8d7193af..148beb06f3 100755
--- 
a/juneau-utest/src/test/java/org/apache/juneau/common/collections/SimpleMap_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/common/collections/SimpleMap_Test.java
@@ -163,11 +163,66 @@ class SimpleMap_Test extends TestBase {
                assertEquals("value4", map.get("key3"));
        }
 
+       
//====================================================================================================
+       // Array length mismatch
+       
//====================================================================================================
+       @Test
+       void c01_arrayLengthMismatch_keysLonger() {
+               var keys = a("key1", "key2", "key3");
+               var values = a("value1", "value2");
+
+               IllegalArgumentException ex = 
assertThrows(IllegalArgumentException.class, () -> {
+                       new SimpleMap<>(keys, values);
+               });
+
+               assertTrue(ex.getMessage().contains("array lengths differ"));
+               assertTrue(ex.getMessage().contains("3")); // keys length
+               assertTrue(ex.getMessage().contains("2")); // values length
+       }
+
+       @Test
+       void c02_arrayLengthMismatch_valuesLonger() {
+               var keys = a("key1", "key2");
+               var values = a("value1", "value2", "value3", "value4");
+
+               IllegalArgumentException ex = 
assertThrows(IllegalArgumentException.class, () -> {
+                       new SimpleMap<>(keys, values);
+               });
+
+               assertTrue(ex.getMessage().contains("array lengths differ"));
+               assertTrue(ex.getMessage().contains("2")); // keys length
+               assertTrue(ex.getMessage().contains("4")); // values length
+       }
+
+       @Test
+       void c03_arrayLengthMismatch_emptyKeys() {
+               var keys = new String[0];
+               var values = a("value1");
+
+               IllegalArgumentException ex = 
assertThrows(IllegalArgumentException.class, () -> {
+                       new SimpleMap<>(keys, values);
+               });
+
+               assertTrue(ex.getMessage().contains("array lengths differ"));
+       }
+
+       @Test
+       void c04_arrayLengthMismatch_emptyValues() {
+               var keys = a("key1", "key2");
+               var values = new String[0];
+
+               IllegalArgumentException ex = 
assertThrows(IllegalArgumentException.class, () -> {
+                       new SimpleMap<>(keys, values);
+               });
+
+               assertTrue(ex.getMessage().contains("array lengths differ"));
+       }
+
        
//====================================================================================================
        // Edge cases
        
//====================================================================================================
        @Test
-       void c01_emptyMap_noNullKeys() {
+       void c05_emptyMap_noNullKeys() {
                String[] keys = {};
                String[] values = {};
 
@@ -179,7 +234,7 @@ class SimpleMap_Test extends TestBase {
        }
 
        @Test
-       void c02_getLookup_nullKeyNotInMap() {
+       void c06_getLookup_nullKeyNotInMap() {
                String[] keys = { "key1", "key2" };
                String[] values = { "value1", "value2" };
 
@@ -190,7 +245,7 @@ class SimpleMap_Test extends TestBase {
        }
 
        @Test
-       void c03_putOperation_cannotAddNewNullKey() {
+       void c07_putOperation_cannotAddNewNullKey() {
                String[] keys = { "key1" };
                String[] values = { "value1" };
 
@@ -204,7 +259,7 @@ class SimpleMap_Test extends TestBase {
        }
 
        @Test
-       void c04_complexTypes_nullKey() {
+       void c08_complexTypes_nullKey() {
                var keys = a(1, null, 3);
                var values = a("one", "null-key", "three");
 

Reply via email to