View Javadoc

1   /**
2    * Copyright 2008-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community 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
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
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.
15   */
16  package org.codehaus.mojo.wagon.shared;
17  
18  /*
19   * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
20   * agreements. See the NOTICE file distributed with this work for additional information regarding
21   * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
22   * "License"); you may not use this file except in compliance with the License. You may obtain a
23   * copy of the License at
24   *
25   * http://www.apache.org/licenses/LICENSE-2.0
26   *
27   * Unless required by applicable law or agreed to in writing, software distributed under the License
28   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
29   * or implied. See the License for the specific language governing permissions and limitations under
30   * the License.
31   */
32  
33  import java.util.StringTokenizer;
34  import java.util.Vector;
35  
36  /**
37   * A copy of plexus-util's SelectorUtils to deal with unix file separator only.
38   */
39  public final class SelectorUtils {
40  
41      /**
42       * Tests whether or not a given path matches the start of a given pattern up to the first "**".
43       * <p>
44       * This is not a general purpose test and should only be used if you can live with false positives. For example,
45       * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>.
46       *
47       * @param pattern
48       *            The pattern to match against. Must not be <code>null</code>.
49       * @param str
50       *            The path to match, as a String. Must not be <code>null</code>.
51       *
52       * @return whether or not a given path matches the start of a given pattern up to the first "**".
53       */
54      public static boolean matchPatternStart(String pattern, String str) {
55          return matchPatternStart(pattern, str, true);
56      }
57  
58      /**
59       * Tests whether or not a given path matches the start of a given pattern up to the first "**".
60       * <p>
61       * This is not a general purpose test and should only be used if you can live with false positives. For example,
62       * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>.
63       *
64       * @param pattern
65       *            The pattern to match against. Must not be <code>null</code>.
66       * @param str
67       *            The path to match, as a String. Must not be <code>null</code>.
68       * @param isCaseSensitive
69       *            Whether or not matching should be performed case sensitively.
70       *
71       * @return whether or not a given path matches the start of a given pattern up to the first "**".
72       */
73      public static boolean matchPatternStart(String pattern, String str, boolean isCaseSensitive) {
74          // When str starts with a separator, pattern has to start with a
75          // separator.
76          // When pattern starts with a separator, str has to start with a
77          // separator.
78          if (str.startsWith("/") != pattern.startsWith("/")) {
79              return false;
80          }
81  
82          Vector<?> patDirs = tokenizePath(pattern);
83          Vector<?> strDirs = tokenizePath(str);
84  
85          int patIdxStart = 0;
86          int patIdxEnd = patDirs.size() - 1;
87          int strIdxStart = 0;
88          int strIdxEnd = strDirs.size() - 1;
89  
90          // up to first '**'
91          while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
92              String patDir = (String) patDirs.elementAt(patIdxStart);
93              if (patDir.equals("**")) {
94                  break;
95              }
96              if (!match(patDir, (String) strDirs.elementAt(strIdxStart), isCaseSensitive)) {
97                  return false;
98              }
99              patIdxStart++;
100             strIdxStart++;
101         }
102 
103         if (strIdxStart > strIdxEnd) {
104             // String is exhausted
105             return true;
106         } else if (patIdxStart > patIdxEnd) {
107             // String not exhausted, but pattern is. Failure.
108             return false;
109         } else {
110             // pattern now holds ** while string is not exhausted
111             // this will generate false positives but we can live with that.
112             return true;
113         }
114     }
115 
116     /**
117      * Tests whether or not a given path matches a given pattern.
118      *
119      * @param pattern
120      *            The pattern to match against. Must not be <code>null</code>.
121      * @param str
122      *            The path to match, as a String. Must not be <code>null</code>.
123      *
124      * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise.
125      */
126     public static boolean matchPath(String pattern, String str) {
127         return matchPath(pattern, str, true);
128     }
129 
130     /**
131      * Tests whether or not a given path matches a given pattern.
132      *
133      * @param pattern
134      *            The pattern to match against. Must not be <code>null</code>.
135      * @param str
136      *            The path to match, as a String. Must not be <code>null</code>.
137      * @param isCaseSensitive
138      *            Whether or not matching should be performed case sensitively.
139      *
140      * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise.
141      */
142     public static boolean matchPath(String pattern, String str, boolean isCaseSensitive) {
143         // When str starts with a separator, pattern has to start with a
144         // separator.
145         // When pattern starts with a separator, str has to start with a
146         // separator.
147         if (str.startsWith("/") != pattern.startsWith("/")) {
148             return false;
149         }
150 
151         Vector<?> patDirs = tokenizePath(pattern);
152         Vector<?> strDirs = tokenizePath(str);
153 
154         int patIdxStart = 0;
155         int patIdxEnd = patDirs.size() - 1;
156         int strIdxStart = 0;
157         int strIdxEnd = strDirs.size() - 1;
158 
159         // up to first '**'
160         while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
161             String patDir = (String) patDirs.elementAt(patIdxStart);
162             if (patDir.equals("**")) {
163                 break;
164             }
165             if (!match(patDir, (String) strDirs.elementAt(strIdxStart), isCaseSensitive)) {
166                 patDirs = null;
167                 strDirs = null;
168                 return false;
169             }
170             patIdxStart++;
171             strIdxStart++;
172         }
173         if (strIdxStart > strIdxEnd) {
174             // String is exhausted
175             for (int i = patIdxStart; i <= patIdxEnd; i++) {
176                 if (!patDirs.elementAt(i).equals("**")) {
177                     patDirs = null;
178                     strDirs = null;
179                     return false;
180                 }
181             }
182             return true;
183         } else {
184             if (patIdxStart > patIdxEnd) {
185                 // String not exhausted, but pattern is. Failure.
186                 patDirs = null;
187                 strDirs = null;
188                 return false;
189             }
190         }
191 
192         // up to last '**'
193         while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
194             String patDir = (String) patDirs.elementAt(patIdxEnd);
195             if (patDir.equals("**")) {
196                 break;
197             }
198             if (!match(patDir, (String) strDirs.elementAt(strIdxEnd), isCaseSensitive)) {
199                 patDirs = null;
200                 strDirs = null;
201                 return false;
202             }
203             patIdxEnd--;
204             strIdxEnd--;
205         }
206         if (strIdxStart > strIdxEnd) {
207             // String is exhausted
208             for (int i = patIdxStart; i <= patIdxEnd; i++) {
209                 if (!patDirs.elementAt(i).equals("**")) {
210                     patDirs = null;
211                     strDirs = null;
212                     return false;
213                 }
214             }
215             return true;
216         }
217 
218         while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
219             int patIdxTmp = -1;
220             for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
221                 if (patDirs.elementAt(i).equals("**")) {
222                     patIdxTmp = i;
223                     break;
224                 }
225             }
226             if (patIdxTmp == patIdxStart + 1) {
227                 // '**/**' situation, so skip one
228                 patIdxStart++;
229                 continue;
230             }
231             // Find the pattern between padIdxStart & padIdxTmp in str between
232             // strIdxStart & strIdxEnd
233             int patLength = (patIdxTmp - patIdxStart - 1);
234             int strLength = (strIdxEnd - strIdxStart + 1);
235             int foundIdx = -1;
236             strLoop: for (int i = 0; i <= strLength - patLength; i++) {
237                 for (int j = 0; j < patLength; j++) {
238                     String subPat = (String) patDirs.elementAt(patIdxStart + j + 1);
239                     String subStr = (String) strDirs.elementAt(strIdxStart + i + j);
240                     if (!match(subPat, subStr, isCaseSensitive)) {
241                         continue strLoop;
242                     }
243                 }
244 
245                 foundIdx = strIdxStart + i;
246                 break;
247             }
248 
249             if (foundIdx == -1) {
250                 patDirs = null;
251                 strDirs = null;
252                 return false;
253             }
254 
255             patIdxStart = patIdxTmp;
256             strIdxStart = foundIdx + patLength;
257         }
258 
259         for (int i = patIdxStart; i <= patIdxEnd; i++) {
260             if (!patDirs.elementAt(i).equals("**")) {
261                 patDirs = null;
262                 strDirs = null;
263                 return false;
264             }
265         }
266 
267         return true;
268     }
269 
270     /**
271      * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:<br>
272      * '*' means zero or more characters<br>
273      * '?' means one and only one character
274      *
275      * @param pattern
276      *            The pattern to match against. Must not be <code>null</code>.
277      * @param str
278      *            The string which must be matched against the pattern. Must not be <code>null</code>.
279      *
280      * @return <code>true</code> if the string matches against the pattern, or <code>false</code> otherwise.
281      */
282     public static boolean match(String pattern, String str) {
283         return match(pattern, str, true);
284     }
285 
286     /**
287      * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:<br>
288      * '*' means zero or more characters<br>
289      * '?' means one and only one character
290      *
291      * @param pattern
292      *            The pattern to match against. Must not be <code>null</code>.
293      * @param str
294      *            The string which must be matched against the pattern. Must not be <code>null</code>.
295      * @param isCaseSensitive
296      *            Whether or not matching should be performed case sensitively.
297      *
298      *
299      * @return <code>true</code> if the string matches against the pattern, or <code>false</code> otherwise.
300      */
301     public static boolean match(String pattern, String str, boolean isCaseSensitive) {
302         char[] patArr = pattern.toCharArray();
303         char[] strArr = str.toCharArray();
304         int patIdxStart = 0;
305         int patIdxEnd = patArr.length - 1;
306         int strIdxStart = 0;
307         int strIdxEnd = strArr.length - 1;
308         char ch;
309 
310         boolean containsStar = false;
311         for (int i = 0; i < patArr.length; i++) {
312             if (patArr[i] == '*') {
313                 containsStar = true;
314                 break;
315             }
316         }
317 
318         if (!containsStar) {
319             // No '*'s, so we make a shortcut
320             if (patIdxEnd != strIdxEnd) {
321                 return false; // Pattern and string do not have the same size
322             }
323             for (int i = 0; i <= patIdxEnd; i++) {
324                 ch = patArr[i];
325                 if (ch != '?' && !equals(ch, strArr[i], isCaseSensitive)) {
326                     return false; // Character mismatch
327                 }
328             }
329             return true; // String matches against pattern
330         }
331 
332         if (patIdxEnd == 0) {
333             return true; // Pattern contains only '*', which matches anything
334         }
335 
336         // Process characters before first star
337         while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
338             if (ch != '?' && !equals(ch, strArr[strIdxStart], isCaseSensitive)) {
339                 return false; // Character mismatch
340             }
341             patIdxStart++;
342             strIdxStart++;
343         }
344         if (strIdxStart > strIdxEnd) {
345             // All characters in the string are used. Check if only '*'s are
346             // left in the pattern. If so, we succeeded. Otherwise failure.
347             for (int i = patIdxStart; i <= patIdxEnd; i++) {
348                 if (patArr[i] != '*') {
349                     return false;
350                 }
351             }
352             return true;
353         }
354 
355         // Process characters after last star
356         while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
357             if (ch != '?' && !equals(ch, strArr[strIdxEnd], isCaseSensitive)) {
358                 return false; // Character mismatch
359             }
360             patIdxEnd--;
361             strIdxEnd--;
362         }
363         if (strIdxStart > strIdxEnd) {
364             // All characters in the string are used. Check if only '*'s are
365             // left in the pattern. If so, we succeeded. Otherwise failure.
366             for (int i = patIdxStart; i <= patIdxEnd; i++) {
367                 if (patArr[i] != '*') {
368                     return false;
369                 }
370             }
371             return true;
372         }
373 
374         // process pattern between stars. padIdxStart and patIdxEnd point
375         // always to a '*'.
376         while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
377             int patIdxTmp = -1;
378             for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
379                 if (patArr[i] == '*') {
380                     patIdxTmp = i;
381                     break;
382                 }
383             }
384             if (patIdxTmp == patIdxStart + 1) {
385                 // Two stars next to each other, skip the first one.
386                 patIdxStart++;
387                 continue;
388             }
389             // Find the pattern between padIdxStart & padIdxTmp in str between
390             // strIdxStart & strIdxEnd
391             int patLength = (patIdxTmp - patIdxStart - 1);
392             int strLength = (strIdxEnd - strIdxStart + 1);
393             int foundIdx = -1;
394             strLoop: for (int i = 0; i <= strLength - patLength; i++) {
395                 for (int j = 0; j < patLength; j++) {
396                     ch = patArr[patIdxStart + j + 1];
397                     if (ch != '?' && !equals(ch, strArr[strIdxStart + i + j], isCaseSensitive)) {
398                         continue strLoop;
399                     }
400                 }
401 
402                 foundIdx = strIdxStart + i;
403                 break;
404             }
405 
406             if (foundIdx == -1) {
407                 return false;
408             }
409 
410             patIdxStart = patIdxTmp;
411             strIdxStart = foundIdx + patLength;
412         }
413 
414         // All characters in the string are used. Check if only '*'s are left
415         // in the pattern. If so, we succeeded. Otherwise failure.
416         for (int i = patIdxStart; i <= patIdxEnd; i++) {
417             if (patArr[i] != '*') {
418                 return false;
419             }
420         }
421         return true;
422     }
423 
424     /**
425      * Tests whether two characters are equal.
426      */
427     private static boolean equals(char c1, char c2, boolean isCaseSensitive) {
428         if (c1 == c2) {
429             return true;
430         }
431         if (!isCaseSensitive) {
432             // NOTE: Try both upper case and lower case as done by String.equalsIgnoreCase()
433             if (Character.toUpperCase(c1) == Character.toUpperCase(c2)
434                     || Character.toLowerCase(c1) == Character.toLowerCase(c2)) {
435                 return true;
436             }
437         }
438         return false;
439     }
440 
441     /**
442      * Breaks a path up into a Vector of path elements, tokenizing on <code>File.separator</code>.
443      *
444      * @param path
445      *            Path to tokenize. Must not be <code>null</code>.
446      *
447      * @return a Vector of path elements from the tokenized path
448      */
449     public static Vector<String> tokenizePath(String path) {
450         Vector<String> ret = new Vector<String>();
451         StringTokenizer st = new StringTokenizer(path, "/");
452         while (st.hasMoreTokens()) {
453             ret.addElement(st.nextToken());
454         }
455         return ret;
456     }
457 
458 }