1 /*
2  * Hunt - A refined core library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.text.PathMatcher;
13 
14 import hunt.collection;
15 import hunt.Boolean;
16 import hunt.Exceptions;
17 import hunt.Nullable;
18 import hunt.text.Common;
19 import hunt.text.StringUtils;
20 import hunt.text.StringBuilder;
21 
22 import std.array;
23 import std.string;
24 import std.regex;
25 import std.container.array;
26 
27 /**
28  * Strategy interface for {@code string}-based path matching.
29  *
30  * <p>Used by {@link hunt.framework.core.io.support.PathMatchingResourcePatternResolver},
31  * {@link hunt.framework.web.servlet.handler.AbstractUrlHandlerMapping},
32  * and {@link hunt.framework.web.servlet.mvc.WebContentInterceptor}.
33  *
34  * <p>The default implementation is {@link AntPathMatcher}, supporting the
35  * Ant-style pattern syntax.
36  *
37  * @author Juergen Hoeller
38  * @since 1.2
39  * @see AntPathMatcher
40  */
41 interface PathMatcher {
42 
43 	/**
44 	 * Does the given {@code path} represent a pattern that can be matched
45 	 * by an implementation of this interface?
46 	 * <p>If the return value is {@code false}, then the {@link #match}
47 	 * method does not have to be used because direct equality comparisons
48 	 * on the static path Strings will lead to the same result.
49 	 * @param path the path string to check
50 	 * @return {@code true} if the given {@code path} represents a pattern
51 	 */
52 	bool isPattern(string path);
53 
54 	/**
55 	 * Match the given {@code path} against the given {@code pattern},
56 	 * according to this PathMatcher's matching strategy.
57 	 * @param pattern the pattern to match against
58 	 * @param path the path string to test
59 	 * @return {@code true} if the supplied {@code path} matched,
60 	 * {@code false} if it didn't
61 	 */
62 	bool match(string pattern, string path);
63 
64 	/**
65 	 * Match the given {@code path} against the corresponding part of the given
66 	 * {@code pattern}, according to this PathMatcher's matching strategy.
67 	 * <p>Determines whether the pattern at least matches as far as the given base
68 	 * path goes, assuming that a full path may then match as well.
69 	 * @param pattern the pattern to match against
70 	 * @param path the path string to test
71 	 * @return {@code true} if the supplied {@code path} matched,
72 	 * {@code false} if it didn't
73 	 */
74 	bool matchStart(string pattern, string path);
75 
76 	/**
77 	 * Given a pattern and a full path, determine the pattern-mapped part.
78 	 * <p>This method is supposed to find out which part of the path is matched
79 	 * dynamically through an actual pattern, that is, it strips off a statically
80 	 * defined leading path from the given full path, returning only the actually
81 	 * pattern-matched part of the path.
82 	 * <p>For example: For "myroot/*.html" as pattern and "myroot/myfile.html"
83 	 * as full path, this method should return "myfile.html". The detailed
84 	 * determination rules are specified to this PathMatcher's matching strategy.
85 	 * <p>A simple implementation may return the given full path as-is in case
86 	 * of an actual pattern, and the empty string in case of the pattern not
87 	 * containing any dynamic parts (i.e. the {@code pattern} parameter being
88 	 * a static path that wouldn't qualify as an actual {@link #isPattern pattern}).
89 	 * A sophisticated implementation will differentiate between the static parts
90 	 * and the dynamic parts of the given path pattern.
91 	 * @param pattern the path pattern
92 	 * @param path the full path to introspect
93 	 * @return the pattern-mapped part of the given {@code path}
94 	 * (never {@code null})
95 	 */
96 	string extractPathWithinPattern(string pattern, string path);
97 
98 	/**
99 	 * Given a pattern and a full path, extract the URI template variables. URI template
100 	 * variables are expressed through curly brackets ('{' and '}').
101 	 * <p>For example: For pattern "/hotels/{hotel}" and path "/hotels/1", this method will
102 	 * return a map containing "hotel"->"1".
103 	 * @param pattern the path pattern, possibly containing URI templates
104 	 * @param path the full path to extract template variables from
105 	 * @return a map, containing variable names as keys; variables values as values
106 	 */
107 	Map!(string, string) extractUriTemplateVariables(string pattern, string path);
108 
109 	/**
110 	 * Given a full path, returns a {@link Comparator} suitable for sorting patterns
111 	 * in order of explicitness for that path.
112 	 * <p>The full algorithm used depends on the underlying implementation,
113 	 * but generally, the returned {@code Comparator} will
114 	 * {@linkplain java.util.List#sort(java.util.Comparator) sort}
115 	 * a list so that more specific patterns come before generic patterns.
116 	 * @param path the full path to use for comparison
117 	 * @return a comparator capable of sorting patterns in order of explicitness
118 	 */
119 	// Comparator!(string) getPatternComparator(string path);
120 
121 	/**
122 	 * Combines two patterns into a new pattern that is returned.
123 	 * <p>The full algorithm used for combining the two pattern depends on the underlying implementation.
124 	 * @param pattern1 the first pattern
125 	 * @param pattern2 the second pattern
126 	 * @return the combination of the two patterns
127 	 * @throws IllegalArgumentException when the two patterns cannot be combined
128 	 */
129 	string combine(string pattern1, string pattern2);
130 
131 }
132 
133 
134 
135 private enum Regex!char VARIABLE_PATTERN = regex("\\{[^/]+?\\}");
136 
137 /**
138  * {@link PathMatcher} implementation for Ant-style path patterns.
139  *
140  * <p>Part of this mapping code has been kindly borrowed from <a href="http://ant.apache.org">Apache Ant</a>.
141  *
142  * <p>The mapping matches URLs using the following rules:<br>
143  * <ul>
144  * <li>{@code ?} matches one character</li>
145  * <li>{@code *} matches zero or more characters</li>
146  * <li>{@code **} matches zero or more <em>directories</em> in a path</li>
147  * <li>{@code {spring:[a-z]+}} matches the regexp {@code [a-z]+} as a path variable named "spring"</li>
148  * </ul>
149  *
150  * <h3>Examples</h3>
151  * <ul>
152  * <li>{@code com/t?st.jsp} &mdash; matches {@code com/test.jsp} but also
153  * {@code com/tast.jsp} or {@code com/txst.jsp}</li>
154  * <li>{@code com/*.jsp} &mdash; matches all {@code .jsp} files in the
155  * {@code com} directory</li>
156  * <li><code>com/&#42;&#42;/test.jsp</code> &mdash; matches all {@code test.jsp}
157  * files underneath the {@code com} path</li>
158  * <li><code>org/springframework/&#42;&#42;/*.jsp</code> &mdash; matches all
159  * {@code .jsp} files underneath the {@code org/springframework} path</li>
160  * <li><code>org/&#42;&#42;/servlet/bla.jsp</code> &mdash; matches
161  * {@code org/springframework/servlet/bla.jsp} but also
162  * {@code org/springframework/testing/servlet/bla.jsp} and {@code org/servlet/bla.jsp}</li>
163  * <li>{@code com/{filename:\\w+}.jsp} will match {@code com/test.jsp} and assign the value {@code test}
164  * to the {@code filename} variable</li>
165  * </ul>
166  *
167  * <p><strong>Note:</strong> a pattern and a path must both be absolute or must
168  * both be relative in order for the two to match. Therefore it is recommended
169  * that users of this implementation to sanitize patterns in order to prefix
170  * them with "/" as it makes sense in the context in which they're used.
171  *
172  * @author Alef Arendsen
173  * @author Juergen Hoeller
174  * @author Rob Harrop
175  * @author Arjen Poutsma
176  * @author Rossen Stoyanchev
177  * @author Sam Brannen
178  * @since 16.07.2003
179  */
180 class AntPathMatcher : PathMatcher {
181 
182 	/** Default path separator: "/" */
183 	enum string DEFAULT_PATH_SEPARATOR = "/";
184 
185 	private enum int CACHE_TURNOFF_THRESHOLD = 65536;
186 
187 	private enum char[] WILDCARD_CHARS = ['*', '?', '{' ];
188 
189 	private string pathSeparator;
190 
191 	private PathSeparatorPatternCache pathSeparatorPatternCache;
192 
193 	private bool caseSensitive = true;
194 
195 	private bool trimTokens = false;
196 	
197 	private Boolean cachePatterns;
198 
199 	private Map!(string, string[]) tokenizedPatternCache;
200 
201 	Map!(string, AntPathStringMatcher) stringMatcherCache;
202 
203 	/**
204 	 * Create a new instance with the {@link #DEFAULT_PATH_SEPARATOR}.
205 	 */
206 	this() {
207 		this.pathSeparator = DEFAULT_PATH_SEPARATOR;
208 		this.pathSeparatorPatternCache = new PathSeparatorPatternCache(DEFAULT_PATH_SEPARATOR);
209         initialize();
210 	}
211 
212 	/**
213 	 * A convenient, alternative constructor to use with a custom path separator.
214 	 * @param pathSeparator the path separator to use, must not be {@code null}.
215 	 * @since 4.1
216 	 */
217 	this(string pathSeparator) {
218 		assert(pathSeparator, "'pathSeparator' is required");
219 		this.pathSeparator = pathSeparator;
220 		this.pathSeparatorPatternCache = new PathSeparatorPatternCache(pathSeparator);
221         initialize();
222 	}
223 
224     private void initialize() {
225         tokenizedPatternCache = new HashMap!(string, string[])(256); //new ConcurrentHashMap<>(256);
226         stringMatcherCache = new HashMap!(string, AntPathStringMatcher)(256);
227     }
228 
229 
230 	/**
231 	 * Set the path separator to use for pattern parsing.
232 	 * <p>Default is "/", as in Ant.
233 	 */
234 	void setPathSeparator( string pathSeparator) {
235 		this.pathSeparator = (pathSeparator !is null ? pathSeparator : DEFAULT_PATH_SEPARATOR);
236 		this.pathSeparatorPatternCache = new PathSeparatorPatternCache(this.pathSeparator);
237 	}
238 
239 	/**
240 	 * Specify whether to perform pattern matching in a case-sensitive fashion.
241 	 * <p>Default is {@code true}. Switch this to {@code false} for case-insensitive matching.
242 	 * @since 4.2
243 	 */
244 	void setCaseSensitive(bool caseSensitive) {
245 		this.caseSensitive = caseSensitive;
246 	}
247 
248 	/**
249 	 * Specify whether to trim tokenized paths and patterns.
250 	 * <p>Default is {@code false}.
251 	 */
252 	void setTrimTokens(bool trimTokens) {
253 		this.trimTokens = trimTokens;
254 	}
255 
256 	/**
257 	 * Specify whether to cache parsed pattern metadata for patterns passed
258 	 * into this matcher's {@link #match} method. A value of {@code true}
259 	 * activates an unlimited pattern cache; a value of {@code false} turns
260 	 * the pattern cache off completely.
261 	 * <p>Default is for the cache to be on, but with the variant to automatically
262 	 * turn it off when encountering too many patterns to cache at runtime
263 	 * (the threshold is 65536), assuming that arbitrary permutations of patterns
264 	 * are coming in, with little chance for encountering a recurring pattern.
265 	 * @since 4.0.1
266 	 * @see #getStringMatcher(string)
267 	 */
268 	void setCachePatterns(bool cachePatterns) {
269 		this.cachePatterns = cachePatterns;
270 	}
271 
272 	private void deactivatePatternCache() {
273 		this.cachePatterns = false;
274 		this.tokenizedPatternCache.clear();
275 		this.stringMatcherCache.clear();
276 	}
277 
278 
279 	override
280 	bool isPattern(string path) {
281 		return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
282 	}
283 
284 	override
285 	bool match(string pattern, string path) {
286 		return doMatch(pattern, path, true, null);
287 	}
288 
289 	override
290 	bool matchStart(string pattern, string path) {
291 		return doMatch(pattern, path, false, null);
292 	}
293 
294 	/**
295 	 * Actually match the given {@code path} against the given {@code pattern}.
296 	 * @param pattern the pattern to match against
297 	 * @param path the path string to test
298 	 * @param fullMatch whether a full pattern match is required (else a pattern match
299 	 * as far as the given base path goes is sufficient)
300 	 * @return {@code true} if the supplied {@code path} matched, {@code false} if it didn't
301 	 */
302 	protected bool doMatch(string pattern, string path, bool fullMatch,
303 			 Map!(string, string) uriTemplateVariables) {
304 
305 		// if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
306 		// 	return false;
307 		// }
308 
309 		// string[] pattDirs = tokenizePattern(pattern);
310 		// if (fullMatch && this.caseSensitive && !isPotentialMatch(path, pattDirs)) {
311 		// 	return false;
312 		// }
313 
314 		// string[] pathDirs = tokenizePath(path);
315 
316 		// int pattIdxStart = 0;
317 		// int pattIdxEnd = cast(int)pattDirs.length - 1;
318 		// int pathIdxStart = 0;
319 		// int pathIdxEnd = cast(int)pathDirs.length - 1;
320 
321 		// // Match all elements up to the first **
322 		// while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
323 		// 	string pattDir = pattDirs[pattIdxStart];
324 		// 	if ("**".equals(pattDir)) {
325 		// 		break;
326 		// 	}
327 		// 	if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
328 		// 		return false;
329 		// 	}
330 		// 	pattIdxStart++;
331 		// 	pathIdxStart++;
332 		// }
333 
334 		// if (pathIdxStart > pathIdxEnd) {
335 		// 	// Path is exhausted, only match if rest of pattern is * or **'s
336 		// 	if (pattIdxStart > pattIdxEnd) {
337 		// 		return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator));
338 		// 	}
339 		// 	if (!fullMatch) {
340 		// 		return true;
341 		// 	}
342 		// 	if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
343 		// 		return true;
344 		// 	}
345 		// 	for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
346 		// 		if (!pattDirs[i].equals("**")) {
347 		// 			return false;
348 		// 		}
349 		// 	}
350 		// 	return true;
351 		// }
352 		// else if (pattIdxStart > pattIdxEnd) {
353 		// 	// string not exhausted, but pattern is. Failure.
354 		// 	return false;
355 		// }
356 		// else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
357 		// 	// Path start definitely matches due to "**" part in pattern.
358 		// 	return true;
359 		// }
360 
361 		// // up to last '**'
362 		// while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
363 		// 	string pattDir = pattDirs[pattIdxEnd];
364 		// 	if (pattDir.equals("**")) {
365 		// 		break;
366 		// 	}
367 		// 	if (!matchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
368 		// 		return false;
369 		// 	}
370 		// 	pattIdxEnd--;
371 		// 	pathIdxEnd--;
372 		// }
373 		// if (pathIdxStart > pathIdxEnd) {
374 		// 	// string is exhausted
375 		// 	for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
376 		// 		if (!pattDirs[i].equals("**")) {
377 		// 			return false;
378 		// 		}
379 		// 	}
380 		// 	return true;
381 		// }
382 
383 		// while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
384 		// 	int patIdxTmp = -1;
385 		// 	for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
386 		// 		if (pattDirs[i].equals("**")) {
387 		// 			patIdxTmp = i;
388 		// 			break;
389 		// 		}
390 		// 	}
391 		// 	if (patIdxTmp == pattIdxStart + 1) {
392 		// 		// '**/**' situation, so skip one
393 		// 		pattIdxStart++;
394 		// 		continue;
395 		// 	}
396 		// 	// Find the pattern between padIdxStart & padIdxTmp in str between
397 		// 	// strIdxStart & strIdxEnd
398 		// 	int patLength = (patIdxTmp - pattIdxStart - 1);
399 		// 	int strLength = (pathIdxEnd - pathIdxStart + 1);
400 		// 	int foundIdx = -1;
401 
402 		// 	strLoop:
403 		// 	for (int i = 0; i <= strLength - patLength; i++) {
404 		// 		for (int j = 0; j < patLength; j++) {
405 		// 			string subPat = pattDirs[pattIdxStart + j + 1];
406 		// 			string subStr = pathDirs[pathIdxStart + i + j];
407 		// 			if (!matchStrings(subPat, subStr, uriTemplateVariables)) {
408 		// 				continue strLoop;
409 		// 			}
410 		// 		}
411 		// 		foundIdx = pathIdxStart + i;
412 		// 		break;
413 		// 	}
414 
415 		// 	if (foundIdx == -1) {
416 		// 		return false;
417 		// 	}
418 
419 		// 	pattIdxStart = patIdxTmp;
420 		// 	pathIdxStart = foundIdx + patLength;
421 		// }
422 
423 		// for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
424 		// 	if (!pattDirs[i].equals("**")) {
425 		// 		return false;
426 		// 	}
427 		// }
428 
429 		return true;
430 	}
431 
432 	private bool isPotentialMatch(string path, string[] pattDirs) {
433 		if (!this.trimTokens) {
434 			int pos = 0;
435 			foreach (string pattDir ; pattDirs) {
436 				int skipped = skipSeparator(path, pos, this.pathSeparator);
437 				pos += skipped;
438 				skipped = skipSegment(path, pos, pattDir);
439 				if (skipped < pattDir.length) {
440 					return (skipped > 0 || (pattDir.length > 0 && isWildcardChar(pattDir.charAt(0))));
441 				}
442 				pos += skipped;
443 			}
444 		}
445 		return true;
446 	}
447 
448 	private int skipSegment(string path, int pos, string prefix) {
449 		int skipped = 0;
450 		for (int i = 0; i < prefix.length; i++) {
451 			char c = prefix.charAt(i);
452 			if (isWildcardChar(c)) {
453 				return skipped;
454 			}
455 			int currPos = pos + skipped;
456 			if (currPos >= path.length) {
457 				return 0;
458 			}
459 			if (c == path.charAt(currPos)) {
460 				skipped++;
461 			}
462 		}
463 		return skipped;
464 	}
465 
466 	private int skipSeparator(string path, int pos, string separator) {
467 		int skipped = 0;
468 		while (path.startsWith(separator, pos + skipped)) {
469 			skipped += separator.length;
470 		}
471 		return skipped;
472 	}
473 
474 	private bool isWildcardChar(char c) {
475 		foreach (char candidate ; WILDCARD_CHARS) {
476 			if (c == candidate) {
477 				return true;
478 			}
479 		}
480 		return false;
481 	}
482 
483 	/**
484 	 * Tokenize the given path pattern into parts, based on this matcher's settings.
485 	 * <p>Performs caching based on {@link #setCachePatterns}, delegating to
486 	 * {@link #tokenizePath(string)} for the actual tokenization algorithm.
487 	 * @param pattern the pattern to tokenize
488 	 * @return the tokenized pattern parts
489 	 */
490 	// protected string[] tokenizePattern(string pattern) {
491 	// 	string[] tokenized = null;
492 	// 	Boolean cachePatterns = this.cachePatterns;
493 	// 	if (cachePatterns is null || cachePatterns.booleanValue()) {
494 	// 		tokenized = this.tokenizedPatternCache.get(pattern);
495 	// 	}
496 	// 	if (tokenized is null) {
497 	// 		tokenized = tokenizePath(pattern);
498 	// 		if (cachePatterns is null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) {
499 	// 			// Try to adapt to the runtime situation that we're encountering:
500 	// 			// There are obviously too many different patterns coming in here...
501 	// 			// So let's turn off the cache since the patterns are unlikely to be reoccurring.
502 	// 			deactivatePatternCache();
503 	// 			return tokenized;
504 	// 		}
505 	// 		if (cachePatterns is null || cachePatterns.booleanValue()) {
506 	// 			this.tokenizedPatternCache.put(pattern, tokenized);
507 	// 		}
508 	// 	}
509 	// 	return tokenized;
510 	// }
511 
512 	/**
513 	 * Tokenize the given path string into parts, based on this matcher's settings.
514 	 * @param path the path to tokenize
515 	 * @return the tokenized path parts
516 	 */
517 	// protected string[] tokenizePath(string path) {
518 	// 	return StringUtils.tokenizeToStringArray(path, this.pathSeparator, this.trimTokens, true);
519 	// }
520 
521 	/**
522 	 * Test whether or not a string matches against a pattern.
523 	 * @param pattern the pattern to match against (never {@code null})
524 	 * @param str the string which must be matched against the pattern (never {@code null})
525 	 * @return {@code true} if the string matches against the pattern, or {@code false} otherwise
526 	 */
527 	// private bool matchStrings(string pattern, string str,
528 	// 		 Map!(string, string) uriTemplateVariables) {
529 
530 	// 	return getStringMatcher(pattern).matchStrings(str, uriTemplateVariables);
531 	// }
532 
533 	/**
534 	 * Build or retrieve an {@link AntPathStringMatcher} for the given pattern.
535 	 * <p>The default implementation checks this AntPathMatcher's internal cache
536 	 * (see {@link #setCachePatterns}), creating a new AntPathStringMatcher instance
537 	 * if no cached copy is found.
538 	 * <p>When encountering too many patterns to cache at runtime (the threshold is 65536),
539 	 * it turns the default cache off, assuming that arbitrary permutations of patterns
540 	 * are coming in, with little chance for encountering a recurring pattern.
541 	 * <p>This method may be overridden to implement a custom cache strategy.
542 	 * @param pattern the pattern to match against (never {@code null})
543 	 * @return a corresponding AntPathStringMatcher (never {@code null})
544 	 * @see #setCachePatterns
545 	 */
546 	protected AntPathStringMatcher getStringMatcher(string pattern) {
547 		AntPathStringMatcher matcher = null;
548 		Boolean cachePatterns = this.cachePatterns;
549 		if (cachePatterns is null || cachePatterns) {
550 			matcher = this.stringMatcherCache.get(pattern);
551 		}
552 		if (matcher is null) {
553 			matcher = new AntPathStringMatcher(pattern, this.caseSensitive);
554 			if (cachePatterns is null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) {
555 				// Try to adapt to the runtime situation that we're encountering:
556 				// There are obviously too many different patterns coming in here...
557 				// So let's turn off the cache since the patterns are unlikely to be reoccurring.
558 				deactivatePatternCache();
559 				return matcher;
560 			}
561 			if (cachePatterns is null || cachePatterns) {
562 				this.stringMatcherCache.put(pattern, matcher);
563 			}
564 		}
565 		return matcher;
566 	}
567 
568 	/**
569 	 * Given a pattern and a full path, determine the pattern-mapped part. <p>For example: <ul>
570 	 * <li>'{@code /docs/cvs/commit.html}' and '{@code /docs/cvs/commit.html} -> ''</li>
571 	 * <li>'{@code /docs/*}' and '{@code /docs/cvs/commit} -> '{@code cvs/commit}'</li>
572 	 * <li>'{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} -> '{@code commit.html}'</li>
573 	 * <li>'{@code /docs/**}' and '{@code /docs/cvs/commit} -> '{@code cvs/commit}'</li>
574 	 * <li>'{@code /docs/**\/*.html}' and '{@code /docs/cvs/commit.html} -> '{@code cvs/commit.html}'</li>
575 	 * <li>'{@code /*.html}' and '{@code /docs/cvs/commit.html} -> '{@code docs/cvs/commit.html}'</li>
576 	 * <li>'{@code *.html}' and '{@code /docs/cvs/commit.html} -> '{@code /docs/cvs/commit.html}'</li>
577 	 * <li>'{@code *}' and '{@code /docs/cvs/commit.html} -> '{@code /docs/cvs/commit.html}'</li> </ul>
578 	 * <p>Assumes that {@link #match} returns {@code true} for '{@code pattern}' and '{@code path}', but
579 	 * does <strong>not</strong> enforce this.
580 	 */
581 	override
582 	string extractPathWithinPattern(string pattern, string path) {
583 		// string[] patternParts = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator, this.trimTokens, true);
584 		// string[] pathParts = StringUtils.tokenizeToStringArray(path, this.pathSeparator, this.trimTokens, true);
585 		// StringBuilder builder = new StringBuilder();
586 		// bool pathStarted = false;
587 
588 		// for (int segment = 0; segment < patternParts.length; segment++) {
589 		// 	string patternPart = patternParts[segment];
590 		// 	if (patternPart.indexOf('*') > -1 || patternPart.indexOf('?') > -1) {
591 		// 		for (; segment < pathParts.length; segment++) {
592 		// 			if (pathStarted || (segment == 0 && !pattern.startsWith(this.pathSeparator))) {
593 		// 				builder.append(this.pathSeparator);
594 		// 			}
595 		// 			builder.append(pathParts[segment]);
596 		// 			pathStarted = true;
597 		// 		}
598 		// 	}
599 		// }
600 
601 		// return builder.toString();
602         return path;
603 	}
604 
605 	override
606 	Map!(string, string) extractUriTemplateVariables(string pattern, string path) {
607 		Map!(string, string) variables = new LinkedHashMap!(string, string)();
608 		bool result = doMatch(pattern, path, true, variables);
609 		if (!result) {
610 			throw new IllegalStateException("Pattern \"" ~ pattern ~ "\" is not a match for \"" ~ path ~ "\"");
611 		}
612 		return variables;
613 	}
614 
615 	/**
616 	 * Combine two patterns into a new pattern.
617 	 * <p>This implementation simply concatenates the two patterns, unless
618 	 * the first pattern contains a file extension match (e.g., {@code *.html}).
619 	 * In that case, the second pattern will be merged into the first. Otherwise,
620 	 * an {@code IllegalArgumentException} will be thrown.
621 	 * <h3>Examples</h3>
622 	 * <table border="1">
623 	 * <tr><th>Pattern 1</th><th>Pattern 2</th><th>Result</th></tr>
624 	 * <tr><td>{@code null}</td><td>{@code null}</td><td>&nbsp;</td></tr>
625 	 * <tr><td>/hotels</td><td>{@code null}</td><td>/hotels</td></tr>
626 	 * <tr><td>{@code null}</td><td>/hotels</td><td>/hotels</td></tr>
627 	 * <tr><td>/hotels</td><td>/bookings</td><td>/hotels/bookings</td></tr>
628 	 * <tr><td>/hotels</td><td>bookings</td><td>/hotels/bookings</td></tr>
629 	 * <tr><td>/hotels/*</td><td>/bookings</td><td>/hotels/bookings</td></tr>
630 	 * <tr><td>/hotels/&#42;&#42;</td><td>/bookings</td><td>/hotels/&#42;&#42;/bookings</td></tr>
631 	 * <tr><td>/hotels</td><td>{hotel}</td><td>/hotels/{hotel}</td></tr>
632 	 * <tr><td>/hotels/*</td><td>{hotel}</td><td>/hotels/{hotel}</td></tr>
633 	 * <tr><td>/hotels/&#42;&#42;</td><td>{hotel}</td><td>/hotels/&#42;&#42;/{hotel}</td></tr>
634 	 * <tr><td>/*.html</td><td>/hotels.html</td><td>/hotels.html</td></tr>
635 	 * <tr><td>/*.html</td><td>/hotels</td><td>/hotels.html</td></tr>
636 	 * <tr><td>/*.html</td><td>/*.txt</td><td>{@code IllegalArgumentException}</td></tr>
637 	 * </table>
638 	 * @param pattern1 the first pattern
639 	 * @param pattern2 the second pattern
640 	 * @return the combination of the two patterns
641 	 * @throws IllegalArgumentException if the two patterns cannot be combined
642 	 */
643 	override
644 	string combine(string pattern1, string pattern2) {
645 		if (pattern1.empty() && pattern2.empty()) {
646 			return "";
647 		}
648 		if (pattern1.empty()) {
649 			return pattern2;
650 		}
651 		if (pattern2.empty()) {
652 			return pattern1;
653 		}
654 
655 		bool pattern1ContainsUriVar = (pattern1.indexOf('{') != -1);
656 		if (!pattern1.equals(pattern2) && !pattern1ContainsUriVar && match(pattern1, pattern2)) {
657 			// /* + /hotel -> /hotel ; "/*.*" ~ "/*.html" -> /*.html
658 			// However /user + /user -> /usr/user ; /{foo} + /bar -> /{foo}/bar
659 			return pattern2;
660 		}
661 
662 		// /hotels/* + /booking -> /hotels/booking
663 		// /hotels/* + booking -> /hotels/booking
664 		if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnWildCard())) {
665 			return concat(pattern1.substring(0, pattern1.length - 2), pattern2);
666 		}
667 
668 		// /hotels/** + /booking -> /hotels/**/booking
669 		// /hotels/** + booking -> /hotels/**/booking
670 		if (pattern1.endsWith(this.pathSeparatorPatternCache.getEndsOnDoubleWildCard())) {
671 			return concat(pattern1, pattern2);
672 		}
673 
674 		int starDotPos1 = cast(int)pattern1.indexOf("*.");
675 		if (pattern1ContainsUriVar || starDotPos1 == -1 || this.pathSeparator.equals(".")) {
676 			// simply concatenate the two patterns
677 			return concat(pattern1, pattern2);
678 		}
679 
680 		string ext1 = pattern1.substring(starDotPos1 + 1);
681 		int dotPos2 = cast(int)pattern2.indexOf('.');
682 		string file2 = (dotPos2 == -1 ? pattern2 : pattern2.substring(0, dotPos2));
683 		string ext2 = (dotPos2 == -1 ? "" : pattern2.substring(dotPos2));
684 		bool ext1All = (ext1.equals(".*") || ext1.equals(""));
685 		bool ext2All = (ext2.equals(".*") || ext2.equals(""));
686 		if (!ext1All && !ext2All) {
687 			throw new IllegalArgumentException("Cannot combine patterns: " ~ pattern1 ~ " vs " ~ pattern2);
688 		}
689 		string ext = (ext1All ? ext2 : ext1);
690 		return file2 ~ ext;
691 	}
692 
693 	private string concat(string path1, string path2) {
694 		bool path1EndsWithSeparator = path1.endsWith(this.pathSeparator);
695 		bool path2StartsWithSeparator = path2.startsWith(this.pathSeparator);
696 
697 		if (path1EndsWithSeparator && path2StartsWithSeparator) {
698 			return path1 ~ path2[1 .. $];
699 		}
700 		else if (path1EndsWithSeparator || path2StartsWithSeparator) {
701 			return path1 ~ path2;
702 		}
703 		else {
704 			return path1 ~ this.pathSeparator ~ path2;
705 		}
706 	}
707 
708 	/**
709 	 * Given a full path, returns a {@link Comparator} suitable for sorting patterns in order of
710 	 * explicitness.
711 	 * <p>This{@code Comparator} will {@linkplain java.util.List#sort(Comparator) sort}
712 	 * a list so that more specific patterns (without uri templates or wild cards) come before
713 	 * generic patterns. So given a list with the following patterns:
714 	 * <ol>
715 	 * <li>{@code /hotels/new}</li>
716 	 * <li>{@code /hotels/{hotel}}</li> <li>{@code /hotels/*}</li>
717 	 * </ol>
718 	 * the returned comparator will sort this list so that the order will be as indicated.
719 	 * <p>The full path given as parameter is used to test for exact matches. So when the given path
720 	 * is {@code /hotels/2}, the pattern {@code /hotels/2} will be sorted before {@code /hotels/1}.
721 	 * @param path the full path to use for comparison
722 	 * @return a comparator capable of sorting patterns in order of explicitness
723 	 */
724 	// override
725 	// Comparator!(string) getPatternComparator(string path) {
726 	// 	return new AntPatternComparator(path);
727 	// }
728 
729 }
730 
731 
732 
733 /**
734  * Tests whether or not a string matches against a pattern via a {@link Pattern}.
735  * <p>The pattern may contain special characters: '*' means zero or more characters; '?' means one and
736  * only one character; '{' and '}' indicate a URI template pattern. For example <tt>/users/{user}</tt>.
737  */
738 protected static class AntPathStringMatcher {
739 
740     private enum Regex!char GLOB_PATTERN = regex("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}");
741 
742     private enum string DEFAULT_VARIABLE_PATTERN = "(.*)";
743 
744     private Regex!char pattern;
745 
746     private Array!(string) variableNames;
747 
748     this(string pattern) {
749         this(pattern, true);
750     }
751 
752     this(string pattern, bool caseSensitive) {
753         StringBuilder patternBuilder = new StringBuilder();
754         RegexMatch!string matcher = matchAll(pattern, GLOB_PATTERN);
755         while(!matcher.empty) {
756 			Captures!string item = matcher.front;
757             string match = item.front;
758             patternBuilder.append(quote(match));
759             if ("?" == match) {
760                 patternBuilder.append('.');
761             }
762             else if ("*" == match) {
763                 patternBuilder.append(".*");
764             }
765             else if (match.startsWith("{") && match.endsWith("}")) {
766                 int colonIdx = cast(int)match.indexOf(':');
767                 if (colonIdx == -1) {
768                     patternBuilder.append(DEFAULT_VARIABLE_PATTERN);
769                     this.variableNames.insertBack(matcher.post());
770                 }
771                 else {
772                     string variablePattern = match[colonIdx + 1 .. $ - 1];
773                     patternBuilder.append('(');
774                     patternBuilder.append(variablePattern);
775                     patternBuilder.append(')');
776                     string variableName = match[1 .. colonIdx];
777                     this.variableNames.insertBack(variableName);
778                 }
779             }
780 
781 			matcher.popFront();
782         }
783         // patternBuilder.append(quote(pattern, end, pattern.length));
784         // this.pattern = (caseSensitive ? Pattern.compile(patternBuilder.toString()) :
785         //         Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE));
786         this.pattern = regex(patternBuilder.toString());
787     }
788 
789     /**
790      * Returns a literal pattern <code>string</code> for the specified
791      * <code>string</code>.
792      *
793      * <p>This method produces a <code>string</code> that can be used to
794      * create a <code>Pattern</code> that would match the string
795      * <code>s</code> as if it were a literal pattern.</p> Metacharacters
796      * or escape sequences in the input sequence will be given no special
797      * meaning.
798      *
799      * @param  s The string to be literalized
800      * @return  A literal string replacement
801      * @since 1.5
802      */
803     static string quote(string s) {
804         int slashEIndex = cast(int)s.indexOf("\\E");
805         if (slashEIndex == -1)
806             return "\\Q" ~ s ~ "\\E";
807 
808         StringBuilder sb = new StringBuilder(s.length * 2);
809         sb.append("\\Q");
810         slashEIndex = 0;
811         int current = 0;
812         while ((slashEIndex = cast(int)s.indexOf("\\E", current)) != -1) {
813             sb.append(s.substring(current, slashEIndex));
814             current = slashEIndex + 2;
815             sb.append("\\E\\\\E\\Q");
816         }
817         sb.append(s.substring(current, s.length));
818         sb.append("\\E");
819         return sb.toString();
820     }
821 
822     /**
823      * Main entry point.
824      * @return {@code true} if the string matches against the pattern, or {@code false} otherwise.
825      */
826     bool matchStrings(string str,  Map!(string, string) uriTemplateVariables) {
827         // RegexMatch!string matcher = matchAll(str, this.pattern);
828         // if (matcher.matches()) {
829         //     if (uriTemplateVariables !is null) {
830         //         // SPR-8455
831         //         if (this.variableNames.length != matcher.groupCount()) {
832         //             throw new IllegalArgumentException("The number of capturing groups in the pattern segment " ~
833         //                     to!string(this.pattern) ~ " does not match the number of URI template variables it defines, " ~
834         //                     "which can occur if capturing groups are used in a URI template regex. " ~
835         //                     "Use non-capturing groups instead.");
836         //         }
837         //         for (int i = 1; i <= matcher.groupCount(); i++) {
838         //             string name = this.variableNames[i - 1];
839         //             string value = matcher.group(i);
840         //             uriTemplateVariables.put(name, value);
841         //         }
842         //     }
843         //     return true;
844         // }
845         // else {
846         //     return false;
847         // }
848         return true;
849     }
850 }
851 
852 
853 
854 /**
855  * The default {@link Comparator} implementation returned by
856  * {@link #getPatternComparator(string)}.
857  * <p>In order, the most "generic" pattern is determined by the following:
858  * <ul>
859  * <li>if it's null or a capture all pattern (i.e. it is equal to "/**")</li>
860  * <li>if the other pattern is an actual match</li>
861  * <li>if it's a catch-all pattern (i.e. it ends with "**"</li>
862  * <li>if it's got more "*" than the other pattern</li>
863  * <li>if it's got more "{foo}" than the other pattern</li>
864  * <li>if it's shorter than the other pattern</li>
865  * </ul>
866  */
867 // protected class AntPatternComparator : Comparator!(string) {
868 
869 //     private string path;
870 
871 //     this(string path) {
872 //         this.path = path;
873 //     }
874 
875 //     /**
876 //      * Compare two patterns to determine which should match first, i.e. which
877 //      * is the most specific regarding the current path.
878 //      * @return a negative integer, zero, or a positive integer as pattern1 is
879 //      * more specific, equally specific, or less specific than pattern2.
880 //      */
881 //     override
882 //     int compare(string pattern1, string pattern2) {
883 //         PatternInfo info1 = new PatternInfo(pattern1);
884 //         PatternInfo info2 = new PatternInfo(pattern2);
885 
886 //         if (info1.isLeastSpecific() && info2.isLeastSpecific()) {
887 //             return 0;
888 //         }
889 //         else if (info1.isLeastSpecific()) {
890 //             return 1;
891 //         }
892 //         else if (info2.isLeastSpecific()) {
893 //             return -1;
894 //         }
895 
896 //         bool pattern1EqualsPath = pattern1.equals(path);
897 //         bool pattern2EqualsPath = pattern2.equals(path);
898 //         if (pattern1EqualsPath && pattern2EqualsPath) {
899 //             return 0;
900 //         }
901 //         else if (pattern1EqualsPath) {
902 //             return -1;
903 //         }
904 //         else if (pattern2EqualsPath) {
905 //             return 1;
906 //         }
907 
908 //         if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) {
909 //             return 1;
910 //         }
911 //         else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) {
912 //             return -1;
913 //         }
914 
915 //         if (info1.getTotalCount() != info2.getTotalCount()) {
916 //             return info1.getTotalCount() - info2.getTotalCount();
917 //         }
918 
919 //         if (info1.getLength() != info2.getLength()) {
920 //             return info2.getLength() - info1.getLength();
921 //         }
922 
923 //         if (info1.getSingleWildcards() < info2.getSingleWildcards()) {
924 //             return -1;
925 //         }
926 //         else if (info2.getSingleWildcards() < info1.getSingleWildcards()) {
927 //             return 1;
928 //         }
929 
930 //         if (info1.getUriVars() < info2.getUriVars()) {
931 //             return -1;
932 //         }
933 //         else if (info2.getUriVars() < info1.getUriVars()) {
934 //             return 1;
935 //         }
936 
937 //         return 0;
938 //     }
939 // }
940 
941 
942 /**
943  * Value class that holds information about the pattern, e.g. number of
944  * occurrences of "*", "**", and "{" pattern elements.
945  */
946 private class PatternInfo {
947     
948     private string pattern;
949 
950     private int uriVars;
951 
952     private int singleWildcards;
953 
954     private int doubleWildcards;
955 
956     private bool catchAllPattern;
957 
958     private bool prefixPattern;
959 
960     private int length;
961 
962     this( string pattern) {
963         this.pattern = pattern;
964         if (this.pattern !is null) {
965             initCounters();
966             this.catchAllPattern = this.pattern.equals("/**");
967             this.prefixPattern = !this.catchAllPattern && this.pattern.endsWith("/**");
968         }
969         if (this.uriVars == 0) {
970             this.length = cast(int) (this.pattern !is null ? this.pattern.length : 0);
971         }
972     }
973 
974     protected void initCounters() {
975         int pos = 0;
976         if (this.pattern !is null) {
977             while (pos < this.pattern.length) {
978                 if (this.pattern.charAt(pos) == '{') {
979                     this.uriVars++;
980                     pos++;
981                 }
982                 else if (this.pattern.charAt(pos) == '*') {
983                     if (pos + 1 < this.pattern.length && this.pattern.charAt(pos + 1) == '*') {
984                         this.doubleWildcards++;
985                         pos += 2;
986                     }
987                     else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) {
988                         this.singleWildcards++;
989                         pos++;
990                     }
991                     else {
992                         pos++;
993                     }
994                 }
995                 else {
996                     pos++;
997                 }
998             }
999         }
1000     }
1001 
1002     int getUriVars() {
1003         return this.uriVars;
1004     }
1005 
1006     int getSingleWildcards() {
1007         return this.singleWildcards;
1008     }
1009 
1010     int getDoubleWildcards() {
1011         return this.doubleWildcards;
1012     }
1013 
1014     bool isLeastSpecific() {
1015         return (this.pattern is null || this.catchAllPattern);
1016     }
1017 
1018     bool isPrefixPattern() {
1019         return this.prefixPattern;
1020     }
1021 
1022     int getTotalCount() {
1023         return this.uriVars + this.singleWildcards + (2 * this.doubleWildcards);
1024     }
1025 
1026     /**
1027      * Returns the length of the given pattern, where template variables are considered to be 1 long.
1028      */
1029     int getLength() {
1030         // if (this.length == 0 && !this.pattern.empty) {
1031         //     Captures!string m = matchFirst(this.pattern, VARIABLE_PATTERN);
1032         //     string r = 
1033         //     this.length = 
1034         //             VARIABLE_PATTERN.matcher(this.pattern).replaceAll("#").length ;
1035         // }
1036         return this.length;
1037     }
1038 }
1039 
1040 /**
1041  * A simple cache for patterns that depend on the configured path separator.
1042  */
1043 private class PathSeparatorPatternCache {
1044 
1045     private string endsOnWildCard;
1046 
1047     private string endsOnDoubleWildCard;
1048 
1049     this(string pathSeparator) {
1050         this.endsOnWildCard = pathSeparator ~ "*";
1051         this.endsOnDoubleWildCard = pathSeparator ~ "**";
1052     }
1053 
1054     string getEndsOnWildCard() {
1055         return this.endsOnWildCard;
1056     }
1057 
1058     string getEndsOnDoubleWildCard() {
1059         return this.endsOnDoubleWildCard;
1060     }
1061 }