Recently, looking at the Spring source code, the AntPatternMatcher class has appeared many times in the source code, which is used to match URL s.
About this kind of introduction, some of the code is borrowed from Apache Ant.
PathMatcher implementation for Ant-style path patterns. Examples are provided below.
Part of this mapping code has been kindly borrowed from Apache Ant.
The mapping matches URLs using the following rules:
- ? matches one character
- * matches zero or more characters
- ** matches zero or more 'directories' in a path
Some examples:
- com/t?st.jsp - matches com/test.jsp but also com/tast.jsp or com/txst.jsp
- com/*.jsp - matches all .jsp files in the com directory
- com/**/test.jsp - matches all test.jsp files underneath the com path
- org/springframework/**/*.jsp - matches all .jsp files underneath the org/springframework path
- org/**/servlet/bla.jsp - matches org/springframework/servlet/bla.jsp but also org/springframework/testing/servlet/bla.jsp and org/servlet/bla.jsp
/** * Actually match the given <code>path</code> against the given <code>pattern</code>. * @param pattern the pattern to match against * @param path the path String to test * @param fullMatch whether a full pattern match is required (else a pattern match * as far as the given base path goes is sufficient) * @return <code>true</code> if the supplied <code>path</code> matched, <code>false</code> if it didn't */ protected boolean doMatch(String pattern, String path, boolean fullMatch, Map<String, String> uriTemplateVariables) { if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { return false; } String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator); String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator); int pattIdxStart = 0; int pattIdxEnd = pattDirs.length - 1; int pathIdxStart = 0; int pathIdxEnd = pathDirs.length - 1; // Match all elements up to the first ** while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { String patDir = pattDirs[pattIdxStart]; if ("**".equals(patDir)) { break; } if (!matchStrings(patDir, pathDirs[pathIdxStart], uriTemplateVariables)) { return false; } pattIdxStart++; pathIdxStart++; } if (pathIdxStart > pathIdxEnd) { // Path is exhausted, only match if rest of pattern is * or **'s if (pattIdxStart > pattIdxEnd) {
/** /a/b/c and /a/b/c/ ==>false **/ return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) : !path.endsWith(this.pathSeparator)); } if (!fullMatch) { return true; }
/** /a/b/c/* and /a/b/c/ ==>true /a/b/c/* and /a/b/c ==> false **/
if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { return true; } /** /a/b/c/**/**/ and /a/b/c ==> true /a/b/c/**/a/ and /a/b/c ==>false **/ for (int i = pattIdxStart; i <= pattIdxEnd; i++) { if (!pattDirs[i].equals("**")) { return false; } } return true; } else if (pattIdxStart > pattIdxEnd) { // String not exhausted, but pattern is. Failure. return false; } else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) { // Path start definitely matches due to "**" part in pattern. return true; } // up to last '**' while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { String patDir = pattDirs[pattIdxEnd]; if (patDir.equals("**")) { break; } if (!matchStrings(patDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { return false; } pattIdxEnd--; pathIdxEnd--; } if (pathIdxStart > pathIdxEnd) { /** /**/a/b/c and /a/b/c ==> true **/ // String is exhausted for (int i = pattIdxStart; i <= pattIdxEnd; i++) { if (!pattDirs[i].equals("**")) { return false; } } return true; } /** /a/b/c/d/**/**/e/f/g/h/**/i/j/k/**/l/m/n/**/o/p/q and /a/b/c/d/a/b/e/f/g/h/a/b/i/j/k/a/b/l/m/n/a/b/o/p/q ==> true **/ while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { int patIdxTmp = -1; for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { if (pattDirs[i].equals("**")) { patIdxTmp = i; break; } } if (patIdxTmp == pattIdxStart + 1) { // '**/**' situation, so skip one pattIdxStart++; continue; } // Find the pattern between padIdxStart & padIdxTmp in str between // strIdxStart & strIdxEnd int patLength = (patIdxTmp - pattIdxStart - 1); int strLength = (pathIdxEnd - pathIdxStart + 1); int foundIdx = -1; strLoop: for (int i = 0; i <= strLength - patLength; i++) { for (int j = 0; j < patLength; j++) { String subPat = pattDirs[pattIdxStart + j + 1]; String subStr = pathDirs[pathIdxStart + i + j]; if (!matchStrings(subPat, subStr, uriTemplateVariables)) { continue strLoop; } } foundIdx = pathIdxStart + i; break; } if (foundIdx == -1) { return false; } pattIdxStart = patIdxTmp; pathIdxStart = foundIdx + patLength; } for (int i = pattIdxStart; i <= pattIdxEnd; i++) { if (!pattDirs[i].equals("**")) { return false; } } return true; }
String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator); String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator); int pattIdxStart = 0; int pattIdxEnd = pattDirs.length - 1; int pathIdxStart = 0; int pathIdxEnd = pathDirs.length - 1;
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { String patDir = pattDirs[pattIdxStart]; if ("**".equals(patDir)) { break; } if (!matchStrings(patDir, pathDirs[pathIdxStart], uriTemplateVariables)) { return false; } pattIdxStart++; pathIdxStart++; }
The conditions for jumping out of this loop are:
PattIdxStart > pattIdxEnd (No **)
Explain that Patterns overflow in the loop before the ** is found, that is, Patterns are shorter than Path's length, and the result must not match and should be returned to false directly.
else if (pattIdxStart > pattIdxEnd) { // String not exhausted, but pattern is. Failure. return false; }
// Path is exhausted, only match if rest of pattern is * or **'s if (pattIdxStart > pattIdxEnd) { return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) : !path.endsWith(this.pathSeparator)); } if (!fullMatch) { return true; } if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) { return true; } for (int i = pattIdxStart; i <= pattIdxEnd; i++) { if (!pattDirs[i].equals("**")) { return false; } } return true; } else if (pattIdxStart > pattIdxEnd) { // String not exhausted, but pattern is. Failure. return false; } else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) { // Path start definitely matches due to "**" part in pattern. return true; }
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { String patDir = pattDirs[pattIdxEnd]; if (patDir.equals("**")) { break; } if (!matchStrings(patDir, pathDirs[pathIdxEnd], uriTemplateVariables)) { return false; } pattIdxEnd--; pathIdxEnd--; } if (pathIdxStart > pathIdxEnd) { // String is exhausted for (int i = pattIdxStart; i <= pattIdxEnd; i++) { if (!pattDirs[i].equals("**")) { return false; } } return true; }
In the second case, neither Pattern s nor Path s overflow out of the loop in advance, this is the case.
/a/b/c/d/**/**/e/f/g/h/**/i/j/k/**/l/m/n/**/o/p/q and /a/b/c/d/a/b/e/f/g/h/a/b/i/j/k/a/b/l/m/n/a/b/o/p/q ==> true
At this time, we need to deal with multiple ** numbers, segment processing, character matching between ** numbers, and return false if the characters between ** numbers do not match.
Paragraph 1:
/a/b/c/d/**/**/e/f/g/h/**/
and
a/b/e/f/g/h/a/b/i/j/k/a/b/l/m/n/a/b/o/p/q
==>true
The second paragraph:
/**/i/j/k/**/l
and
/a/b/i/j/k/a/b/l/m/n/a/b/o/p/q
==>true
The third paragraph:
/**/l/m/n/**/
/a/b/l/m/n/a/b/o/p/q
==>true
while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { int patIdxTmp = -1; for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) { if (pattDirs[i].equals("**")) { patIdxTmp = i; break; } } if (patIdxTmp == pattIdxStart + 1) { // '**/**' situation, so skip one pattIdxStart++; continue; } // Find the pattern between padIdxStart & padIdxTmp in str between // strIdxStart & strIdxEnd int patLength = (patIdxTmp - pattIdxStart - 1); int strLength = (pathIdxEnd - pathIdxStart + 1); int foundIdx = -1; strLoop: for (int i = 0; i <= strLength - patLength; i++) { for (int j = 0; j < patLength; j++) { String subPat = pattDirs[pattIdxStart + j + 1]; String subStr = pathDirs[pathIdxStart + i + j]; if (!matchStrings(subPat, subStr, uriTemplateVariables)) { continue strLoop; } } foundIdx = pathIdxStart + i; break; } if (foundIdx == -1) { return false; } pattIdxStart = patIdxTmp; pathIdxStart = foundIdx + patLength; }
Reprinted in: https://my.oschina.net/iqoFil/blog/221620