Adding in new Interpolation class for opinions. It is, apart from cleaning up,

creation of a unit test and renaming to 'interpolate', the submission from Ken
Fitzpatrick in Bugzilla: #18962.

Lang has had a class much like this before so opinions saught (I'm in fact suffering
from some major deja vu with this, need to research how many times I've committed this
type of code to Lang :) ).

Submitted by:	Ken Fitzpatrick


git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/lang/trunk@137865 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Henri Yandell 2004-07-04 04:51:25 +00:00
parent 18baf613e5
commit cddc22f28c
3 changed files with 308 additions and 1 deletions

View File

@ -0,0 +1,182 @@
package org.apache.commons.lang;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Performs basic variable interpolation on a String for variables within
* a Map. Variables of the form, ${var}, are supported.
*
* @author Ken Fitzpatrick
* @author Henri Yandell
*/
public class Interpolation {
// QUERY: Anyway to escape the ${..} variable so it is not interpolated?
// TODO: Consider making these configurable?
private static final String SYMBOLIC_VALUE_MARKER_START = "${";
private static final String SYMBOLIC_VALUE_MARKER_END = "}";
/**
* <p>
* Returns a String that is the result of having performed
* variable interpolation on <code>templateString</code>,
* using the value set found in <code>values</code>.
* </p>
* <p>
* The solution is compatible with all JDK versions
* where Jakarta/Commons/Lang also is supported.
* </p>
* <p>
* The expected format of <code>templateString</code> is:
*<code><pre>
* The ${animal} jumped over the ${target}.
*</pre></code>
* such that the key/value pairs found in <code>values</code>
* are substituted into the string at the <code>${key-name}</code> markers.
* In the above example, <code>valuesMap</code> could have been populated as:
*<code><pre>
* Map valuesMap = HashMap();
* valuesMap.put( "animal", "quick brown fox" );
* valuesMap.put( "target", "lazy dog" );
* String resolvedString = StringUtils.interpolate( templateString, valuesMap );
*</pre></code>
* yielding:
*<code><pre>
* The quick brown fox jumped over the lazy dog.
*</pre></code>
* </p>
* <p>
* The same <code>templateString</code> from the above example could be reused as:
*<code><pre>
* Map valuesMap = HashMap();
* valuesMap.put( "animal", "cow" );
* valuesMap.put( "target", "moon" );
* String resolvedString = StringUtils.interpolate( templateString, valuesMap );
*</pre></code>
* yielding:
*<code><pre>
* The cow jumped over the moon.
*</pre></code>
* </p>
* <p>
* The value of <code>templateString</code> is returned in an unaltered if <code>templateString</code>
* is null, empty, or contains no marked variables that can be resolved by the key/value pairs found in
* <code>valuesMap</code>, or if <code>valuesMap</code> is null, empty or has no key/value pairs that can be
* applied to the marked variables within <code>templateString</code>.
* </p>
* @param templateString String containing any mixture of variable and non-variable
* content, to be used as a template for the value substitution process
* @param valuesMap Map containing the key/value pairs to be used to resolve
* the values of the marked variables found within <code>templateString</code>
* @return String
*/
public static String interpolate( String templateString, Map valuesMap ) {
// pre-conditions
if ( valuesMap == null )
return templateString;
if ( templateString == null )
return templateString;
if ( templateString.length() < 1 )
return templateString;
if ( valuesMap.isEmpty() )
return templateString;
// default the returned String to the templateString
String returnString = templateString;
String nextKey = null;
Object substitutionBean = null;
String substitutionValue = null;
String nextValueToBeSubstituted = null;
// get a list of substitution valuesMap
Iterator keys = valuesMap.keySet().iterator();
while( keys.hasNext() ) {
nextKey = ( String ) keys.next();
substitutionValue = StringUtils.defaultString( ( String ) valuesMap.get( nextKey ) );
nextValueToBeSubstituted = SYMBOLIC_VALUE_MARKER_START + nextKey + SYMBOLIC_VALUE_MARKER_END;
returnString = StringUtils.replace( returnString, nextValueToBeSubstituted, substitutionValue );
}
return returnString;
}
/**
* <p>
* Returns a String that is the result of having performed variable interpolation on
* <code>templateString</code>, using the value set found in <code>values</code>,
* repeatedly until there are no changes.
* </p>
* <p>
* The expected format of <code>templateString</code> is:
*<code><pre>
* The ${animal} jumped over the ${target}.
*</pre></code>
* such that the key/value pairs found in <code>values</code> are substituted into the string at the
* <code>${key-name}</code> markers. In the above example, <code>valuesMap</code>
* could have been populated as:
*<code><pre>
* Map valuesMap = 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" );
* String resolvedString = StringUtils.interpolate( templateString, valuesMap, true );
*</pre></code>
* yielding:
*<code><pre>
* The quick brown fox jumped over the lazy dog.
*</pre></code>
* </p>
* yielding:
*<code><pre>
* The cow jumped over the moon.
*</pre></code>
* </p>
* <p>
* The value of <code>templateString</code> is returned in an unaltered form if
* <code>templateString</code> is null, empty, or
* contains no marked variables that can be resolved by the key/value pairs found in
* <code>valuesMap</code>, or if <code>valuesMap</code> is null, empty or has no key/value
* pairs that can be applied to the marked variables within <code>templateString</code>.
* </p>
* @param templateString String containing any mixture of variable and non-variable
* content, to be used as a template for the value substitution process
* @param valuesMap Map containing the key/value pairs to be used to resolve
* the values of the marked variables found within <code>templateString</code>
* @return String
*/
public static String interpolateRepeatedly(
String templateString,
Map valuesMap)
{
// pre-conditions
if ( valuesMap == null )
return templateString;
if ( templateString == null )
return templateString;
if ( templateString.length() < 1 )
return templateString;
if ( valuesMap.isEmpty() )
return templateString;
String currentResult = templateString;
String previousResult = null;
while( ! StringUtils.equals( currentResult, previousResult ) )
{
previousResult = currentResult;
currentResult = Interpolation.interpolate( previousResult, valuesMap );
}
return currentResult;
}
}

View File

@ -0,0 +1,124 @@
/*
* Copyright 2002-2004 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;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import junit.textui.TestRunner;
import java.util.Map;
import java.util.HashMap;
/**
* Unit tests {@link org.apache.commons.lang.Interpolation}.
*
* @author Henri Yandell
* @author Ken Fitzpatrick
* @version $Id: InterpolationTest.java,v 1.1 2004/07/04 04:51:25 bayard Exp $
*/
public class InterpolationTest extends TestCase {
private static final String INPUT_TEMPLATE = "The ${animal} jumped over the ${target}.";
private static final String EXPECTED_RESULTS_1 = "The quick brown fox jumped over the lazy dog.";
private static final String EXPECTED_RESULTS_2 = "The cow jumped over the moon.";
public InterpolationTest(String name) {
super(name);
}
public static void main(String[] args) {
TestRunner.run(suite());
}
public static Test suite() {
TestSuite suite = new TestSuite(InterpolationTest.class);
suite.setName("Interpolation Tests");
return suite;
}
protected void setUp() throws Exception {
super.setUp();
}
protected void tearDown() throws Exception {
super.tearDown();
}
public void testSimpleVariableSubstitution() {
// test case: "The quick brown fox jumped over the lazy dog."
Map valuesMap = new HashMap();
valuesMap.put( "animal", "quick brown fox" );
valuesMap.put( "target", "lazy dog" );
assertEquals( "Test case 1: simple variable substitution", EXPECTED_RESULTS_1,
Interpolation.interpolate( INPUT_TEMPLATE, valuesMap) );
// test case: "The cow jumped over the moon."
valuesMap = new HashMap();
valuesMap.put( "animal", "cow" );
valuesMap.put( "target", "moon" );
assertEquals( "Test case 2: template reuse, different results" ,EXPECTED_RESULTS_2,
Interpolation.interpolate( INPUT_TEMPLATE, valuesMap) );
}
public void testNullMap() {
// negative test case: Map == null
Map valuesMap = null;
assertEquals( "Test case 3: Map == null", INPUT_TEMPLATE,
Interpolation.interpolate( INPUT_TEMPLATE, valuesMap) );
}
public void testEmptyMap() {
// negative test case: Map.isEmpty()
Map valuesMap = new HashMap();
assertEquals( "Test case 4: Map.isEmpty()", INPUT_TEMPLATE,
Interpolation.interpolate( INPUT_TEMPLATE, valuesMap) );
}
public void testNullTemplate() {
// negative test case: INPUT_TEMPLATE == null
Map valuesMap = new HashMap();
valuesMap.put( "animal", "cow" );
valuesMap.put( "target", "moon" );
assertNull( "Test case 5: template == null",
Interpolation.interpolate( null, valuesMap) );
}
public void testRecursive() {
// test case: process repeatedly
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" );
assertEquals( "Test case 6: interpolateRepeatedly", EXPECTED_RESULTS_1,
Interpolation.interpolateRepeatedly( INPUT_TEMPLATE, valuesMap ) );
// test case: process repeatedly
valuesMap = new HashMap();
valuesMap.put( "animal", "cow" );
valuesMap.put( "target", "${celestialObject}" );
valuesMap.put( "celestialObject", "moon" );
assertEquals( "Test case 8: interpolateRepeatedly", EXPECTED_RESULTS_2,
Interpolation.interpolateRepeatedly( INPUT_TEMPLATE, valuesMap ) );
}
}

View File

@ -26,7 +26,7 @@ import junit.textui.TestRunner;
* @author Stephen Colebourne
* @author <a href="mailto:ridesmet@users.sourceforge.net">Ringo De Smet</a>
* @author Matthew Hawthorne
* @version $Id: LangTestSuite.java,v 1.26 2004/02/18 23:06:19 ggregory Exp $
* @version $Id: LangTestSuite.java,v 1.27 2004/07/04 04:51:25 bayard Exp $
*/
public class LangTestSuite extends TestCase {
@ -62,6 +62,7 @@ public class LangTestSuite extends TestCase {
suite.addTest(EntitiesTest.suite());
suite.addTest(IllegalClassExceptionTest.suite());
suite.addTest(IncompleteArgumentExceptionTest.suite());
suite.addTest(InterpolationTest.suite());
suite.addTest(NotImplementedExceptionTest.suite());
suite.addTest(NullArgumentExceptionTest.suite());
suite.addTest(NumberRangeTest.suite());