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<String,Object> <jv>config</jv> = <jk>new</jk>
SimpleMap<>(<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<ConfigKey,Object> <jv>configMap</jv> = <jk>new</jk>
SimpleMap<>(<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 > 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<String,Object> <jv>person</jv> = <jk>new</jk>
SimpleMap<>(<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<String,Integer> <jv>map</jv> = <jk>new</jk>
SimpleMap<>(
- * <jk>new</jk> String[]{<js>"a"</js>, <js>"b"</js>},
- * <jk>new</jk> Integer[]{1, 2}
- * );
- *
- * <jk>for</jk> (Map.Entry<String,Integer> <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<String,Integer> <jv>map</jv> = <jk>new</jk>
SimpleMap<>(
- * <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<String,Integer> <jv>map</jv> = <jk>new</jk>
SimpleMap<>(
- * <jk>new</jk> String[]{<js>"x"</js>, <js>"y"</js>,
<js>"z"</js>},
- * <jk>new</jk> Integer[]{1, 2, 3}
- * );
- *
- * Set<String> <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<String,Integer> <jv>map</jv> = <jk>new</jk>
SimpleMap<>(
- * <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());
+ }
}