replaced Commons Collections dependency with Spring-provided LinkedCaseInsensitiveMap; revised CollectionFactory and Spring Map implementations for consistency

This commit is contained in:
Juergen Hoeller
2009-05-12 23:37:43 +00:00
parent da71f266ae
commit 59101c096f
16 changed files with 227 additions and 141 deletions

View File

@@ -36,12 +36,7 @@ import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import org.apache.commons.collections.map.CaseInsensitiveMap;
import org.apache.commons.collections.map.ListOrderedMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.ClassUtils;
import org.springframework.util.LinkedCaseInsensitiveMap;
/**
* Factory for collections, being aware of Commons Collection 3.x's extended
@@ -58,13 +53,6 @@ import org.springframework.util.ClassUtils;
*/
public abstract class CollectionFactory {
private static final Log logger = LogFactory.getLog(CollectionFactory.class);
/** Whether the Commons Collections 3.x library is present on the classpath */
private static final boolean commonsCollections3Available =
ClassUtils.isPresent("org.apache.commons.collections.map.CaseInsensitiveMap",
CollectionFactory.class.getClassLoader());
private static final Set<Class> approximableCollectionTypes = new HashSet<Class>(10);
private static final Set<Class> approximableMapTypes = new HashSet<Class>(6);
@@ -129,23 +117,15 @@ public abstract class CollectionFactory {
}
/**
* Create a linked case-insensitive Map if possible: if Commons Collections
* 3.x is available, a CaseInsensitiveMap with ListOrderedMap decorator will
* be created. Else, a JDK {@link java.util.LinkedHashMap} will be used.
* Create a linked case-insensitive Map if possible: This implementation
* always returns a {@link org.springframework.util.LinkedCaseInsensitiveMap}.
* @param initialCapacity the initial capacity of the Map
* @return the new Map instance
* @see org.apache.commons.collections.map.CaseInsensitiveMap
* @see org.apache.commons.collections.map.ListOrderedMap
* @deprecated as of Spring 3.0, for usage on JDK 1.5 or higher
*/
public static <K,V> Map<K,V> createLinkedCaseInsensitiveMapIfPossible(int initialCapacity) {
if (commonsCollections3Available) {
logger.trace("Creating [org.apache.commons.collections.map.ListOrderedMap/CaseInsensitiveMap]");
return CommonsCollectionFactory.createListOrderedCaseInsensitiveMap(initialCapacity);
}
else {
logger.debug("Falling back to [java.util.LinkedHashMap] for linked case-insensitive map");
return new LinkedHashMap<K,V>(initialCapacity);
}
@Deprecated
public static Map createLinkedCaseInsensitiveMapIfPossible(int initialCapacity) {
return new LinkedCaseInsensitiveMap(initialCapacity);
}
/**
@@ -157,8 +137,8 @@ public abstract class CollectionFactory {
* @deprecated as of Spring 2.5, for usage on JDK 1.4 or higher
*/
@Deprecated
public static <K,V> Map<K,V> createIdentityMapIfPossible(int initialCapacity) {
return new IdentityHashMap<K,V>(initialCapacity);
public static Map createIdentityMapIfPossible(int initialCapacity) {
return new IdentityHashMap(initialCapacity);
}
/**
@@ -170,8 +150,8 @@ public abstract class CollectionFactory {
* @deprecated as of Spring 3.0, for usage on JDK 1.5 or higher
*/
@Deprecated
public static <K,V> Map<K,V> createConcurrentMapIfPossible(int initialCapacity) {
return new ConcurrentHashMap<K,V>(initialCapacity);
public static Map createConcurrentMapIfPossible(int initialCapacity) {
return new ConcurrentHashMap(initialCapacity);
}
/**
@@ -183,8 +163,8 @@ public abstract class CollectionFactory {
* @deprecated as of Spring 3.0, for usage on JDK 1.5 or higher
*/
@Deprecated
public static <K,V> ConcurrentMap<K,V> createConcurrentMap(int initialCapacity) {
return new JdkConcurrentHashMap<K,V>(initialCapacity);
public static ConcurrentMap createConcurrentMap(int initialCapacity) {
return new JdkConcurrentHashMap(initialCapacity);
}
/**
@@ -246,26 +226,12 @@ public abstract class CollectionFactory {
* @see java.util.LinkedHashMap
*/
@SuppressWarnings("unchecked")
public static <K,V> Map<K,V> createApproximateMap(Object map, int initialCapacity) {
public static Map createApproximateMap(Object map, int initialCapacity) {
if (map instanceof SortedMap) {
return new TreeMap<K,V>(((SortedMap<K,V>) map).comparator());
return new TreeMap(((SortedMap) map).comparator());
}
else {
return new LinkedHashMap<K,V>(initialCapacity);
}
}
/**
* Actual creation of Commons Collections.
* In separate inner class to avoid runtime dependency on Commons Collections 3.x.
*/
private static abstract class CommonsCollectionFactory {
@SuppressWarnings("unchecked")
private static <K,V> Map<K,V> createListOrderedCaseInsensitiveMap(int initialCapacity) {
// Commons Collections does not support initial capacity of 0.
return ListOrderedMap.decorate(new CaseInsensitiveMap(initialCapacity == 0 ? 1 : initialCapacity));
return new LinkedHashMap(initialCapacity);
}
}
@@ -274,7 +240,7 @@ public abstract class CollectionFactory {
* ConcurrentMap adapter for the JDK ConcurrentHashMap class.
*/
@Deprecated
private static class JdkConcurrentHashMap<K,V> extends ConcurrentHashMap<K,V> implements ConcurrentMap<K,V> {
private static class JdkConcurrentHashMap extends ConcurrentHashMap implements ConcurrentMap {
private JdkConcurrentHashMap(int initialCapacity) {
super(initialCapacity);

View File

@@ -33,14 +33,14 @@ import java.util.Map;
* is available on Java 5+ anyway
*/
@Deprecated
public interface ConcurrentMap<K,V> extends Map<K,V> {
public interface ConcurrentMap extends Map {
V putIfAbsent(K key, V value);
Object putIfAbsent(Object key, Object value);
boolean remove(Object key, Object value);
boolean replace(K key, V oldValue, V newValue);
boolean replace(Object key, Object oldValue, Object newValue);
V replace(K key, V value);
Object replace(Object key, Object value);
}

View File

@@ -0,0 +1,139 @@
/*
* Copyright 2002-2009 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
*
* http://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.util;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
/**
* {@link LinkedHashMap} variant that stores String keys in a case-insensitive
* manner, for example for key-based access in a results table.
*
* <p>Preserves the original order as well as the original casing of keys,
* while allowing for contains, get and remove calls with any case of key.
*
* <p>Does <i>not</i> support <code>null</code> keys.
*
* @author Juergen Hoeller
* @since 3.0
*/
public class LinkedCaseInsensitiveMap<V> extends LinkedHashMap<String, V> {
private final Map<String, String> caseInsensitiveKeys;
private final Locale locale;
/**
* Create a new LinkedCaseInsensitiveMap for the default Locale.
* @see java.lang.String#toLowerCase()
*/
public LinkedCaseInsensitiveMap() {
this(null);
}
/**
* Create a new LinkedCaseInsensitiveMap that stores lower-case keys
* according to the given Locale.
* @param locale the Locale to use for lower-case conversion
* @see java.lang.String#toLowerCase(java.util.Locale)
*/
public LinkedCaseInsensitiveMap(Locale locale) {
super();
this.caseInsensitiveKeys = new HashMap<String, String>();
this.locale = (locale != null ? locale : Locale.getDefault());
}
/**
* Create a new LinkedCaseInsensitiveMap that wraps a {@link LinkedHashMap}
* with the given initial capacity and stores lower-case keys according
* to the default Locale.
* @param initialCapacity the initial capacity
* @see java.lang.String#toLowerCase()
*/
public LinkedCaseInsensitiveMap(int initialCapacity) {
this(initialCapacity, null);
}
/**
* Create a new LinkedCaseInsensitiveMap that wraps a {@link LinkedHashMap}
* with the given initial capacity and stores lower-case keys according
* to the given Locale.
* @param initialCapacity the initial capacity
* @param locale the Locale to use for lower-case conversion
* @see java.lang.String#toLowerCase(java.util.Locale)
*/
public LinkedCaseInsensitiveMap(int initialCapacity, Locale locale) {
super(initialCapacity);
this.caseInsensitiveKeys = new HashMap<String, String>(initialCapacity);
this.locale = (locale != null ? locale : Locale.getDefault());
}
@Override
public V put(String key, V value) {
this.caseInsensitiveKeys.put(convertKey(key), key);
return super.put(key, value);
}
@Override
public boolean containsKey(Object key) {
return (key instanceof String && this.caseInsensitiveKeys.containsKey(convertKey((String) key)));
}
@Override
public V get(Object key) {
if (key instanceof String) {
return super.get(this.caseInsensitiveKeys.get(convertKey((String) key)));
}
else {
return null;
}
}
@Override
public V remove(Object key) {
if (key instanceof String ) {
return super.remove(this.caseInsensitiveKeys.remove(convertKey((String) key)));
}
else {
return null;
}
}
@Override
public void clear() {
this.caseInsensitiveKeys.clear();
super.clear();
}
/**
* Convert the given key to a case-insensitive key.
* <p>The default implementation converts the key
* to lower-case according to this Map's Locale.
* @param key the user-specified key
* @return the key to use for storing
* @see java.lang.String#toLowerCase(java.util.Locale)
*/
protected String convertKey(String key) {
return key.toLowerCase(this.locale);
}
}

View File

@@ -24,11 +24,12 @@ import java.util.Map;
import java.util.Set;
/**
* Simple implementation of {@link MultiValueMap} that wraps a plain {@code Map}
* (by default a {@link LinkedHashMap}, storing multiple values in a {@link LinkedList}.
* Simple implementation of {@link MultiValueMap} that wraps a {@link LinkedHashMap},
* storing multiple values in a {@link LinkedList}.
*
* <p>This Map implementation is generally not thread-safe. It is primarily designed
* for data structures exposed from request objects, for use in a single thread only.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @since 3.0
@@ -37,15 +38,17 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V> {
private final Map<K, List<V>> targetMap;
/**
* Create a new SimpleMultiValueMap that wraps a newly created {@link LinkedHashMap}.
* Create a new LinkedMultiValueMap that wraps a {@link LinkedHashMap}.
*/
public LinkedMultiValueMap() {
this.targetMap = new LinkedHashMap<K, List<V>>();
}
/**
* Create a new SimpleMultiValueMap that wraps a newly created {@link LinkedHashMap} with the given initial capacity.
* Create a new LinkedMultiValueMap that wraps a {@link LinkedHashMap}
* with the given initial capacity.
* @param initialCapacity the initial capacity
*/
public LinkedMultiValueMap(int initialCapacity) {
@@ -53,17 +56,15 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V> {
}
/**
* Create a new SimpleMultiValueMap that wraps the given target Map.
* <p>Note: The given Map will be used as active underlying Map.
* Any changes in the underlying map will be reflected in the
* MultiValueMap object, and vice versa.
* @param targetMap the target Map to wrap
* Copy constructor: Create a new LinkedMultiValueMap with the same mappings
* as the specified Map.
* @param otherMap the Map whose mappings are to be placed in this Map
*/
public LinkedMultiValueMap(Map<K, List<V>> targetMap) {
Assert.notNull(targetMap, "'targetMap' must not be null");
this.targetMap = targetMap;
public LinkedMultiValueMap(Map<K, List<V>> otherMap) {
this.targetMap = new LinkedHashMap<K, List<V>>(otherMap);
}
// MultiValueMap implementation
public void add(K key, V value) {
@@ -86,6 +87,7 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V> {
this.targetMap.put(key, values);
}
// Map implementation
public int size() {
@@ -136,6 +138,7 @@ public class LinkedMultiValueMap<K, V> implements MultiValueMap<K, V> {
return this.targetMap.entrySet();
}
@Override
public boolean equals(Object obj) {
return this.targetMap.equals(obj);