001/*
002 * Copyright 2002-2018 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.core;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.EnumMap;
022import java.util.EnumSet;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.LinkedHashMap;
026import java.util.LinkedHashSet;
027import java.util.LinkedList;
028import java.util.List;
029import java.util.Map;
030import java.util.NavigableMap;
031import java.util.NavigableSet;
032import java.util.Properties;
033import java.util.Set;
034import java.util.SortedMap;
035import java.util.SortedSet;
036import java.util.TreeMap;
037import java.util.TreeSet;
038
039import org.springframework.util.Assert;
040import org.springframework.util.LinkedMultiValueMap;
041import org.springframework.util.MultiValueMap;
042
043/**
044 * Factory for collections that is aware of Java 5, Java 6, and Spring collection types.
045 *
046 * <p>Mainly for internal use within the framework.
047 *
048 * @author Juergen Hoeller
049 * @author Arjen Poutsma
050 * @author Oliver Gierke
051 * @author Sam Brannen
052 * @since 1.1.1
053 */
054public abstract class CollectionFactory {
055
056        private static final Set<Class<?>> approximableCollectionTypes = new HashSet<Class<?>>();
057
058        private static final Set<Class<?>> approximableMapTypes = new HashSet<Class<?>>();
059
060
061        static {
062                // Standard collection interfaces
063                approximableCollectionTypes.add(Collection.class);
064                approximableCollectionTypes.add(List.class);
065                approximableCollectionTypes.add(Set.class);
066                approximableCollectionTypes.add(SortedSet.class);
067                approximableCollectionTypes.add(NavigableSet.class);
068                approximableMapTypes.add(Map.class);
069                approximableMapTypes.add(SortedMap.class);
070                approximableMapTypes.add(NavigableMap.class);
071
072                // Common concrete collection classes
073                approximableCollectionTypes.add(ArrayList.class);
074                approximableCollectionTypes.add(LinkedList.class);
075                approximableCollectionTypes.add(HashSet.class);
076                approximableCollectionTypes.add(LinkedHashSet.class);
077                approximableCollectionTypes.add(TreeSet.class);
078                approximableCollectionTypes.add(EnumSet.class);
079                approximableMapTypes.add(HashMap.class);
080                approximableMapTypes.add(LinkedHashMap.class);
081                approximableMapTypes.add(TreeMap.class);
082                approximableMapTypes.add(EnumMap.class);
083        }
084
085
086        /**
087         * Determine whether the given collection type is an <em>approximable</em> type,
088         * i.e. a type that {@link #createApproximateCollection} can approximate.
089         * @param collectionType the collection type to check
090         * @return {@code true} if the type is <em>approximable</em>
091         */
092        public static boolean isApproximableCollectionType(Class<?> collectionType) {
093                return (collectionType != null && approximableCollectionTypes.contains(collectionType));
094        }
095
096        /**
097         * Create the most approximate collection for the given collection.
098         * <p><strong>Warning</strong>: Since the parameterized type {@code E} is
099         * not bound to the type of elements contained in the supplied
100         * {@code collection}, type safety cannot be guaranteed if the supplied
101         * {@code collection} is an {@link EnumSet}. In such scenarios, the caller
102         * is responsible for ensuring that the element type for the supplied
103         * {@code collection} is an enum type matching type {@code E}. As an
104         * alternative, the caller may wish to treat the return value as a raw
105         * collection or collection of {@link Object}.
106         * @param collection the original collection object, potentially {@code null}
107         * @param capacity the initial capacity
108         * @return a new, empty collection instance
109         * @see #isApproximableCollectionType
110         * @see java.util.LinkedList
111         * @see java.util.ArrayList
112         * @see java.util.EnumSet
113         * @see java.util.TreeSet
114         * @see java.util.LinkedHashSet
115         */
116        @SuppressWarnings({ "unchecked", "cast", "rawtypes" })
117        public static <E> Collection<E> createApproximateCollection(Object collection, int capacity) {
118                if (collection instanceof LinkedList) {
119                        return new LinkedList<E>();
120                }
121                else if (collection instanceof List) {
122                        return new ArrayList<E>(capacity);
123                }
124                else if (collection instanceof EnumSet) {
125                        // Cast is necessary for compilation in Eclipse 4.4.1.
126                        Collection<E> enumSet = (Collection<E>) EnumSet.copyOf((EnumSet) collection);
127                        enumSet.clear();
128                        return enumSet;
129                }
130                else if (collection instanceof SortedSet) {
131                        return new TreeSet<E>(((SortedSet<E>) collection).comparator());
132                }
133                else {
134                        return new LinkedHashSet<E>(capacity);
135                }
136        }
137
138        /**
139         * Create the most appropriate collection for the given collection type.
140         * <p>Delegates to {@link #createCollection(Class, Class, int)} with a
141         * {@code null} element type.
142         * @param collectionType the desired type of the target collection; never {@code null}
143         * @param capacity the initial capacity
144         * @return a new collection instance
145         * @throws IllegalArgumentException if the supplied {@code collectionType}
146         * is {@code null} or of type {@link EnumSet}
147         */
148        public static <E> Collection<E> createCollection(Class<?> collectionType, int capacity) {
149                return createCollection(collectionType, null, capacity);
150        }
151
152        /**
153         * Create the most appropriate collection for the given collection type.
154         * <p><strong>Warning</strong>: Since the parameterized type {@code E} is
155         * not bound to the supplied {@code elementType}, type safety cannot be
156         * guaranteed if the desired {@code collectionType} is {@link EnumSet}.
157         * In such scenarios, the caller is responsible for ensuring that the
158         * supplied {@code elementType} is an enum type matching type {@code E}.
159         * As an alternative, the caller may wish to treat the return value as a
160         * raw collection or collection of {@link Object}.
161         * @param collectionType the desired type of the target collection; never {@code null}
162         * @param elementType the collection's element type, or {@code null} if unknown
163         * (note: only relevant for {@link EnumSet} creation)
164         * @param capacity the initial capacity
165         * @return a new collection instance
166         * @since 4.1.3
167         * @see java.util.LinkedHashSet
168         * @see java.util.ArrayList
169         * @see java.util.TreeSet
170         * @see java.util.EnumSet
171         * @throws IllegalArgumentException if the supplied {@code collectionType} is
172         * {@code null}; or if the desired {@code collectionType} is {@link EnumSet} and
173         * the supplied {@code elementType} is not a subtype of {@link Enum}
174         */
175        @SuppressWarnings({ "unchecked", "cast" })
176        public static <E> Collection<E> createCollection(Class<?> collectionType, Class<?> elementType, int capacity) {
177                Assert.notNull(collectionType, "Collection type must not be null");
178                if (collectionType.isInterface()) {
179                        if (Set.class == collectionType || Collection.class == collectionType) {
180                                return new LinkedHashSet<E>(capacity);
181                        }
182                        else if (List.class == collectionType) {
183                                return new ArrayList<E>(capacity);
184                        }
185                        else if (SortedSet.class == collectionType || NavigableSet.class == collectionType) {
186                                return new TreeSet<E>();
187                        }
188                        else {
189                                throw new IllegalArgumentException("Unsupported Collection interface: " + collectionType.getName());
190                        }
191                }
192                else if (EnumSet.class.isAssignableFrom(collectionType)) {
193                        Assert.notNull(elementType, "Cannot create EnumSet for unknown element type");
194                        // Cast is necessary for compilation in Eclipse 4.4.1.
195                        return (Collection<E>) EnumSet.noneOf(asEnumType(elementType));
196                }
197                else {
198                        if (!Collection.class.isAssignableFrom(collectionType)) {
199                                throw new IllegalArgumentException("Unsupported Collection type: " + collectionType.getName());
200                        }
201                        try {
202                                return (Collection<E>) collectionType.newInstance();
203                        }
204                        catch (Throwable ex) {
205                                throw new IllegalArgumentException(
206                                        "Could not instantiate Collection type: " + collectionType.getName(), ex);
207                        }
208                }
209        }
210
211        /**
212         * Determine whether the given map type is an <em>approximable</em> type,
213         * i.e. a type that {@link #createApproximateMap} can approximate.
214         * @param mapType the map type to check
215         * @return {@code true} if the type is <em>approximable</em>
216         */
217        public static boolean isApproximableMapType(Class<?> mapType) {
218                return (mapType != null && approximableMapTypes.contains(mapType));
219        }
220
221        /**
222         * Create the most approximate map for the given map.
223         * <p><strong>Warning</strong>: Since the parameterized type {@code K} is
224         * not bound to the type of keys contained in the supplied {@code map},
225         * type safety cannot be guaranteed if the supplied {@code map} is an
226         * {@link EnumMap}. In such scenarios, the caller is responsible for
227         * ensuring that the key type in the supplied {@code map} is an enum type
228         * matching type {@code K}. As an alternative, the caller may wish to
229         * treat the return value as a raw map or map keyed by {@link Object}.
230         * @param map the original map object, potentially {@code null}
231         * @param capacity the initial capacity
232         * @return a new, empty map instance
233         * @see #isApproximableMapType
234         * @see java.util.EnumMap
235         * @see java.util.TreeMap
236         * @see java.util.LinkedHashMap
237         */
238        @SuppressWarnings({"unchecked", "rawtypes"})
239        public static <K, V> Map<K, V> createApproximateMap(Object map, int capacity) {
240                if (map instanceof EnumMap) {
241                        EnumMap enumMap = new EnumMap((EnumMap) map);
242                        enumMap.clear();
243                        return enumMap;
244                }
245                else if (map instanceof SortedMap) {
246                        return new TreeMap<K, V>(((SortedMap<K, V>) map).comparator());
247                }
248                else {
249                        return new LinkedHashMap<K, V>(capacity);
250                }
251        }
252
253        /**
254         * Create the most appropriate map for the given map type.
255         * <p>Delegates to {@link #createMap(Class, Class, int)} with a
256         * {@code null} key type.
257         * @param mapType the desired type of the target map
258         * @param capacity the initial capacity
259         * @return a new map instance
260         * @throws IllegalArgumentException if the supplied {@code mapType} is
261         * {@code null} or of type {@link EnumMap}
262         */
263        public static <K, V> Map<K, V> createMap(Class<?> mapType, int capacity) {
264                return createMap(mapType, null, capacity);
265        }
266
267        /**
268         * Create the most appropriate map for the given map type.
269         * <p><strong>Warning</strong>: Since the parameterized type {@code K}
270         * is not bound to the supplied {@code keyType}, type safety cannot be
271         * guaranteed if the desired {@code mapType} is {@link EnumMap}. In such
272         * scenarios, the caller is responsible for ensuring that the {@code keyType}
273         * is an enum type matching type {@code K}. As an alternative, the caller
274         * may wish to treat the return value as a raw map or map keyed by
275         * {@link Object}. Similarly, type safety cannot be enforced if the
276         * desired {@code mapType} is {@link MultiValueMap}.
277         * @param mapType the desired type of the target map; never {@code null}
278         * @param keyType the map's key type, or {@code null} if unknown
279         * (note: only relevant for {@link EnumMap} creation)
280         * @param capacity the initial capacity
281         * @return a new map instance
282         * @since 4.1.3
283         * @see java.util.LinkedHashMap
284         * @see java.util.TreeMap
285         * @see org.springframework.util.LinkedMultiValueMap
286         * @see java.util.EnumMap
287         * @throws IllegalArgumentException if the supplied {@code mapType} is
288         * {@code null}; or if the desired {@code mapType} is {@link EnumMap} and
289         * the supplied {@code keyType} is not a subtype of {@link Enum}
290         */
291        @SuppressWarnings({"unchecked", "rawtypes"})
292        public static <K, V> Map<K, V> createMap(Class<?> mapType, Class<?> keyType, int capacity) {
293                Assert.notNull(mapType, "Map type must not be null");
294                if (mapType.isInterface()) {
295                        if (Map.class == mapType) {
296                                return new LinkedHashMap<K, V>(capacity);
297                        }
298                        else if (SortedMap.class == mapType || NavigableMap.class == mapType) {
299                                return new TreeMap<K, V>();
300                        }
301                        else if (MultiValueMap.class == mapType) {
302                                return new LinkedMultiValueMap();
303                        }
304                        else {
305                                throw new IllegalArgumentException("Unsupported Map interface: " + mapType.getName());
306                        }
307                }
308                else if (EnumMap.class == mapType) {
309                        Assert.notNull(keyType, "Cannot create EnumMap for unknown key type");
310                        return new EnumMap(asEnumType(keyType));
311                }
312                else {
313                        if (!Map.class.isAssignableFrom(mapType)) {
314                                throw new IllegalArgumentException("Unsupported Map type: " + mapType.getName());
315                        }
316                        try {
317                                return (Map<K, V>) mapType.newInstance();
318                        }
319                        catch (Throwable ex) {
320                                throw new IllegalArgumentException("Could not instantiate Map type: " + mapType.getName(), ex);
321                        }
322                }
323        }
324
325        /**
326         * Create a variant of {@code java.util.Properties} that automatically adapts
327         * non-String values to String representations on {@link Properties#getProperty}.
328         * @return a new {@code Properties} instance
329         * @since 4.3.4
330         */
331        @SuppressWarnings("serial")
332        public static Properties createStringAdaptingProperties() {
333                return new Properties() {
334                        @Override
335                        public String getProperty(String key) {
336                                Object value = get(key);
337                                return (value != null ? value.toString() : null);
338                        }
339                };
340        }
341
342        /**
343         * Cast the given type to a subtype of {@link Enum}.
344         * @param enumType the enum type, never {@code null}
345         * @return the given type as subtype of {@link Enum}
346         * @throws IllegalArgumentException if the given type is not a subtype of {@link Enum}
347         */
348        @SuppressWarnings("rawtypes")
349        private static Class<? extends Enum> asEnumType(Class<?> enumType) {
350                Assert.notNull(enumType, "Enum type must not be null");
351                if (!Enum.class.isAssignableFrom(enumType)) {
352                        throw new IllegalArgumentException("Supplied type is not an enum: " + enumType.getName());
353                }
354                return enumType.asSubclass(Enum.class);
355        }
356
357}