【发布时间】:2016-08-22 22:27:48
【问题描述】:
这是我检查replacement string 是否有效的实用方法:
public static boolean isValidReplacementString(String regex, String replacement) {
try {
"".replaceFirst(regex, replacement);
return true;
} catch (IllegalArgumentException | NullPointerException e) {
return false;
}
}
我想在执行真正的替换之前检查一下,因为获取源字符串是昂贵的 (I/O)。
我觉得这个解决方案很老套。我缺少的标准库中是否已有方法?
编辑: As pointed out by sln,如果找不到匹配项,这甚至不起作用。
编辑: Following shmosel's answer,我想出了这个“解决方案”:
private static boolean isLower(char c) {
return c >= 'a' && c <= 'z';
}
private static boolean isUpper(char c) {
return c >= 'A' && c <= 'Z';
}
private static boolean isDigit(char c) {
return isDigit(c - '0');
}
private static boolean isDigit(int c) {
return c >= 0 && c <= 9;
}
@SuppressWarnings("unchecked")
public static void checkRegexAndReplacement(String regex, String replacement) {
Pattern parentPattern = Pattern.compile(regex);
Map<String, Integer> namedGroups;
int capturingGroupCount;
try {
Field namedGroupsField = Pattern.class.getDeclaredField("namedGroups");
namedGroupsField.setAccessible(true);
namedGroups = (Map<String, Integer>) namedGroupsField.get(parentPattern);
Field capturingGroupCountField = Pattern.class.getDeclaredField("capturingGroupCount");
capturingGroupCountField.setAccessible(true);
capturingGroupCount = capturingGroupCountField.getInt(parentPattern);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException("That's what you get for using reflection!", e);
}
int groupCount = capturingGroupCount - 1;
// Process substitution string to replace group references with groups
int cursor = 0;
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);
cursor++;
} else if (nextChar == '$') {
// Skip past $
cursor++;
// Throw IAE if this "$" is the last character in replacement
if (cursor == replacement.length())
throw new IllegalArgumentException(
"Illegal group reference: group index is missing");
nextChar = replacement.charAt(cursor);
int refNum = -1;
if (nextChar == '{') {
cursor++;
StringBuilder gsb = new StringBuilder();
while (cursor < replacement.length()) {
nextChar = replacement.charAt(cursor);
if (isLower(nextChar) ||
isUpper(nextChar) ||
isDigit(nextChar)) {
gsb.append(nextChar);
cursor++;
} else {
break;
}
}
if (gsb.length() == 0)
throw new IllegalArgumentException(
"named capturing group has 0 length name");
if (nextChar != '}')
throw new IllegalArgumentException(
"named capturing group is missing trailing '}'");
String gname = gsb.toString();
if (isDigit(gname.charAt(0)))
throw new IllegalArgumentException(
"capturing group name {" + gname +
"} starts with digit character");
if (namedGroups == null || !namedGroups.containsKey(gname))
throw new IllegalArgumentException(
"No group with name {" + gname + "}");
refNum = namedGroups.get(gname);
cursor++;
} else {
// The first number is always a group
refNum = (int)nextChar - '0';
if (!isDigit(refNum))
throw new IllegalArgumentException(
"Illegal group reference");
cursor++;
// Capture the largest legal group string
boolean done = false;
while (!done) {
if (cursor >= replacement.length()) {
break;
}
int nextDigit = replacement.charAt(cursor) - '0';
if (!isDigit(nextDigit)) { // not a number
break;
}
int newRefNum = (refNum * 10) + nextDigit;
if (groupCount < newRefNum) {
done = true;
} else {
refNum = newRefNum;
cursor++;
}
}
}
if (refNum < 0 || refNum > groupCount) {
throw new IndexOutOfBoundsException("No group " + refNum);
}
} else {
cursor++;
}
}
}
如果此方法抛出,则正则表达式或替换字符串无效。
这甚至比replaceAll 或replaceFirst 更严格,因为如果找不到匹配项,这些方法将不会调用appendReplacement,因此会“丢失”无效的组引用。
【问题讨论】:
-
如果不匹配,我不确定引擎是否会检查替换字符串,我可能是错的。话虽如此,替换时的一些错误可能是在正则表达式中未定义的无效捕获组反向引用。
-
在替换前使用 apache StringUtils.isNotNull 方法检查 null。
-
@sln 你是对的。
isValidReplacementString("test", "$")正在返回true,因为找不到匹配项。所以我的方法甚至不能正常工作。 -
@amitmah 检查
null只是一个小方面。这更多的是关于语法和语义(例如,引用不存在的捕获组)。 -
java的String.replace方法需要什么语法检查?