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 5ea2ae8df8 Marshall module improvements
5ea2ae8df8 is described below

commit 5ea2ae8df8771c51c6c4c8fa204a2a7bfcab0e88
Author: James Bognar <[email protected]>
AuthorDate: Sat Dec 13 09:58:27 2025 -0500

    Marshall module improvements
---
 .../java/org/apache/juneau/bean/ResultSetList.java |   2 +-
 .../apache/juneau/commons/collections/BidiMap.java |  51 ++++
 .../juneau/commons/collections/KeywordSet.java     |  46 +++
 .../juneau/commons/collections/MultiList.java      |  23 +-
 .../juneau/commons/collections/MultiMap.java       |   8 +-
 .../juneau/commons/collections/MultiSet.java       |  10 +-
 .../juneau/commons/collections/ReversedList.java   |  77 +++++
 .../juneau/commons/collections/SimpleMap.java      | 324 ---------------------
 .../commons/collections/SimpleUnmodifiableMap.java |  76 ++++-
 .../org/apache/juneau/rest/util/UrlPathMatch.java  |   2 +-
 .../juneau/commons/collections/BidiMap_Test.java   | 102 +++++++
 .../commons/collections/KeywordSet_Test.java       | 108 +++++++
 .../commons/collections/ReversedList_Test.java     |  83 ++++++
 .../juneau/commons/collections/SimpleMap_Test.java | 312 --------------------
 .../collections/SimpleUnmodifiableMap_Test.java    | 107 +++++++
 15 files changed, 666 insertions(+), 665 deletions(-)

diff --git 
a/juneau-bean/juneau-bean-common/src/main/java/org/apache/juneau/bean/ResultSetList.java
 
b/juneau-bean/juneau-bean-common/src/main/java/org/apache/juneau/bean/ResultSetList.java
index c3700b8ea7..5b18309805 100644
--- 
a/juneau-bean/juneau-bean-common/src/main/java/org/apache/juneau/bean/ResultSetList.java
+++ 
b/juneau-bean/juneau-bean-common/src/main/java/org/apache/juneau/bean/ResultSetList.java
@@ -107,7 +107,7 @@ public class ResultSetList extends 
LinkedList<Map<String,Object>> {
                                        var o = readEntry(rs, i + 1, 
colTypes[i]);
                                        row[i + offset] = o;
                                }
-                               add(new SimpleMap<>(columns, row));
+                               add(new SimpleUnmodifiableMap<>(columns, row));
                        }
                } finally {
                        rs.close();
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/BidiMap.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/BidiMap.java
index 526bae8437..bb84397ef7 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/BidiMap.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/BidiMap.java
@@ -370,4 +370,55 @@ public class BidiMap<K,V> implements Map<K,V> {
        public Collection<V> values() {
                return forward.values();
        }
+
+       /**
+        * Returns a string representation of this map.
+        *
+        * <p>
+        * The format follows the standard Java map convention: 
<c>"{key1=value1, key2=value2, ...}"</c>
+        *
+        * @return A string representation of this map.
+        */
+       @Override
+       public String toString() {
+               return forward.toString();
+       }
+
+       /**
+        * Compares the specified object with this map for equality.
+        *
+        * <p>
+        * Returns <jk>true</jk> if the given object is also a map and the two 
maps represent the same
+        * mappings. More formally, two maps <c>m1</c> and <c>m2</c> represent 
the same mappings if
+        * <c>m1.entrySet().equals(m2.entrySet())</c>.
+        *
+        * <p>
+        * This implementation compares the entry sets of the two maps.
+        *
+        * @param o Object to be compared for equality with this map.
+        * @return <jk>true</jk> if the specified object is equal to this map.
+        */
+       @Override
+       public boolean equals(Object o) {
+               return (o instanceof Map o2) && eq(this, o2, (x, y) -> 
x.entrySet().equals(y.entrySet()));
+       }
+
+       /**
+        * Returns the hash code value for this map.
+        *
+        * <p>
+        * The hash code of a map is defined to be the sum of the hash codes of 
each entry in the map's
+        * <c>entrySet()</c> view. This ensures that <c>m1.equals(m2)</c> 
implies that
+        * <c>m1.hashCode()==m2.hashCode()</c> for any two maps <c>m1</c> and 
<c>m2</c>, as required
+        * by the general contract of {@link Object#hashCode()}.
+        *
+        * <p>
+        * This implementation computes the hash code from the entry set.
+        *
+        * @return The hash code value for this map.
+        */
+       @Override
+       public int hashCode() {
+               return entrySet().hashCode();
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/KeywordSet.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/KeywordSet.java
index 0d5848a30e..31ebf21f7d 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/KeywordSet.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/KeywordSet.java
@@ -16,6 +16,8 @@
  */
 package org.apache.juneau.commons.collections;
 
+import static org.apache.juneau.commons.utils.Utils.*;
+
 import java.util.*;
 
 /**
@@ -169,4 +171,48 @@ public class KeywordSet {
                        return false;
                return Arrays.binarySearch(store, s) >= 0;
        }
+
+       /**
+        * Returns a string representation of this keyword set.
+        *
+        * <p>
+        * The format follows the standard Java set convention: <c>"[keyword1, 
keyword2, ...]"</c>
+        *
+        * @return A string representation of this keyword set.
+        */
+       @Override
+       public String toString() {
+               return Arrays.toString(store);
+       }
+
+       /**
+        * Compares the specified object with this keyword set for equality.
+        *
+        * <p>
+        * Returns <jk>true</jk> if the given object is also a keyword set and 
contains the same keywords
+        * in the same order. Two keyword sets are equal if their internal 
arrays are equal.
+        *
+        * @param o Object to be compared for equality with this keyword set.
+        * @return <jk>true</jk> if the specified object is equal to this 
keyword set.
+        */
+       @Override
+       public boolean equals(Object o) {
+               return (o instanceof KeywordSet o2) && eq(this, o2, (x, y) -> 
Arrays.equals(x.store, y.store));
+       }
+
+       /**
+        * Returns the hash code value for this keyword set.
+        *
+        * <p>
+        * The hash code is computed from the internal array of keywords using 
{@link Arrays#hashCode(Object[])}.
+        * This ensures that <c>ks1.equals(ks2)</c> implies that 
<c>ks1.hashCode()==ks2.hashCode()</c>
+        * for any two keyword sets <c>ks1</c> and <c>ks2</c>, as required by 
the general contract of
+        * {@link Object#hashCode()}.
+        *
+        * @return The hash code value for this keyword set.
+        */
+       @Override
+       public int hashCode() {
+               return hash((Object[])store);
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/MultiList.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/MultiList.java
index 75145cee25..dcf571d20e 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/MultiList.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/MultiList.java
@@ -17,6 +17,7 @@
 package org.apache.juneau.commons.collections;
 
 import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
 
 import java.util.*;
 import java.util.stream.Collectors;
@@ -500,19 +501,19 @@ public class MultiList<E> extends AbstractList<E> {
         */
        @Override
        public boolean equals(Object o) {
-               if (o == this)
-                       return true;
                if (!(o instanceof List))
                        return false;
-               var e1 = listIterator();
-               var e2 = ((List<?>)o).listIterator();
-               while (e1.hasNext() && e2.hasNext()) {
-                       var o1 = e1.next();
-                       var o2 = e2.next();
-                       if (!(o1 == null ? o2 == null : o1.equals(o2)))
-                               return false;
-               }
-               return !(e1.hasNext() || e2.hasNext());
+               return eq(this, (List<?>)o, (x, y) -> {
+                       var e1 = x.listIterator();
+                       var e2 = y.listIterator();
+                       while (e1.hasNext() && e2.hasNext()) {
+                               var o1 = e1.next();
+                               var o2 = e2.next();
+                               if (!eq(o1, o2))
+                                       return false;
+                       }
+                       return !(e1.hasNext() || e2.hasNext());
+               });
        }
 
        /**
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/MultiMap.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/MultiMap.java
index 5dc821d5b5..cb523c2bb0 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/MultiMap.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/MultiMap.java
@@ -20,6 +20,8 @@ import static 
org.apache.juneau.commons.utils.AssertionUtils.*;
 import static org.apache.juneau.commons.utils.ThrowableUtils.*;
 
 import java.util.*;
+
+import org.apache.juneau.commons.utils.Utils;
 import java.util.stream.Collectors;
 
 /**
@@ -416,11 +418,7 @@ public class MultiMap<K,V> extends AbstractMap<K,V> {
         */
        @Override
        public boolean equals(Object o) {
-               if (o == this)
-                       return true;
-               if (!(o instanceof Map))
-                       return false;
-               return entrySet().equals(((Map<?,?>)o).entrySet());
+               return (o instanceof Map o2) && Utils.eq(this, o2, (x, y) -> 
x.entrySet().equals(y.entrySet()));
        }
 
        /**
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/MultiSet.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/MultiSet.java
index 7d7faf2867..7a0c5d4a9f 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/MultiSet.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/MultiSet.java
@@ -17,6 +17,7 @@
 package org.apache.juneau.commons.collections;
 
 import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
 
 import java.util.*;
 import java.util.stream.Collectors;
@@ -304,14 +305,7 @@ public class MultiSet<E> extends AbstractSet<E> {
         */
        @Override
        public boolean equals(Object o) {
-               if (o == this)
-                       return true;
-               if (!(o instanceof Set))
-                       return false;
-               var s = (Set<?>)o;
-               if (s.size() != size())
-                       return false;
-               return containsAll(s);
+               return (o instanceof Set o2) && eq(this, o2, (x,y) -> 
eq(x.size(), y.size()) && x.containsAll(y));
        }
 
        /**
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/ReversedList.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/ReversedList.java
index 03a54cedc9..9edc6edbde 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/ReversedList.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/ReversedList.java
@@ -17,8 +17,10 @@
 package org.apache.juneau.commons.collections;
 
 import static org.apache.juneau.commons.utils.AssertionUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
 
 import java.util.*;
+import java.util.stream.Collectors;
 
 /**
  * A reversed view of a list that does not modify the underlying list.
@@ -279,4 +281,79 @@ public class ReversedList<E> extends AbstractList<E> 
implements RandomAccess {
 
                return new ReversedList<>(list.subList(translatedFrom, 
translatedTo));
        }
+
+       /**
+        * Returns a string representation of this reversed list.
+        *
+        * <p>
+        * The format follows the standard Java list convention: <c>"[element1, 
element2, ...]"</c>
+        * Elements are shown in reversed order (as they appear in this view).
+        *
+        * @return A string representation of this reversed list.
+        */
+       @Override
+       public String toString() {
+               return 
stream().map(Object::toString).collect(Collectors.joining(", ", "[", "]"));
+       }
+
+       /**
+        * Compares the specified object with this list for equality.
+        *
+        * <p>
+        * Returns <jk>true</jk> if and only if the specified object is also a 
list, both lists have the
+        * same size, and all corresponding pairs of elements in the two lists 
are <i>equal</i>. In other
+        * words, two lists are defined to be equal if they contain the same 
elements in the same order.
+        *
+        * <p>
+        * This implementation compares elements in the reversed order as they 
appear in this view.
+        *
+        * @param o The object to be compared for equality with this list.
+        * @return <jk>true</jk> if the specified object is equal to this list.
+        */
+       @Override
+       public boolean equals(Object o) {
+               if (!(o instanceof List))
+                       return false;
+               return eq(this, (List<?>)o, (x, y) -> {
+                       var e1 = x.listIterator();
+                       var e2 = y.listIterator();
+                       while (e1.hasNext() && e2.hasNext()) {
+                               var o1 = e1.next();
+                               var o2 = e2.next();
+                               if (!eq(o1, o2))
+                                       return false;
+                       }
+                       return !(e1.hasNext() || e2.hasNext());
+               });
+       }
+
+       /**
+        * Returns the hash code value for this list.
+        *
+        * <p>
+        * The hash code of a list is defined to be the result of the following 
calculation:
+        * <p class='bcode w800'>
+        *      <jk>int</jk> hashCode = 1;
+        *      <jk>for</jk> (E e : list)
+        *              hashCode = 31 * hashCode + (e == <jk>null</jk> ? 0 : 
e.hashCode());
+        * </p>
+        *
+        * <p>
+        * This ensures that <c>list1.equals(list2)</c> implies that
+        * <c>list1.hashCode()==list2.hashCode()</c> for any two lists 
<c>list1</c> and <c>list2</c>,
+        * as required by the general contract of {@link Object#hashCode()}.
+        *
+        * <p>
+        * This implementation computes the hash code from the elements in 
reversed order as they appear
+        * in this view.
+        *
+        * @return The hash code value for this list.
+        */
+       @Override
+       public int hashCode() {
+               int hashCode = 1;
+               for (E e : this)
+                       hashCode = 31 * hashCode + (e == null ? 0 : 
e.hashCode());
+               return hashCode;
+       }
 }
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/SimpleMap.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/SimpleMap.java
deleted file mode 100644
index 276f9ed296..0000000000
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/SimpleMap.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * 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.commons.collections;
-
-import static org.apache.juneau.commons.utils.AssertionUtils.*;
-import static org.apache.juneau.commons.utils.CollectionUtils.*;
-import static org.apache.juneau.commons.utils.ThrowableUtils.*;
-import static org.apache.juneau.commons.utils.Utils.*;
-
-import java.lang.reflect.*;
-import java.util.*;
-
-/**
- * A lightweight, fixed-size map implementation backed by parallel key and 
value arrays.
- *
- * <p>
- * This class provides a simple, efficient map implementation for scenarios 
where the set of keys
- * is known in advance and doesn't need to change. It's particularly useful 
for small maps (typically
- * less than 10 entries) where the overhead of a {@link HashMap} is not 
justified.
- *
- * <h5 class='section'>Features:</h5>
- * <ul class='spaced-list'>
- *     <li><b>Fixed Keys:</b> Keys are set at construction time and cannot be 
added or removed
- *     <li><b>Mutable Values:</b> Values can be updated via {@link 
#put(Object, Object)} or {@link java.util.Map.Entry#setValue(Object)}
- *     <li><b>Array-Backed:</b> Uses simple arrays for storage, avoiding hash 
computation overhead
- *     <li><b>Memory Efficient:</b> Minimal memory footprint compared to 
{@link HashMap}
- *     <li><b>Predictable Performance:</b> O(n) lookup time, but faster than 
{@link HashMap} for small n
- * </ul>
- *
- * <h5 class='section'>Use Cases:</h5>
- * <ul class='spaced-list'>
- *     <li>Configuration maps with a fixed set of known keys
- *     <li>Small lookup tables (e.g., enum-to-value mappings)
- *     <li>Temporary maps for method parameter passing
- *     <li>Situations where map size is small and keys are known at compile 
time
- * </ul>
- *
- * <h5 class='section'>Usage:</h5>
- * <p class='bjava'>
- *     <jc>// Create a SimpleMap with fixed keys</jc>
- *     String[] <jv>keys</jv> = {<js>"host"</js>, <js>"port"</js>, 
<js>"timeout"</js>};
- *     Object[] <jv>values</jv> = {<js>"localhost"</js>, 8080, 30000};
- *
- *     SimpleMap&lt;String,Object&gt; <jv>config</jv> = <jk>new</jk> 
SimpleMap&lt;&gt;(<jv>keys</jv>, <jv>values</jv>);
- *
- *     <jc>// Get values</jc>
- *     String <jv>host</jv> = (String)<jv>config</jv>.get(<js>"host"</js>);    
 <jc>// Returns: "localhost"</jc>
- *     Integer <jv>port</jv> = (Integer)<jv>config</jv>.get(<js>"port"</js>);  
 <jc>// Returns: 8080</jc>
- *
- *     <jc>// Update values</jc>
- *     <jv>config</jv>.put(<js>"port"</js>, 9090);  <jc>// Updates value in 
underlying array</jc>
- *
- *     <jc>// Cannot add new keys</jc>
- *     <jv>config</jv>.put(<js>"user"</js>, <js>"admin"</js>);  <jc>// Throws 
IllegalArgumentException</jc>
- * </p>
- *
- * <h5 class='section'>Restrictions:</h5>
- * <ul class='spaced-list'>
- *     <li><b>Fixed Size:</b> Cannot add or remove entries after construction
- *     <li><b>Unique Keys:</b> Keys must be unique (no duplicates)
- *     <li><b>Array Length:</b> Keys and values arrays must have the same 
length
- *     <li><b>Existing Keys Only:</b> {@link #put(Object, Object)} only works 
for existing keys
- * </ul>
- *
- * <h5 class='section'>Performance Characteristics:</h5>
- * <ul class='spaced-list'>
- *     <li><b>get(key):</b> O(n) - Linear search through keys array
- *     <li><b>put(key, value):</b> O(n) - Linear search to find key, then 
update
- *     <li><b>size():</b> O(1) - Direct array length access
- *     <li><b>Memory:</b> Lower overhead than {@link HashMap} for small maps
- * </ul>
- *
- * <h5 class='section'>Thread Safety:</h5>
- * <p>
- * This class is not thread-safe. If multiple threads access a SimpleMap 
concurrently, and at least
- * one thread modifies the map structurally (via {@link #put(Object, 
Object)}), it must be
- * synchronized externally.
- *
- * <h5 class='section'>Example - Enum Configuration:</h5>
- * <p class='bjava'>
- *     <jc>// Map configuration keys to default values</jc>
- *     <jk>enum</jk> ConfigKey { HOST, PORT, TIMEOUT }
- *
- *     ConfigKey[] <jv>keys</jv> = ConfigKey.values();
- *     Object[] <jv>defaults</jv> = {<js>"localhost"</js>, 8080, 5000};
- *
- *     SimpleMap&lt;ConfigKey,Object&gt; <jv>configMap</jv> = <jk>new</jk> 
SimpleMap&lt;&gt;(<jv>keys</jv>, <jv>defaults</jv>);
- *
- *     <jc>// Update from user settings</jc>
- *     <jv>configMap</jv>.put(ConfigKey.PORT, userSettings.getPort());
- * </p>
- *
- * <h5 class='section'>See Also:</h5>
- * <ul>
- *     <li class='link'><a class="doclink" 
href="../../../../../index.html#juneau-commons">Overview &gt; juneau-commons</a>
- * </ul>
- *
- * @param <K> The key type.
- * @param <V> The value type.
- */
-public class SimpleMap<K,V> extends AbstractMap<K,V> {
-
-       /**
-        * Inner class representing a single key-value entry in this map.
-        * <p>
-        * This entry is backed by the underlying key and value arrays, so 
changes
-        * made via {@link #setValue(Object)} directly modify the underlying 
value array.
-        */
-       class SimpleMapEntry implements Map.Entry<K,V> {
-
-               /** The index into the keys/values arrays for this entry. */
-               private final int index;
-
-               /**
-                * Constructor.
-                * @param index The array index for this entry.
-                */
-               SimpleMapEntry(int index) {
-                       this.index = index;
-               }
-
-               @Override /* Map.Entry */
-               public K getKey() { return keys[index]; }
-
-               @Override /* Map.Entry */
-               public V getValue() { return values[index]; }
-
-               @Override /* Map.Entry */
-               public V setValue(V val) {
-                       var v = values[index];
-                       values[index] = val;
-                       return v;
-               }
-       }
-
-       /** The array of keys. Keys are immutable after construction. */
-       final K[] keys;
-
-       /** The array of values. Values can be modified via {@link #put(Object, 
Object)}. */
-       final V[] values;
-
-       /** Pre-constructed entries array for {@link #entrySet()}. */
-       final SimpleMapEntry[] entries;
-
-       /**
-        * Constructs a new SimpleMap with the specified keys and values.
-        *
-        * <p>
-        * The keys and values arrays are stored by reference (not copied), so 
external
-        * modifications to the arrays after construction will be reflected in 
the map's behavior.
-        * However, the keys array should be treated as immutable after 
construction.
-        *
-        * <h5 class='section'>Example:</h5>
-        * <p class='bjava'>
-        *      String[] <jv>keys</jv> = {<js>"name"</js>, <js>"age"</js>, 
<js>"city"</js>};
-        *      Object[] <jv>values</jv> = {<js>"John"</js>, 30, 
<js>"NYC"</js>};
-        *
-        *      SimpleMap&lt;String,Object&gt; <jv>person</jv> = <jk>new</jk> 
SimpleMap&lt;&gt;(<jv>keys</jv>, <jv>values</jv>);
-        * </p>
-        *
-        * @param keys The map keys. Must not be <jk>null</jk>. Individual keys 
can be <jk>null</jk>.
-        * @param values The map values. Must not be <jk>null</jk> and must 
have the same length as keys.
-        *               Individual values can be <jk>null</jk>.
-        * @throws IllegalArgumentException if:
-        *         <ul>
-        *              <li>The keys array is <jk>null</jk>
-        *              <li>The values array is <jk>null</jk>
-        *              <li>The keys and values arrays have different lengths
-        *              <li>Any key appears more than once (duplicate keys)
-        *         </ul>
-        */
-       @SuppressWarnings("unchecked")
-       public SimpleMap(K[] keys, V[] values) {
-               assertArgsNotNull("keys", keys, "values", values);
-               assertArg(keys.length == values.length, "keys ''{0}'' and 
values ''{1}'' array lengths differ", keys.length, values.length);
-
-               // Check for duplicate keys
-               for (var i = 0; i < keys.length; i++) {
-                       for (var j = i + 1; j < keys.length; j++) {
-                               assertArg(ne(keys[i], keys[j]), "Duplicate key 
found: {0}", keys[i]);
-                       }
-               }
-
-               this.keys = keys;
-               this.values = values;
-               entries = 
(SimpleMapEntry[])Array.newInstance(SimpleMapEntry.class, keys.length);
-               for (var i = 0; i < keys.length; i++) {
-                       entries[i] = new SimpleMapEntry(i);
-               }
-       }
-
-       /**
-        * Returns a {@link Set} view of the mappings contained in this map.
-        *
-        * <p>
-        * The returned set is backed by the underlying entries array, so 
changes to the entry
-        * values via {@link java.util.Map.Entry#setValue(Object)} will be 
reflected in the map.
-        *
-        * <h5 class='section'>Example:</h5>
-        * <p class='bjava'>
-        *      SimpleMap&lt;String,Integer&gt; <jv>map</jv> = <jk>new</jk> 
SimpleMap&lt;&gt;(
-        *              <jk>new</jk> String[]{<js>"a"</js>, <js>"b"</js>},
-        *              <jk>new</jk> Integer[]{1, 2}
-        *      );
-        *
-        *      <jk>for</jk> (Map.Entry&lt;String,Integer&gt; <jv>entry</jv> : 
<jv>map</jv>.entrySet()) {
-        *              <jv>entry</jv>.setValue(<jv>entry</jv>.getValue() * 
10);  <jc>// Modifies underlying array</jc>
-        *      }
-        * </p>
-        *
-        * @return A set view of the mappings in this map.
-        */
-       @Override /* Map */
-       public Set<Map.Entry<K,V>> entrySet() {
-               return toSet(entries);
-       }
-
-       /**
-        * Returns the value associated with the specified key.
-        *
-        * <p>
-        * This method performs a linear search through the keys array, using 
{@link Object#equals(Object)}
-        * for comparison. For small maps (typically less than 10 entries), 
this is often faster than
-        * the hash lookup in {@link HashMap}.
-        *
-        * <h5 class='section'>Example:</h5>
-        * <p class='bjava'>
-        *      SimpleMap&lt;String,Integer&gt; <jv>map</jv> = <jk>new</jk> 
SimpleMap&lt;&gt;(
-        *              <jk>new</jk> String[]{<js>"age"</js>, <js>"score"</js>},
-        *              <jk>new</jk> Integer[]{25, 100}
-        *      );
-        *
-        *      Integer <jv>age</jv> = <jv>map</jv>.get(<js>"age"</js>);     
<jc>// Returns: 25</jc>
-        *      Integer <jv>height</jv> = <jv>map</jv>.get(<js>"height"</js>); 
<jc>// Returns: null (key not found)</jc>
-        * </p>
-        *
-        * @param key The key whose associated value is to be returned.
-        * @return The value associated with the specified key, or 
<jk>null</jk> if the key is not found
-        *         (or if the key is mapped to <jk>null</jk>).
-        */
-       @Override /* Map */
-       public V get(Object key) {
-               for (var i = 0; i < keys.length; i++)
-                       if (eq(keys[i], key))
-                               return values[i];
-               return null;
-       }
-
-       /**
-        * Returns a {@link Set} view of the keys contained in this map.
-        *
-        * <p>
-        * The returned set is backed by the underlying keys array. Since the 
keys are immutable
-        * after construction, this set is effectively read-only.
-        *
-        * <h5 class='section'>Example:</h5>
-        * <p class='bjava'>
-        *      SimpleMap&lt;String,Integer&gt; <jv>map</jv> = <jk>new</jk> 
SimpleMap&lt;&gt;(
-        *              <jk>new</jk> String[]{<js>"x"</js>, <js>"y"</js>, 
<js>"z"</js>},
-        *              <jk>new</jk> Integer[]{1, 2, 3}
-        *      );
-        *
-        *      Set&lt;String&gt; <jv>keys</jv> = <jv>map</jv>.keySet();  
<jc>// Returns: [x, y, z]</jc>
-        * </p>
-        *
-        * @return A set view of the keys in this map.
-        */
-       @Override /* Map */
-       public Set<K> keySet() {
-               return toSet(keys);
-       }
-
-       /**
-        * Associates the specified value with the specified key in this map.
-        *
-        * <p>
-        * This method can only update values for existing keys. It cannot add 
new keys to the map.
-        * If the specified key doesn't exist in the map, an {@link 
IllegalArgumentException} is thrown.
-        *
-        * <p>
-        * The value is updated in the underlying values array.
-        *
-        * <h5 class='section'>Example:</h5>
-        * <p class='bjava'>
-        *      SimpleMap&lt;String,Integer&gt; <jv>map</jv> = <jk>new</jk> 
SimpleMap&lt;&gt;(
-        *              <jk>new</jk> String[]{<js>"count"</js>, 
<js>"limit"</js>},
-        *              <jk>new</jk> Integer[]{0, 100}
-        *      );
-        *
-        *      <jv>map</jv>.put(<js>"count"</js>, 5);       <jc>// Returns: 0 
(previous value)</jc>
-        *      <jv>map</jv>.put(<js>"newKey"</js>, 10);     <jc>// Throws 
IllegalArgumentException</jc>
-        * </p>
-        *
-        * @param key The key whose value is to be updated. Must be an existing 
key in this map.
-        * @param value The new value to associate with the key.
-        * @return The previous value associated with the specified key.
-        * @throws IllegalArgumentException if the specified key doesn't exist 
in this map.
-        */
-       @Override /* Map */
-       public V put(K key, V value) {
-               for (var i = 0; i < keys.length; i++) {
-                       if (eq(keys[i], key)) {
-                               var v = values[i];
-                               values[i] = value;
-                               return v;
-                       }
-               }
-               throw illegalArg("No key ''{0}'' defined in map", key);
-       }
-}
\ No newline at end of file
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/SimpleUnmodifiableMap.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/SimpleUnmodifiableMap.java
index 637d27deba..72cbe62e07 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/SimpleUnmodifiableMap.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/SimpleUnmodifiableMap.java
@@ -19,10 +19,12 @@ package org.apache.juneau.commons.collections;
 import static org.apache.juneau.commons.utils.AssertionUtils.*;
 import static org.apache.juneau.commons.utils.CollectionUtils.*;
 import static org.apache.juneau.commons.utils.ThrowableUtils.*;
-import static org.apache.juneau.commons.utils.Utils.*;
 
 import java.lang.reflect.*;
 import java.util.*;
+import java.util.stream.Collectors;
+
+import org.apache.juneau.commons.utils.Utils;
 
 /**
  * An unmodifiable, fixed-size map implementation backed by parallel key and 
value arrays.
@@ -147,6 +149,23 @@ public class SimpleUnmodifiableMap<K,V> extends 
AbstractMap<K,V> {
                public V setValue(V val) {
                        throw unsupportedOp("Map is unmodifiable");
                }
+
+               @Override
+               public String toString() {
+                       return getKey() + "=" + getValue();
+               }
+
+               @Override
+               public boolean equals(Object o) {
+                       return (o instanceof Map.Entry<?,?> o2) && 
Utils.eq(this, o2, (x,y) -> Utils.eq(x.getKey(), y.getKey()) && 
Utils.eq(x.getValue(), y.getValue()));
+               }
+
+               @Override
+               public int hashCode() {
+                       K key = getKey();
+                       V value = getValue();
+                       return (key == null ? 0 : key.hashCode()) ^ (value == 
null ? 0 : value.hashCode());
+               }
        }
 
        /** The array of keys. Keys are immutable after construction. */
@@ -194,7 +213,7 @@ public class SimpleUnmodifiableMap<K,V> extends 
AbstractMap<K,V> {
                // Check for duplicate keys
                for (var i = 0; i < keys.length; i++) {
                        for (var j = i + 1; j < keys.length; j++) {
-                               if (eq(keys[i], keys[j])) {
+                               if (Utils.eq(keys[i], keys[j])) {
                                        throw illegalArg("Duplicate key found: 
{0}", keys[i]);
                                }
                        }
@@ -259,7 +278,7 @@ public class SimpleUnmodifiableMap<K,V> extends 
AbstractMap<K,V> {
        @Override /* Map */
        public V get(Object key) {
                for (var i = 0; i < keys.length; i++)
-                       if (eq(keys[i], key))
+                       if (Utils.eq(keys[i], key))
                                return values[i];
                return null;
        }
@@ -299,4 +318,55 @@ public class SimpleUnmodifiableMap<K,V> extends 
AbstractMap<K,V> {
        public V put(K key, V value) {
                throw unsupportedOp("Map is unmodifiable");
        }
+
+       /**
+        * Returns a string representation of this map.
+        *
+        * <p>
+        * The format follows the standard Java map convention: 
<c>"{key1=value1, key2=value2, ...}"</c>
+        *
+        * @return A string representation of this map.
+        */
+       @Override
+       public String toString() {
+               return 
entrySet().stream().map(Object::toString).collect(Collectors.joining(", ", "{", 
"}"));
+       }
+
+       /**
+        * Compares the specified object with this map for equality.
+        *
+        * <p>
+        * Returns <jk>true</jk> if the given object is also a map and the two 
maps represent the same
+        * mappings. More formally, two maps <c>m1</c> and <c>m2</c> represent 
the same mappings if
+        * <c>m1.entrySet().equals(m2.entrySet())</c>.
+        *
+        * <p>
+        * This implementation compares the entry sets of the two maps.
+        *
+        * @param o Object to be compared for equality with this map.
+        * @return <jk>true</jk> if the specified object is equal to this map.
+        */
+       @Override
+       public boolean equals(Object o) {
+               return (o instanceof Map o2) && Utils.eq(this, o2, (x, y) -> 
x.entrySet().equals(y.entrySet()));
+       }
+
+       /**
+        * Returns the hash code value for this map.
+        *
+        * <p>
+        * The hash code of a map is defined to be the sum of the hash codes of 
each entry in the map's
+        * <c>entrySet()</c> view. This ensures that <c>m1.equals(m2)</c> 
implies that
+        * <c>m1.hashCode()==m2.hashCode()</c> for any two maps <c>m1</c> and 
<c>m2</c>, as required
+        * by the general contract of {@link Object#hashCode()}.
+        *
+        * <p>
+        * This implementation computes the hash code from the entry set.
+        *
+        * @return The hash code value for this map.
+        */
+       @Override
+       public int hashCode() {
+               return entrySet().hashCode();
+       }
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/UrlPathMatch.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/UrlPathMatch.java
index 14bb810a8a..bb17c70c56 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/UrlPathMatch.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/util/UrlPathMatch.java
@@ -49,7 +49,7 @@ public class UrlPathMatch {
        protected UrlPathMatch(String path, int matchedParts, String[] keys, 
String[] values) {
                this.path = path;
                this.matchedParts = matchedParts;
-               this.vars = keys == null ? mape() : new SimpleMap<>(keys, 
values);
+               this.vars = keys == null ? mape() : new 
SimpleUnmodifiableMap<>(keys, values);
        }
 
        /**
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/BidiMap_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/BidiMap_Test.java
index 246dbe113c..dade9ee646 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/BidiMap_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/BidiMap_Test.java
@@ -580,4 +580,106 @@ class BidiMap_Test extends TestBase {
                map.remove("one");
                assertTrue(map.isEmpty());
        }
+
+       
//====================================================================================================
+       // toString(), equals(), hashCode()
+       
//====================================================================================================
+
+       @Test
+       void w01_toString_delegatesToForwardMap() {
+               var map = BidiMap.<String,Integer>create()
+                       .add("one", 1)
+                       .add("two", 2)
+                       .build();
+
+               var forwardMap = new LinkedHashMap<String, Integer>();
+               forwardMap.put("one", 1);
+               forwardMap.put("two", 2);
+
+               assertEquals(forwardMap.toString(), map.toString());
+       }
+
+       @Test
+       void w02_equals_sameContents() {
+               var map1 = BidiMap.<String,Integer>create()
+                       .add("one", 1)
+                       .add("two", 2)
+                       .build();
+
+               var map2 = BidiMap.<String,Integer>create()
+                       .add("one", 1)
+                       .add("two", 2)
+                       .build();
+
+               assertTrue(map1.equals(map2));
+               assertTrue(map2.equals(map1));
+       }
+
+       @Test
+       void w03_equals_differentContents() {
+               var map1 = BidiMap.<String,Integer>create()
+                       .add("one", 1)
+                       .build();
+
+               var map2 = BidiMap.<String,Integer>create()
+                       .add("one", 2)
+                       .build();
+
+               assertFalse(map1.equals(map2));
+               assertFalse(map2.equals(map1));
+       }
+
+       @Test
+       void w04_equals_regularMap() {
+               var map = BidiMap.<String,Integer>create()
+                       .add("one", 1)
+                       .add("two", 2)
+                       .build();
+
+               var regularMap = new LinkedHashMap<String, Integer>();
+               regularMap.put("one", 1);
+               regularMap.put("two", 2);
+
+               assertTrue(map.equals(regularMap));
+               assertTrue(regularMap.equals(map));
+       }
+
+       @Test
+       void w05_equals_notAMap() {
+               var map = BidiMap.<String,Integer>create()
+                       .add("one", 1)
+                       .build();
+
+               assertFalse(map.equals("not a map"));
+               assertFalse(map.equals(null));
+       }
+
+       @Test
+       void w06_hashCode_sameContents() {
+               var map1 = BidiMap.<String,Integer>create()
+                       .add("one", 1)
+                       .add("two", 2)
+                       .build();
+
+               var map2 = BidiMap.<String,Integer>create()
+                       .add("one", 1)
+                       .add("two", 2)
+                       .build();
+
+               assertEquals(map1.hashCode(), map2.hashCode());
+       }
+
+       @Test
+       void w07_hashCode_regularMap() {
+               var map = BidiMap.<String,Integer>create()
+                       .add("one", 1)
+                       .add("two", 2)
+                       .build();
+
+               var regularMap = new LinkedHashMap<String, Integer>();
+               regularMap.put("one", 1);
+               regularMap.put("two", 2);
+
+               assertEquals(map.hashCode(), regularMap.hashCode());
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/KeywordSet_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/KeywordSet_Test.java
index 81c5a9d58f..a23f342655 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/KeywordSet_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/KeywordSet_Test.java
@@ -41,5 +41,113 @@ class KeywordSet_Test extends TestBase {
                assertFalse(ks.contains("Aa"));
                assertFalse(ks.contains("aA"));
        }
+
+       
//====================================================================================================
+       // toString(), equals(), hashCode()
+       
//====================================================================================================
+
+       @Test
+       void w01_toString_standardFormat() {
+               var ks = new KeywordSet("apple", "banana", "cherry");
+               var result = ks.toString();
+
+               // Should be in sorted order: [apple, banana, cherry]
+               assertTrue(result.startsWith("["));
+               assertTrue(result.endsWith("]"));
+               assertTrue(result.contains("apple"));
+               assertTrue(result.contains("banana"));
+               assertTrue(result.contains("cherry"));
+       }
+
+       @Test
+       void w02_toString_emptySet() {
+               var ks = new KeywordSet();
+               assertEquals("[]", ks.toString());
+       }
+
+       @Test
+       void w03_toString_singleKeyword() {
+               var ks = new KeywordSet("test");
+               assertEquals("[test]", ks.toString());
+       }
+
+       @Test
+       void w04_equals_sameContents() {
+               var ks1 = new KeywordSet("apple", "banana", "cherry");
+               var ks2 = new KeywordSet("apple", "banana", "cherry");
+
+               assertTrue(ks1.equals(ks2));
+               assertTrue(ks2.equals(ks1));
+       }
+
+       @Test
+       void w05_equals_differentOrder() {
+               var ks1 = new KeywordSet("apple", "banana", "cherry");
+               var ks2 = new KeywordSet("cherry", "apple", "banana");
+
+               // Should be equal because both are sorted internally
+               assertTrue(ks1.equals(ks2));
+               assertTrue(ks2.equals(ks1));
+       }
+
+       @Test
+       void w06_equals_differentContents() {
+               var ks1 = new KeywordSet("apple", "banana");
+               var ks2 = new KeywordSet("apple", "cherry");
+
+               assertFalse(ks1.equals(ks2));
+               assertFalse(ks2.equals(ks1));
+       }
+
+       @Test
+       void w07_equals_differentSizes() {
+               var ks1 = new KeywordSet("apple", "banana");
+               var ks2 = new KeywordSet("apple", "banana", "cherry");
+
+               assertFalse(ks1.equals(ks2));
+               assertFalse(ks2.equals(ks1));
+       }
+
+       @Test
+       void w08_equals_notAKeywordSet() {
+               var ks = new KeywordSet("apple", "banana");
+
+               assertFalse(ks.equals("not a keyword set"));
+               assertFalse(ks.equals(null));
+       }
+
+       @Test
+       void w09_equals_emptySets() {
+               var ks1 = new KeywordSet();
+               var ks2 = new KeywordSet();
+
+               assertTrue(ks1.equals(ks2));
+               assertTrue(ks2.equals(ks1));
+       }
+
+       @Test
+       void w10_hashCode_sameContents() {
+               var ks1 = new KeywordSet("apple", "banana", "cherry");
+               var ks2 = new KeywordSet("apple", "banana", "cherry");
+
+               assertEquals(ks1.hashCode(), ks2.hashCode());
+       }
+
+       @Test
+       void w11_hashCode_differentOrder() {
+               var ks1 = new KeywordSet("apple", "banana", "cherry");
+               var ks2 = new KeywordSet("cherry", "apple", "banana");
+
+               // Should have same hash code because both are sorted internally
+               assertEquals(ks1.hashCode(), ks2.hashCode());
+       }
+
+       @Test
+       void w12_hashCode_emptySets() {
+               var ks1 = new KeywordSet();
+               var ks2 = new KeywordSet();
+
+               assertEquals(ks1.hashCode(), ks2.hashCode());
+       }
 }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/ReversedList_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/ReversedList_Test.java
index 46c57c08e9..7b83ea7cde 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/ReversedList_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/ReversedList_Test.java
@@ -509,5 +509,88 @@ class ReversedList_Test extends TestBase {
                assertFalse(it.hasNext());
                assertFalse(it.hasPrevious());
        }
+
+       
//====================================================================================================
+       // toString(), equals(), hashCode()
+       
//====================================================================================================
+
+       @Test
+       void k01_toString_showsReversedOrder() {
+               var original = new ArrayList<>(List.of("a", "b", "c"));
+               var reversed = new ReversedList<>(original);
+
+               // ReversedList.toString() should show the reversed order
+               // The underlying list is ["a", "b", "c"], so reversed should 
show ["c", "b", "a"]
+               var expected = "[c, b, a]";
+               assertEquals(expected, reversed.toString());
+       }
+
+       @Test
+       void k02_equals_sameContents() {
+               var original1 = new ArrayList<>(List.of("a", "b", "c"));
+               var reversed1 = new ReversedList<>(original1);
+
+               var original2 = new ArrayList<>(List.of("a", "b", "c"));
+               var reversed2 = new ReversedList<>(original2);
+
+               // ReversedList.equals() compares in reversed order
+               assertTrue(reversed1.equals(reversed2));
+               assertTrue(reversed2.equals(reversed1));
+       }
+
+       @Test
+       void k03_equals_differentContents() {
+               var original1 = new ArrayList<>(List.of("a", "b", "c"));
+               var reversed1 = new ReversedList<>(original1);
+
+               var original2 = new ArrayList<>(List.of("a", "b", "d"));
+               var reversed2 = new ReversedList<>(original2);
+
+               assertFalse(reversed1.equals(reversed2));
+               assertFalse(reversed2.equals(reversed1));
+       }
+
+       @Test
+       void k04_equals_regularList() {
+               var original = new ArrayList<>(List.of("a", "b", "c"));
+               var reversed = new ReversedList<>(original);
+
+               // A reversed list ["c", "b", "a"] should equal a regular list 
["c", "b", "a"]
+               var regularList = new ArrayList<>(List.of("c", "b", "a"));
+
+               assertTrue(reversed.equals(regularList));
+               assertTrue(regularList.equals(reversed));
+       }
+
+       @Test
+       void k05_equals_notAList() {
+               var original = new ArrayList<>(List.of("a", "b", "c"));
+               var reversed = new ReversedList<>(original);
+
+               assertFalse(reversed.equals("not a list"));
+               assertFalse(reversed.equals(null));
+       }
+
+       @Test
+       void k06_hashCode_sameContents() {
+               var original1 = new ArrayList<>(List.of("a", "b", "c"));
+               var reversed1 = new ReversedList<>(original1);
+
+               var original2 = new ArrayList<>(List.of("a", "b", "c"));
+               var reversed2 = new ReversedList<>(original2);
+
+               assertEquals(reversed1.hashCode(), reversed2.hashCode());
+       }
+
+       @Test
+       void k07_hashCode_regularList() {
+               var original = new ArrayList<>(List.of("a", "b", "c"));
+               var reversed = new ReversedList<>(original);
+
+               // A reversed list ["c", "b", "a"] should have same hash as a 
regular list ["c", "b", "a"]
+               var regularList = new ArrayList<>(List.of("c", "b", "a"));
+
+               assertEquals(reversed.hashCode(), regularList.hashCode());
+       }
 }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/SimpleMap_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/SimpleMap_Test.java
deleted file mode 100755
index 6707b80d34..0000000000
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/SimpleMap_Test.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * 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.commons.collections;
-
-import static org.apache.juneau.TestUtils.assertThrowsWithMessage;
-import static org.apache.juneau.commons.utils.CollectionUtils.*;
-import static org.apache.juneau.junit.bct.BctAssertions.*;
-import static org.junit.jupiter.api.Assertions.*;
-
-import java.util.*;
-
-import org.apache.juneau.*;
-import org.junit.jupiter.api.*;
-
-class SimpleMap_Test extends TestBase {
-
-       
//====================================================================================================
-       // Null key support
-       
//====================================================================================================
-       @Test
-       void a01_nullKey_singleEntry() {
-               var keys = a((String)null);
-               var values = a("value1");
-
-               SimpleMap<String,String> map = new SimpleMap<>(keys, values);
-
-               assertSize(1, map);
-               assertEquals("value1", map.get(null));
-               assertTrue(map.containsKey(null));
-       }
-
-       @Test
-       void a02_nullKey_withOtherKeys() {
-               var keys = a("key1", null, "key3");
-               var values = a("value1", "value2", "value3");
-
-               SimpleMap<String,String> map = new SimpleMap<>(keys, values);
-
-               assertSize(3, map);
-               assertEquals("value1", map.get("key1"));
-               assertEquals("value2", map.get(null));
-               assertEquals("value3", map.get("key3"));
-               assertTrue(map.containsKey(null));
-       }
-
-       @Test
-       void a03_nullKey_updateValue() {
-               var keys = a("key1", null);
-               var values = a("value1", "value2");
-
-               SimpleMap<String,String> map = new SimpleMap<>(keys, values);
-
-               String oldValue = map.put(null, "newValue");
-
-               assertEquals("value2", oldValue);
-               assertEquals("newValue", map.get(null));
-               assertSize(2, map);
-       }
-
-       @Test
-       void a04_nullKey_withNullValue() {
-               var keys = a((String)null);
-               var values = a((String)null);
-
-               SimpleMap<String,String> map = new SimpleMap<>(keys, values);
-
-               assertSize(1, map);
-               assertNull(map.get(null));
-               assertTrue(map.containsKey(null));
-       }
-
-       @Test
-       void a05_nullKey_entrySet() {
-               var keys = a("key1", null, "key3");
-               var values = a("value1", "value2", "value3");
-
-               SimpleMap<String,String> map = new SimpleMap<>(keys, values);
-
-               var foundNullKey = false;
-               for (var entry : map.entrySet()) {
-                       if (entry.getKey() == null) {
-                               foundNullKey = true;
-                               assertEquals("value2", entry.getValue());
-                       }
-               }
-               assertTrue(foundNullKey, "Null key not found in entrySet");
-       }
-
-       @Test
-       void a06_nullKey_keySet() {
-               String[] keys = { "key1", null, "key3" };
-               String[] values = { "value1", "value2", "value3" };
-
-               SimpleMap<String,String> map = new SimpleMap<>(keys, values);
-
-               assertTrue(map.keySet().contains(null), "Null key not found in 
keySet");
-               assertSize(3, map.keySet());
-       }
-
-       
//====================================================================================================
-       // Duplicate key detection
-       
//====================================================================================================
-       @Test
-       void b01_duplicateKey_nonNullKeys() {
-               String[] keys = { "key1", "key2", "key1" };
-               String[] values = { "value1", "value2", "value3" };
-
-               assertThrowsWithMessage(IllegalArgumentException.class, 
"Duplicate key found: key1", () -> {
-                       new SimpleMap<>(keys, values);
-               });
-       }
-
-       @Test
-       void b02_duplicateKey_nullKeys() {
-               String[] keys = { null, "key2", null };
-               String[] values = { "value1", "value2", "value3" };
-
-               assertThrowsWithMessage(IllegalArgumentException.class, 
"Duplicate key found: null", () -> {
-                       new SimpleMap<>(keys, values);
-               });
-       }
-
-       @Test
-       void b03_duplicateKey_mixedNullAndNonNull() {
-               String[] keys = { "key1", null, "key2", "key1" };
-               String[] values = { "value1", "value2", "value3", "value4" };
-
-               assertThrowsWithMessage(IllegalArgumentException.class, 
"Duplicate key found: key1", () -> {
-                       new SimpleMap<>(keys, values);
-               });
-       }
-
-       @Test
-       void b04_noDuplicateKeys_success() {
-               String[] keys = { "key1", null, "key2", "key3" };
-               String[] values = { "value1", "value2", "value3", "value4" };
-
-               SimpleMap<String,String> map = assertDoesNotThrow(() -> new 
SimpleMap<>(keys, values));
-
-               assertSize(4, map);
-               assertEquals("value1", map.get("key1"));
-               assertEquals("value2", map.get(null));
-               assertEquals("value3", map.get("key2"));
-               assertEquals("value4", map.get("key3"));
-       }
-
-       
//====================================================================================================
-       // Array length mismatch
-       
//====================================================================================================
-       @Test
-       void c01_arrayLengthMismatch_keysLonger() {
-               var keys = a("key1", "key2", "key3");
-               var values = a("value1", "value2");
-
-               assertThrowsWithMessage(IllegalArgumentException.class, 
java.util.List.of("array lengths differ", "3", "2"), () -> {
-                       new SimpleMap<>(keys, values);
-               });
-       }
-
-       @Test
-       void c02_arrayLengthMismatch_valuesLonger() {
-               var keys = a("key1", "key2");
-               var values = a("value1", "value2", "value3", "value4");
-
-               assertThrowsWithMessage(IllegalArgumentException.class, 
java.util.List.of("array lengths differ", "2", "4"), () -> {
-                       new SimpleMap<>(keys, values);
-               });
-       }
-
-       @Test
-       void c03_arrayLengthMismatch_emptyKeys() {
-               var keys = new String[0];
-               var values = a("value1");
-
-               assertThrowsWithMessage(IllegalArgumentException.class, "array 
lengths differ", () -> {
-                       new SimpleMap<>(keys, values);
-               });
-       }
-
-       @Test
-       void c04_arrayLengthMismatch_emptyValues() {
-               var keys = a("key1", "key2");
-               var values = new String[0];
-
-               assertThrowsWithMessage(IllegalArgumentException.class, "array 
lengths differ", () -> {
-                       new SimpleMap<>(keys, values);
-               });
-       }
-
-       
//====================================================================================================
-       // Edge cases
-       
//====================================================================================================
-       @Test
-       void c05_emptyMap_noNullKeys() {
-               String[] keys = {};
-               String[] values = {};
-
-               SimpleMap<String,String> map = new SimpleMap<>(keys, values);
-
-               assertEmpty(map);
-               assertNull(map.get(null));
-               assertFalse(map.containsKey(null));
-       }
-
-       @Test
-       void c06_getLookup_nullKeyNotInMap() {
-               String[] keys = { "key1", "key2" };
-               String[] values = { "value1", "value2" };
-
-               SimpleMap<String,String> map = new SimpleMap<>(keys, values);
-
-               assertNull(map.get(null));
-               assertFalse(map.containsKey(null));
-       }
-
-       @Test
-       void c07_putOperation_cannotAddNewNullKey() {
-               String[] keys = { "key1" };
-               String[] values = { "value1" };
-
-               SimpleMap<String,String> map = new SimpleMap<>(keys, values);
-
-               assertThrowsWithMessage(IllegalArgumentException.class, "No key 
'null' defined in map", () -> {
-                       map.put(null, "newValue");
-               });
-       }
-
-       @Test
-       void c08_complexTypes_nullKey() {
-               var keys = a(1, null, 3);
-               var values = a("one", "null-key", "three");
-
-               SimpleMap<Integer,String> map = new SimpleMap<>(keys, values);
-
-               assertEquals("one", map.get(1));
-               assertEquals("null-key", map.get(null));
-               assertEquals("three", map.get(3));
-       }
-
-       
//====================================================================================================
-       // Entry setValue
-       
//====================================================================================================
-
-       @Test
-       void d01_entrySetValue() {
-               String[] keys = { "key1", "key2", "key3" };
-               String[] values = { "value1", "value2", "value3" };
-
-               SimpleMap<String,String> map = new SimpleMap<>(keys, values);
-
-               // Get an entry and modify its value
-               var entry = map.entrySet().iterator().next();
-               String oldValue = entry.setValue("newValue");
-
-               // Verify the value was updated in the map
-               assertEquals("newValue", map.get(entry.getKey()));
-               assertEquals("newValue", entry.getValue());
-               assertTrue(oldValue.equals("value1") || 
oldValue.equals("value2") || oldValue.equals("value3"));
-       }
-
-       @Test
-       void d02_entrySetValue_updatesUnderlyingArray() {
-               var keys = a("key1", "key2");
-               var values = a("value1", "value2");
-
-               var map = new SimpleMap<>(keys, values);
-
-               // Find the entry for "key1"
-               Map.Entry<String,String> entry = null;
-               for (var e : map.entrySet()) {
-                       if ("key1".equals(e.getKey())) {
-                               entry = e;
-                               break;
-                       }
-               }
-
-               assertNotNull(entry);
-               var oldValue = entry.setValue("updated");
-               assertEquals("value1", oldValue);
-               assertEquals("updated", map.get("key1"));
-               assertEquals("updated", entry.getValue());
-       }
-
-       @Test
-       void d03_entrySetValue_nullValue() {
-               String[] keys = { "key1" };
-               String[] values = { "value1" };
-
-               SimpleMap<String,String> map = new SimpleMap<>(keys, values);
-
-               var entry = map.entrySet().iterator().next();
-               String oldValue = entry.setValue(null);
-
-               assertEquals("value1", oldValue);
-               assertNull(map.get("key1"));
-               assertNull(entry.getValue());
-       }
-}
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/SimpleUnmodifiableMap_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/SimpleUnmodifiableMap_Test.java
index a442012175..145ca8d5e2 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/collections/SimpleUnmodifiableMap_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/collections/SimpleUnmodifiableMap_Test.java
@@ -21,6 +21,8 @@ import static 
org.apache.juneau.commons.utils.CollectionUtils.*;
 import static org.apache.juneau.junit.bct.BctAssertions.*;
 import static org.junit.jupiter.api.Assertions.*;
 
+import java.util.*;
+
 import org.apache.juneau.*;
 import org.junit.jupiter.api.*;
 
@@ -308,4 +310,109 @@ class SimpleUnmodifiableMap_Test extends TestBase {
                assertEquals("value2", map.get(null));
                assertEquals("value3", map.get("key3"));
        }
+
+       
//====================================================================================================
+       // toString(), equals(), hashCode()
+       
//====================================================================================================
+
+       @Test
+       void e01_toString_standardFormat() {
+               String[] keys = { "key1", "key2", "key3" };
+               Object[] values = { "value1", "value2", "value3" };
+               SimpleUnmodifiableMap<String,Object> map = new 
SimpleUnmodifiableMap<>(keys, values);
+
+               var result = map.toString();
+               assertTrue(result.startsWith("{"));
+               assertTrue(result.endsWith("}"));
+               assertTrue(result.contains("key1=value1"));
+               assertTrue(result.contains("key2=value2"));
+               assertTrue(result.contains("key3=value3"));
+       }
+
+       @Test
+       void e02_toString_emptyMap() {
+               String[] keys = {};
+               Object[] values = {};
+               SimpleUnmodifiableMap<String,Object> map = new 
SimpleUnmodifiableMap<>(keys, values);
+
+               assertEquals("{}", map.toString());
+       }
+
+       @Test
+       void e03_equals_sameContents() {
+               String[] keys1 = { "key1", "key2" };
+               Object[] values1 = { "value1", "value2" };
+               SimpleUnmodifiableMap<String,Object> map1 = new 
SimpleUnmodifiableMap<>(keys1, values1);
+
+               String[] keys2 = { "key1", "key2" };
+               Object[] values2 = { "value1", "value2" };
+               SimpleUnmodifiableMap<String,Object> map2 = new 
SimpleUnmodifiableMap<>(keys2, values2);
+
+               assertTrue(map1.equals(map2));
+               assertTrue(map2.equals(map1));
+       }
+
+       @Test
+       void e04_equals_differentContents() {
+               String[] keys1 = { "key1", "key2" };
+               Object[] values1 = { "value1", "value2" };
+               SimpleUnmodifiableMap<String,Object> map1 = new 
SimpleUnmodifiableMap<>(keys1, values1);
+
+               String[] keys2 = { "key1", "key2" };
+               Object[] values2 = { "value1", "value3" };
+               SimpleUnmodifiableMap<String,Object> map2 = new 
SimpleUnmodifiableMap<>(keys2, values2);
+
+               assertFalse(map1.equals(map2));
+               assertFalse(map2.equals(map1));
+       }
+
+       @Test
+       void e05_equals_regularMap() {
+               String[] keys = { "key1", "key2" };
+               Object[] values = { "value1", "value2" };
+               SimpleUnmodifiableMap<String,Object> map = new 
SimpleUnmodifiableMap<>(keys, values);
+
+               var regularMap = new LinkedHashMap<String, Object>();
+               regularMap.put("key1", "value1");
+               regularMap.put("key2", "value2");
+
+               assertTrue(map.equals(regularMap));
+               assertTrue(regularMap.equals(map));
+       }
+
+       @Test
+       void e06_equals_notAMap() {
+               String[] keys = { "key1" };
+               Object[] values = { "value1" };
+               SimpleUnmodifiableMap<String,Object> map = new 
SimpleUnmodifiableMap<>(keys, values);
+
+               assertFalse(map.equals("not a map"));
+               assertFalse(map.equals(null));
+       }
+
+       @Test
+       void e07_hashCode_sameContents() {
+               String[] keys1 = { "key1", "key2" };
+               Object[] values1 = { "value1", "value2" };
+               SimpleUnmodifiableMap<String,Object> map1 = new 
SimpleUnmodifiableMap<>(keys1, values1);
+
+               String[] keys2 = { "key1", "key2" };
+               Object[] values2 = { "value1", "value2" };
+               SimpleUnmodifiableMap<String,Object> map2 = new 
SimpleUnmodifiableMap<>(keys2, values2);
+
+               assertEquals(map1.hashCode(), map2.hashCode());
+       }
+
+       @Test
+       void e08_hashCode_regularMap() {
+               String[] keys = { "key1", "key2" };
+               Object[] values = { "value1", "value2" };
+               SimpleUnmodifiableMap<String,Object> map = new 
SimpleUnmodifiableMap<>(keys, values);
+
+               var regularMap = new LinkedHashMap<String, Object>();
+               regularMap.put("key1", "value1");
+               regularMap.put("key2", "value2");
+
+               assertEquals(map.hashCode(), regularMap.hashCode());
+       }
 }

Reply via email to