001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.geronimo.kernel.config; 018 019 import java.io.File; 020 import java.util.StringTokenizer; 021 import java.util.Vector; 022 023 /** 024 * <p>This is a utility class used by selectors and DirectoryScanner. The 025 * functionality more properly belongs just to selectors, but unfortunately 026 * DirectoryScanner exposed these as protected methods. Thus we have to 027 * support any subclasses of DirectoryScanner that may access these methods. 028 * </p> 029 * <p>This is a Singleton.</p> 030 * 031 * @version $Rev: 476049 $ $Date: 2006-11-16 23:35:17 -0500 (Thu, 16 Nov 2006) $ 032 */ 033 public final class SelectorUtils { 034 private static SelectorUtils instance = new SelectorUtils(); 035 036 private static boolean onNetWare = Os.isFamily("netware"); 037 private static boolean onDos = Os.isFamily("dos"); 038 039 /** 040 * Private Constructor 041 */ 042 private SelectorUtils() { 043 } 044 045 /** 046 * Retrieves the instance of the Singleton. 047 * @return singleton instance 048 */ 049 public static SelectorUtils getInstance() { 050 return instance; 051 } 052 053 /** 054 * Tests whether or not a given path matches the start of a given 055 * pattern up to the first "**". 056 * <p> 057 * This is not a general purpose test and should only be used if you 058 * can live with false positives. For example, <code>pattern=**\a</code> 059 * and <code>str=b</code> will yield <code>true</code>. 060 * 061 * @param pattern The pattern to match against. Must not be 062 * <code>null</code>. 063 * @param str The path to match, as a String. Must not be 064 * <code>null</code>. 065 * 066 * @return whether or not a given path matches the start of a given 067 * pattern up to the first "**". 068 */ 069 public static boolean matchPatternStart(String pattern, String str) { 070 return matchPatternStart(pattern, str, true); 071 } 072 073 /** 074 * Tests whether or not a given path matches the start of a given 075 * pattern up to the first "**". 076 * <p> 077 * This is not a general purpose test and should only be used if you 078 * can live with false positives. For example, <code>pattern=**\a</code> 079 * and <code>str=b</code> will yield <code>true</code>. 080 * 081 * @param pattern The pattern to match against. Must not be 082 * <code>null</code>. 083 * @param str The path to match, as a String. Must not be 084 * <code>null</code>. 085 * @param isCaseSensitive Whether or not matching should be performed 086 * case sensitively. 087 * 088 * @return whether or not a given path matches the start of a given 089 * pattern up to the first "**". 090 */ 091 public static boolean matchPatternStart(String pattern, String str, 092 boolean isCaseSensitive) { 093 // When str starts with a File.separator, pattern has to start with a 094 // File.separator. 095 // When pattern starts with a File.separator, str has to start with a 096 // File.separator. 097 if (str.startsWith(File.separator) 098 != pattern.startsWith(File.separator)) { 099 return false; 100 } 101 102 String[] patDirs = tokenizePathAsArray(pattern); 103 String[] strDirs = tokenizePathAsArray(str); 104 105 int patIdxStart = 0; 106 int patIdxEnd = patDirs.length - 1; 107 int strIdxStart = 0; 108 int strIdxEnd = strDirs.length - 1; 109 110 // up to first '**' 111 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { 112 String patDir = patDirs[patIdxStart]; 113 if (patDir.equals("**")) { 114 break; 115 } 116 if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) { 117 return false; 118 } 119 patIdxStart++; 120 strIdxStart++; 121 } 122 123 if (strIdxStart > strIdxEnd) { 124 // String is exhausted 125 return true; 126 } else if (patIdxStart > patIdxEnd) { 127 // String not exhausted, but pattern is. Failure. 128 return false; 129 } else { 130 // pattern now holds ** while string is not exhausted 131 // this will generate false positives but we can live with that. 132 return true; 133 } 134 } 135 136 /** 137 * Tests whether or not a given path matches a given pattern. 138 * 139 * @param pattern The pattern to match against. Must not be 140 * <code>null</code>. 141 * @param str The path to match, as a String. Must not be 142 * <code>null</code>. 143 * 144 * @return <code>true</code> if the pattern matches against the string, 145 * or <code>false</code> otherwise. 146 */ 147 public static boolean matchPath(String pattern, String str) { 148 return matchPath(pattern, str, true); 149 } 150 151 /** 152 * Tests whether or not a given path matches a given pattern. 153 * 154 * @param pattern The pattern to match against. Must not be 155 * <code>null</code>. 156 * @param str The path to match, as a String. Must not be 157 * <code>null</code>. 158 * @param isCaseSensitive Whether or not matching should be performed 159 * case sensitively. 160 * 161 * @return <code>true</code> if the pattern matches against the string, 162 * or <code>false</code> otherwise. 163 */ 164 public static boolean matchPath(String pattern, String str, 165 boolean isCaseSensitive) { 166 String[] patDirs = tokenizePathAsArray(pattern); 167 String[] strDirs = tokenizePathAsArray(str); 168 169 int patIdxStart = 0; 170 int patIdxEnd = patDirs.length - 1; 171 int strIdxStart = 0; 172 int strIdxEnd = strDirs.length - 1; 173 174 // up to first '**' 175 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { 176 String patDir = patDirs[patIdxStart]; 177 if (patDir.equals("**")) { 178 break; 179 } 180 if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) { 181 patDirs = null; 182 strDirs = null; 183 return false; 184 } 185 patIdxStart++; 186 strIdxStart++; 187 } 188 if (strIdxStart > strIdxEnd) { 189 // String is exhausted 190 for (int i = patIdxStart; i <= patIdxEnd; i++) { 191 if (!patDirs[i].equals("**")) { 192 patDirs = null; 193 strDirs = null; 194 return false; 195 } 196 } 197 return true; 198 } else { 199 if (patIdxStart > patIdxEnd) { 200 // String not exhausted, but pattern is. Failure. 201 patDirs = null; 202 strDirs = null; 203 return false; 204 } 205 } 206 207 // up to last '**' 208 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) { 209 String patDir = patDirs[patIdxEnd]; 210 if (patDir.equals("**")) { 211 break; 212 } 213 if (!match(patDir, strDirs[strIdxEnd], isCaseSensitive)) { 214 patDirs = null; 215 strDirs = null; 216 return false; 217 } 218 patIdxEnd--; 219 strIdxEnd--; 220 } 221 if (strIdxStart > strIdxEnd) { 222 // String is exhausted 223 for (int i = patIdxStart; i <= patIdxEnd; i++) { 224 if (!patDirs[i].equals("**")) { 225 patDirs = null; 226 strDirs = null; 227 return false; 228 } 229 } 230 return true; 231 } 232 233 while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) { 234 int patIdxTmp = -1; 235 for (int i = patIdxStart + 1; i <= patIdxEnd; i++) { 236 if (patDirs[i].equals("**")) { 237 patIdxTmp = i; 238 break; 239 } 240 } 241 if (patIdxTmp == patIdxStart + 1) { 242 // '**/**' situation, so skip one 243 patIdxStart++; 244 continue; 245 } 246 // Find the pattern between padIdxStart & padIdxTmp in str between 247 // strIdxStart & strIdxEnd 248 int patLength = (patIdxTmp - patIdxStart - 1); 249 int strLength = (strIdxEnd - strIdxStart + 1); 250 int foundIdx = -1; 251 strLoop: 252 for (int i = 0; i <= strLength - patLength; i++) { 253 for (int j = 0; j < patLength; j++) { 254 String subPat = patDirs[patIdxStart + j + 1]; 255 String subStr = strDirs[strIdxStart + i + j]; 256 if (!match(subPat, subStr, isCaseSensitive)) { 257 continue strLoop; 258 } 259 } 260 261 foundIdx = strIdxStart + i; 262 break; 263 } 264 265 if (foundIdx == -1) { 266 patDirs = null; 267 strDirs = null; 268 return false; 269 } 270 271 patIdxStart = patIdxTmp; 272 strIdxStart = foundIdx + patLength; 273 } 274 275 for (int i = patIdxStart; i <= patIdxEnd; i++) { 276 if (!patDirs[i].equals("**")) { 277 patDirs = null; 278 strDirs = null; 279 return false; 280 } 281 } 282 283 return true; 284 } 285 286 /** 287 * Tests whether or not a string matches against a pattern. 288 * The pattern may contain two special characters:<br> 289 * '*' means zero or more characters<br> 290 * '?' means one and only one character 291 * 292 * @param pattern The pattern to match against. 293 * Must not be <code>null</code>. 294 * @param str The string which must be matched against the pattern. 295 * Must not be <code>null</code>. 296 * 297 * @return <code>true</code> if the string matches against the pattern, 298 * or <code>false</code> otherwise. 299 */ 300 public static boolean match(String pattern, String str) { 301 return match(pattern, str, true); 302 } 303 304 /** 305 * Tests whether or not a string matches against a pattern. 306 * The pattern may contain two special characters:<br> 307 * '*' means zero or more characters<br> 308 * '?' means one and only one character 309 * 310 * @param pattern The pattern to match against. 311 * Must not be <code>null</code>. 312 * @param str The string which must be matched against the pattern. 313 * Must not be <code>null</code>. 314 * @param isCaseSensitive Whether or not matching should be performed 315 * case sensitively. 316 * 317 * 318 * @return <code>true</code> if the string matches against the pattern, 319 * or <code>false</code> otherwise. 320 */ 321 public static boolean match(String pattern, String str, 322 boolean isCaseSensitive) { 323 char[] patArr = pattern.toCharArray(); 324 char[] strArr = str.toCharArray(); 325 int patIdxStart = 0; 326 int patIdxEnd = patArr.length - 1; 327 int strIdxStart = 0; 328 int strIdxEnd = strArr.length - 1; 329 char ch; 330 331 boolean containsStar = false; 332 for (int i = 0; i < patArr.length; i++) { 333 if (patArr[i] == '*') { 334 containsStar = true; 335 break; 336 } 337 } 338 339 if (!containsStar) { 340 // No '*'s, so we make a shortcut 341 if (patIdxEnd != strIdxEnd) { 342 return false; // Pattern and string do not have the same size 343 } 344 for (int i = 0; i <= patIdxEnd; i++) { 345 ch = patArr[i]; 346 if (ch != '?') { 347 if (isCaseSensitive && ch != strArr[i]) { 348 return false; // Character mismatch 349 } 350 if (!isCaseSensitive && Character.toUpperCase(ch) 351 != Character.toUpperCase(strArr[i])) { 352 return false; // Character mismatch 353 } 354 } 355 } 356 return true; // String matches against pattern 357 } 358 359 if (patIdxEnd == 0) { 360 return true; // Pattern contains only '*', which matches anything 361 } 362 363 // Process characters before first star 364 while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) { 365 if (ch != '?') { 366 if (isCaseSensitive && ch != strArr[strIdxStart]) { 367 return false; // Character mismatch 368 } 369 if (!isCaseSensitive && Character.toUpperCase(ch) 370 != Character.toUpperCase(strArr[strIdxStart])) { 371 return false; // Character mismatch 372 } 373 } 374 patIdxStart++; 375 strIdxStart++; 376 } 377 if (strIdxStart > strIdxEnd) { 378 // All characters in the string are used. Check if only '*'s are 379 // left in the pattern. If so, we succeeded. Otherwise failure. 380 for (int i = patIdxStart; i <= patIdxEnd; i++) { 381 if (patArr[i] != '*') { 382 return false; 383 } 384 } 385 return true; 386 } 387 388 // Process characters after last star 389 while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) { 390 if (ch != '?') { 391 if (isCaseSensitive && ch != strArr[strIdxEnd]) { 392 return false; // Character mismatch 393 } 394 if (!isCaseSensitive && Character.toUpperCase(ch) 395 != Character.toUpperCase(strArr[strIdxEnd])) { 396 return false; // Character mismatch 397 } 398 } 399 patIdxEnd--; 400 strIdxEnd--; 401 } 402 if (strIdxStart > strIdxEnd) { 403 // All characters in the string are used. Check if only '*'s are 404 // left in the pattern. If so, we succeeded. Otherwise failure. 405 for (int i = patIdxStart; i <= patIdxEnd; i++) { 406 if (patArr[i] != '*') { 407 return false; 408 } 409 } 410 return true; 411 } 412 413 // process pattern between stars. padIdxStart and patIdxEnd point 414 // always to a '*'. 415 while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) { 416 int patIdxTmp = -1; 417 for (int i = patIdxStart + 1; i <= patIdxEnd; i++) { 418 if (patArr[i] == '*') { 419 patIdxTmp = i; 420 break; 421 } 422 } 423 if (patIdxTmp == patIdxStart + 1) { 424 // Two stars next to each other, skip the first one. 425 patIdxStart++; 426 continue; 427 } 428 // Find the pattern between padIdxStart & padIdxTmp in str between 429 // strIdxStart & strIdxEnd 430 int patLength = (patIdxTmp - patIdxStart - 1); 431 int strLength = (strIdxEnd - strIdxStart + 1); 432 int foundIdx = -1; 433 strLoop: 434 for (int i = 0; i <= strLength - patLength; i++) { 435 for (int j = 0; j < patLength; j++) { 436 ch = patArr[patIdxStart + j + 1]; 437 if (ch != '?') { 438 if (isCaseSensitive && ch != strArr[strIdxStart + i 439 + j]) { 440 continue strLoop; 441 } 442 if (!isCaseSensitive 443 && Character.toUpperCase(ch) 444 != Character.toUpperCase(strArr[strIdxStart + i + j])) { 445 continue strLoop; 446 } 447 } 448 } 449 450 foundIdx = strIdxStart + i; 451 break; 452 } 453 454 if (foundIdx == -1) { 455 return false; 456 } 457 458 patIdxStart = patIdxTmp; 459 strIdxStart = foundIdx + patLength; 460 } 461 462 // All characters in the string are used. Check if only '*'s are left 463 // in the pattern. If so, we succeeded. Otherwise failure. 464 for (int i = patIdxStart; i <= patIdxEnd; i++) { 465 if (patArr[i] != '*') { 466 return false; 467 } 468 } 469 return true; 470 } 471 472 /** 473 * Breaks a path up into a Vector of path elements, tokenizing on 474 * <code>File.separator</code>. 475 * 476 * @param path Path to tokenize. Must not be <code>null</code>. 477 * 478 * @return a Vector of path elements from the tokenized path 479 */ 480 public static Vector tokenizePath (String path) { 481 return tokenizePath(path, File.separator); 482 } 483 484 /** 485 * Verifies that the specified filename represents an absolute path. 486 * Differs from new java.io.File("filename").isAbsolute() in that a path 487 * beginning with a double file separator--signifying a Windows UNC--must 488 * at minimum match "\\a\b" to be considered an absolute path. 489 * @param filename the filename to be checked. 490 * @return true if the filename represents an absolute path. 491 * @throws java.lang.NullPointerException if filename is null. 492 * @since Ant 1.6.3 493 */ 494 public static boolean isAbsolutePath(String filename) { 495 int len = filename.length(); 496 if (len == 0) { 497 return false; 498 } 499 char sep = File.separatorChar; 500 filename = filename.replace('/', sep).replace('\\', sep); 501 char c = filename.charAt(0); 502 if (!(onDos || onNetWare)) { 503 return (c == sep); 504 } 505 if (c == sep) { 506 if (!(onDos && len > 4 && filename.charAt(1) == sep)) { 507 return false; 508 } 509 int nextsep = filename.indexOf(sep, 2); 510 return nextsep > 2 && nextsep + 1 < len; 511 } 512 int colon = filename.indexOf(':'); 513 return (Character.isLetter(c) && colon == 1 514 && filename.length() > 2 && filename.charAt(2) == sep) 515 || (onNetWare && colon > 0); 516 } 517 518 /** 519 * Dissect the specified absolute path. 520 * @param path the path to dissect. 521 * @return String[] {root, remaining path}. 522 * @throws java.lang.NullPointerException if path is null. 523 */ 524 public static String[] dissect(String path) { 525 char sep = File.separatorChar; 526 path = path.replace('/', sep).replace('\\', sep); 527 528 // make sure we are dealing with an absolute path 529 if (!isAbsolutePath(path)) { 530 throw new IllegalArgumentException(path + " is not an absolute path"); 531 } 532 String root = null; 533 int colon = path.indexOf(':'); 534 if (colon > 0 && (onDos || onNetWare)) { 535 536 int next = colon + 1; 537 root = path.substring(0, next).toUpperCase(); 538 char[] ca = path.toCharArray(); 539 root += sep; 540 //remove the initial separator; the root has it. 541 next = (ca[next] == sep) ? next + 1 : next; 542 543 StringBuffer sbPath = new StringBuffer(); 544 // Eliminate consecutive slashes after the drive spec: 545 for (int i = next; i < ca.length; i++) { 546 if (ca[i] != sep || ca[i - 1] != sep) { 547 sbPath.append(ca[i]); 548 } 549 } 550 path = sbPath.toString(); 551 } else if (path.length() > 1 && path.charAt(1) == sep) { 552 // UNC drive 553 int nextsep = path.indexOf(sep, 2); 554 nextsep = path.indexOf(sep, nextsep + 1); 555 root = (nextsep > 2) ? path.substring(0, nextsep + 1) : path; 556 path = path.substring(root.length()); 557 } else { 558 root = File.separator; 559 path = path.substring(1); 560 } 561 return new String[] {root, path}; 562 } 563 564 565 /** 566 * Breaks a path up into a Vector of path elements, tokenizing on 567 * 568 * @param path Path to tokenize. Must not be <code>null</code>. 569 * @param separator the separator against which to tokenize. 570 * 571 * @return a Vector of path elements from the tokenized path 572 * @since Ant 1.6 573 */ 574 public static Vector tokenizePath (String path, String separator) { 575 Vector ret = new Vector(); 576 if (isAbsolutePath(path)) { 577 String[] s = dissect(path); 578 ret.add(s[0]); 579 path = s[1]; 580 } 581 StringTokenizer st = new StringTokenizer(path, separator); 582 while (st.hasMoreTokens()) { 583 ret.addElement(st.nextToken()); 584 } 585 return ret; 586 } 587 588 /** 589 * Same as {@link #tokenizePath tokenizePath} but hopefully faster. 590 */ 591 private static String[] tokenizePathAsArray(String path) { 592 String root = null; 593 if (isAbsolutePath(path)) { 594 String[] s = dissect(path); 595 root = s[0]; 596 path = s[1]; 597 } 598 char sep = File.separatorChar; 599 int start = 0; 600 int len = path.length(); 601 int count = 0; 602 for (int pos = 0; pos < len; pos++) { 603 if (path.charAt(pos) == sep) { 604 if (pos != start) { 605 count++; 606 } 607 start = pos + 1; 608 } 609 } 610 if (len != start) { 611 count++; 612 } 613 String[] l = new String[count + ((root == null) ? 0 : 1)]; 614 615 if (root != null) { 616 l[0] = root; 617 count = 1; 618 } else { 619 count = 0; 620 } 621 start = 0; 622 for (int pos = 0; pos < len; pos++) { 623 if (path.charAt(pos) == sep) { 624 if (pos != start) { 625 String tok = path.substring(start, pos); 626 l[count++] = tok; 627 } 628 start = pos + 1; 629 } 630 } 631 if (len != start) { 632 String tok = path.substring(start); 633 l[count/*++*/] = tok; 634 } 635 return l; 636 } 637 638 639 /** 640 * Returns dependency information on these two files. If src has been 641 * modified later than target, it returns true. If target doesn't exist, 642 * it likewise returns true. Otherwise, target is newer than src and 643 * is not out of date, thus the method returns false. It also returns 644 * false if the src file doesn't even exist, since how could the 645 * target then be out of date. 646 * 647 * @param src the original file 648 * @param target the file being compared against 649 * @param granularity the amount in seconds of slack we will give in 650 * determining out of dateness 651 * @return whether the target is out of date 652 */ 653 public static boolean isOutOfDate(File src, File target, int granularity) { 654 if (!src.exists()) { 655 return false; 656 } 657 if (!target.exists()) { 658 return true; 659 } 660 if ((src.lastModified() - granularity) > target.lastModified()) { 661 return true; 662 } 663 return false; 664 } 665 666 /** 667 * "Flattens" a string by removing all whitespace (space, tab, linefeed, 668 * carriage return, and formfeed). This uses StringTokenizer and the 669 * default set of tokens as documented in the single arguement constructor. 670 * 671 * @param input a String to remove all whitespace. 672 * @return a String that has had all whitespace removed. 673 */ 674 public static String removeWhitespace(String input) { 675 StringBuffer result = new StringBuffer(); 676 if (input != null) { 677 StringTokenizer st = new StringTokenizer(input); 678 while (st.hasMoreTokens()) { 679 result.append(st.nextToken()); 680 } 681 } 682 return result.toString(); 683 } 684 685 /** 686 * Tests if a string contains stars or question marks 687 * @param input a String which one wants to test for containing wildcard 688 * @return true if the string contains at least a star or a question mark 689 */ 690 public static boolean hasWildcards(String input) { 691 return (input.indexOf('*') != -1 || input.indexOf('?') != -1); 692 } 693 694 /** 695 * removes from a pattern all tokens to the right containing wildcards 696 * @param input the input string 697 * @return the leftmost part of the pattern without wildcards 698 */ 699 public static String rtrimWildcardTokens(String input) { 700 Vector v = tokenizePath(input, File.separator); 701 StringBuffer sb = new StringBuffer(); 702 for (int counter = 0; counter < v.size(); counter++) { 703 if (hasWildcards((String) v.elementAt(counter))) { 704 break; 705 } 706 if (counter > 0 && sb.charAt(sb.length() - 1) != File.separatorChar) { 707 sb.append(File.separator); 708 } 709 sb.append((String) v.elementAt(counter)); 710 } 711 return sb.toString(); 712 } 713 } 714