All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.tom.mvc.ext.AntPathMatcher Maven / Gradle / Ivy

There is a newer version: 1.0.3
Show newest version
 package cn.tom.mvc.ext;



import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import cn.tom.kit.StringUtil;


 /**
  * Ant-style的路径匹配工具类
  *
  */
 public class AntPathMatcher implements PathMatcher {

     private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?\\}");

     /** Default path separator: "/" */
     public static final String DEFAULT_PATH_SEPARATOR = "/";

     private String pathSeparator = DEFAULT_PATH_SEPARATOR;


     /** Set the path separator to use for pattern parsing. Default is "/", as in Ant. */
     public void setPathSeparator(String pathSeparator) {
         this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR);
     }


     @Override
	public boolean isPattern(String path) {
         return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
     }

    /**
     * pattern *|?|**
     * [**] 表示任意字符,包括分隔符(.|,|/等)
     * [*] 表示由分隔符起作用的任意字符
     * [?] 表示 单个占位字符
     * 大小写敏感
     */
     @Override
	public boolean match(String pattern, String path) {
         return doMatch(pattern, path, true, null);
     }

     @Override
	public boolean matchStart(String pattern, String path) {
         return doMatch(pattern, path, false, null);
     }


     /**
      * Actually match the given path against the given pattern.
      * @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 true if the supplied path matched, false if it didn't
      */
     public boolean doMatch(String pattern, String path, boolean fullMatch,
             Map uriTemplateVariables) {

         if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
             return false;
         }

         String[] pattDirs = StringUtil.tokenizeToStringArray(pattern, this.pathSeparator);
         String[] pathDirs = StringUtil.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) {
                 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;
         }

         // 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) {
             // String is exhausted
             for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
                 if (!pattDirs[i].equals("**")) {
                     return false;
                 }
             }
             return 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;
     }

     /**
      * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:
'*' * means zero or more characters
'?' means one and only one character * @param pattern pattern to match against. Must not be null. * @param str string which must be matched against the pattern. Must not be null. * @return true if the string matches against the pattern, or false otherwise. */ private boolean matchStrings(String pattern, String str, Map uriTemplateVariables) { AntPathStringMatcher matcher = new AntPathStringMatcher(pattern, str, uriTemplateVariables); return matcher.matchStrings(); } /**获取匹配pattern的值, demo如下 * Given a pattern and a full path, determine the pattern-mapped part.

For example:

    *
  • '/docs/cvs/commit.html' and '/docs/cvs/commit.html -> ''
  • *
  • '/docs/*' and '/docs/cvs/commit -> 'cvs/commit'
  • *
  • '/docs/cvs/*.html' and '/docs/cvs/commit.html -> 'commit.html'
  • *
  • '/docs/**' and '/docs/cvs/commit -> 'cvs/commit'
  • *
  • '/docs/**\/*.html' and '/docs/cvs/commit.html -> 'cvs/commit.html'
  • *
  • '/*.html' and '/docs/cvs/commit.html -> 'docs/cvs/commit.html'
  • *
  • '*.html' and '/docs/cvs/commit.html -> '/docs/cvs/commit.html'
  • *
  • '*' and '/docs/cvs/commit.html -> '/docs/cvs/commit.html'
*

Assumes that {@link #match} returns true for 'pattern' and 'path', but * does not enforce this. */ @Override public String extractPathWithinPattern(String pattern, String path) { String[] patternParts = StringUtil.tokenizeToStringArray(pattern, this.pathSeparator); String[] pathParts = StringUtil.tokenizeToStringArray(path, this.pathSeparator); StringBuilder builder = new StringBuilder(); // Add any path parts that have a wildcarded pattern part. int puts = 0; for (int i = 0; i < patternParts.length; i++) { String patternPart = patternParts[i]; if ((patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) && pathParts.length >= i + 1) { if (puts > 0 || (i == 0 && !pattern.startsWith(this.pathSeparator))) { builder.append(this.pathSeparator); } builder.append(pathParts[i]); puts++; } } // Append any trailing path parts. for (int i = patternParts.length; i < pathParts.length; i++) { if (puts > 0 || i > 0) { builder.append(this.pathSeparator); } builder.append(pathParts[i]); } return builder.toString(); } /** * 获取{xxx}匹配上的参数 * /{page}.html --> /42.html --> map.get(page) = 42 * /A-{B}-C --> /A-b-C --> map.get(B) = b * /{name}.{extension} --> /test.html --> map.get(name) = test && map.get(extension) = html * 支持正则 * [{symbolicName:[\\p{L}\\.]+}-sources-{version:[\\p{N}\\.]+}.jar] * [com.example-sources-1.0.0.jar] * map.get(symbolicName) = com.example * map.get(version) = 1.0.0 * */ @Override public Map extractUriTemplateVariables(String pattern, String path) { Map variables = new LinkedHashMap(); boolean result = doMatch(pattern, path, true, variables); if (!result) { throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\""); } return variables; } /**组合成一个新的pattern, 新的pattern 满足第一个pattern * Combines two patterns into a new pattern that is returned. *

This implementation simply concatenates the two patterns, unless the first pattern * contains a file extension match (such as {@code *.html}. In that case, the second pattern * should be included in the first, or an {@code IllegalArgumentException} is thrown. *

For example:

* * * * * * *
Pattern 1Pattern 2Result
/hotels{@code * null}/hotels
{@code null}/hotels/hotels
/hotels/bookings/hotels/bookings
/hotelsbookings/hotels/bookings
/hotels/*/bookings/hotels/bookings
/hotels/**/bookings/hotels/**/bookings
/hotels{hotel}/hotels/{hotel}
/hotels/*{hotel}/hotels/{hotel}
/hotels/**{hotel}/hotels/**/{hotel}
/*.html/hotels.html/hotels.html
/*.html/hotels/hotels.html
/*.html/*.txtIllegalArgumentException
* @param pattern1 the first pattern * @param pattern2 the second pattern * @return the combination of the two patterns * @throws IllegalArgumentException when the two patterns cannot be combined */ @Override public String combine(String pattern1, String pattern2) { if (!StringUtil.hasText(pattern1) && !StringUtil.hasText(pattern2)) { return ""; } else if (!StringUtil.hasText(pattern1)) { return pattern2; } else if (!StringUtil.hasText(pattern2)) { return pattern1; } else if (match(pattern1, pattern2)) { return pattern2; } else if (pattern1.endsWith("/*")) { if (pattern2.startsWith("/")) { // /hotels/* + /booking -> /hotels/booking return pattern1.substring(0, pattern1.length() - 1) + pattern2.substring(1); } else { // /hotels/* + booking -> /hotels/booking return pattern1.substring(0, pattern1.length() - 1) + pattern2; } } else if (pattern1.endsWith("/**")) { if (pattern2.startsWith("/")) { // /hotels/** + /booking -> /hotels/**/booking return pattern1 + pattern2; } else { // /hotels/** + booking -> /hotels/**/booking return pattern1 + "/" + pattern2; } } else { int dotPos1 = pattern1.indexOf('.'); if (dotPos1 == -1) { // simply concatenate the two patterns if (pattern1.endsWith("/") || pattern2.startsWith("/")) { return pattern1 + pattern2; } else { return pattern1 + "/" + pattern2; } } String fileName1 = pattern1.substring(0, dotPos1); String extension1 = pattern1.substring(dotPos1); String fileName2; String extension2; int dotPos2 = pattern2.indexOf('.'); if (dotPos2 != -1) { fileName2 = pattern2.substring(0, dotPos2); extension2 = pattern2.substring(dotPos2); } else { fileName2 = pattern2; extension2 = ""; } String fileName = fileName1.endsWith("*") ? fileName2 : fileName1; String extension = extension1.startsWith("*") ? extension2 : extension1; return fileName + extension; } } /** * Given a full path, returns a {@link java.util.Comparator} suitable for sorting patterns in order of explicitness. *

The returned Comparator will {@linkplain java.util.Collections#sort(java.util.List, * java.util.Comparator) sort} a list so that more specific patterns (without uri templates or wild cards) come before * generic patterns. So given a list with the following patterns:

  1. /hotels/new
  2. *
  3. /hotels/{hotel}
  4. /hotels/*
the returned comparator will sort this * list so that the order will be as indicated. *

The full path given as parameter is used to test for exact matches. So when the given path is {@code /hotels/2}, * the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}. * @param path the full path to use for comparison * @return a comparator capable of sorting patterns in order of explicitness */ @Override public Comparator getPatternComparator(String path) { return new AntPatternComparator(path); } private static class AntPatternComparator implements Comparator { private final String path; private AntPatternComparator(String path) { this.path = path; } @Override public int compare(String pattern1, String pattern2) { if (pattern1 == null && pattern2 == null) { return 0; } else if (pattern1 == null) { return 1; } else if (pattern2 == null) { return -1; } boolean pattern1EqualsPath = pattern1.equals(path); boolean pattern2EqualsPath = pattern2.equals(path); if (pattern1EqualsPath && pattern2EqualsPath) { return 0; } else if (pattern1EqualsPath) { return -1; } else if (pattern2EqualsPath) { return 1; } int wildCardCount1 = getWildCardCount(pattern1); int wildCardCount2 = getWildCardCount(pattern2); int bracketCount1 = StringUtil.countOccurrencesOf(pattern1, "{"); int bracketCount2 = StringUtil.countOccurrencesOf(pattern2, "{"); int totalCount1 = wildCardCount1 + bracketCount1; int totalCount2 = wildCardCount2 + bracketCount2; if (totalCount1 != totalCount2) { return totalCount1 - totalCount2; } int pattern1Length = getPatternLength(pattern1); int pattern2Length = getPatternLength(pattern2); if (pattern1Length != pattern2Length) { return pattern2Length - pattern1Length; } if (wildCardCount1 < wildCardCount2) { return -1; } else if (wildCardCount2 < wildCardCount1) { return 1; } if (bracketCount1 < bracketCount2) { return -1; } else if (bracketCount2 < bracketCount1) { return 1; } return 0; } private int getWildCardCount(String pattern) { if (pattern.endsWith(".*")) { pattern = pattern.substring(0, pattern.length() - 2); } return StringUtil.countOccurrencesOf(pattern, "*"); } /** * Returns the length of the given pattern, where template variables are considered to be 1 long. */ private int getPatternLength(String pattern) { Matcher m = VARIABLE_PATTERN.matcher(pattern); return m.replaceAll("#").length(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy