2 * Copyright 2002-2007 the original author or authors.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package org
.argeo
.slc
.repo
.internal
.springutil
;
20 * PathMatcher implementation for Ant-style path patterns. Examples are provided
24 * Part of this mapping code has been kindly borrowed from
25 * <a href="http://ant.apache.org">Apache Ant</a>.
28 * The mapping matches URLs using the following rules:<br>
30 * <li>? matches one character</li>
31 * <li>* matches zero or more characters</li>
32 * <li>** matches zero or more 'directories' in a path</li>
38 * <li><code>com/t?st.jsp</code> - matches <code>com/test.jsp</code> but also
39 * <code>com/tast.jsp</code> or <code>com/txst.jsp</code></li>
40 * <li><code>com/*.jsp</code> - matches all <code>.jsp</code> files in the
41 * <code>com</code> directory</li>
42 * <li><code>com/**/test.jsp</code> - matches all <code>test.jsp</code>
43 * files underneath the <code>com</code> path</li>
44 * <li><code>org/springframework/**/*.jsp</code> - matches all
45 * <code>.jsp</code> files underneath the <code>org/springframework</code>
47 * <li><code>org/**/servlet/bla.jsp</code> - matches
48 * <code>org/springframework/servlet/bla.jsp</code> but also
49 * <code>org/springframework/testing/servlet/bla.jsp</code> and
50 * <code>org/servlet/bla.jsp</code></li>
53 * @author Alef Arendsen
54 * @author Juergen Hoeller
58 public class AntPathMatcher
implements PathMatcher
{
60 /** Default path separator: "/" */
61 public static final String DEFAULT_PATH_SEPARATOR
= "/";
63 private String pathSeparator
= DEFAULT_PATH_SEPARATOR
;
66 * Set the path separator to use for pattern parsing. Default is "/", as in Ant.
68 public void setPathSeparator(String pathSeparator
) {
69 this.pathSeparator
= (pathSeparator
!= null ? pathSeparator
: DEFAULT_PATH_SEPARATOR
);
72 public boolean isPattern(String path
) {
73 return (path
.indexOf('*') != -1 || path
.indexOf('?') != -1);
76 public boolean match(String pattern
, String path
) {
77 return doMatch(pattern
, path
, true);
80 public boolean matchStart(String pattern
, String path
) {
81 return doMatch(pattern
, path
, false);
85 * Actually match the given <code>path</code> against the given
86 * <code>pattern</code>.
88 * @param pattern the pattern to match against
89 * @param path the path String to test
90 * @param fullMatch whether a full pattern match is required (else a pattern
91 * match as far as the given base path goes is sufficient)
92 * @return <code>true</code> if the supplied <code>path</code> matched,
93 * <code>false</code> if it didn't
95 protected boolean doMatch(String pattern
, String path
, boolean fullMatch
) {
96 if (path
.startsWith(this.pathSeparator
) != pattern
.startsWith(this.pathSeparator
)) {
100 // String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
101 // String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
102 // mbaudier - 2020-03-13 : Use standard Java call:
103 String
[] pattDirs
= pattern
.split(this.pathSeparator
);
104 String
[] pathDirs
= path
.split(this.pathSeparator
);
106 int pattIdxStart
= 0;
107 int pattIdxEnd
= pattDirs
.length
- 1;
108 int pathIdxStart
= 0;
109 int pathIdxEnd
= pathDirs
.length
- 1;
111 // Match all elements up to the first **
112 while (pattIdxStart
<= pattIdxEnd
&& pathIdxStart
<= pathIdxEnd
) {
113 String patDir
= pattDirs
[pattIdxStart
];
114 if ("**".equals(patDir
)) {
117 if (!matchStrings(patDir
, pathDirs
[pathIdxStart
])) {
124 if (pathIdxStart
> pathIdxEnd
) {
125 // Path is exhausted, only match if rest of pattern is * or **'s
126 if (pattIdxStart
> pattIdxEnd
) {
127 return (pattern
.endsWith(this.pathSeparator
) ? path
.endsWith(this.pathSeparator
)
128 : !path
.endsWith(this.pathSeparator
));
133 if (pattIdxStart
== pattIdxEnd
&& pattDirs
[pattIdxStart
].equals("*") && path
.endsWith(this.pathSeparator
)) {
136 for (int i
= pattIdxStart
; i
<= pattIdxEnd
; i
++) {
137 if (!pattDirs
[i
].equals("**")) {
142 } else if (pattIdxStart
> pattIdxEnd
) {
143 // String not exhausted, but pattern is. Failure.
145 } else if (!fullMatch
&& "**".equals(pattDirs
[pattIdxStart
])) {
146 // Path start definitely matches due to "**" part in pattern.
151 while (pattIdxStart
<= pattIdxEnd
&& pathIdxStart
<= pathIdxEnd
) {
152 String patDir
= pattDirs
[pattIdxEnd
];
153 if (patDir
.equals("**")) {
156 if (!matchStrings(patDir
, pathDirs
[pathIdxEnd
])) {
162 if (pathIdxStart
> pathIdxEnd
) {
163 // String is exhausted
164 for (int i
= pattIdxStart
; i
<= pattIdxEnd
; i
++) {
165 if (!pattDirs
[i
].equals("**")) {
172 while (pattIdxStart
!= pattIdxEnd
&& pathIdxStart
<= pathIdxEnd
) {
174 for (int i
= pattIdxStart
+ 1; i
<= pattIdxEnd
; i
++) {
175 if (pattDirs
[i
].equals("**")) {
180 if (patIdxTmp
== pattIdxStart
+ 1) {
181 // '**/**' situation, so skip one
185 // Find the pattern between padIdxStart & padIdxTmp in str between
186 // strIdxStart & strIdxEnd
187 int patLength
= (patIdxTmp
- pattIdxStart
- 1);
188 int strLength
= (pathIdxEnd
- pathIdxStart
+ 1);
191 strLoop
: for (int i
= 0; i
<= strLength
- patLength
; i
++) {
192 for (int j
= 0; j
< patLength
; j
++) {
193 String subPat
= (String
) pattDirs
[pattIdxStart
+ j
+ 1];
194 String subStr
= (String
) pathDirs
[pathIdxStart
+ i
+ j
];
195 if (!matchStrings(subPat
, subStr
)) {
199 foundIdx
= pathIdxStart
+ i
;
203 if (foundIdx
== -1) {
207 pattIdxStart
= patIdxTmp
;
208 pathIdxStart
= foundIdx
+ patLength
;
211 for (int i
= pattIdxStart
; i
<= pattIdxEnd
; i
++) {
212 if (!pattDirs
[i
].equals("**")) {
221 * Tests whether or not a string matches against a pattern. The pattern may
222 * contain two special characters:<br>
223 * '*' means zero or more characters<br>
224 * '?' means one and only one character
226 * @param pattern pattern to match against. Must not be <code>null</code>.
227 * @param str string which must be matched against the pattern. Must not be
229 * @return <code>true</code> if the string matches against the pattern, or
230 * <code>false</code> otherwise.
232 private boolean matchStrings(String pattern
, String str
) {
233 char[] patArr
= pattern
.toCharArray();
234 char[] strArr
= str
.toCharArray();
236 int patIdxEnd
= patArr
.length
- 1;
238 int strIdxEnd
= strArr
.length
- 1;
241 boolean containsStar
= false;
242 for (int i
= 0; i
< patArr
.length
; i
++) {
243 if (patArr
[i
] == '*') {
250 // No '*'s, so we make a shortcut
251 if (patIdxEnd
!= strIdxEnd
) {
252 return false; // Pattern and string do not have the same size
254 for (int i
= 0; i
<= patIdxEnd
; i
++) {
257 if (ch
!= strArr
[i
]) {
258 return false;// Character mismatch
262 return true; // String matches against pattern
265 if (patIdxEnd
== 0) {
266 return true; // Pattern contains only '*', which matches anything
269 // Process characters before first star
270 while ((ch
= patArr
[patIdxStart
]) != '*' && strIdxStart
<= strIdxEnd
) {
272 if (ch
!= strArr
[strIdxStart
]) {
273 return false;// Character mismatch
279 if (strIdxStart
> strIdxEnd
) {
280 // All characters in the string are used. Check if only '*'s are
281 // left in the pattern. If so, we succeeded. Otherwise failure.
282 for (int i
= patIdxStart
; i
<= patIdxEnd
; i
++) {
283 if (patArr
[i
] != '*') {
290 // Process characters after last star
291 while ((ch
= patArr
[patIdxEnd
]) != '*' && strIdxStart
<= strIdxEnd
) {
293 if (ch
!= strArr
[strIdxEnd
]) {
294 return false;// Character mismatch
300 if (strIdxStart
> strIdxEnd
) {
301 // All characters in the string are used. Check if only '*'s are
302 // left in the pattern. If so, we succeeded. Otherwise failure.
303 for (int i
= patIdxStart
; i
<= patIdxEnd
; i
++) {
304 if (patArr
[i
] != '*') {
311 // process pattern between stars. padIdxStart and patIdxEnd point
313 while (patIdxStart
!= patIdxEnd
&& strIdxStart
<= strIdxEnd
) {
315 for (int i
= patIdxStart
+ 1; i
<= patIdxEnd
; i
++) {
316 if (patArr
[i
] == '*') {
321 if (patIdxTmp
== patIdxStart
+ 1) {
322 // Two stars next to each other, skip the first one.
326 // Find the pattern between padIdxStart & padIdxTmp in str between
327 // strIdxStart & strIdxEnd
328 int patLength
= (patIdxTmp
- patIdxStart
- 1);
329 int strLength
= (strIdxEnd
- strIdxStart
+ 1);
331 strLoop
: for (int i
= 0; i
<= strLength
- patLength
; i
++) {
332 for (int j
= 0; j
< patLength
; j
++) {
333 ch
= patArr
[patIdxStart
+ j
+ 1];
335 if (ch
!= strArr
[strIdxStart
+ i
+ j
]) {
341 foundIdx
= strIdxStart
+ i
;
345 if (foundIdx
== -1) {
349 patIdxStart
= patIdxTmp
;
350 strIdxStart
= foundIdx
+ patLength
;
353 // All characters in the string are used. Check if only '*'s are left
354 // in the pattern. If so, we succeeded. Otherwise failure.
355 for (int i
= patIdxStart
; i
<= patIdxEnd
; i
++) {
356 if (patArr
[i
] != '*') {
365 * Given a pattern and a full path, determine the pattern-mapped part.
369 * <li>'<code>/docs/cvs/commit.html</code>' and
370 * '<code>/docs/cvs/commit.html</code> to ''</li>
371 * <li>'<code>/docs/*</code>' and '<code>/docs/cvs/commit</code> to
372 * '<code>cvs/commit</code>'</li>
373 * <li>'<code>/docs/cvs/*.html</code>' and '<code>/docs/cvs/commit.html</code>
374 * to '<code>commit.html</code>'</li>
375 * <li>'<code>/docs/**</code>' and '<code>/docs/cvs/commit</code> to
376 * '<code>cvs/commit</code>'</li>
377 * <li>'<code>/docs/**\/*.html</code>' and '<code>/docs/cvs/commit.html</code>
378 * to '<code>cvs/commit.html</code>'</li>
379 * <li>'<code>/*.html</code>' and '<code>/docs/cvs/commit.html</code> to
380 * '<code>docs/cvs/commit.html</code>'</li>
381 * <li>'<code>*.html</code>' and '<code>/docs/cvs/commit.html</code> to
382 * '<code>/docs/cvs/commit.html</code>'</li>
383 * <li>'<code>*</code>' and '<code>/docs/cvs/commit.html</code> to
384 * '<code>/docs/cvs/commit.html</code>'</li>
387 * Assumes that {@link #match} returns <code>true</code> for
388 * '<code>pattern</code>' and '<code>path</code>', but does <strong>not</strong>
391 public String
extractPathWithinPattern(String pattern
, String path
) {
392 // String[] patternParts = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
393 // String[] pathParts = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
394 // mbaudier - 2020-03-13 : Use standard Java call:
395 String
[] patternParts
= pattern
.split(this.pathSeparator
);
396 String
[] pathParts
= path
.split(this.pathSeparator
);
398 StringBuffer buffer
= new StringBuffer();
400 // Add any path parts that have a wildcarded pattern part.
402 for (int i
= 0; i
< patternParts
.length
; i
++) {
403 String patternPart
= patternParts
[i
];
404 if ((patternPart
.indexOf('*') > -1 || patternPart
.indexOf('?') > -1) && pathParts
.length
>= i
+ 1) {
405 if (puts
> 0 || (i
== 0 && !pattern
.startsWith(this.pathSeparator
))) {
406 buffer
.append(this.pathSeparator
);
408 buffer
.append(pathParts
[i
]);
413 // Append any trailing path parts.
414 for (int i
= patternParts
.length
; i
< pathParts
.length
; i
++) {
415 if (puts
> 0 || i
> 0) {
416 buffer
.append(this.pathSeparator
);
418 buffer
.append(pathParts
[i
]);
421 return buffer
.toString();