Reputation: 1099
I have the following cycle:
public class Main {
public static void main (String[] args){
String test = "#{value} lorem ipsum #{value} lorem ipsum";
String regex = "(#\\{)([^}]*)(})";
Pattern callPattern = Pattern.compile(regex);
Matcher callMatcher = callPattern.matcher(test);
while (callMatcher.find()) {
test = callMatcher.replaceFirst(generate());
}
System.out.println(test);
}
private static String generate(){
Random random = new Random();
return String.valueOf(random.nextInt(100));
}
}
And execution gets stuck in my while loop. I have used the similar algorithm in the past, so why is this one stuck? It seems it is able to replace the first occurrence, but then finds but never replaces the second.
Upvotes: 2
Views: 2169
Reputation: 31699
The question isn't all that simple. It's true that in this code:
Matcher callMatcher = callPattern.matcher(test);
while (callMatcher.find()) {
test = callMatcher.replaceFirst(generate());
assigning to test
doesn't replace the string in the matcher.
However, that doesn't entirely explain the situation, since callMatcher.find()
normally finds the next occurrence of the match. Depending on what's in the body of the while
loop, this means that the while
loop may be executed only twice. So why is it looping infinitely in this case?
It has to do with the matcher being reset. The javadoc for find()
says:
This method starts at the beginning of this matcher's region, or, if a previous invocation of the method was successful and the matcher has not since been reset, at the first character not matched by the previous match.
The last half of the sentence means that if the matcher isn't reset, then find()
will find the next occurrence of the pattern, which means that find()
would only succeed twice, and then the while
loop would exit. But the javadoc says this about replaceFirst()
in Matcher
:
This method first resets this matcher.
So since the matcher is reset, find()
will search from the beginning each time, instead of where the last match left off. This might explain the question in your comment, about why the code works in other places. It may be that you're not calling anything that resets the matcher.
Perhaps the best way to handle a situation like this, where you want to replace each occurrence of a pattern, is to use appendReplacement
and appendTail
. The javadoc for appendReplacement
has a nice example showing how to use it.
Upvotes: 0
Reputation: 521629
You could also avoid a matcher completely and instead just rely on the base string functions String#matches
and String#replaceFirst
:
String test = "#{value} lorem ipsum #{value} lorem ipsum";
while (test.matches(".*#\\{[^}]*\\}.*")) {
test = test.replaceFirst("#\\{[^}]*\\}", generate());
}
System.out.println(test);
Output:
87 lorem ipsum 57 lorem ipsum
Demo here:
Upvotes: 2
Reputation: 31938
The reason being the Matcher in your case remains same within the while
loop:
Matcher callMatcher = callPattern.matcher(test);
while (callMatcher.find()) { // same value for matcher
test = callMatcher.replaceFirst(generate()); // you just keep updating the same #{value} everytime
}
Change this to :
Matcher callMatcher = callPattern.matcher(test);
while (callMatcher.find()) {
test = callMatcher.replaceFirst(generate());
callMatcher = callPattern.matcher(test); // updates the matcher with replaced text
}
Upvotes: 4