Skip to content

Commit caafa31

Browse files
sbrannenjgallimore
authored andcommitted
Increase scope of regex pattern cache for the SpEL matches operator
Prior to this commit, the pattern cache for the SpEL `matches` operator only applied to expressions such as the following where the same `matches` operator is invoked multiple times with different input: "map.keySet().?[#this matches '.+xyz']" The pattern cache did not apply to expressions such as the following where the same pattern ('.+xyz') is used in multiple `matches` operations: "foo matches '.+xyz' AND bar matches '.+xyz'" This commit addresses this by moving the instance of the pattern cache map from OperatorMatches to InternalSpelExpressionParser so that the cache can be reused for all `matches` operations for the given parser. Closes spring-projectsgh-30140
1 parent f2ac120 commit caafa31

File tree

2 files changed

+242
-216
lines changed

2 files changed

+242
-216
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/ast/OperatorMatches.java

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -42,13 +42,28 @@ public class OperatorMatches extends Operator {
4242

4343
private static final int PATTERN_ACCESS_THRESHOLD = 1000000;
4444

45-
private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<String, Pattern>();
45+
private final ConcurrentMap<String, Pattern> patternCache;
4646

4747

48-
public OperatorMatches(int pos, SpelNodeImpl... operands) {
49-
super("matches", pos, operands);
48+
/**
49+
* Create a new {@link OperatorMatches} instance.
50+
* @deprecated as of Spring Framework 5.3.26 in favor of invoking
51+
* {@link #OperatorMatches(ConcurrentMap, int, int, SpelNodeImpl...)}
52+
* with a shared pattern cache instead
53+
*/
54+
@Deprecated(since = "5.3.26")
55+
public OperatorMatches(int startPos, int endPos, SpelNodeImpl... operands) {
56+
this(new ConcurrentHashMap<>(), startPos, endPos, operands);
5057
}
5158

59+
/**
60+
* Create a new {@link OperatorMatches} instance with a shared pattern cache.
61+
* @since 5.3.26
62+
*/
63+
public OperatorMatches(ConcurrentMap<String, Pattern> patternCache, int startPos, int endPos, SpelNodeImpl... operands) {
64+
super("matches", startPos, endPos, operands);
65+
this.patternCache = patternCache;
66+
}
5267

5368
/**
5469
* Check the first operand matches the regex specified as the second operand.
@@ -67,15 +82,14 @@ public BooleanTypedValue getValueInternal(ExpressionState state) throws Evaluati
6782

6883
if (left == null) {
6984
throw new SpelEvaluationException(leftOp.getStartPosition(),
70-
SpelMessage.INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR, left);
85+
SpelMessage.INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR, (Object) null);
7186
}
72-
if (!(right instanceof String)) {
87+
if (!(right instanceof String rightString)) {
7388
throw new SpelEvaluationException(rightOp.getStartPosition(),
7489
SpelMessage.INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR, right);
7590
}
7691

7792
try {
78-
String rightString = (String) right;
7993
Pattern pattern = this.patternCache.get(rightString);
8094
if (pattern == null) {
8195
pattern = Pattern.compile(rightString);
@@ -111,22 +125,25 @@ private static class MatcherInput implements CharSequence {
111125

112126
private final CharSequence value;
113127

114-
private AccessCount access;
128+
private final AccessCount access;
115129

116130
public MatcherInput(CharSequence value, AccessCount access) {
117131
this.value = value;
118132
this.access = access;
119133
}
120134

135+
@Override
121136
public char charAt(int index) {
122137
this.access.check();
123138
return this.value.charAt(index);
124139
}
125140

141+
@Override
126142
public CharSequence subSequence(int start, int end) {
127143
return new MatcherInput(this.value.subSequence(start, end), this.access);
128144
}
129145

146+
@Override
130147
public int length() {
131148
return this.value.length();
132149
}

0 commit comments

Comments
 (0)