关于Java中Match类的appendReplacement()方法的一个坑{java.lang.IllegalArgumentException: character to be escaped is missing}
问题描述
在向替换结果中追加信息的时候,调用match.appendReplacement(buffer,str)时出现了上述异常。
class XX{
public String renderString(String source, Map<String, Object> context, Map<String, Object> data) throws OgnlException {
Pattern pattern = Pattern.compile(DELIM);
Matcher matcher = pattern.matcher(source);
StringBuffer buffer = new StringBuffer();
while (matcher.find()) {
String e = matcher.group(1);
if (e == null) throw new NullPointerException("expression can not be null");
Object value = Ognl.getValue(e, context, data);
String str = null == value ? "null" : value.toString();
matcher.appendReplacement(buffer, str); //在此处报错
}
matcher.appendTail(buffer);
return buffer.toString();
}
}
问题原因
通过阅读Matcher类的源码并查阅文档得知,在 matcher.appendReplacement(buffer, str)方法中str中若含有'\'、'$'字符时,将存在特殊含义,
'$n'代表匹配的第n组结果,'\'将对其之后的字符进行转义。
文档如下
实现非终端添加和替换步骤。
此方法执行以下操作:
它从添加位置开始在输入序列读取字符,并将其添加到给定字符串缓冲区。在读取以前匹配之前的最后字符(即位于索引 start() - 1 处的字符)之后,它就会停止。
它将给定替换字符串添加到字符串缓冲区。
它将此匹配器的添加位置设置为最后匹配位置的索引加 1,即 end()。
替换字符串可能包含到以前匹配期间所捕获的子序列的引用:$g 每次出现时,都将被 group(g) 的计算结果替换。$ 之后的第一个数始终被视为组引用的一部分。如果后续的数可以形成合法组引用,则将被合并到 g 中。只有数字 '0' 到 '9' 被视为组引用的可能组件。例如,如果第二个组匹配字符串 "foo",则传递替换字符串 "$2bar" 将导致 "foobar" 被添加到字符串缓冲区。可能将美元符号 ($) 作为替换字符串中的字面值(通过前面使用一个反斜线 (\$))包括进来。
注意,在替换字符串中使用反斜线 (\) 和美元符号 ($) 可能导致与作为字面值替换字符串时所产生的结果不同。美元符号可视为到如上所述已捕获子序列的引用,反斜线可用于转义替换字符串中的字面值字符。
此方法设计用于循环以及 appendTail 和 find 方法中。例如,以下代码将 one dog two dogs in the yard 写入标准输出流中:
Pattern p = Pattern.compile("cat");
Matcher m = p.matcher("one cat two cats in the yard");
StringBuffer sb = new StringBuffer();
while (m.find()) {
m.appendReplacement(sb, "dog");
}
m.appendTail(sb);
System.out.println(sb.toString());
参数:
sb - 目标字符串缓冲区。
replacement - 替换字符串。
返回:
匹配器。
抛出:
IllegalStateException - 如果没有尝试任何匹配,或者以前的匹配操作失败。
IndexOutOfBoundsException - 如果替换字符串引用模式中不存在的捕获组。
源码如下
public final class Matcher implements MatchResult {
//省略
public Matcher appendReplacement(StringBuffer sb, String replacement) {
//省略
while (cursor < replacement.length()) {
char nextChar = replacement.charAt(cursor);
if (nextChar == '\\') {
cursor++;
if (cursor == replacement.length())
throw new IllegalArgumentException(
"character to be escaped is missing");
nextChar = replacement.charAt(cursor);
result.append(nextChar);
cursor++;
} else if (nextChar == '$') {
//省略
} else {
result.append(nextChar);
cursor++;
}
}
// Append the intervening text
sb.append(text, lastAppendPosition, first);
// Append the match substitution
sb.append(result);
lastAppendPosition = last;
return this;
}
}