50
50
*/
51
51
public class AntPathMatcher implements PathMatcher {
52
52
53
+ private static final int CACHE_TURNOFF_THRESHOLD = 65536 ;
54
+
53
55
private static final Pattern VARIABLE_PATTERN = Pattern .compile ("\\ {[^/]+?\\ }" );
54
56
55
57
/** Default path separator: "/" */
56
58
public static final String DEFAULT_PATH_SEPARATOR = "/" ;
57
59
58
- private String pathSeparator = DEFAULT_PATH_SEPARATOR ;
59
60
60
- private final Map <String , AntPathStringMatcher > stringMatcherCache =
61
- new ConcurrentHashMap <String , AntPathStringMatcher >(256 );
61
+ private String pathSeparator = DEFAULT_PATH_SEPARATOR ;
62
62
63
63
private boolean trimTokens = true ;
64
64
65
+ private volatile Boolean cachePatterns ;
66
+
67
+ final Map <String , AntPathStringMatcher > stringMatcherCache =
68
+ new ConcurrentHashMap <String , AntPathStringMatcher >(256 );
69
+
65
70
66
- /** Set the path separator to use for pattern parsing. Default is "/", as in Ant. */
71
+ /**
72
+ * Set the path separator to use for pattern parsing.
73
+ * Default is "/", as in Ant.
74
+ */
67
75
public void setPathSeparator (String pathSeparator ) {
68
76
this .pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR );
69
77
}
70
78
71
- /** Whether to trim tokenized paths and patterns. */
79
+ /**
80
+ * Specify whether to trim tokenized paths and patterns.
81
+ * Default is {@code true}.
82
+ */
72
83
public void setTrimTokens (boolean trimTokens ) {
73
84
this .trimTokens = trimTokens ;
74
85
}
75
86
87
+ /**
88
+ * Specify whether to cache parsed pattern metadata for patterns passed
89
+ * into this matcher's {@link #match} method. A value of {@code true}
90
+ * activates an unlimited pattern cache; a value of {@code false} turns
91
+ * the pattern cache off completely.
92
+ * <p>Default is for the cache to be on, but with the variant to automatically
93
+ * turn it off when encountering too many patterns to cache at runtime
94
+ * (the threshold is 65536), assuming that arbitrary permutations of patterns
95
+ * are coming in, with little chance for encountering a reoccurring pattern.
96
+ * @see #getStringMatcher(String)
97
+ */
98
+ public void setCachePatterns (boolean cachePatterns ) {
99
+ this .cachePatterns = cachePatterns ;
100
+ }
101
+
102
+
76
103
@ Override
77
104
public boolean isPattern (String path ) {
78
105
return (path .indexOf ('*' ) != -1 || path .indexOf ('?' ) != -1 );
@@ -88,7 +115,6 @@ public boolean matchStart(String pattern, String path) {
88
115
return doMatch (pattern , path , false , null );
89
116
}
90
117
91
-
92
118
/**
93
119
* Actually match the given {@code path} against the given {@code pattern}.
94
120
* @param pattern the pattern to match against
@@ -97,9 +123,7 @@ public boolean matchStart(String pattern, String path) {
97
123
* as far as the given base path goes is sufficient)
98
124
* @return {@code true} if the supplied {@code path} matched, {@code false} if it didn't
99
125
*/
100
- protected boolean doMatch (String pattern , String path , boolean fullMatch ,
101
- Map <String , String > uriTemplateVariables ) {
102
-
126
+ protected boolean doMatch (String pattern , String path , boolean fullMatch , Map <String , String > uriTemplateVariables ) {
103
127
if (path .startsWith (this .pathSeparator ) != pattern .startsWith (this .pathSeparator )) {
104
128
return false ;
105
129
}
@@ -114,11 +138,11 @@ protected boolean doMatch(String pattern, String path, boolean fullMatch,
114
138
115
139
// Match all elements up to the first **
116
140
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd ) {
117
- String patDir = pattDirs [pattIdxStart ];
118
- if ("**" .equals (patDir )) {
141
+ String pattDir = pattDirs [pattIdxStart ];
142
+ if ("**" .equals (pattDir )) {
119
143
break ;
120
144
}
121
- if (!matchStrings (patDir , pathDirs [pathIdxStart ], uriTemplateVariables )) {
145
+ if (!matchStrings (pattDir , pathDirs [pathIdxStart ], uriTemplateVariables )) {
122
146
return false ;
123
147
}
124
148
pattIdxStart ++;
@@ -155,11 +179,11 @@ else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
155
179
156
180
// up to last '**'
157
181
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd ) {
158
- String patDir = pattDirs [pattIdxEnd ];
159
- if (patDir .equals ("**" )) {
182
+ String pattDir = pattDirs [pattIdxEnd ];
183
+ if (pattDir .equals ("**" )) {
160
184
break ;
161
185
}
162
- if (!matchStrings (patDir , pathDirs [pathIdxEnd ], uriTemplateVariables )) {
186
+ if (!matchStrings (pattDir , pathDirs [pathIdxEnd ], uriTemplateVariables )) {
163
187
return false ;
164
188
}
165
189
pattIdxEnd --;
@@ -225,20 +249,49 @@ else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
225
249
}
226
250
227
251
/**
228
- * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:
229
- * <br>'*' means zero or more characters
230
- * <br>'?' means one and only one character
231
- * @param pattern pattern to match against. Must not be {@code null}.
232
- * @param str string which must be matched against the pattern. Must not be {@code null}.
233
- * @return {@code true} if the string matches against the pattern, or {@code false} otherwise.
252
+ * Tests whether or not a string matches against a pattern.
253
+ * @param pattern the pattern to match against (never {@code null})
254
+ * @param str the String which must be matched against the pattern (never {@code null})
255
+ * @return {@code true} if the string matches against the pattern, or {@code false} otherwise
234
256
*/
235
257
private boolean matchStrings (String pattern , String str , Map <String , String > uriTemplateVariables ) {
236
- AntPathStringMatcher matcher = this .stringMatcherCache .get (pattern );
258
+ return getStringMatcher (pattern ).matchStrings (str , uriTemplateVariables );
259
+ }
260
+
261
+ /**
262
+ * Build or retrieve an {@link AntPathStringMatcher} for the given pattern.
263
+ * <p>The default implementation checks this AntPathMatcher's internal cache
264
+ * (see {@link #setCachePatterns}, creating a new AntPathStringMatcher instance
265
+ * through {@link AntPathStringMatcher#AntPathStringMatcher(String)} if none found.
266
+ * When encountering too many patterns to cache at runtime (the threshold is 65536),
267
+ * it turns the default cache off, assuming that arbitrary permutations of patterns
268
+ * are coming in, with little chance for encountering a reoccurring pattern.
269
+ * <p>This method may get overridden to implement a custom cache strategy.
270
+ * @param pattern the pattern to match against (never {@code null})
271
+ * @return a corresponding AntPathStringMatcher (never {@code null})
272
+ * @see #setCachePatterns
273
+ */
274
+ protected AntPathStringMatcher getStringMatcher (String pattern ) {
275
+ AntPathStringMatcher matcher = null ;
276
+ Boolean cachePatterns = this .cachePatterns ;
277
+ if (cachePatterns == null || cachePatterns .booleanValue ()) {
278
+ matcher = this .stringMatcherCache .get (pattern );
279
+ }
237
280
if (matcher == null ) {
238
281
matcher = new AntPathStringMatcher (pattern );
239
- this .stringMatcherCache .put (pattern , matcher );
282
+ if (cachePatterns == null && this .stringMatcherCache .size () == CACHE_TURNOFF_THRESHOLD ) {
283
+ // Try to adapt to the runtime situation that we're encountering:
284
+ // There are obviously too many different paths coming in here...
285
+ // So let's turn off the cache since the patterns are unlikely to be reoccurring.
286
+ this .cachePatterns = false ;
287
+ this .stringMatcherCache .clear ();
288
+ return matcher ;
289
+ }
290
+ if (cachePatterns == null || cachePatterns .booleanValue ()) {
291
+ this .stringMatcherCache .put (pattern , matcher );
292
+ }
240
293
}
241
- return matcher . matchStrings ( str , uriTemplateVariables ) ;
294
+ return matcher ;
242
295
}
243
296
244
297
/**
@@ -381,11 +434,99 @@ public Comparator<String> getPatternComparator(String path) {
381
434
}
382
435
383
436
384
- private static class AntPatternComparator implements Comparator <String > {
437
+ /**
438
+ * Tests whether or not a string matches against a pattern via a {@link Pattern}.
439
+ * <p>The pattern may contain special characters: '*' means zero or more characters; '?' means one and
440
+ * only one character; '{' and '}' indicate a URI template pattern. For example <tt>/users/{user}</tt>.
441
+ */
442
+ protected static class AntPathStringMatcher {
443
+
444
+ private static final Pattern GLOB_PATTERN = Pattern .compile ("\\ ?|\\ *|\\ {((?:\\ {[^/]+?\\ }|[^/{}]|\\ \\ [{}])+?)\\ }" );
445
+
446
+ private static final String DEFAULT_VARIABLE_PATTERN = "(.*)" ;
447
+
448
+ private final Pattern pattern ;
449
+
450
+ private final List <String > variableNames = new LinkedList <String >();
451
+
452
+ public AntPathStringMatcher (String pattern ) {
453
+ StringBuilder patternBuilder = new StringBuilder ();
454
+ Matcher m = GLOB_PATTERN .matcher (pattern );
455
+ int end = 0 ;
456
+ while (m .find ()) {
457
+ patternBuilder .append (quote (pattern , end , m .start ()));
458
+ String match = m .group ();
459
+ if ("?" .equals (match )) {
460
+ patternBuilder .append ('.' );
461
+ }
462
+ else if ("*" .equals (match )) {
463
+ patternBuilder .append (".*" );
464
+ }
465
+ else if (match .startsWith ("{" ) && match .endsWith ("}" )) {
466
+ int colonIdx = match .indexOf (':' );
467
+ if (colonIdx == -1 ) {
468
+ patternBuilder .append (DEFAULT_VARIABLE_PATTERN );
469
+ this .variableNames .add (m .group (1 ));
470
+ }
471
+ else {
472
+ String variablePattern = match .substring (colonIdx + 1 , match .length () - 1 );
473
+ patternBuilder .append ('(' );
474
+ patternBuilder .append (variablePattern );
475
+ patternBuilder .append (')' );
476
+ String variableName = match .substring (1 , colonIdx );
477
+ this .variableNames .add (variableName );
478
+ }
479
+ }
480
+ end = m .end ();
481
+ }
482
+ patternBuilder .append (quote (pattern , end , pattern .length ()));
483
+ this .pattern = Pattern .compile (patternBuilder .toString ());
484
+ }
485
+
486
+ private String quote (String s , int start , int end ) {
487
+ if (start == end ) {
488
+ return "" ;
489
+ }
490
+ return Pattern .quote (s .substring (start , end ));
491
+ }
492
+
493
+ /**
494
+ * Main entry point.
495
+ * @return {@code true} if the string matches against the pattern, or {@code false} otherwise.
496
+ */
497
+ public boolean matchStrings (String str , Map <String , String > uriTemplateVariables ) {
498
+ Matcher matcher = this .pattern .matcher (str );
499
+ if (matcher .matches ()) {
500
+ if (uriTemplateVariables != null ) {
501
+ // SPR-8455
502
+ Assert .isTrue (this .variableNames .size () == matcher .groupCount (),
503
+ "The number of capturing groups in the pattern segment " + this .pattern +
504
+ " does not match the number of URI template variables it defines, which can occur if " +
505
+ " capturing groups are used in a URI template regex. Use non-capturing groups instead." );
506
+ for (int i = 1 ; i <= matcher .groupCount (); i ++) {
507
+ String name = this .variableNames .get (i - 1 );
508
+ String value = matcher .group (i );
509
+ uriTemplateVariables .put (name , value );
510
+ }
511
+ }
512
+ return true ;
513
+ }
514
+ else {
515
+ return false ;
516
+ }
517
+ }
518
+ }
519
+
520
+
521
+ /**
522
+ * The default {@link Comparator} implementation returned by
523
+ * {@link #getPatternComparator(String)}.
524
+ */
525
+ protected static class AntPatternComparator implements Comparator <String > {
385
526
386
527
private final String path ;
387
528
388
- private AntPatternComparator (String path ) {
529
+ public AntPatternComparator (String path ) {
389
530
this .path = path ;
390
531
}
391
532
@@ -465,92 +606,7 @@ private int getWildCardCount(String pattern) {
465
606
* Returns the length of the given pattern, where template variables are considered to be 1 long.
466
607
*/
467
608
private int getPatternLength (String pattern ) {
468
- Matcher m = VARIABLE_PATTERN .matcher (pattern );
469
- return m .replaceAll ("#" ).length ();
470
- }
471
- }
472
-
473
-
474
- /**
475
- * Tests whether or not a string matches against a pattern via a {@link Pattern}.
476
- * <p>The pattern may contain special characters: '*' means zero or more characters; '?' means one and
477
- * only one character; '{' and '}' indicate a URI template pattern. For example <tt>/users/{user}</tt>.
478
- */
479
- private static class AntPathStringMatcher {
480
-
481
- private static final Pattern GLOB_PATTERN = Pattern .compile ("\\ ?|\\ *|\\ {((?:\\ {[^/]+?\\ }|[^/{}]|\\ \\ [{}])+?)\\ }" );
482
-
483
- private static final String DEFAULT_VARIABLE_PATTERN = "(.*)" ;
484
-
485
- private final Pattern pattern ;
486
-
487
- private final List <String > variableNames = new LinkedList <String >();
488
-
489
- public AntPathStringMatcher (String pattern ) {
490
- StringBuilder patternBuilder = new StringBuilder ();
491
- Matcher m = GLOB_PATTERN .matcher (pattern );
492
- int end = 0 ;
493
- while (m .find ()) {
494
- patternBuilder .append (quote (pattern , end , m .start ()));
495
- String match = m .group ();
496
- if ("?" .equals (match )) {
497
- patternBuilder .append ('.' );
498
- }
499
- else if ("*" .equals (match )) {
500
- patternBuilder .append (".*" );
501
- }
502
- else if (match .startsWith ("{" ) && match .endsWith ("}" )) {
503
- int colonIdx = match .indexOf (':' );
504
- if (colonIdx == -1 ) {
505
- patternBuilder .append (DEFAULT_VARIABLE_PATTERN );
506
- this .variableNames .add (m .group (1 ));
507
- }
508
- else {
509
- String variablePattern = match .substring (colonIdx + 1 , match .length () - 1 );
510
- patternBuilder .append ('(' );
511
- patternBuilder .append (variablePattern );
512
- patternBuilder .append (')' );
513
- String variableName = match .substring (1 , colonIdx );
514
- this .variableNames .add (variableName );
515
- }
516
- }
517
- end = m .end ();
518
- }
519
- patternBuilder .append (quote (pattern , end , pattern .length ()));
520
- this .pattern = Pattern .compile (patternBuilder .toString ());
521
- }
522
-
523
- private String quote (String s , int start , int end ) {
524
- if (start == end ) {
525
- return "" ;
526
- }
527
- return Pattern .quote (s .substring (start , end ));
528
- }
529
-
530
- /**
531
- * Main entry point.
532
- * @return {@code true} if the string matches against the pattern, or {@code false} otherwise.
533
- */
534
- public boolean matchStrings (String str , Map <String , String > uriTemplateVariables ) {
535
- Matcher matcher = this .pattern .matcher (str );
536
- if (matcher .matches ()) {
537
- if (uriTemplateVariables != null ) {
538
- // SPR-8455
539
- Assert .isTrue (this .variableNames .size () == matcher .groupCount (),
540
- "The number of capturing groups in the pattern segment " + this .pattern +
541
- " does not match the number of URI template variables it defines, which can occur if " +
542
- " capturing groups are used in a URI template regex. Use non-capturing groups instead." );
543
- for (int i = 1 ; i <= matcher .groupCount (); i ++) {
544
- String name = this .variableNames .get (i - 1 );
545
- String value = matcher .group (i );
546
- uriTemplateVariables .put (name , value );
547
- }
548
- }
549
- return true ;
550
- }
551
- else {
552
- return false ;
553
- }
609
+ return VARIABLE_PATTERN .matcher (pattern ).replaceAll ("#" ).length ();
554
610
}
555
611
}
556
612
0 commit comments