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}