001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.util;
018:
019: /**
020: * PathMatcher implementation for Ant-style path patterns.
021: * Examples are provided below.
022: *
023: * <p>Part of this mapping code has been kindly borrowed from
024: * <a href="http://ant.apache.org">Apache Ant</a>.
025: *
026: * <p>The mapping matches URLs using the following rules:<br>
027: * <ul>
028: * <li>? matches one character</li>
029: * <li>* matches zero or more characters</li>
030: * <li>** matches zero or more 'directories' in a path</li>
031: * </ul>
032: *
033: * <p>Some examples:<br>
034: * <ul>
035: * <li><code>com/t?st.jsp</code> - matches <code>com/test.jsp</code> but also
036: * <code>com/tast.jsp</code> or <code>com/txst.jsp</code></li>
037: * <li><code>com/*.jsp</code> - matches all <code>.jsp</code> files in the
038: * <code>com</code> directory</li>
039: * <li><code>com/**/test.jsp</code> - matches all <code>test.jsp</code>
040: * files underneath the <code>com</code> path</li>
041: * <li><code>org/springframework/**/*.jsp</code> - matches all <code>.jsp</code>
042: * files underneath the <code>org/springframework</code> path</li>
043: * <li><code>org/**/servlet/bla.jsp</code> - matches
044: * <code>org/springframework/servlet/bla.jsp</code> but also
045: * <code>org/springframework/testing/servlet/bla.jsp</code> and
046: * <code>org/servlet/bla.jsp</code></li>
047: * </ul>
048: *
049: * @author Alef Arendsen
050: * @author Juergen Hoeller
051: * @author Rob Harrop
052: * @since 16.07.2003
053: */
054: public class AntPathMatcher implements PathMatcher {
055:
056: /** Default path separator: "/" */
057: public static final String DEFAULT_PATH_SEPARATOR = "/";
058:
059: private String pathSeparator = DEFAULT_PATH_SEPARATOR;
060:
061: /**
062: * Set the path separator to use for pattern parsing.
063: * Default is "/", as in Ant.
064: */
065: public void setPathSeparator(String pathSeparator) {
066: this .pathSeparator = (pathSeparator != null ? pathSeparator
067: : DEFAULT_PATH_SEPARATOR);
068: }
069:
070: public boolean isPattern(String path) {
071: return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
072: }
073:
074: public boolean match(String pattern, String path) {
075: return doMatch(pattern, path, true);
076: }
077:
078: public boolean matchStart(String pattern, String path) {
079: return doMatch(pattern, path, false);
080: }
081:
082: /**
083: * Actually match the given <code>path</code> against the given <code>pattern</code>.
084: * @param pattern the pattern to match against
085: * @param path the path String to test
086: * @param fullMatch whether a full pattern match is required
087: * (else a pattern match as far as the given base path goes is sufficient)
088: * @return <code>true</code> if the supplied <code>path</code> matched,
089: * <code>false</code> if it didn't
090: */
091: protected boolean doMatch(String pattern, String path,
092: boolean fullMatch) {
093: if (path.startsWith(this .pathSeparator) != pattern
094: .startsWith(this .pathSeparator)) {
095: return false;
096: }
097:
098: String[] pattDirs = StringUtils.tokenizeToStringArray(pattern,
099: this .pathSeparator);
100: String[] pathDirs = StringUtils.tokenizeToStringArray(path,
101: this .pathSeparator);
102:
103: int pattIdxStart = 0;
104: int pattIdxEnd = pattDirs.length - 1;
105: int pathIdxStart = 0;
106: int pathIdxEnd = pathDirs.length - 1;
107:
108: // Match all elements up to the first **
109: while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
110: String patDir = pattDirs[pattIdxStart];
111: if ("**".equals(patDir)) {
112: break;
113: }
114: if (!matchStrings(patDir, pathDirs[pathIdxStart])) {
115: return false;
116: }
117: pattIdxStart++;
118: pathIdxStart++;
119: }
120:
121: if (pathIdxStart > pathIdxEnd) {
122: // Path is exhausted, only match if rest of pattern is * or **'s
123: if (pattIdxStart > pattIdxEnd) {
124: return (pattern.endsWith(this .pathSeparator) ? path
125: .endsWith(this .pathSeparator) : !path
126: .endsWith(this .pathSeparator));
127: }
128: if (!fullMatch) {
129: return true;
130: }
131: if (pattIdxStart == pattIdxEnd
132: && pattDirs[pattIdxStart].equals("*")
133: && path.endsWith(this .pathSeparator)) {
134: return true;
135: }
136: for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
137: if (!pattDirs[i].equals("**")) {
138: return false;
139: }
140: }
141: return true;
142: } else if (pattIdxStart > pattIdxEnd) {
143: // String not exhausted, but pattern is. Failure.
144: return false;
145: } else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
146: // Path start definitely matches due to "**" part in pattern.
147: return true;
148: }
149:
150: // up to last '**'
151: while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
152: String patDir = pattDirs[pattIdxEnd];
153: if (patDir.equals("**")) {
154: break;
155: }
156: if (!matchStrings(patDir, pathDirs[pathIdxEnd])) {
157: return false;
158: }
159: pattIdxEnd--;
160: pathIdxEnd--;
161: }
162: if (pathIdxStart > pathIdxEnd) {
163: // String is exhausted
164: for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
165: if (!pattDirs[i].equals("**")) {
166: return false;
167: }
168: }
169: return true;
170: }
171:
172: while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
173: int patIdxTmp = -1;
174: for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
175: if (pattDirs[i].equals("**")) {
176: patIdxTmp = i;
177: break;
178: }
179: }
180: if (patIdxTmp == pattIdxStart + 1) {
181: // '**/**' situation, so skip one
182: pattIdxStart++;
183: continue;
184: }
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);
189: int foundIdx = -1;
190:
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
194: + 1];
195: String subStr = (String) pathDirs[pathIdxStart + i
196: + j];
197: if (!matchStrings(subPat, subStr)) {
198: continue strLoop;
199: }
200: }
201: foundIdx = pathIdxStart + i;
202: break;
203: }
204:
205: if (foundIdx == -1) {
206: return false;
207: }
208:
209: pattIdxStart = patIdxTmp;
210: pathIdxStart = foundIdx + patLength;
211: }
212:
213: for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
214: if (!pattDirs[i].equals("**")) {
215: return false;
216: }
217: }
218:
219: return true;
220: }
221:
222: /**
223: * Tests whether or not a string matches against a pattern.
224: * The pattern may contain two special characters:<br>
225: * '*' means zero or more characters<br>
226: * '?' means one and only one character
227: * @param pattern pattern to match against.
228: * Must not be <code>null</code>.
229: * @param str string which must be matched against the pattern.
230: * Must not be <code>null</code>.
231: * @return <code>true</code> if the string matches against the
232: * pattern, or <code>false</code> otherwise.
233: */
234: private boolean matchStrings(String pattern, String str) {
235: char[] patArr = pattern.toCharArray();
236: char[] strArr = str.toCharArray();
237: int patIdxStart = 0;
238: int patIdxEnd = patArr.length - 1;
239: int strIdxStart = 0;
240: int strIdxEnd = strArr.length - 1;
241: char ch;
242:
243: boolean containsStar = false;
244: for (int i = 0; i < patArr.length; i++) {
245: if (patArr[i] == '*') {
246: containsStar = true;
247: break;
248: }
249: }
250:
251: if (!containsStar) {
252: // No '*'s, so we make a shortcut
253: if (patIdxEnd != strIdxEnd) {
254: return false; // Pattern and string do not have the same size
255: }
256: for (int i = 0; i <= patIdxEnd; i++) {
257: ch = patArr[i];
258: if (ch != '?') {
259: if (ch != strArr[i]) {
260: return false;// Character mismatch
261: }
262: }
263: }
264: return true; // String matches against pattern
265: }
266:
267: if (patIdxEnd == 0) {
268: return true; // Pattern contains only '*', which matches anything
269: }
270:
271: // Process characters before first star
272: while ((ch = patArr[patIdxStart]) != '*'
273: && strIdxStart <= strIdxEnd) {
274: if (ch != '?') {
275: if (ch != strArr[strIdxStart]) {
276: return false;// Character mismatch
277: }
278: }
279: patIdxStart++;
280: strIdxStart++;
281: }
282: if (strIdxStart > strIdxEnd) {
283: // All characters in the string are used. Check if only '*'s are
284: // left in the pattern. If so, we succeeded. Otherwise failure.
285: for (int i = patIdxStart; i <= patIdxEnd; i++) {
286: if (patArr[i] != '*') {
287: return false;
288: }
289: }
290: return true;
291: }
292:
293: // Process characters after last star
294: while ((ch = patArr[patIdxEnd]) != '*'
295: && strIdxStart <= strIdxEnd) {
296: if (ch != '?') {
297: if (ch != strArr[strIdxEnd]) {
298: return false;// Character mismatch
299: }
300: }
301: patIdxEnd--;
302: strIdxEnd--;
303: }
304: if (strIdxStart > strIdxEnd) {
305: // All characters in the string are used. Check if only '*'s are
306: // left in the pattern. If so, we succeeded. Otherwise failure.
307: for (int i = patIdxStart; i <= patIdxEnd; i++) {
308: if (patArr[i] != '*') {
309: return false;
310: }
311: }
312: return true;
313: }
314:
315: // process pattern between stars. padIdxStart and patIdxEnd point
316: // always to a '*'.
317: while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
318: int patIdxTmp = -1;
319: for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
320: if (patArr[i] == '*') {
321: patIdxTmp = i;
322: break;
323: }
324: }
325: if (patIdxTmp == patIdxStart + 1) {
326: // Two stars next to each other, skip the first one.
327: patIdxStart++;
328: continue;
329: }
330: // Find the pattern between padIdxStart & padIdxTmp in str between
331: // strIdxStart & strIdxEnd
332: int patLength = (patIdxTmp - patIdxStart - 1);
333: int strLength = (strIdxEnd - strIdxStart + 1);
334: int foundIdx = -1;
335: strLoop: for (int i = 0; i <= strLength - patLength; i++) {
336: for (int j = 0; j < patLength; j++) {
337: ch = patArr[patIdxStart + j + 1];
338: if (ch != '?') {
339: if (ch != strArr[strIdxStart + i + j]) {
340: continue strLoop;
341: }
342: }
343: }
344:
345: foundIdx = strIdxStart + i;
346: break;
347: }
348:
349: if (foundIdx == -1) {
350: return false;
351: }
352:
353: patIdxStart = patIdxTmp;
354: strIdxStart = foundIdx + patLength;
355: }
356:
357: // All characters in the string are used. Check if only '*'s are left
358: // in the pattern. If so, we succeeded. Otherwise failure.
359: for (int i = patIdxStart; i <= patIdxEnd; i++) {
360: if (patArr[i] != '*') {
361: return false;
362: }
363: }
364:
365: return true;
366: }
367:
368: /**
369: * Given a pattern and a full path, determine the pattern-mapped part.
370: * <p>For example:
371: * <ul>
372: * <li>'<code>/docs/cvs/commit.html</code>' and '<code>/docs/cvs/commit.html</code> -> ''</li>
373: * <li>'<code>/docs/*</code>' and '<code>/docs/cvs/commit</code> -> '<code>cvs/commit</code>'</li>
374: * <li>'<code>/docs/cvs/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>commit.html</code>'</li>
375: * <li>'<code>/docs/**</code>' and '<code>/docs/cvs/commit</code> -> '<code>cvs/commit</code>'</li>
376: * <li>'<code>/docs/**\/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>cvs/commit.html</code>'</li>
377: * <li>'<code>/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>docs/cvs/commit.html</code>'</li>
378: * <li>'<code>*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>/docs/cvs/commit.html</code>'</li>
379: * <li>'<code>*</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>/docs/cvs/commit.html</code>'</li>
380: * </ul>
381: * <p>Assumes that {@link #match} returns <code>true</code> for '<code>pattern</code>'
382: * and '<code>path</code>', but does <strong>not</strong> enforce this.
383: */
384: public String extractPathWithinPattern(String pattern, String path) {
385: String[] patternParts = StringUtils.tokenizeToStringArray(
386: pattern, this .pathSeparator);
387: String[] pathParts = StringUtils.tokenizeToStringArray(path,
388: this .pathSeparator);
389:
390: StringBuffer buffer = new StringBuffer();
391:
392: // Add any path parts that have a wildcarded pattern part.
393: int puts = 0;
394: for (int i = 0; i < patternParts.length; i++) {
395: String patternPart = patternParts[i];
396: if ((patternPart.indexOf('*') > -1 || patternPart
397: .indexOf('?') > -1)
398: && pathParts.length >= i + 1) {
399: if (puts > 0
400: || (i == 0 && !pattern
401: .startsWith(this .pathSeparator))) {
402: buffer.append(this .pathSeparator);
403: }
404: buffer.append(pathParts[i]);
405: puts++;
406: }
407: }
408:
409: // Append any trailing path parts.
410: for (int i = patternParts.length; i < pathParts.length; i++) {
411: if (puts > 0 || i > 0) {
412: buffer.append(this.pathSeparator);
413: }
414: buffer.append(pathParts[i]);
415: }
416:
417: return buffer.toString();
418: }
419:
420: }
|