001/*
002 *  Copyright 2020 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.core.util;
017
018import java.util.ArrayList;
019import java.util.List;
020import java.util.Map;
021import java.util.function.BinaryOperator;
022import java.util.stream.Collectors;
023import java.util.stream.Stream;
024
025/**
026 * Utility methods for {@link Map}s
027 */
028public final class MapUtils
029{
030    private static BinaryOperator<?> __DEFAULT_MERGE_FUNCTION = (value1, value2) -> value1;
031    
032    private MapUtils()
033    {
034        // Nothing
035    }
036    
037    @SuppressWarnings("unchecked")
038    private static <V> BinaryOperator<V> _defaultMergeFunction()
039    {
040        return (BinaryOperator<V>) __DEFAULT_MERGE_FUNCTION;
041    }
042    
043    /**
044     * Merges the entries of two maps into one map. Entries of the first one have the priority (i.e. kept if same keys) over ones of the second one.
045     * @param <K> The type of the keys
046     * @param <V> The type of the values
047     * @param map1 The first map (not modified)
048     * @param map2 The second map (not modified)
049     * @return The merged map
050     */
051    public static <K, V> Map<K, V> merge(
052            Map<? extends K, ? extends V> map1, 
053            Map<? extends K, ? extends V> map2)
054    {
055        Stream<Map<? extends K, ? extends V>> sequentialStreamOfMaps = Stream.of(map1, map2);
056        return _merge(_defaultMergeFunction(), sequentialStreamOfMaps);
057    }
058    
059    
060    /**
061     * Merges the entries of several maps into one map. Entries of any input map one have the priority (i.e. kept if same keys) over ones of any following map.
062     * @param <K> The type of the keys
063     * @param <V> The type of the values
064     * @param map1 The first map (not modified)
065     * @param map2 The second map (not modified)
066     * @param others The other maps (not modified)
067     * @return The merged map
068     */
069    @SafeVarargs // Creating a stream from an array is safe
070    public static <K, V> Map<K, V> merge(
071            Map<? extends K, ? extends V> map1, 
072            Map<? extends K, ? extends V> map2, 
073            Map<? extends K, ? extends V>... others)
074    {
075        return merge(_defaultMergeFunction(), map1, map2, others);
076    }
077    
078    /**
079     * Merges the entries of two maps into one map.
080     * @param <K> The type of the keys
081     * @param <V> The type of the values
082     * @param mergeFunction The merging function, to determine which value of the entry to keep when same keys are encountered.
083     * @param map1 The first map (not modified)
084     * @param map2 The second map (not modified)
085     * @return The merged map
086     */
087    public static <K, V> Map<K, V> merge(
088            BinaryOperator<V> mergeFunction, 
089            Map<? extends K, ? extends V> map1, 
090            Map<? extends K, ? extends V> map2)
091    {
092        Stream<Map<? extends K, ? extends V>> sequentialStreamOfMaps = Stream.of(map1, map2);
093        return _merge(mergeFunction, sequentialStreamOfMaps);
094    }
095    
096    /**
097     * Merges the entries of several maps into one map. Entries of any input map one have the priority (i.e. kept if same keys) over ones of any following map.
098     * @param <K> The type of the keys
099     * @param <V> The type of the values
100     * @param mergeFunction The merging function, to determine which value of the entry to keep when same keys are encountered.
101     * @param map1 The first map (not modified)
102     * @param map2 The second map (not modified)
103     * @param others The other maps (not modified)
104     * @return The merged map
105     */
106    @SafeVarargs // Creating a stream from an array is safe
107    public static <K, V> Map<K, V> merge(
108            BinaryOperator<V> mergeFunction, 
109            Map<? extends K, ? extends V> map1, 
110            Map<? extends K, ? extends V> map2, 
111            Map<? extends K, ? extends V>... others)
112    {
113        Stream<Map<? extends K, ? extends V>> sequentialStreamOfMaps = Stream.of(
114                Stream.of(map1, map2),
115                Stream.of(others)
116            )
117            .flatMap(stream -> stream)
118            .sequential();
119        
120        return _merge(mergeFunction, sequentialStreamOfMaps);
121    }
122    
123    
124    private static <K, V> Map<K, V> _merge(
125            BinaryOperator<V> mergeFunction,
126            Stream<Map<? extends K, ? extends V>> sequentialStreamOfMaps)
127    {
128        return sequentialStreamOfMaps
129                .map(Map::entrySet)
130                .map(ArrayList::new) // Convert Set into List in order to be sure entries of a given map all come before any entry of a following map
131                .flatMap(List::stream)
132                .collect(Collectors.toMap(
133                        Map.Entry::getKey, 
134                        Map.Entry::getValue,
135                        mergeFunction));
136    }
137}