EBM
EBM

Reputation: 1099

Pattern matcher replace cycles in an infinite loop

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

Answers (3)

ajb
ajb

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

Tim Biegeleisen
Tim Biegeleisen

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:

Rextester

Upvotes: 2

Naman
Naman

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

Related Questions