I wasn't quite satisfied with any of the solutions here. I wanted a stateless solution. And I didn't want to end up in an infinite loop if my replacement string happened to match the pattern. While I was at it I added support for a limit
parameter and a returned count
parameter. (I used an AtomicInteger
to simulate passing an integer by reference.) I moved the callback
parameter to the end of the parameter list, to make it easier to define an anonymous class.
Here is an example of usage:
final Map<String,String> props = new HashMap<String,String>();
props.put("MY_NAME", "Kip");
props.put("DEPT", "R&D");
props.put("BOSS", "Dave");
String subjectString = "Hi my name is ${MY_NAME} and I work in ${DEPT} for ${BOSS}";
String sRegex = "\\$\\{([A-Za-z0-9_]+)\\}";
String replacement = ReplaceCallback.replace(sRegex, subjectString, new ReplaceCallback.Callback() {
public String matchFound(MatchResult match) {
String group1 = match.group(1);
if(group1 != null && props.containsKey(group1))
return props.get(group1);
return match.group();
}
});
System.out.println("replacement: " + replacement);
And here is my version of ReplaceCallback class:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.*;
public class ReplaceCallback
{
public static interface Callback {
/**
* This function is called when a match is made. The string which was matched
* can be obtained via match.group(), and the individual groupings via
* match.group(n).
*/
public String matchFound(MatchResult match);
}
/**
* Replaces with callback, with no limit to the number of replacements.
* Probably what you want most of the time.
*/
public static String replace(String pattern, String subject, Callback callback)
{
return replace(pattern, subject, -1, null, callback);
}
public static String replace(String pattern, String subject, int limit, Callback callback)
{
return replace(pattern, subject, limit, null, callback);
}
/**
* @param regex The regular expression pattern to search on.
* @param subject The string to be replaced.
* @param limit The maximum number of replacements to make. A negative value
* indicates replace all.
* @param count If this is not null, it will be set to the number of
* replacements made.
* @param callback Callback function
*/
public static String replace(String regex, String subject, int limit,
AtomicInteger count, Callback callback)
{
StringBuffer sb = new StringBuffer();
Matcher matcher = Pattern.compile(regex).matcher(subject);
int i;
for(i = 0; (limit < 0 || i < limit) && matcher.find(); i++)
{
String replacement = callback.matchFound(matcher.toMatchResult());
replacement = Matcher.quoteReplacement(replacement); //probably what you want...
matcher.appendReplacement(sb, replacement);
}
matcher.appendTail(sb);
if(count != null)
count.set(i);
return sb.toString();
}
}