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 str) {
071: return (str.indexOf('*') != -1 || str.indexOf('?') != -1);
072: }
073:
074: public boolean match(String pattern, String str) {
075: if (str.startsWith(this .pathSeparator) != pattern
076: .startsWith(this .pathSeparator)) {
077: return false;
078: }
079:
080: String[] patDirs = StringUtils.tokenizeToStringArray(pattern,
081: this .pathSeparator);
082: String[] strDirs = StringUtils.tokenizeToStringArray(str,
083: this .pathSeparator);
084:
085: int patIdxStart = 0;
086: int patIdxEnd = patDirs.length - 1;
087: int strIdxStart = 0;
088: int strIdxEnd = strDirs.length - 1;
089:
090: // Match all elements up to the first **
091: while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
092: String patDir = (String) patDirs[patIdxStart];
093: if (patDir.equals("**")) {
094: break;
095: }
096: if (!matchStrings(patDir, (String) strDirs[strIdxStart])) {
097: return false;
098: }
099: patIdxStart++;
100: strIdxStart++;
101: }
102:
103: if (strIdxStart > strIdxEnd) {
104: // String is exhausted, only match if rest of pattern is * or **'s
105: if (patIdxStart == patIdxEnd
106: && patDirs[patIdxStart].equals("*")
107: && str.endsWith(this .pathSeparator)) {
108: return true;
109: }
110: for (int i = patIdxStart; i <= patIdxEnd; i++) {
111: if (!patDirs[i].equals("**")) {
112: return false;
113: }
114: }
115: return true;
116: } else {
117: if (patIdxStart > patIdxEnd) {
118: // String not exhausted, but pattern is. Failure.
119: return false;
120: }
121: }
122:
123: // up to last '**'
124: while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
125: String patDir = (String) patDirs[patIdxEnd];
126: if (patDir.equals("**")) {
127: break;
128: }
129: if (!matchStrings(patDir, (String) strDirs[strIdxEnd])) {
130: return false;
131: }
132: patIdxEnd--;
133: strIdxEnd--;
134: }
135: if (strIdxStart > strIdxEnd) {
136: // String is exhausted
137: for (int i = patIdxStart; i <= patIdxEnd; i++) {
138: if (!patDirs[i].equals("**")) {
139: return false;
140: }
141: }
142: return true;
143: }
144:
145: while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
146: int patIdxTmp = -1;
147: for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
148: if (patDirs[i].equals("**")) {
149: patIdxTmp = i;
150: break;
151: }
152: }
153: if (patIdxTmp == patIdxStart + 1) {
154: // '**/**' situation, so skip one
155: patIdxStart++;
156: continue;
157: }
158: // Find the pattern between padIdxStart & padIdxTmp in str between
159: // strIdxStart & strIdxEnd
160: int patLength = (patIdxTmp - patIdxStart - 1);
161: int strLength = (strIdxEnd - strIdxStart + 1);
162: int foundIdx = -1;
163: strLoop: for (int i = 0; i <= strLength - patLength; i++) {
164: for (int j = 0; j < patLength; j++) {
165: String subPat = (String) patDirs[patIdxStart + j
166: + 1];
167: String subStr = (String) strDirs[strIdxStart + i
168: + j];
169: if (!matchStrings(subPat, subStr)) {
170: continue strLoop;
171: }
172: }
173:
174: foundIdx = strIdxStart + i;
175: break;
176: }
177:
178: if (foundIdx == -1) {
179: return false;
180: }
181:
182: patIdxStart = patIdxTmp;
183: strIdxStart = foundIdx + patLength;
184: }
185:
186: for (int i = patIdxStart; i <= patIdxEnd; i++) {
187: if (!patDirs[i].equals("**")) {
188: return false;
189: }
190: }
191:
192: return true;
193: }
194:
195: /**
196: * Tests whether or not a string matches against a pattern.
197: * The pattern may contain two special characters:<br>
198: * '*' means zero or more characters<br>
199: * '?' means one and only one character
200: * @param pattern pattern to match against.
201: * Must not be <code>null</code>.
202: * @param str string which must be matched against the pattern.
203: * Must not be <code>null</code>.
204: * @return <code>true</code> if the string matches against the
205: * pattern, or <code>false</code> otherwise.
206: */
207: private boolean matchStrings(String pattern, String str) {
208: char[] patArr = pattern.toCharArray();
209: char[] strArr = str.toCharArray();
210: int patIdxStart = 0;
211: int patIdxEnd = patArr.length - 1;
212: int strIdxStart = 0;
213: int strIdxEnd = strArr.length - 1;
214: char ch;
215:
216: boolean containsStar = false;
217: for (int i = 0; i < patArr.length; i++) {
218: if (patArr[i] == '*') {
219: containsStar = true;
220: break;
221: }
222: }
223:
224: if (!containsStar) {
225: // No '*'s, so we make a shortcut
226: if (patIdxEnd != strIdxEnd) {
227: return false; // Pattern and string do not have the same size
228: }
229: for (int i = 0; i <= patIdxEnd; i++) {
230: ch = patArr[i];
231: if (ch != '?') {
232: if (ch != strArr[i]) {
233: return false;// Character mismatch
234: }
235: }
236: }
237: return true; // String matches against pattern
238: }
239:
240: if (patIdxEnd == 0) {
241: return true; // Pattern contains only '*', which matches anything
242: }
243:
244: // Process characters before first star
245: while ((ch = patArr[patIdxStart]) != '*'
246: && strIdxStart <= strIdxEnd) {
247: if (ch != '?') {
248: if (ch != strArr[strIdxStart]) {
249: return false;// Character mismatch
250: }
251: }
252: patIdxStart++;
253: strIdxStart++;
254: }
255: if (strIdxStart > strIdxEnd) {
256: // All characters in the string are used. Check if only '*'s are
257: // left in the pattern. If so, we succeeded. Otherwise failure.
258: for (int i = patIdxStart; i <= patIdxEnd; i++) {
259: if (patArr[i] != '*') {
260: return false;
261: }
262: }
263: return true;
264: }
265:
266: // Process characters after last star
267: while ((ch = patArr[patIdxEnd]) != '*'
268: && strIdxStart <= strIdxEnd) {
269: if (ch != '?') {
270: if (ch != strArr[strIdxEnd]) {
271: return false;// Character mismatch
272: }
273: }
274: patIdxEnd--;
275: strIdxEnd--;
276: }
277: if (strIdxStart > strIdxEnd) {
278: // All characters in the string are used. Check if only '*'s are
279: // left in the pattern. If so, we succeeded. Otherwise failure.
280: for (int i = patIdxStart; i <= patIdxEnd; i++) {
281: if (patArr[i] != '*') {
282: return false;
283: }
284: }
285: return true;
286: }
287:
288: // process pattern between stars. padIdxStart and patIdxEnd point
289: // always to a '*'.
290: while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
291: int patIdxTmp = -1;
292: for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
293: if (patArr[i] == '*') {
294: patIdxTmp = i;
295: break;
296: }
297: }
298: if (patIdxTmp == patIdxStart + 1) {
299: // Two stars next to each other, skip the first one.
300: patIdxStart++;
301: continue;
302: }
303: // Find the pattern between padIdxStart & padIdxTmp in str between
304: // strIdxStart & strIdxEnd
305: int patLength = (patIdxTmp - patIdxStart - 1);
306: int strLength = (strIdxEnd - strIdxStart + 1);
307: int foundIdx = -1;
308: strLoop: for (int i = 0; i <= strLength - patLength; i++) {
309: for (int j = 0; j < patLength; j++) {
310: ch = patArr[patIdxStart + j + 1];
311: if (ch != '?') {
312: if (ch != strArr[strIdxStart + i + j]) {
313: continue strLoop;
314: }
315: }
316: }
317:
318: foundIdx = strIdxStart + i;
319: break;
320: }
321:
322: if (foundIdx == -1) {
323: return false;
324: }
325:
326: patIdxStart = patIdxTmp;
327: strIdxStart = foundIdx + patLength;
328: }
329:
330: // All characters in the string are used. Check if only '*'s are left
331: // in the pattern. If so, we succeeded. Otherwise failure.
332: for (int i = patIdxStart; i <= patIdxEnd; i++) {
333: if (patArr[i] != '*') {
334: return false;
335: }
336: }
337:
338: return true;
339: }
340:
341: /**
342: * Given a pattern and a full path, determine the pattern-mapped part.
343: * <p>For example:
344: * <ul>
345: * <li>'<code>/docs/cvs/commit.html</code>' and '<code>/docs/cvs/commit.html</code> -> ''</li>
346: * <li>'<code>/docs/*</code>' and '<code>/docs/cvs/commit</code> -> '<code>cvs/commit</code>'</li>
347: * <li>'<code>/docs/cvs/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>commit.html</code>'</li>
348: * <li>'<code>/docs/**</code>' and '<code>/docs/cvs/commit</code> -> '<code>cvs/commit</code>'</li>
349: * <li>'<code>/docs/**\/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>cvs/commit.html</code>'</li>
350: * <li>'<code>/*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>docs/cvs/commit.html</code>'</li>
351: * <li>'<code>*.html</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>/docs/cvs/commit.html</code>'</li>
352: * <li>'<code>*</code>' and '<code>/docs/cvs/commit.html</code> -> '<code>/docs/cvs/commit.html</code>'</li>
353: * </ul>
354: * <p>Assumes that {@link #match} returns <code>true</code> for '<code>pattern</code>'
355: * and '<code>path</code>', but does <strong>not</strong> enforce this.
356: */
357: public String extractPathWithinPattern(String pattern, String path) {
358: String[] patternParts = StringUtils.tokenizeToStringArray(
359: pattern, this .pathSeparator);
360: String[] pathParts = StringUtils.tokenizeToStringArray(path,
361: this .pathSeparator);
362:
363: StringBuffer buffer = new StringBuffer();
364:
365: // Add any path parts that have a wildcarded pattern part.
366: int puts = 0;
367: for (int i = 0; i < patternParts.length; i++) {
368: String patternPart = patternParts[i];
369: if ((patternPart.indexOf('*') > -1 || patternPart
370: .indexOf('?') > -1)
371: && pathParts.length >= i + 1) {
372: if (puts > 0
373: || (i == 0 && !pattern
374: .startsWith(this .pathSeparator))) {
375: buffer.append(this .pathSeparator);
376: }
377: buffer.append(pathParts[i]);
378: puts++;
379: }
380: }
381:
382: // Append any trailing path parts.
383: for (int i = patternParts.length; i < pathParts.length; i++) {
384: if (puts > 0 || i > 0) {
385: buffer.append(this.pathSeparator);
386: }
387: buffer.append(pathParts[i]);
388: }
389:
390: return buffer.toString();
391: }
392:
393: }
|