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.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.function.BinaryOperator;
023import java.util.stream.Collectors;
024import java.util.stream.Stream;
025
026/**
027 * Utility methods for {@link Map}s
028 */
029public final class MapUtils
030{
031    private static BinaryOperator<?> __DEFAULT_MERGE_FUNCTION = (value1, value2) -> value1;
032    
033    private MapUtils()
034    {
035        // Nothing
036    }
037    
038    @SuppressWarnings("unchecked")
039    private static <V> BinaryOperator<V> _defaultMergeFunction()
040    {
041        return (BinaryOperator<V>) __DEFAULT_MERGE_FUNCTION;
042    }
043    
044    /**
045     * 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.
046     * @param <K> The type of the keys
047     * @param <V> The type of the values
048     * @param map1 The first map (not modified)
049     * @param map2 The second map (not modified)
050     * @return The merged map
051     */
052    public static <K, V> Map<K, V> merge(
053            Map<? extends K, ? extends V> map1, 
054            Map<? extends K, ? extends V> map2)
055    {
056        Stream<Map<? extends K, ? extends V>> sequentialStreamOfMaps = Stream.of(map1, map2);
057        return _merge(_defaultMergeFunction(), sequentialStreamOfMaps);
058    }
059    
060    
061    /**
062     * 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.
063     * @param <K> The type of the keys
064     * @param <V> The type of the values
065     * @param map1 The first map (not modified)
066     * @param map2 The second map (not modified)
067     * @param others The other maps (not modified)
068     * @return The merged map
069     */
070    @SafeVarargs // Creating a stream from an array is safe
071    public static <K, V> Map<K, V> merge(
072            Map<? extends K, ? extends V> map1, 
073            Map<? extends K, ? extends V> map2, 
074            Map<? extends K, ? extends V>... others)
075    {
076        return merge(_defaultMergeFunction(), map1, map2, others);
077    }
078    
079    /**
080     * Merges the entries of two maps into one map.
081     * @param <K> The type of the keys
082     * @param <V> The type of the values
083     * @param mergeFunction The merging function, to determine which value of the entry to keep when same keys are encountered.
084     * @param map1 The first map (not modified)
085     * @param map2 The second map (not modified)
086     * @return The merged map
087     */
088    public static <K, V> Map<K, V> merge(
089            BinaryOperator<V> mergeFunction, 
090            Map<? extends K, ? extends V> map1, 
091            Map<? extends K, ? extends V> map2)
092    {
093        Stream<Map<? extends K, ? extends V>> sequentialStreamOfMaps = Stream.of(map1, map2);
094        return _merge(mergeFunction, sequentialStreamOfMaps);
095    }
096    
097    /**
098     * 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.
099     * @param <K> The type of the keys
100     * @param <V> The type of the values
101     * @param mergeFunction The merging function, to determine which value of the entry to keep when same keys are encountered.
102     * @param map1 The first map (not modified)
103     * @param map2 The second map (not modified)
104     * @param others The other maps (not modified)
105     * @return The merged map
106     */
107    @SafeVarargs // Creating a stream from an array is safe
108    public static <K, V> Map<K, V> merge(
109            BinaryOperator<V> mergeFunction, 
110            Map<? extends K, ? extends V> map1, 
111            Map<? extends K, ? extends V> map2, 
112            Map<? extends K, ? extends V>... others)
113    {
114        Stream<Map<? extends K, ? extends V>> sequentialStreamOfMaps = Stream.of(
115                Stream.of(map1, map2),
116                Stream.of(others)
117            )
118            .flatMap(stream -> stream)
119            .sequential();
120        
121        return _merge(mergeFunction, sequentialStreamOfMaps);
122    }
123    
124    
125    private static <K, V> Map<K, V> _merge(
126            BinaryOperator<V> mergeFunction,
127            Stream<Map<? extends K, ? extends V>> sequentialStreamOfMaps)
128    {
129        return sequentialStreamOfMaps
130                .map(Map::entrySet)
131                .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
132                .flatMap(List::stream)
133                .collect(Collectors.toMap(
134                        Map.Entry::getKey, 
135                        Map.Entry::getValue,
136                        mergeFunction));
137    }
138
139    /**
140     * Function to merge deeply two maps. If the two values are maps, they are merged, but if not, value1 is returned.
141     * This operation is done recursively.
142     * @param value1 First value to merge
143     * @param value2 Second value to merge
144     * @return Both values merged if they are maps.
145     */
146    @SuppressWarnings("unchecked")
147    public static Object deepMerge(Object value1, Object value2)
148    {
149        if (value1 instanceof Map && value2 instanceof Map)
150        {
151            Map<Object, Object> firstMap = new HashMap<>((Map<Object, Object>) value1);
152            Map<Object, Object> secondMap = new HashMap<>((Map<Object, Object>) value2);
153            for (Object key : secondMap.keySet())
154            {
155                Object value = secondMap.get(key);
156                if (value != null)
157                {
158                    firstMap.merge(key, value, MapUtils::deepMerge);
159                }
160                // null value are not supported by remapping function, add it only if the value is not in value1
161                else if (!firstMap.containsKey(key))
162                {
163                    firstMap.put(key, value);
164                }
165            }
166            
167            return firstMap;
168        }
169        return value1;
170    }
171}