org.springframework.core.CollectionFactory Maven / Gradle / Ivy
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Properties;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
/**
* Factory for collections that is aware of common Java and Spring collection types.
*
* Mainly for internal use within the framework.
*
* @author Juergen Hoeller
* @author Arjen Poutsma
* @author Oliver Gierke
* @author Sam Brannen
* @since 1.1.1
*/
public final class CollectionFactory {
private static final Set> approximableCollectionTypes = new HashSet<>();
private static final Set> approximableMapTypes = new HashSet<>();
static {
// Standard collection interfaces
approximableCollectionTypes.add(Collection.class);
approximableCollectionTypes.add(List.class);
approximableCollectionTypes.add(Set.class);
approximableCollectionTypes.add(SortedSet.class);
approximableCollectionTypes.add(NavigableSet.class);
approximableMapTypes.add(Map.class);
approximableMapTypes.add(SortedMap.class);
approximableMapTypes.add(NavigableMap.class);
// Common concrete collection classes
approximableCollectionTypes.add(ArrayList.class);
approximableCollectionTypes.add(LinkedList.class);
approximableCollectionTypes.add(HashSet.class);
approximableCollectionTypes.add(LinkedHashSet.class);
approximableCollectionTypes.add(TreeSet.class);
approximableCollectionTypes.add(EnumSet.class);
approximableMapTypes.add(HashMap.class);
approximableMapTypes.add(LinkedHashMap.class);
approximableMapTypes.add(TreeMap.class);
approximableMapTypes.add(EnumMap.class);
}
private CollectionFactory() {
}
/**
* Determine whether the given collection type is an approximable type,
* i.e. a type that {@link #createApproximateCollection} can approximate.
* @param collectionType the collection type to check
* @return {@code true} if the type is approximable
*/
public static boolean isApproximableCollectionType(@Nullable Class collectionType) {
return (collectionType != null && approximableCollectionTypes.contains(collectionType));
}
/**
* Create the most approximate collection for the given collection.
* Warning: Since the parameterized type {@code E} is
* not bound to the type of elements contained in the supplied
* {@code collection}, type safety cannot be guaranteed if the supplied
* {@code collection} is an {@link EnumSet}. In such scenarios, the caller
* is responsible for ensuring that the element type for the supplied
* {@code collection} is an enum type matching type {@code E}. As an
* alternative, the caller may wish to treat the return value as a raw
* collection or collection of {@link Object}.
* @param collection the original collection object, potentially {@code null}
* @param capacity the initial capacity
* @return a new, empty collection instance
* @see #isApproximableCollectionType
* @see java.util.LinkedList
* @see java.util.ArrayList
* @see java.util.EnumSet
* @see java.util.TreeSet
* @see java.util.LinkedHashSet
*/
@SuppressWarnings({"rawtypes", "unchecked", "cast"})
public static Collection createApproximateCollection(@Nullable Object collection, int capacity) {
if (collection instanceof LinkedList) {
return new LinkedList<>();
}
else if (collection instanceof List) {
return new ArrayList<>(capacity);
}
else if (collection instanceof EnumSet) {
// Cast is necessary for compilation in Eclipse 4.4.1.
Collection enumSet = (Collection) EnumSet.copyOf((EnumSet) collection);
enumSet.clear();
return enumSet;
}
else if (collection instanceof SortedSet) {
return new TreeSet<>(((SortedSet) collection).comparator());
}
else {
return new LinkedHashSet<>(capacity);
}
}
/**
* Create the most appropriate collection for the given collection type.
* Delegates to {@link #createCollection(Class, Class, int)} with a
* {@code null} element type.
* @param collectionType the desired type of the target collection (never {@code null})
* @param capacity the initial capacity
* @return a new collection instance
* @throws IllegalArgumentException if the supplied {@code collectionType}
* is {@code null} or of type {@link EnumSet}
*/
public static Collection createCollection(Class collectionType, int capacity) {
return createCollection(collectionType, null, capacity);
}
/**
* Create the most appropriate collection for the given collection type.
* Warning: Since the parameterized type {@code E} is
* not bound to the supplied {@code elementType}, type safety cannot be
* guaranteed if the desired {@code collectionType} is {@link EnumSet}.
* In such scenarios, the caller is responsible for ensuring that the
* supplied {@code elementType} is an enum type matching type {@code E}.
* As an alternative, the caller may wish to treat the return value as a
* raw collection or collection of {@link Object}.
* @param collectionType the desired type of the target collection (never {@code null})
* @param elementType the collection's element type, or {@code null} if unknown
* (note: only relevant for {@link EnumSet} creation)
* @param capacity the initial capacity
* @return a new collection instance
* @since 4.1.3
* @see java.util.LinkedHashSet
* @see java.util.ArrayList
* @see java.util.TreeSet
* @see java.util.EnumSet
* @throws IllegalArgumentException if the supplied {@code collectionType} is
* {@code null}; or if the desired {@code collectionType} is {@link EnumSet} and
* the supplied {@code elementType} is not a subtype of {@link Enum}
*/
@SuppressWarnings({"unchecked", "cast"})
public static Collection createCollection(Class collectionType, @Nullable Class elementType, int capacity) {
Assert.notNull(collectionType, "Collection type must not be null");
if (collectionType.isInterface()) {
if (Set.class == collectionType || Collection.class == collectionType) {
return new LinkedHashSet<>(capacity);
}
else if (List.class == collectionType) {
return new ArrayList<>(capacity);
}
else if (SortedSet.class == collectionType || NavigableSet.class == collectionType) {
return new TreeSet<>();
}
else {
throw new IllegalArgumentException("Unsupported Collection interface: " + collectionType.getName());
}
}
else if (EnumSet.class.isAssignableFrom(collectionType)) {
Assert.notNull(elementType, "Cannot create EnumSet for unknown element type");
// Cast is necessary for compilation in Eclipse 4.4.1.
return (Collection) EnumSet.noneOf(asEnumType(elementType));
}
else {
if (!Collection.class.isAssignableFrom(collectionType)) {
throw new IllegalArgumentException("Unsupported Collection type: " + collectionType.getName());
}
try {
return (Collection) ReflectionUtils.accessibleConstructor(collectionType).newInstance();
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Could not instantiate Collection type: " + collectionType.getName(), ex);
}
}
}
/**
* Determine whether the given map type is an approximable type,
* i.e. a type that {@link #createApproximateMap} can approximate.
* @param mapType the map type to check
* @return {@code true} if the type is approximable
*/
public static boolean isApproximableMapType(@Nullable Class mapType) {
return (mapType != null && approximableMapTypes.contains(mapType));
}
/**
* Create the most approximate map for the given map.
* Warning: Since the parameterized type {@code K} is
* not bound to the type of keys contained in the supplied {@code map},
* type safety cannot be guaranteed if the supplied {@code map} is an
* {@link EnumMap}. In such scenarios, the caller is responsible for
* ensuring that the key type in the supplied {@code map} is an enum type
* matching type {@code K}. As an alternative, the caller may wish to
* treat the return value as a raw map or map keyed by {@link Object}.
* @param map the original map object, potentially {@code null}
* @param capacity the initial capacity
* @return a new, empty map instance
* @see #isApproximableMapType
* @see java.util.EnumMap
* @see java.util.TreeMap
* @see java.util.LinkedHashMap
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public static Map createApproximateMap(@Nullable Object map, int capacity) {
if (map instanceof EnumMap) {
EnumMap enumMap = new EnumMap((EnumMap) map);
enumMap.clear();
return enumMap;
}
else if (map instanceof SortedMap) {
return new TreeMap<>(((SortedMap) map).comparator());
}
else {
return new LinkedHashMap<>(capacity);
}
}
/**
* Create the most appropriate map for the given map type.
* Delegates to {@link #createMap(Class, Class, int)} with a
* {@code null} key type.
* @param mapType the desired type of the target map
* @param capacity the initial capacity
* @return a new map instance
* @throws IllegalArgumentException if the supplied {@code mapType} is
* {@code null} or of type {@link EnumMap}
*/
public static Map createMap(Class mapType, int capacity) {
return createMap(mapType, null, capacity);
}
/**
* Create the most appropriate map for the given map type.
* Warning: Since the parameterized type {@code K}
* is not bound to the supplied {@code keyType}, type safety cannot be
* guaranteed if the desired {@code mapType} is {@link EnumMap}. In such
* scenarios, the caller is responsible for ensuring that the {@code keyType}
* is an enum type matching type {@code K}. As an alternative, the caller
* may wish to treat the return value as a raw map or map keyed by
* {@link Object}. Similarly, type safety cannot be enforced if the
* desired {@code mapType} is {@link MultiValueMap}.
* @param mapType the desired type of the target map (never {@code null})
* @param keyType the map's key type, or {@code null} if unknown
* (note: only relevant for {@link EnumMap} creation)
* @param capacity the initial capacity
* @return a new map instance
* @since 4.1.3
* @see java.util.LinkedHashMap
* @see java.util.TreeMap
* @see org.springframework.util.LinkedMultiValueMap
* @see java.util.EnumMap
* @throws IllegalArgumentException if the supplied {@code mapType} is
* {@code null}; or if the desired {@code mapType} is {@link EnumMap} and
* the supplied {@code keyType} is not a subtype of {@link Enum}
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public static Map createMap(Class mapType, @Nullable Class keyType, int capacity) {
Assert.notNull(mapType, "Map type must not be null");
if (mapType.isInterface()) {
if (Map.class == mapType) {
return new LinkedHashMap<>(capacity);
}
else if (SortedMap.class == mapType || NavigableMap.class == mapType) {
return new TreeMap<>();
}
else if (MultiValueMap.class == mapType) {
return new LinkedMultiValueMap();
}
else {
throw new IllegalArgumentException("Unsupported Map interface: " + mapType.getName());
}
}
else if (EnumMap.class == mapType) {
Assert.notNull(keyType, "Cannot create EnumMap for unknown key type");
return new EnumMap(asEnumType(keyType));
}
else {
if (!Map.class.isAssignableFrom(mapType)) {
throw new IllegalArgumentException("Unsupported Map type: " + mapType.getName());
}
try {
return (Map) ReflectionUtils.accessibleConstructor(mapType).newInstance();
}
catch (Throwable ex) {
throw new IllegalArgumentException("Could not instantiate Map type: " + mapType.getName(), ex);
}
}
}
/**
* Create a variant of {@link java.util.Properties} that automatically adapts
* non-String values to String representations in {@link Properties#getProperty}.
* In addition, the returned {@code Properties} instance sorts properties
* alphanumerically based on their keys.
* @return a new {@code Properties} instance
* @since 4.3.4
* @see #createSortedProperties(boolean)
* @see #createSortedProperties(Properties, boolean)
*/
@SuppressWarnings("serial")
public static Properties createStringAdaptingProperties() {
return new SortedProperties(false) {
@Override
@Nullable
public String getProperty(String key) {
Object value = get(key);
return (value != null ? value.toString() : null);
}
};
}
/**
* Create a variant of {@link java.util.Properties} that sorts properties
* alphanumerically based on their keys.
*
This can be useful when storing the {@link Properties} instance in a
* properties file, since it allows such files to be generated in a repeatable
* manner with consistent ordering of properties. Comments in generated
* properties files can also be optionally omitted.
* @param omitComments {@code true} if comments should be omitted when
* storing properties in a file
* @return a new {@code Properties} instance
* @since 5.2
* @see #createStringAdaptingProperties()
* @see #createSortedProperties(Properties, boolean)
*/
public static Properties createSortedProperties(boolean omitComments) {
return new SortedProperties(omitComments);
}
/**
* Create a variant of {@link java.util.Properties} that sorts properties
* alphanumerically based on their keys.
*
This can be useful when storing the {@code Properties} instance in a
* properties file, since it allows such files to be generated in a repeatable
* manner with consistent ordering of properties. Comments in generated
* properties files can also be optionally omitted.
*
The returned {@code Properties} instance will be populated with
* properties from the supplied {@code properties} object, but default
* properties from the supplied {@code properties} object will not be copied.
* @param properties the {@code Properties} object from which to copy the
* initial properties
* @param omitComments {@code true} if comments should be omitted when
* storing properties in a file
* @return a new {@code Properties} instance
* @since 5.2
* @see #createStringAdaptingProperties()
* @see #createSortedProperties(boolean)
*/
public static Properties createSortedProperties(Properties properties, boolean omitComments) {
return new SortedProperties(properties, omitComments);
}
/**
* Cast the given type to a subtype of {@link Enum}.
* @param enumType the enum type, never {@code null}
* @return the given type as subtype of {@link Enum}
* @throws IllegalArgumentException if the given type is not a subtype of {@link Enum}
*/
@SuppressWarnings("rawtypes")
private static Class asEnumType(Class enumType) {
Assert.notNull(enumType, "Enum type must not be null");
if (!Enum.class.isAssignableFrom(enumType)) {
throw new IllegalArgumentException("Supplied type is not an enum: " + enumType.getName());
}
return enumType.asSubclass(Enum.class);
}
}