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 */ 017package org.apache.commons.io; 018 019import java.io.File; 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Stack; 024 025/** 026 * General filename and filepath manipulation utilities. 027 * <p> 028 * When dealing with filenames you can hit problems when moving from a Windows 029 * based development machine to a Unix based production machine. 030 * This class aims to help avoid those problems. 031 * <p> 032 * <b>NOTE</b>: You may be able to avoid using this class entirely simply by 033 * using JDK {@link java.io.File File} objects and the two argument constructor 034 * {@link java.io.File#File(java.io.File, java.lang.String) File(File,String)}. 035 * <p> 036 * Most methods on this class are designed to work the same on both Unix and Windows. 037 * Those that don't include 'System', 'Unix' or 'Windows' in their name. 038 * <p> 039 * Most methods recognise both separators (forward and back), and both 040 * sets of prefixes. See the javadoc of each method for details. 041 * <p> 042 * This class defines six components within a filename 043 * (example C:\dev\project\file.txt): 044 * <ul> 045 * <li>the prefix - C:\</li> 046 * <li>the path - dev\project\</li> 047 * <li>the full path - C:\dev\project\</li> 048 * <li>the name - file.txt</li> 049 * <li>the base name - file</li> 050 * <li>the extension - txt</li> 051 * </ul> 052 * Note that this class works best if directory filenames end with a separator. 053 * If you omit the last separator, it is impossible to determine if the filename 054 * corresponds to a file or a directory. As a result, we have chosen to say 055 * it corresponds to a file. 056 * <p> 057 * This class only supports Unix and Windows style names. 058 * Prefixes are matched as follows: 059 * <pre> 060 * Windows: 061 * a\b\c.txt --> "" --> relative 062 * \a\b\c.txt --> "\" --> current drive absolute 063 * C:a\b\c.txt --> "C:" --> drive relative 064 * C:\a\b\c.txt --> "C:\" --> absolute 065 * \\server\a\b\c.txt --> "\\server\" --> UNC 066 * 067 * Unix: 068 * a/b/c.txt --> "" --> relative 069 * /a/b/c.txt --> "/" --> absolute 070 * ~/a/b/c.txt --> "~/" --> current user 071 * ~ --> "~/" --> current user (slash added) 072 * ~user/a/b/c.txt --> "~user/" --> named user 073 * ~user --> "~user/" --> named user (slash added) 074 * </pre> 075 * Both prefix styles are matched always, irrespective of the machine that you are 076 * currently running on. 077 * <p> 078 * Origin of code: Excalibur, Alexandria, Tomcat, Commons-Utils. 079 * 080 * @since 1.1 081 */ 082public class FilenameUtils { 083 084 private static final int NOT_FOUND = -1; 085 086 /** 087 * The extension separator character. 088 * @since 1.4 089 */ 090 public static final char EXTENSION_SEPARATOR = '.'; 091 092 /** 093 * The extension separator String. 094 * @since 1.4 095 */ 096 public static final String EXTENSION_SEPARATOR_STR = Character.toString(EXTENSION_SEPARATOR); 097 098 /** 099 * The Unix separator character. 100 */ 101 private static final char UNIX_SEPARATOR = '/'; 102 103 /** 104 * The Windows separator character. 105 */ 106 private static final char WINDOWS_SEPARATOR = '\\'; 107 108 /** 109 * The system separator character. 110 */ 111 private static final char SYSTEM_SEPARATOR = File.separatorChar; 112 113 /** 114 * The separator character that is the opposite of the system separator. 115 */ 116 private static final char OTHER_SEPARATOR; 117 static { 118 if (isSystemWindows()) { 119 OTHER_SEPARATOR = UNIX_SEPARATOR; 120 } else { 121 OTHER_SEPARATOR = WINDOWS_SEPARATOR; 122 } 123 } 124 125 /** 126 * Instances should NOT be constructed in standard programming. 127 */ 128 public FilenameUtils() { 129 super(); 130 } 131 132 //----------------------------------------------------------------------- 133 /** 134 * Determines if Windows file system is in use. 135 * 136 * @return true if the system is Windows 137 */ 138 static boolean isSystemWindows() { 139 return SYSTEM_SEPARATOR == WINDOWS_SEPARATOR; 140 } 141 142 //----------------------------------------------------------------------- 143 /** 144 * Checks if the character is a separator. 145 * 146 * @param ch the character to check 147 * @return true if it is a separator character 148 */ 149 private static boolean isSeparator(final char ch) { 150 return ch == UNIX_SEPARATOR || ch == WINDOWS_SEPARATOR; 151 } 152 153 //----------------------------------------------------------------------- 154 /** 155 * Normalizes a path, removing double and single dot path steps. 156 * <p> 157 * This method normalizes a path to a standard format. 158 * The input may contain separators in either Unix or Windows format. 159 * The output will contain separators in the format of the system. 160 * <p> 161 * A trailing slash will be retained. 162 * A double slash will be merged to a single slash (but UNC names are handled). 163 * A single dot path segment will be removed. 164 * A double dot will cause that path segment and the one before to be removed. 165 * If the double dot has no parent path segment to work with, {@code null} 166 * is returned. 167 * <p> 168 * The output will be the same on both Unix and Windows except 169 * for the separator character. 170 * <pre> 171 * /foo// --> /foo/ 172 * /foo/./ --> /foo/ 173 * /foo/../bar --> /bar 174 * /foo/../bar/ --> /bar/ 175 * /foo/../bar/../baz --> /baz 176 * //foo//./bar --> /foo/bar 177 * /../ --> null 178 * ../foo --> null 179 * foo/bar/.. --> foo/ 180 * foo/../../bar --> null 181 * foo/../bar --> bar 182 * //server/foo/../bar --> //server/bar 183 * //server/../bar --> null 184 * C:\foo\..\bar --> C:\bar 185 * C:\..\bar --> null 186 * ~/foo/../bar/ --> ~/bar/ 187 * ~/../bar --> null 188 * </pre> 189 * (Note the file separator returned will be correct for Windows/Unix) 190 * 191 * @param filename the filename to normalize, null returns null 192 * @return the normalized filename, or null if invalid. Null bytes inside string will be removed 193 */ 194 public static String normalize(final String filename) { 195 return doNormalize(filename, SYSTEM_SEPARATOR, true); 196 } 197 /** 198 * Normalizes a path, removing double and single dot path steps. 199 * <p> 200 * This method normalizes a path to a standard format. 201 * The input may contain separators in either Unix or Windows format. 202 * The output will contain separators in the format specified. 203 * <p> 204 * A trailing slash will be retained. 205 * A double slash will be merged to a single slash (but UNC names are handled). 206 * A single dot path segment will be removed. 207 * A double dot will cause that path segment and the one before to be removed. 208 * If the double dot has no parent path segment to work with, {@code null} 209 * is returned. 210 * <p> 211 * The output will be the same on both Unix and Windows except 212 * for the separator character. 213 * <pre> 214 * /foo// --> /foo/ 215 * /foo/./ --> /foo/ 216 * /foo/../bar --> /bar 217 * /foo/../bar/ --> /bar/ 218 * /foo/../bar/../baz --> /baz 219 * //foo//./bar --> /foo/bar 220 * /../ --> null 221 * ../foo --> null 222 * foo/bar/.. --> foo/ 223 * foo/../../bar --> null 224 * foo/../bar --> bar 225 * //server/foo/../bar --> //server/bar 226 * //server/../bar --> null 227 * C:\foo\..\bar --> C:\bar 228 * C:\..\bar --> null 229 * ~/foo/../bar/ --> ~/bar/ 230 * ~/../bar --> null 231 * </pre> 232 * The output will be the same on both Unix and Windows including 233 * the separator character. 234 * 235 * @param filename the filename to normalize, null returns null 236 * @param unixSeparator {@code true} if a unix separator should 237 * be used or {@code false} if a windows separator should be used. 238 * @return the normalized filename, or null if invalid. Null bytes inside string will be removed 239 * @since 2.0 240 */ 241 public static String normalize(final String filename, final boolean unixSeparator) { 242 final char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR; 243 return doNormalize(filename, separator, true); 244 } 245 246 //----------------------------------------------------------------------- 247 /** 248 * Normalizes a path, removing double and single dot path steps, 249 * and removing any final directory separator. 250 * <p> 251 * This method normalizes a path to a standard format. 252 * The input may contain separators in either Unix or Windows format. 253 * The output will contain separators in the format of the system. 254 * <p> 255 * A trailing slash will be removed. 256 * A double slash will be merged to a single slash (but UNC names are handled). 257 * A single dot path segment will be removed. 258 * A double dot will cause that path segment and the one before to be removed. 259 * If the double dot has no parent path segment to work with, {@code null} 260 * is returned. 261 * <p> 262 * The output will be the same on both Unix and Windows except 263 * for the separator character. 264 * <pre> 265 * /foo// --> /foo 266 * /foo/./ --> /foo 267 * /foo/../bar --> /bar 268 * /foo/../bar/ --> /bar 269 * /foo/../bar/../baz --> /baz 270 * //foo//./bar --> /foo/bar 271 * /../ --> null 272 * ../foo --> null 273 * foo/bar/.. --> foo 274 * foo/../../bar --> null 275 * foo/../bar --> bar 276 * //server/foo/../bar --> //server/bar 277 * //server/../bar --> null 278 * C:\foo\..\bar --> C:\bar 279 * C:\..\bar --> null 280 * ~/foo/../bar/ --> ~/bar 281 * ~/../bar --> null 282 * </pre> 283 * (Note the file separator returned will be correct for Windows/Unix) 284 * 285 * @param filename the filename to normalize, null returns null 286 * @return the normalized filename, or null if invalid. Null bytes inside string will be removed 287 */ 288 public static String normalizeNoEndSeparator(final String filename) { 289 return doNormalize(filename, SYSTEM_SEPARATOR, false); 290 } 291 292 /** 293 * Normalizes a path, removing double and single dot path steps, 294 * and removing any final directory separator. 295 * <p> 296 * This method normalizes a path to a standard format. 297 * The input may contain separators in either Unix or Windows format. 298 * The output will contain separators in the format specified. 299 * <p> 300 * A trailing slash will be removed. 301 * A double slash will be merged to a single slash (but UNC names are handled). 302 * A single dot path segment will be removed. 303 * A double dot will cause that path segment and the one before to be removed. 304 * If the double dot has no parent path segment to work with, {@code null} 305 * is returned. 306 * <p> 307 * The output will be the same on both Unix and Windows including 308 * the separator character. 309 * <pre> 310 * /foo// --> /foo 311 * /foo/./ --> /foo 312 * /foo/../bar --> /bar 313 * /foo/../bar/ --> /bar 314 * /foo/../bar/../baz --> /baz 315 * //foo//./bar --> /foo/bar 316 * /../ --> null 317 * ../foo --> null 318 * foo/bar/.. --> foo 319 * foo/../../bar --> null 320 * foo/../bar --> bar 321 * //server/foo/../bar --> //server/bar 322 * //server/../bar --> null 323 * C:\foo\..\bar --> C:\bar 324 * C:\..\bar --> null 325 * ~/foo/../bar/ --> ~/bar 326 * ~/../bar --> null 327 * </pre> 328 * 329 * @param filename the filename to normalize, null returns null 330 * @param unixSeparator {@code true} if a unix separator should 331 * be used or {@code false} if a windows separator should be used. 332 * @return the normalized filename, or null if invalid. Null bytes inside string will be removed 333 * @since 2.0 334 */ 335 public static String normalizeNoEndSeparator(final String filename, final boolean unixSeparator) { 336 final char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR; 337 return doNormalize(filename, separator, false); 338 } 339 340 /** 341 * Internal method to perform the normalization. 342 * 343 * @param filename the filename 344 * @param separator The separator character to use 345 * @param keepSeparator true to keep the final separator 346 * @return the normalized filename. Null bytes inside string will be removed. 347 */ 348 private static String doNormalize(final String filename, final char separator, final boolean keepSeparator) { 349 if (filename == null) { 350 return null; 351 } 352 353 failIfNullBytePresent(filename); 354 355 int size = filename.length(); 356 if (size == 0) { 357 return filename; 358 } 359 final int prefix = getPrefixLength(filename); 360 if (prefix < 0) { 361 return null; 362 } 363 364 final char[] array = new char[size + 2]; // +1 for possible extra slash, +2 for arraycopy 365 filename.getChars(0, filename.length(), array, 0); 366 367 // fix separators throughout 368 final char otherSeparator = separator == SYSTEM_SEPARATOR ? OTHER_SEPARATOR : SYSTEM_SEPARATOR; 369 for (int i = 0; i < array.length; i++) { 370 if (array[i] == otherSeparator) { 371 array[i] = separator; 372 } 373 } 374 375 // add extra separator on the end to simplify code below 376 boolean lastIsDirectory = true; 377 if (array[size - 1] != separator) { 378 array[size++] = separator; 379 lastIsDirectory = false; 380 } 381 382 // adjoining slashes 383 for (int i = prefix + 1; i < size; i++) { 384 if (array[i] == separator && array[i - 1] == separator) { 385 System.arraycopy(array, i, array, i - 1, size - i); 386 size--; 387 i--; 388 } 389 } 390 391 // dot slash 392 for (int i = prefix + 1; i < size; i++) { 393 if (array[i] == separator && array[i - 1] == '.' && 394 (i == prefix + 1 || array[i - 2] == separator)) { 395 if (i == size - 1) { 396 lastIsDirectory = true; 397 } 398 System.arraycopy(array, i + 1, array, i - 1, size - i); 399 size -=2; 400 i--; 401 } 402 } 403 404 // double dot slash 405 outer: 406 for (int i = prefix + 2; i < size; i++) { 407 if (array[i] == separator && array[i - 1] == '.' && array[i - 2] == '.' && 408 (i == prefix + 2 || array[i - 3] == separator)) { 409 if (i == prefix + 2) { 410 return null; 411 } 412 if (i == size - 1) { 413 lastIsDirectory = true; 414 } 415 int j; 416 for (j = i - 4 ; j >= prefix; j--) { 417 if (array[j] == separator) { 418 // remove b/../ from a/b/../c 419 System.arraycopy(array, i + 1, array, j + 1, size - i); 420 size -= i - j; 421 i = j + 1; 422 continue outer; 423 } 424 } 425 // remove a/../ from a/../c 426 System.arraycopy(array, i + 1, array, prefix, size - i); 427 size -= i + 1 - prefix; 428 i = prefix + 1; 429 } 430 } 431 432 if (size <= 0) { // should never be less than 0 433 return ""; 434 } 435 if (size <= prefix) { // should never be less than prefix 436 return new String(array, 0, size); 437 } 438 if (lastIsDirectory && keepSeparator) { 439 return new String(array, 0, size); // keep trailing separator 440 } 441 return new String(array, 0, size - 1); // lose trailing separator 442 } 443 444 //----------------------------------------------------------------------- 445 /** 446 * Concatenates a filename to a base path using normal command line style rules. 447 * <p> 448 * The effect is equivalent to resultant directory after changing 449 * directory to the first argument, followed by changing directory to 450 * the second argument. 451 * <p> 452 * The first argument is the base path, the second is the path to concatenate. 453 * The returned path is always normalized via {@link #normalize(String)}, 454 * thus <code>..</code> is handled. 455 * <p> 456 * If <code>pathToAdd</code> is absolute (has an absolute prefix), then 457 * it will be normalized and returned. 458 * Otherwise, the paths will be joined, normalized and returned. 459 * <p> 460 * The output will be the same on both Unix and Windows except 461 * for the separator character. 462 * <pre> 463 * /foo/ + bar --> /foo/bar 464 * /foo + bar --> /foo/bar 465 * /foo + /bar --> /bar 466 * /foo + C:/bar --> C:/bar 467 * /foo + C:bar --> C:bar (*) 468 * /foo/a/ + ../bar --> foo/bar 469 * /foo/ + ../../bar --> null 470 * /foo/ + /bar --> /bar 471 * /foo/.. + /bar --> /bar 472 * /foo + bar/c.txt --> /foo/bar/c.txt 473 * /foo/c.txt + bar --> /foo/c.txt/bar (!) 474 * </pre> 475 * (*) Note that the Windows relative drive prefix is unreliable when 476 * used with this method. 477 * (!) Note that the first parameter must be a path. If it ends with a name, then 478 * the name will be built into the concatenated path. If this might be a problem, 479 * use {@link #getFullPath(String)} on the base path argument. 480 * 481 * @param basePath the base path to attach to, always treated as a path 482 * @param fullFilenameToAdd the filename (or path) to attach to the base 483 * @return the concatenated path, or null if invalid. Null bytes inside string will be removed 484 */ 485 public static String concat(final String basePath, final String fullFilenameToAdd) { 486 final int prefix = getPrefixLength(fullFilenameToAdd); 487 if (prefix < 0) { 488 return null; 489 } 490 if (prefix > 0) { 491 return normalize(fullFilenameToAdd); 492 } 493 if (basePath == null) { 494 return null; 495 } 496 final int len = basePath.length(); 497 if (len == 0) { 498 return normalize(fullFilenameToAdd); 499 } 500 final char ch = basePath.charAt(len - 1); 501 if (isSeparator(ch)) { 502 return normalize(basePath + fullFilenameToAdd); 503 } else { 504 return normalize(basePath + '/' + fullFilenameToAdd); 505 } 506 } 507 508 /** 509 * Determines whether the {@code parent} directory contains the {@code child} element (a file or directory). 510 * <p> 511 * The files names are expected to be normalized. 512 * </p> 513 * 514 * Edge cases: 515 * <ul> 516 * <li>A {@code directory} must not be null: if null, throw IllegalArgumentException</li> 517 * <li>A directory does not contain itself: return false</li> 518 * <li>A null child file is not contained in any parent: return false</li> 519 * </ul> 520 * 521 * @param canonicalParent 522 * the file to consider as the parent. 523 * @param canonicalChild 524 * the file to consider as the child. 525 * @return true is the candidate leaf is under by the specified composite. False otherwise. 526 * @throws IOException 527 * if an IO error occurs while checking the files. 528 * @since 2.2 529 * @see FileUtils#directoryContains(File, File) 530 */ 531 public static boolean directoryContains(final String canonicalParent, final String canonicalChild) 532 throws IOException { 533 534 // Fail fast against NullPointerException 535 if (canonicalParent == null) { 536 throw new IllegalArgumentException("Directory must not be null"); 537 } 538 539 if (canonicalChild == null) { 540 return false; 541 } 542 543 if (IOCase.SYSTEM.checkEquals(canonicalParent, canonicalChild)) { 544 return false; 545 } 546 547 return IOCase.SYSTEM.checkStartsWith(canonicalChild, canonicalParent); 548 } 549 550 //----------------------------------------------------------------------- 551 /** 552 * Converts all separators to the Unix separator of forward slash. 553 * 554 * @param path the path to be changed, null ignored 555 * @return the updated path 556 */ 557 public static String separatorsToUnix(final String path) { 558 if (path == null || path.indexOf(WINDOWS_SEPARATOR) == NOT_FOUND) { 559 return path; 560 } 561 return path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR); 562 } 563 564 /** 565 * Converts all separators to the Windows separator of backslash. 566 * 567 * @param path the path to be changed, null ignored 568 * @return the updated path 569 */ 570 public static String separatorsToWindows(final String path) { 571 if (path == null || path.indexOf(UNIX_SEPARATOR) == NOT_FOUND) { 572 return path; 573 } 574 return path.replace(UNIX_SEPARATOR, WINDOWS_SEPARATOR); 575 } 576 577 /** 578 * Converts all separators to the system separator. 579 * 580 * @param path the path to be changed, null ignored 581 * @return the updated path 582 */ 583 public static String separatorsToSystem(final String path) { 584 if (path == null) { 585 return null; 586 } 587 if (isSystemWindows()) { 588 return separatorsToWindows(path); 589 } else { 590 return separatorsToUnix(path); 591 } 592 } 593 594 //----------------------------------------------------------------------- 595 /** 596 * Returns the length of the filename prefix, such as <code>C:/</code> or <code>~/</code>. 597 * <p> 598 * This method will handle a file in either Unix or Windows format. 599 * <p> 600 * The prefix length includes the first slash in the full filename 601 * if applicable. Thus, it is possible that the length returned is greater 602 * than the length of the input string. 603 * <pre> 604 * Windows: 605 * a\b\c.txt --> "" --> relative 606 * \a\b\c.txt --> "\" --> current drive absolute 607 * C:a\b\c.txt --> "C:" --> drive relative 608 * C:\a\b\c.txt --> "C:\" --> absolute 609 * \\server\a\b\c.txt --> "\\server\" --> UNC 610 * \\\a\b\c.txt --> error, length = -1 611 * 612 * Unix: 613 * a/b/c.txt --> "" --> relative 614 * /a/b/c.txt --> "/" --> absolute 615 * ~/a/b/c.txt --> "~/" --> current user 616 * ~ --> "~/" --> current user (slash added) 617 * ~user/a/b/c.txt --> "~user/" --> named user 618 * ~user --> "~user/" --> named user (slash added) 619 * //server/a/b/c.txt --> "//server/" 620 * ///a/b/c.txt --> error, length = -1 621 * </pre> 622 * <p> 623 * The output will be the same irrespective of the machine that the code is running on. 624 * ie. both Unix and Windows prefixes are matched regardless. 625 * 626 * Note that a leading // (or \\) is used to indicate a UNC name on Windows. 627 * These must be followed by a server name, so double-slashes are not collapsed 628 * to a single slash at the start of the filename. 629 * 630 * @param filename the filename to find the prefix in, null returns -1 631 * @return the length of the prefix, -1 if invalid or null 632 */ 633 public static int getPrefixLength(final String filename) { 634 if (filename == null) { 635 return NOT_FOUND; 636 } 637 final int len = filename.length(); 638 if (len == 0) { 639 return 0; 640 } 641 char ch0 = filename.charAt(0); 642 if (ch0 == ':') { 643 return NOT_FOUND; 644 } 645 if (len == 1) { 646 if (ch0 == '~') { 647 return 2; // return a length greater than the input 648 } 649 return isSeparator(ch0) ? 1 : 0; 650 } else { 651 if (ch0 == '~') { 652 int posUnix = filename.indexOf(UNIX_SEPARATOR, 1); 653 int posWin = filename.indexOf(WINDOWS_SEPARATOR, 1); 654 if (posUnix == NOT_FOUND && posWin == NOT_FOUND) { 655 return len + 1; // return a length greater than the input 656 } 657 posUnix = posUnix == NOT_FOUND ? posWin : posUnix; 658 posWin = posWin == NOT_FOUND ? posUnix : posWin; 659 return Math.min(posUnix, posWin) + 1; 660 } 661 final char ch1 = filename.charAt(1); 662 if (ch1 == ':') { 663 ch0 = Character.toUpperCase(ch0); 664 if (ch0 >= 'A' && ch0 <= 'Z') { 665 if (len == 2 || isSeparator(filename.charAt(2)) == false) { 666 return 2; 667 } 668 return 3; 669 } else if (ch0 == UNIX_SEPARATOR) { 670 return 1; 671 } 672 return NOT_FOUND; 673 674 } else if (isSeparator(ch0) && isSeparator(ch1)) { 675 int posUnix = filename.indexOf(UNIX_SEPARATOR, 2); 676 int posWin = filename.indexOf(WINDOWS_SEPARATOR, 2); 677 if (posUnix == NOT_FOUND && posWin == NOT_FOUND || posUnix == 2 || posWin == 2) { 678 return NOT_FOUND; 679 } 680 posUnix = posUnix == NOT_FOUND ? posWin : posUnix; 681 posWin = posWin == NOT_FOUND ? posUnix : posWin; 682 return Math.min(posUnix, posWin) + 1; 683 } else { 684 return isSeparator(ch0) ? 1 : 0; 685 } 686 } 687 } 688 689 /** 690 * Returns the index of the last directory separator character. 691 * <p> 692 * This method will handle a file in either Unix or Windows format. 693 * The position of the last forward or backslash is returned. 694 * <p> 695 * The output will be the same irrespective of the machine that the code is running on. 696 * 697 * @param filename the filename to find the last path separator in, null returns -1 698 * @return the index of the last separator character, or -1 if there 699 * is no such character 700 */ 701 public static int indexOfLastSeparator(final String filename) { 702 if (filename == null) { 703 return NOT_FOUND; 704 } 705 final int lastUnixPos = filename.lastIndexOf(UNIX_SEPARATOR); 706 final int lastWindowsPos = filename.lastIndexOf(WINDOWS_SEPARATOR); 707 return Math.max(lastUnixPos, lastWindowsPos); 708 } 709 710 /** 711 * Returns the index of the last extension separator character, which is a dot. 712 * <p> 713 * This method also checks that there is no directory separator after the last dot. To do this it uses 714 * {@link #indexOfLastSeparator(String)} which will handle a file in either Unix or Windows format. 715 * </p> 716 * <p> 717 * The output will be the same irrespective of the machine that the code is running on. 718 * </p> 719 * 720 * @param filename 721 * the filename to find the last extension separator in, null returns -1 722 * @return the index of the last extension separator character, or -1 if there is no such character 723 */ 724 public static int indexOfExtension(final String filename) { 725 if (filename == null) { 726 return NOT_FOUND; 727 } 728 final int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR); 729 final int lastSeparator = indexOfLastSeparator(filename); 730 return lastSeparator > extensionPos ? NOT_FOUND : extensionPos; 731 } 732 733 //----------------------------------------------------------------------- 734 /** 735 * Gets the prefix from a full filename, such as <code>C:/</code> 736 * or <code>~/</code>. 737 * <p> 738 * This method will handle a file in either Unix or Windows format. 739 * The prefix includes the first slash in the full filename where applicable. 740 * <pre> 741 * Windows: 742 * a\b\c.txt --> "" --> relative 743 * \a\b\c.txt --> "\" --> current drive absolute 744 * C:a\b\c.txt --> "C:" --> drive relative 745 * C:\a\b\c.txt --> "C:\" --> absolute 746 * \\server\a\b\c.txt --> "\\server\" --> UNC 747 * 748 * Unix: 749 * a/b/c.txt --> "" --> relative 750 * /a/b/c.txt --> "/" --> absolute 751 * ~/a/b/c.txt --> "~/" --> current user 752 * ~ --> "~/" --> current user (slash added) 753 * ~user/a/b/c.txt --> "~user/" --> named user 754 * ~user --> "~user/" --> named user (slash added) 755 * </pre> 756 * <p> 757 * The output will be the same irrespective of the machine that the code is running on. 758 * ie. both Unix and Windows prefixes are matched regardless. 759 * 760 * @param filename the filename to query, null returns null 761 * @return the prefix of the file, null if invalid. Null bytes inside string will be removed 762 */ 763 public static String getPrefix(final String filename) { 764 if (filename == null) { 765 return null; 766 } 767 final int len = getPrefixLength(filename); 768 if (len < 0) { 769 return null; 770 } 771 if (len > filename.length()) { 772 failIfNullBytePresent(filename + UNIX_SEPARATOR); 773 return filename + UNIX_SEPARATOR; 774 } 775 final String path = filename.substring(0, len); 776 failIfNullBytePresent(path); 777 return path; 778 } 779 780 /** 781 * Gets the path from a full filename, which excludes the prefix. 782 * <p> 783 * This method will handle a file in either Unix or Windows format. 784 * The method is entirely text based, and returns the text before and 785 * including the last forward or backslash. 786 * <pre> 787 * C:\a\b\c.txt --> a\b\ 788 * ~/a/b/c.txt --> a/b/ 789 * a.txt --> "" 790 * a/b/c --> a/b/ 791 * a/b/c/ --> a/b/c/ 792 * </pre> 793 * <p> 794 * The output will be the same irrespective of the machine that the code is running on. 795 * <p> 796 * This method drops the prefix from the result. 797 * See {@link #getFullPath(String)} for the method that retains the prefix. 798 * 799 * @param filename the filename to query, null returns null 800 * @return the path of the file, an empty string if none exists, null if invalid. 801 * Null bytes inside string will be removed 802 */ 803 public static String getPath(final String filename) { 804 return doGetPath(filename, 1); 805 } 806 807 /** 808 * Gets the path from a full filename, which excludes the prefix, and 809 * also excluding the final directory separator. 810 * <p> 811 * This method will handle a file in either Unix or Windows format. 812 * The method is entirely text based, and returns the text before the 813 * last forward or backslash. 814 * <pre> 815 * C:\a\b\c.txt --> a\b 816 * ~/a/b/c.txt --> a/b 817 * a.txt --> "" 818 * a/b/c --> a/b 819 * a/b/c/ --> a/b/c 820 * </pre> 821 * <p> 822 * The output will be the same irrespective of the machine that the code is running on. 823 * <p> 824 * This method drops the prefix from the result. 825 * See {@link #getFullPathNoEndSeparator(String)} for the method that retains the prefix. 826 * 827 * @param filename the filename to query, null returns null 828 * @return the path of the file, an empty string if none exists, null if invalid. 829 * Null bytes inside string will be removed 830 */ 831 public static String getPathNoEndSeparator(final String filename) { 832 return doGetPath(filename, 0); 833 } 834 835 /** 836 * Does the work of getting the path. 837 * 838 * @param filename the filename 839 * @param separatorAdd 0 to omit the end separator, 1 to return it 840 * @return the path. Null bytes inside string will be removed 841 */ 842 private static String doGetPath(final String filename, final int separatorAdd) { 843 if (filename == null) { 844 return null; 845 } 846 final int prefix = getPrefixLength(filename); 847 if (prefix < 0) { 848 return null; 849 } 850 final int index = indexOfLastSeparator(filename); 851 final int endIndex = index+separatorAdd; 852 if (prefix >= filename.length() || index < 0 || prefix >= endIndex) { 853 return ""; 854 } 855 final String path = filename.substring(prefix, endIndex); 856 failIfNullBytePresent(path); 857 return path; 858 } 859 860 /** 861 * Gets the full path from a full filename, which is the prefix + path. 862 * <p> 863 * This method will handle a file in either Unix or Windows format. 864 * The method is entirely text based, and returns the text before and 865 * including the last forward or backslash. 866 * <pre> 867 * C:\a\b\c.txt --> C:\a\b\ 868 * ~/a/b/c.txt --> ~/a/b/ 869 * a.txt --> "" 870 * a/b/c --> a/b/ 871 * a/b/c/ --> a/b/c/ 872 * C: --> C: 873 * C:\ --> C:\ 874 * ~ --> ~/ 875 * ~/ --> ~/ 876 * ~user --> ~user/ 877 * ~user/ --> ~user/ 878 * </pre> 879 * <p> 880 * The output will be the same irrespective of the machine that the code is running on. 881 * 882 * @param filename the filename to query, null returns null 883 * @return the path of the file, an empty string if none exists, null if invalid 884 */ 885 public static String getFullPath(final String filename) { 886 return doGetFullPath(filename, true); 887 } 888 889 /** 890 * Gets the full path from a full filename, which is the prefix + path, 891 * and also excluding the final directory separator. 892 * <p> 893 * This method will handle a file in either Unix or Windows format. 894 * The method is entirely text based, and returns the text before the 895 * last forward or backslash. 896 * <pre> 897 * C:\a\b\c.txt --> C:\a\b 898 * ~/a/b/c.txt --> ~/a/b 899 * a.txt --> "" 900 * a/b/c --> a/b 901 * a/b/c/ --> a/b/c 902 * C: --> C: 903 * C:\ --> C:\ 904 * ~ --> ~ 905 * ~/ --> ~ 906 * ~user --> ~user 907 * ~user/ --> ~user 908 * </pre> 909 * <p> 910 * The output will be the same irrespective of the machine that the code is running on. 911 * 912 * @param filename the filename to query, null returns null 913 * @return the path of the file, an empty string if none exists, null if invalid 914 */ 915 public static String getFullPathNoEndSeparator(final String filename) { 916 return doGetFullPath(filename, false); 917 } 918 919 /** 920 * Does the work of getting the path. 921 * 922 * @param filename the filename 923 * @param includeSeparator true to include the end separator 924 * @return the path 925 */ 926 private static String doGetFullPath(final String filename, final boolean includeSeparator) { 927 if (filename == null) { 928 return null; 929 } 930 final int prefix = getPrefixLength(filename); 931 if (prefix < 0) { 932 return null; 933 } 934 if (prefix >= filename.length()) { 935 if (includeSeparator) { 936 return getPrefix(filename); // add end slash if necessary 937 } else { 938 return filename; 939 } 940 } 941 final int index = indexOfLastSeparator(filename); 942 if (index < 0) { 943 return filename.substring(0, prefix); 944 } 945 int end = index + (includeSeparator ? 1 : 0); 946 if (end == 0) { 947 end++; 948 } 949 return filename.substring(0, end); 950 } 951 952 /** 953 * Gets the name minus the path from a full filename. 954 * <p> 955 * This method will handle a file in either Unix or Windows format. 956 * The text after the last forward or backslash is returned. 957 * <pre> 958 * a/b/c.txt --> c.txt 959 * a.txt --> a.txt 960 * a/b/c --> c 961 * a/b/c/ --> "" 962 * </pre> 963 * <p> 964 * The output will be the same irrespective of the machine that the code is running on. 965 * 966 * @param filename the filename to query, null returns null 967 * @return the name of the file without the path, or an empty string if none exists. 968 * Null bytes inside string will be removed 969 */ 970 public static String getName(final String filename) { 971 if (filename == null) { 972 return null; 973 } 974 failIfNullBytePresent(filename); 975 final int index = indexOfLastSeparator(filename); 976 return filename.substring(index + 1); 977 } 978 979 /** 980 * Check the input for null bytes, a sign of unsanitized data being passed to to file level functions. 981 * 982 * This may be used for poison byte attacks. 983 * @param path the path to check 984 */ 985 private static void failIfNullBytePresent(final String path) { 986 final int len = path.length(); 987 for (int i = 0; i < len; i++) { 988 if (path.charAt(i) == 0) { 989 throw new IllegalArgumentException("Null byte present in file/path name. There are no " + 990 "known legitimate use cases for such data, but several injection attacks may use it"); 991 } 992 } 993 } 994 995 /** 996 * Gets the base name, minus the full path and extension, from a full filename. 997 * <p> 998 * This method will handle a file in either Unix or Windows format. 999 * The text after the last forward or backslash and before the last dot is returned. 1000 * <pre> 1001 * a/b/c.txt --> c 1002 * a.txt --> a 1003 * a/b/c --> c 1004 * a/b/c/ --> "" 1005 * </pre> 1006 * <p> 1007 * The output will be the same irrespective of the machine that the code is running on. 1008 * 1009 * @param filename the filename to query, null returns null 1010 * @return the name of the file without the path, or an empty string if none exists. Null bytes inside string 1011 * will be removed 1012 */ 1013 public static String getBaseName(final String filename) { 1014 return removeExtension(getName(filename)); 1015 } 1016 1017 /** 1018 * Gets the extension of a filename. 1019 * <p> 1020 * This method returns the textual part of the filename after the last dot. 1021 * There must be no directory separator after the dot. 1022 * <pre> 1023 * foo.txt --> "txt" 1024 * a/b/c.jpg --> "jpg" 1025 * a/b.txt/c --> "" 1026 * a/b/c --> "" 1027 * </pre> 1028 * <p> 1029 * The output will be the same irrespective of the machine that the code is running on. 1030 * 1031 * @param filename the filename to retrieve the extension of. 1032 * @return the extension of the file or an empty string if none exists or {@code null} 1033 * if the filename is {@code null}. 1034 */ 1035 public static String getExtension(final String filename) { 1036 if (filename == null) { 1037 return null; 1038 } 1039 final int index = indexOfExtension(filename); 1040 if (index == NOT_FOUND) { 1041 return ""; 1042 } else { 1043 return filename.substring(index + 1); 1044 } 1045 } 1046 1047 //----------------------------------------------------------------------- 1048 /** 1049 * Removes the extension from a filename. 1050 * <p> 1051 * This method returns the textual part of the filename before the last dot. 1052 * There must be no directory separator after the dot. 1053 * <pre> 1054 * foo.txt --> foo 1055 * a\b\c.jpg --> a\b\c 1056 * a\b\c --> a\b\c 1057 * a.b\c --> a.b\c 1058 * </pre> 1059 * <p> 1060 * The output will be the same irrespective of the machine that the code is running on. 1061 * 1062 * @param filename the filename to query, null returns null 1063 * @return the filename minus the extension 1064 */ 1065 public static String removeExtension(final String filename) { 1066 if (filename == null) { 1067 return null; 1068 } 1069 failIfNullBytePresent(filename); 1070 1071 final int index = indexOfExtension(filename); 1072 if (index == NOT_FOUND) { 1073 return filename; 1074 } else { 1075 return filename.substring(0, index); 1076 } 1077 } 1078 1079 //----------------------------------------------------------------------- 1080 /** 1081 * Checks whether two filenames are equal exactly. 1082 * <p> 1083 * No processing is performed on the filenames other than comparison, 1084 * thus this is merely a null-safe case-sensitive equals. 1085 * 1086 * @param filename1 the first filename to query, may be null 1087 * @param filename2 the second filename to query, may be null 1088 * @return true if the filenames are equal, null equals null 1089 * @see IOCase#SENSITIVE 1090 */ 1091 public static boolean equals(final String filename1, final String filename2) { 1092 return equals(filename1, filename2, false, IOCase.SENSITIVE); 1093 } 1094 1095 /** 1096 * Checks whether two filenames are equal using the case rules of the system. 1097 * <p> 1098 * No processing is performed on the filenames other than comparison. 1099 * The check is case-sensitive on Unix and case-insensitive on Windows. 1100 * 1101 * @param filename1 the first filename to query, may be null 1102 * @param filename2 the second filename to query, may be null 1103 * @return true if the filenames are equal, null equals null 1104 * @see IOCase#SYSTEM 1105 */ 1106 public static boolean equalsOnSystem(final String filename1, final String filename2) { 1107 return equals(filename1, filename2, false, IOCase.SYSTEM); 1108 } 1109 1110 //----------------------------------------------------------------------- 1111 /** 1112 * Checks whether two filenames are equal after both have been normalized. 1113 * <p> 1114 * Both filenames are first passed to {@link #normalize(String)}. 1115 * The check is then performed in a case-sensitive manner. 1116 * 1117 * @param filename1 the first filename to query, may be null 1118 * @param filename2 the second filename to query, may be null 1119 * @return true if the filenames are equal, null equals null 1120 * @see IOCase#SENSITIVE 1121 */ 1122 public static boolean equalsNormalized(final String filename1, final String filename2) { 1123 return equals(filename1, filename2, true, IOCase.SENSITIVE); 1124 } 1125 1126 /** 1127 * Checks whether two filenames are equal after both have been normalized 1128 * and using the case rules of the system. 1129 * <p> 1130 * Both filenames are first passed to {@link #normalize(String)}. 1131 * The check is then performed case-sensitive on Unix and 1132 * case-insensitive on Windows. 1133 * 1134 * @param filename1 the first filename to query, may be null 1135 * @param filename2 the second filename to query, may be null 1136 * @return true if the filenames are equal, null equals null 1137 * @see IOCase#SYSTEM 1138 */ 1139 public static boolean equalsNormalizedOnSystem(final String filename1, final String filename2) { 1140 return equals(filename1, filename2, true, IOCase.SYSTEM); 1141 } 1142 1143 /** 1144 * Checks whether two filenames are equal, optionally normalizing and providing 1145 * control over the case-sensitivity. 1146 * 1147 * @param filename1 the first filename to query, may be null 1148 * @param filename2 the second filename to query, may be null 1149 * @param normalized whether to normalize the filenames 1150 * @param caseSensitivity what case sensitivity rule to use, null means case-sensitive 1151 * @return true if the filenames are equal, null equals null 1152 * @since 1.3 1153 */ 1154 public static boolean equals( 1155 String filename1, String filename2, 1156 final boolean normalized, IOCase caseSensitivity) { 1157 1158 if (filename1 == null || filename2 == null) { 1159 return filename1 == null && filename2 == null; 1160 } 1161 if (normalized) { 1162 filename1 = normalize(filename1); 1163 filename2 = normalize(filename2); 1164 if (filename1 == null || filename2 == null) { 1165 throw new NullPointerException( 1166 "Error normalizing one or both of the file names"); 1167 } 1168 } 1169 if (caseSensitivity == null) { 1170 caseSensitivity = IOCase.SENSITIVE; 1171 } 1172 return caseSensitivity.checkEquals(filename1, filename2); 1173 } 1174 1175 //----------------------------------------------------------------------- 1176 /** 1177 * Checks whether the extension of the filename is that specified. 1178 * <p> 1179 * This method obtains the extension as the textual part of the filename 1180 * after the last dot. There must be no directory separator after the dot. 1181 * The extension check is case-sensitive on all platforms. 1182 * 1183 * @param filename the filename to query, null returns false 1184 * @param extension the extension to check for, null or empty checks for no extension 1185 * @return true if the filename has the specified extension 1186 * @throws java.lang.IllegalArgumentException if the supplied filename contains null bytes 1187 */ 1188 public static boolean isExtension(final String filename, final String extension) { 1189 if (filename == null) { 1190 return false; 1191 } 1192 failIfNullBytePresent(filename); 1193 1194 if (extension == null || extension.isEmpty()) { 1195 return indexOfExtension(filename) == NOT_FOUND; 1196 } 1197 final String fileExt = getExtension(filename); 1198 return fileExt.equals(extension); 1199 } 1200 1201 /** 1202 * Checks whether the extension of the filename is one of those specified. 1203 * <p> 1204 * This method obtains the extension as the textual part of the filename 1205 * after the last dot. There must be no directory separator after the dot. 1206 * The extension check is case-sensitive on all platforms. 1207 * 1208 * @param filename the filename to query, null returns false 1209 * @param extensions the extensions to check for, null checks for no extension 1210 * @return true if the filename is one of the extensions 1211 * @throws java.lang.IllegalArgumentException if the supplied filename contains null bytes 1212 */ 1213 public static boolean isExtension(final String filename, final String[] extensions) { 1214 if (filename == null) { 1215 return false; 1216 } 1217 failIfNullBytePresent(filename); 1218 1219 if (extensions == null || extensions.length == 0) { 1220 return indexOfExtension(filename) == NOT_FOUND; 1221 } 1222 final String fileExt = getExtension(filename); 1223 for (final String extension : extensions) { 1224 if (fileExt.equals(extension)) { 1225 return true; 1226 } 1227 } 1228 return false; 1229 } 1230 1231 /** 1232 * Checks whether the extension of the filename is one of those specified. 1233 * <p> 1234 * This method obtains the extension as the textual part of the filename 1235 * after the last dot. There must be no directory separator after the dot. 1236 * The extension check is case-sensitive on all platforms. 1237 * 1238 * @param filename the filename to query, null returns false 1239 * @param extensions the extensions to check for, null checks for no extension 1240 * @return true if the filename is one of the extensions 1241 * @throws java.lang.IllegalArgumentException if the supplied filename contains null bytes 1242 */ 1243 public static boolean isExtension(final String filename, final Collection<String> extensions) { 1244 if (filename == null) { 1245 return false; 1246 } 1247 failIfNullBytePresent(filename); 1248 1249 if (extensions == null || extensions.isEmpty()) { 1250 return indexOfExtension(filename) == NOT_FOUND; 1251 } 1252 final String fileExt = getExtension(filename); 1253 for (final String extension : extensions) { 1254 if (fileExt.equals(extension)) { 1255 return true; 1256 } 1257 } 1258 return false; 1259 } 1260 1261 //----------------------------------------------------------------------- 1262 /** 1263 * Checks a filename to see if it matches the specified wildcard matcher, 1264 * always testing case-sensitive. 1265 * <p> 1266 * The wildcard matcher uses the characters '?' and '*' to represent a 1267 * single or multiple (zero or more) wildcard characters. 1268 * This is the same as often found on Dos/Unix command lines. 1269 * The check is case-sensitive always. 1270 * <pre> 1271 * wildcardMatch("c.txt", "*.txt") --> true 1272 * wildcardMatch("c.txt", "*.jpg") --> false 1273 * wildcardMatch("a/b/c.txt", "a/b/*") --> true 1274 * wildcardMatch("c.txt", "*.???") --> true 1275 * wildcardMatch("c.txt", "*.????") --> false 1276 * </pre> 1277 * N.B. the sequence "*?" does not work properly at present in match strings. 1278 * 1279 * @param filename the filename to match on 1280 * @param wildcardMatcher the wildcard string to match against 1281 * @return true if the filename matches the wildcard string 1282 * @see IOCase#SENSITIVE 1283 */ 1284 public static boolean wildcardMatch(final String filename, final String wildcardMatcher) { 1285 return wildcardMatch(filename, wildcardMatcher, IOCase.SENSITIVE); 1286 } 1287 1288 /** 1289 * Checks a filename to see if it matches the specified wildcard matcher 1290 * using the case rules of the system. 1291 * <p> 1292 * The wildcard matcher uses the characters '?' and '*' to represent a 1293 * single or multiple (zero or more) wildcard characters. 1294 * This is the same as often found on Dos/Unix command lines. 1295 * The check is case-sensitive on Unix and case-insensitive on Windows. 1296 * <pre> 1297 * wildcardMatch("c.txt", "*.txt") --> true 1298 * wildcardMatch("c.txt", "*.jpg") --> false 1299 * wildcardMatch("a/b/c.txt", "a/b/*") --> true 1300 * wildcardMatch("c.txt", "*.???") --> true 1301 * wildcardMatch("c.txt", "*.????") --> false 1302 * </pre> 1303 * N.B. the sequence "*?" does not work properly at present in match strings. 1304 * 1305 * @param filename the filename to match on 1306 * @param wildcardMatcher the wildcard string to match against 1307 * @return true if the filename matches the wildcard string 1308 * @see IOCase#SYSTEM 1309 */ 1310 public static boolean wildcardMatchOnSystem(final String filename, final String wildcardMatcher) { 1311 return wildcardMatch(filename, wildcardMatcher, IOCase.SYSTEM); 1312 } 1313 1314 /** 1315 * Checks a filename to see if it matches the specified wildcard matcher 1316 * allowing control over case-sensitivity. 1317 * <p> 1318 * The wildcard matcher uses the characters '?' and '*' to represent a 1319 * single or multiple (zero or more) wildcard characters. 1320 * N.B. the sequence "*?" does not work properly at present in match strings. 1321 * 1322 * @param filename the filename to match on 1323 * @param wildcardMatcher the wildcard string to match against 1324 * @param caseSensitivity what case sensitivity rule to use, null means case-sensitive 1325 * @return true if the filename matches the wildcard string 1326 * @since 1.3 1327 */ 1328 public static boolean wildcardMatch(final String filename, final String wildcardMatcher, IOCase caseSensitivity) { 1329 if (filename == null && wildcardMatcher == null) { 1330 return true; 1331 } 1332 if (filename == null || wildcardMatcher == null) { 1333 return false; 1334 } 1335 if (caseSensitivity == null) { 1336 caseSensitivity = IOCase.SENSITIVE; 1337 } 1338 final String[] wcs = splitOnTokens(wildcardMatcher); 1339 boolean anyChars = false; 1340 int textIdx = 0; 1341 int wcsIdx = 0; 1342 final Stack<int[]> backtrack = new Stack<>(); 1343 1344 // loop around a backtrack stack, to handle complex * matching 1345 do { 1346 if (backtrack.size() > 0) { 1347 final int[] array = backtrack.pop(); 1348 wcsIdx = array[0]; 1349 textIdx = array[1]; 1350 anyChars = true; 1351 } 1352 1353 // loop whilst tokens and text left to process 1354 while (wcsIdx < wcs.length) { 1355 1356 if (wcs[wcsIdx].equals("?")) { 1357 // ? so move to next text char 1358 textIdx++; 1359 if (textIdx > filename.length()) { 1360 break; 1361 } 1362 anyChars = false; 1363 1364 } else if (wcs[wcsIdx].equals("*")) { 1365 // set any chars status 1366 anyChars = true; 1367 if (wcsIdx == wcs.length - 1) { 1368 textIdx = filename.length(); 1369 } 1370 1371 } else { 1372 // matching text token 1373 if (anyChars) { 1374 // any chars then try to locate text token 1375 textIdx = caseSensitivity.checkIndexOf(filename, textIdx, wcs[wcsIdx]); 1376 if (textIdx == NOT_FOUND) { 1377 // token not found 1378 break; 1379 } 1380 final int repeat = caseSensitivity.checkIndexOf(filename, textIdx + 1, wcs[wcsIdx]); 1381 if (repeat >= 0) { 1382 backtrack.push(new int[] {wcsIdx, repeat}); 1383 } 1384 } else { 1385 // matching from current position 1386 if (!caseSensitivity.checkRegionMatches(filename, textIdx, wcs[wcsIdx])) { 1387 // couldnt match token 1388 break; 1389 } 1390 } 1391 1392 // matched text token, move text index to end of matched token 1393 textIdx += wcs[wcsIdx].length(); 1394 anyChars = false; 1395 } 1396 1397 wcsIdx++; 1398 } 1399 1400 // full match 1401 if (wcsIdx == wcs.length && textIdx == filename.length()) { 1402 return true; 1403 } 1404 1405 } while (backtrack.size() > 0); 1406 1407 return false; 1408 } 1409 1410 /** 1411 * Splits a string into a number of tokens. 1412 * The text is split by '?' and '*'. 1413 * Where multiple '*' occur consecutively they are collapsed into a single '*'. 1414 * 1415 * @param text the text to split 1416 * @return the array of tokens, never null 1417 */ 1418 static String[] splitOnTokens(final String text) { 1419 // used by wildcardMatch 1420 // package level so a unit test may run on this 1421 1422 if (text.indexOf('?') == NOT_FOUND && text.indexOf('*') == NOT_FOUND) { 1423 return new String[] { text }; 1424 } 1425 1426 final char[] array = text.toCharArray(); 1427 final ArrayList<String> list = new ArrayList<>(); 1428 final StringBuilder buffer = new StringBuilder(); 1429 char prevChar = 0; 1430 for (final char ch : array) { 1431 if (ch == '?' || ch == '*') { 1432 if (buffer.length() != 0) { 1433 list.add(buffer.toString()); 1434 buffer.setLength(0); 1435 } 1436 if (ch == '?') { 1437 list.add("?"); 1438 } else if (prevChar != '*') {// ch == '*' here; check if previous char was '*' 1439 list.add("*"); 1440 } 1441 } else { 1442 buffer.append(ch); 1443 } 1444 prevChar = ch; 1445 } 1446 if (buffer.length() != 0) { 1447 list.add(buffer.toString()); 1448 } 1449 1450 return list.toArray( new String[ list.size() ] ); 1451 } 1452 1453}