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}