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 }