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}