All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sirius.kernel.commons.MultiMap Maven / Gradle / Ivy

Go to download

Provides common core classes and the microkernel powering all Sirius applications

There is a newer version: 12.9.1
Show newest version
/*
 * Made with all the love in the world
 * by scireum in Remshalden, Germany
 *
 * Copyright by scireum GmbH
 * http://www.scireum.de - [email protected]
 */

package sirius.kernel.commons;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;

/**
 * Represents a map which contains a collection of elements per key.
 * 

* Provides an implementation which simulates a {@code Map<K, Collection<V>>} by providing * specific put, get and remove methods. * * @param the key type used by the map * @param the value type used by the map */ public class MultiMap { protected Map> base; /** * Used the static factory methods create or createdSynchronized to obtain an instance. * * @param base the underlying map to use */ protected MultiMap(Map> base) { this.base = base; } /** * Creates a new MultiMap for the specified types which is not thread safe. * * @param the type of the keys used in the map * @param the type of the values used withing the value lists of the map * @return a new instance of MultiMap which is not thread safe. */ public static MultiMap create() { return new MultiMap<>(new HashMap>()); } /** * Creates a new MultiMap for the specified types which is not thread safe but keeps its insertion order. * * @param the type of the keys used in the map * @param the type of the values used withing the value lists of the map * @return a new instance of MultiMap which is not thread safe. */ public static MultiMap createOrdered() { return new MultiMap<>(new LinkedHashMap>()); } /** * Creates a new MultiMap for the specified types which is thread safe. * * @param the type of the keys used in the map * @param the type of the values used withing the value lists of the map * @return a new instance of MultiMap which is thread safe. */ public static MultiMap createSynchronized() { return new MultiMap(Collections.synchronizedMap(new HashMap<>())) { @Override @SuppressWarnings("squid:S1185") @Explain("We need to overwrite this to make it synchronized.") public synchronized void put(K key, V value) { super.put(key, value); } @Override protected List createValueList() { return new CopyOnWriteArrayList<>(); } }; } /** * Adds the given value to the list of values kept for the given key. *

* Note that the values for a given key don't from a Set. Therefore adding the same value twice * for the same key, will result in having a value list containing the added element twice. * * @param key the key for which the value is added to the map * @param value the value which is added to the list of values for this key */ public void put(@Nonnull K key, @Nullable V value) { Collection list = base.computeIfAbsent(key, k -> createValueList()); list.add(value); } /** * Sets the given value to the given name. *

* All previously set values will be removed. * * @param key the key for which the value is added to the map * @param value the name (and only) value for the given key */ public void set(@Nonnull K key, @Nullable V value) { Collection list = base.get(key); if (list == null) { list = createValueList(); base.put(key, list); } else { list.clear(); } list.add(value); } /** * Can be overridden to specify the subclass of List used to store value lists. * * @return a new instance which is used as value list for a key. */ protected List createValueList() { return new ArrayList<>(); } /** * Removes all occurrences of the given value in the value list of the given key. *

* If the value does not occur in the value list or if the key is completely unknown, nothing will happen. * * @param key the key of which value list the value will be removed from * @param value the value which will be removed from the value list */ public void remove(@Nonnull K key, @Nullable V value) { Collection list = base.get(key); if (list != null) { while (list.remove(value)) { //iterate... } } } /** * Returns the value list for the given key. *

* If the key is completely unknown, an empty list will be returned. * * @param key the key which value list is to be returned * @return the value map associated with the given key or an empty list is the key is unknown */ @Nonnull public Collection get(@Nonnull K key) { Collection list = base.get(key); if (list == null) { return Collections.emptyList(); } return Collections.unmodifiableCollection(list); } /** * Returns the set of known keys. * * @return returns the set of known keys, that is keys for which put was called */ @Nonnull public Set keySet() { return base.keySet(); } /** * Provides direct access to the underlying map. *

* For the sake of simplicity and extensibility, the original map is returned. Therefore manipulations should * be well considered. * * @return the underlying Map of this instance. */ @Nonnull public Map> getUnderlyingMap() { return base; } /** * Returns a list of all values for all keys. *

* Note that this list has no Set like behaviour. Therefore the same value might occur several times * if it was added more than once for the same or for different keys. * * @return a list of all values stored for all keys */ @Nonnull public List values() { List result = new ArrayList<>(); for (Collection val : getUnderlyingMap().values()) { result.addAll(val); } return result; } /** * Removes all entries from this map */ public void clear() { getUnderlyingMap().clear(); } @Override public String toString() { if (base == null) { return "(empty)"; } return base.toString(); } /** * Merges the given multi map into this one. *

* If both maps contain values for the same key, to lists will be joined together. *

* Note: This will modify the callee instead of creating a new result map * * @param other the other map to merge into this one * @return the callee itself for further processing */ public MultiMap merge(MultiMap other) { if (other != null) { other.base.entrySet().stream().flatMap(Tuple::flatten).forEach(t -> put(t.getFirst(), t.getSecond())); } return this; } /** * Creates a {@link Collector} which can be used to group a stream into a multi map. * * @param supplier the factory for creating the result map * @param classifier the method used to extract the key from the elements * @param the extracted key type of the map * @param the value type of the incoming stream and outgoing map * @return a Collector to be used with {@link Stream#collect(java.util.stream.Collector)} */ public static Collector, MultiMap> groupingBy(Supplier> supplier, Function classifier) { return Collector.of(supplier, (map, value) -> map.put(classifier.apply(value), value), (a, b) -> a.merge(b), Function.identity(), Collector.Characteristics.IDENTITY_FINISH); } /** * Creates a {@link Collector} which can be used to group a stream into a multi map. *

* This method permits the classifier function to return multiple keys for a single element. The element will * be added for all returned keys. * * @param supplier the factory for creating the result map * @param classifier the method used to extract the keys from the elements * @param the extracted key type of the map * @param the value type of the incoming stream and outgoing map * @return a Collector to be used with {@link Stream#collect(java.util.stream.Collector)} */ public static Collector, MultiMap> groupingByMultiple(Supplier> supplier, Function> classifier) { return Collector.of(supplier, (map, value) -> classifier.apply(value).forEach(key -> map.put(key, value)), (a, b) -> a.merge(b), Function.identity(), Collector.Characteristics.IDENTITY_FINISH); } /** * Boilerplate method to access all entries of this map as {@link Stream}. *

* Note: Calling {@link sirius.kernel.commons.Tuple#flatten(java.util.Map.Entry)} via * {@link Stream#flatMap(java.util.function.Function)} will transform the resulting stream into a stream of * all pairs represented by this map. * * @return a Stream containing all entries of this map */ public Stream>> stream() { return getUnderlyingMap().entrySet().stream(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy