blob: e0bfe57515236d85c95af643e90257b41a0b108d [file] [log] [blame]
/*
* Copyright 2001-2005 The Apache Software Foundation
*
* 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.apache.commons.collections;
import java.io.PrintStream;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.commons.collections.map.FixedSizeMap;
import org.apache.commons.collections.map.FixedSizeSortedMap;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.LazySortedMap;
import org.apache.commons.collections.map.ListOrderedMap;
import org.apache.commons.collections.map.MultiValueMap;
import org.apache.commons.collections.map.PredicatedMap;
import org.apache.commons.collections.map.PredicatedSortedMap;
import org.apache.commons.collections.map.TransformedMap;
import org.apache.commons.collections.map.TransformedSortedMap;
import org.apache.commons.collections.map.TypedMap;
import org.apache.commons.collections.map.TypedSortedMap;
import org.apache.commons.collections.map.UnmodifiableMap;
import org.apache.commons.collections.map.UnmodifiableSortedMap;
/**
* Provides utility methods and decorators for
* {@link Map} and {@link SortedMap} instances.
* <p>
* It contains various type safe methods
* as well as other useful features like deep copying.
* <p>
* It also provides the following decorators:
*
* <ul>
* <li>{@link #fixedSizeMap(Map)}
* <li>{@link #fixedSizeSortedMap(SortedMap)}
* <li>{@link #lazyMap(Map,Factory)}
* <li>{@link #lazyMap(Map,Transformer)}
* <li>{@link #lazySortedMap(SortedMap,Factory)}
* <li>{@link #lazySortedMap(SortedMap,Transformer)}
* <li>{@link #predicatedMap(Map,Predicate,Predicate)}
* <li>{@link #predicatedSortedMap(SortedMap,Predicate,Predicate)}
* <li>{@link #transformedMap(Map, Transformer, Transformer)}
* <li>{@link #transformedSortedMap(SortedMap, Transformer, Transformer)}
* <li>{@link #typedMap(Map, Class, Class)}
* <li>{@link #typedSortedMap(SortedMap, Class, Class)}
* <li>{@link #multiValueMap( Map )}
* <li>{@link #multiValueMap( Map, Class )}
* <li>{@link #multiValueMap( Map, Factory )}
* </ul>
*
* @since Commons Collections 1.0
* @version $Revision: 1.1 $ $Date: 2009/05/27 22:16:18 $
*
* @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
* @author <a href="mailto:nissim@nksystems.com">Nissim Karpenstein</a>
* @author <a href="mailto:knielsen@apache.org">Kasper Nielsen</a>
* @author Paul Jack
* @author Stephen Colebourne
* @author Matthew Hawthorne
* @author Arun Mammen Thomas
* @author Janek Bogucki
* @author Max Rydahl Andersen
* @author <a href="mailto:equinus100@hotmail.com">Ashwin S</a>
* @author <a href="mailto:jcarman@apache.org">James Carman</a>
* @author Neil O'Toole
*/
public class MapUtils {
/**
* An empty unmodifiable map.
* This was not provided in JDK1.2.
*/
public static final Map EMPTY_MAP = UnmodifiableMap.decorate(new HashMap(1));
/**
* An empty unmodifiable sorted map.
* This is not provided in the JDK.
*/
public static final SortedMap EMPTY_SORTED_MAP = UnmodifiableSortedMap.decorate(new TreeMap());
/**
* String used to indent the verbose and debug Map prints.
*/
private static final String INDENT_STRING = " ";
/**
* <code>MapUtils</code> should not normally be instantiated.
*/
public MapUtils() {
}
// Type safe getters
//-------------------------------------------------------------------------
/**
* Gets from a Map in a null-safe manner.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map, <code>null</code> if null map input
*/
public static Object getObject(final Map map, final Object key) {
if (map != null) {
return map.get(key);
}
return null;
}
/**
* Gets a String from a Map in a null-safe manner.
* <p>
* The String is obtained via <code>toString</code>.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as a String, <code>null</code> if null map input
*/
public static String getString(final Map map, final Object key) {
if (map != null) {
Object answer = map.get(key);
if (answer != null) {
return answer.toString();
}
}
return null;
}
/**
* Gets a Boolean from a Map in a null-safe manner.
* <p>
* If the value is a <code>Boolean</code> it is returned directly.
* If the value is a <code>String</code> and it equals 'true' ignoring case
* then <code>true</code> is returned, otherwise <code>false</code>.
* If the value is a <code>Number</code> an integer zero value returns
* <code>false</code> and non-zero returns <code>true</code>.
* Otherwise, <code>null</code> is returned.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as a Boolean, <code>null</code> if null map input
*/
public static Boolean getBoolean(final Map map, final Object key) {
if (map != null) {
Object answer = map.get(key);
if (answer != null) {
if (answer instanceof Boolean) {
return (Boolean) answer;
} else if (answer instanceof String) {
return new Boolean((String) answer);
} else if (answer instanceof Number) {
Number n = (Number) answer;
return (n.intValue() != 0) ? Boolean.TRUE : Boolean.FALSE;
}
}
}
return null;
}
/**
* Gets a Number from a Map in a null-safe manner.
* <p>
* If the value is a <code>Number</code> it is returned directly.
* If the value is a <code>String</code> it is converted using
* {@link NumberFormat#parse(String)} on the system default formatter
* returning <code>null</code> if the conversion fails.
* Otherwise, <code>null</code> is returned.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as a Number, <code>null</code> if null map input
*/
public static Number getNumber(final Map map, final Object key) {
if (map != null) {
Object answer = map.get(key);
if (answer != null) {
if (answer instanceof Number) {
return (Number) answer;
} else if (answer instanceof String) {
try {
String text = (String) answer;
return NumberFormat.getInstance().parse(text);
} catch (ParseException e) {
logInfo(e);
}
}
}
}
return null;
}
/**
* Gets a Byte from a Map in a null-safe manner.
* <p>
* The Byte is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as a Byte, <code>null</code> if null map input
*/
public static Byte getByte(final Map map, final Object key) {
Number answer = getNumber(map, key);
if (answer == null) {
return null;
} else if (answer instanceof Byte) {
return (Byte) answer;
}
return new Byte(answer.byteValue());
}
/**
* Gets a Short from a Map in a null-safe manner.
* <p>
* The Short is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as a Short, <code>null</code> if null map input
*/
public static Short getShort(final Map map, final Object key) {
Number answer = getNumber(map, key);
if (answer == null) {
return null;
} else if (answer instanceof Short) {
return (Short) answer;
}
return new Short(answer.shortValue());
}
/**
* Gets a Integer from a Map in a null-safe manner.
* <p>
* The Integer is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as a Integer, <code>null</code> if null map input
*/
public static Integer getInteger(final Map map, final Object key) {
Number answer = getNumber(map, key);
if (answer == null) {
return null;
} else if (answer instanceof Integer) {
return (Integer) answer;
}
return new Integer(answer.intValue());
}
/**
* Gets a Long from a Map in a null-safe manner.
* <p>
* The Long is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as a Long, <code>null</code> if null map input
*/
public static Long getLong(final Map map, final Object key) {
Number answer = getNumber(map, key);
if (answer == null) {
return null;
} else if (answer instanceof Long) {
return (Long) answer;
}
return new Long(answer.longValue());
}
/**
* Gets a Float from a Map in a null-safe manner.
* <p>
* The Float is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as a Float, <code>null</code> if null map input
*/
public static Float getFloat(final Map map, final Object key) {
Number answer = getNumber(map, key);
if (answer == null) {
return null;
} else if (answer instanceof Float) {
return (Float) answer;
}
return new Float(answer.floatValue());
}
/**
* Gets a Double from a Map in a null-safe manner.
* <p>
* The Double is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as a Double, <code>null</code> if null map input
*/
public static Double getDouble(final Map map, final Object key) {
Number answer = getNumber(map, key);
if (answer == null) {
return null;
} else if (answer instanceof Double) {
return (Double) answer;
}
return new Double(answer.doubleValue());
}
/**
* Gets a Map from a Map in a null-safe manner.
* <p>
* If the value returned from the specified map is not a Map then
* <code>null</code> is returned.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as a Map, <code>null</code> if null map input
*/
public static Map getMap(final Map map, final Object key) {
if (map != null) {
Object answer = map.get(key);
if (answer != null && answer instanceof Map) {
return (Map) answer;
}
}
return null;
}
// Type safe getters with default values
//-------------------------------------------------------------------------
/**
* Looks up the given key in the given map, converting null into the
* given default value.
*
* @param map the map whose value to look up
* @param key the key of the value to look up in that map
* @param defaultValue what to return if the value is null
* @return the value in the map, or defaultValue if the original value
* is null or the map is null
*/
public static Object getObject( Map map, Object key, Object defaultValue ) {
if ( map != null ) {
Object answer = map.get( key );
if ( answer != null ) {
return answer;
}
}
return defaultValue;
}
/**
* Looks up the given key in the given map, converting the result into
* a string, using the default value if the the conversion fails.
*
* @param map the map whose value to look up
* @param key the key of the value to look up in that map
* @param defaultValue what to return if the value is null or if the
* conversion fails
* @return the value in the map as a string, or defaultValue if the
* original value is null, the map is null or the string conversion
* fails
*/
public static String getString( Map map, Object key, String defaultValue ) {
String answer = getString( map, key );
if ( answer == null ) {
answer = defaultValue;
}
return answer;
}
/**
* Looks up the given key in the given map, converting the result into
* a boolean, using the default value if the the conversion fails.
*
* @param map the map whose value to look up
* @param key the key of the value to look up in that map
* @param defaultValue what to return if the value is null or if the
* conversion fails
* @return the value in the map as a boolean, or defaultValue if the
* original value is null, the map is null or the boolean conversion
* fails
*/
public static Boolean getBoolean( Map map, Object key, Boolean defaultValue ) {
Boolean answer = getBoolean( map, key );
if ( answer == null ) {
answer = defaultValue;
}
return answer;
}
/**
* Looks up the given key in the given map, converting the result into
* a number, using the default value if the the conversion fails.
*
* @param map the map whose value to look up
* @param key the key of the value to look up in that map
* @param defaultValue what to return if the value is null or if the
* conversion fails
* @return the value in the map as a number, or defaultValue if the
* original value is null, the map is null or the number conversion
* fails
*/
public static Number getNumber( Map map, Object key, Number defaultValue ) {
Number answer = getNumber( map, key );
if ( answer == null ) {
answer = defaultValue;
}
return answer;
}
/**
* Looks up the given key in the given map, converting the result into
* a byte, using the default value if the the conversion fails.
*
* @param map the map whose value to look up
* @param key the key of the value to look up in that map
* @param defaultValue what to return if the value is null or if the
* conversion fails
* @return the value in the map as a number, or defaultValue if the
* original value is null, the map is null or the number conversion
* fails
*/
public static Byte getByte( Map map, Object key, Byte defaultValue ) {
Byte answer = getByte( map, key );
if ( answer == null ) {
answer = defaultValue;
}
return answer;
}
/**
* Looks up the given key in the given map, converting the result into
* a short, using the default value if the the conversion fails.
*
* @param map the map whose value to look up
* @param key the key of the value to look up in that map
* @param defaultValue what to return if the value is null or if the
* conversion fails
* @return the value in the map as a number, or defaultValue if the
* original value is null, the map is null or the number conversion
* fails
*/
public static Short getShort( Map map, Object key, Short defaultValue ) {
Short answer = getShort( map, key );
if ( answer == null ) {
answer = defaultValue;
}
return answer;
}
/**
* Looks up the given key in the given map, converting the result into
* an integer, using the default value if the the conversion fails.
*
* @param map the map whose value to look up
* @param key the key of the value to look up in that map
* @param defaultValue what to return if the value is null or if the
* conversion fails
* @return the value in the map as a number, or defaultValue if the
* original value is null, the map is null or the number conversion
* fails
*/
public static Integer getInteger( Map map, Object key, Integer defaultValue ) {
Integer answer = getInteger( map, key );
if ( answer == null ) {
answer = defaultValue;
}
return answer;
}
/**
* Looks up the given key in the given map, converting the result into
* a long, using the default value if the the conversion fails.
*
* @param map the map whose value to look up
* @param key the key of the value to look up in that map
* @param defaultValue what to return if the value is null or if the
* conversion fails
* @return the value in the map as a number, or defaultValue if the
* original value is null, the map is null or the number conversion
* fails
*/
public static Long getLong( Map map, Object key, Long defaultValue ) {
Long answer = getLong( map, key );
if ( answer == null ) {
answer = defaultValue;
}
return answer;
}
/**
* Looks up the given key in the given map, converting the result into
* a float, using the default value if the the conversion fails.
*
* @param map the map whose value to look up
* @param key the key of the value to look up in that map
* @param defaultValue what to return if the value is null or if the
* conversion fails
* @return the value in the map as a number, or defaultValue if the
* original value is null, the map is null or the number conversion
* fails
*/
public static Float getFloat( Map map, Object key, Float defaultValue ) {
Float answer = getFloat( map, key );
if ( answer == null ) {
answer = defaultValue;
}
return answer;
}
/**
* Looks up the given key in the given map, converting the result into
* a double, using the default value if the the conversion fails.
*
* @param map the map whose value to look up
* @param key the key of the value to look up in that map
* @param defaultValue what to return if the value is null or if the
* conversion fails
* @return the value in the map as a number, or defaultValue if the
* original value is null, the map is null or the number conversion
* fails
*/
public static Double getDouble( Map map, Object key, Double defaultValue ) {
Double answer = getDouble( map, key );
if ( answer == null ) {
answer = defaultValue;
}
return answer;
}
/**
* Looks up the given key in the given map, converting the result into
* a map, using the default value if the the conversion fails.
*
* @param map the map whose value to look up
* @param key the key of the value to look up in that map
* @param defaultValue what to return if the value is null or if the
* conversion fails
* @return the value in the map as a number, or defaultValue if the
* original value is null, the map is null or the map conversion
* fails
*/
public static Map getMap( Map map, Object key, Map defaultValue ) {
Map answer = getMap( map, key );
if ( answer == null ) {
answer = defaultValue;
}
return answer;
}
// Type safe primitive getters
//-------------------------------------------------------------------------
/**
* Gets a boolean from a Map in a null-safe manner.
* <p>
* If the value is a <code>Boolean</code> its value is returned.
* If the value is a <code>String</code> and it equals 'true' ignoring case
* then <code>true</code> is returned, otherwise <code>false</code>.
* If the value is a <code>Number</code> an integer zero value returns
* <code>false</code> and non-zero returns <code>true</code>.
* Otherwise, <code>false</code> is returned.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as a Boolean, <code>false</code> if null map input
*/
public static boolean getBooleanValue(final Map map, final Object key) {
Boolean booleanObject = getBoolean(map, key);
if (booleanObject == null) {
return false;
}
return booleanObject.booleanValue();
}
/**
* Gets a byte from a Map in a null-safe manner.
* <p>
* The byte is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as a byte, <code>0</code> if null map input
*/
public static byte getByteValue(final Map map, final Object key) {
Byte byteObject = getByte(map, key);
if (byteObject == null) {
return 0;
}
return byteObject.byteValue();
}
/**
* Gets a short from a Map in a null-safe manner.
* <p>
* The short is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as a short, <code>0</code> if null map input
*/
public static short getShortValue(final Map map, final Object key) {
Short shortObject = getShort(map, key);
if (shortObject == null) {
return 0;
}
return shortObject.shortValue();
}
/**
* Gets an int from a Map in a null-safe manner.
* <p>
* The int is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as an int, <code>0</code> if null map input
*/
public static int getIntValue(final Map map, final Object key) {
Integer integerObject = getInteger(map, key);
if (integerObject == null) {
return 0;
}
return integerObject.intValue();
}
/**
* Gets a long from a Map in a null-safe manner.
* <p>
* The long is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as a long, <code>0L</code> if null map input
*/
public static long getLongValue(final Map map, final Object key) {
Long longObject = getLong(map, key);
if (longObject == null) {
return 0L;
}
return longObject.longValue();
}
/**
* Gets a float from a Map in a null-safe manner.
* <p>
* The float is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as a float, <code>0.0F</code> if null map input
*/
public static float getFloatValue(final Map map, final Object key) {
Float floatObject = getFloat(map, key);
if (floatObject == null) {
return 0f;
}
return floatObject.floatValue();
}
/**
* Gets a double from a Map in a null-safe manner.
* <p>
* The double is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @return the value in the Map as a double, <code>0.0</code> if null map input
*/
public static double getDoubleValue(final Map map, final Object key) {
Double doubleObject = getDouble(map, key);
if (doubleObject == null) {
return 0d;
}
return doubleObject.doubleValue();
}
// Type safe primitive getters with default values
//-------------------------------------------------------------------------
/**
* Gets a boolean from a Map in a null-safe manner,
* using the default value if the the conversion fails.
* <p>
* If the value is a <code>Boolean</code> its value is returned.
* If the value is a <code>String</code> and it equals 'true' ignoring case
* then <code>true</code> is returned, otherwise <code>false</code>.
* If the value is a <code>Number</code> an integer zero value returns
* <code>false</code> and non-zero returns <code>true</code>.
* Otherwise, <code>defaultValue</code> is returned.
*
* @param map the map to use
* @param key the key to look up
* @param defaultValue return if the value is null or if the
* conversion fails
* @return the value in the Map as a Boolean, <code>defaultValue</code> if null map input
*/
public static boolean getBooleanValue(final Map map, final Object key, boolean defaultValue) {
Boolean booleanObject = getBoolean(map, key);
if (booleanObject == null) {
return defaultValue;
}
return booleanObject.booleanValue();
}
/**
* Gets a byte from a Map in a null-safe manner,
* using the default value if the the conversion fails.
* <p>
* The byte is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @param defaultValue return if the value is null or if the
* conversion fails
* @return the value in the Map as a byte, <code>defaultValue</code> if null map input
*/
public static byte getByteValue(final Map map, final Object key, byte defaultValue) {
Byte byteObject = getByte(map, key);
if (byteObject == null) {
return defaultValue;
}
return byteObject.byteValue();
}
/**
* Gets a short from a Map in a null-safe manner,
* using the default value if the the conversion fails.
* <p>
* The short is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @param defaultValue return if the value is null or if the
* conversion fails
* @return the value in the Map as a short, <code>defaultValue</code> if null map input
*/
public static short getShortValue(final Map map, final Object key, short defaultValue) {
Short shortObject = getShort(map, key);
if (shortObject == null) {
return defaultValue;
}
return shortObject.shortValue();
}
/**
* Gets an int from a Map in a null-safe manner,
* using the default value if the the conversion fails.
* <p>
* The int is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @param defaultValue return if the value is null or if the
* conversion fails
* @return the value in the Map as an int, <code>defaultValue</code> if null map input
*/
public static int getIntValue(final Map map, final Object key, int defaultValue) {
Integer integerObject = getInteger(map, key);
if (integerObject == null) {
return defaultValue;
}
return integerObject.intValue();
}
/**
* Gets a long from a Map in a null-safe manner,
* using the default value if the the conversion fails.
* <p>
* The long is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @param defaultValue return if the value is null or if the
* conversion fails
* @return the value in the Map as a long, <code>defaultValue</code> if null map input
*/
public static long getLongValue(final Map map, final Object key, long defaultValue) {
Long longObject = getLong(map, key);
if (longObject == null) {
return defaultValue;
}
return longObject.longValue();
}
/**
* Gets a float from a Map in a null-safe manner,
* using the default value if the the conversion fails.
* <p>
* The float is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @param defaultValue return if the value is null or if the
* conversion fails
* @return the value in the Map as a float, <code>defaultValue</code> if null map input
*/
public static float getFloatValue(final Map map, final Object key, float defaultValue) {
Float floatObject = getFloat(map, key);
if (floatObject == null) {
return defaultValue;
}
return floatObject.floatValue();
}
/**
* Gets a double from a Map in a null-safe manner,
* using the default value if the the conversion fails.
* <p>
* The double is obtained from the results of {@link #getNumber(Map,Object)}.
*
* @param map the map to use
* @param key the key to look up
* @param defaultValue return if the value is null or if the
* conversion fails
* @return the value in the Map as a double, <code>defaultValue</code> if null map input
*/
public static double getDoubleValue(final Map map, final Object key, double defaultValue) {
Double doubleObject = getDouble(map, key);
if (doubleObject == null) {
return defaultValue;
}
return doubleObject.doubleValue();
}
// Conversion methods
//-------------------------------------------------------------------------
/**
* Gets a new Properties object initialised with the values from a Map.
* A null input will return an empty properties object.
*
* @param map the map to convert to a Properties object, may not be null
* @return the properties object
*/
public static Properties toProperties(final Map map) {
Properties answer = new Properties();
if (map != null) {
for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object value = entry.getValue();
answer.put(key, value);
}
}
return answer;
}
/**
* Creates a new HashMap using data copied from a ResourceBundle.
*
* @param resourceBundle the resource bundle to convert, may not be null
* @return the hashmap containing the data
* @throws NullPointerException if the bundle is null
*/
public static Map toMap(final ResourceBundle resourceBundle) {
Enumeration enumeration = resourceBundle.getKeys();
Map map = new HashMap();
while (enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement();
Object value = resourceBundle.getObject(key);
map.put(key, value);
}
return map;
}
// Printing methods
//-------------------------------------------------------------------------
/**
* Prints the given map with nice line breaks.
* <p>
* This method prints a nicely formatted String describing the Map.
* Each map entry will be printed with key and value.
* When the value is a Map, recursive behaviour occurs.
* <p>
* This method is NOT thread-safe in any special way. You must manually
* synchronize on either this class or the stream as required.
*
* @param out the stream to print to, must not be null
* @param label The label to be used, may be <code>null</code>.
* If <code>null</code>, the label is not output.
* It typically represents the name of the property in a bean or similar.
* @param map The map to print, may be <code>null</code>.
* If <code>null</code>, the text 'null' is output.
* @throws NullPointerException if the stream is <code>null</code>
*/
public static void verbosePrint(
final PrintStream out,
final Object label,
final Map map) {
verbosePrintInternal(out, label, map, new ArrayStack(), false);
}
/**
* Prints the given map with nice line breaks.
* <p>
* This method prints a nicely formatted String describing the Map.
* Each map entry will be printed with key, value and value classname.
* When the value is a Map, recursive behaviour occurs.
* <p>
* This method is NOT thread-safe in any special way. You must manually
* synchronize on either this class or the stream as required.
*
* @param out the stream to print to, must not be null
* @param label The label to be used, may be <code>null</code>.
* If <code>null</code>, the label is not output.
* It typically represents the name of the property in a bean or similar.
* @param map The map to print, may be <code>null</code>.
* If <code>null</code>, the text 'null' is output.
* @throws NullPointerException if the stream is <code>null</code>
*/
public static void debugPrint(
final PrintStream out,
final Object label,
final Map map) {
verbosePrintInternal(out, label, map, new ArrayStack(), true);
}
// Implementation methods
//-------------------------------------------------------------------------
/**
* Logs the given exception to <code>System.out</code>.
* <p>
* This method exists as Jakarta Collections does not depend on logging.
*
* @param ex the exception to log
*/
protected static void logInfo(final Exception ex) {
System.out.println("INFO: Exception: " + ex);
}
/**
* Implementation providing functionality for {@link #debugPrint} and for
* {@link #verbosePrint}. This prints the given map with nice line breaks.
* If the debug flag is true, it additionally prints the type of the object
* value. If the contents of a map include the map itself, then the text
* <em>(this Map)</em> is printed out. If the contents include a
* parent container of the map, the the text <em>(ancestor[i] Map)</em> is
* printed, where i actually indicates the number of levels which must be
* traversed in the sequential list of ancestors (e.g. father, grandfather,
* great-grandfather, etc).
*
* @param out the stream to print to
* @param label the label to be used, may be <code>null</code>.
* If <code>null</code>, the label is not output.
* It typically represents the name of the property in a bean or similar.
* @param map the map to print, may be <code>null</code>.
* If <code>null</code>, the text 'null' is output
* @param lineage a stack consisting of any maps in which the previous
* argument is contained. This is checked to avoid infinite recursion when
* printing the output
* @param debug flag indicating whether type names should be output.
* @throws NullPointerException if the stream is <code>null</code>
*/
private static void verbosePrintInternal(
final PrintStream out,
final Object label,
final Map map,
final ArrayStack lineage,
final boolean debug) {
printIndent(out, lineage.size());
if (map == null) {
if (label != null) {
out.print(label);
out.print(" = ");
}
out.println("null");
return;
}
if (label != null) {
out.print(label);
out.println(" = ");
}
printIndent(out, lineage.size());
out.println("{");
lineage.push(map);
for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
Object childKey = entry.getKey();
Object childValue = entry.getValue();
if (childValue instanceof Map && !lineage.contains(childValue)) {
verbosePrintInternal(
out,
(childKey == null ? "null" : childKey),
(Map) childValue,
lineage,
debug);
} else {
printIndent(out, lineage.size());
out.print(childKey);
out.print(" = ");
final int lineageIndex = lineage.indexOf(childValue);
if (lineageIndex == -1) {
out.print(childValue);
} else if (lineage.size() - 1 == lineageIndex) {
out.print("(this Map)");
} else {
out.print(
"(ancestor["
+ (lineage.size() - 1 - lineageIndex - 1)
+ "] Map)");
}
if (debug && childValue != null) {
out.print(' ');
out.println(childValue.getClass().getName());
} else {
out.println();
}
}
}
lineage.pop();
printIndent(out, lineage.size());
out.println(debug ? "} " + map.getClass().getName() : "}");
}
/**
* Writes indentation to the given stream.
*
* @param out the stream to indent
*/
private static void printIndent(final PrintStream out, final int indent) {
for (int i = 0; i < indent; i++) {
out.print(INDENT_STRING);
}
}
// Misc
//-----------------------------------------------------------------------
/**
* Inverts the supplied map returning a new HashMap such that the keys of
* the input are swapped with the values.
* <p>
* This operation assumes that the inverse mapping is well defined.
* If the input map had multiple entries with the same value mapped to
* different keys, the returned map will map one of those keys to the
* value, but the exact key which will be mapped is undefined.
*
* @param map the map to invert, may not be null
* @return a new HashMap containing the inverted data
* @throws NullPointerException if the map is null
*/
public static Map invertMap(Map map) {
Map out = new HashMap(map.size());
for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
out.put(entry.getValue(), entry.getKey());
}
return out;
}
//-----------------------------------------------------------------------
/**
* Protects against adding null values to a map.
* <p>
* This method checks the value being added to the map, and if it is null
* it is replaced by an empty string.
* <p>
* This could be useful if the map does not accept null values, or for
* receiving data from a source that may provide null or empty string
* which should be held in the same way in the map.
* <p>
* Keys are not validated.
*
* @param map the map to add to, may not be null
* @param key the key
* @param value the value, null converted to ""
* @throws NullPointerException if the map is null
*/
public static void safeAddToMap(Map map, Object key, Object value) throws NullPointerException {
if (value == null) {
map.put(key, "");
} else {
map.put(key, value);
}
}
//-----------------------------------------------------------------------
/**
* Puts all the keys and values from the specified array into the map.
* <p>
* This method is an alternative to the {@link java.util.Map#putAll(java.util.Map)}
* method and constructors. It allows you to build a map from an object array
* of various possible styles.
* <p>
* If the first entry in the object array implements {@link java.util.Map.Entry}
* or {@link KeyValue} then the key and value are added from that object.
* If the first entry in the object array is an object array itself, then
* it is assumed that index 0 in the sub-array is the key and index 1 is the value.
* Otherwise, the array is treated as keys and values in alternate indices.
* <p>
* For example, to create a color map:
* <pre>
* Map colorMap = MapUtils.putAll(new HashMap(), new String[][] {
* {"RED", "#FF0000"},
* {"GREEN", "#00FF00"},
* {"BLUE", "#0000FF"}
* });
* </pre>
* or:
* <pre>
* Map colorMap = MapUtils.putAll(new HashMap(), new String[] {
* "RED", "#FF0000",
* "GREEN", "#00FF00",
* "BLUE", "#0000FF"
* });
* </pre>
* or:
* <pre>
* Map colorMap = MapUtils.putAll(new HashMap(), new Map.Entry[] {
* new DefaultMapEntry("RED", "#FF0000"),
* new DefaultMapEntry("GREEN", "#00FF00"),
* new DefaultMapEntry("BLUE", "#0000FF")
* });
* </pre>
*
* @param map the map to populate, must not be null
* @param array an array to populate from, null ignored
* @return the input map
* @throws NullPointerException if map is null
* @throws IllegalArgumentException if sub-array or entry matching used and an
* entry is invalid
* @throws ClassCastException if the array contents is mixed
* @since Commons Collections 3.2
*/
public static Map putAll(Map map, Object[] array) {
map.size(); // force NPE
if (array == null || array.length == 0) {
return map;
}
Object obj = array[0];
if (obj instanceof Map.Entry) {
for (int i = 0; i < array.length; i++) {
Map.Entry entry = (Map.Entry) array[i];
map.put(entry.getKey(), entry.getValue());
}
} else if (obj instanceof KeyValue) {
for (int i = 0; i < array.length; i++) {
KeyValue keyval = (KeyValue) array[i];
map.put(keyval.getKey(), keyval.getValue());
}
} else if (obj instanceof Object[]) {
for (int i = 0; i < array.length; i++) {
Object[] sub = (Object[]) array[i];
if (sub == null || sub.length < 2) {
throw new IllegalArgumentException("Invalid array element: " + i);
}
map.put(sub[0], sub[1]);
}
} else {
for (int i = 0; i < array.length - 1;) {
map.put(array[i++], array[i++]);
}
}
return map;
}
//-----------------------------------------------------------------------
/**
* Null-safe check if the specified map is empty.
* <p>
* Null returns true.
*
* @param map the map to check, may be null
* @return true if empty or null
* @since Commons Collections 3.2
*/
public static boolean isEmpty(Map map) {
return (map == null || map.isEmpty());
}
/**
* Null-safe check if the specified map is not empty.
* <p>
* Null returns false.
*
* @param map the map to check, may be null
* @return true if non-null and non-empty
* @since Commons Collections 3.2
*/
public static boolean isNotEmpty(Map map) {
return !MapUtils.isEmpty(map);
}
// Map decorators
//-----------------------------------------------------------------------
/**
* Returns a synchronized map backed by the given map.
* <p>
* You must manually synchronize on the returned buffer's iterator to
* avoid non-deterministic behavior:
*
* <pre>
* Map m = MapUtils.synchronizedMap(myMap);
* Set s = m.keySet(); // outside synchronized block
* synchronized (m) { // synchronized on MAP!
* Iterator i = s.iterator();
* while (i.hasNext()) {
* process (i.next());
* }
* }
* </pre>
*
* This method uses the implementation in {@link java.util.Collections Collections}.
*
* @param map the map to synchronize, must not be null
* @return a synchronized map backed by the given map
* @throws IllegalArgumentException if the map is null
*/
public static Map synchronizedMap(Map map) {
return Collections.synchronizedMap(map);
}
/**
* Returns an unmodifiable map backed by the given map.
* <p>
* This method uses the implementation in the decorators subpackage.
*
* @param map the map to make unmodifiable, must not be null
* @return an unmodifiable map backed by the given map
* @throws IllegalArgumentException if the map is null
*/
public static Map unmodifiableMap(Map map) {
return UnmodifiableMap.decorate(map);
}
/**
* Returns a predicated (validating) map backed by the given map.
* <p>
* Only objects that pass the tests in the given predicates can be added to the map.
* Trying to add an invalid object results in an IllegalArgumentException.
* Keys must pass the key predicate, values must pass the value predicate.
* It is important not to use the original map after invoking this method,
* as it is a backdoor for adding invalid objects.
*
* @param map the map to predicate, must not be null
* @param keyPred the predicate for keys, null means no check
* @param valuePred the predicate for values, null means no check
* @return a predicated map backed by the given map
* @throws IllegalArgumentException if the Map is null
*/
public static Map predicatedMap(Map map, Predicate keyPred, Predicate valuePred) {
return PredicatedMap.decorate(map, keyPred, valuePred);
}
/**
* Returns a typed map backed by the given map.
* <p>
* Only keys and values of the specified types can be added to the map.
*
* @param map the map to limit to a specific type, must not be null
* @param keyType the type of keys which may be added to the map, must not be null
* @param valueType the type of values which may be added to the map, must not be null
* @return a typed map backed by the specified map
* @throws IllegalArgumentException if the Map or Class is null
*/
public static Map typedMap(Map map, Class keyType, Class valueType) {
return TypedMap.decorate(map, keyType, valueType);
}
/**
* Returns a transformed map backed by the given map.
* <p>
* This method returns a new map (decorating the specified map) that
* will transform any new entries added to it.
* Existing entries in the specified map will not be transformed.
* If you want that behaviour, see {@link TransformedMap#decorateTransform}.
* <p>
* Each object is passed through the transformers as it is added to the
* Map. It is important not to use the original map after invoking this
* method, as it is a backdoor for adding untransformed objects.
* <p>
* If there are any elements already in the map being decorated, they
* are NOT transformed.
*
* @param map the map to transform, must not be null, typically empty
* @param keyTransformer the transformer for the map keys, null means no transformation
* @param valueTransformer the transformer for the map values, null means no transformation
* @return a transformed map backed by the given map
* @throws IllegalArgumentException if the Map is null
*/
public static Map transformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return TransformedMap.decorate(map, keyTransformer, valueTransformer);
}
/**
* Returns a fixed-sized map backed by the given map.
* Elements may not be added or removed from the returned map, but
* existing elements can be changed (for instance, via the
* {@link Map#put(Object,Object)} method).
*
* @param map the map whose size to fix, must not be null
* @return a fixed-size map backed by that map
* @throws IllegalArgumentException if the Map is null
*/
public static Map fixedSizeMap(Map map) {
return FixedSizeMap.decorate(map);
}
/**
* Returns a "lazy" map whose values will be created on demand.
* <p>
* When the key passed to the returned map's {@link Map#get(Object)}
* method is not present in the map, then the factory will be used
* to create a new object and that object will become the value
* associated with that key.
* <p>
* For instance:
* <pre>
* Factory factory = new Factory() {
* public Object create() {
* return new Date();
* }
* }
* Map lazyMap = MapUtils.lazyMap(new HashMap(), factory);
* Object obj = lazyMap.get("test");
* </pre>
*
* After the above code is executed, <code>obj</code> will contain
* a new <code>Date</code> instance. Furthermore, that <code>Date</code>
* instance is the value for the <code>"test"</code> key in the map.
*
* @param map the map to make lazy, must not be null
* @param factory the factory for creating new objects, must not be null
* @return a lazy map backed by the given map
* @throws IllegalArgumentException if the Map or Factory is null
*/
public static Map lazyMap(Map map, Factory factory) {
return LazyMap.decorate(map, factory);
}
/**
* Returns a "lazy" map whose values will be created on demand.
* <p>
* When the key passed to the returned map's {@link Map#get(Object)}
* method is not present in the map, then the factory will be used
* to create a new object and that object will become the value
* associated with that key. The factory is a {@link Transformer}
* that will be passed the key which it must transform into the value.
* <p>
* For instance:
* <pre>
* Transformer factory = new Transformer() {
* public Object transform(Object mapKey) {
* return new File(mapKey);
* }
* }
* Map lazyMap = MapUtils.lazyMap(new HashMap(), factory);
* Object obj = lazyMap.get("C:/dev");
* </pre>
*
* After the above code is executed, <code>obj</code> will contain
* a new <code>File</code> instance for the C drive dev directory.
* Furthermore, that <code>File</code> instance is the value for the
* <code>"C:/dev"</code> key in the map.
* <p>
* If a lazy map is wrapped by a synchronized map, the result is a simple
* synchronized cache. When an object is not is the cache, the cache itself
* calls back to the factory Transformer to populate itself, all within the
* same synchronized block.
*
* @param map the map to make lazy, must not be null
* @param transformerFactory the factory for creating new objects, must not be null
* @return a lazy map backed by the given map
* @throws IllegalArgumentException if the Map or Transformer is null
*/
public static Map lazyMap(Map map, Transformer transformerFactory) {
return LazyMap.decorate(map, transformerFactory);
}
/**
* Returns a map that maintains the order of keys that are added
* backed by the given map.
* <p>
* If a key is added twice, the order is determined by the first add.
* The order is observed through the keySet, values and entrySet.
*
* @param map the map to order, must not be null
* @return an ordered map backed by the given map
* @throws IllegalArgumentException if the Map is null
*/
public static Map orderedMap(Map map) {
return ListOrderedMap.decorate(map);
}
/**
* Creates a mult-value map backed by the given map which returns
* collections of type ArrayList.
*
* @param map the map to decorate
* @return a multi-value map backed by the given map which returns ArrayLists of values.
* @see MultiValueMap
* @since Commons Collections 3.2
*/
public static Map multiValueMap(Map map) {
return MultiValueMap.decorate(map);
}
/**
* Creates a multi-value map backed by the given map which returns
* collections of the specified type.
*
* @param map the map to decorate
* @param collectionClass the type of collections to return from the map (must contain public no-arg constructor
* and extend Collection).
* @return a multi-value map backed by the given map which returns collections of the specified type
* @see MultiValueMap
* @since Commons Collections 3.2
*/
public static Map multiValueMap(Map map, Class collectionClass) {
return MultiValueMap.decorate(map, collectionClass);
}
/**
* Creates a multi-value map backed by the given map which returns
* collections created by the specified collection factory.
*
* @param map the map to decorate
* @param collectionFactory a factor which creates collection objects
* @return a multi-value map backed by the given map which returns collections
* created by the specified collection factory
* @see MultiValueMap
* @since Commons Collections 3.2
*/
public static Map multiValueMap(Map map, Factory collectionFactory) {
return MultiValueMap.decorate(map, collectionFactory);
}
// SortedMap decorators
//-----------------------------------------------------------------------
/**
* Returns a synchronized sorted map backed by the given sorted map.
* <p>
* You must manually synchronize on the returned buffer's iterator to
* avoid non-deterministic behavior:
*
* <pre>
* Map m = MapUtils.synchronizedSortedMap(myMap);
* Set s = m.keySet(); // outside synchronized block
* synchronized (m) { // synchronized on MAP!
* Iterator i = s.iterator();
* while (i.hasNext()) {
* process (i.next());
* }
* }
* </pre>
*
* This method uses the implementation in {@link java.util.Collections Collections}.
*
* @param map the map to synchronize, must not be null
* @return a synchronized map backed by the given map
* @throws IllegalArgumentException if the map is null
*/
public static Map synchronizedSortedMap(SortedMap map) {
return Collections.synchronizedSortedMap(map);
}
/**
* Returns an unmodifiable sorted map backed by the given sorted map.
* <p>
* This method uses the implementation in the decorators subpackage.
*
* @param map the sorted map to make unmodifiable, must not be null
* @return an unmodifiable map backed by the given map
* @throws IllegalArgumentException if the map is null
*/
public static Map unmodifiableSortedMap(SortedMap map) {
return UnmodifiableSortedMap.decorate(map);
}
/**
* Returns a predicated (validating) sorted map backed by the given map.
* <p>
* Only objects that pass the tests in the given predicates can be added to the map.
* Trying to add an invalid object results in an IllegalArgumentException.
* Keys must pass the key predicate, values must pass the value predicate.
* It is important not to use the original map after invoking this method,
* as it is a backdoor for adding invalid objects.
*
* @param map the map to predicate, must not be null
* @param keyPred the predicate for keys, null means no check
* @param valuePred the predicate for values, null means no check
* @return a predicated map backed by the given map
* @throws IllegalArgumentException if the SortedMap is null
*/
public static SortedMap predicatedSortedMap(SortedMap map, Predicate keyPred, Predicate valuePred) {
return PredicatedSortedMap.decorate(map, keyPred, valuePred);
}
/**
* Returns a typed sorted map backed by the given map.
* <p>
* Only keys and values of the specified types can be added to the map.
*
* @param map the map to limit to a specific type, must not be null
* @param keyType the type of keys which may be added to the map, must not be null
* @param valueType the type of values which may be added to the map, must not be null
* @return a typed map backed by the specified map
*/
public static SortedMap typedSortedMap(SortedMap map, Class keyType, Class valueType) {
return TypedSortedMap.decorate(map, keyType, valueType);
}
/**
* Returns a transformed sorted map backed by the given map.
* <p>
* This method returns a new sorted map (decorating the specified map) that
* will transform any new entries added to it.
* Existing entries in the specified map will not be transformed.
* If you want that behaviour, see {@link TransformedSortedMap#decorateTransform}.
* <p>
* Each object is passed through the transformers as it is added to the
* Map. It is important not to use the original map after invoking this
* method, as it is a backdoor for adding untransformed objects.
* <p>
* If there are any elements already in the map being decorated, they
* are NOT transformed.
*
* @param map the map to transform, must not be null, typically empty
* @param keyTransformer the transformer for the map keys, null means no transformation
* @param valueTransformer the transformer for the map values, null means no transformation
* @return a transformed map backed by the given map
* @throws IllegalArgumentException if the SortedMap is null
*/
public static SortedMap transformedSortedMap(SortedMap map, Transformer keyTransformer, Transformer valueTransformer) {
return TransformedSortedMap.decorate(map, keyTransformer, valueTransformer);
}
/**
* Returns a fixed-sized sorted map backed by the given sorted map.
* Elements may not be added or removed from the returned map, but
* existing elements can be changed (for instance, via the
* {@link Map#put(Object,Object)} method).
*
* @param map the map whose size to fix, must not be null
* @return a fixed-size map backed by that map
* @throws IllegalArgumentException if the SortedMap is null
*/
public static SortedMap fixedSizeSortedMap(SortedMap map) {
return FixedSizeSortedMap.decorate(map);
}
/**
* Returns a "lazy" sorted map whose values will be created on demand.
* <p>
* When the key passed to the returned map's {@link Map#get(Object)}
* method is not present in the map, then the factory will be used
* to create a new object and that object will become the value
* associated with that key.
* <p>
* For instance:
*
* <pre>
* Factory factory = new Factory() {
* public Object create() {
* return new Date();
* }
* }
* SortedMap lazy = MapUtils.lazySortedMap(new TreeMap(), factory);
* Object obj = lazy.get("test");
* </pre>
*
* After the above code is executed, <code>obj</code> will contain
* a new <code>Date</code> instance. Furthermore, that <code>Date</code>
* instance is the value for the <code>"test"</code> key.
*
* @param map the map to make lazy, must not be null
* @param factory the factory for creating new objects, must not be null
* @return a lazy map backed by the given map
* @throws IllegalArgumentException if the SortedMap or Factory is null
*/
public static SortedMap lazySortedMap(SortedMap map, Factory factory) {
return LazySortedMap.decorate(map, factory);
}
/**
* Returns a "lazy" sorted map whose values will be created on demand.
* <p>
* When the key passed to the returned map's {@link Map#get(Object)}
* method is not present in the map, then the factory will be used
* to create a new object and that object will become the value
* associated with that key. The factory is a {@link Transformer}
* that will be passed the key which it must transform into the value.
* <p>
* For instance:
* <pre>
* Transformer factory = new Transformer() {
* public Object transform(Object mapKey) {
* return new File(mapKey);
* }
* }
* SortedMap lazy = MapUtils.lazySortedMap(new TreeMap(), factory);
* Object obj = lazy.get("C:/dev");
* </pre>
*
* After the above code is executed, <code>obj</code> will contain
* a new <code>File</code> instance for the C drive dev directory.
* Furthermore, that <code>File</code> instance is the value for the
* <code>"C:/dev"</code> key in the map.
* <p>
* If a lazy map is wrapped by a synchronized map, the result is a simple
* synchronized cache. When an object is not is the cache, the cache itself
* calls back to the factory Transformer to populate itself, all within the
* same synchronized block.
*
* @param map the map to make lazy, must not be null
* @param transformerFactory the factory for creating new objects, must not be null
* @return a lazy map backed by the given map
* @throws IllegalArgumentException if the Map or Transformer is null
*/
public static SortedMap lazySortedMap(SortedMap map, Transformer transformerFactory) {
return LazySortedMap.decorate(map, transformerFactory);
}
}