1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"). 5 * You may not use this file except in compliance with the License. 6 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.utils; 17 18 import java.util.ArrayList; 19 import java.util.Collection; 20 import java.util.HashMap; 21 import java.util.LinkedHashMap; 22 import java.util.LinkedList; 23 import java.util.List; 24 import java.util.Map; 25 import java.util.function.Function; 26 import java.util.function.Predicate; 27 import java.util.function.Supplier; 28 import java.util.stream.Collector; 29 import java.util.stream.Collectors; 30 import software.amazon.awssdk.annotations.SdkProtectedApi; 31 32 @SdkProtectedApi 33 public final class CollectionUtils { 34 CollectionUtils()35 private CollectionUtils() { 36 } 37 isNullOrEmpty(Collection<?> collection)38 public static boolean isNullOrEmpty(Collection<?> collection) { 39 return collection == null || collection.isEmpty(); 40 } 41 isNullOrEmpty(Map<?, ?> map)42 public static boolean isNullOrEmpty(Map<?, ?> map) { 43 return map == null || map.isEmpty(); 44 } 45 isNotEmpty(Map<?, ?> map)46 public static boolean isNotEmpty(Map<?, ?> map) { 47 return map != null && !map.isEmpty(); 48 } 49 50 /** 51 * Returns a new list containing the second list appended to the first list. 52 */ mergeLists(List<T> list1, List<T> list2)53 public static <T> List<T> mergeLists(List<T> list1, List<T> list2) { 54 List<T> merged = new LinkedList<>(); 55 if (list1 != null) { 56 merged.addAll(list1); 57 } 58 if (list2 != null) { 59 merged.addAll(list2); 60 } 61 return merged; 62 } 63 64 /** 65 * @param list List to get first element from. 66 * @param <T> Type of elements in the list. 67 * @return The first element in the list if it exists. If the list is null or empty this will 68 * return null. 69 */ firstIfPresent(List<T> list)70 public static <T> T firstIfPresent(List<T> list) { 71 if (list == null || list.isEmpty()) { 72 return null; 73 } else { 74 return list.get(0); 75 } 76 } 77 78 /** 79 * Perform a deep copy of the provided map of lists. This only performs a deep copy of the map and lists. Entries are not 80 * copied, so care should be taken to ensure that entries are immutable if preventing unwanted mutations of the elements is 81 * desired. 82 */ deepCopyMap(Map<T, ? extends List<U>> map)83 public static <T, U> Map<T, List<U>> deepCopyMap(Map<T, ? extends List<U>> map) { 84 return deepCopyMap(map, () -> new LinkedHashMap<>(map.size())); 85 } 86 87 /** 88 * Perform a deep copy of the provided map of lists. This only performs a deep copy of the map and lists. Entries are not 89 * copied, so care should be taken to ensure that entries are immutable if preventing unwanted mutations of the elements is 90 * desired. 91 */ deepCopyMap(Map<T, ? extends List<U>> map, Supplier<Map<T, List<U>>> mapConstructor)92 public static <T, U> Map<T, List<U>> deepCopyMap(Map<T, ? extends List<U>> map, Supplier<Map<T, List<U>>> mapConstructor) { 93 Map<T, List<U>> result = mapConstructor.get(); 94 map.forEach((k, v) -> result.put(k, new ArrayList<>(v))); 95 return result; 96 } 97 unmodifiableMapOfLists(Map<T, List<U>> map)98 public static <T, U> Map<T, List<U>> unmodifiableMapOfLists(Map<T, List<U>> map) { 99 return new UnmodifiableMapOfLists<>(map); 100 } 101 102 /** 103 * Perform a deep copy of the provided map of lists, and make the result unmodifiable. 104 * 105 * This is equivalent to calling {@link #deepCopyMap} followed by {@link #unmodifiableMapOfLists}. 106 */ deepUnmodifiableMap(Map<T, ? extends List<U>> map)107 public static <T, U> Map<T, List<U>> deepUnmodifiableMap(Map<T, ? extends List<U>> map) { 108 return unmodifiableMapOfLists(deepCopyMap(map)); 109 } 110 111 /** 112 * Perform a deep copy of the provided map of lists, and make the result unmodifiable. 113 * 114 * This is equivalent to calling {@link #deepCopyMap} followed by {@link #unmodifiableMapOfLists}. 115 */ deepUnmodifiableMap(Map<T, ? extends List<U>> map, Supplier<Map<T, List<U>>> mapConstructor)116 public static <T, U> Map<T, List<U>> deepUnmodifiableMap(Map<T, ? extends List<U>> map, 117 Supplier<Map<T, List<U>>> mapConstructor) { 118 return unmodifiableMapOfLists(deepCopyMap(map, mapConstructor)); 119 } 120 121 122 /** 123 * Collect a stream of {@link Map.Entry} to a {@link Map} with the same key/value types 124 * @param <K> the key type 125 * @param <V> the value type 126 * @return a map 127 */ toMap()128 public static <K, V> Collector<Map.Entry<K, V>, ?, Map<K, V>> toMap() { 129 return Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue); 130 } 131 132 /** 133 * Transforms the values of a map to another map with the same keys, using the supplied function. 134 * 135 * @param inputMap the input map 136 * @param mapper the function used to transform the map values 137 * @param <K> the key type 138 * @param <VInT> the value type for the input map 139 * @param <VOutT> the value type for the output map 140 * @return a map 141 */ mapValues(Map<K, VInT> inputMap, Function<VInT, VOutT> mapper)142 public static <K, VInT, VOutT> Map<K, VOutT> mapValues(Map<K, VInT> inputMap, Function<VInT, VOutT> mapper) { 143 return inputMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> mapper.apply(e.getValue()))); 144 } 145 146 /** 147 * Filters a map based on a condition 148 * 149 * @param map the input map 150 * @param condition the predicate to filter on 151 * @param <K> the key type 152 * @param <V> the value type 153 * @return the filtered map 154 */ filterMap(Map<K, V> map, Predicate<Map.Entry<K, V>> condition)155 public static <K, V> Map<K, V> filterMap(Map<K, V> map, Predicate<Map.Entry<K, V>> condition) { 156 return map.entrySet() 157 .stream() 158 .filter(condition) 159 .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); 160 } 161 162 /** 163 * Return a new map that is the inverse of the supplied map, with the values becoming the keys 164 * and vice versa. Requires the values to be unique. 165 * 166 * @param inputMap a map where both the keys and values are unique 167 * @param <K> the key type 168 * @param <V> the value type 169 * @return a map 170 */ inverseMap(Map<V, K> inputMap)171 public static <K, V> Map<K, V> inverseMap(Map<V, K> inputMap) { 172 return inputMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)); 173 } 174 175 /** 176 * For a collection of values of type {@code V} that can all be converted to type {@code K}, create a map that 177 * indexes all of the values by {@code K}. This requires that no two values map to the same index. 178 * 179 * @param values the collection of values to index 180 * @param indexFunction the function used to convert a value to its index 181 * @param <K> the index (or key) type 182 * @param <V> the value type 183 * @return a (modifiable) map that indexes K to its unique value V 184 * @throws IllegalArgumentException if any of the values map to the same index 185 */ uniqueIndex(Iterable<V> values, Function<? super V, K> indexFunction)186 public static <K, V> Map<K, V> uniqueIndex(Iterable<V> values, Function<? super V, K> indexFunction) { 187 Map<K, V> map = new HashMap<>(); 188 for (V value : values) { 189 K index = indexFunction.apply(value); 190 V prev = map.put(index, value); 191 Validate.isNull(prev, "No duplicate indices allowed but both %s and %s have the same index: %s", 192 prev, value, index); 193 } 194 return map; 195 } 196 } 197