org.apache.wicket.util.value.ValueMap Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.wicket.util.value;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.wicket.util.parse.metapattern.MetaPattern;
import org.apache.wicket.util.parse.metapattern.parsers.VariableAssignmentParser;
import org.apache.wicket.util.string.IStringIterator;
import org.apache.wicket.util.string.StringList;
import org.apache.wicket.util.string.StringValue;
import org.apache.wicket.util.string.StringValueConversionException;
import org.apache.wicket.util.time.Duration;
import org.apache.wicket.util.time.Time;
/**
* A IValueMap
implementation that holds values, parses String
s, and
* exposes a variety of convenience methods.
*
* In addition to a no-arg constructor and a copy constructor that takes a Map
* argument, ValueMap
s can be constructed using a parsing constructor.
* ValueMap(String)
will parse values from the string in comma separated key/value
* assignment pairs. For example, new ValueMap("a=9,b=foo")
.
*
* Values can be retrieved from the ValueMap
in the usual way or with methods that do
* handy conversions to various types, including String
, StringValue
,
* int
, long
, double
, Time
and
* Duration
.
*
* The makeImmutable
method will make the underlying Map
immutable.
* Further attempts to change the Map
will result in a RuntimeException
.
*
* The toString
method converts a ValueMap
object to a readable key/value
* string for diagnostics.
*
* @author Jonathan Locke
* @author Doug Donohoe
* @since 1.2.6
*/
public class ValueMap extends LinkedHashMap implements IValueMap
{
/** an empty ValueMap
. */
public static final ValueMap EMPTY_MAP;
/** create EMPTY_MAP, make immutable * */
static
{
EMPTY_MAP = new ValueMap();
EMPTY_MAP.makeImmutable();
}
private static final long serialVersionUID = 1L;
/**
* true
if this ValueMap
has been made immutable.
*/
private boolean immutable = false;
/**
* Constructs empty ValueMap
.
*/
public ValueMap()
{
super();
}
/**
* Copy constructor.
*
* @param map
* the ValueMap
to copy
*/
public ValueMap(final Map extends String, ?> map)
{
super();
super.putAll(map);
}
/**
* Constructor.
*
* NOTE: Please use RequestUtils.decodeParameters()
if you wish to properly decode
* a request URL.
*
* @param keyValuePairs
* list of key/value pairs separated by commas. For example, "
* param1=foo,param2=bar
"
*/
public ValueMap(final String keyValuePairs)
{
this(keyValuePairs, ",");
}
/**
* Constructor.
*
* NOTE: Please use RequestUtils.decodeParameters()
if you wish to properly decode
* a request URL.
*
* @param keyValuePairs
* list of key/value pairs separated by a given delimiter. For example, "
* param1=foo,param2=bar
" where delimiter is ",
".
* @param delimiter
* delimiter String
used to separate key/value pairs
*/
public ValueMap(final String keyValuePairs, final String delimiter)
{
super();
int start = 0;
int equalsIndex = keyValuePairs.indexOf('=');
int delimiterIndex = keyValuePairs.indexOf(delimiter, equalsIndex);
if (delimiterIndex == -1)
{
delimiterIndex = keyValuePairs.length();
}
while (equalsIndex != -1)
{
if (delimiterIndex < keyValuePairs.length())
{
int equalsIndex2 = keyValuePairs.indexOf('=', delimiterIndex + 1);
if (equalsIndex2 != -1)
{
delimiterIndex = keyValuePairs.lastIndexOf(delimiter, equalsIndex2);
}
else
{
delimiterIndex = keyValuePairs.length();
}
}
String key = keyValuePairs.substring(start, equalsIndex);
String value = keyValuePairs.substring(equalsIndex + 1, delimiterIndex);
add(key, value);
if (delimiterIndex < keyValuePairs.length())
{
start = delimiterIndex + 1;
equalsIndex = keyValuePairs.indexOf('=', start);
if (equalsIndex != -1)
{
delimiterIndex = keyValuePairs.indexOf(delimiter, equalsIndex);
if (delimiterIndex == -1)
{
delimiterIndex = keyValuePairs.length();
}
}
}
else
{
equalsIndex = -1;
}
}
}
/**
* Constructor.
*
* @param keyValuePairs
* list of key/value pairs separated by a given delimiter. For example, "
* param1=foo,param2=bar
" where delimiter is ",
".
* @param delimiter
* delimiter string used to separate key/value pairs
* @param valuePattern
* pattern for value. To pass a simple regular expression, pass "
* new MetaPattern(regexp)
".
*/
public ValueMap(final String keyValuePairs, final String delimiter,
final MetaPattern valuePattern)
{
super();
// Get list of strings separated by the delimiter
final StringList pairs = StringList.tokenize(keyValuePairs, delimiter);
// Go through each string in the list
for (IStringIterator iterator = pairs.iterator(); iterator.hasNext();)
{
// Get the next key value pair
final String pair = iterator.next();
// Parse using metapattern parser for variable assignments
final VariableAssignmentParser parser = new VariableAssignmentParser(pair, valuePattern);
// Does it parse?
if (parser.matches())
{
// Succeeded. Put key and value into map
put(parser.getKey(), parser.getValue());
}
else
{
throw new IllegalArgumentException("Invalid key value list: '" + keyValuePairs +
'\'');
}
}
}
/**
* @see java.util.Map#clear()
*/
@Override
public final void clear()
{
checkMutability();
super.clear();
}
/**
* @see IValueMap#getBoolean(String)
*/
@Override
public final boolean getBoolean(final String key) throws StringValueConversionException
{
return getStringValue(key).toBoolean();
}
/**
* @see IValueMap#getDouble(String)
*/
@Override
public final double getDouble(final String key) throws StringValueConversionException
{
return getStringValue(key).toDouble();
}
/**
* @see IValueMap#getDouble(String, double)
*/
@Override
public final double getDouble(final String key, final double defaultValue)
{
return getStringValue(key).toDouble(defaultValue);
}
/**
* @see IValueMap#getDuration(String)
*/
@Override
public final Duration getDuration(final String key) throws StringValueConversionException
{
return getStringValue(key).toDuration();
}
/**
* @see IValueMap#getInt(String)
*/
@Override
public final int getInt(final String key) throws StringValueConversionException
{
return getStringValue(key).toInt();
}
/**
* @see IValueMap#getInt(String, int)
*/
@Override
public final int getInt(final String key, final int defaultValue)
{
return getStringValue(key).toInt(defaultValue);
}
/**
* @see IValueMap#getLong(String)
*/
@Override
public final long getLong(final String key) throws StringValueConversionException
{
return getStringValue(key).toLong();
}
/**
* @see IValueMap#getLong(String, long)
*/
@Override
public final long getLong(final String key, final long defaultValue)
{
return getStringValue(key).toLong(defaultValue);
}
/**
* @see IValueMap#getString(String, String)
*/
@Override
public final String getString(final String key, final String defaultValue)
{
final String value = getString(key);
return value != null ? value : defaultValue;
}
/**
* @see IValueMap#getString(String)
*/
@Override
public final String getString(final String key)
{
final Object o = get(key);
if (o == null)
{
return null;
}
else if (o.getClass().isArray() && (Array.getLength(o) > 0))
{
// if it is an array just get the first value
final Object arrayValue = Array.get(o, 0);
if (arrayValue == null)
{
return null;
}
else
{
return arrayValue.toString();
}
}
else
{
return o.toString();
}
}
/**
* @see IValueMap#getCharSequence(String)
*/
@Override
public final CharSequence getCharSequence(final String key)
{
final Object o = get(key);
if (o == null)
{
return null;
}
else if (o.getClass().isArray() && (Array.getLength(o) > 0))
{
// if it is an array just get the first value
final Object arrayValue = Array.get(o, 0);
if (arrayValue == null)
{
return null;
}
else
{
if (arrayValue instanceof CharSequence)
{
return (CharSequence)arrayValue;
}
return arrayValue.toString();
}
}
else
{
if (o instanceof CharSequence)
{
return (CharSequence)o;
}
return o.toString();
}
}
/**
* @see IValueMap#getStringArray(String)
*/
@Override
public String[] getStringArray(final String key)
{
final Object o = get(key);
if (o == null)
{
return null;
}
else if (o instanceof String[])
{
return (String[])o;
}
else if (o.getClass().isArray())
{
int length = Array.getLength(o);
String[] array = new String[length];
for (int i = 0; i < length; i++)
{
final Object arrayValue = Array.get(o, i);
if (arrayValue != null)
{
array[i] = arrayValue.toString();
}
}
return array;
}
return new String[] { o.toString() };
}
/**
* @see IValueMap#getStringValue(String)
*/
@Override
public StringValue getStringValue(final String key)
{
return StringValue.valueOf(getString(key));
}
/**
* @see IValueMap#getTime(String)
*/
@Override
public final Time getTime(final String key) throws StringValueConversionException
{
return getStringValue(key).toTime();
}
/**
* @see IValueMap#isImmutable()
*/
@Override
public final boolean isImmutable()
{
return immutable;
}
/**
* @see IValueMap#makeImmutable()
*/
@Override
public final IValueMap makeImmutable()
{
immutable = true;
return this;
}
/**
* @see java.util.Map#put(Object, Object)
*/
@Override
public Object put(final String key, final Object value)
{
checkMutability();
return super.put(key, value);
}
/**
* Adds the value to this ValueMap
with the given key. If the key already is in the
* ValueMap
it will combine the values into a String
array, else it
* will just store the value itself.
*
* @param key
* the key to store the value under
* @param value
* the value that must be added/merged to the ValueMap
* @return the value itself if there was no previous value, or a String
array with
* the combined values
*/
public final Object add(final String key, final String value)
{
checkMutability();
final Object o = get(key);
if (o == null)
{
return put(key, value);
}
else if (o.getClass().isArray())
{
int length = Array.getLength(o);
String destArray[] = new String[length + 1];
for (int i = 0; i < length; i++)
{
final Object arrayValue = Array.get(o, i);
if (arrayValue != null)
{
destArray[i] = arrayValue.toString();
}
}
destArray[length] = value;
return put(key, destArray);
}
else
{
return put(key, new String[] { o.toString(), value });
}
}
/**
* @see java.util.Map#putAll(java.util.Map)
*/
@Override
public void putAll(final Map extends String, ?> map)
{
checkMutability();
super.putAll(map);
}
/**
* @see java.util.Map#remove(java.lang.Object)
*/
@Override
public Object remove(final Object key)
{
checkMutability();
return super.remove(key);
}
/**
* @see IValueMap#getKey(String)
*/
@Override
public String getKey(final String key)
{
for (String other : keySet())
{
if (other.equalsIgnoreCase(key))
{
return other;
}
}
return null;
}
/**
* Generates a String
representation of this object.
*
* @return String
representation of this ValueMap
consistent with the
* tag-attribute style of markup elements. For example: a="x" b="y" c="z"
.
*/
@Override
public String toString()
{
final StringBuilder buffer = new StringBuilder();
boolean first = true;
for (Map.Entry entry : entrySet())
{
if (first == false)
{
buffer.append(' ');
}
first = false;
buffer.append(entry.getKey());
buffer.append(" = \"");
final Object value = entry.getValue();
if (value == null)
{
buffer.append("null");
}
else if (value.getClass().isArray())
{
buffer.append(Arrays.asList((Object[])value));
}
else
{
buffer.append(value);
}
buffer.append('\"');
}
return buffer.toString();
}
/**
* Throws an exception if ValueMap
is immutable.
*/
private void checkMutability()
{
if (immutable)
{
throw new UnsupportedOperationException("Map is immutable");
}
}
// //
// // getAs convenience methods
// //
/**
* @see IValueMap#getAsBoolean(String)
*
*/
@Override
public Boolean getAsBoolean(final String key)
{
if (!containsKey(key))
{
return null;
}
try
{
return getBoolean(key);
}
catch (StringValueConversionException ignored)
{
return null;
}
}
/**
* @see IValueMap#getAsBoolean(String, boolean)
*
*/
@Override
public boolean getAsBoolean(final String key, final boolean defaultValue)
{
if (!containsKey(key))
{
return defaultValue;
}
try
{
return getBoolean(key);
}
catch (StringValueConversionException ignored)
{
return defaultValue;
}
}
/**
* @see IValueMap#getAsInteger(String)
*/
@Override
public Integer getAsInteger(final String key)
{
if (!containsKey(key))
{
return null;
}
try
{
return getInt(key);
}
catch (StringValueConversionException ignored)
{
return null;
}
}
/**
* @see IValueMap#getAsInteger(String, int)
*/
@Override
public int getAsInteger(final String key, final int defaultValue)
{
return getInt(key, defaultValue);
}
/**
* @see IValueMap#getAsLong(String)
*/
@Override
public Long getAsLong(final String key)
{
if (!containsKey(key))
{
return null;
}
try
{
return getLong(key);
}
catch (StringValueConversionException ignored)
{
return null;
}
}
/**
* @see IValueMap#getAsLong(String, long)
*/
@Override
public long getAsLong(final String key, final long defaultValue)
{
return getLong(key, defaultValue);
}
/**
* @see IValueMap#getAsDouble(String)
*/
@Override
public Double getAsDouble(final String key)
{
if (!containsKey(key))
{
return null;
}
try
{
return getDouble(key);
}
catch (StringValueConversionException ignored)
{
return null;
}
}
/**
* @see IValueMap#getAsDouble(String, double)
*/
@Override
public double getAsDouble(final String key, final double defaultValue)
{
return getDouble(key, defaultValue);
}
/**
* @see IValueMap#getAsDuration(String)
*/
@Override
public Duration getAsDuration(final String key)
{
return getAsDuration(key, null);
}
/**
* @see IValueMap#getAsDuration(String, Duration)
*/
@Override
public Duration getAsDuration(final String key, final Duration defaultValue)
{
if (!containsKey(key))
{
return defaultValue;
}
try
{
return getDuration(key);
}
catch (StringValueConversionException ignored)
{
return defaultValue;
}
}
/**
* @see IValueMap#getAsTime(String)
*/
@Override
public Time getAsTime(final String key)
{
return getAsTime(key, null);
}
/**
* @see IValueMap#getAsTime(String, Time)
*/
@Override
public Time getAsTime(final String key, final Time defaultValue)
{
if (!containsKey(key))
{
return defaultValue;
}
try
{
return getTime(key);
}
catch (StringValueConversionException ignored)
{
return defaultValue;
}
}
/**
* @see org.apache.wicket.util.value.IValueMap#getAsEnum(java.lang.String, java.lang.Class)
*/
@Override
public > T getAsEnum(final String key, final Class eClass)
{
// explicitly pass T as type to be able to build with JDK 1.8. WICKET-5427
return this.getEnumImpl(key, eClass, (T)null);
}
/**
* @see org.apache.wicket.util.value.IValueMap#getAsEnum(java.lang.String, java.lang.Enum)
*/
@Override
public > T getAsEnum(final String key, final T defaultValue)
{
if (defaultValue == null)
{
throw new IllegalArgumentException("Default value cannot be null");
}
return getEnumImpl(key, defaultValue.getClass(), defaultValue);
}
/**
* @see org.apache.wicket.util.value.IValueMap#getAsEnum(java.lang.String, java.lang.Class,
* java.lang.Enum)
*/
@Override
public > T getAsEnum(final String key, final Class eClass,
final T defaultValue)
{
return getEnumImpl(key, eClass, defaultValue);
}
/**
* get enum implementation
*
* @param key
* @param eClass
* @param defaultValue
* @param
* @return Enum
*/
@SuppressWarnings({ "unchecked" })
private > T getEnumImpl(final String key, final Class> eClass,
final T defaultValue)
{
if (eClass == null)
{
throw new IllegalArgumentException("eClass value cannot be null");
}
String value = getString(key);
if (value == null)
{
return defaultValue;
}
Method valueOf = null;
try
{
valueOf = eClass.getMethod("valueOf", String.class);
}
catch (NoSuchMethodException e)
{
throw new RuntimeException("Could not find method valueOf(String s) for " +
eClass.getName(), e);
}
try
{
return (T)valueOf.invoke(eClass, value);
}
catch (IllegalAccessException e)
{
throw new RuntimeException("Could not invoke method valueOf(String s) on " +
eClass.getName(), e);
}
catch (InvocationTargetException e)
{
// IllegalArgumentException thrown if enum isn't defined - just return default
if (e.getCause() instanceof IllegalArgumentException)
{
return defaultValue;
}
throw new RuntimeException(e); // shouldn't happen
}
}
}