[Bug 35588] - [lang] New interpolation features.
Retro-fits the VariableResolver interface into the VariableFormatter class and provides a Map-backed VariableResolver implementation. git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/lang/trunk@219076 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
ce389de49f
commit
421ba4c94c
|
@ -96,204 +96,97 @@ import org.apache.commons.lang.StringUtils;
|
||||||
* @since 2.2
|
* @since 2.2
|
||||||
*/
|
*/
|
||||||
public class VariableFormatter {
|
public class VariableFormatter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A VariableResolver backed by a {@link Map}.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
|
||||||
|
* @version $Id$
|
||||||
|
*/
|
||||||
|
public static class MapVariableResolver implements VariableResolver {
|
||||||
|
/**
|
||||||
|
* Map keys are variable names and value
|
||||||
|
*/
|
||||||
|
private Map map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new VariableResolver backed by a Map.
|
||||||
|
*
|
||||||
|
* @param map
|
||||||
|
* The variable names and values.
|
||||||
|
*/
|
||||||
|
public MapVariableResolver(Map map) {
|
||||||
|
this.setMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the variable names and values.
|
||||||
|
*
|
||||||
|
* @return the variable names and values.
|
||||||
|
*/
|
||||||
|
public Map getMap() {
|
||||||
|
return this.map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the given variable name with the backing Map.
|
||||||
|
*
|
||||||
|
* @param varName
|
||||||
|
* a variable name
|
||||||
|
* @return a value or <code>null</code> if the variable name is not in Map
|
||||||
|
*/
|
||||||
|
public Object resolveVariable(String varName) {
|
||||||
|
if (this.getMap() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.getMap().get(varName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the variable names and values.
|
||||||
|
*
|
||||||
|
* @param map
|
||||||
|
* the variable names and values.
|
||||||
|
*/
|
||||||
|
public void setMap(Map map) {
|
||||||
|
this.map = map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Definition of an interface for obtaining values for variables.
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* Objects implementing this interface can be passed to <code>VariableFormatter</code> as source for variables'
|
||||||
|
* values. The interface is quite simple and defines only a single method for retrieving the value of a specified
|
||||||
|
* value.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public static interface VariableResolver {
|
||||||
|
/**
|
||||||
|
* Returns the value of the specified variable. The variable's value can be an arbitrary object. If no variable
|
||||||
|
* with the given name is known, an implementation should return <b>null</b>.
|
||||||
|
*
|
||||||
|
* @param varName
|
||||||
|
* the name of the searched variable
|
||||||
|
* @return the variable's value
|
||||||
|
*/
|
||||||
|
Object resolveVariable(String varName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constant for the default escape character. */
|
||||||
|
static final char DEFAULT_ESCAPE = '$';
|
||||||
|
|
||||||
/** Constant for the default variable prefix. */
|
/** Constant for the default variable prefix. */
|
||||||
static final String DEFAULT_PREFIX = "${";
|
static final String DEFAULT_PREFIX = "${";
|
||||||
|
|
||||||
/** Constant for the default variable suffix. */
|
/** Constant for the default variable suffix. */
|
||||||
static final String DEFAULT_SUFFIX = "}";
|
static final String DEFAULT_SUFFIX = "}";
|
||||||
|
|
||||||
/** Constant for the default escape character. */
|
|
||||||
static final char DEFAULT_ESCAPE = '$';
|
|
||||||
|
|
||||||
/** Stores the map with the variables' values. */
|
|
||||||
private Map valueMap;
|
|
||||||
|
|
||||||
/** Stores the variable prefix. */
|
|
||||||
private String variablePrefix;
|
|
||||||
|
|
||||||
/** Stores the variable suffix. */
|
|
||||||
private String variableSuffix;
|
|
||||||
|
|
||||||
/** Stores the escape character. */
|
|
||||||
private char escapeCharacter;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of <code>VariableFormat</code> and initializes it.
|
* Replaces the occurrences of all variables in the given source data by their current values obtained from the
|
||||||
*
|
* passed in map.
|
||||||
* @param valueMap
|
|
||||||
* the map with the variables' values
|
|
||||||
* @param prefix
|
|
||||||
* the prefix for variables
|
|
||||||
* @param suffix
|
|
||||||
* the suffix for variables
|
|
||||||
* @param escape
|
|
||||||
* the escape character
|
|
||||||
* @throws IllegalArgumentException
|
|
||||||
* if the map is undefined
|
|
||||||
*/
|
|
||||||
public VariableFormatter(Map valueMap, String prefix, String suffix, char escape) {
|
|
||||||
setValueMap(valueMap);
|
|
||||||
setVariablePrefix(prefix);
|
|
||||||
setVariableSuffix(suffix);
|
|
||||||
setEscapeCharacter(escape);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance of <code>VariableFormat</code> and initializes it.
|
|
||||||
* Uses a default escaping character.
|
|
||||||
*
|
|
||||||
* @param valueMap
|
|
||||||
* the map with the variables' values
|
|
||||||
* @param prefix
|
|
||||||
* the prefix for variables
|
|
||||||
* @param suffix
|
|
||||||
* the suffix for variables
|
|
||||||
* @throws IllegalArgumentException
|
|
||||||
* if the map is undefined
|
|
||||||
*/
|
|
||||||
public VariableFormatter(Map valueMap, String prefix, String suffix) {
|
|
||||||
this(valueMap, prefix, suffix, DEFAULT_ESCAPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance of <code>VariableFormat</code> and initializes it.
|
|
||||||
* Uses defaults for variable prefix and suffix and the escaping character.
|
|
||||||
*
|
|
||||||
* @param valueMap
|
|
||||||
* the map with the variables' values
|
|
||||||
* @throws IllegalArgumentException
|
|
||||||
* if the map is undefined
|
|
||||||
*/
|
|
||||||
public VariableFormatter(Map valueMap) {
|
|
||||||
this(valueMap, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the escape character.
|
|
||||||
*
|
|
||||||
* @return the character used for escaping variable references
|
|
||||||
*/
|
|
||||||
public char getEscapeCharacter() {
|
|
||||||
return this.escapeCharacter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the escape character. If this character is placed before a
|
|
||||||
* variable reference in the source text, this variable will be ignored.
|
|
||||||
*
|
|
||||||
* @param escapeCharacter
|
|
||||||
* the escape character (0 for disabling escaping)
|
|
||||||
*/
|
|
||||||
public void setEscapeCharacter(char escapeCharacter) {
|
|
||||||
this.escapeCharacter = escapeCharacter;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the map with the variables' values.
|
|
||||||
*
|
|
||||||
* @return the values of the variables
|
|
||||||
*/
|
|
||||||
public Map getValueMap() {
|
|
||||||
return this.valueMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the map with the variables' values.
|
|
||||||
*
|
|
||||||
* @param valueMap
|
|
||||||
* the values of the variables
|
|
||||||
* @throws IllegalArgumentException
|
|
||||||
* if <code>valueMap</code> is <b>null</b>
|
|
||||||
*/
|
|
||||||
public void setValueMap(Map valueMap) throws IllegalArgumentException {
|
|
||||||
if (valueMap == null) {
|
|
||||||
throw new IllegalArgumentException("Value map must not be null");
|
|
||||||
}
|
|
||||||
this.valueMap = valueMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the prefix for variables.
|
|
||||||
*
|
|
||||||
* @return the prefix for variables
|
|
||||||
*/
|
|
||||||
public String getVariablePrefix() {
|
|
||||||
return this.variablePrefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the prefix for variables.
|
|
||||||
*
|
|
||||||
* @param variablePrefix
|
|
||||||
* the prefix for variables
|
|
||||||
* @throws IllegalArgumentException
|
|
||||||
* if the prefix is <b>null</b>
|
|
||||||
*/
|
|
||||||
public void setVariablePrefix(String variablePrefix) throws IllegalArgumentException {
|
|
||||||
if (variablePrefix == null) {
|
|
||||||
throw new IllegalArgumentException("Variable prefix must not be null!");
|
|
||||||
}
|
|
||||||
this.variablePrefix = variablePrefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the suffix for variables.
|
|
||||||
*
|
|
||||||
* @return the suffix for variables
|
|
||||||
*/
|
|
||||||
public String getVariableSuffix() {
|
|
||||||
return this.variableSuffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the suffix for variables
|
|
||||||
*
|
|
||||||
* @param variableSuffix
|
|
||||||
* the suffix for variables
|
|
||||||
* @throws IllegalArgumentException
|
|
||||||
* if the prefix is <b>null</b>
|
|
||||||
*/
|
|
||||||
public void setVariableSuffix(String variableSuffix) throws IllegalArgumentException {
|
|
||||||
if (variableSuffix == null) {
|
|
||||||
throw new IllegalArgumentException("Variable suffix must not be null!");
|
|
||||||
}
|
|
||||||
this.variableSuffix = variableSuffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces the occurrences of all variables in the given source data by
|
|
||||||
* their current values. If the source consists only of a single variable
|
|
||||||
* reference, this method directly returns the value of this variable
|
|
||||||
* (which can be an arbitrary object). If the source contains multiple
|
|
||||||
* variable references or static text, the return value will always be a
|
|
||||||
* String with the concatenation of all these elements.
|
|
||||||
*
|
|
||||||
* @param source
|
|
||||||
* the text to be interpolated; this can be an arbitrary object whose <code>toString()</code> method
|
|
||||||
* will be called
|
|
||||||
* @return the result of the replace operation
|
|
||||||
*/
|
|
||||||
public Object replaceObject(Object source) {
|
|
||||||
return doReplace(source, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces the occurrences of all variables in the given source data by
|
|
||||||
* their current values.
|
|
||||||
*
|
|
||||||
* @param source
|
|
||||||
* the text to be interpolated; this can be an arbitrary object whose <code>toString()</code> method
|
|
||||||
* will be called
|
|
||||||
* @return the result of the replace operation
|
|
||||||
*/
|
|
||||||
public String replace(Object source) {
|
|
||||||
Object result = replaceObject(source);
|
|
||||||
return result == null ? null : result.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replaces the occurrences of all variables in the given source data by
|
|
||||||
* their current values obtained from the passed in map.
|
|
||||||
*
|
*
|
||||||
* @param valueMap
|
* @param valueMap
|
||||||
* the map with the values
|
* the map with the values
|
||||||
|
@ -306,9 +199,8 @@ public class VariableFormatter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces the occurrences of all variables in the given source data by
|
* Replaces the occurrences of all variables in the given source data by their current values obtained from the
|
||||||
* their current values obtained from the passed in map. This method
|
* passed in map. This method allows to specifiy a custom variable prefix and suffix
|
||||||
* allows to specifiy a custom variable prefix and suffix
|
|
||||||
*
|
*
|
||||||
* @param valueMap
|
* @param valueMap
|
||||||
* the map with the values
|
* the map with the values
|
||||||
|
@ -325,8 +217,7 @@ public class VariableFormatter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replaces all variables in the given source data with values obtained
|
* Replaces all variables in the given source data with values obtained from system properties.
|
||||||
* from system properties.
|
|
||||||
*
|
*
|
||||||
* @param source
|
* @param source
|
||||||
* the source text
|
* the source text
|
||||||
|
@ -336,102 +227,70 @@ public class VariableFormatter {
|
||||||
return new VariableFormatter(System.getProperties()).replace(source);
|
return new VariableFormatter(System.getProperties()).replace(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Stores the escape character. */
|
||||||
|
private char escapeCharacter;
|
||||||
|
|
||||||
|
/** Stores the variable prefix. */
|
||||||
|
private String variablePrefix;
|
||||||
|
|
||||||
|
private VariableResolver variableResolver;
|
||||||
|
|
||||||
|
/** Stores the variable suffix. */
|
||||||
|
private String variableSuffix;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the variable reference found at the specified position is
|
* Creates a new instance of <code>VariableFormat</code> and initializes it. Uses defaults for variable prefix and
|
||||||
* escaped and if this is the case, where the escaped text starts.
|
* suffix and the escaping character.
|
||||||
*
|
*
|
||||||
* @param text
|
* @param valueMap
|
||||||
* the text to be processed
|
* the map with the variables' values
|
||||||
* @param beginIndex
|
* @throws IllegalArgumentException
|
||||||
* the start index of the variable reference to check
|
* if the map is undefined
|
||||||
* @return the starting index of the escaped text or -1 if this reference is not escaped
|
|
||||||
*/
|
*/
|
||||||
protected int escaped(String text, int beginIndex) {
|
public VariableFormatter(Map valueMap) {
|
||||||
if (beginIndex < 1 || text.charAt(beginIndex - 1) != getEscapeCharacter()) {
|
this(valueMap, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
int idx = beginIndex - 2;
|
|
||||||
while (idx >= 0 && text.charAt(idx) == getEscapeCharacter()) {
|
|
||||||
idx--;
|
|
||||||
}
|
|
||||||
return idx + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unescapes an escaped variable reference. This method is called if
|
* Creates a new instance of <code>VariableFormat</code> and initializes it. Uses a default escaping character.
|
||||||
* <code>escaped()</code> has determined an escaped variable reference.
|
|
||||||
* Its purpose is to remove any escaping characters and to add the
|
|
||||||
* resulting text into the target buffer. This implementation will remove
|
|
||||||
* the first escape character. So if the default values are used,
|
|
||||||
* a text portion of <code>$${myvar}</code> will become <code>${myvar}</code>,
|
|
||||||
* <code>$$$${var with dollars}</code> will result in <code>$$${var with
|
|
||||||
* dollars}</code>. Text between the first variable start token and the last
|
|
||||||
* unescaped variable end token can contain variable references and will be
|
|
||||||
* recursively replaced. So constructs of the following form can be built:
|
|
||||||
* <code>Variable $${${varName$}} is incorrect!</code> (note how the first
|
|
||||||
* "}" character is escaped, so that the second "}"
|
|
||||||
* marks the end of this construct.
|
|
||||||
*
|
*
|
||||||
* @param buf
|
* @param valueMap
|
||||||
* the target buffer
|
* the map with the variables' values
|
||||||
* @param text
|
* @param prefix
|
||||||
* the text to be processed
|
* the prefix for variables
|
||||||
* @param beginIndex
|
* @param suffix
|
||||||
* the begin index of the escaped variable reference
|
* the suffix for variables
|
||||||
* @param endIndex
|
* @throws IllegalArgumentException
|
||||||
* the end index of the escaped variable reference
|
* if the map is undefined
|
||||||
* @param priorVariables
|
|
||||||
* keeps track of the replaced variables
|
|
||||||
*/
|
*/
|
||||||
protected void unescape(StringBuffer buf, String text, int beginIndex, int endIndex, List priorVariables) {
|
public VariableFormatter(Map valueMap, String prefix, String suffix) {
|
||||||
int startToken = text.indexOf(getVariablePrefix(), beginIndex);
|
this(valueMap, prefix, suffix, DEFAULT_ESCAPE);
|
||||||
buf.append(text.substring(beginIndex + 1, startToken));
|
|
||||||
buf.append(getVariablePrefix());
|
|
||||||
String escapedContent = text.substring(startToken + getVariablePrefix().length(), endIndex);
|
|
||||||
buf.append(doReplace(StringUtils.replace(escapedContent, String.valueOf(getEscapeCharacter())
|
|
||||||
+ getVariableSuffix(), getVariableSuffix()), priorVariables));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Searches for a variable end token in the given string from the
|
* Creates a new instance of <code>VariableFormat</code> and initializes it.
|
||||||
* specified start position.
|
|
||||||
*
|
*
|
||||||
* @param text
|
* @param valueMap
|
||||||
* the text to search
|
* the map with the variables' values
|
||||||
* @param beginIndex
|
* @param prefix
|
||||||
* the start index
|
* the prefix for variables
|
||||||
* @return the index of the end token or -1 if none was found
|
* @param suffix
|
||||||
|
* the suffix for variables
|
||||||
|
* @param escape
|
||||||
|
* the escape character
|
||||||
|
* @throws IllegalArgumentException
|
||||||
|
* if the map is undefined
|
||||||
*/
|
*/
|
||||||
protected int findEndToken(String text, int beginIndex) {
|
public VariableFormatter(Map valueMap, String prefix, String suffix, char escape) {
|
||||||
int pos = beginIndex - getVariableSuffix().length();
|
this.setVariableResolver(new MapVariableResolver(valueMap));
|
||||||
|
this.setVariablePrefix(prefix);
|
||||||
do {
|
this.setVariableSuffix(suffix);
|
||||||
pos = text.indexOf(getVariableSuffix(), pos + getVariableSuffix().length());
|
this.setEscapeCharacter(escape);
|
||||||
} while (pos > 0 && getEscapeCharacter() == text.charAt(pos - 1));
|
|
||||||
|
|
||||||
return pos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the specified variable. This method is called whenever a
|
* Recursive handler for multple levels of interpolation. This is the main interpolation method, which resolves the
|
||||||
* variable reference is detected in the source text. It is passed the
|
* values of all variable references contained in the passed in text.
|
||||||
* variable's name and must return the corresponding value.
|
|
||||||
* This implementation accesses the value map using the variable's name
|
|
||||||
* as key. Derived classes may override this method to implement a different
|
|
||||||
* strategy for resolving variables.
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* the name of the variable
|
|
||||||
* @return the variable's value or <b>null</b> if the variable is unknown
|
|
||||||
*/
|
|
||||||
protected Object resolveVariable(String name) {
|
|
||||||
return getValueMap().get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursive handler for multple levels of interpolation. This is the main
|
|
||||||
* interpolation method, which resolves the values of all variable
|
|
||||||
* references contained in the passed in text.
|
|
||||||
*
|
*
|
||||||
* @param base
|
* @param base
|
||||||
* string with the ${key} variables
|
* string with the ${key} variables
|
||||||
|
@ -530,4 +389,197 @@ public class VariableFormatter {
|
||||||
return (objResult != null && objLen > 0 && objLen == result.length()) ? objResult : result.toString();
|
return (objResult != null && objLen > 0 && objLen == result.length()) ? objResult : result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the variable reference found at the specified position is escaped and if this is the case, where the
|
||||||
|
* escaped text starts.
|
||||||
|
*
|
||||||
|
* @param text
|
||||||
|
* the text to be processed
|
||||||
|
* @param beginIndex
|
||||||
|
* the start index of the variable reference to check
|
||||||
|
* @return the starting index of the escaped text or -1 if this reference is not escaped
|
||||||
|
*/
|
||||||
|
protected int escaped(String text, int beginIndex) {
|
||||||
|
if (beginIndex < 1 || text.charAt(beginIndex - 1) != getEscapeCharacter()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int idx = beginIndex - 2;
|
||||||
|
while (idx >= 0 && text.charAt(idx) == getEscapeCharacter()) {
|
||||||
|
idx--;
|
||||||
|
}
|
||||||
|
return idx + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for a variable end token in the given string from the specified start position.
|
||||||
|
*
|
||||||
|
* @param text
|
||||||
|
* the text to search
|
||||||
|
* @param beginIndex
|
||||||
|
* the start index
|
||||||
|
* @return the index of the end token or -1 if none was found
|
||||||
|
*/
|
||||||
|
protected int findEndToken(String text, int beginIndex) {
|
||||||
|
int pos = beginIndex - getVariableSuffix().length();
|
||||||
|
|
||||||
|
do {
|
||||||
|
pos = text.indexOf(getVariableSuffix(), pos + getVariableSuffix().length());
|
||||||
|
} while (pos > 0 && getEscapeCharacter() == text.charAt(pos - 1));
|
||||||
|
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the escape character.
|
||||||
|
*
|
||||||
|
* @return the character used for escaping variable references
|
||||||
|
*/
|
||||||
|
public char getEscapeCharacter() {
|
||||||
|
return this.escapeCharacter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the prefix for variables.
|
||||||
|
*
|
||||||
|
* @return the prefix for variables
|
||||||
|
*/
|
||||||
|
public String getVariablePrefix() {
|
||||||
|
return this.variablePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VariableResolver getVariableResolver() {
|
||||||
|
return this.variableResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the suffix for variables.
|
||||||
|
*
|
||||||
|
* @return the suffix for variables
|
||||||
|
*/
|
||||||
|
public String getVariableSuffix() {
|
||||||
|
return this.variableSuffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the occurrences of all variables in the given source data by their current values.
|
||||||
|
*
|
||||||
|
* @param source
|
||||||
|
* the text to be interpolated; this can be an arbitrary object whose <code>toString()</code> method
|
||||||
|
* will be called
|
||||||
|
* @return the result of the replace operation
|
||||||
|
*/
|
||||||
|
public String replace(Object source) {
|
||||||
|
Object result = replaceObject(source);
|
||||||
|
return result == null ? null : result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the occurrences of all variables in the given source data by their current values. If the source
|
||||||
|
* consists only of a single variable reference, this method directly returns the value of this variable (which can
|
||||||
|
* be an arbitrary object). If the source contains multiple variable references or static text, the return value
|
||||||
|
* will always be a String with the concatenation of all these elements.
|
||||||
|
*
|
||||||
|
* @param source
|
||||||
|
* the text to be interpolated; this can be an arbitrary object whose <code>toString()</code> method
|
||||||
|
* will be called
|
||||||
|
* @return the result of the replace operation
|
||||||
|
*/
|
||||||
|
public Object replaceObject(Object source) {
|
||||||
|
return doReplace(source, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the specified variable. This method is called whenever a variable reference is detected in the source
|
||||||
|
* text. It is passed the variable's name and must return the corresponding value. This implementation accesses the
|
||||||
|
* value map using the variable's name as key. Derived classes may override this method to implement a different
|
||||||
|
* strategy for resolving variables.
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* the name of the variable
|
||||||
|
* @return the variable's value or <b>null</b> if the variable is unknown
|
||||||
|
*/
|
||||||
|
protected Object resolveVariable(String name) {
|
||||||
|
if (this.getVariableResolver() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.getVariableResolver().resolveVariable(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the escape character. If this character is placed before a variable reference in the source text, this
|
||||||
|
* variable will be ignored.
|
||||||
|
*
|
||||||
|
* @param escapeCharacter
|
||||||
|
* the escape character (0 for disabling escaping)
|
||||||
|
*/
|
||||||
|
public void setEscapeCharacter(char escapeCharacter) {
|
||||||
|
this.escapeCharacter = escapeCharacter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the prefix for variables.
|
||||||
|
*
|
||||||
|
* @param variablePrefix
|
||||||
|
* the prefix for variables
|
||||||
|
* @throws IllegalArgumentException
|
||||||
|
* if the prefix is <b>null</b>
|
||||||
|
*/
|
||||||
|
public void setVariablePrefix(String variablePrefix) throws IllegalArgumentException {
|
||||||
|
if (variablePrefix == null) {
|
||||||
|
throw new IllegalArgumentException("Variable prefix must not be null!");
|
||||||
|
}
|
||||||
|
this.variablePrefix = variablePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVariableResolver(VariableResolver variableResolver) {
|
||||||
|
this.variableResolver = variableResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the suffix for variables
|
||||||
|
*
|
||||||
|
* @param variableSuffix
|
||||||
|
* the suffix for variables
|
||||||
|
* @throws IllegalArgumentException
|
||||||
|
* if the prefix is <b>null</b>
|
||||||
|
*/
|
||||||
|
public void setVariableSuffix(String variableSuffix) throws IllegalArgumentException {
|
||||||
|
if (variableSuffix == null) {
|
||||||
|
throw new IllegalArgumentException("Variable suffix must not be null!");
|
||||||
|
}
|
||||||
|
this.variableSuffix = variableSuffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unescapes an escaped variable reference. This method is called if <code>escaped()</code> has determined an
|
||||||
|
* escaped variable reference. Its purpose is to remove any escaping characters and to add the resulting text into
|
||||||
|
* the target buffer. This implementation will remove the first escape character. So if the default values are used,
|
||||||
|
* a text portion of <code>$${myvar}</code> will become <code>${myvar}</code>,
|
||||||
|
* <code>$$$${var with dollars}</code> will result in <code>$$${var with
|
||||||
|
* dollars}</code>. Text between the
|
||||||
|
* first variable start token and the last unescaped variable end token can contain variable references and will be
|
||||||
|
* recursively replaced. So constructs of the following form can be built:
|
||||||
|
* <code>Variable $${${varName$}} is incorrect!</code> (note how the first "}" character is escaped, so
|
||||||
|
* that the second "}" marks the end of this construct.
|
||||||
|
*
|
||||||
|
* @param buf
|
||||||
|
* the target buffer
|
||||||
|
* @param text
|
||||||
|
* the text to be processed
|
||||||
|
* @param beginIndex
|
||||||
|
* the begin index of the escaped variable reference
|
||||||
|
* @param endIndex
|
||||||
|
* the end index of the escaped variable reference
|
||||||
|
* @param priorVariables
|
||||||
|
* keeps track of the replaced variables
|
||||||
|
*/
|
||||||
|
protected void unescape(StringBuffer buf, String text, int beginIndex, int endIndex, List priorVariables) {
|
||||||
|
int startToken = text.indexOf(getVariablePrefix(), beginIndex);
|
||||||
|
buf.append(text.substring(beginIndex + 1, startToken));
|
||||||
|
buf.append(getVariablePrefix());
|
||||||
|
String escapedContent = text.substring(startToken + getVariablePrefix().length(), endIndex);
|
||||||
|
buf.append(doReplace(StringUtils.replace(escapedContent, String.valueOf(getEscapeCharacter())
|
||||||
|
+ getVariableSuffix(), getVariableSuffix()), priorVariables));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ import java.util.Map;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
import org.apache.commons.lang.text.VariableFormatter.MapVariableResolver;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test class for VariableFormatter.
|
* Test class for VariableFormatter.
|
||||||
*
|
*
|
||||||
|
@ -34,6 +36,26 @@ public class VariableFormatterTest extends TestCase {
|
||||||
|
|
||||||
private Map values;
|
private Map values;
|
||||||
|
|
||||||
|
VariableFormatter getFormat() {
|
||||||
|
return this.format;
|
||||||
|
}
|
||||||
|
|
||||||
|
MapVariableResolver getMapVariableResolver() {
|
||||||
|
return (MapVariableResolver)this.getFormat().getVariableResolver();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map getValueMap() {
|
||||||
|
return this.getMapVariableResolver().getMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map getValues() {
|
||||||
|
return this.values;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setFormat(VariableFormatter format) {
|
||||||
|
this.format = format;
|
||||||
|
}
|
||||||
|
|
||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
super.setUp();
|
super.setUp();
|
||||||
Map map = new HashMap();
|
Map map = new HashMap();
|
||||||
|
@ -43,89 +65,12 @@ public class VariableFormatterTest extends TestCase {
|
||||||
setFormat(new VariableFormatter(map));
|
setFormat(new VariableFormatter(map));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void setValueMap(Map valuesMap) {
|
||||||
* Tests creating new <code>VariableFormat</code> objects.
|
this.getMapVariableResolver().setMap(valuesMap);
|
||||||
*/
|
|
||||||
public void testInitialize() {
|
|
||||||
assertNotNull(format.getValueMap());
|
|
||||||
assertEquals(VariableFormatter.DEFAULT_PREFIX, format.getVariablePrefix());
|
|
||||||
assertEquals(VariableFormatter.DEFAULT_SUFFIX, format.getVariableSuffix());
|
|
||||||
assertEquals(VariableFormatter.DEFAULT_ESCAPE, format.getEscapeCharacter());
|
|
||||||
|
|
||||||
format = new VariableFormatter(values, "<<", ">>", '\\');
|
|
||||||
assertEquals("<<", format.getVariablePrefix());
|
|
||||||
assertEquals(">>", format.getVariableSuffix());
|
|
||||||
assertEquals('\\', format.getEscapeCharacter());
|
|
||||||
|
|
||||||
try {
|
|
||||||
format = new VariableFormatter(null);
|
|
||||||
fail("Could create format object with null map!");
|
|
||||||
} catch (IllegalArgumentException iex) {
|
|
||||||
// ok
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
format = new VariableFormatter(values, "${", null);
|
|
||||||
fail("Could create format object with undefined suffix!");
|
|
||||||
} catch (IllegalArgumentException iex) {
|
|
||||||
// ok
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
format = new VariableFormatter(values, null, "]");
|
|
||||||
fail("Could create format object with undefined prefix!");
|
|
||||||
} catch (IllegalArgumentException iex) {
|
|
||||||
// ok
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void setValues(Map values) {
|
||||||
* Tests typical replace operations.
|
this.values = values;
|
||||||
*/
|
|
||||||
public void testReplace() {
|
|
||||||
assertEquals("The quick brown fox jumps over the lazy dog.", format.replaceObject(REPLACE_TEMPLATE));
|
|
||||||
|
|
||||||
format.getValueMap().put("animal", "cow");
|
|
||||||
format.getValueMap().put("target", "moon");
|
|
||||||
assertEquals("The cow jumps over the moon.", format.replace(REPLACE_TEMPLATE));
|
|
||||||
|
|
||||||
assertEquals("Variable ${var} is unknown!", format.replace("Variable ${var} is unknown!"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests source texts with nothing to replace.
|
|
||||||
*/
|
|
||||||
public void testReplaceNothing() {
|
|
||||||
assertNull(format.replace(null));
|
|
||||||
assertEquals("Nothing to replace.", format.replace("Nothing to replace."));
|
|
||||||
assertEquals("42", format.replace(new Integer(42)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests escaping variable references.
|
|
||||||
*/
|
|
||||||
public void testEscape() {
|
|
||||||
assertEquals("${animal}", format.replace("$${animal}"));
|
|
||||||
format.getValueMap().put("var_name", "x");
|
|
||||||
assertEquals("Many $$$$${target} $s", format.replace("Many $$$$$${target} $s"));
|
|
||||||
assertEquals("Variable ${x} must be used!", format.replace("Variable $${${var_name$}} must be used!"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests recursive replacements.
|
|
||||||
*/
|
|
||||||
public void testRecursiveReplacement() {
|
|
||||||
Map valuesMap = new HashMap();
|
|
||||||
valuesMap.put("animal", "${critter}");
|
|
||||||
valuesMap.put("target", "${pet}");
|
|
||||||
valuesMap.put("pet", "${petCharacteristic} dog");
|
|
||||||
valuesMap.put("petCharacteristic", "lazy");
|
|
||||||
valuesMap.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
|
|
||||||
valuesMap.put("critterSpeed", "quick");
|
|
||||||
valuesMap.put("critterColor", "brown");
|
|
||||||
valuesMap.put("critterType", "fox");
|
|
||||||
format.setValueMap(valuesMap);
|
|
||||||
assertEquals("The quick brown fox jumps over the lazy dog.", format.replace(REPLACE_TEMPLATE));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -141,9 +86,9 @@ public class VariableFormatterTest extends TestCase {
|
||||||
valuesMap.put("critterSpeed", "quick");
|
valuesMap.put("critterSpeed", "quick");
|
||||||
valuesMap.put("critterColor", "brown");
|
valuesMap.put("critterColor", "brown");
|
||||||
valuesMap.put("critterType", "${animal}");
|
valuesMap.put("critterType", "${animal}");
|
||||||
format.setValueMap(valuesMap);
|
this.setValueMap(valuesMap);
|
||||||
try {
|
try {
|
||||||
format.replace(REPLACE_TEMPLATE);
|
this.getFormat().replace(REPLACE_TEMPLATE);
|
||||||
fail("Cyclic replacement was not detected!");
|
fail("Cyclic replacement was not detected!");
|
||||||
} catch (IllegalStateException isx) {
|
} catch (IllegalStateException isx) {
|
||||||
// ok
|
// ok
|
||||||
|
@ -151,12 +96,51 @@ public class VariableFormatterTest extends TestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests operating on objects.
|
* Tests escaping variable references.
|
||||||
*/
|
*/
|
||||||
public void testReplaceObject() {
|
public void testEscape() {
|
||||||
format.getValueMap().put("value", new Integer(42));
|
assertEquals("${animal}", this.getFormat().replace("$${animal}"));
|
||||||
assertEquals(new Integer(42), format.replaceObject("${value}"));
|
this.getValueMap().put("var_name", "x");
|
||||||
assertEquals("The answer is 42.", format.replaceObject("The answer is ${value}."));
|
assertEquals("Many $$$$${target} $s", this.getFormat().replace("Many $$$$$${target} $s"));
|
||||||
|
assertEquals("Variable ${x} must be used!", this.getFormat().replace("Variable $${${var_name$}} must be used!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests creating new <code>VariableFormat</code> objects.
|
||||||
|
*/
|
||||||
|
public void testInitialize() {
|
||||||
|
assertNotNull(this.getFormat().getVariableResolver());
|
||||||
|
assertEquals(VariableFormatter.DEFAULT_PREFIX, this.getFormat().getVariablePrefix());
|
||||||
|
assertEquals(VariableFormatter.DEFAULT_SUFFIX, this.getFormat().getVariableSuffix());
|
||||||
|
assertEquals(VariableFormatter.DEFAULT_ESCAPE, this.getFormat().getEscapeCharacter());
|
||||||
|
|
||||||
|
format = new VariableFormatter(values, "<<", ">>", '\\');
|
||||||
|
assertEquals("<<", this.getFormat().getVariablePrefix());
|
||||||
|
assertEquals(">>", this.getFormat().getVariableSuffix());
|
||||||
|
assertEquals('\\', this.getFormat().getEscapeCharacter());
|
||||||
|
|
||||||
|
// new VariableFormatter(null) should be OK IMO
|
||||||
|
// Gary Gregory - July 14 2005
|
||||||
|
// try {
|
||||||
|
// format = new VariableFormatter(null);
|
||||||
|
// fail("Could create format object with null map!");
|
||||||
|
// } catch (IllegalArgumentException iex) {
|
||||||
|
// // ok
|
||||||
|
// }
|
||||||
|
|
||||||
|
try {
|
||||||
|
format = new VariableFormatter(values, "${", null);
|
||||||
|
fail("Could create format object with undefined suffix!");
|
||||||
|
} catch (IllegalArgumentException iex) {
|
||||||
|
// ok
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
format = new VariableFormatter(values, null, "]");
|
||||||
|
fail("Could create format object with undefined prefix!");
|
||||||
|
} catch (IllegalArgumentException iex) {
|
||||||
|
// ok
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -181,6 +165,54 @@ public class VariableFormatterTest extends TestCase {
|
||||||
"The &animal; jumps over the ⌖."));
|
"The &animal; jumps over the ⌖."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests recursive replacements.
|
||||||
|
*/
|
||||||
|
public void testRecursiveReplacement() {
|
||||||
|
Map valuesMap = new HashMap();
|
||||||
|
valuesMap.put("animal", "${critter}");
|
||||||
|
valuesMap.put("target", "${pet}");
|
||||||
|
valuesMap.put("pet", "${petCharacteristic} dog");
|
||||||
|
valuesMap.put("petCharacteristic", "lazy");
|
||||||
|
valuesMap.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
|
||||||
|
valuesMap.put("critterSpeed", "quick");
|
||||||
|
valuesMap.put("critterColor", "brown");
|
||||||
|
valuesMap.put("critterType", "fox");
|
||||||
|
this.setValueMap(valuesMap);
|
||||||
|
assertEquals("The quick brown fox jumps over the lazy dog.", this.getFormat().replace(REPLACE_TEMPLATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests typical replace operations.
|
||||||
|
*/
|
||||||
|
public void testReplace() {
|
||||||
|
assertEquals("The quick brown fox jumps over the lazy dog.", this.getFormat().replaceObject(REPLACE_TEMPLATE));
|
||||||
|
Map map = this.getValueMap();
|
||||||
|
map.put("animal", "cow");
|
||||||
|
map.put("target", "moon");
|
||||||
|
assertEquals("The cow jumps over the moon.", this.getFormat().replace(REPLACE_TEMPLATE));
|
||||||
|
|
||||||
|
assertEquals("Variable ${var} is unknown!", this.getFormat().replace("Variable ${var} is unknown!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests source texts with nothing to replace.
|
||||||
|
*/
|
||||||
|
public void testReplaceNothing() {
|
||||||
|
assertNull(this.getFormat().replace(null));
|
||||||
|
assertEquals("Nothing to replace.", this.getFormat().replace("Nothing to replace."));
|
||||||
|
assertEquals("42", this.getFormat().replace(new Integer(42)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests operating on objects.
|
||||||
|
*/
|
||||||
|
public void testReplaceObject() {
|
||||||
|
this.getValueMap().put("value", new Integer(42));
|
||||||
|
assertEquals(new Integer(42), this.getFormat().replaceObject("${value}"));
|
||||||
|
assertEquals("The answer is 42.", this.getFormat().replaceObject("The answer is ${value}."));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests interpolation with system properties.
|
* Tests interpolation with system properties.
|
||||||
*/
|
*/
|
||||||
|
@ -195,20 +227,4 @@ public class VariableFormatterTest extends TestCase {
|
||||||
+ "working with ${os.name}, your home "
|
+ "working with ${os.name}, your home "
|
||||||
+ "directory is ${user.home}."));
|
+ "directory is ${user.home}."));
|
||||||
}
|
}
|
||||||
|
|
||||||
Map getValues() {
|
|
||||||
return this.values;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setValues(Map values) {
|
|
||||||
this.values = values;
|
|
||||||
}
|
|
||||||
|
|
||||||
VariableFormatter getFormat() {
|
|
||||||
return this.format;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setFormat(VariableFormatter format) {
|
|
||||||
this.format = format;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue