diff --git a/org.springframework.core/src/main/java/org/springframework/util/AntPatchStringMatcher.java b/org.springframework.core/src/main/java/org/springframework/util/AntPatchStringMatcher.java index 2bee2e4e4f8..fa84d3e36e0 100644 --- a/org.springframework.core/src/main/java/org/springframework/util/AntPatchStringMatcher.java +++ b/org.springframework.core/src/main/java/org/springframework/util/AntPatchStringMatcher.java @@ -16,11 +16,15 @@ package org.springframework.util; +import java.util.LinkedList; +import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Package-protected helper class for {@link AntPathMatcher}. - * Tests whether or not a string matches against a pattern. + * Tests whether or not a string matches against a pattern using a regular expression. * *
The pattern may contain special characters: '*' means zero or more characters;
* '?' means one and only one character; '{' and '}' indicate a URI template pattern.
@@ -30,189 +34,66 @@ import java.util.Map;
*/
class AntPatchStringMatcher {
- private final char[] patArr;
+ private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{([^/]+?)\\}");
- private final char[] strArr;
+ private final Pattern pattern;
- private int patIdxStart = 0;
+ private String str;
- private int patIdxEnd;
-
- private int strIdxStart = 0;
-
- private int strIdxEnd;
-
- private char ch;
+ private final ListAntPatchStringMatcher.
- */
- public AntPatchStringMatcher(String pattern, String str, MapAntPatchStringMatcher. */
+ AntPatchStringMatcher(String pattern, String str, Maptrue if the string matches against the pattern, or false otherwise.
- */
- public boolean matchStrings() {
- if (shortcutPossible()) {
- return doShortcut();
- }
- if (patternContainsOnlyStar()) {
- return true;
- }
- if (patternContainsOneTemplateVariable()) {
- addTemplateVariable(0, patIdxEnd, 0, strIdxEnd);
- return true;
- }
- if (!matchBeforeFirstStarOrCurly()) {
- return false;
- }
- if (allCharsUsed()) {
- return onlyStarsLeft();
- }
- if (!matchAfterLastStarOrCurly()) {
- return false;
- }
- if (allCharsUsed()) {
- return onlyStarsLeft();
- }
- // process pattern between stars. padIdxStart and patIdxEnd point
- // always to a '*'.
- while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
- int patIdxTmp;
- if (patArr[patIdxStart] == '{') {
- patIdxTmp = findClosingCurly();
- addTemplateVariable(patIdxStart, patIdxTmp, strIdxStart, strIdxEnd);
- patIdxStart = patIdxTmp + 1;
- strIdxStart = strIdxEnd + 1;
- continue;
- }
- patIdxTmp = findNextStarOrCurly();
- if (consecutiveStars(patIdxTmp)) {
- continue;
- }
- // Find the pattern between padIdxStart & padIdxTmp in str between
- // strIdxStart & strIdxEnd
- int patLength = (patIdxTmp - patIdxStart - 1);
- int strLength = (strIdxEnd - strIdxStart + 1);
- int foundIdx = -1;
- strLoop:
- for (int i = 0; i <= strLength - patLength; i++) {
- for (int j = 0; j < patLength; j++) {
- ch = patArr[patIdxStart + j + 1];
- if (ch != '?') {
- if (ch != strArr[strIdxStart + i + j]) {
- continue strLoop;
- }
- }
- }
-
- foundIdx = strIdxStart + i;
- break;
- }
-
- if (foundIdx == -1) {
- return false;
- }
-
- patIdxStart = patIdxTmp;
- strIdxStart = foundIdx + patLength;
- }
-
- return onlyStarsLeft();
- }
-
- private void addTemplateVariable(int curlyIdxStart, int curlyIdxEnd, int valIdxStart, int valIdxEnd) {
- if (uriTemplateVariables != null) {
- String varName = new String(patArr, curlyIdxStart + 1, curlyIdxEnd - curlyIdxStart - 1);
- String varValue = new String(strArr, valIdxStart, valIdxEnd - valIdxStart + 1);
- uriTemplateVariables.put(varName, varValue);
- }
- }
-
- private boolean consecutiveStars(int patIdxTmp) {
- if (patIdxTmp == patIdxStart + 1 && patArr[patIdxStart] == '*' && patArr[patIdxTmp] == '*') {
- // Two stars next to each other, skip the first one.
- patIdxStart++;
- return true;
- }
- return false;
- }
-
- private int findNextStarOrCurly() {
- for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
- if (patArr[i] == '*' || patArr[i] == '{') {
- return i;
+ private Pattern createPattern(String pattern) {
+ StringBuilder patternBuilder = new StringBuilder();
+ Matcher m = GLOB_PATTERN.matcher(pattern);
+ int end = 0;
+ while (m.find()) {
+ patternBuilder.append(quote(pattern, end, m.start()));
+ String match = m.group();
+ if ("?".equals(match)) {
+ patternBuilder.append('.');
}
- }
- return -1;
- }
-
- private int findClosingCurly() {
- for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
- if (patArr[i] == '}') {
- return i;
+ else if ("*".equals(match)) {
+ patternBuilder.append(".*");
}
- }
- return -1;
- }
-
- private boolean onlyStarsLeft() {
- for (int i = patIdxStart; i <= patIdxEnd; i++) {
- if (patArr[i] != '*') {
- return false;
+ else if (match.startsWith("{") && match.endsWith("}")) {
+ patternBuilder.append("(.*)");
+ variableNames.add(m.group(1));
}
+ end = m.end();
}
- return true;
- }
-
- private boolean allCharsUsed() {
- return strIdxStart > strIdxEnd;
+ patternBuilder.append(quote(pattern, end, pattern.length()));
+ return Pattern.compile(patternBuilder.toString());
}
- private boolean shortcutPossible() {
- for (char ch : patArr) {
- if (ch == '*' || ch == '{' || ch == '}') {
- return false;
- }
+ private String quote(String s, int start, int end) {
+ if (start == end) {
+ return "";
}
- return true;
+ return Pattern.quote(s.substring(start, end));
}
- private boolean doShortcut() {
- if (patIdxEnd != strIdxEnd) {
- return false; // Pattern and string do not have the same size
- }
- for (int i = 0; i <= patIdxEnd; i++) {
- ch = patArr[i];
- if (ch != '?') {
- if (ch != strArr[i]) {
- return false;// Character mismatch
- }
- }
- }
- return true; // String matches against pattern
- }
-
- private boolean patternContainsOnlyStar() {
- return (patIdxEnd == 0 && patArr[0] == '*');
- }
-
- private boolean patternContainsOneTemplateVariable() {
- if ((patIdxEnd >= 2 && patArr[0] == '{' && patArr[patIdxEnd] == '}')) {
- for (int i = 1; i < patIdxEnd; i++) {
- if (patArr[i] == '}') {
- return false;
+ /**
+ * Main entry point.
+ *
+ * @return true if the string matches against the pattern, or false otherwise.
+ */
+ public boolean matchStrings() {
+ Matcher matcher = pattern.matcher(str);
+ if (matcher.matches()) {
+ if (uriTemplateVariables != null) {
+ for (int i = 1; i <= matcher.groupCount(); i++) {
+ String name = this.variableNames.get(i - 1);
+ String value = matcher.group(i);
+ uriTemplateVariables.put(name, value);
}
}
return true;
@@ -222,30 +103,4 @@ class AntPatchStringMatcher {
}
}
- private boolean matchBeforeFirstStarOrCurly() {
- while ((ch = patArr[patIdxStart]) != '*' && ch != '{' && strIdxStart <= strIdxEnd) {
- if (ch != '?') {
- if (ch != strArr[strIdxStart]) {
- return false;
- }
- }
- patIdxStart++;
- strIdxStart++;
- }
- return true;
- }
-
- private boolean matchAfterLastStarOrCurly() {
- while ((ch = patArr[patIdxEnd]) != '*' && ch != '}' && strIdxStart <= strIdxEnd) {
- if (ch != '?') {
- if (ch != strArr[strIdxEnd]) {
- return false;
- }
- }
- patIdxEnd--;
- strIdxEnd--;
- }
- return true;
- }
-
}
diff --git a/org.springframework.core/src/test/java/org/springframework/util/AntPathMatcherTests.java b/org.springframework.core/src/test/java/org/springframework/util/AntPathMatcherTests.java
index aec70062d71..1491299ec65 100644
--- a/org.springframework.core/src/test/java/org/springframework/util/AntPathMatcherTests.java
+++ b/org.springframework.core/src/test/java/org/springframework/util/AntPathMatcherTests.java
@@ -43,7 +43,7 @@ public class AntPathMatcherTests {
}
@Test
- public void standard() {
+ public void match() {
// test exact matching
assertTrue(pathMatcher.match("test", "test"));
assertTrue(pathMatcher.match("/test", "/test"));
@@ -123,6 +123,8 @@ public class AntPathMatcherTests {
assertFalse(pathMatcher.match("/x/x/**/bla", "/x/x/x/"));
assertTrue(pathMatcher.match("", ""));
+
+ assertTrue(pathMatcher.match("/{bla}.*", "/testing.html"));
}
@Test
@@ -319,8 +321,17 @@ public class AntPathMatcherTests {
result = pathMatcher.extractUriTemplateVariables("/{page}.html", "/42.html");
assertEquals(Collections.singletonMap("page", "42"), result);
+ result = pathMatcher.extractUriTemplateVariables("/{page}.*", "/42.html");
+ assertEquals(Collections.singletonMap("page", "42"), result);
+
result = pathMatcher.extractUriTemplateVariables("/A-{B}-C", "/A-b-C");
assertEquals(Collections.singletonMap("B", "b"), result);
+
+ result = pathMatcher.extractUriTemplateVariables("/{name}.{extension}", "/test.html");
+ expected = new LinkedHashMap