CAUSEWAY-4052: provides proper equality for MultiMap implementations
diff --git a/commons/src/main/java/org/apache/causeway/commons/internal/collections/_Multimaps.java b/commons/src/main/java/org/apache/causeway/commons/internal/collections/_Multimaps.java
index 56d6ab0..d50593c 100644
--- a/commons/src/main/java/org/apache/causeway/commons/internal/collections/_Multimaps.java
+++ b/commons/src/main/java/org/apache/causeway/commons/internal/collections/_Multimaps.java
@@ -156,6 +156,19 @@ public Optional<NavigableMap<K, List<V>>> asNavigableMap() {
             public ListMultimap<K, V> asUnmodifiable() {
                 return new ListMultimapWrapper<>(Collections.unmodifiableMap(delegate), elementCollectionFactory);
             }
+            @Override
+            public final boolean equals(final Object obj) {
+            	return obj == this
+            			|| delegate.equals(obj);
+            }
+            @Override
+            public final int hashCode() {
+	            return delegate.hashCode();
+            }
+            @Override
+            public final String toString() {
+            	return "ListMultimapWrapper[delegate=%s]".formatted(delegate);
+            }
     }
 
     /**
@@ -219,17 +232,28 @@ private record SetMultimapWrapper<K, V>(
             public void putElement(final K key, final V value) {
                 getOrElseNew(key).add(value);
             }
-
             @Override
             public Set<V> getOrElseNew(final K key) {
                 var collection = delegate.computeIfAbsent(key, __->elementCollectionFactory.get());
                 return collection;
             }
-
             @Override
             public SetMultimap<K, V> asUnmodifiable() {
                 return new SetMultimapWrapper<>(Collections.unmodifiableMap(delegate), elementCollectionFactory);
             }
+            @Override
+            public final boolean equals(final Object obj) {
+            	return obj == this
+            			|| delegate.equals(obj);
+            }
+            @Override
+            public final int hashCode() {
+	            return delegate.hashCode();
+            }
+            @Override
+            public final String toString() {
+            	return "SetMultimapWrapper[delegate=%s]".formatted(delegate);
+            }
     }
 
     /**
@@ -324,19 +348,29 @@ public static <K1, K2, V> MapMultimap<K1, K2, V> newMapMultimap(
             public void putElement(final K1 key, final K2 subkey, final V value) {
                 getOrElseNew(key).put(subkey, value);
             }
-
             @Override
             public V getElement(final K1 key, final K2 subkey) {
                 final Map<K2, V> elementMap = delegate.get(key);
                 return elementMap!=null ? elementMap.get(subkey) : null;
             }
-
             @Override
             public Map<K2, V> getOrElseNew(final K1 key) {
                 var elementMap = delegate.computeIfAbsent(key, __->elementMapFactory.get());
                 return elementMap;
             }
-
+            @Override
+            public final boolean equals(final Object obj) {
+            	return obj == this
+            			|| delegate.equals(obj);
+            }
+            @Override
+            public final int hashCode() {
+	            return delegate.hashCode();
+            }
+            @Override
+            public final String toString() {
+            	return "MapMultimap[delegate=%s]".formatted(delegate);
+            }
         };
     }