org.conqat.lib.commons.collections.TwoDimHashMap Maven / Gradle / Ivy
Show all versions of teamscale-lib-commons Show documentation
/*
* Copyright (c) CQSE GmbH
*
* Licensed 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.conqat.lib.commons.collections;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import org.conqat.lib.commons.test.IndexValueClass;
/**
* A 2-dimensional hash map. Allows storage of items identified by two different keys. This can be
* used to store the following data structure:
*
*
* - Project A
*
* - Dan — Testing
* - Flo — Documentation
*
*
* - Project B
*
* - Flo — Design
* - Dan — QA
* - Markus — CM
* - Jorge — Testing
*
*
*
*/
@IndexValueClass
public class TwoDimHashMap implements Serializable {
private static final long serialVersionUID = 1L;
/** The first level map. */
private final Map> data;
/** Create a new doubly hashed map. */
public TwoDimHashMap() {
data = new HashMap<>();
}
/** Create a new two dimensional map using the data in the given map. */
public TwoDimHashMap(Map> map) {
data = map;
}
/** Put all values of another {@link TwoDimHashMap} into this map. */
public void putAll(TwoDimHashMap otherMap) {
for (K1 key1 : otherMap.getFirstKeys()) {
for (K2 key2 : otherMap.getSecondKeys(key1)) {
V value = otherMap.getValue(key1, key2);
putValue(key1, key2, value);
}
}
}
/**
* Puts the given value into this map under the given keys. Any potentially existing value will be
* overwritten.
*
* @param key1
* first level key
* @param key2
* second level key
* @param value
* the value
*/
public void putValue(K1 key1, K2 key2, V value) {
Map map = data.computeIfAbsent(key1, ignored -> new HashMap<>());
map.put(key2, value);
}
/**
* Get a value by specifying first and second level key.
*
* @param firstKey
* first level key
* @param secondKey
* second level key
* @return the value. Is null
if first or second level key does not exist or if
* null
was explicitly stored.
*/
public V getValue(K1 firstKey, K2 secondKey) {
Map map = data.get(firstKey);
if (map == null) {
return null;
}
return map.get(secondKey);
}
/**
* The same as {@link #getValue(Object, Object)} but returns the default value instead of null when
* the map doesn't contain an entry for the keys.
*/
public V getValueOrDefault(K1 firstKey, K2 secondKey, V defaultValue) {
V value = getValue(firstKey, secondKey);
if (value == null) {
return defaultValue;
}
return value;
}
/**
* If the map does not {@link #containsKey(Object, Object)} firstKey secondKey, the result of the
* function get put into the map. Returns the value for the both keys.
*/
public V computeIfAbsent(K1 firstKey, K2 secondKey, BiFunction mappingFunction) {
if (containsKey(firstKey, secondKey)) {
return getValue(firstKey, secondKey);
}
V result = mappingFunction.apply(firstKey, secondKey);
putValue(firstKey, secondKey, result);
return result;
}
/**
* Returns whether the given key combination is available in the map.
*
* @param firstKey
* first level key
* @param secondKey
* second level key
*/
public boolean containsKey(K1 firstKey, K2 secondKey) {
Map map = data.get(firstKey);
if (map == null) {
return false;
}
return map.containsKey(secondKey);
}
/**
* Get all values referenced by a first level key.
*
* @param firstKey
* the first level key
* @return a list of values referenced by the specified first level key
*/
public Collection getValuesByFirstKey(K1 firstKey) {
Map map = data.get(firstKey);
if (map == null) {
return null;
}
return map.values();
}
/**
* Get all first level keys.
*/
public Set getFirstKeys() {
return data.keySet();
}
/**
* Get all the second level keys stored under the given first level key.
*
* @param firstKey
* the first level key.
* @return all second level keys for a first level key.
*/
public Set getSecondKeys(K1 firstKey) {
Map map = data.get(firstKey);
if (map == null) {
return CollectionUtils.emptySet();
}
return map.keySet();
}
/**
* Get all values referenced by a second level key.
*
* Note: This method's complexity is linear in the number of first level keys.
*
* @param secondKey
* the second level key
* @return a new list of values referenced by the specified second level key
*/
public List getValuesBySecondKey(K2 secondKey) {
ArrayList result = new ArrayList<>();
for (Map map : data.values()) {
if (map.containsKey(secondKey)) {
result.add(map.get(secondKey));
}
}
return result;
}
/**
* Get all values stored in the map.
*
* @return a new list of all values.
*/
public List getValues() {
ArrayList result = new ArrayList<>();
for (Map map : data.values()) {
result.addAll(map.values());
}
return result;
}
/**
* Get size of the map.
*
* @return the number of values stored in this map.
*/
public int getSize() {
int size = 0;
for (Map map : data.values()) {
size += map.size();
}
return size;
}
/**
* Check if the map is empty, i.e. no values are stored in it.
*/
public boolean isEmpty() {
return getSize() == 0;
}
/**
* Get the size of the (second) map stored for a first key.
*
* @return the size or 0 if the first level key wasn't found.
*/
public int getSecondSize(K1 key1) {
Map map = data.get(key1);
if (map == null) {
return 0;
}
return map.size();
}
/**
* Clear the whole map.
*/
public void clear() {
data.clear();
}
/**
* Removes the value associated with the given key combination.
*
* @return previous value associated with specified keys, or null
if there was no
* mapping for those keys. A null
return can also indicate that the map
* previously associated null
with the specified keys.
*/
public V remove(K1 key1, K2 key2) {
Map map = data.get(key1);
if (map == null) {
return null;
}
if (!map.containsKey(key2)) {
return null;
}
V result = map.remove(key2);
if (map.isEmpty()) {
data.remove(key1);
}
return result;
}
/**
* Remove all values stored under the given first level key.
*
* @param key
* first level key
* @return true
if the given key was present in the map, false
otherwise.
*/
public boolean remove(K1 key) {
Map result = data.remove(key);
return result != null;
}
/**
* Returns the data stored under the given first-level key as an unmodifiable map or
* null
if nothing is stored under that key.
*/
public UnmodifiableMap getSecondMap(K1 key) {
Map secondMap = data.get(key);
if (secondMap == null) {
return null;
}
return CollectionUtils.asUnmodifiable(secondMap);
}
}