View Javadoc

1   /*
2    * The Apache Software License, Version 1.1
3    *
4    * Copyright (c) 2002-2003 The Apache Software Foundation.  All rights
5    * reserved.
6    *
7    * Redistribution and use in source and binary forms, with or without
8    * modification, are permitted provided that the following conditions
9    * are met:
10   *
11   * 1. Redistributions of source code must retain the above copyright
12   *    notice, this list of conditions and the following disclaimer.
13   *
14   * 2. Redistributions in binary form must reproduce the above copyright
15   *    notice, this list of conditions and the following disclaimer in
16   *    the documentation and/or other materials provided with the
17   *    distribution.
18   *
19   * 3. The end-user documentation included with the redistribution, if
20   *    any, must include the following acknowlegement:
21   *       "This product includes software developed by the
22   *        Apache Software Foundation (http://www.apache.org/)."
23   *    Alternately, this acknowlegement may appear in the software itself,
24   *    if and wherever such third-party acknowlegements normally appear.
25   *
26   * 4. The names "Ant" and "Apache Software
27   *    Foundation" must not be used to endorse or promote products derived
28   *    from this software without prior written permission. For written
29   *    permission, please contact apache@apache.org.
30   *
31   * 5. Products derived from this software may not be called "Apache"
32   *    nor may "Apache" appear in their names without prior written
33   *    permission of the Apache Group.
34   *
35   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46   * SUCH DAMAGE.
47   * ====================================================================
48   *
49   * This software consists of voluntary contributions made by many
50   * individuals on behalf of the Apache Software Foundation.  For more
51   * information on the Apache Software Foundation, please see
52   * <http://www.apache.org/>.
53   */
54  
55  package org.apache.maven.it.util;
56  
57  import java.io.File;
58  import java.util.StringTokenizer;
59  import java.util.Vector;
60  
61  /**
62   * <p>This is a utility class used by selectors and DirectoryScanner. The
63   * functionality more properly belongs just to selectors, but unfortunately
64   * DirectoryScanner exposed these as protected methods. Thus we have to
65   * support any subclasses of DirectoryScanner that may access these methods.
66   * </p>
67   * <p>This is a Singleton.</p>
68   *
69   * @author Arnout J. Kuiper
70   * <a href="mailto:ajkuiper@wxs.nl">ajkuiper@wxs.nl</a>
71   * @author Magesh Umasankar
72   * @author <a href="mailto:bruce@callenish.com">Bruce Atherton</a>
73   * @since 1.5
74   */
75  public final class SelectorUtils
76  {
77  
78      private static SelectorUtils instance = new SelectorUtils();
79  
80      /**
81       * Private Constructor
82       */
83      private SelectorUtils()
84      {
85      }
86  
87      /**
88       * Retrieves the manager of the Singleton.
89       */
90      public static SelectorUtils getInstance()
91      {
92          return instance;
93      }
94  
95      /**
96       * Tests whether or not a given path matches the start of a given
97       * pattern up to the first "**".
98       * <p>
99       * This is not a general purpose test and should only be used if you
100      * can live with false positives. For example, <code>pattern=**\a</code>
101      * and <code>str=b</code> will yield <code>true</code>.
102      *
103      * @param pattern The pattern to match against. Must not be
104      *                <code>null</code>.
105      * @param str     The path to match, as a String. Must not be
106      *                <code>null</code>.
107      *
108      * @return whether or not a given path matches the start of a given
109      * pattern up to the first "**".
110      */
111     public static boolean matchPatternStart( String pattern, String str )
112     {
113         return matchPatternStart( pattern, str, true );
114     }
115 
116     /**
117      * Tests whether or not a given path matches the start of a given
118      * pattern up to the first "**".
119      * <p>
120      * This is not a general purpose test and should only be used if you
121      * can live with false positives. For example, <code>pattern=**\a</code>
122      * and <code>str=b</code> will yield <code>true</code>.
123      *
124      * @param pattern The pattern to match against. Must not be
125      *                <code>null</code>.
126      * @param str     The path to match, as a String. Must not be
127      *                <code>null</code>.
128      * @param isCaseSensitive Whether or not matching should be performed
129      *                        case sensitively.
130      *
131      * @return whether or not a given path matches the start of a given
132      * pattern up to the first "**".
133      */
134     public static boolean matchPatternStart( String pattern, String str,
135                                              boolean isCaseSensitive )
136     {
137         // When str starts with a File.separator, pattern has to start with a
138         // File.separator.
139         // When pattern starts with a File.separator, str has to start with a
140         // File.separator.
141         if ( str.startsWith( File.separator ) !=
142             pattern.startsWith( File.separator ) )
143         {
144             return false;
145         }
146 
147         Vector patDirs = tokenizePath( pattern );
148         Vector strDirs = tokenizePath( str );
149 
150         int patIdxStart = 0;
151         int patIdxEnd = patDirs.size() - 1;
152         int strIdxStart = 0;
153         int strIdxEnd = strDirs.size() - 1;
154 
155         // up to first '**'
156         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
157         {
158             String patDir = (String) patDirs.elementAt( patIdxStart );
159             if ( patDir.equals( "**" ) )
160             {
161                 break;
162             }
163             if ( !match( patDir, (String) strDirs.elementAt( strIdxStart ),
164                          isCaseSensitive ) )
165             {
166                 return false;
167             }
168             patIdxStart++;
169             strIdxStart++;
170         }
171 
172         if ( strIdxStart > strIdxEnd )
173         {
174             // String is exhausted
175             return true;
176         }
177         else if ( patIdxStart > patIdxEnd )
178         {
179             // String not exhausted, but pattern is. Failure.
180             return false;
181         }
182         else
183         {
184             // pattern now holds ** while string is not exhausted
185             // this will generate false positives but we can live with that.
186             return true;
187         }
188     }
189 
190     /**
191      * Tests whether or not a given path matches a given pattern.
192      *
193      * @param pattern The pattern to match against. Must not be
194      *                <code>null</code>.
195      * @param str     The path to match, as a String. Must not be
196      *                <code>null</code>.
197      *
198      * @return <code>true</code> if the pattern matches against the string,
199      *         or <code>false</code> otherwise.
200      */
201     public static boolean matchPath( String pattern, String str )
202     {
203         return matchPath( pattern, str, true );
204     }
205 
206     /**
207      * Tests whether or not a given path matches a given pattern.
208      *
209      * @param pattern The pattern to match against. Must not be
210      *                <code>null</code>.
211      * @param str     The path to match, as a String. Must not be
212      *                <code>null</code>.
213      * @param isCaseSensitive Whether or not matching should be performed
214      *                        case sensitively.
215      *
216      * @return <code>true</code> if the pattern matches against the string,
217      *         or <code>false</code> otherwise.
218      */
219     public static boolean matchPath( String pattern, String str,
220                                      boolean isCaseSensitive )
221     {
222         // When str starts with a File.separator, pattern has to start with a
223         // File.separator.
224         // When pattern starts with a File.separator, str has to start with a
225         // File.separator.
226         if ( str.startsWith( File.separator ) !=
227             pattern.startsWith( File.separator ) )
228         {
229             return false;
230         }
231 
232         Vector patDirs = tokenizePath( pattern );
233         Vector strDirs = tokenizePath( str );
234 
235         int patIdxStart = 0;
236         int patIdxEnd = patDirs.size() - 1;
237         int strIdxStart = 0;
238         int strIdxEnd = strDirs.size() - 1;
239 
240         // up to first '**'
241         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
242         {
243             String patDir = (String) patDirs.elementAt( patIdxStart );
244             if ( patDir.equals( "**" ) )
245             {
246                 break;
247             }
248             if ( !match( patDir, (String) strDirs.elementAt( strIdxStart ),
249                          isCaseSensitive ) )
250             {
251                 patDirs = null;
252                 strDirs = null;
253                 return false;
254             }
255             patIdxStart++;
256             strIdxStart++;
257         }
258         if ( strIdxStart > strIdxEnd )
259         {
260             // String is exhausted
261             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
262             {
263                 if ( !patDirs.elementAt( i ).equals( "**" ) )
264                 {
265                     patDirs = null;
266                     strDirs = null;
267                     return false;
268                 }
269             }
270             return true;
271         }
272         else
273         {
274             if ( patIdxStart > patIdxEnd )
275             {
276                 // String not exhausted, but pattern is. Failure.
277                 patDirs = null;
278                 strDirs = null;
279                 return false;
280             }
281         }
282 
283         // up to last '**'
284         while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
285         {
286             String patDir = (String) patDirs.elementAt( patIdxEnd );
287             if ( patDir.equals( "**" ) )
288             {
289                 break;
290             }
291             if ( !match( patDir, (String) strDirs.elementAt( strIdxEnd ),
292                          isCaseSensitive ) )
293             {
294                 patDirs = null;
295                 strDirs = null;
296                 return false;
297             }
298             patIdxEnd--;
299             strIdxEnd--;
300         }
301         if ( strIdxStart > strIdxEnd )
302         {
303             // String is exhausted
304             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
305             {
306                 if ( !patDirs.elementAt( i ).equals( "**" ) )
307                 {
308                     patDirs = null;
309                     strDirs = null;
310                     return false;
311                 }
312             }
313             return true;
314         }
315 
316         while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
317         {
318             int patIdxTmp = -1;
319             for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
320             {
321                 if ( patDirs.elementAt( i ).equals( "**" ) )
322                 {
323                     patIdxTmp = i;
324                     break;
325                 }
326             }
327             if ( patIdxTmp == patIdxStart + 1 )
328             {
329                 // '**/**' situation, so skip one
330                 patIdxStart++;
331                 continue;
332             }
333             // Find the pattern between padIdxStart & padIdxTmp in str between
334             // strIdxStart & strIdxEnd
335             int patLength = ( patIdxTmp - patIdxStart - 1 );
336             int strLength = ( strIdxEnd - strIdxStart + 1 );
337             int foundIdx = -1;
338             strLoop:
339                         for ( int i = 0; i <= strLength - patLength; i++ )
340                         {
341                             for ( int j = 0; j < patLength; j++ )
342                             {
343                                 String subPat = (String) patDirs.elementAt( patIdxStart + j + 1 );
344                                 String subStr = (String) strDirs.elementAt( strIdxStart + i + j );
345                                 if ( !match( subPat, subStr, isCaseSensitive ) )
346                                 {
347                                     continue strLoop;
348                                 }
349                             }
350 
351                             foundIdx = strIdxStart + i;
352                             break;
353                         }
354 
355             if ( foundIdx == -1 )
356             {
357                 patDirs = null;
358                 strDirs = null;
359                 return false;
360             }
361 
362             patIdxStart = patIdxTmp;
363             strIdxStart = foundIdx + patLength;
364         }
365 
366         for ( int i = patIdxStart; i <= patIdxEnd; i++ )
367         {
368             if ( !patDirs.elementAt( i ).equals( "**" ) )
369             {
370                 patDirs = null;
371                 strDirs = null;
372                 return false;
373             }
374         }
375 
376         return true;
377     }
378 
379     /**
380      * Tests whether or not a string matches against a pattern.
381      * The pattern may contain two special characters:<br>
382      * '*' means zero or more characters<br>
383      * '?' means one and only one character
384      *
385      * @param pattern The pattern to match against.
386      *                Must not be <code>null</code>.
387      * @param str     The string which must be matched against the pattern.
388      *                Must not be <code>null</code>.
389      *
390      * @return <code>true</code> if the string matches against the pattern,
391      *         or <code>false</code> otherwise.
392      */
393     public static boolean match( String pattern, String str )
394     {
395         return match( pattern, str, true );
396     }
397 
398     /**
399      * Tests whether or not a string matches against a pattern.
400      * The pattern may contain two special characters:<br>
401      * '*' means zero or more characters<br>
402      * '?' means one and only one character
403      *
404      * @param pattern The pattern to match against.
405      *                Must not be <code>null</code>.
406      * @param str     The string which must be matched against the pattern.
407      *                Must not be <code>null</code>.
408      * @param isCaseSensitive Whether or not matching should be performed
409      *                        case sensitively.
410      *
411      *
412      * @return <code>true</code> if the string matches against the pattern,
413      *         or <code>false</code> otherwise.
414      */
415     public static boolean match( String pattern, String str,
416                                  boolean isCaseSensitive )
417     {
418         char[] patArr = pattern.toCharArray();
419         char[] strArr = str.toCharArray();
420         int patIdxStart = 0;
421         int patIdxEnd = patArr.length - 1;
422         int strIdxStart = 0;
423         int strIdxEnd = strArr.length - 1;
424         char ch;
425 
426         boolean containsStar = false;
427         for ( int i = 0; i < patArr.length; i++ )
428         {
429             if ( patArr[i] == '*' )
430             {
431                 containsStar = true;
432                 break;
433             }
434         }
435 
436         if ( !containsStar )
437         {
438             // No '*'s, so we make a shortcut
439             if ( patIdxEnd != strIdxEnd )
440             {
441                 return false; // Pattern and string do not have the same size
442             }
443             for ( int i = 0; i <= patIdxEnd; i++ )
444             {
445                 ch = patArr[i];
446                 if ( ch != '?' )
447                 {
448                     if ( isCaseSensitive && ch != strArr[i] )
449                     {
450                         return false;// Character mismatch
451                     }
452                     if ( !isCaseSensitive && Character.toUpperCase( ch ) !=
453                         Character.toUpperCase( strArr[i] ) )
454                     {
455                         return false; // Character mismatch
456                     }
457                 }
458             }
459             return true; // String matches against pattern
460         }
461 
462         if ( patIdxEnd == 0 )
463         {
464             return true; // Pattern contains only '*', which matches anything
465         }
466 
467         // Process characters before first star
468         while ( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd )
469         {
470             if ( ch != '?' )
471             {
472                 if ( isCaseSensitive && ch != strArr[strIdxStart] )
473                 {
474                     return false;// Character mismatch
475                 }
476                 if ( !isCaseSensitive && Character.toUpperCase( ch ) !=
477                     Character.toUpperCase( strArr[strIdxStart] ) )
478                 {
479                     return false;// Character mismatch
480                 }
481             }
482             patIdxStart++;
483             strIdxStart++;
484         }
485         if ( strIdxStart > strIdxEnd )
486         {
487             // All characters in the string are used. Check if only '*'s are
488             // left in the pattern. If so, we succeeded. Otherwise failure.
489             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
490             {
491                 if ( patArr[i] != '*' )
492                 {
493                     return false;
494                 }
495             }
496             return true;
497         }
498 
499         // Process characters after last star
500         while ( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd )
501         {
502             if ( ch != '?' )
503             {
504                 if ( isCaseSensitive && ch != strArr[strIdxEnd] )
505                 {
506                     return false;// Character mismatch
507                 }
508                 if ( !isCaseSensitive && Character.toUpperCase( ch ) !=
509                     Character.toUpperCase( strArr[strIdxEnd] ) )
510                 {
511                     return false;// Character mismatch
512                 }
513             }
514             patIdxEnd--;
515             strIdxEnd--;
516         }
517         if ( strIdxStart > strIdxEnd )
518         {
519             // All characters in the string are used. Check if only '*'s are
520             // left in the pattern. If so, we succeeded. Otherwise failure.
521             for ( int i = patIdxStart; i <= patIdxEnd; i++ )
522             {
523                 if ( patArr[i] != '*' )
524                 {
525                     return false;
526                 }
527             }
528             return true;
529         }
530 
531         // process pattern between stars. padIdxStart and patIdxEnd point
532         // always to a '*'.
533         while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
534         {
535             int patIdxTmp = -1;
536             for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
537             {
538                 if ( patArr[i] == '*' )
539                 {
540                     patIdxTmp = i;
541                     break;
542                 }
543             }
544             if ( patIdxTmp == patIdxStart + 1 )
545             {
546                 // Two stars next to each other, skip the first one.
547                 patIdxStart++;
548                 continue;
549             }
550             // Find the pattern between padIdxStart & padIdxTmp in str between
551             // strIdxStart & strIdxEnd
552             int patLength = ( patIdxTmp - patIdxStart - 1 );
553             int strLength = ( strIdxEnd - strIdxStart + 1 );
554             int foundIdx = -1;
555             strLoop:
556             for ( int i = 0; i <= strLength - patLength; i++ )
557             {
558                 for ( int j = 0; j < patLength; j++ )
559                 {
560                     ch = patArr[patIdxStart + j + 1];
561                     if ( ch != '?' )
562                     {
563                         if ( isCaseSensitive && ch != strArr[strIdxStart + i + j] )
564                         {
565                             continue strLoop;
566                         }
567                         if ( !isCaseSensitive && Character.toUpperCase( ch ) !=
568                             Character.toUpperCase( strArr[strIdxStart + i + j] ) )
569                         {
570                             continue strLoop;
571                         }
572                     }
573                 }
574 
575                 foundIdx = strIdxStart + i;
576                 break;
577             }
578 
579             if ( foundIdx == -1 )
580             {
581                 return false;
582             }
583 
584             patIdxStart = patIdxTmp;
585             strIdxStart = foundIdx + patLength;
586         }
587 
588         // All characters in the string are used. Check if only '*'s are left
589         // in the pattern. If so, we succeeded. Otherwise failure.
590         for ( int i = patIdxStart; i <= patIdxEnd; i++ )
591         {
592             if ( patArr[i] != '*' )
593             {
594                 return false;
595             }
596         }
597         return true;
598     }
599 
600     /**
601      * Breaks a path up into a Vector of path elements, tokenizing on
602      * <code>File.separator</code>.
603      *
604      * @param path Path to tokenize. Must not be <code>null</code>.
605      *
606      * @return a Vector of path elements from the tokenized path
607      */
608     public static Vector tokenizePath( String path )
609     {
610         Vector ret = new Vector();
611         StringTokenizer st = new StringTokenizer( path, File.separator );
612         while ( st.hasMoreTokens() )
613         {
614             ret.addElement( st.nextToken() );
615         }
616         return ret;
617     }
618 
619 
620     /**
621      * Returns dependency information on these two files. If src has been
622      * modified later than target, it returns true. If target doesn't exist,
623      * it likewise returns true. Otherwise, target is newer than src and
624      * is not out of date, thus the method returns false. It also returns
625      * false if the src file doesn't even exist, since how could the
626      * target then be out of date.
627      *
628      * @param src the original file
629      * @param target the file being compared against
630      * @param granularity the amount in seconds of slack we will give in
631      *        determining out of dateness
632      * @return whether the target is out of date
633      */
634     public static boolean isOutOfDate( File src, File target, int granularity )
635     {
636         if ( !src.exists() )
637         {
638             return false;
639         }
640         if ( !target.exists() )
641         {
642             return true;
643         }
644         if ( ( src.lastModified() - granularity ) > target.lastModified() )
645         {
646             return true;
647         }
648         return false;
649     }
650 
651     /**
652      * "Flattens" a string by removing all whitespace (space, tab, linefeed,
653      * carriage return, and formfeed). This uses StringTokenizer and the
654      * default set of tokens as documented in the single arguement constructor.
655      *
656      * @param input a String to remove all whitespace.
657      * @return a String that has had all whitespace removed.
658      */
659     public static String removeWhitespace( String input )
660     {
661         StringBuffer result = new StringBuffer();
662         if ( input != null )
663         {
664             StringTokenizer st = new StringTokenizer( input );
665             while ( st.hasMoreTokens() )
666             {
667                 result.append( st.nextToken() );
668             }
669         }
670         return result.toString();
671     }
672 }