From 1c5d3664c737b082e22b3416898da878fec15cf1 Mon Sep 17 00:00:00 2001 From: Ziver Koc Date: Thu, 24 Sep 2020 00:12:39 +0200 Subject: [PATCH] Implemented a Map that can store multiple values per key. --- src/zutil/struct/MultiMap.java | 189 ++++++++++++++++++++++++++ test/zutil/struct/MultiMapTest.java | 198 ++++++++++++++++++++++++++++ 2 files changed, 387 insertions(+) create mode 100644 src/zutil/struct/MultiMap.java create mode 100644 test/zutil/struct/MultiMapTest.java diff --git a/src/zutil/struct/MultiMap.java b/src/zutil/struct/MultiMap.java new file mode 100644 index 0000000..81d7825 --- /dev/null +++ b/src/zutil/struct/MultiMap.java @@ -0,0 +1,189 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ziver Koc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package zutil.struct; + +import java.lang.reflect.InvocationTargetException; +import java.util.*; + +/** + * A Map wrapping object that allows supports for multiple values per key. + * + * @param – the type of keys maintained by this map + * @param – the type of mapped values + */ +public class MultiMap implements Map { + + private Map> internalMap; + + + /** + * Creates a MultiMap + */ + public MultiMap() { + this.internalMap = new HashMap<>(); + } + + /** + * Creates a MultiMap + * + * @param internalMapClass Provide the type of the internal map. + */ + public MultiMap(Class internalMapClass) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { + this.internalMap = internalMapClass.getDeclaredConstructor().newInstance(); + } + + + /** + * @return the number of keys registered in the Map. Note that the number + * of values contained might be more than the value returned here. + */ + @Override + public int size() { + return internalMap.size(); + } + + @Override + public boolean isEmpty() { + return internalMap.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return internalMap.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + for (K key : internalMap.keySet()) { + for (V keyValue : internalMap.get(key)){ + if (Objects.equals(value, keyValue)) + return true; + } + } + + return false; + } + + + @Override + public V put(K key, V value) { + if (value == null) { + remove(key); + return null; + } + + LinkedList valueList = internalMap.get(key); + + if (valueList == null) { + valueList = new LinkedList(); + internalMap.put(key, valueList); + } + + V prevValue = (valueList.isEmpty() ? null : valueList.getLast()); + valueList.add(value); + + return prevValue; + } + + @Override + public void putAll(Map m) { + if (m instanceof MultiMap) { + for (K key : m.keySet()) { + for (V value : ((MultiMap) m).getAll(key)){ + put(key, value); + } + } + } else { + for (Entry e : m.entrySet()) { + put(e.getKey(), e.getValue()); + } + } + } + + @Override + public V remove(Object key) { + if (key == null) { + return null; + } + + LinkedList valueList = internalMap.remove(key); + + if (valueList != null) + return valueList.getFirst(); + else + return null; + } + + @Override + public void clear() { + internalMap.clear(); + } + + + @Override + public V get(Object key) { + LinkedList valueList = internalMap.get(key); + + if (valueList != null) + return valueList.getLast(); + else + return null; + } + + /** + * @return a list of all values associated with the specified key. A empty list will be returned if the key does not exist. + */ + public List getAll(Object key) { + LinkedList valueList = internalMap.get(key); + + if (valueList != null) + return Collections.unmodifiableList(valueList); + else + return Collections.emptyList(); + } + + @Override + public Set keySet() { + return internalMap.keySet(); + } + + @Override + public Collection values() { + LinkedList list = new LinkedList<>(); + + for(List valueList : internalMap.values()) { + list.addAll(valueList); + } + + return list; + } + + @Override + public Set> entrySet() { + throw new UnsupportedOperationException(); + } + + +} diff --git a/test/zutil/struct/MultiMapTest.java b/test/zutil/struct/MultiMapTest.java new file mode 100644 index 0000000..5e1047b --- /dev/null +++ b/test/zutil/struct/MultiMapTest.java @@ -0,0 +1,198 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2020 Ziver Koc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +package zutil.struct; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; + +public class MultiMapTest { + + @Test + public void size() { + MultiMap map = new MultiMap<>(); + assertEquals(0, map.size()); + + map.put("test", "test"); + assertEquals(1, map.size()); + + map.put("test", "test"); + assertEquals(1, map.size()); + } + + @Test + public void isEmpty() { + MultiMap map = new MultiMap<>(); + assertTrue(map.isEmpty()); + + map.put("test", "test"); + assertFalse(map.isEmpty()); + } + + @Test + public void containsKey() { + MultiMap map = new MultiMap<>(); + assertEquals(false, map.containsKey("test")); + + map.put("test", "test"); + assertEquals(true, map.containsKey("test")); + + map.put("test", "test2"); + assertEquals(true, map.containsKey("test")); + + map.remove("test"); + assertEquals(false, map.containsKey("test")); + } + + @Test + public void containsValue() { + MultiMap map = new MultiMap<>(); + assertEquals(false, map.containsValue("value")); + assertEquals(false, map.containsValue("value2")); + + map.put("test", "value"); + assertEquals(true, map.containsValue("value")); + assertEquals(false, map.containsValue("value2")); + + map.put("test", "value2"); + assertEquals(true, map.containsValue("value2")); + } + + @Test + public void put() { + MultiMap map = new MultiMap<>(); + assertNull(map.put("test", "value")); + assertEquals("value", map.put("test", "value2")); + } + + @Test + public void putAll() { + Map sourceMap = new HashMap<>(); + sourceMap.put("test", "value"); + sourceMap.put("test2", "value2"); + + MultiMap map = new MultiMap<>(); + map.putAll(sourceMap); + + assertEquals("value", map.get("test")); + assertEquals("value2", map.get("test2")); + + MultiMap sourceMap2 = new MultiMap<>(); + sourceMap2.put("test", "value"); + sourceMap2.put("test2", "value2"); + sourceMap2.put("test2", "value3"); + + MultiMap map2 = new MultiMap<>(); + map2.putAll(sourceMap2); + + assertEquals("value", map2.get("test")); + assertEquals(Arrays.asList("value"), map2.getAll("test")); + assertEquals(Arrays.asList("value2", "value3"), map2.getAll("test2")); + } + + @Test + public void remove() { + MultiMap map = new MultiMap<>(); + assertEquals(null, map.remove("test")); + + map.put("test", "value"); + assertEquals("value", map.remove("test")); + assertEquals(null, map.get("test")); + + map.put("test", "value"); + map.put("test", "value2"); + assertEquals("value", map.remove("test")); + assertEquals(null, map.get("test")); + } + + @Test + public void clear() { + MultiMap map = new MultiMap<>(); + map.put("test", "value"); + map.put("test", "value2"); + map.put("test3", "value3"); + + assertEquals(false, map.isEmpty()); + + map.clear(); + + assertEquals(true, map.isEmpty()); + } + + @Test + public void get() { + MultiMap map = new MultiMap<>(); + assertEquals(null, map.get("test")); + + assertEquals(null, map.put("test", "value")); + assertEquals("value", map.get("test")); + + assertEquals("value", map.put("test", "value2")); + assertEquals("value2", map.get("test")); + } + + @Test + public void getAll() { + MultiMap map = new MultiMap<>(); + assertEquals(Arrays.asList(), map.getAll("test")); + + map.put("test", "value"); + assertEquals(Arrays.asList("value"), map.getAll("test")); + + map.put("test", "value2"); + assertEquals(Arrays.asList("value", "value2"), map.getAll("test")); + } + + @Test + public void keySet() { + MultiMap map = new MultiMap<>(); + assertEquals(true, map.keySet().isEmpty()); + + map.put("test", "value"); + assertEquals(1, map.keySet().size()); + assertEquals(true, map.keySet().contains("test")); + + map.put("test", "value2"); + assertEquals(1, map.keySet().size()); + assertEquals(true, map.keySet().contains("test")); + } + + @Test + public void values() { + MultiMap map = new MultiMap<>(); + assertEquals(Arrays.asList(), map.values()); + + map.put("test", "value"); + assertEquals(Arrays.asList("value"), map.values()); + + map.put("test", "value2"); + assertEquals(Arrays.asList("value", "value2"), map.values()); + } +} \ No newline at end of file