Friday, July 14, 2017

Nasty problem using String.replaceAll and String.replaceFirst

A customer ran into a problem using my code yesterday. My code does a bunch of SQL stuff and years ago I started using tokens and the replace methods to make things more readable.
Instead of:

String query = "select value from synonymtable where domainid='" + domainid.toUpperCase() + "' and internalvalue='" + internalvalue.toUpperCase() + "' and defaults = 1" ;

I had:

String query = "select value from synonymtable where domainid='<domainid>' and internalvalue='<internalvalue>' and defaults = 1"
.replaceAll("<domainid>", domainid.toUpperCase())
.replaceAll("<internalvalue>", internalvalue.toUpperCase()) ;

Using the replacement methods made the code much clearer. And I could create a template string.

static final String queryTemplate = "select value from synonymtable where domainid='<domainid>' and internalvalue='<internalvalue>' and defaults = 1" ;

The problem comes in because these replace methods take regular expressions, but I thought I'm fine since I'm always just doing straight replacements. Unfortunately the client had added a column with a $ to their database. Something like COST$.  In a regular expression the dollar sign ($) means something, so this breaks a bunch of code.

Caused by: Illegal group reference
at java.util.regex.Matcher(Matcher.java: 819)
at java.util.regex.Matcher(Matcher.java: 917)
at java.lang.String(String.java: 1718)

And I have 400 over places where I'm using the replace methods. Arrgh!

I've created a utility method to work around this.
public static String replaceAll(String template, String ... tokenValuePairs)
{
    if (tokenValuePairs.length % 2 != 0)
    {
        throw new RuntimeException("Method expects pairs of tokens and replacement values") ;
    }
   
    String ret = template ;
   
    for (int x = 0; x < tokenValuePairs.length; x = x + 2)
    {
        ret = ret.replaceAll(tokenValuePairs[x], Matcher.quoteReplacement(tokenValuePairs[x+1])) ;
    }
   
    return ret ;
}

The Matcher#quoteReplacement formats replacement string so I don't need to escape any characters.
Now the code will look like:

String query = StringUtility.replaceAll("select value from synonymtable where domainid='' and internalvalue='' and defaults = 1",
    "", domainid.toUpperCase(),
    "", internalvalue.toUpperCase()) ;


Unfortunately I need to fix probably hundreds of places where the replace methods are used.

1 comment: