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