From 841b386226f278fdde17deb219d022cd7f9bfee7 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 28 Sep 2023 14:36:04 +0200 Subject: [PATCH] Kotlin conversion: MultiMap Warning: This commit changes the semantics of MultiMap.put() put() now replaces values, while plus() adds them. --- .../java/org/pgpainless/util/MultiMap.java | 137 ------------------ .../PublicKeyRingSelectionStrategy.java | 7 +- .../SecretKeyRingSelectionStrategy.java | 7 +- .../kotlin/org/pgpainless/util/MultiMap.kt | 75 ++++++++++ .../org/pgpainless/util/MultiMapTest.java | 84 +++++++---- .../keyring/KeyRingsFromCollectionTest.java | 14 +- 6 files changed, 147 insertions(+), 177 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/util/MultiMap.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/util/MultiMap.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/MultiMap.java b/pgpainless-core/src/main/java/org/pgpainless/util/MultiMap.java deleted file mode 100644 index 3b342778..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/util/MultiMap.java +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.util; - -import javax.annotation.Nonnull; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -public class MultiMap { - - private final Map> map; - - public MultiMap() { - map = new HashMap<>(); - } - - public MultiMap(@Nonnull MultiMap other) { - this.map = new HashMap<>(); - for (K k : other.map.keySet()) { - map.put(k, new LinkedHashSet<>(other.map.get(k))); - } - } - - public MultiMap(@Nonnull Map> content) { - this.map = new HashMap<>(content); - } - - public int size() { - return map.size(); - } - - public boolean isEmpty() { - return map.isEmpty(); - } - - public boolean containsKey(K o) { - return map.containsKey(o); - } - - public boolean containsValue(V o) { - for (Set values : map.values()) { - if (values.contains(o)) return true; - } - return false; - } - - public Set get(K o) { - return map.get(o); - } - - public void put(K k, V v) { - Set values = map.get(k); - if (values == null) { - values = new LinkedHashSet<>(); - map.put(k, values); - } - values.add(v); - } - - public void put(K k, Set vs) { - for (V v : vs) { - put(k, v); - } - } - - public void removeAll(K o) { - map.remove(o); - } - - public void remove(K o, V v) { - Set vs = map.get(o); - if (vs == null) return; - vs.remove(v); - } - - public void putAll(MultiMap other) { - for (K key : other.keySet()) { - put(key, other.get(key)); - } - } - - public void clear() { - map.clear(); - } - - public Set keySet() { - return map.keySet(); - } - - public Collection> values() { - return map.values(); - } - - public Set>> entrySet() { - return map.entrySet(); - } - - /** - * Return all values of the {@link MultiMap} in a single {@link LinkedHashSet}. - * - * @return set of all values - */ - public Set flatten() { - LinkedHashSet flattened = new LinkedHashSet<>(); - for (Set items : map.values()) { - flattened.addAll(items); - } - return flattened; - } - - @Override - public boolean equals(Object o) { - if (o == null) { - return false; - } - - if (!(o instanceof MultiMap)) { - return false; - } - - if (this == o) { - return true; - } - - return map.equals(((MultiMap) o).map); - } - - @Override - public int hashCode() { - return map.hashCode(); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/PublicKeyRingSelectionStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/PublicKeyRingSelectionStrategy.java index 68c9d946..7dbf7c93 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/PublicKeyRingSelectionStrategy.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/PublicKeyRingSelectionStrategy.java @@ -7,6 +7,7 @@ package org.pgpainless.util.selection.keyring; import javax.annotation.Nonnull; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.Set; import org.bouncycastle.openpgp.PGPPublicKeyRing; @@ -33,9 +34,9 @@ public abstract class PublicKeyRingSelectionStrategy implements KeyRingSelect @Override public MultiMap selectKeyRingsFromCollections(@Nonnull MultiMap keyRingCollections) { MultiMap keyRings = new MultiMap<>(); - for (O identifier : keyRingCollections.keySet()) { - for (PGPPublicKeyRingCollection collection : keyRingCollections.get(identifier)) { - keyRings.put(identifier, selectKeyRingsFromCollection(identifier, collection)); + for (Map.Entry> entry : keyRingCollections.entrySet()) { + for (PGPPublicKeyRingCollection collection : entry.getValue()) { + keyRings.plus(entry.getKey(), selectKeyRingsFromCollection(entry.getKey(), collection)); } } return keyRings; diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/SecretKeyRingSelectionStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/SecretKeyRingSelectionStrategy.java index ac5e8065..9e57b575 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/SecretKeyRingSelectionStrategy.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/selection/keyring/SecretKeyRingSelectionStrategy.java @@ -6,6 +6,7 @@ package org.pgpainless.util.selection.keyring; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.Set; import javax.annotation.Nonnull; @@ -32,9 +33,9 @@ public abstract class SecretKeyRingSelectionStrategy implements KeyRingSelect @Override public MultiMap selectKeyRingsFromCollections(@Nonnull MultiMap keyRingCollections) { MultiMap keyRings = new MultiMap<>(); - for (O identifier : keyRingCollections.keySet()) { - for (PGPSecretKeyRingCollection collection : keyRingCollections.get(identifier)) { - keyRings.put(identifier, selectKeyRingsFromCollection(identifier, collection)); + for (Map.Entry> entry : keyRingCollections.entrySet()) { + for (PGPSecretKeyRingCollection collection : entry.getValue()) { + keyRings.plus(entry.getKey(), selectKeyRingsFromCollection(entry.getKey(), collection)); } } return keyRings; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/util/MultiMap.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/util/MultiMap.kt new file mode 100644 index 00000000..9ca193a6 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/util/MultiMap.kt @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.util + +class MultiMap : Iterable>> { + + private val map: Map> + + constructor(): this(mutableMapOf()) + constructor(other: MultiMap): this(other.map) + constructor(content: Map>) { + map = mutableMapOf() + content.forEach { + map[it.key] = it.value.toMutableSet() + } + } + + override fun iterator(): Iterator>> { + return map.iterator() + } + + val size: Int + get() = map.size + fun size() = size + val keys: Set + get() = map.keys + fun keySet() = keys + val values: Collection> + get() = map.values + fun values() = values + val entries: Set>> + get() = map.entries + fun entrySet() = entries + fun isEmpty(): Boolean = map.isEmpty() + fun containsKey(key: K): Boolean = map.containsKey(key) + fun containsValue(value: V): Boolean = map.values.any { it.contains(value) } + fun contains(key: K, value: V): Boolean = map[key]?.contains(value) ?: false + operator fun get(key: K): Set? = map[key] + fun put(key: K, value: V) = + (map as MutableMap).put(key, mutableSetOf(value)) + fun plus(key: K, value: V) = + (map as MutableMap).getOrPut(key) { mutableSetOf() }.add(value) + fun put(key: K, values: Set) = + (map as MutableMap).put(key, values.toMutableSet()) + fun plus(key: K, values: Set) = + (map as MutableMap).getOrPut(key) { mutableSetOf() }.addAll(values) + fun putAll(other: MultiMap) = other.map.entries.forEach { + put(it.key, it.value) + } + fun plusAll(other: MultiMap) = other.map.entries.forEach { + plus(it.key, it.value) + } + fun removeAll(key: K) = (map as MutableMap).remove(key) + fun remove(key: K, value: V) = (map as MutableMap)[key]?.remove(value) + fun clear() = (map as MutableMap).clear() + fun flatten() = map.flatMap { it.value }.toSet() + + override fun equals(other: Any?): Boolean { + return if (other == null) + false + else if (other !is MultiMap<*, *>) + false + else if (this === other) { + true + } else { + map == other.map + } + } + + override fun hashCode(): Int { + return map.hashCode() + } +} \ No newline at end of file diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/MultiMapTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/MultiMapTest.java index 98688a94..164befb1 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/util/MultiMapTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/util/MultiMapTest.java @@ -41,7 +41,37 @@ public class MultiMapTest { assertTrue(multiMap.containsKey("alice")); assertTrue(multiMap.containsValue("wonderland")); assertNotNull(multiMap.get("alice")); - assertTrue(multiMap.get("alice").contains("wonderland")); + assertTrue(multiMap.contains("alice", "wonderland")); + } + + @Test + public void putOverwritesExistingElements() { + MultiMap map = new MultiMap<>(); + map.put("alice", "wonderland"); + map.put("alice", "whothefrickisalice"); + assertFalse(map.containsValue("wonderland")); + } + + @Test + public void plusDoesNotOverwriteButAdd() { + MultiMap map = new MultiMap<>(); + map.put("alice", "wonderland"); + map.plus("alice", "whothefrickisalice"); + assertTrue(map.containsValue("wonderland")); + assertTrue(map.containsValue("whothefrickisalice")); + } + + @Test + public void containsWorks() { + MultiMap map = new MultiMap<>(); + map.put("alice", "wonderland"); + map.plus("alice", "bar"); + map.put("bob", "builder"); + + assertTrue(map.contains("alice", "wonderland")); + assertTrue(map.contains("alice", "bar")); + assertTrue(map.contains("bob", "builder")); + assertFalse(map.contains("bob", "bar")); } @Test @@ -104,7 +134,7 @@ public class MultiMapTest { @Test public void emptyAfterClear() { MultiMap map = new MultiMap<>(); - map.put("test", "foo"); + map.plus("test", "foo"); assertFalse(map.isEmpty()); map.clear(); assertTrue(map.isEmpty()); @@ -113,8 +143,8 @@ public class MultiMapTest { @Test public void addTwoRemoveOneWorks() { MultiMap map = new MultiMap<>(); - map.put("alice", "wonderland"); - map.put("bob", "builder"); + map.plus("alice", "wonderland"); + map.plus("bob", "builder"); map.removeAll("alice"); assertFalse(map.containsKey("alice")); @@ -125,11 +155,11 @@ public class MultiMapTest { @Test public void addMultiValue() { MultiMap addOneByOne = new MultiMap<>(); - addOneByOne.put("foo", "bar"); - addOneByOne.put("foo", "baz"); + addOneByOne.plus("foo", "bar"); + addOneByOne.plus("foo", "baz"); MultiMap addOnce = new MultiMap<>(); - addOnce.put("foo", new HashSet<>(Arrays.asList("baz", "bar"))); + addOnce.plus("foo", new HashSet<>(Arrays.asList("baz", "bar"))); assertEquals(addOneByOne, addOnce); } @@ -138,7 +168,7 @@ public class MultiMapTest { public void addMultiValueRemoveSingle() { MultiMap map = new MultiMap<>(); map.put("foo", "bar"); - map.put("foo", "baz"); + map.plus("foo", "baz"); map.remove("foo", "bar"); assertFalse(map.isEmpty()); @@ -149,9 +179,9 @@ public class MultiMapTest { @Test public void addMultiValueRemoveAll() { MultiMap map = new MultiMap<>(); - map.put("foo", "bar"); - map.put("foo", "baz"); - map.put("bingo", "bango"); + map.plus("foo", "bar"); + map.plus("foo", "baz"); + map.plus("bingo", "bango"); map.removeAll("foo"); assertFalse(map.isEmpty()); @@ -160,23 +190,23 @@ public class MultiMapTest { } @Test - public void putAll() { + public void plusAll() { MultiMap map = new MultiMap<>(); - map.put("A", "1"); - map.put("A", "2"); - map.put("B", "1"); + map.plus("A", "1"); + map.plus("A", "2"); + map.plus("B", "1"); MultiMap other = new MultiMap<>(); - other.put("A", "1"); - other.put("B", "2"); - other.put("C", "3"); + other.plus("A", "1"); + other.plus("B", "2"); + other.plus("C", "3"); - map.putAll(other); - assertTrue(map.get("A").contains("1")); - assertTrue(map.get("A").contains("2")); - assertTrue(map.get("B").contains("1")); - assertTrue(map.get("B").contains("2")); - assertTrue(map.get("C").contains("3")); + map.plusAll(other); + assertTrue(map.contains("A", "1")); + assertTrue(map.contains("A", "2")); + assertTrue(map.contains("B", "1")); + assertTrue(map.contains("B", "2")); + assertTrue(map.contains("C", "3")); } @Test @@ -188,9 +218,9 @@ public class MultiMapTest { @Test public void flattenMap() { MultiMap map = new MultiMap<>(); - map.put("A", "1"); - map.put("A", "2"); - map.put("B", "1"); + map.plus("A", "1"); + map.plus("A", "2"); + map.plus("B", "1"); Set expected = new LinkedHashSet<>(); expected.add("1"); diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/selection/keyring/KeyRingsFromCollectionTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/selection/keyring/KeyRingsFromCollectionTest.java index bf4955cd..c7f6d722 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/util/selection/keyring/KeyRingsFromCollectionTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/util/selection/keyring/KeyRingsFromCollectionTest.java @@ -5,7 +5,7 @@ package org.pgpainless.util.selection.keyring; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.util.Arrays; @@ -19,8 +19,8 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; import org.junit.jupiter.api.Test; import org.pgpainless.key.TestKeys; -import org.pgpainless.util.selection.keyring.impl.ExactUserId; import org.pgpainless.util.MultiMap; +import org.pgpainless.util.selection.keyring.impl.ExactUserId; public class KeyRingsFromCollectionTest { @@ -52,7 +52,7 @@ public class KeyRingsFromCollectionTest { MultiMap selected = strategy.selectKeyRingsFromCollections(map); assertEquals(1, selected.get(TestKeys.JULIET_UID).size()); assertEquals(1, selected.get(TestKeys.EMIL_UID).size()); - assertNull(selected.get("invalidId")); + assertTrue(selected.get("invalidId").isEmpty()); } @Test @@ -73,16 +73,16 @@ public class KeyRingsFromCollectionTest { PGPPublicKeyRing juliet = TestKeys.getJulietPublicKeyRing(); MultiMap map = new MultiMap<>(); PGPPublicKeyRingCollection julietCollection = new PGPPublicKeyRingCollection(Arrays.asList(emil, juliet)); - map.put(TestKeys.JULIET_UID, julietCollection); + map.plus(TestKeys.JULIET_UID, julietCollection); PGPPublicKeyRingCollection emilCollection = new PGPPublicKeyRingCollection(Collections.singletonList(emil)); - map.put(TestKeys.EMIL_UID, emilCollection); + map.plus(TestKeys.EMIL_UID, emilCollection); assertEquals(2, julietCollection.size()); - map.put("invalidId", emilCollection); + map.plus("invalidId", emilCollection); PublicKeyRingSelectionStrategy strategy = new ExactUserId.PubRingSelectionStrategy(); MultiMap selected = strategy.selectKeyRingsFromCollections(map); assertEquals(1, selected.get(TestKeys.JULIET_UID).size()); assertEquals(1, selected.get(TestKeys.EMIL_UID).size()); - assertNull(selected.get("invalidId")); + assertTrue(selected.get("invalidId").isEmpty()); } }