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
new file mode 100644
index 00000000000..013d3ce02c7
--- /dev/null
+++ b/org.springframework.core/src/main/java/org/springframework/util/AntPatchStringMatcher.java
@@ -0,0 +1,212 @@
+package org.springframework.util;
+
+import java.util.Map;
+
+/**
+ * Package-protected helper class for {@link AntPathMatcher}. Tests whether or not a string matches against a pattern.
+ * The pattern may contain special characters:
'*' means zero or more characters
'?' means one and only one
+ * character, '{' and '}' indicate a uri template pattern
+ *
+ * @author Arjen Poutsma
+ * @since 3.0
+ */
+class AntPatchStringMatcher {
+
+ private final char[] patArr;
+
+ private final char[] strArr;
+
+ private int patIdxStart = 0;
+
+ private int patIdxEnd;
+
+ private int strIdxStart = 0;
+
+ private int strIdxEnd;
+
+ private char ch;
+
+ private final Map uriTemplateVariables;
+
+ /**
+ * Constructs a new instance of the AntPatchStringMatcher.
+ * @param pattern
+ * @param str
+ * @param uriTemplateVariables
+ */
+ AntPatchStringMatcher(String pattern, String str, Map uriTemplateVariables) {
+ patArr = pattern.toCharArray();
+ strArr = str.toCharArray();
+ this.uriTemplateVariables = uriTemplateVariables;
+ patIdxEnd = patArr.length - 1;
+ strIdxEnd = strArr.length - 1;
+ }
+
+ private void addTemplateVariable(String varName, String varValue) {
+ if (uriTemplateVariables != null) {
+ uriTemplateVariables.put(varName, varValue);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ 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 = findNextStar();
+ if (patIdxTmp == patIdxStart + 1 && patArr[patIdxTmp] == '*') {
+ // Two stars next to each other, skip the first one.
+ patIdxStart++;
+ 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 int findNextStar() {
+ for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
+ if (patArr[i] == '*') {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private boolean onlyStarsLeft() {
+ for (int i = patIdxStart; i <= patIdxEnd; i++) {
+ if (patArr[i] != '*') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean allCharsUsed() {
+ return strIdxStart > strIdxEnd;
+ }
+
+ private boolean shortcutPossible() {
+ for (char ch : patArr) {
+ if (ch == '*' || ch == '{' || ch == '}') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ 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;
+ }
+ }
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+
+ 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/main/java/org/springframework/util/AntPathMatcher.java b/org.springframework.core/src/main/java/org/springframework/util/AntPathMatcher.java
index b98a44557fa..01994949d5d 100644
--- a/org.springframework.core/src/main/java/org/springframework/util/AntPathMatcher.java
+++ b/org.springframework.core/src/main/java/org/springframework/util/AntPathMatcher.java
@@ -18,8 +18,6 @@ package org.springframework.util;
import java.util.LinkedHashMap;
import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* PathMatcher implementation for Ant-style path patterns.
@@ -61,10 +59,6 @@ public class AntPathMatcher implements PathMatcher {
/** Default path separator: "/" */
public static final String DEFAULT_PATH_SEPARATOR = "/";
- /** Captures URI template variable names. */
- private static final Pattern URI_TEMPLATE_NAMES_PATTERN = Pattern.compile("\\{([\\w-~_\\.]+?)\\}");
-
-
private String pathSeparator = DEFAULT_PATH_SEPARATOR;
@@ -82,11 +76,11 @@ public class AntPathMatcher implements PathMatcher {
}
public boolean match(String pattern, String path) {
- return doMatch(pattern, path, true);
+ return doMatch(pattern, path, true, null);
}
public boolean matchStart(String pattern, String path) {
- return doMatch(pattern, path, false);
+ return doMatch(pattern, path, false, null);
}
@@ -99,8 +93,7 @@ public class AntPathMatcher implements PathMatcher {
* @return true if the supplied path matched,
* false if it didn't
*/
- protected boolean doMatch(String pattern, String path, boolean fullMatch) {
- pattern = uriTemplateToAntPattern(pattern);
+ protected boolean doMatch(String pattern, String path, boolean fullMatch, Map uriTemplateVariables) {
if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
return false;
}
@@ -119,7 +112,7 @@ public class AntPathMatcher implements PathMatcher {
if ("**".equals(patDir)) {
break;
}
- if (!matchStrings(patDir, pathDirs[pathIdxStart])) {
+ if (!matchStrings(patDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
return false;
}
pattIdxStart++;
@@ -160,7 +153,7 @@ public class AntPathMatcher implements PathMatcher {
if (patDir.equals("**")) {
break;
}
- if (!matchStrings(patDir, pathDirs[pathIdxEnd])) {
+ if (!matchStrings(patDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
return false;
}
pattIdxEnd--;
@@ -198,9 +191,9 @@ public class AntPathMatcher implements PathMatcher {
strLoop:
for (int i = 0; i <= strLength - patLength; i++) {
for (int j = 0; j < patLength; j++) {
- String subPat = (String) pattDirs[pattIdxStart + j + 1];
- String subStr = (String) pathDirs[pathIdxStart + i + j];
- if (!matchStrings(subPat, subStr)) {
+ String subPat = pattDirs[pattIdxStart + j + 1];
+ String subStr = pathDirs[pathIdxStart + i + j];
+ if (!matchStrings(subPat, subStr, uriTemplateVariables)) {
continue strLoop;
}
}
@@ -237,138 +230,9 @@ public class AntPathMatcher implements PathMatcher {
* @return true if the string matches against the
* pattern, or false otherwise.
*/
- private boolean matchStrings(String pattern, String str) {
- char[] patArr = pattern.toCharArray();
- char[] strArr = str.toCharArray();
- int patIdxStart = 0;
- int patIdxEnd = patArr.length - 1;
- int strIdxStart = 0;
- int strIdxEnd = strArr.length - 1;
- char ch;
-
- boolean containsStar = false;
- for (int i = 0; i < patArr.length; i++) {
- if (patArr[i] == '*') {
- containsStar = true;
- break;
- }
- }
-
- if (!containsStar) {
- // No '*'s, so we make a shortcut
- 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
- }
-
-
- if (patIdxEnd == 0) {
- return true; // Pattern contains only '*', which matches anything
- }
-
- // Process characters before first star
- while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
- if (ch != '?') {
- if (ch != strArr[strIdxStart]) {
- return false;// Character mismatch
- }
- }
- patIdxStart++;
- strIdxStart++;
- }
- if (strIdxStart > strIdxEnd) {
- // All characters in the string are used. Check if only '*'s are
- // left in the pattern. If so, we succeeded. Otherwise failure.
- for (int i = patIdxStart; i <= patIdxEnd; i++) {
- if (patArr[i] != '*') {
- return false;
- }
- }
- return true;
- }
-
- // Process characters after last star
- while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
- if (ch != '?') {
- if (ch != strArr[strIdxEnd]) {
- return false;// Character mismatch
- }
- }
- patIdxEnd--;
- strIdxEnd--;
- }
- if (strIdxStart > strIdxEnd) {
- // All characters in the string are used. Check if only '*'s are
- // left in the pattern. If so, we succeeded. Otherwise failure.
- for (int i = patIdxStart; i <= patIdxEnd; i++) {
- if (patArr[i] != '*') {
- return false;
- }
- }
- return true;
- }
-
- // process pattern between stars. padIdxStart and patIdxEnd point
- // always to a '*'.
- while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
- int patIdxTmp = -1;
- for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
- if (patArr[i] == '*') {
- patIdxTmp = i;
- break;
- }
- }
- if (patIdxTmp == patIdxStart + 1) {
- // Two stars next to each other, skip the first one.
- patIdxStart++;
- 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;
- }
-
- // All characters in the string are used. Check if only '*'s are left
- // in the pattern. If so, we succeeded. Otherwise failure.
- for (int i = patIdxStart; i <= patIdxEnd; i++) {
- if (patArr[i] != '*') {
- return false;
- }
- }
-
- return true;
+ private boolean matchStrings(String pattern, String str, Map uriTemplateVariables) {
+ AntPatchStringMatcher matcher = new AntPatchStringMatcher(pattern, str, uriTemplateVariables);
+ return matcher.matchStrings();
}
/**
@@ -417,42 +281,10 @@ public class AntPathMatcher implements PathMatcher {
return builder.toString();
}
- /**
- * Replaces URI template variables with Ant-style pattern patchs. Looks for variables within curly braces, and replaces
- * those with *.
- *
- * For example: /hotels/{hotel}/bookings becomes
- * /hotels/*/bookings
- *
- * @param pattern the pattern, possibly containing URI template variables
- * @return the Ant-stlye pattern path
- * @see org.springframework.util.AntPathMatcher
- */
- private static String uriTemplateToAntPattern(String pattern) {
- Matcher matcher = URI_TEMPLATE_NAMES_PATTERN.matcher(pattern);
- return matcher.replaceAll("*");
- }
-
-
public Map extractUriTemplateVariables(String pattern, String path) {
- if (pattern.contains("**") && pattern.contains("{")) {
- throw new IllegalArgumentException("Combining '**' and URI templates is not allowed");
- }
- String[] patternParts = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
- String[] pathParts = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
-
Map variables = new LinkedHashMap();
-
- for (int i = 0; i < patternParts.length && i < pathParts.length; i++) {
- String patternPart = patternParts[i];
- String pathPart = pathParts[i];
- int patternEnd = patternPart.length() -1 ;
- if (patternEnd > 1 && patternPart.charAt(0) == '{' && patternPart.charAt(patternEnd) == '}') {
- String varName = patternPart.substring(1, patternEnd);
- variables.put(varName, pathPart);
- }
- }
-
+ boolean result = doMatch(pattern, path, true, variables);
+ Assert.state(result, "Pattern \"" + pattern + "\" is not a match for \"" + path + "\"");
return variables;
}
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 c855c66e1eb..3fe76a28d3b 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
@@ -303,6 +303,9 @@ public class AntPathMatcherTests {
expected.put("hotel", "1");
expected.put("booking", "2");
assertEquals(expected, result);
+
+ result = pathMatcher.extractUriTemplateVariables("/**/hotels/**/{hotel}", "/foo/hotels/bar/1");
+ assertEquals(Collections.singletonMap("hotel", "1"), result);
}