1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package org.apache.tools.ant.types.selectors; 20 21 import org.apache.tools.ant.util.FileUtils; 22 23 import java.io.File; 24 import java.util.StringTokenizer; 25 import java.util.Vector; 26 27 /** 28 * <p>This is a utility class used by selectors and DirectoryScanner. The 29 * functionality more properly belongs just to selectors, but unfortunately 30 * DirectoryScanner exposed these as protected methods. Thus we have to 31 * support any subclasses of DirectoryScanner that may access these methods. 32 * </p> 33 * <p>This is a Singleton.</p> 34 * 35 * @since 1.5 36 */ 37 public final class SelectorUtils { 38 39 /** 40 * The pattern that matches an arbitrary number of directories. 41 * @since Ant 1.8.0 42 */ 43 public static final String DEEP_TREE_MATCH = "**"; 44 45 private static final SelectorUtils instance = new SelectorUtils(); 46 private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); 47 48 /** 49 * Private Constructor 50 */ 51 private SelectorUtils() { 52 } 53 54 /** 55 * Retrieves the instance of the Singleton. 56 * @return singleton instance 57 */ 58 public static SelectorUtils getInstance() { 59 return instance; 60 } 61 62 /** 63 * Tests whether or not a given path matches the start of a given 64 * pattern up to the first "**". 65 * <p> 66 * This is not a general purpose test and should only be used if you 67 * can live with false positives. For example, <code>pattern=**\a</code> 68 * and <code>str=b</code> will yield <code>true</code>. 69 * 70 * @param pattern The pattern to match against. Must not be 71 * <code>null</code>. 72 * @param str The path to match, as a String. Must not be 73 * <code>null</code>. 74 * 75 * @return whether or not a given path matches the start of a given 76 * pattern up to the first "**". 77 */ 78 public static boolean matchPatternStart(String pattern, String str) { 79 return matchPatternStart(pattern, str, true); 80 } 81 82 /** 83 * Tests whether or not a given path matches the start of a given 84 * pattern up to the first "**". 85 * <p> 86 * This is not a general purpose test and should only be used if you 87 * can live with false positives. For example, <code>pattern=**\a</code> 88 * and <code>str=b</code> will yield <code>true</code>. 89 * 90 * @param pattern The pattern to match against. Must not be 91 * <code>null</code>. 92 * @param str The path to match, as a String. Must not be 93 * <code>null</code>. 94 * @param isCaseSensitive Whether or not matching should be performed 95 * case sensitively. 96 * 97 * @return whether or not a given path matches the start of a given 98 * pattern up to the first "**". 99 */ 100 public static boolean matchPatternStart(String pattern, String str, 101 boolean isCaseSensitive) { 102 // When str starts with a File.separator, pattern has to start with a 103 // File.separator. 104 // When pattern starts with a File.separator, str has to start with a 105 // File.separator. 106 if (str.startsWith(File.separator) 107 != pattern.startsWith(File.separator)) { 108 return false; 109 } 110 111 String[] patDirs = tokenizePathAsArray(pattern); 112 String[] strDirs = tokenizePathAsArray(str); 113 return matchPatternStart(patDirs, strDirs, isCaseSensitive); 114 } 115 116 117 /** 118 * Tests whether or not a given path matches the start of a given 119 * pattern up to the first "**". 120 * <p> 121 * This is not a general purpose test and should only be used if you 122 * can live with false positives. For example, <code>pattern=**\a</code> 123 * and <code>str=b</code> will yield <code>true</code>. 124 * 125 * @param patDirs The tokenized pattern to match against. Must not be 126 * <code>null</code>. 127 * @param strDirs The tokenized path to match. Must not be 128 * <code>null</code>. 129 * @param isCaseSensitive Whether or not matching should be performed 130 * case sensitively. 131 * 132 * @return whether or not a given path matches the start of a given 133 * pattern up to the first "**". 134 */ 135 static boolean matchPatternStart(String[] patDirs, String[] strDirs, 136 boolean isCaseSensitive) { 137 int patIdxStart = 0; 138 int patIdxEnd = patDirs.length - 1; 139 int strIdxStart = 0; 140 int strIdxEnd = strDirs.length - 1; 141 142 // up to first '**' 143 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { 144 String patDir = patDirs[patIdxStart]; 145 if (patDir.equals(DEEP_TREE_MATCH)) { 146 break; 147 } 148 if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) { 149 return false; 150 } 151 patIdxStart++; 152 strIdxStart++; 153 } 154 155 // CheckStyle:SimplifyBooleanReturnCheck OFF 156 // Check turned off as the code needs the comments for the various 157 // code paths. 158 if (strIdxStart > strIdxEnd) { 159 // String is exhausted 160 return true; 161 } else if (patIdxStart > patIdxEnd) { 162 // String not exhausted, but pattern is. Failure. 163 return false; 164 } else { 165 // pattern now holds ** while string is not exhausted 166 // this will generate false positives but we can live with that. 167 return true; 168 } 169 } 170 171 /** 172 * Tests whether or not a given path matches a given pattern. 173 * 174 * If you need to call this method multiple times with the same 175 * pattern you should rather use TokenizedPath 176 * 177 * @see TokenizedPath 178 * 179 * @param pattern The pattern to match against. Must not be 180 * <code>null</code>. 181 * @param str The path to match, as a String. Must not be 182 * <code>null</code>. 183 * 184 * @return <code>true</code> if the pattern matches against the string, 185 * or <code>false</code> otherwise. 186 */ 187 public static boolean matchPath(String pattern, String str) { 188 String[] patDirs = tokenizePathAsArray(pattern); 189 return matchPath(patDirs, tokenizePathAsArray(str), true); 190 } 191 192 /** 193 * Tests whether or not a given path matches a given pattern. 194 * 195 * If you need to call this method multiple times with the same 196 * pattern you should rather use TokenizedPattern 197 * 198 * @see TokenizedPattern 199 * 200 * @param pattern The pattern to match against. Must not be 201 * <code>null</code>. 202 * @param str The path to match, as a String. Must not be 203 * <code>null</code>. 204 * @param isCaseSensitive Whether or not matching should be performed 205 * case sensitively. 206 * 207 * @return <code>true</code> if the pattern matches against the string, 208 * or <code>false</code> otherwise. 209 */ 210 public static boolean matchPath(String pattern, String str, 211 boolean isCaseSensitive) { 212 String[] patDirs = tokenizePathAsArray(pattern); 213 return matchPath(patDirs, tokenizePathAsArray(str), isCaseSensitive); 214 } 215 216 /** 217 * Core implementation of matchPath. It is isolated so that it 218 * can be called from TokenizedPattern. 219 */ 220 static boolean matchPath(String[] tokenizedPattern, String[] strDirs, 221 boolean isCaseSensitive) { 222 int patIdxStart = 0; 223 int patIdxEnd = tokenizedPattern.length - 1; 224 int strIdxStart = 0; 225 int strIdxEnd = strDirs.length - 1; 226 227 // up to first '**' 228 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { 229 String patDir = tokenizedPattern[patIdxStart]; 230 if (patDir.equals(DEEP_TREE_MATCH)) { 231 break; 232 } 233 if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) { 234 return false; 235 } 236 patIdxStart++; 237 strIdxStart++; 238 } 239 if (strIdxStart > strIdxEnd) { 240 // String is exhausted 241 for (int i = patIdxStart; i <= patIdxEnd; i++) { 242 if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) { 243 return false; 244 } 245 } 246 return true; 247 } else { 248 if (patIdxStart > patIdxEnd) { 249 // String not exhausted, but pattern is. Failure. 250 return false; 251 } 252 } 253 254 // up to last '**' 255 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { 256 String patDir = tokenizedPattern[patIdxEnd]; 257 if (patDir.equals(DEEP_TREE_MATCH)) { 258 break; 259 } 260 if (!match(patDir, strDirs[strIdxEnd], isCaseSensitive)) { 261 return false; 262 } 263 patIdxEnd--; 264 strIdxEnd--; 265 } 266 if (strIdxStart > strIdxEnd) { 267 // String is exhausted 268 for (int i = patIdxStart; i <= patIdxEnd; i++) { 269 if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) { 270 return false; 271 } 272 } 273 return true; 274 } 275 276 while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) { 277 int patIdxTmp = -1; 278 for (int i = patIdxStart + 1; i <= patIdxEnd; i++) { 279 if (tokenizedPattern[i].equals(DEEP_TREE_MATCH)) { 280 patIdxTmp = i; 281 break; 282 } 283 } 284 if (patIdxTmp == patIdxStart + 1) { 285 // '**/**' situation, so skip one 286 patIdxStart++; 287 continue; 288 } 289 // Find the pattern between padIdxStart & padIdxTmp in str between 290 // strIdxStart & strIdxEnd 291 int patLength = (patIdxTmp - patIdxStart - 1); 292 int strLength = (strIdxEnd - strIdxStart + 1); 293 int foundIdx = -1; 294 strLoop: 295 for (int i = 0; i <= strLength - patLength; i++) { 296 for (int j = 0; j < patLength; j++) { 297 String subPat = tokenizedPattern[patIdxStart + j + 1]; 298 String subStr = strDirs[strIdxStart + i + j]; 299 if (!match(subPat, subStr, isCaseSensitive)) { 300 continue strLoop; 301 } 302 } 303 304 foundIdx = strIdxStart + i; 305 break; 306 } 307 308 if (foundIdx == -1) { 309 return false; 310 } 311 312 patIdxStart = patIdxTmp; 313 strIdxStart = foundIdx + patLength; 314 } 315 316 for (int i = patIdxStart; i <= patIdxEnd; i++) { 317 if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) { 318 return false; 319 } 320 } 321 322 return true; 323 } 324 325 /** 326 * Tests whether or not a string matches against a pattern. 327 * The pattern may contain two special characters:<br> 328 * '*' means zero or more characters<br> 329 * '?' means one and only one character 330 * 331 * @param pattern The pattern to match against. 332 * Must not be <code>null</code>. 333 * @param str The string which must be matched against the pattern. 334 * Must not be <code>null</code>. 335 * 336 * @return <code>true</code> if the string matches against the pattern, 337 * or <code>false</code> otherwise. 338 */ 339 public static boolean match(String pattern, String str) { 340 return match(pattern, str, true); 341 } 342 343 /** 344 * Tests whether or not a string matches against a pattern. 345 * The pattern may contain two special characters:<br> 346 * '*' means zero or more characters<br> 347 * '?' means one and only one character 348 * 349 * @param pattern The pattern to match against. 350 * Must not be <code>null</code>. 351 * @param str The string which must be matched against the pattern. 352 * Must not be <code>null</code>. 353 * @param caseSensitive Whether or not matching should be performed 354 * case sensitively. 355 * 356 * 357 * @return <code>true</code> if the string matches against the pattern, 358 * or <code>false</code> otherwise. 359 */ 360 public static boolean match(String pattern, String str, 361 boolean caseSensitive) { 362 char[] patArr = pattern.toCharArray(); 363 char[] strArr = str.toCharArray(); 364 int patIdxStart = 0; 365 int patIdxEnd = patArr.length - 1; 366 int strIdxStart = 0; 367 int strIdxEnd = strArr.length - 1; 368 char ch; 369 370 boolean containsStar = false; 371 for (int i = 0; i < patArr.length; i++) { 372 if (patArr[i] == '*') { 373 containsStar = true; 374 break; 375 } 376 } 377 378 if (!containsStar) { 379 // No '*'s, so we make a shortcut 380 if (patIdxEnd != strIdxEnd) { 381 return false; // Pattern and string do not have the same size 382 } 383 for (int i = 0; i <= patIdxEnd; i++) { 384 ch = patArr[i]; 385 if (ch != '?') { 386 if (different(caseSensitive, ch, strArr[i])) { 387 return false; // Character mismatch 388 } 389 } 390 } 391 return true; // String matches against pattern 392 } 393 394 if (patIdxEnd == 0) { 395 return true; // Pattern contains only '*', which matches anything 396 } 397 398 // Process characters before first star 399 while (true) { 400 ch = patArr[patIdxStart]; 401 if (ch == '*' || strIdxStart > strIdxEnd) { 402 break; 403 } 404 if (ch != '?') { 405 if (different(caseSensitive, ch, strArr[strIdxStart])) { 406 return false; // Character mismatch 407 } 408 } 409 patIdxStart++; 410 strIdxStart++; 411 } 412 if (strIdxStart > strIdxEnd) { 413 // All characters in the string are used. Check if only '*'s are 414 // left in the pattern. If so, we succeeded. Otherwise failure. 415 return allStars(patArr, patIdxStart, patIdxEnd); 416 } 417 418 // Process characters after last star 419 while (true) { 420 ch = patArr[patIdxEnd]; 421 if (ch == '*' || strIdxStart > strIdxEnd) { 422 break; 423 } 424 if (ch != '?') { 425 if (different(caseSensitive, ch, strArr[strIdxEnd])) { 426 return false; // Character mismatch 427 } 428 } 429 patIdxEnd--; 430 strIdxEnd--; 431 } 432 if (strIdxStart > strIdxEnd) { 433 // All characters in the string are used. Check if only '*'s are 434 // left in the pattern. If so, we succeeded. Otherwise failure. 435 return allStars(patArr, patIdxStart, patIdxEnd); 436 } 437 438 // process pattern between stars. padIdxStart and patIdxEnd point 439 // always to a '*'. 440 while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) { 441 int patIdxTmp = -1; 442 for (int i = patIdxStart + 1; i <= patIdxEnd; i++) { 443 if (patArr[i] == '*') { 444 patIdxTmp = i; 445 break; 446 } 447 } 448 if (patIdxTmp == patIdxStart + 1) { 449 // Two stars next to each other, skip the first one. 450 patIdxStart++; 451 continue; 452 } 453 // Find the pattern between padIdxStart & padIdxTmp in str between 454 // strIdxStart & strIdxEnd 455 int patLength = (patIdxTmp - patIdxStart - 1); 456 int strLength = (strIdxEnd - strIdxStart + 1); 457 int foundIdx = -1; 458 strLoop: 459 for (int i = 0; i <= strLength - patLength; i++) { 460 for (int j = 0; j < patLength; j++) { 461 ch = patArr[patIdxStart + j + 1]; 462 if (ch != '?') { 463 if (different(caseSensitive, ch, 464 strArr[strIdxStart + i + j])) { 465 continue strLoop; 466 } 467 } 468 } 469 470 foundIdx = strIdxStart + i; 471 break; 472 } 473 474 if (foundIdx == -1) { 475 return false; 476 } 477 478 patIdxStart = patIdxTmp; 479 strIdxStart = foundIdx + patLength; 480 } 481 482 // All characters in the string are used. Check if only '*'s are left 483 // in the pattern. If so, we succeeded. Otherwise failure. 484 return allStars(patArr, patIdxStart, patIdxEnd); 485 } 486 487 private static boolean allStars(char[] chars, int start, int end) { 488 for (int i = start; i <= end; ++i) { 489 if (chars[i] != '*') { 490 return false; 491 } 492 } 493 return true; 494 } 495 496 private static boolean different( 497 boolean caseSensitive, char ch, char other) { 498 return caseSensitive 499 ? ch != other 500 : Character.toUpperCase(ch) != Character.toUpperCase(other); 501 } 502 503 /** 504 * Breaks a path up into a Vector of path elements, tokenizing on 505 * <code>File.separator</code>. 506 * 507 * @param path Path to tokenize. Must not be <code>null</code>. 508 * 509 * @return a Vector of path elements from the tokenized path 510 */ 511 @SuppressWarnings("rawtypes") 512 public static Vector tokenizePath (String path) { 513 return tokenizePath(path, File.separator); 514 } 515 516 /** 517 * Breaks a path up into a Vector of path elements, tokenizing on 518 * 519 * @param path Path to tokenize. Must not be <code>null</code>. 520 * @param separator the separator against which to tokenize. 521 * 522 * @return a Vector of path elements from the tokenized path 523 * @since Ant 1.6 524 */ 525 @SuppressWarnings({ 526 "rawtypes", "unchecked" 527 }) 528 public static Vector tokenizePath (String path, String separator) { 529 Vector ret = new Vector(); 530 if (FileUtils.isAbsolutePath(path)) { 531 String[] s = FILE_UTILS.dissect(path); 532 ret.add(s[0]); 533 path = s[1]; 534 } 535 StringTokenizer st = new StringTokenizer(path, separator); 536 while (st.hasMoreTokens()) { 537 ret.addElement(st.nextToken()); 538 } 539 return ret; 540 } 541 542 /** 543 * Same as {@link #tokenizePath tokenizePath} but hopefully faster. 544 */ 545 /*package*/ static String[] tokenizePathAsArray(String path) { 546 String root = null; 547 if (FileUtils.isAbsolutePath(path)) { 548 String[] s = FILE_UTILS.dissect(path); 549 root = s[0]; 550 path = s[1]; 551 } 552 char sep = File.separatorChar; 553 int start = 0; 554 int len = path.length(); 555 int count = 0; 556 for (int pos = 0; pos < len; pos++) { 557 if (path.charAt(pos) == sep) { 558 if (pos != start) { 559 count++; 560 } 561 start = pos + 1; 562 } 563 } 564 if (len != start) { 565 count++; 566 } 567 String[] l = new String[count + ((root == null) ? 0 : 1)]; 568 569 if (root != null) { 570 l[0] = root; 571 count = 1; 572 } else { 573 count = 0; 574 } 575 start = 0; 576 for (int pos = 0; pos < len; pos++) { 577 if (path.charAt(pos) == sep) { 578 if (pos != start) { 579 String tok = path.substring(start, pos); 580 l[count++] = tok; 581 } 582 start = pos + 1; 583 } 584 } 585 if (len != start) { 586 String tok = path.substring(start); 587 l[count/*++*/] = tok; 588 } 589 return l; 590 } 591 592 /** 593 * Returns dependency information on these two files. If src has been 594 * modified later than target, it returns true. If target doesn't exist, 595 * it likewise returns true. Otherwise, target is newer than src and 596 * is not out of date, thus the method returns false. It also returns 597 * false if the src file doesn't even exist, since how could the 598 * target then be out of date. 599 * 600 * @param src the original file 601 * @param target the file being compared against 602 * @param granularity the amount in seconds of slack we will give in 603 * determining out of dateness 604 * @return whether the target is out of date 605 */ 606 public static boolean isOutOfDate(File src, File target, int granularity) { 607 if (!src.exists()) { 608 return false; 609 } 610 if (!target.exists()) { 611 return true; 612 } 613 if ((src.lastModified() - granularity) > target.lastModified()) { 614 return true; 615 } 616 return false; 617 } 618 619 /** 620 * "Flattens" a string by removing all whitespace (space, tab, linefeed, 621 * carriage return, and formfeed). This uses StringTokenizer and the 622 * default set of tokens as documented in the single arguement constructor. 623 * 624 * @param input a String to remove all whitespace. 625 * @return a String that has had all whitespace removed. 626 */ 627 public static String removeWhitespace(String input) { 628 StringBuffer result = new StringBuffer(); 629 if (input != null) { 630 StringTokenizer st = new StringTokenizer(input); 631 while (st.hasMoreTokens()) { 632 result.append(st.nextToken()); 633 } 634 } 635 return result.toString(); 636 } 637 638 /** 639 * Tests if a string contains stars or question marks 640 * @param input a String which one wants to test for containing wildcard 641 * @return true if the string contains at least a star or a question mark 642 */ 643 public static boolean hasWildcards(String input) { 644 return (input.indexOf('*') != -1 || input.indexOf('?') != -1); 645 } 646 } 647 648