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