001/* 002 * Copyright 2002-2020 the original author or authors. 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 * https://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 */ 016 017package org.springframework.util; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.Enumeration; 024import java.util.Iterator; 025import java.util.LinkedHashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.Properties; 029import java.util.Set; 030import java.util.SortedSet; 031 032import org.springframework.lang.Nullable; 033 034/** 035 * Miscellaneous collection utility methods. 036 * Mainly for internal use within the framework. 037 * 038 * @author Juergen Hoeller 039 * @author Rob Harrop 040 * @author Arjen Poutsma 041 * @since 1.1.3 042 */ 043public abstract class CollectionUtils { 044 045 /** 046 * Return {@code true} if the supplied Collection is {@code null} or empty. 047 * Otherwise, return {@code false}. 048 * @param collection the Collection to check 049 * @return whether the given Collection is empty 050 */ 051 public static boolean isEmpty(@Nullable Collection<?> collection) { 052 return (collection == null || collection.isEmpty()); 053 } 054 055 /** 056 * Return {@code true} if the supplied Map is {@code null} or empty. 057 * Otherwise, return {@code false}. 058 * @param map the Map to check 059 * @return whether the given Map is empty 060 */ 061 public static boolean isEmpty(@Nullable Map<?, ?> map) { 062 return (map == null || map.isEmpty()); 063 } 064 065 /** 066 * Convert the supplied array into a List. A primitive array gets converted 067 * into a List of the appropriate wrapper type. 068 * <p><b>NOTE:</b> Generally prefer the standard {@link Arrays#asList} method. 069 * This {@code arrayToList} method is just meant to deal with an incoming Object 070 * value that might be an {@code Object[]} or a primitive array at runtime. 071 * <p>A {@code null} source value will be converted to an empty List. 072 * @param source the (potentially primitive) array 073 * @return the converted List result 074 * @see ObjectUtils#toObjectArray(Object) 075 * @see Arrays#asList(Object[]) 076 */ 077 @SuppressWarnings("rawtypes") 078 public static List arrayToList(@Nullable Object source) { 079 return Arrays.asList(ObjectUtils.toObjectArray(source)); 080 } 081 082 /** 083 * Merge the given array into the given Collection. 084 * @param array the array to merge (may be {@code null}) 085 * @param collection the target Collection to merge the array into 086 */ 087 @SuppressWarnings("unchecked") 088 public static <E> void mergeArrayIntoCollection(@Nullable Object array, Collection<E> collection) { 089 Object[] arr = ObjectUtils.toObjectArray(array); 090 for (Object elem : arr) { 091 collection.add((E) elem); 092 } 093 } 094 095 /** 096 * Merge the given Properties instance into the given Map, 097 * copying all properties (key-value pairs) over. 098 * <p>Uses {@code Properties.propertyNames()} to even catch 099 * default properties linked into the original Properties instance. 100 * @param props the Properties instance to merge (may be {@code null}) 101 * @param map the target Map to merge the properties into 102 */ 103 @SuppressWarnings("unchecked") 104 public static <K, V> void mergePropertiesIntoMap(@Nullable Properties props, Map<K, V> map) { 105 if (props != null) { 106 for (Enumeration<?> en = props.propertyNames(); en.hasMoreElements();) { 107 String key = (String) en.nextElement(); 108 Object value = props.get(key); 109 if (value == null) { 110 // Allow for defaults fallback or potentially overridden accessor... 111 value = props.getProperty(key); 112 } 113 map.put((K) key, (V) value); 114 } 115 } 116 } 117 118 119 /** 120 * Check whether the given Iterator contains the given element. 121 * @param iterator the Iterator to check 122 * @param element the element to look for 123 * @return {@code true} if found, {@code false} otherwise 124 */ 125 public static boolean contains(@Nullable Iterator<?> iterator, Object element) { 126 if (iterator != null) { 127 while (iterator.hasNext()) { 128 Object candidate = iterator.next(); 129 if (ObjectUtils.nullSafeEquals(candidate, element)) { 130 return true; 131 } 132 } 133 } 134 return false; 135 } 136 137 /** 138 * Check whether the given Enumeration contains the given element. 139 * @param enumeration the Enumeration to check 140 * @param element the element to look for 141 * @return {@code true} if found, {@code false} otherwise 142 */ 143 public static boolean contains(@Nullable Enumeration<?> enumeration, Object element) { 144 if (enumeration != null) { 145 while (enumeration.hasMoreElements()) { 146 Object candidate = enumeration.nextElement(); 147 if (ObjectUtils.nullSafeEquals(candidate, element)) { 148 return true; 149 } 150 } 151 } 152 return false; 153 } 154 155 /** 156 * Check whether the given Collection contains the given element instance. 157 * <p>Enforces the given instance to be present, rather than returning 158 * {@code true} for an equal element as well. 159 * @param collection the Collection to check 160 * @param element the element to look for 161 * @return {@code true} if found, {@code false} otherwise 162 */ 163 public static boolean containsInstance(@Nullable Collection<?> collection, Object element) { 164 if (collection != null) { 165 for (Object candidate : collection) { 166 if (candidate == element) { 167 return true; 168 } 169 } 170 } 171 return false; 172 } 173 174 /** 175 * Return {@code true} if any element in '{@code candidates}' is 176 * contained in '{@code source}'; otherwise returns {@code false}. 177 * @param source the source Collection 178 * @param candidates the candidates to search for 179 * @return whether any of the candidates has been found 180 */ 181 public static boolean containsAny(Collection<?> source, Collection<?> candidates) { 182 return findFirstMatch(source, candidates) != null; 183 } 184 185 /** 186 * Return the first element in '{@code candidates}' that is contained in 187 * '{@code source}'. If no element in '{@code candidates}' is present in 188 * '{@code source}' returns {@code null}. Iteration order is 189 * {@link Collection} implementation specific. 190 * @param source the source Collection 191 * @param candidates the candidates to search for 192 * @return the first present object, or {@code null} if not found 193 */ 194 @SuppressWarnings("unchecked") 195 @Nullable 196 public static <E> E findFirstMatch(Collection<?> source, Collection<E> candidates) { 197 if (isEmpty(source) || isEmpty(candidates)) { 198 return null; 199 } 200 for (Object candidate : candidates) { 201 if (source.contains(candidate)) { 202 return (E) candidate; 203 } 204 } 205 return null; 206 } 207 208 /** 209 * Find a single value of the given type in the given Collection. 210 * @param collection the Collection to search 211 * @param type the type to look for 212 * @return a value of the given type found if there is a clear match, 213 * or {@code null} if none or more than one such value found 214 */ 215 @SuppressWarnings("unchecked") 216 @Nullable 217 public static <T> T findValueOfType(Collection<?> collection, @Nullable Class<T> type) { 218 if (isEmpty(collection)) { 219 return null; 220 } 221 T value = null; 222 for (Object element : collection) { 223 if (type == null || type.isInstance(element)) { 224 if (value != null) { 225 // More than one value found... no clear single value. 226 return null; 227 } 228 value = (T) element; 229 } 230 } 231 return value; 232 } 233 234 /** 235 * Find a single value of one of the given types in the given Collection: 236 * searching the Collection for a value of the first type, then 237 * searching for a value of the second type, etc. 238 * @param collection the collection to search 239 * @param types the types to look for, in prioritized order 240 * @return a value of one of the given types found if there is a clear match, 241 * or {@code null} if none or more than one such value found 242 */ 243 @Nullable 244 public static Object findValueOfType(Collection<?> collection, Class<?>[] types) { 245 if (isEmpty(collection) || ObjectUtils.isEmpty(types)) { 246 return null; 247 } 248 for (Class<?> type : types) { 249 Object value = findValueOfType(collection, type); 250 if (value != null) { 251 return value; 252 } 253 } 254 return null; 255 } 256 257 /** 258 * Determine whether the given Collection only contains a single unique object. 259 * @param collection the Collection to check 260 * @return {@code true} if the collection contains a single reference or 261 * multiple references to the same instance, {@code false} otherwise 262 */ 263 public static boolean hasUniqueObject(Collection<?> collection) { 264 if (isEmpty(collection)) { 265 return false; 266 } 267 boolean hasCandidate = false; 268 Object candidate = null; 269 for (Object elem : collection) { 270 if (!hasCandidate) { 271 hasCandidate = true; 272 candidate = elem; 273 } 274 else if (candidate != elem) { 275 return false; 276 } 277 } 278 return true; 279 } 280 281 /** 282 * Find the common element type of the given Collection, if any. 283 * @param collection the Collection to check 284 * @return the common element type, or {@code null} if no clear 285 * common type has been found (or the collection was empty) 286 */ 287 @Nullable 288 public static Class<?> findCommonElementType(Collection<?> collection) { 289 if (isEmpty(collection)) { 290 return null; 291 } 292 Class<?> candidate = null; 293 for (Object val : collection) { 294 if (val != null) { 295 if (candidate == null) { 296 candidate = val.getClass(); 297 } 298 else if (candidate != val.getClass()) { 299 return null; 300 } 301 } 302 } 303 return candidate; 304 } 305 306 /** 307 * Retrieve the first element of the given Set, using {@link SortedSet#first()} 308 * or otherwise using the iterator. 309 * @param set the Set to check (may be {@code null} or empty) 310 * @return the first element, or {@code null} if none 311 * @since 5.2.3 312 * @see SortedSet 313 * @see LinkedHashMap#keySet() 314 * @see java.util.LinkedHashSet 315 */ 316 @Nullable 317 public static <T> T firstElement(@Nullable Set<T> set) { 318 if (isEmpty(set)) { 319 return null; 320 } 321 if (set instanceof SortedSet) { 322 return ((SortedSet<T>) set).first(); 323 } 324 325 Iterator<T> it = set.iterator(); 326 T first = null; 327 if (it.hasNext()) { 328 first = it.next(); 329 } 330 return first; 331 } 332 333 /** 334 * Retrieve the first element of the given List, accessing the zero index. 335 * @param list the List to check (may be {@code null} or empty) 336 * @return the first element, or {@code null} if none 337 * @since 5.2.3 338 */ 339 @Nullable 340 public static <T> T firstElement(@Nullable List<T> list) { 341 if (isEmpty(list)) { 342 return null; 343 } 344 return list.get(0); 345 } 346 347 /** 348 * Retrieve the last element of the given Set, using {@link SortedSet#last()} 349 * or otherwise iterating over all elements (assuming a linked set). 350 * @param set the Set to check (may be {@code null} or empty) 351 * @return the last element, or {@code null} if none 352 * @since 5.0.3 353 * @see SortedSet 354 * @see LinkedHashMap#keySet() 355 * @see java.util.LinkedHashSet 356 */ 357 @Nullable 358 public static <T> T lastElement(@Nullable Set<T> set) { 359 if (isEmpty(set)) { 360 return null; 361 } 362 if (set instanceof SortedSet) { 363 return ((SortedSet<T>) set).last(); 364 } 365 366 // Full iteration necessary... 367 Iterator<T> it = set.iterator(); 368 T last = null; 369 while (it.hasNext()) { 370 last = it.next(); 371 } 372 return last; 373 } 374 375 /** 376 * Retrieve the last element of the given List, accessing the highest index. 377 * @param list the List to check (may be {@code null} or empty) 378 * @return the last element, or {@code null} if none 379 * @since 5.0.3 380 */ 381 @Nullable 382 public static <T> T lastElement(@Nullable List<T> list) { 383 if (isEmpty(list)) { 384 return null; 385 } 386 return list.get(list.size() - 1); 387 } 388 389 /** 390 * Marshal the elements from the given enumeration into an array of the given type. 391 * Enumeration elements must be assignable to the type of the given array. The array 392 * returned will be a different instance than the array given. 393 */ 394 public static <A, E extends A> A[] toArray(Enumeration<E> enumeration, A[] array) { 395 ArrayList<A> elements = new ArrayList<>(); 396 while (enumeration.hasMoreElements()) { 397 elements.add(enumeration.nextElement()); 398 } 399 return elements.toArray(array); 400 } 401 402 /** 403 * Adapt an {@link Enumeration} to an {@link Iterator}. 404 * @param enumeration the original {@code Enumeration} 405 * @return the adapted {@code Iterator} 406 */ 407 public static <E> Iterator<E> toIterator(@Nullable Enumeration<E> enumeration) { 408 return (enumeration != null ? new EnumerationIterator<>(enumeration) : Collections.emptyIterator()); 409 } 410 411 /** 412 * Adapt a {@code Map<K, List<V>>} to an {@code MultiValueMap<K, V>}. 413 * @param targetMap the original map 414 * @return the adapted multi-value map (wrapping the original map) 415 * @since 3.1 416 */ 417 public static <K, V> MultiValueMap<K, V> toMultiValueMap(Map<K, List<V>> targetMap) { 418 Assert.notNull(targetMap, "'targetMap' must not be null"); 419 return new MultiValueMapAdapter<>(targetMap); 420 } 421 422 /** 423 * Return an unmodifiable view of the specified multi-value map. 424 * @param targetMap the map for which an unmodifiable view is to be returned. 425 * @return an unmodifiable view of the specified multi-value map 426 * @since 3.1 427 */ 428 @SuppressWarnings("unchecked") 429 public static <K, V> MultiValueMap<K, V> unmodifiableMultiValueMap( 430 MultiValueMap<? extends K, ? extends V> targetMap) { 431 432 Assert.notNull(targetMap, "'targetMap' must not be null"); 433 Map<K, List<V>> result = new LinkedHashMap<>(targetMap.size()); 434 targetMap.forEach((key, value) -> { 435 List<? extends V> values = Collections.unmodifiableList(value); 436 result.put(key, (List<V>) values); 437 }); 438 Map<K, List<V>> unmodifiableMap = Collections.unmodifiableMap(result); 439 return toMultiValueMap(unmodifiableMap); 440 } 441 442 443 /** 444 * Iterator wrapping an Enumeration. 445 */ 446 private static class EnumerationIterator<E> implements Iterator<E> { 447 448 private final Enumeration<E> enumeration; 449 450 public EnumerationIterator(Enumeration<E> enumeration) { 451 this.enumeration = enumeration; 452 } 453 454 @Override 455 public boolean hasNext() { 456 return this.enumeration.hasMoreElements(); 457 } 458 459 @Override 460 public E next() { 461 return this.enumeration.nextElement(); 462 } 463 464 @Override 465 public void remove() throws UnsupportedOperationException { 466 throw new UnsupportedOperationException("Not supported"); 467 } 468 } 469 470 471}