Author: scolebourne Date: Sun Jul 23 18:00:37 2006 New Revision: 424871 URL: http://svn.apache.org/viewvc?rev=424871&view=rev Log: Initial commit of StrSubstitutor (VariableFormatter) for review
Added: jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/StrSubstitutor.java (with props) jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/StrSubstitutorTest.java (with props) Added: jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/StrSubstitutor.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/StrSubstitutor.java?rev=424871&view=auto ============================================================================== --- jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/StrSubstitutor.java (added) +++ jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/StrSubstitutor.java Sun Jul 23 18:00:37 2006 @@ -0,0 +1,729 @@ +/* + * Copyright 2005-2006 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.lang.text; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Substitutes variables within a string by values. + * <p> + * This class takes a piece of text and substitutes all the variables within it. + * The default definition of a variable is <code>${variableName}</code>. + * The prefix and suffix can be changed via constructors and set methods. + * <p> + * Variable values are typically resolved from a map, but could also be resolved + * from system properties, or by supplying a custom variable resolver. + * <p> + * The simplest example is to use this class to replace Java System properties. For example: + * <pre> + * StrSubstitutor.replaceSystemProperties( + * "You are running with java.version = ${java.version} and os.name = ${os.name}."); + * </pre> + * <p> + * Typical usage of this class follows the following pattern: First an instance is created + * and initialized with the map that contains the values for the available variables. + * If a prefix and/or suffix for variables should be used other than the default ones, + * the appropriate settings can be performed. After that the <code>replace()</code> + * method can be called passing in the source text for interpolation. In the returned + * text all variable references (as long as their values are known) will be resolved. + * The following example demonstrates this: + * <pre> + * Map valuesMap = HashMap(); + * valuesMap.put("animal", "quick brown fox"); + * valuesMap.put("target", "lazy dog"); + * String templateString = "The ${animal} jumped over the ${target}."; + * StrSubstitutor sub = new StrSubstitutor(valuesMap); + * String resolvedString = sub.replace(templateString); + * </pre> + * yielding: + * <pre> + * The quick brown fox jumped over the lazy dog. + * </pre> + * <p> + * In addition to this usage pattern there are some static convenience methods that + * cover the most common use cases. These methods can be used without the need of + * manually creating an instance. However if multiple replace operations are to be + * performed, creating and reusing an instance of this class will be more efficient. + * <p> + * Variable replacement works in a recursive way. Thus, if a variable value contains + * a variable then that variable will also be replaced. Cyclic replacements are + * detected and will cause an exception to be thrown. + * <p> + * Sometimes the interpolation's result must contain a variable prefix. As an example + * take the following source text: + * <pre> + * The variable ${${name}} must be used. + * </pre> + * Here only the variable's name refered to in the text should be replaced resulting + * in the text (assuming that the value of the <code>name</code> variable is <code>x</code>: + * <pre> + * The variable ${x} must be used. + * </pre> + * To achieve this effect there are two possibilities: Either set a different prefix + * and suffix for variables which do not conflict with the result text you want to + * produce. The other possibility is to use the escape character, by default '$'. + * If this character is placed before a variable reference, this reference is ignored + * and won't be replaced. For example: + * <pre> + * The variable $${${name}} must be used. + * </pre> + * + * @author Oliver Heger + * @author Stephen Colebourne + * @version $Id: VariableFormatter.java 420491 2006-07-10 11:23:57 +0000 (Mon, 10 Jul 2006) niallp $ + * @since 2.2 + */ +public class StrSubstitutor { + + /** + * Constant for the default escape character. + */ + public static final char DEFAULT_ESCAPE = '$'; + /** + * Constant for the default variable prefix. + */ + public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher("${"); + /** + * Constant for the default variable suffix. + */ + public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}"); + + /** + * Stores the escape character. + */ + private char escapeChar; + /** + * Stores the variable prefix. + */ + private StrMatcher prefixMatcher; + /** + * Stores the variable suffix. + */ + private StrMatcher suffixMatcher; + /** + * Variable resolution is delegated to an implementor of VariableResolver. + */ + private VariableResolver variableResolver; + + //----------------------------------------------------------------------- + /** + * Replaces all the occurrences of variables in the given source object with + * their matching values from the map. + * + * @param source the source text containing the variables to substitute + * @param valueMap the map with the values + * @return the result of the replace operation + */ + public static String replace(Object source, Map valueMap) { + return new StrSubstitutor(valueMap).replace(source); + } + + /** + * Replaces all the occurrences of variables in the given source object with + * their matching values from the map. This method allows to specifiy a + * custom variable prefix and suffix + * + * @param source the source text containing the variables to substitute + * @param valueMap the map with the values + * @param prefix the prefix of variables + * @param suffix the suffix of variables + * @return the result of the replace operation + */ + public static String replace(Object source, Map valueMap, String prefix, String suffix) { + return new StrSubstitutor(valueMap, prefix, suffix).replace(source); + } + + /** + * Replaces all the occurrences of variables in the given source object with + * their matching values from the system properties. + * + * @param source the source text containing the variables to substitute + * @return the result of the replace operation + */ + public static String replaceSystemProperties(Object source) { + return new StrSubstitutor(System.getProperties()).replace(source); + } + + //----------------------------------------------------------------------- + /** + * Creates a new instance with defaults for variable prefix and suffix + * and the escaping character. + */ + public StrSubstitutor() { + this((VariableResolver) null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); + } + + /** + * Creates a new instance and initializes it. Uses defaults for variable + * prefix and suffix and the escaping character. + * + * @param valueMap the map with the variables' values, may be null + */ + public StrSubstitutor(Map valueMap) { + this(new MapVariableResolver(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); + } + + /** + * Creates a new instance and initializes it. Uses a default escaping character. + * + * @param valueMap the map with the variables' values, may be null + * @param prefix the prefix for variables, not null + * @param suffix the suffix for variables, not null + * @throws IllegalArgumentException if the prefix or suffix is null + */ + public StrSubstitutor(Map valueMap, String prefix, String suffix) { + this(valueMap, prefix, suffix, DEFAULT_ESCAPE); + } + + /** + * Creates a new instance and initializes it. + * + * @param valueMap the map with the variables' values, may be null + * @param prefix the prefix for variables, not null + * @param suffix the suffix for variables, not null + * @param escape the escape character + * @throws IllegalArgumentException if the prefix or suffix is null + */ + public StrSubstitutor(Map valueMap, String prefix, String suffix, char escape) { + this(new MapVariableResolver(valueMap), prefix, suffix, escape); + } + + /** + * Creates a new instance and initializes it. + * + * @param variableResolver the variable resolver, may be null + * @param prefix the prefix for variables, not null + * @param suffix the suffix for variables, not null + * @param escape the escape character + * @throws IllegalArgumentException if the prefix or suffix is null + */ + public StrSubstitutor(VariableResolver variableResolver, String prefix, String suffix, char escape) { + this.setVariableResolver(variableResolver); + this.setVariablePrefix(prefix); + this.setVariableSuffix(suffix); + this.setEscapeChar(escape); + } + + /** + * Creates a new instance and initializes it. + * + * @param variableResolver the variable resolver, may be null + * @param prefix the prefix for variables, not null + * @param suffix the suffix for variables, not null + * @param escape the escape character + * @throws IllegalArgumentException if the prefix or suffix is null + */ + public StrSubstitutor(VariableResolver variableResolver, StrMatcher prefix, StrMatcher suffix, char escape) { + this.setVariableResolver(variableResolver); + this.setVariablePrefixMatcher(prefix); + this.setVariableSuffixMatcher(suffix); + this.setEscapeChar(escape); + } + + //----------------------------------------------------------------------- + /** + * Replaces all the occurrences of variables in the given source array with + * their matching values from the resolver. + * + * @param source the character array to replace in, not altered, null returns null + * @return the result of the replace operation + */ + public String replace(char[] source) { + if (source == null) { + return null; + } + StrBuilder buf = new StrBuilder(source.length).append(source); + substitute(buf, 0, source.length); + return buf.toString(); + } + + /** + * Replaces all the occurrences of variables in the given source array by with + * their matching values from the resolver. + * Only the specified portion of the array will be processed. + * + * @param source the character array to replace in, not altered, null returns null + * @param offset the start offset within the array, must be valid + * @param length the length within the array to be processed, must be valid + * @return the result of the replace operation + */ + public String replace(char[] source, int offset, int length) { + if (source == null) { + return null; + } + StrBuilder buf = new StrBuilder(length).append(source, offset, length); + substitute(buf, 0, length); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * Replaces all the occurrences of variables in the given source string with + * their matching values from the resolver. + * + * @param source the string to replace in, null returns null + * @return the result of the replace operation + */ + public String replace(String source) { + if (source == null) { + return null; + } + StrBuilder buf = new StrBuilder(source); + if (substitute(buf, 0, source.length()) == false) { + return source; + } + return buf.toString(); + } + + /** + * Replaces all the occurrences of variables in the given source string by with + * their matching values from the resolver. + * Only the specified portion of the string will be processed. + * + * @param source the string to replace in, null returns null + * @param offset the start offset within the array, must be valid + * @param length the length within the array to be processed, must be valid + * @return the result of the replace operation + */ + public String replace(String source, int offset, int length) { + if (source == null) { + return null; + } + StrBuilder buf = new StrBuilder(length).append(source, offset, length); + if (substitute(buf, 0, length) == false) { + return source.substring(offset, length); + } + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * Replaces all the occurrences of variables in the given source object with + * their matching values from the resolver. The input source object is + * converted to a string using <code>toString</code> and is not altered. + * + * @param source the source to replace in, null returns null + * @return the result of the replace operation + */ + public String replace(Object source) { + if (source == null) { + return null; + } + StrBuilder buf = new StrBuilder().append(source); + substitute(buf, 0, buf.length()); + return buf.toString(); + } + + //----------------------------------------------------------------------- + /** + * Replaces all the occurrences of variables within the given source + * builder with their matching values from the resolver. + * + * @param source the builder to replace in, updated, null returns zero + * @return true if altered + */ + public boolean replace(StrBuilder source) { + if (source == null) { + return false; + } + return substitute(source, 0, source.length()); + } + + /** + * Replaces all the occurrences of variables within the given source + * builder with their matching values from the resolver. + * Only the specified portion of the builder will be processed, with + * the remainder left untouched. + * + * @param source the builder to replace in, null returns zero + * @param offset the start offset within the array, must be valid + * @param length the length within the array to be processed, must be valid + * @return true if altered + */ + public boolean replace(StrBuilder source, int offset, int length) { + if (source == null) { + return false; + } + return substitute(source, offset, length); + } + + //----------------------------------------------------------------------- + /** + * Main method for substituting variables. + * + * @param buf the string builder to substitute into, not null + * @param offset the start offset within the builder, must be valid + * @param length the length within the builder to be processed, must be valid + * @return true if altered + */ + private boolean substitute(StrBuilder buf, int offset, int length) { + return substitute(buf, offset, length, null) > 0; + } + + /** + * Recursive handler for multiple levels of interpolation. This is the main + * interpolation method, which resolves the values of all variable references + * contained in the passed in text. + * + * @param buf the string builder to substitute into, not null + * @param offset the start offset within the builder, must be valid + * @param length the length within the builder to be processed, must be valid + * @param priorVariables the stack keeping track of the replaced variables, may be null + * @return the length change that occurs, unless priorVariables is null when the int + * represents a boolean flag as to whether any change occurred. + */ + private int substitute(StrBuilder buf, int offset, int length, List priorVariables) { + StrMatcher prefixMatcher = getVariablePrefixMatcher(); + StrMatcher suffixMatcher = getVariableSuffixMatcher(); + char escape = getEscapeChar(); + + boolean top = (priorVariables == null); + boolean altered = false; + int lengthChange = 0; + char[] chars = buf.buffer; + int bufEnd = offset + length; + int pos = offset; + while (pos < bufEnd) { + int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd); + if (startMatchLen == 0) { + pos++; + } else { + // found variable start marker + if (pos > offset && chars[pos - 1] == escape) { + // escaped + buf.deleteCharAt(pos - 1); + chars = buf.buffer; // in case buffer was altered + lengthChange--; + altered = true; + bufEnd--; + } else { + // find suffix + int startPos = pos; + pos += startMatchLen; + int endMatchLen = 0; + while (pos < bufEnd) { + endMatchLen = suffixMatcher.isMatch(chars, pos, offset, bufEnd); + if (endMatchLen == 0) { + pos++; + } else { + // found variable end marker + String varName = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen); + pos += endMatchLen; + int endPos = pos; + + // on the first call initialize priorVariables + if (priorVariables == null) { + priorVariables = new ArrayList(); + priorVariables.add(new String(chars, offset, length)); + } + + // handle cyclic substitution + checkCyclicSubstitution(varName, priorVariables); + priorVariables.add(varName); + + // resolve the variable + String varValue = resolveVariable(varName); + if (varValue != null) { + // recursive replace + int varLen = varValue.length(); + buf.replace(startPos, endPos, varValue); + altered = true; + int change = substitute(buf, startPos, varLen, priorVariables); + change = change + (varLen - (endPos - startPos)); + pos += change; + bufEnd += change; + lengthChange += change; + chars = buf.buffer; // in case buffer was altered + } + + // remove variable from the cyclic stack + priorVariables.remove(priorVariables.size() - 1); + break; + } + } + } + } + } + if (top) { + return (altered ? 1 : 0); + } + return lengthChange; + } + + /** + * Checks if the specified variable is already in the stack (list) of variables. + * + * @param varName the variable name to check + * @param priorVariables the list of prior variables + */ + private void checkCyclicSubstitution(String varName, List priorVariables) { + if (priorVariables.contains(varName) == false) { + return; + } + StrBuilder buf = new StrBuilder(256); + buf.append("Infinite loop in property interpolation of "); + buf.append(priorVariables.remove(0)); + buf.append(": "); + buf.appendWithSeparators(priorVariables, "->"); + throw new IllegalStateException(buf.toString()); + } + + /** + * 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 varName the name of the variable + * @return the variable's value or <b>null</b> if the variable is unknown + */ + protected String resolveVariable(String varName) { + VariableResolver lookup = getVariableResolver(); + if (lookup == null) { + return null; + } + return lookup.resolveVariable(varName); + } + + // Escape + //----------------------------------------------------------------------- + /** + * Returns the escape character. + * + * @return the character used for escaping variable references + */ + public char getEscapeChar() { + return this.escapeChar; + } + + /** + * 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 setEscapeChar(char escapeCharacter) { + this.escapeChar = escapeCharacter; + } + + // Prefix + //----------------------------------------------------------------------- + /** + * Gets the variable prefix matcher currently in use. + * <p> + * The variable prefix is the characer or characters that identify the + * start of a variable. This prefix is expressed in terms of a matcher + * allowing advanced prefix matches. + * + * @return the prefix matcher in use + */ + public StrMatcher getVariablePrefixMatcher() { + return prefixMatcher; + } + + /** + * Sets the variable prefix matcher currently in use. + * <p> + * The variable prefix is the characer or characters that identify the + * start of a variable. This prefix is expressed in terms of a matcher + * allowing advanced prefix matches. + * + * @param prefixMatcher the prefix matcher to use, null ignored + * @return this, to enable chaining + * @throws IllegalArgumentException if the prefix matcher is null + */ + public StrSubstitutor setVariablePrefixMatcher(StrMatcher prefixMatcher) { + if (prefixMatcher == null) { + throw new IllegalArgumentException("Variable prefix matcher must not be null!"); + } + this.prefixMatcher = prefixMatcher; + return this; + } + + /** + * Sets the variable prefix to use. + * <p> + * The variable prefix is the characer or characters that identify the + * start of a variable. This method allows a single character prefix to + * be easily set. + * + * @param prefix the prefix character to use + * @return this, to enable chaining + */ + public StrSubstitutor setVariablePrefix(char prefix) { + return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix)); + } + + /** + * Sets the variable prefix to use. + * <p> + * The variable prefix is the characer or characters that identify the + * start of a variable. This method allows a string prefix to be easily set. + * + * @param prefix the prefix for variables, not null + * @return this, to enable chaining + * @throws IllegalArgumentException if the prefix is null + */ + public StrSubstitutor setVariablePrefix(String prefix) { + if (prefix == null) { + throw new IllegalArgumentException("Variable prefix must not be null!"); + } + return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix)); + } + + // Suffix + //----------------------------------------------------------------------- + /** + * Gets the variable suffix matcher currently in use. + * <p> + * The variable suffix is the characer or characters that identify the + * end of a variable. This suffix is expressed in terms of a matcher + * allowing advanced suffix matches. + * + * @return the suffix matcher in use + */ + public StrMatcher getVariableSuffixMatcher() { + return suffixMatcher; + } + + /** + * Sets the variable suffix matcher currently in use. + * <p> + * The variable suffix is the characer or characters that identify the + * end of a variable. This suffix is expressed in terms of a matcher + * allowing advanced suffix matches. + * + * @param suffixMatcher the suffix matcher to use, null ignored + * @return this, to enable chaining + * @throws IllegalArgumentException if the suffix matcher is null + */ + public StrSubstitutor setVariableSuffixMatcher(StrMatcher suffixMatcher) { + if (suffixMatcher == null) { + throw new IllegalArgumentException("Variable suffix matcher must not be null!"); + } + this.suffixMatcher = suffixMatcher; + return this; + } + + /** + * Sets the variable suffix to use. + * <p> + * The variable suffix is the characer or characters that identify the + * end of a variable. This method allows a single character suffix to + * be easily set. + * + * @param suffix the suffix character to use + * @return this, to enable chaining + */ + public StrSubstitutor setVariableSuffix(char suffix) { + return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix)); + } + + /** + * Sets the variable suffix to use. + * <p> + * The variable suffix is the characer or characters that identify the + * end of a variable. This method allows a string suffix to be easily set. + * + * @param suffix the suffix for variables, not null + * @return this, to enable chaining + * @throws IllegalArgumentException if the suffix is null + */ + public StrSubstitutor setVariableSuffix(String suffix) { + if (suffix == null) { + throw new IllegalArgumentException("Variable suffix must not be null!"); + } + return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix)); + } + + // Resolver + //----------------------------------------------------------------------- + /** + * Gets the VariableResolver + * + * @return the VariableResolver + */ + public VariableResolver getVariableResolver() { + return this.variableResolver; + } + + /** + * Sets the VariableResolver + * + * @param variableResolver the VariableResolver + */ + public void setVariableResolver(VariableResolver variableResolver) { + this.variableResolver = variableResolver; + } + + //----------------------------------------------------------------------- + /** + * Looks up a string value by name. + * This represents the simplest form of a map. + */ + public static interface VariableResolver { + /** + * Resolves the variable name to a value. + * + * @param varName the name to be looked up, may be null + * @return the matching value, null if no match + */ + String resolveVariable(String varName); + } + + //----------------------------------------------------------------------- + /** + * Looks up a string value by name using a [EMAIL PROTECTED] Map}. + */ + static class MapVariableResolver implements VariableResolver { + /** + * Map keys are variable names and value + */ + Map map; + + /** + * Creates a new resolver backed by a Map. + * + * @param map the variable names and values + */ + MapVariableResolver(Map map) { + this.map = map; + } + + /** + * Resolves the given variable name with the backing Map. + * + * @param varName a variable name + * @return a value or null if the variable name is not in Map + */ + public String resolveVariable(String varName) { + if (map == null) { + return null; + } + Object obj = map.get(varName); + if (obj == null) { + return null; + } + return obj.toString(); + } + } + +} Propchange: jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/StrSubstitutor.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jakarta/commons/proper/lang/trunk/src/java/org/apache/commons/lang/text/StrSubstitutor.java ------------------------------------------------------------------------------ svn:keywords = author date id revision Added: jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/StrSubstitutorTest.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/StrSubstitutorTest.java?rev=424871&view=auto ============================================================================== --- jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/StrSubstitutorTest.java (added) +++ jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/StrSubstitutorTest.java Sun Jul 23 18:00:37 2006 @@ -0,0 +1,440 @@ +/* + * Copyright 2005-2006 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.lang.text; + +import java.util.HashMap; +import java.util.Map; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; +import junit.textui.TestRunner; + +import org.apache.commons.lang.text.StrSubstitutor.MapVariableResolver; + +/** + * Test class for StrSubstitutor. + * + * @author Oliver Heger + * @version $Id: StrSubstitutorTest.java 231316 2005-08-10 20:36:26Z ggregory $ + */ +public class StrSubstitutorTest extends TestCase { + + private Map values; + + /** + * Main method. + * + * @param args command line arguments, ignored + */ + public static void main(String[] args) { + TestRunner.run(suite()); + } + + /** + * Return a new test suite containing this test case. + * + * @return a new test suite containing this test case + */ + public static Test suite() { + TestSuite suite = new TestSuite(StrSubstitutorTest.class); + suite.setName("StrSubstitutor Tests"); + return suite; + } + + protected void setUp() throws Exception { + super.setUp(); + values = new HashMap(); + values.put("animal", "quick brown fox"); + values.put("target", "lazy dog"); + } + + protected void tearDown() throws Exception { + super.tearDown(); + values = null; + } + + //----------------------------------------------------------------------- +// /** +// * Tests escaping variable references. +// */ +// public void testEscape() { +// assertEquals("${", this.getFormat().replace("$${")); +// assertEquals("${animal}", this.getFormat().replace("$${animal}")); +// this.getValueMap().put("var_name", "x"); +// 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(StrSubstitutor.DEFAULT_PREFIX, this.getFormat().getVariablePrefixMatcher()); +// assertEquals(StrSubstitutor.DEFAULT_SUFFIX, this.getFormat().getVariableSuffixMatcher()); +// assertEquals(StrSubstitutor.DEFAULT_ESCAPE, this.getFormat().getEscapeChar()); +// +// format = new StrSubstitutor(values, "<<", ">>", '\\'); +// assertEquals("<<", this.getFormat().getVariablePrefixMatcher()); +// assertEquals(">>", this.getFormat().getVariableSuffixMatcher()); +// assertEquals('\\', this.getFormat().getEscapeChar()); +// +// // new StrSubstitutor(null) should be OK IMO +// // Gary Gregory - July 14 2005 +// // try { +// // format = new StrSubstitutor(null); +// // fail("Could create format object with null map!"); +// // } catch (IllegalArgumentException iex) { +// // // ok +// // } +// +// try { +// format = new StrSubstitutor(values, "${", null); +// fail("Could create format object with undefined suffix!"); +// } catch (IllegalArgumentException iex) { +// // ok +// } +// +// try { +// format = new StrSubstitutor(values, null, "]"); +// fail("Could create format object with undefined prefix!"); +// } catch (IllegalArgumentException iex) { +// // ok +// } +// } +// +// /** +// * Tests chaning variable prefix and suffix and the escaping character. +// */ +// public void testNonDefaultTokens() { +// format = new StrSubstitutor(values, "<<", ">>", '\\'); +// assertEquals("The quick brown fox jumps over the lazy dog.", format +// .replace("The <<animal>> jumps over the <<target>>.")); +// assertEquals("The quick brown fox jumps over the <<target>>.", format +// .replace("The <<animal>> jumps over the \\<<target>>.")); +// } +// +// /** +// * Tests invoking the static convenience methods. +// */ +// public void testNonInstanceMethods() { +// assertEquals("The quick brown fox jumps over the lazy dog.", +// StrSubstitutor.replace(REPLACE_TEMPLATE, values)); +// values.put(KEY_ANIMAL, "cow"); +// values.put(KEY_TARGET, "moon"); +// assertEquals("The cow jumps over the moon.", +// StrSubstitutor.replace("The &animal; jumps over the ⌖.", values, "&", ";")); +// } +// +// public void testNoResolver() throws Exception { +// this.testNoResolver(new StrSubstitutor()); +// this.testNoResolver(new StrSubstitutor(null)); +// } +// +// void testNoResolver(StrSubstitutor formatter) throws Exception { +// formatter.setVariableResolver(null); +// this.validateNoReplace(formatter); +// } +// +// public void testNullMap() throws Exception { +// StrSubstitutor formatter = new StrSubstitutor(null); +// validateNoReplace(formatter); +// } +// + + //----------------------------------------------------------------------- + /** + * Tests simple key replace. + */ + public void testReplaceSimple() { + doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true); + } + + /** + * Tests simple key replace. + */ + public void testReplaceSolo() { + doTestReplace("quick brown fox", "${animal}", false); + } + + /** + * Tests replace with no variables. + */ + public void testReplaceNoVariables() { + doTestNoReplace("The balloon arrived."); + } + + /** + * Tests replace with null. + */ + public void testReplaceNull() { + doTestNoReplace(null); + } + + /** + * Tests replace with null. + */ + public void testReplaceEmpty() { + doTestNoReplace(""); + } + + /** + * Tests key replace changing map after initialization (not recommended). + */ + public void testReplaceChangedMap() { + StrSubstitutor sub = new StrSubstitutor(values); + values.put("target", "moon"); + assertEquals("The quick brown fox jumps over the moon.", sub.replace("The ${animal} jumps over the ${target}.")); + } + + /** + * Tests unknown key replace. + */ + public void testReplaceUnknownKey() { + doTestReplace("The ${person} jumps over the lazy dog.", "The ${person} jumps over the ${target}.", true); + } + + /** + * Tests adjacent keys. + */ + public void testReplaceAdjacentAtStart() { + values.put("code", "GBP"); + values.put("amount", "12.50"); + StrSubstitutor sub = new StrSubstitutor(values); + assertEquals("GBP12.50 charged", sub.replace("${code}${amount} charged")); + } + + /** + * Tests adjacent keys. + */ + public void testReplaceAdjacentAtEnd() { + values.put("code", "GBP"); + values.put("amount", "12.50"); + StrSubstitutor sub = new StrSubstitutor(values); + assertEquals("Amount is GBP12.50", sub.replace("Amount is ${code}${amount}")); + } + + /** + * Tests simple recursive replace. + */ + public void testReplaceRecursive() { + values.put("animal", "${critter}"); + values.put("target", "${pet}"); + values.put("pet", "${petCharacteristic} dog"); + values.put("petCharacteristic", "lazy"); + values.put("critter", "${critterSpeed} ${critterColor} ${critterType}"); + values.put("critterSpeed", "quick"); + values.put("critterColor", "brown"); + values.put("critterType", "fox"); + doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true); + } + + /** + * Tests escaping. + */ + public void testReplaceEscaping() { + doTestReplace("The ${animal} jumps over the lazy dog.", "The $${animal} jumps over the ${target}.", true); + } + + /** + * Tests escaping. + */ + public void testReplaceSoloEscaping() { + doTestReplace("${animal}", "$${animal}", false); + } + + /** + * Tests complex escaping. + */ + public void testReplaceComplexEscaping() { + doTestReplace("The ${quick brown fox} jumps over the lazy dog.", "The $${${animal}} jumps over the ${target}.", true); + } + + /** + * Tests when no prefix or suffix. + */ + public void testReplaceNoPefixNoSuffix() { + doTestReplace("The animal jumps over the lazy dog.", "The animal jumps over the ${target}.", true); + } + + /** + * Tests when no incomplete prefix. + */ + public void testReplaceIncompletePefix() { + doTestReplace("The {animal} jumps over the lazy dog.", "The {animal} jumps over the ${target}.", true); + } + + /** + * Tests when prefix but no suffix. + */ + public void testReplacePrefixNoSuffix() { + doTestReplace("The ${animal jumps over the ${target} lazy dog.", "The ${animal jumps over the ${target} ${target}.", true); + } + + /** + * Tests when suffix but no prefix. + */ + public void testReplaceNoPrefixSuffix() { + doTestReplace("The animal} jumps over the lazy dog.", "The animal} jumps over the ${target}.", true); + } + + /** + * Tests when no variable name. + */ + public void testReplaceEmptyKeys() { + doTestReplace("The ${} jumps over the lazy dog.", "The ${} jumps over the ${target}.", true); + } + + /** + * Tests replace creates output same as input. + */ + public void testReplaceToIdentical() { + values.put("animal", "$${${thing}}"); + values.put("thing", "animal"); + doTestReplace("The ${animal} jumps.", "The ${animal} jumps.", true); + } + + /** + * Tests a cyclic replace operation. + * The cycle should be detected and cause an exception to be thrown. + */ + public void testCyclicReplacement() { + Map map = new HashMap(); + map.put("animal", "${critter}"); + map.put("target", "${pet}"); + map.put("pet", "${petCharacteristic} dog"); + map.put("petCharacteristic", "lazy"); + map.put("critter", "${critterSpeed} ${critterColor} ${critterType}"); + map.put("critterSpeed", "quick"); + map.put("critterColor", "brown"); + map.put("critterType", "${animal}"); + StrSubstitutor sub = new StrSubstitutor(map); + try { + sub.replace("The ${animal} jumps over the ${target}."); + fail("Cyclic replacement was not detected!"); + } catch (IllegalStateException ex) { + // expected + } + } + + /** + * Tests interpolation with weird boundary patterns. + */ + public void testReplaceWeirdPattens() { + doTestNoReplace(""); + doTestNoReplace("${}"); + doTestNoReplace("${ }"); + doTestNoReplace("${\t}"); + doTestNoReplace("${\n}"); + doTestNoReplace("${\b}"); + doTestNoReplace("${"); + doTestNoReplace("$}"); + doTestNoReplace("}"); + doTestNoReplace("${}$"); + doTestNoReplace("${${"); + doTestNoReplace("${${}}"); + doTestNoReplace("${$${}}"); + doTestNoReplace("${$$${}}"); + doTestNoReplace("${$$${$}}"); + doTestNoReplace("${${}}"); + doTestNoReplace("${${ }}"); + } + + //----------------------------------------------------------------------- +// /** +// * Tests source texts with nothing to replace. +// */ +// public void testReplaceNothing() { +// assertNull(this.getFormat().replace((char[]) null)); +// assertNull(this.getFormat().replace((String) null)); +// assertNull(this.getFormat().replace((Object) null)); +// assertEquals("Nothing to replace.", this.getFormat().replace("Nothing to replace.")); +// assertEquals("42", this.getFormat().replace(new Integer(42))); +// assertEquals(0, this.getFormat().replace((StrBuilder) null)); +// } +// +//// /** +//// * 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. +// */ +// public void testReplaceSystemProperties() { +// StringBuffer buf = new StringBuffer(); +// buf.append("Hi ").append(System.getProperty("user.name")); +// buf.append(", you are working with "); +// buf.append(System.getProperty("os.name")); +// buf.append(", your home directory is "); +// buf.append(System.getProperty("user.home")).append('.'); +// assertEquals(buf.toString(), StrSubstitutor.replaceSystemProperties("Hi ${user.name}, you are " +// + "working with ${os.name}, your home " +// + "directory is ${user.home}.")); +// } +// + //----------------------------------------------------------------------- + private void doTestReplace(String expectedResult, String replaceTemplate, boolean substring) { + String expectedShortResult = expectedResult.substring(1, expectedResult.length() - 1); + + StrSubstitutor sub = new StrSubstitutor(values); + assertEquals(expectedResult, sub.replace(replaceTemplate)); + if (substring) { + assertEquals(expectedShortResult, sub.replace(replaceTemplate, 1, replaceTemplate.length() - 2)); + } + + char[] chars = replaceTemplate.toCharArray(); + assertEquals(expectedResult, sub.replace(chars)); + if (substring) { + assertEquals(expectedShortResult, sub.replace(chars, 1, chars.length - 2)); + } + + StringBuffer buf = new StringBuffer(replaceTemplate); + assertEquals(expectedResult, sub.replace(buf)); + + StrBuilder bld = new StrBuilder(replaceTemplate); + assertEquals(true, sub.replace(bld)); + assertEquals(expectedResult, bld.toString()); + + if (substring) { + bld = new StrBuilder(replaceTemplate); + assertEquals(true, sub.replace(bld, 1, bld.length() - 2)); + assertEquals(expectedResult, bld.toString()); // expect full result as remainder is untouched + } + } + + private void doTestNoReplace(String replaceTemplate) { + StrSubstitutor sub = new StrSubstitutor(values); + assertEquals(replaceTemplate, sub.replace(replaceTemplate)); + + if (replaceTemplate == null) { + assertEquals(null, sub.replace((char[]) null)); + assertEquals(null, sub.replace((Object) null)); + assertEquals(false, sub.replace((StrBuilder) null)); + } else { + StrBuilder bld = new StrBuilder(replaceTemplate); + assertEquals(false, sub.replace(bld)); + assertEquals(replaceTemplate, bld.toString()); + } + } + +} Propchange: jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/StrSubstitutorTest.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: jakarta/commons/proper/lang/trunk/src/test/org/apache/commons/lang/text/StrSubstitutorTest.java ------------------------------------------------------------------------------ svn:keywords = author date id revision --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]