View Javadoc

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