@ -50,29 +50,56 @@ import java.util.regex.Pattern;
* /
public class AntPathMatcher implements PathMatcher {
private static final int CACHE_TURNOFF_THRESHOLD = 65536 ;
private static final Pattern VARIABLE_PATTERN = Pattern . compile ( "\\{[^/]+?\\}" ) ;
/** Default path separator: "/" */
public static final String DEFAULT_PATH_SEPARATOR = "/" ;
private String pathSeparator = DEFAULT_PATH_SEPARATOR ;
private final Map < String , AntPathStringMatcher > stringMatcherCache =
new ConcurrentHashMap < String , AntPathStringMatcher > ( 256 ) ;
private String pathSeparator = DEFAULT_PATH_SEPARATOR ;
private boolean trimTokens = true ;
private volatile Boolean cachePatterns ;
final Map < String , AntPathStringMatcher > stringMatcherCache =
new ConcurrentHashMap < String , AntPathStringMatcher > ( 256 ) ;
/** Set the path separator to use for pattern parsing. Default is "/", as in Ant. */
/ * *
* 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 ) ;
}
/** Whether to trim tokenized paths and patterns. */
/ * *
* Specify whether to trim tokenized paths and patterns .
* Default is { @code true } .
* /
public void setTrimTokens ( boolean trimTokens ) {
this . trimTokens = trimTokens ;
}
/ * *
* Specify whether to cache parsed pattern metadata for patterns passed
* into this matcher ' s { @link # match } method . A value of { @code true }
* activates an unlimited pattern cache ; a value of { @code false } turns
* the pattern cache off completely .
* < p > Default is for the cache to be on , but with the variant to automatically
* turn it off when encountering too many patterns to cache at runtime
* ( the threshold is 65536 ) , assuming that arbitrary permutations of patterns
* are coming in , with little chance for encountering a reoccurring pattern .
* @see # getStringMatcher ( String )
* /
public void setCachePatterns ( boolean cachePatterns ) {
this . cachePatterns = cachePatterns ;
}
@Override
public boolean isPattern ( String path ) {
return ( path . indexOf ( '*' ) ! = - 1 | | path . indexOf ( '?' ) ! = - 1 ) ;
@ -88,7 +115,6 @@ public class AntPathMatcher implements PathMatcher {
return doMatch ( pattern , path , false , null ) ;
}
/ * *
* Actually match the given { @code path } against the given { @code pattern } .
* @param pattern the pattern to match against
@ -97,9 +123,7 @@ public class AntPathMatcher implements PathMatcher {
* as far as the given base path goes is sufficient )
* @return { @code true } if the supplied { @code path } matched , { @code false } if it didn ' t
* /
protected boolean doMatch ( String pattern , String path , boolean fullMatch ,
Map < String , String > uriTemplateVariables ) {
protected boolean doMatch ( String pattern , String path , boolean fullMatch , Map < String , String > uriTemplateVariables ) {
if ( path . startsWith ( this . pathSeparator ) ! = pattern . startsWith ( this . pathSeparator ) ) {
return false ;
}
@ -114,11 +138,11 @@ public class AntPathMatcher implements PathMatcher {
// Match all elements up to the first **
while ( pattIdxStart < = pattIdxEnd & & pathIdxStart < = pathIdxEnd ) {
String patDir = pattDirs [ pattIdxStart ] ;
if ( "**" . equals ( patDir ) ) {
String patt Dir = pattDirs [ pattIdxStart ] ;
if ( "**" . equals ( patt Dir ) ) {
break ;
}
if ( ! matchStrings ( patDir , pathDirs [ pathIdxStart ] , uriTemplateVariables ) ) {
if ( ! matchStrings ( patt Dir , pathDirs [ pathIdxStart ] , uriTemplateVariables ) ) {
return false ;
}
pattIdxStart + + ;
@ -155,11 +179,11 @@ public class AntPathMatcher implements PathMatcher {
// up to last '**'
while ( pattIdxStart < = pattIdxEnd & & pathIdxStart < = pathIdxEnd ) {
String patDir = pattDirs [ pattIdxEnd ] ;
if ( patDir . equals ( "**" ) ) {
String patt Dir = pattDirs [ pattIdxEnd ] ;
if ( patt Dir . equals ( "**" ) ) {
break ;
}
if ( ! matchStrings ( patDir , pathDirs [ pathIdxEnd ] , uriTemplateVariables ) ) {
if ( ! matchStrings ( patt Dir , pathDirs [ pathIdxEnd ] , uriTemplateVariables ) ) {
return false ;
}
pattIdxEnd - - ;
@ -225,20 +249,49 @@ public class AntPathMatcher implements PathMatcher {
}
/ * *
* Tests whether or not a string matches against a pattern . The pattern may contain two special characters :
* < br > '*' means zero or more characters
* < br > '?' means one and only one character
* @param pattern pattern to match against . Must not be { @code null } .
* @param str string which must be matched against the pattern . Must not be { @code null } .
* @return { @code true } if the string matches against the pattern , or { @code false } otherwise .
* Tests whether or not a string matches against a pattern .
* @param pattern the pattern to match against ( never { @code null } )
* @param str the String which must be matched against the pattern ( never { @code null } )
* @return { @code true } if the string matches against the pattern , or { @code false } otherwise
* /
private boolean matchStrings ( String pattern , String str , Map < String , String > uriTemplateVariables ) {
AntPathStringMatcher matcher = this . stringMatcherCache . get ( pattern ) ;
return getStringMatcher ( pattern ) . matchStrings ( str , uriTemplateVariables ) ;
}
/ * *
* Build or retrieve an { @link AntPathStringMatcher } for the given pattern .
* < p > The default implementation checks this AntPathMatcher ' s internal cache
* ( see { @link # setCachePatterns } , creating a new AntPathStringMatcher instance
* through { @link AntPathStringMatcher # AntPathStringMatcher ( String ) } if none found .
* When encountering too many patterns to cache at runtime ( the threshold is 65536 ) ,
* it turns the default cache off , assuming that arbitrary permutations of patterns
* are coming in , with little chance for encountering a reoccurring pattern .
* < p > This method may get overridden to implement a custom cache strategy .
* @param pattern the pattern to match against ( never { @code null } )
* @return a corresponding AntPathStringMatcher ( never { @code null } )
* @see # setCachePatterns
* /
protected AntPathStringMatcher getStringMatcher ( String pattern ) {
AntPathStringMatcher matcher = null ;
Boolean cachePatterns = this . cachePatterns ;
if ( cachePatterns = = null | | cachePatterns . booleanValue ( ) ) {
matcher = this . stringMatcherCache . get ( pattern ) ;
}
if ( matcher = = null ) {
matcher = new AntPathStringMatcher ( pattern ) ;
this . stringMatcherCache . put ( pattern , matcher ) ;
if ( cachePatterns = = null & & this . stringMatcherCache . size ( ) = = CACHE_TURNOFF_THRESHOLD ) {
// Try to adapt to the runtime situation that we're encountering:
// There are obviously too many different paths coming in here...
// So let's turn off the cache since the patterns are unlikely to be reoccurring.
this . cachePatterns = false ;
this . stringMatcherCache . clear ( ) ;
return matcher ;
}
if ( cachePatterns = = null | | cachePatterns . booleanValue ( ) ) {
this . stringMatcherCache . put ( pattern , matcher ) ;
}
}
return matcher . matchStrings ( str , uriTemplateVariables ) ;
return matcher ;
}
/ * *
@ -381,11 +434,99 @@ public class AntPathMatcher implements PathMatcher {
}
private static class AntPatternComparator implements Comparator < String > {
/ * *
* Tests whether or not a string matches against a pattern via a { @link Pattern } .
* < p > The pattern may contain special characters : '*' means zero or more characters ; '?' means one and
* only one character ; '{' and '}' indicate a URI template pattern . For example < tt > / users / { user } < / tt > .
* /
protected static class AntPathStringMatcher {
private static final Pattern GLOB_PATTERN = Pattern . compile ( "\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}" ) ;
private static final String DEFAULT_VARIABLE_PATTERN = "(.*)" ;
private final Pattern pattern ;
private final List < String > variableNames = new LinkedList < String > ( ) ;
public AntPathStringMatcher ( 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 ( '.' ) ;
}
else if ( "*" . equals ( match ) ) {
patternBuilder . append ( ".*" ) ;
}
else if ( match . startsWith ( "{" ) & & match . endsWith ( "}" ) ) {
int colonIdx = match . indexOf ( ':' ) ;
if ( colonIdx = = - 1 ) {
patternBuilder . append ( DEFAULT_VARIABLE_PATTERN ) ;
this . variableNames . add ( m . group ( 1 ) ) ;
}
else {
String variablePattern = match . substring ( colonIdx + 1 , match . length ( ) - 1 ) ;
patternBuilder . append ( '(' ) ;
patternBuilder . append ( variablePattern ) ;
patternBuilder . append ( ')' ) ;
String variableName = match . substring ( 1 , colonIdx ) ;
this . variableNames . add ( variableName ) ;
}
}
end = m . end ( ) ;
}
patternBuilder . append ( quote ( pattern , end , pattern . length ( ) ) ) ;
this . pattern = Pattern . compile ( patternBuilder . toString ( ) ) ;
}
private String quote ( String s , int start , int end ) {
if ( start = = end ) {
return "" ;
}
return Pattern . quote ( s . substring ( start , end ) ) ;
}
/ * *
* Main entry point .
* @return { @code true } if the string matches against the pattern , or { @code false } otherwise .
* /
public boolean matchStrings ( String str , Map < String , String > uriTemplateVariables ) {
Matcher matcher = this . pattern . matcher ( str ) ;
if ( matcher . matches ( ) ) {
if ( uriTemplateVariables ! = null ) {
// SPR-8455
Assert . isTrue ( this . variableNames . size ( ) = = matcher . groupCount ( ) ,
"The number of capturing groups in the pattern segment " + this . pattern +
" does not match the number of URI template variables it defines, which can occur if " +
" capturing groups are used in a URI template regex. Use non-capturing groups instead." ) ;
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 ;
}
else {
return false ;
}
}
}
/ * *
* The default { @link Comparator } implementation returned by
* { @link # getPatternComparator ( String ) } .
* /
protected static class AntPatternComparator implements Comparator < String > {
private final String path ;
private AntPatternComparator ( String path ) {
public AntPatternComparator ( String path ) {
this . path = path ;
}
@ -465,92 +606,7 @@ public class AntPathMatcher implements PathMatcher {
* 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 ( ) ;
}
}
/ * *
* Tests whether or not a string matches against a pattern via a { @link Pattern } .
* < p > The pattern may contain special characters : '*' means zero or more characters ; '?' means one and
* only one character ; '{' and '}' indicate a URI template pattern . For example < tt > / users / { user } < / tt > .
* /
private static class AntPathStringMatcher {
private static final Pattern GLOB_PATTERN = Pattern . compile ( "\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}" ) ;
private static final String DEFAULT_VARIABLE_PATTERN = "(.*)" ;
private final Pattern pattern ;
private final List < String > variableNames = new LinkedList < String > ( ) ;
public AntPathStringMatcher ( 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 ( '.' ) ;
}
else if ( "*" . equals ( match ) ) {
patternBuilder . append ( ".*" ) ;
}
else if ( match . startsWith ( "{" ) & & match . endsWith ( "}" ) ) {
int colonIdx = match . indexOf ( ':' ) ;
if ( colonIdx = = - 1 ) {
patternBuilder . append ( DEFAULT_VARIABLE_PATTERN ) ;
this . variableNames . add ( m . group ( 1 ) ) ;
}
else {
String variablePattern = match . substring ( colonIdx + 1 , match . length ( ) - 1 ) ;
patternBuilder . append ( '(' ) ;
patternBuilder . append ( variablePattern ) ;
patternBuilder . append ( ')' ) ;
String variableName = match . substring ( 1 , colonIdx ) ;
this . variableNames . add ( variableName ) ;
}
}
end = m . end ( ) ;
}
patternBuilder . append ( quote ( pattern , end , pattern . length ( ) ) ) ;
this . pattern = Pattern . compile ( patternBuilder . toString ( ) ) ;
}
private String quote ( String s , int start , int end ) {
if ( start = = end ) {
return "" ;
}
return Pattern . quote ( s . substring ( start , end ) ) ;
}
/ * *
* Main entry point .
* @return { @code true } if the string matches against the pattern , or { @code false } otherwise .
* /
public boolean matchStrings ( String str , Map < String , String > uriTemplateVariables ) {
Matcher matcher = this . pattern . matcher ( str ) ;
if ( matcher . matches ( ) ) {
if ( uriTemplateVariables ! = null ) {
// SPR-8455
Assert . isTrue ( this . variableNames . size ( ) = = matcher . groupCount ( ) ,
"The number of capturing groups in the pattern segment " + this . pattern +
" does not match the number of URI template variables it defines, which can occur if " +
" capturing groups are used in a URI template regex. Use non-capturing groups instead." ) ;
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 ;
}
else {
return false ;
}
return VARIABLE_PATTERN . matcher ( pattern ) . replaceAll ( "#" ) . length ( ) ;
}
}