/*
* Cuelib library for manipulating cue sheets.
* Copyright (C) 2007-2008 Jan-Willem van den Broek
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package jwbroek.util;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <p>A StringReplacer will perform a number of string replacements on the same string in a single pass. For instance,
* you can replace all occurrences of "schnauzer" by "bulldog" and all occurrences of "dog" by "cat". Doing this in a
* single pass may yield a different result than doing it in sequence.</p>
*
* <p>For instance, consider the string "The schnauzer chases the other dog." Replacing in a single pass will yield "The
* bulldog chases the other cat.", while doing the replacements in sequence yields first "The bulldog chases the other
* dog.", and then finally "The bullcat chases the other cat."</p>
*
* <p>Searches are done greedily. That is to say, the string "bulldogs rule" will match the search string "bulldog" in
* preference over the search strings "bull" (matches less) and "dogs rule" (matches more, but later).</p>
*
* <p>Instances of this class are reusable. They are also safe for concurrent use, as long as the Map instance they are
* constructed on is safe for concurrent reads. (The Map need not be safe for concurrent writes.) Most Map
* implementations, including {@link java.util.HashMap}, {@link java.util.Hashtable}, and {@link java.util.TreeMap},
* will meet this requirement.</p>
* @author jwbroek
*/
public class StringReplacer
{
/**
* The logger for this class.
*/
private final static Logger logger = Logger.getLogger(StringReplacer.class.getCanonicalName());
/**
* A Pattern that is used to perform the replacements.
*/
private Pattern replacementPattern;
/**
* A map from "value to search for", to "value to change to".
*/
private Map<String, String> replacements;
/**
* Build a reusable replacer based on a "from" "to" mapping of search and replace strings.
* @param replacements A "from" "to" mapping. This Map should not be modified after being passed to this constructor,
* or the behaviour of the StringReplacer will be undefined.
*/
public StringReplacer(Map<String, String> replacements)
{
StringReplacer.logger.entering
(StringReplacer.class.getCanonicalName(), "StringReplacer(Map<String,String>)", replacements);
StringBuilder builder = new StringBuilder();
builder.append('(');
boolean isFirst = true;
for (String key : replacements.keySet())
{
if (isFirst)
{
isFirst = false;
}
else
{
builder.append('|');
}
builder.append("(?:").append(Pattern.quote(key)).append(')');
}
builder.append(')');
this.replacementPattern = Pattern.compile(builder.toString());
this.replacements = replacements;
StringReplacer.logger.exiting(StringReplacer.class.getCanonicalName(), "StringReplacer(Map<String,String>)");
}
/**
* Perform the replacements on the specified input.
* @param input The string to perform replacements on. Note that this String instance will not be modified, as
* String instances are immutable in java.
* @return The result of doing all relevant replacements on the input string.
*/
public String replace(String input)
{
StringReplacer.logger.entering(StringReplacer.class.getCanonicalName(), "replace(String)", input);
StringBuffer buffer = new StringBuffer();
Matcher matcher = this.replacementPattern.matcher(input);
while(matcher.find())
{
matcher.appendReplacement(buffer, Matcher.quoteReplacement(this.replacements.get(matcher.group())));
}
matcher.appendTail(buffer);
String result = buffer.toString();
StringReplacer.logger.entering(StringReplacer.class.getCanonicalName(), "replace(String)", result);
return result;
}
}