| package org.apache.velocity.tools.generic; |
| |
| /* |
| * 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. |
| */ |
| |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| import org.apache.velocity.tools.ConversionUtils; |
| import org.apache.velocity.tools.Scope; |
| import org.apache.velocity.tools.config.DefaultKey; |
| import org.apache.velocity.tools.config.InvalidScope; |
| import org.apache.velocity.tools.config.SkipSetters; |
| |
| /** |
| * <p>Utility class for easy parsing of String values held in a Map.</p> |
| * |
| * <p>This comes in very handy when parsing parameters.</p> |
| * |
| * <p>When subkeys are allowed, getValue("foo") will also search for all keys |
| * of the form "foo.bar" and return a ValueParser of the type "bar" -> value for all found values.</p> |
| * |
| * TODO: someone doing java configuration ought to be able to put a source Map |
| * in the tool properties, allowing this to be used like other tools |
| * |
| * @author Nathan Bubna |
| * @version $Revision$ $Date$ |
| * @since VelocityTools 1.2 |
| */ |
| |
| @DefaultKey("parser") |
| @InvalidScope(Scope.SESSION) /* session scope forbidden: Object may not be Serializable */ |
| @SkipSetters |
| public class ValueParser extends FormatConfig implements Map<String,Object> |
| { |
| public static final String STRINGS_DELIMITER_FORMAT_KEY = "stringsDelimiter"; |
| public static final String DEFAULT_STRINGS_DELIMITER = ","; |
| private String stringsDelimiter = DEFAULT_STRINGS_DELIMITER; |
| |
| private Map<String,Object> source = null; |
| |
| private boolean allowSubkeys = true; |
| |
| /* when using subkeys, cache at least the presence of any subkey, |
| so that the rendering of templates not using subkeys will only |
| look once for subkeys |
| */ |
| private Boolean hasSubkeys = null; |
| |
| /* whether the wrapped map should be read-only or not */ |
| private boolean readOnly = true; |
| |
| /** |
| * The key used for specifying whether to support subkeys |
| */ |
| public static final String ALLOWSUBKEYS_KEY = "allowSubkeys"; |
| |
| /** |
| * The key used for specifying whether to be read-only |
| */ |
| public static final String READONLY_KEY = "readOnly"; |
| |
| public ValueParser() |
| { |
| } |
| |
| public ValueParser(Map<String,Object> source) |
| { |
| setSource(source); |
| } |
| |
| protected void setSource(Map<String,Object> source) |
| { |
| this.source = source; |
| } |
| |
| protected Map<String,Object> getSource(boolean create) |
| { |
| // If this method has not been overrided, make sure source is not null |
| if (source == null && create) |
| { |
| source = new HashMap<String, Object>(); |
| } |
| return this.source; |
| } |
| |
| protected Map<String,Object> getSource() |
| { |
| return getSource(true); |
| } |
| |
| /** |
| * Are subkeys allowed ? |
| * @return yes/no |
| */ |
| protected boolean getAllowSubkeys() |
| { |
| return allowSubkeys; |
| } |
| |
| /** |
| * allow or disallow subkeys |
| * @param allow flag value |
| */ |
| protected void setAllowSubkeys(boolean allow) |
| { |
| allowSubkeys = allow; |
| } |
| |
| /** |
| * Is the Map read-only? |
| * @return yes/no |
| */ |
| protected boolean getReadOnly() |
| { |
| return readOnly; |
| } |
| |
| /** |
| * Set or unset read-only behaviour |
| * @param ro flag value |
| */ |
| protected void setReadOnly(boolean ro) |
| { |
| readOnly = ro; |
| } |
| |
| /** |
| * Sets the delimiter used for separating values in a single String value. |
| * The default string delimiter is a comma. |
| * |
| * @param stringsDelimiter strings delimiter |
| * @see #getValues(String) |
| */ |
| protected final void setStringsDelimiter(String stringsDelimiter) |
| { |
| this.stringsDelimiter = stringsDelimiter; |
| } |
| |
| /** |
| * Does the actual configuration. This is protected, so |
| * subclasses may share the same ValueParser and call configure |
| * at any time, while preventing templates from doing so when |
| * configure(Map) is locked. |
| * @param values configuration values |
| */ |
| @Override |
| protected void configure(ValueParser values) |
| { |
| super.configure(values); |
| |
| String delimiter = values.getString(STRINGS_DELIMITER_FORMAT_KEY); |
| if (delimiter != null) |
| { |
| setStringsDelimiter(delimiter); |
| } |
| |
| Boolean allow = values.getBoolean(ALLOWSUBKEYS_KEY); |
| if(allow != null) |
| { |
| setAllowSubkeys(allow); |
| } |
| |
| Boolean ro = values.getBoolean(READONLY_KEY); |
| if(ro != null) |
| { |
| setReadOnly(ro); |
| } |
| } |
| |
| // ----------------- public parsing methods -------------------------- |
| |
| /** |
| * Convenience method for checking whether a certain parameter exists. |
| * |
| * @param key the parameter's key |
| * @return <code>true</code> if a parameter exists for the specified |
| * key; otherwise, returns <code>false</code>. |
| */ |
| public boolean exists(String key) |
| { |
| return (getValue(key) != null); |
| } |
| |
| /** |
| * Convenience method for use in Velocity templates. |
| * This allows for easy "dot" access to parameters. |
| * |
| * e.g. $params.foo instead of $params.getString('foo') |
| * |
| * @param key the parameter's key |
| * @return parameter matching the specified key or |
| * <code>null</code> if there is no matching |
| * parameter |
| */ |
| public Object get(String key) |
| { |
| Object value = getValue(key); |
| if (value == null && getSource() != null && getAllowSubkeys()) |
| { |
| value = getSubkey(key); |
| } |
| return value; |
| } |
| |
| /** |
| * Returns the value mapped to the specified key |
| * in the {@link Map} returned by {@link #getSource()}. If there is |
| * no source, then this will always return {@code null}. |
| * @param key property key |
| * @return property value, or null |
| */ |
| public Object getValue(String key) |
| { |
| if (getSource() == null) |
| { |
| return null; |
| } |
| return getSource().get(key); |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @param alternate The alternate value |
| * @return parameter matching the specified key or the |
| * specified alternate Object if there is no matching |
| * parameter |
| */ |
| public Object getValue(String key, Object alternate) |
| { |
| Object value = getValue(key); |
| if (value == null) |
| { |
| return alternate; |
| } |
| return value; |
| } |
| |
| protected String[] parseStringList(String value) |
| { |
| String[] values; |
| if (stringsDelimiter.length() == 0 || value.indexOf(stringsDelimiter) < 0) |
| { |
| values = new String[] { value }; |
| } |
| else |
| { |
| values = value.split(stringsDelimiter); |
| } |
| |
| return values; |
| } |
| |
| /** |
| * <p>Returns an array of values. If the internal value is a string, it is split using the configured delimitor |
| * (',' by default).</p> |
| * <p>If the internal value is not an array or is a string without any delimiter, a singletin array is returned.</p> |
| * @param key the desired parameter's key |
| * @return array of values, or null of the key has not been found. |
| * specified alternate Object if there is no matching |
| * parameter |
| */ |
| public Object[] getValues(String key) |
| { |
| Object value = getValue(key); |
| if (value == null) |
| { |
| return null; |
| } |
| if (value instanceof String) |
| { |
| return parseStringList((String)value); |
| } |
| if (value instanceof Object[]) |
| { |
| return (Object[])value; |
| } |
| return new Object[] { value }; |
| } |
| |
| /** |
| * @param key the parameter's key |
| * @return parameter matching the specified key or |
| * <code>null</code> if there is no matching |
| * parameter |
| */ |
| public String getString(String key) |
| { |
| return ConversionUtils.toString(getValue(key)); |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @param alternate The alternate value |
| * @return parameter matching the specified key or the |
| * specified alternate String if there is no matching |
| * parameter |
| */ |
| public String getString(String key, String alternate) |
| { |
| String s = getString(key); |
| return (s != null) ? s : alternate; |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @return a {@link Boolean} object for the specified key or |
| * <code>null</code> if no matching parameter is found |
| */ |
| public Boolean getBoolean(String key) |
| { |
| return ConversionUtils.toBoolean(getValue(key)); |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @param alternate The alternate boolean value |
| * @return boolean value for the specified key or the |
| * alternate boolean is no value is found |
| */ |
| public boolean getBoolean(String key, boolean alternate) |
| { |
| Boolean bool = getBoolean(key); |
| return (bool != null) ? bool.booleanValue() : alternate; |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @param alternate the alternate {@link Boolean} |
| * @return a {@link Boolean} for the specified key or the specified |
| * alternate if no matching parameter is found |
| */ |
| public Boolean getBoolean(String key, Boolean alternate) |
| { |
| Boolean bool = getBoolean(key); |
| return (bool != null) ? bool : alternate; |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @return a {@link Integer} for the specified key or |
| * <code>null</code> if no matching parameter is found |
| */ |
| public Integer getInteger(String key) |
| { |
| Object value = getValue(key); |
| if (value == null) |
| { |
| return null; |
| } |
| Number number = ConversionUtils.toNumber(value, getFormat(), getLocale()); |
| return number == null ? null : number.intValue(); |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @param alternate The alternate Integer |
| * @return an Integer for the specified key or the specified |
| * alternate if no matching parameter is found |
| */ |
| public Integer getInteger(String key, Integer alternate) |
| { |
| Integer num = getInteger(key); |
| if (num == null) |
| { |
| return alternate; |
| } |
| return num; |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @return a {@link Long} for the specified key or |
| * <code>null</code> if no matching parameter is found |
| */ |
| public Long getLong(String key) |
| { |
| Object value = getValue(key); |
| if (value == null) |
| { |
| return null; |
| } |
| Number number = ConversionUtils.toNumber(value, getFormat(), getLocale()); |
| return number == null ? null : number.longValue(); |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @param alternate The alternate Long |
| * @return a Long for the specified key or the specified |
| * alternate if no matching parameter is found |
| */ |
| public Long getLong(String key, Long alternate) |
| { |
| Long num = getLong(key); |
| if (num == null) |
| { |
| return alternate; |
| } |
| return num; |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @return a {@link Double} for the specified key or |
| * <code>null</code> if no matching parameter is found |
| */ |
| public Double getDouble(String key) |
| { |
| Object value = getValue(key); |
| if (value == null) |
| { |
| return null; |
| } |
| Number number = ConversionUtils.toNumber(value, getFormat(), getLocale()); |
| return number == null ? null : number.doubleValue(); |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @param alternate The alternate Double |
| * @return an Double for the specified key or the specified |
| * alternate if no matching parameter is found |
| */ |
| public Double getDouble(String key, Double alternate) |
| { |
| Double num = getDouble(key); |
| if (num == null) |
| { |
| return alternate; |
| } |
| return num; |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @return a {@link Number} for the specified key or |
| * <code>null</code> if no matching parameter is found |
| */ |
| public Number getNumber(String key) |
| { |
| return ConversionUtils.toNumber(getValue(key), getFormat(), getLocale()); |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @return a {@link Locale} for the specified key or |
| * <code>null</code> if no matching parameter is found |
| */ |
| public Locale getLocale(String key) |
| { |
| return toLocale(getValue(key)); |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @param alternate The alternate Number |
| * @return a Number for the specified key or the specified |
| * alternate if no matching parameter is found |
| */ |
| public Number getNumber(String key, Number alternate) |
| { |
| Number n = getNumber(key); |
| return (n != null) ? n : alternate; |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @param alternate The alternate int value |
| * @return the int value for the specified key or the specified |
| * alternate value if no matching parameter is found |
| */ |
| public int getInt(String key, int alternate) |
| { |
| Number n = getNumber(key); |
| return (n != null) ? n.intValue() : alternate; |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @param alternate The alternate double value |
| * @return the double value for the specified key or the specified |
| * alternate value if no matching parameter is found |
| */ |
| public double getDouble(String key, double alternate) |
| { |
| Number n = getNumber(key); |
| return (n != null) ? n.doubleValue() : alternate; |
| } |
| |
| /** |
| * @param key the desired parameter's key |
| * @param alternate The alternate Locale |
| * @return a Locale for the specified key or the specified |
| * alternate if no matching parameter is found |
| */ |
| public Locale getLocale(String key, Locale alternate) |
| { |
| Locale l = getLocale(key); |
| return (l != null) ? l : alternate; |
| } |
| |
| |
| /** |
| * @param key the key for the desired parameter |
| * @return an array of String objects containing all of the values |
| * associated with the given key, or <code>null</code> |
| * if the no values are associated with the given key |
| */ |
| public String[] getStrings(String key) |
| { |
| Object[] array = getValues(key); |
| if (array == null || String.class.isAssignableFrom(array.getClass().getComponentType())) |
| { |
| return (String[])array; |
| } |
| String[] ret = new String[array.length]; |
| for (int i = 0; i < array.length; ++i) |
| { |
| ret[i] = ConversionUtils.toString(array[i]); |
| } |
| return ret; |
| } |
| |
| |
| /** |
| * @param key the key for the desired parameter |
| * @return an array of Boolean objects associated with the given key. |
| */ |
| public Boolean[] getBooleans(String key) |
| { |
| Object[] array = getValues(key); |
| if (array == null || Boolean.class.isAssignableFrom(array.getClass().getComponentType())) |
| { |
| return (Boolean[])array; |
| } |
| Boolean[] ret = new Boolean[array.length]; |
| for (int i = 0; i < array.length; ++i) |
| { |
| ret[i] = ConversionUtils.toBoolean(array[i]); |
| } |
| return ret; |
| } |
| |
| /** |
| * @param key the key for the desired parameter |
| * @return an array of Number objects associated with the given key, |
| * or <code>null</code> if Numbers are not associated with it. |
| */ |
| public Number[] getNumbers(String key) |
| { |
| Object[] array = getValues(key); |
| if (array == null || Number.class.isAssignableFrom(array.getClass().getComponentType())) |
| { |
| return (Number[])array; |
| } |
| Number[] ret = new Number[array.length]; |
| for (int i = 0; i < array.length; ++i) |
| { |
| ret[i] = ConversionUtils.toNumber(array[i], getFormat(), getLocale()); |
| } |
| return ret; |
| } |
| |
| /** |
| * @param key the key for the desired parameter |
| * @return an array of int values associated with the given key, |
| * or <code>null</code> if numbers are not associated with it. |
| */ |
| public int[] getInts(String key) |
| { |
| Object[] array = getValues(key); |
| if (array == null) |
| { |
| return null; |
| } |
| int[] ret = new int[array.length]; |
| for (int i = 0; i < array.length; ++i) |
| { |
| ret[i] = ConversionUtils.toNumber(array[i], getFormat(), getLocale()).intValue(); |
| } |
| return ret; |
| } |
| |
| /** |
| * @param key the key for the desired parameter |
| * @return an array of double values associated with the given key, |
| * or <code>null</code> if numbers are not associated with it. |
| */ |
| public double[] getDoubles(String key) |
| { |
| Object[] array = getValues(key); |
| if (array == null) |
| { |
| return null; |
| } |
| double[] ret = new double[array.length]; |
| for (int i = 0; i < array.length; ++i) |
| { |
| ret[i] = ConversionUtils.toNumber(array[i], getFormat(), getLocale()).doubleValue(); |
| } |
| return ret; |
| } |
| |
| /** |
| * @param key the key for the desired parameter |
| * @return an array of Locale objects associated with the given key, |
| * or <code>null</code> if Locales are not associated with it. |
| */ |
| public Locale[] getLocales(String key) |
| { |
| Object[] array = getValues(key); |
| if (array == null || Locale.class.isAssignableFrom(array.getClass().getComponentType())) |
| { |
| return (Locale[])array; |
| } |
| Locale[] ret = new Locale[array.length]; |
| for (int i = 0; i < array.length; ++i) |
| { |
| ret[i] = ConversionUtils.toLocale(String.valueOf(array[i])); |
| } |
| return ret; |
| } |
| |
| /** |
| * Determines whether there are subkeys available in the source map. |
| * @return <code>true</code> if there are subkeys (key names containing a dot) |
| */ |
| public boolean hasSubkeys() |
| { |
| if (getSource() == null || !getAllowSubkeys()) |
| { |
| return false; |
| } |
| |
| if (hasSubkeys == null) |
| { |
| for (String key : getSource().keySet()) |
| { |
| int dot = key.indexOf('.'); |
| if (dot > 0 && dot < key.length()) |
| { |
| hasSubkeys = Boolean.TRUE; |
| break; |
| } |
| } |
| if (hasSubkeys == null) |
| { |
| hasSubkeys = Boolean.FALSE; |
| } |
| } |
| return hasSubkeys; |
| } |
| |
| /** |
| * returns the set of all possible first-level subkeys, including complete keys without dots (or returns keySet() if allowSubKeys is false) |
| * @return the set of all possible first-level subkeys |
| */ |
| public Set<String> getSubkeys() |
| { |
| Set<String> keys = keySet(); |
| if (getSource() == null || !getAllowSubkeys()) |
| { |
| return keys; |
| } |
| else |
| { |
| Set<String> result = new TreeSet<String>(); |
| for (String key: keys) |
| { |
| int dot = key.indexOf('.'); |
| if (dot > 0 && dot < key.length()) |
| { |
| result.add(key.substring(0, dot)); |
| } |
| } |
| return result; |
| } |
| } |
| |
| /** |
| * subkey getter that returns a map subkey#2 -> value |
| * for every "subkey.subkey2" found entry |
| * |
| * @param subkey subkey to search for |
| * @return the map of found values |
| */ |
| public ValueParser getSubkey(String subkey) |
| { |
| if (!hasSubkeys() || subkey == null || subkey.length() == 0) |
| { |
| return null; |
| } |
| |
| Map<String,Object> values = null; |
| subkey = subkey.concat("."); |
| for (Map.Entry<String,Object> entry : getSource().entrySet()) |
| { |
| if (entry.getKey().startsWith(subkey) && |
| entry.getKey().length() > subkey.length()) |
| { |
| if (values == null) |
| { |
| values = new HashMap<String, Object>(); |
| } |
| values.put(entry.getKey().substring(subkey.length()),entry.getValue()); |
| } |
| } |
| if (values == null) |
| { |
| return null; |
| } |
| else |
| { |
| ValueParser ret = new ValueParser(values); |
| /* honnor readOnly option on submaps */ |
| ret.setReadOnly(getReadOnly()); |
| return ret; |
| } |
| } |
| |
| public int size() |
| { |
| return getSource() == null ? 0 : getSource().size(); |
| } |
| |
| public boolean isEmpty() |
| { |
| return getSource() == null || getSource().isEmpty(); |
| } |
| |
| public boolean containsKey(Object key) |
| { |
| return getSource() == null ? false : getSource().containsKey(key); |
| } |
| |
| public boolean containsValue(Object value) |
| { |
| return getSource() == null ? false : getSource().containsValue(value); |
| } |
| |
| public Object get(Object key) |
| { |
| return get(String.valueOf(key)); |
| } |
| |
| public Object put(String key, Object value) |
| { |
| if(readOnly) |
| { |
| throw new UnsupportedOperationException("Cannot put("+key+","+value+"); "+getClass().getName()+" is read-only"); |
| } |
| if(hasSubkeys != null && hasSubkeys.equals(Boolean.FALSE) && key.indexOf('.') != -1) |
| { |
| hasSubkeys = Boolean.TRUE; |
| } |
| return getSource().put(key,value); // TODO this tool should be made thread-safe (the request-scoped ParameterTool doesn't need it, but other uses could...) |
| } |
| |
| public Object remove(Object key) |
| { |
| if(readOnly) |
| { |
| throw new UnsupportedOperationException("Cannot remove("+key+"); "+getClass().getName()+" is read-only"); |
| } |
| if(hasSubkeys != null && hasSubkeys.equals(Boolean.TRUE) && ((String)key).indexOf('.') != -1) |
| { |
| hasSubkeys = null; |
| } |
| return getSource().remove(key); |
| } |
| |
| public void putAll(Map<? extends String,? extends Object> m) { |
| if(readOnly) |
| { |
| throw new UnsupportedOperationException("Cannot putAll("+m+"); "+getClass().getName()+" is read-only"); |
| } |
| hasSubkeys = null; |
| getSource().putAll(m); |
| } |
| |
| public void clear() { |
| if(readOnly) |
| { |
| throw new UnsupportedOperationException("Cannot clear(); "+getClass().getName()+" is read-only"); |
| } |
| hasSubkeys = Boolean.FALSE; |
| getSource().clear(); |
| } |
| |
| public Set<String> keySet() { |
| return getSource() == null ? null : getSource().keySet(); |
| } |
| |
| public Collection values() { |
| return getSource() == null ? null : getSource().values(); |
| } |
| |
| public Set<Map.Entry<String,Object>> entrySet() { |
| return getSource().entrySet(); |
| } |
| |
| public String toString() { |
| StringBuilder builder = new StringBuilder(); |
| builder.append('{'); |
| boolean empty = true; |
| for(Map.Entry<String,Object> entry:entrySet()) |
| { |
| if(!empty) |
| { |
| builder.append(", "); |
| } |
| empty = false; |
| builder.append(entry.getKey()); |
| builder.append('='); |
| builder.append(String.valueOf(entry.getValue())); |
| } |
| builder.append('}'); |
| return builder.toString(); |
| } |
| } |