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