org.conqat.lib.commons.collections.CollectionMap 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.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* A collection map deals with a map of collections, i.e. each key can store multiple elements.
* Depending on the collection implementation chosen, this can also be interpreted as a multi map.
*
* If you deal with the basic case of {@link Map}s and {@link List}s, use the {@link ListMap}, which
* is much easier to apply.
*
* @param
* the key type.
* @param
* the value type (i.e. the values stored in the collections).
* @param
* the collection type, which is made explicit at the interface.
*/
public abstract class CollectionMap> implements Serializable, Iterable> {
private static final long serialVersionUID = 1L;
/** The underlying map. */
private final Map map;
/** Constructor. */
public CollectionMap() {
map = createUnderlyingMap();
}
/**
* Create a new instance of the underlying map, called exactly once during the constructor. Can be
* used to prepopulate the list with values if necessary.
*/
protected Map createUnderlyingMap() {
return new HashMap<>();
}
/**
* Returns a new instance of the underlying collection to be used as value for the underlaying map.
*/
protected abstract C createNewCollection();
/**
* Returns the collection stored under the given key (or null). Modifying the collection will
* directly affect this object.
*/
public @Nullable C getCollection(K key) {
return map.get(key);
}
/**
* Returns the collection stored under the given key (or the given default if no collection is
* stored). Modifying the collection will directly affect this object.
*/
public C getCollectionOrElse(K key, C defaultCollection) {
if (map.containsKey(key)) {
return map.get(key);
}
return defaultCollection;
}
/**
* Returns the collection stored under the given key (or an empty collection). If there was a
* collection stored under the key, modifying the collection will directly affect this object.
*/
public C getCollectionOrEmpty(K key) {
C result = getCollection(key);
if (result == null) {
return createNewCollection();
}
return result;
}
/**
* Adds a value to the collection associated with the given key.
*
* @return true
if the collection associated with the given key changed as a result of
* the call.
*/
public boolean add(K key, V value) {
return getOrCreateCollection(key).add(value);
}
/**
* Adds all values to the collection associated with the given key.
*
* @return true
if the collection associated with the given key changed as a result of
* the call.
*/
public boolean addAll(K key, Collection values) {
return getOrCreateCollection(key).addAll(values);
}
/**
* Returns the collection stored under the given key (or creates a new one).
*/
/* package */C getOrCreateCollection(K key) {
C collection = map.get(key);
if (collection == null) {
collection = createNewCollection();
map.put(key, collection);
}
return collection;
}
/** Adds all elements from another collection map. */
public void addAll(CollectionMap other) {
for (K key : other.getKeys()) {
C collection = other.getCollection(key);
if (collection != null) {
addAll(key, collection);
}
}
}
/** Adds all elements from a Java map. */
public void addAll(Map other) {
for (Entry entry : other.entrySet()) {
C collection = entry.getValue();
if (collection != null) {
addAll(entry.getKey(), collection);
}
}
}
/** Returns whether an element is contained. */
public boolean contains(K key, V value) {
C collection = map.get(key);
if (collection == null) {
return false;
}
return collection.contains(value);
}
/**
* See {@link Collection#isEmpty()}.
*
* @return {@code true} if this collection contains no elements.
*/
public boolean isEmpty() {
return map.isEmpty();
}
/**
* Removes an element.
*
* @return true if an element was removed as a result of this call.
*/
public boolean remove(K key, V value) {
C collection = map.get(key);
if (collection == null) {
return false;
}
return collection.remove(value);
}
/**
* Check if a (possibly empty) collection is present for a given key.
*/
public boolean containsCollection(K key) {
return map.containsKey(key);
}
/**
* Removes the collection stored for a key.
*
* @return true if a collection was removed as a result of this call.
*/
public boolean removeCollection(K key) {
return map.remove(key) != null;
}
/** Get the keys. */
public UnmodifiableSet getKeys() {
return CollectionUtils.asUnmodifiable(map.keySet());
}
/** Return all values from all collections. */
public C getValues() {
C result = createNewCollection();
for (C values : map.values()) {
result.addAll(values);
}
return result;
}
/**
* Remove elements that match the filter from each collection.
*/
public void removeIf(Predicate filter) {
for (C collection : map.values()) {
collection.removeIf(filter);
}
}
/**
* see {@link Map#entrySet()}
*/
public Set> entrySet() {
return map.entrySet();
}
/**
* Returns the size of this map, i.e. number of key-value mappings in this map.
*/
public int size() {
return map.size();
}
/** Return the total count of values over all collections. */
public int getValueCount() {
int result = 0;
for (C values : map.values()) {
result += values.size();
}
return result;
}
/** Clears the underlying map and thus all contents. */
public void clear() {
map.clear();
}
/**
* Converts the {@link CollectionMap} to a map with arrays
*
* @param type
* Type of the target array
*/
public Map collectionsToArrays(Class type) {
Map map = new HashMap<>();
for (K key : getKeys()) {
map.put(key, CollectionUtils.toArray(Objects.requireNonNull(getCollection(key)), type));
}
return map;
}
@Override
public boolean equals(Object other) {
if (other instanceof CollectionMap) {
return map.equals(((CollectionMap) other).map);
}
return false;
}
@Override
public int hashCode() {
return map.hashCode();
}
@Override
public String toString() {
return map.toString();
}
/** {@inheritDoc} */
@Override
public @NonNull Iterator> iterator() {
return map.entrySet().iterator();
}
/**
* Helper method to create a new CollectionMap with a given type of Collection initializer. Usage
* e.g.:
* CollectionMap.createWithCollectionInitializer(() -> new LinkedList());
*/
public static > CollectionMap createWithCollectionSupplier(
Supplier collectionInitializer) {
return new CollectionMap() {
private static final long serialVersionUID = 1L;
@Override
protected C createNewCollection() {
return collectionInitializer.get();
}
};
}
}