21
21
import org .slf4j .Logger ;
22
22
import org .slf4j .LoggerFactory ;
23
23
24
+ /**
25
+ * <p>
26
+ * RegexPathSpec is a PathSpec implementation for a {@link PathMappings} instance.
27
+ * </p>
28
+ *
29
+ * <p>
30
+ * Supports the standard Java regex found in {@link java.util.regex.Pattern}.
31
+ * </p>
32
+ *
33
+ * <p>
34
+ * Supports {@link PathSpecGroup} for {@link PathSpecGroup#EXACT}, {@link PathSpecGroup#PREFIX_GLOB}, {@link PathSpecGroup#MIDDLE_GLOB}, and {@link PathSpecGroup#SUFFIX_GLOB}.
35
+ * This is done by evaluating the signature or the provided regex pattern for what is a literal vs a glob (of any kind).
36
+ * </p>
37
+ *
38
+ * <ul>
39
+ * <li>Only literals, it's a {@link PathSpecGroup#EXACT}.</li>
40
+ * <li>Starts with literals, ends with globs, it's a {@link PathSpecGroup#PREFIX_GLOB}</li>
41
+ * <li>Starts with glob, has at least 1 literal, then any thing else, it's a {@link PathSpecGroup#SUFFIX_GLOB}</li>
42
+ * <li>All other signatures are a {@link PathSpecGroup#MIDDLE_GLOB}</li>
43
+ * </ul>
44
+ *
45
+ * <p>
46
+ * The use of regex capture groups, regex character classes, regex quantifiers, and regex special contructs
47
+ * will be identified as a glob (for signature determination), all other characters are identified
48
+ * as literal. The regex {@code ^} beginning of line, and regex {@code $} end of line are ignored.
49
+ * </p>
50
+ *
51
+ * <p>
52
+ * <b>Support for {@link MatchedPath} and PathMatch vs PathInfo</b>
53
+ * </p>
54
+ *
55
+ * <p>
56
+ * There's a few steps in evaluating the matched input path for determining where the split
57
+ * in the input path should occur for {@link MatchedPath#getPathMatch()} and {@link MatchedPath#getPathInfo()}.
58
+ * </p>
59
+ *
60
+ * <ol>
61
+ * <li>
62
+ * If there are no regex capturing groups,
63
+ * the entire path is returned in {@link MatchedPath#getPathMatch()},
64
+ * and a null returned for {@link MatchedPath#getPathInfo()}
65
+ * </li>
66
+ * <li>
67
+ * If both the named regex capturing groups {@code name} and {@code info} are present, then
68
+ * the {@code name} group is returned in {@link MatchedPath#getPathMatch()} and the
69
+ * {@code info} group is returned in {@link MatchedPath#getPathInfo()}
70
+ * </li>
71
+ * <li>
72
+ * If there is only 1 regex capturing group
73
+ * <ul>
74
+ * <li>
75
+ * If the named regex capturing group {@code name} is present, the
76
+ * input path up to the end of the capturing group is returned
77
+ * in {@link MatchedPath#getPathMatch()} and any following characters (or null)
78
+ * are returned in {@link MatchedPath#getPathInfo()}
79
+ * </li>
80
+ * <li>
81
+ * other wise the input path up to the start of the capturing group is returned
82
+ * in {@link MatchedPath#getPathMatch()} and any following characters (or null)
83
+ * are returned in {@link MatchedPath#getPathInfo()}
84
+ * </li>
85
+ * </ul>
86
+ * If the split on pathMatch ends with {@code /} AND the pathInfo doesn't start with {@code /}
87
+ * then the slash is moved from pathMatch to pathInfo.
88
+ * </li>
89
+ * <li>
90
+ * All other RegexPathSpec signatures will return the entire path
91
+ * in {@link MatchedPath#getPathMatch()}, and a null returned for {@link MatchedPath#getPathInfo()}
92
+ * </li>
93
+ * </ol>
94
+ */
24
95
public class RegexPathSpec extends AbstractPathSpec
25
96
{
26
97
private static final Logger LOG = LoggerFactory .getLogger (UriTemplatePathSpec .class );
@@ -54,8 +125,9 @@ public RegexPathSpec(String regex)
54
125
declaration = regex ;
55
126
int specLength = declaration .length ();
56
127
// build up a simple signature we can use to identify the grouping
57
- boolean inTextList = false ;
128
+ boolean inCharacterClass = false ;
58
129
boolean inQuantifier = false ;
130
+ boolean inCaptureGroup = false ;
59
131
StringBuilder signature = new StringBuilder ();
60
132
61
133
int pathDepth = 0 ;
@@ -68,8 +140,6 @@ public RegexPathSpec(String regex)
68
140
case '^' : // ignore anchors
69
141
case '$' : // ignore anchors
70
142
case '\'' : // ignore escaping
71
- case '(' : // ignore grouping
72
- case ')' : // ignore grouping
73
143
break ;
74
144
case '+' : // single char quantifier
75
145
case '?' : // single char quantifier
@@ -78,25 +148,32 @@ public RegexPathSpec(String regex)
78
148
case '.' : // any char token
79
149
signature .append ('g' ); // glob
80
150
break ;
81
- case '{' :
151
+ case '(' : // in regex capture group
152
+ inCaptureGroup = true ;
153
+ break ;
154
+ case ')' :
155
+ inCaptureGroup = false ;
156
+ signature .append ('g' );
157
+ break ;
158
+ case '{' : // in regex quantifier
82
159
inQuantifier = true ;
83
160
break ;
84
161
case '}' :
85
162
inQuantifier = false ;
86
163
break ;
87
- case '[' :
88
- inTextList = true ;
164
+ case '[' : // in regex character class
165
+ inCharacterClass = true ;
89
166
break ;
90
167
case ']' :
91
- inTextList = false ;
168
+ inCharacterClass = false ;
92
169
signature .append ('g' ); // glob
93
170
break ;
94
171
case '/' :
95
- if (!inTextList && !inQuantifier )
172
+ if (!inCharacterClass && !inQuantifier && ! inCaptureGroup )
96
173
pathDepth ++;
97
174
break ;
98
175
default :
99
- if (!inTextList && !inQuantifier && Character .isLetterOrDigit (c ))
176
+ if (!inCharacterClass && !inQuantifier && ! inCaptureGroup && Character .isLetterOrDigit (c ))
100
177
{
101
178
if (last == '\\' ) // escaped
102
179
{
@@ -135,9 +212,9 @@ public RegexPathSpec(String regex)
135
212
String sig = signature .toString ();
136
213
137
214
PathSpecGroup group ;
138
- if (Pattern .matches ("^l* $" , sig ))
215
+ if (Pattern .matches ("^l+ $" , sig ))
139
216
group = PathSpecGroup .EXACT ;
140
- else if (Pattern .matches ("^l* g+" , sig ))
217
+ else if (Pattern .matches ("^l+ g+" , sig ))
141
218
group = PathSpecGroup .PREFIX_GLOB ;
142
219
else if (Pattern .matches ("^g+l+.*" , sig ))
143
220
group = PathSpecGroup .SUFFIX_GLOB ;
@@ -193,44 +270,19 @@ public int getPathDepth()
193
270
@ Override
194
271
public String getPathInfo (String path )
195
272
{
196
- // Path Info only valid for PREFIX_GLOB types
197
- if (_group == PathSpecGroup .PREFIX_GLOB )
198
- {
199
- Matcher matcher = getMatcher (path );
200
- if (matcher .matches ())
201
- {
202
- if (matcher .groupCount () >= 1 )
203
- {
204
- String pathInfo = matcher .group (1 );
205
- if ("" .equals (pathInfo ))
206
- return "/" ;
207
- else
208
- return pathInfo ;
209
- }
210
- }
211
- }
212
- return null ;
273
+ MatchedPath matched = matched (path );
274
+ if (matched == null )
275
+ return null ;
276
+ return matched .getPathInfo ();
213
277
}
214
278
215
279
@ Override
216
280
public String getPathMatch (String path )
217
281
{
218
- Matcher matcher = getMatcher (path );
219
- if (matcher .matches ())
220
- {
221
- if (_group == PathSpecGroup .PREFIX_GLOB && matcher .groupCount () >= 1 )
222
- {
223
- int idx = matcher .start (1 );
224
- if (idx > 0 )
225
- {
226
- if (path .charAt (idx - 1 ) == '/' )
227
- idx --;
228
- return path .substring (0 , idx );
229
- }
230
- }
231
- return path ;
232
- }
233
- return null ;
282
+ MatchedPath matched = matched (path );
283
+ if (matched == null )
284
+ return "" ;
285
+ return matched .getPathMatch ();
234
286
}
235
287
236
288
@ Override
@@ -277,74 +329,117 @@ private class RegexMatchedPath implements MatchedPath
277
329
{
278
330
private final RegexPathSpec pathSpec ;
279
331
private final String path ;
280
- private final Matcher matcher ;
332
+ private String pathMatch ;
333
+ private String pathInfo ;
281
334
282
335
public RegexMatchedPath (RegexPathSpec regexPathSpec , String path , Matcher matcher )
283
336
{
284
337
this .pathSpec = regexPathSpec ;
285
338
this .path = path ;
286
- this .matcher = matcher ;
339
+
340
+ calcPathMatchInfo (matcher );
287
341
}
288
342
289
- @ Override
290
- public String getPathMatch ()
343
+ private void calcPathMatchInfo (Matcher matcher )
291
344
{
292
- try
293
- {
294
- String p = matcher .group ("name" );
295
- if (p != null )
296
- {
297
- return p ;
298
- }
299
- }
300
- catch (IllegalArgumentException ignore )
345
+ int groupCount = matcher .groupCount ();
346
+
347
+
348
+ if (groupCount == 0 )
301
349
{
302
- // ignore if group name not found.
350
+ pathMatch = path ;
351
+ pathInfo = null ;
352
+ return ;
303
353
}
304
354
305
- if (pathSpec . getGroup () == PathSpecGroup . PREFIX_GLOB && matcher . groupCount () > = 1 )
355
+ if (groupCount = = 1 )
306
356
{
357
+ // we know we are splitting
358
+ int idxNameEnd = endOf (matcher , "name" );
359
+ if (idxNameEnd >= 0 )
360
+ {
361
+ pathMatch = path .substring (0 , idxNameEnd );
362
+ pathInfo = path .substring (idxNameEnd );
363
+
364
+ // If split on pathMatch ends with '/'
365
+ // AND pathInfo doesn't have one, move the slash to pathInfo only move 1 level
366
+ if (pathMatch .length () > 0 && pathMatch .charAt (pathMatch .length () - 1 ) == '/' &&
367
+ !pathInfo .startsWith ("/" ))
368
+ {
369
+ pathMatch = pathMatch .substring (0 , pathMatch .length () - 1 );
370
+ pathInfo = '/' + pathInfo ;
371
+ }
372
+ return ;
373
+ }
374
+
375
+ // Use start of anonymous group
307
376
int idx = matcher .start (1 );
308
- if (idx > 0 )
377
+ if (idx >= 0 )
309
378
{
310
- if (this .path .charAt (idx - 1 ) == '/' )
311
- idx --;
312
- return this .path .substring (0 , idx );
379
+ pathMatch = path .substring (0 , idx );
380
+ pathInfo = path .substring (idx );
381
+
382
+ if (pathMatch .length () > 0 && pathMatch .charAt (pathMatch .length () - 1 ) == '/' &&
383
+ !pathInfo .startsWith ("/" ))
384
+ {
385
+ pathMatch = pathMatch .substring (0 , pathMatch .length () - 1 );
386
+ pathInfo = '/' + pathInfo ;
387
+ }
388
+ return ;
313
389
}
314
390
}
315
391
316
- // default is the full path
317
- return this .path ;
392
+ // Reach here we have 2+ groups
393
+
394
+ String gName = valueOf (matcher , "name" );
395
+ String gInfo = valueOf (matcher , "info" );
396
+
397
+ // if both named groups exist
398
+ if (gName != null && gInfo != null )
399
+ {
400
+ pathMatch = gName ;
401
+ pathInfo = gInfo ;
402
+ return ;
403
+ }
404
+
405
+ pathMatch = path ;
406
+ pathInfo = null ;
318
407
}
319
408
320
- @ Override
321
- public String getPathInfo ()
409
+ private String valueOf (Matcher matcher , String groupName )
322
410
{
323
411
try
324
412
{
325
- String p = matcher .group ("info" );
326
- if (p != null )
327
- {
328
- return p ;
329
- }
413
+ return matcher .group (groupName );
330
414
}
331
- catch (IllegalArgumentException ignore )
415
+ catch (IllegalArgumentException notFound )
332
416
{
333
- // ignore if group info not found.
417
+ return null ;
334
418
}
419
+ }
335
420
336
- // Path Info only valid for PREFIX_GLOB
337
- if (pathSpec .getGroup () == PathSpecGroup .PREFIX_GLOB && matcher .groupCount () >= 1 )
421
+ private int endOf (Matcher matcher , String groupName )
422
+ {
423
+ try
424
+ {
425
+ return matcher .end (groupName );
426
+ }
427
+ catch (IllegalArgumentException notFound )
338
428
{
339
- String pathInfo = matcher .group (1 );
340
- if ("" .equals (pathInfo ))
341
- return "/" ;
342
- else
343
- return pathInfo ;
429
+ return -2 ;
344
430
}
431
+ }
345
432
346
- // default is null
347
- return null ;
433
+ @ Override
434
+ public String getPathMatch ()
435
+ {
436
+ return this .pathMatch ;
437
+ }
438
+
439
+ @ Override
440
+ public String getPathInfo ()
441
+ {
442
+ return this .pathInfo ;
348
443
}
349
444
350
445
@ Override
@@ -353,7 +448,6 @@ public String toString()
353
448
return getClass ().getSimpleName () + "[" +
354
449
"pathSpec=" + pathSpec +
355
450
", path=\" " + path + "\" " +
356
- ", matcher=" + matcher +
357
451
']' ;
358
452
}
359
453
}
0 commit comments