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 }