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