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 acknowledgement:
21 * "This product includes software developed by the
22 * Apache Software Foundation (http://www.codehaus.org/)."
23 * Alternately, this acknowledgement may appear in the software itself,
24 * if and wherever such third-party acknowledgements 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 codehaus@codehaus.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.codehaus.org/>.
53 */
54
55 package org.codehaus.plexus.util;
56
57 import java.io.File;
58 import java.util.ArrayList;
59 import java.util.List;
60 import java.util.StringTokenizer;
61
62 /**
63 * <p>This is a utility class used by selectors and DirectoryScanner. The functionality more properly belongs just to
64 * selectors, but unfortunately DirectoryScanner exposed these as protected methods. Thus we have to support any
65 * subclasses of DirectoryScanner that may access these methods.</p>
66 *
67 * <p>This is a Singleton.</p>
68 *
69 * @author Arnout J. Kuiper <a href="mailto:ajkuiper@wxs.nl">ajkuiper@wxs.nl</a>
70 * @author Magesh Umasankar
71 * @author <a href="mailto:bruce@callenish.com">Bruce Atherton</a>
72 *
73 * @since 1.5
74 */
75 public final class SelectorUtils
76 {
77
78 public static final String PATTERN_HANDLER_PREFIX = "[";
79
80 public static final String PATTERN_HANDLER_SUFFIX = "]";
81
82 public static final String REGEX_HANDLER_PREFIX = "%regex" + PATTERN_HANDLER_PREFIX;
83
84 public static final String ANT_HANDLER_PREFIX = "%ant" + PATTERN_HANDLER_PREFIX;
85
86 private static SelectorUtils instance = new SelectorUtils();
87
88 /**
89 * Private Constructor
90 */
91 private SelectorUtils()
92 {
93 }
94
95 /**
96 * @return Retrieves the manager of the Singleton.
97 */
98 public static SelectorUtils getInstance()
99 {
100 return instance;
101 }
102
103 /**
104 * <p>Tests whether or not a given path matches the start of a given pattern up to the first "**".</p>
105 *
106 * <p>This is not a general purpose test and should only be used if you can live with false positives. For example,
107 * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>.</p>
108 *
109 * @param pattern The pattern to match against. Must not be <code>null</code>.
110 * @param str The path to match, as a String. Must not be <code>null</code>.
111 * @return whether or not a given path matches the start of a given pattern up to the first "**".
112 */
113 public static boolean matchPatternStart( String pattern, String str )
114 {
115 return matchPatternStart( pattern, str, true );
116 }
117
118 /**
119 * <p>Tests whether or not a given path matches the start of a given pattern up to the first "**".</p>
120 *
121 * <p>This is not a general purpose test and should only be used if you can live with false positives. For example,
122 * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>.</p>
123 *
124 * @param pattern The pattern to match against. Must not be <code>null</code>.
125 * @param str The path to match, as a String. Must not be <code>null</code>.
126 * @param isCaseSensitive Whether or not matching should be performed case sensitively.
127 * @return whether or not a given path matches the start of a given pattern up to the first "**".
128 */
129 public static boolean matchPatternStart( String pattern, String str, boolean isCaseSensitive )
130 {
131 if ( isRegexPrefixedPattern( pattern ) )
132 {
133 // FIXME: ICK! But we can't do partial matches for regex, so we have to reserve judgement until we have
134 // a file to deal with, or we can definitely say this is an exclusion...
135 return true;
136 }
137 else
138 {
139 if ( isAntPrefixedPattern( pattern ) )
140 {
141 pattern = pattern.substring( ANT_HANDLER_PREFIX.length(),
142 pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
143 }
144
145 String altStr = str.replace( '\\', '/' );
146
147 return matchAntPathPatternStart( pattern, str, File.separator, isCaseSensitive )
148 || matchAntPathPatternStart( pattern, altStr, "/", isCaseSensitive );
149 }
150 }
151
152 static boolean isAntPrefixedPattern( String pattern )
153 {
154 return pattern.length() > ( ANT_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
155 && pattern.startsWith( ANT_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX );
156 }
157
158 @SuppressWarnings( "SimplifiableIfStatement" )
159 static boolean matchAntPathPatternStart( MatchPattern pattern, String str, String separator,
160 boolean isCaseSensitive )
161 {
162 if ( separatorPatternStartSlashMismatch( pattern, str, separator ) )
163 {
164 return false;
165 }
166
167 return matchAntPathPatternStart( pattern.getTokenizedPathString(), str, separator, isCaseSensitive );
168 }
169
170 static boolean matchAntPathPatternStart( String pattern, String str, String separator, boolean isCaseSensitive )
171 {
172 // When str starts with a File.separator, pattern has to start with a
173 // File.separator.
174 // When pattern starts with a File.separator, str has to start with a
175 // File.separator.
176 if ( separatorPatternStartSlashMismatch( pattern, str, separator ) )
177 {
178 return false;
179 }
180
181 String[] patDirs = tokenizePathToString( pattern, separator );
182 return matchAntPathPatternStart( patDirs, str, separator, isCaseSensitive );
183 }
184
185 // When str starts with a File.separator, pattern has to start with a
186 // File.separator.
187 // When pattern starts with a File.separator, str has to start with a
188 // File.separator.
189 private static boolean separatorPatternStartSlashMismatch( String pattern, String str, String separator )
190 {
191 return str.startsWith( separator ) != pattern.startsWith( separator );
192 }
193
194 private static boolean separatorPatternStartSlashMismatch( MatchPattern matchPattern, String str, String separator )
195 {
196 return str.startsWith( separator ) != matchPattern.startsWith( separator );
197 }
198
199 static boolean matchAntPathPatternStart( String[] patDirs, String str, String separator, boolean isCaseSensitive )
200 {
201 String[] strDirs = tokenizePathToString( str, separator );
202
203 int patIdxStart = 0;
204 int patIdxEnd = patDirs.length - 1;
205 int strIdxStart = 0;
206 int strIdxEnd = strDirs.length - 1;
207
208 // up to first '**'
209 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
210 {
211 String patDir = patDirs[patIdxStart];
212 if ( patDir.equals( "**" ) )
213 {
214 break;
215 }
216 if ( !match( patDir, strDirs[strIdxStart], isCaseSensitive ) )
217 {
218 return false;
219 }
220 patIdxStart++;
221 strIdxStart++;
222 }
223
224 return strIdxStart > strIdxEnd || patIdxStart <= patIdxEnd;
225 }
226
227 /**
228 * Tests whether or not a given path matches a given pattern.
229 *
230 * @param pattern The pattern to match against. Must not be <code>null</code>.
231 * @param str The path to match, as a String. Must not be <code>null</code>.
232 * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise.
233 */
234 public static boolean matchPath( String pattern, String str )
235 {
236 return matchPath( pattern, str, true );
237 }
238
239 /**
240 * Tests whether or not a given path matches a given pattern.
241 *
242 * @param pattern The pattern to match against. Must not be <code>null</code>.
243 * @param str The path to match, as a String. Must not be <code>null</code>.
244 * @param isCaseSensitive Whether or not matching should be performed case sensitively.
245 * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise.
246 */
247 public static boolean matchPath( String pattern, String str, boolean isCaseSensitive )
248 {
249 return matchPath( pattern, str, File.separator, isCaseSensitive );
250 }
251
252 public static boolean matchPath( String pattern, String str, String separator, boolean isCaseSensitive )
253 {
254 if ( isRegexPrefixedPattern( pattern ) )
255 {
256 String localPattern =
257 pattern.substring( REGEX_HANDLER_PREFIX.length(), pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
258
259 return str.matches( localPattern );
260 }
261 else
262 {
263 String localPattern = isAntPrefixedPattern( pattern )
264 ? pattern.substring( ANT_HANDLER_PREFIX.length(), pattern.length() - PATTERN_HANDLER_SUFFIX.length() )
265 : pattern;
266 final String osRelatedPath = toOSRelatedPath( str, separator );
267 final String osRelatedPattern = toOSRelatedPath( localPattern, separator );
268 return matchAntPathPattern( osRelatedPattern, osRelatedPath, separator, isCaseSensitive );
269 }
270 }
271
272 private static String toOSRelatedPath( String pattern, String separator )
273 {
274 if ( "/".equals( separator ) )
275 {
276 return pattern.replace( "\\", separator );
277 }
278 if ( "\\".equals( separator ) ) {
279 return pattern.replace( "/", separator );
280 }
281 return pattern;
282 }
283
284 static boolean isRegexPrefixedPattern( String pattern )
285 {
286 return pattern.length() > ( REGEX_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
287 && pattern.startsWith( REGEX_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX );
288 }
289
290 static boolean matchAntPathPattern( MatchPattern matchPattern, String str, String separator,
291 boolean isCaseSensitive )
292 {
293 if ( separatorPatternStartSlashMismatch( matchPattern, str, separator ) )
294 {
295 return false;
296 }
297 String[] patDirs = matchPattern.getTokenizedPathString();
298 String[] strDirs = tokenizePathToString( str, separator );
299 return matchAntPathPattern( patDirs, strDirs, isCaseSensitive );
300 }
301
302 static boolean matchAntPathPattern( String pattern, String str, String separator, boolean isCaseSensitive )
303 {
304 if ( separatorPatternStartSlashMismatch( pattern, str, separator ) )
305 {
306 return false;
307 }
308 String[] patDirs = tokenizePathToString( pattern, separator );
309 String[] strDirs = tokenizePathToString( str, separator );
310 return matchAntPathPattern( patDirs, strDirs, isCaseSensitive );
311
312 }
313
314 static boolean matchAntPathPattern( String[] patDirs, String[] strDirs, boolean isCaseSensitive )
315 {
316 int patIdxStart = 0;
317 int patIdxEnd = patDirs.length - 1;
318 int strIdxStart = 0;
319 int strIdxEnd = strDirs.length - 1;
320
321 // up to first '**'
322 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
323 {
324 String patDir = patDirs[patIdxStart];
325 if ( patDir.equals( "**" ) )
326 {
327 break;
328 }
329 if ( !match( patDir, strDirs[strIdxStart], isCaseSensitive ) )
330 {
331 return false;
332 }
333 patIdxStart++;
334 strIdxStart++;
335 }
336 if ( strIdxStart > strIdxEnd )
337 {
338 // String is exhausted
339 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
340 {
341 if ( !patDirs[i].equals( "**" ) )
342 {
343 return false;
344 }
345 }
346 return true;
347 }
348 else
349 {
350 if ( patIdxStart > patIdxEnd )
351 {
352 // String not exhausted, but pattern is. Failure.
353 return false;
354 }
355 }
356
357 // up to last '**'
358 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
359 {
360 String patDir = patDirs[patIdxEnd];
361 if ( patDir.equals( "**" ) )
362 {
363 break;
364 }
365 if ( !match( patDir, strDirs[strIdxEnd], isCaseSensitive ) )
366 {
367 return false;
368 }
369 patIdxEnd--;
370 strIdxEnd--;
371 }
372 if ( strIdxStart > strIdxEnd )
373 {
374 // String is exhausted
375 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
376 {
377 if ( !patDirs[i].equals( "**" ) )
378 {
379 return false;
380 }
381 }
382 return true;
383 }
384
385 while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
386 {
387 int patIdxTmp = -1;
388 for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
389 {
390 if ( patDirs[i].equals( "**" ) )
391 {
392 patIdxTmp = i;
393 break;
394 }
395 }
396 if ( patIdxTmp == patIdxStart + 1 )
397 {
398 // '**/**' situation, so skip one
399 patIdxStart++;
400 continue;
401 }
402 // Find the pattern between padIdxStart & padIdxTmp in str between
403 // strIdxStart & strIdxEnd
404 int patLength = ( patIdxTmp - patIdxStart - 1 );
405 int strLength = ( strIdxEnd - strIdxStart + 1 );
406 int foundIdx = -1;
407 strLoop: for ( int i = 0; i <= strLength - patLength; i++ )
408 {
409 for ( int j = 0; j < patLength; j++ )
410 {
411 String subPat = patDirs[patIdxStart + j + 1];
412 String subStr = strDirs[strIdxStart + i + j];
413 if ( !match( subPat, subStr, isCaseSensitive ) )
414 {
415 continue strLoop;
416 }
417 }
418
419 foundIdx = strIdxStart + i;
420 break;
421 }
422
423 if ( foundIdx == -1 )
424 {
425 return false;
426 }
427
428 patIdxStart = patIdxTmp;
429 strIdxStart = foundIdx + patLength;
430 }
431
432 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
433 {
434 if ( !patDirs[i].equals( "**" ) )
435 {
436 return false;
437 }
438 }
439
440 return true;
441 }
442
443 static boolean matchAntPathPattern( char[][] patDirs, char[][] strDirs, boolean isCaseSensitive )
444 {
445 int patIdxStart = 0;
446 int patIdxEnd = patDirs.length - 1;
447 int strIdxStart = 0;
448 int strIdxEnd = strDirs.length - 1;
449
450 // up to first '**'
451 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
452 {
453 char[] patDir = patDirs[patIdxStart];
454 if ( isDoubleStar( patDir ) )
455 {
456 break;
457 }
458 if ( !match( patDir, strDirs[strIdxStart], isCaseSensitive ) )
459 {
460 return false;
461 }
462 patIdxStart++;
463 strIdxStart++;
464 }
465 if ( strIdxStart > strIdxEnd )
466 {
467 // String is exhausted
468 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
469 {
470 if ( !isDoubleStar( patDirs[i] ) )
471 {
472 return false;
473 }
474 }
475 return true;
476 }
477 else
478 {
479 if ( patIdxStart > patIdxEnd )
480 {
481 // String not exhausted, but pattern is. Failure.
482 return false;
483 }
484 }
485
486 // up to last '**'
487 while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
488 {
489 char[] patDir = patDirs[patIdxEnd];
490 if ( isDoubleStar( patDir ) )
491 {
492 break;
493 }
494 if ( !match( patDir, strDirs[strIdxEnd], isCaseSensitive ) )
495 {
496 return false;
497 }
498 patIdxEnd--;
499 strIdxEnd--;
500 }
501 if ( strIdxStart > strIdxEnd )
502 {
503 // String is exhausted
504 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
505 {
506 if ( !isDoubleStar( patDirs[i] ) )
507 {
508 return false;
509 }
510 }
511 return true;
512 }
513
514 while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
515 {
516 int patIdxTmp = -1;
517 for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
518 {
519 if ( isDoubleStar( patDirs[i] ) )
520 {
521 patIdxTmp = i;
522 break;
523 }
524 }
525 if ( patIdxTmp == patIdxStart + 1 )
526 {
527 // '**/**' situation, so skip one
528 patIdxStart++;
529 continue;
530 }
531 // Find the pattern between padIdxStart & padIdxTmp in str between
532 // strIdxStart & strIdxEnd
533 int patLength = ( patIdxTmp - patIdxStart - 1 );
534 int strLength = ( strIdxEnd - strIdxStart + 1 );
535 int foundIdx = -1;
536 strLoop: for ( int i = 0; i <= strLength - patLength; i++ )
537 {
538 for ( int j = 0; j < patLength; j++ )
539 {
540 char[] subPat = patDirs[patIdxStart + j + 1];
541 char[] subStr = strDirs[strIdxStart + i + j];
542 if ( !match( subPat, subStr, isCaseSensitive ) )
543 {
544 continue strLoop;
545 }
546 }
547
548 foundIdx = strIdxStart + i;
549 break;
550 }
551
552 if ( foundIdx == -1 )
553 {
554 return false;
555 }
556
557 patIdxStart = patIdxTmp;
558 strIdxStart = foundIdx + patLength;
559 }
560
561 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
562 {
563 if ( !isDoubleStar( patDirs[i] ) )
564 {
565 return false;
566 }
567 }
568
569 return true;
570 }
571
572 private static boolean isDoubleStar( char[] patDir )
573 {
574 return patDir != null && patDir.length == 2 && patDir[0] == '*' && patDir[1] == '*';
575 }
576
577 /**
578 * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:<br>
579 * '*' means zero or more characters<br>
580 * '?' means one and only one character
581 *
582 * @param pattern The pattern to match against. Must not be <code>null</code>.
583 * @param str The string which must be matched against the pattern. Must not be <code>null</code>.
584 * @return <code>true</code> if the string matches against the pattern, or <code>false</code> otherwise.
585 */
586 public static boolean match( String pattern, String str )
587 {
588 return match( pattern, str, true );
589 }
590
591 /**
592 * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:<br>
593 * '*' means zero or more characters<br>
594 * '?' means one and only one character
595 *
596 * @param pattern The pattern to match against. Must not be <code>null</code>.
597 * @param str The string which must be matched against the pattern. Must not be <code>null</code>.
598 * @param isCaseSensitive Whether or not matching should be performed case sensitively.
599 * @return <code>true</code> if the string matches against the pattern, or <code>false</code> otherwise.
600 */
601 public static boolean match( String pattern, String str, boolean isCaseSensitive )
602 {
603 char[] patArr = pattern.toCharArray();
604 char[] strArr = str.toCharArray();
605 return match( patArr, strArr, isCaseSensitive );
606 }
607
608 public static boolean match( char[] patArr, char[] strArr, boolean isCaseSensitive )
609 {
610 int patIdxStart = 0;
611 int patIdxEnd = patArr.length - 1;
612 int strIdxStart = 0;
613 int strIdxEnd = strArr.length - 1;
614 char ch;
615
616 boolean containsStar = false;
617 for ( char aPatArr : patArr )
618 {
619 if ( aPatArr == '*' )
620 {
621 containsStar = true;
622 break;
623 }
624 }
625
626 if ( !containsStar )
627 {
628 // No '*'s, so we make a shortcut
629 if ( patIdxEnd != strIdxEnd )
630 {
631 return false; // Pattern and string do not have the same size
632 }
633 for ( int i = 0; i <= patIdxEnd; i++ )
634 {
635 ch = patArr[i];
636 if ( ch != '?' && !equals( ch, strArr[i], isCaseSensitive ) )
637 {
638 return false; // Character mismatch
639 }
640 }
641 return true; // String matches against pattern
642 }
643
644 if ( patIdxEnd == 0 )
645 {
646 return true; // Pattern contains only '*', which matches anything
647 }
648
649 // Process characters before first star
650 while ( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd )
651 {
652 if ( ch != '?' && !equals( ch, strArr[strIdxStart], isCaseSensitive ) )
653 {
654 return false; // Character mismatch
655 }
656 patIdxStart++;
657 strIdxStart++;
658 }
659 if ( strIdxStart > strIdxEnd )
660 {
661 // All characters in the string are used. Check if only '*'s are
662 // left in the pattern. If so, we succeeded. Otherwise failure.
663 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
664 {
665 if ( patArr[i] != '*' )
666 {
667 return false;
668 }
669 }
670 return true;
671 }
672
673 // Process characters after last star
674 while ( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd )
675 {
676 if ( ch != '?' && !equals( ch, strArr[strIdxEnd], isCaseSensitive ) )
677 {
678 return false; // Character mismatch
679 }
680 patIdxEnd--;
681 strIdxEnd--;
682 }
683 if ( strIdxStart > strIdxEnd )
684 {
685 // All characters in the string are used. Check if only '*'s are
686 // left in the pattern. If so, we succeeded. Otherwise failure.
687 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
688 {
689 if ( patArr[i] != '*' )
690 {
691 return false;
692 }
693 }
694 return true;
695 }
696
697 // process pattern between stars. padIdxStart and patIdxEnd point
698 // always to a '*'.
699 while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
700 {
701 int patIdxTmp = -1;
702 for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
703 {
704 if ( patArr[i] == '*' )
705 {
706 patIdxTmp = i;
707 break;
708 }
709 }
710 if ( patIdxTmp == patIdxStart + 1 )
711 {
712 // Two stars next to each other, skip the first one.
713 patIdxStart++;
714 continue;
715 }
716 // Find the pattern between padIdxStart & padIdxTmp in str between
717 // strIdxStart & strIdxEnd
718 int patLength = ( patIdxTmp - patIdxStart - 1 );
719 int strLength = ( strIdxEnd - strIdxStart + 1 );
720 int foundIdx = -1;
721 strLoop: for ( int i = 0; i <= strLength - patLength; i++ )
722 {
723 for ( int j = 0; j < patLength; j++ )
724 {
725 ch = patArr[patIdxStart + j + 1];
726 if ( ch != '?' && !equals( ch, strArr[strIdxStart + i + j], isCaseSensitive ) )
727 {
728 continue strLoop;
729 }
730 }
731
732 foundIdx = strIdxStart + i;
733 break;
734 }
735
736 if ( foundIdx == -1 )
737 {
738 return false;
739 }
740
741 patIdxStart = patIdxTmp;
742 strIdxStart = foundIdx + patLength;
743 }
744
745 // All characters in the string are used. Check if only '*'s are left
746 // in the pattern. If so, we succeeded. Otherwise failure.
747 for ( int i = patIdxStart; i <= patIdxEnd; i++ )
748 {
749 if ( patArr[i] != '*' )
750 {
751 return false;
752 }
753 }
754 return true;
755 }
756
757 /**
758 * Tests whether two characters are equal.
759 */
760 private static boolean equals( char c1, char c2, boolean isCaseSensitive )
761 {
762 if ( c1 == c2 )
763 {
764 return true;
765 }
766 if ( !isCaseSensitive )
767 {
768 // NOTE: Try both upper case and lower case as done by String.equalsIgnoreCase()
769 if ( Character.toUpperCase( c1 ) == Character.toUpperCase( c2 )
770 || Character.toLowerCase( c1 ) == Character.toLowerCase( c2 ) )
771 {
772 return true;
773 }
774 }
775 return false;
776 }
777
778 private static String[] tokenizePathToString( String path, String separator )
779 {
780 List<String> ret = new ArrayList<String>();
781 StringTokenizer st = new StringTokenizer( path, separator );
782 while ( st.hasMoreTokens() )
783 {
784 ret.add( st.nextToken() );
785 }
786 return ret.toArray( new String[0] );
787 }
788
789 /**
790 * Returns dependency information on these two files. If src has been modified later than target, it returns true.
791 * If target doesn't exist, it likewise returns true. Otherwise, target is newer than src and is not out of date,
792 * thus the method returns false. It also returns false if the src file doesn't even exist, since how could the
793 * target then be out of date.
794 *
795 * @param src the original file
796 * @param target the file being compared against
797 * @param granularity the amount in seconds of slack we will give in determining out of dateness
798 * @return whether the target is out of date
799 */
800 public static boolean isOutOfDate( File src, File target, int granularity )
801 {
802 if ( !src.exists() )
803 {
804 return false;
805 }
806 if ( !target.exists() )
807 {
808 return true;
809 }
810 if ( ( src.lastModified() - granularity ) > target.lastModified() )
811 {
812 return true;
813 }
814 return false;
815 }
816
817 /**
818 * "Flattens" a string by removing all whitespace (space, tab, linefeed, carriage return, and formfeed). This uses
819 * StringTokenizer and the default set of tokens as documented in the single argument constructor.
820 *
821 * @param input a String to remove all whitespace.
822 * @return a String that has had all whitespace removed.
823 */
824 public static String removeWhitespace( String input )
825 {
826 StringBuilder result = new StringBuilder();
827 if ( input != null )
828 {
829 StringTokenizer st = new StringTokenizer( input );
830 while ( st.hasMoreTokens() )
831 {
832 result.append( st.nextToken() );
833 }
834 }
835 return result.toString();
836 }
837 }