/*************************************************************************
*
* ADOBE CONFIDENTIAL __________________
*
* Copyright 2002 - 2007 Adobe Systems Incorporated All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains the property of Adobe Systems Incorporated and its suppliers, if any. The intellectual and technical concepts contained herein are
* proprietary to Adobe Systems Incorporated and its suppliers and may be covered by U.S. and Foreign Patents, patents in process, and are protected by trade secret or copyright law. Dissemination of
* this information or reproduction of this material is strictly forbidden unless prior written permission is obtained from Adobe Systems Incorporated.
**************************************************************************/
package flex.messaging.config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* The ConfigMap class is a helper implementation of Map that makes it easier to handle properties that can appear one or more times. If a property is set more than once, it is converted to a List and
* added as another property rather than replacing the existing property. It also provides utility APIs for getting properties from the Map, cast to a certain type and allows a default to be specified
* in the event that the property is missing.
*
* @author Peter Farland
*/
public class ConfigMap extends LinkedHashMap
{
/**
* This number was generated using the 'serialver' command line tool. This number should remain consistent with the version used by ColdFusion to communicate with the message broker over RMI.
*/
private static final long serialVersionUID = 8913604659150919550L;
/**
* This error is thrown when a property unexpectedly contains multiple values.
*/
private static final int UNEXPECTED_MULTIPLE_VALUES = 10169;
/**
* An *undocumented* system property can be used to revert to legacy config property handling: Legacy behavior - config property values were not trimmed, retaining leading/trailing whitespace
* which proved problematic for customers. New default behavior - config property values are trimmed.
*/
private static final String SYSPROPNAME_TRIM_CONFIG_PROPERTY_VALUES = "flex.trim-config-property-values";
private static final boolean TRIM_CONFIG_PROPERTY_VALUES = Boolean.valueOf(System.getProperty(SYSPROPNAME_TRIM_CONFIG_PROPERTY_VALUES, "true")).booleanValue();
/**
* Map to keep track of accessed properties.
*/
private HashSet accessedKeys = new HashSet();
/**
* Constructs an empty <code>ConfigMap</code> with the default initial capacity of 10.
*/
public ConfigMap()
{
super();
}
// TODO UCdetector: Remove unused code:
// /**
// * Constructs a new <code>ConfigMap</code> with the initial
// * capacity specified.
// *
// * @param initialCapacity the initial capacity.
// */
// public ConfigMap(int initialCapacity)
// {
// super(initialCapacity);
// }
// TODO UCdetector: Remove unused code:
// /**
// * Constructs a new <code>ConfigMap</code> and copies the values
// * from the supplied map to this map.
// *
// * @param m a <code>ConfigMap</code> whose properties are to be added to
// * this <code>ConfigMap</code>.
// */
// public ConfigMap(ConfigMap m)
// {
// this();
// addProperties(m);
// }
// TODO UCdetector: Remove unused code:
// /**
// * Adds all properties from a map to this map.
// *
// * @param p a <code>ConfigMap</code> whose properties are to be added to
// * this <code>ConfigMap</code>.
// */
// public void addProperties(ConfigMap p)
// {
// Iterator it = p.entrySet().iterator();
// while (it.hasNext())
// {
// Map.Entry entry = (Map.Entry) it.next();
// Object key = entry.getKey();
// Object value = entry.getValue();
// if (value instanceof ValueList)
// {
// addProperties(key, (ValueList) value);
// }
// else
// {
// addPropertyLogic(key, value);
// }
// }
// }
/**
* Helper method to add a list of values under a key.
*
* @param key
* The key to add the values under.
* @param values
* The list of values to add.
*/
private void addProperties(Object key, ValueList values)
{
ValueList list = getValueList(key);
if (list == null)
{
put(key, values.clone());
}
else
{
list.addAll(values);
}
}
/**
* Helper method to add a value under a key.
*
* @param key
* The key to add the value under.
* @param value
* The value to add.
*/
private void addPropertyLogic(Object key, Object value)
{
ValueList list = getValueList(key);
if (list == null)
{
put(key, value);
}
else
{
list.add(value);
}
}
private static class ValueList extends ArrayList
{
/**
* Serial version id.
*/
static final long serialVersionUID = -5637755312744414675L;
}
/**
* Given a key, returns a list of values associated with that key.
*
* @param key
* The key.
* @return A list of values associated with the key.
*/
private ValueList getValueList(Object key)
{
ValueList list;
Object old = super.get(key);
if (old instanceof ValueList)
{
list = (ValueList) old;
}
else if (old != null)
{
list = new ValueList();
list.add(old);
put(key, list);
}
else
{
list = null;
}
return list;
}
/**
* Adds a <code>String</code> value to this map for the given property name.
*
* @param name
* the property name
* @param value
* the property value
*/
public void addProperty(String name, String value)
{
addPropertyLogic(name, TRIM_CONFIG_PROPERTY_VALUES && value != null ? value.trim() : value);
}
/**
* Adds a <code>ConfigMap</code> value to this map for the given property name.
*
* @param name
* the property name
* @param value
* the property value
*/
public void addProperty(String name, ConfigMap value)
{
addPropertyLogic(name, value);
}
// TODO UCdetector: Remove unused code:
// /**
// * Gets the set of property names contained in this map.
// *
// * @return a <code>Set</code> of property name <code>String</code>s.
// */
// public Set propertyNames()
// {
// return keySet();
// }
/**
* Sets a property name as allowed without needing to access the property value. This marks a property as allowed for validation purposes.
*
* @param name
* the property name to allow
*/
public void allowProperty(String name)
{
accessedKeys.add(name);
}
/**
* Gets the value for the given property name. Also records that this property was accessed.
*
* @param name
* the property name
* @return the value for the property, or null if property does not exist in this map.
*/
@Override
public Object get(Object name)
{
accessedKeys.add(name);
return super.get(name);
}
/**
* Helper method to get the property with the specified name as a string if possible.
*
* @param name
* The property name.
* @return The property name.
* @throws ConfigurationException
* if there are multiple values for the property name.
*/
private Object getSinglePropertyOrFail(Object name)
{
Object result = get(name);
if (result instanceof ValueList)
{
ConfigurationException exception = new ConfigurationException();
exception.setMessage(UNEXPECTED_MULTIPLE_VALUES, new Object[] { name });
throw exception;
}
return result;
}
/**
* Gets the property with the specified name as a string if possible.
*
* @param name
* The property name.
* @return The property name.
* @throws ConfigurationException
* if there are multiple values for the property name.
*/
public String getProperty(String name)
{
return getPropertyAsString(name, null);
}
/**
* Gets the property with the specified name as a ConfigMap if possible, or returns the default value if the property is undefined.
*
* @throws ConfigurationException
* if there are multiple values for the property name.
*/
public ConfigMap getPropertyAsMap(String name, ConfigMap defaultValue)
{
Object prop = getSinglePropertyOrFail(name);
if (prop instanceof ConfigMap)
{
return (ConfigMap) prop;
}
return defaultValue;
}
/**
* Gets the property with the specified name as a String if possible, or returns the default value if the property is undefined.
*
* @throws ConfigurationException
* if there are multiple values for the property name.
*/
public String getPropertyAsString(String name, String defaultValue)
{
Object prop = getSinglePropertyOrFail(name);
if (prop instanceof String)
{
return (String) prop;
}
return defaultValue;
}
/**
* Gets a property (or set of properties) as a List. If only one property exists it is added as the only entry to a new List.
*
* @param name
* the property name
* @param defaultValue
* the value to return if the property is not found
* @return the value for the property as a List if it exists in this map, otherwise the defaultValue is returned.
*/
public List getPropertyAsList(String name, List defaultValue)
{
Object prop = get(name);
if (prop != null)
{
if (prop instanceof List)
{
return (List) prop;
}
else
{
List list = new ArrayList();
list.add(prop);
return list;
}
}
return defaultValue;
}
/**
* Gets the property with the specified name as a boolean if possible, or returns the default value if the property is undefined.
*
* @throws ConfigurationException
* if there are multiple values for the property name.
*/
public boolean getPropertyAsBoolean(String name, boolean defaultValue)
{
Object prop = getSinglePropertyOrFail(name);
if (prop instanceof String)
{
return Boolean.valueOf((String) prop).booleanValue();
}
return defaultValue;
}
/**
* Gets the property with the specified name as an int if possible, or returns the default value if the property is undefined.
*
* @throws ConfigurationException
* if there are multiple values for the property name.
*/
public int getPropertyAsInt(String name, int defaultValue)
{
Object prop = getSinglePropertyOrFail(name);
if (prop instanceof String)
{
try
{
return Integer.parseInt((String) prop);
}
catch (NumberFormatException ex)
{
}
}
return defaultValue;
}
/**
* Gets the property with the specified name as a long if possible, or returns the default value if the property is undefined.
*
* @throws ConfigurationException
* if there are multiple values for the property name.
*/
public long getPropertyAsLong(String name, long defaultValue)
{
Object prop = getSinglePropertyOrFail(name);
if (prop instanceof String)
{
try
{
return Long.parseLong((String) prop);
}
catch (NumberFormatException ex)
{
}
}
return defaultValue;
}
// TODO UCdetector: Remove unused code:
// /**
// * Returns a list of qualified property names that have not been accessed
// * by one of the get*() methods.
// */
// public List findAllUnusedProperties()
// {
// List result = new ArrayList();
// findUnusedProperties("", true, result);
// return result;
// }
/**
* Gathers a collection of properties that exist in the map but have not been explicitly accessed nor marked as allowed. This list is helpful in validating a set of properties as one can detect
* those that are unknown or unexpected.
*
* @param parentPath
* Used to track the depth of property in a potential hierarchy of <code>ConfigMap</code>s.
* @param recurse
* Whether sub maps should be recursively searched.
* @param result
* the collection of unused properties in this map.
*/
public void findUnusedProperties(String parentPath, boolean recurse, Collection result)
{
Iterator itr = entrySet().iterator();
while (itr.hasNext())
{
Map.Entry entry = (Map.Entry) itr.next();
Object key = entry.getKey();
String currentPath = parentPath + '/' + String.valueOf(key);
if (!accessedKeys.contains(key))
{
result.add(currentPath);
}
else if (recurse)
{
Object value = entry.getValue();
List values = value instanceof List ? (List) value : Collections.singletonList(value);
for (int i = 0; i < values.size(); i++)
{
Object child = values.get(i);
if (child instanceof ConfigMap)
{
((ConfigMap) child).findUnusedProperties(currentPath, recurse, result);
}
}
}
}
}
}