Coverage Report - liquibase.util.file.FilenameUtils
 
Classes in this File Line Coverage Branch Coverage Complexity
FilenameUtils
22%
64/287
17%
48/278
6.389
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
 3  
  * file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
 4  
  * to You under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
 5  
  * License. You may obtain a copy of the License at
 6  
  * 
 7  
  * http://www.apache.org/licenses/LICENSE-2.0
 8  
  * 
 9  
  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 10  
  * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 11  
  * specific language governing permissions and limitations under the License.
 12  
  */
 13  
 package liquibase.util.file;
 14  
 
 15  
 import java.io.File;
 16  
 import java.util.ArrayList;
 17  
 import java.util.Collection;
 18  
 import java.util.Iterator;
 19  
 import java.util.Stack;
 20  
 
 21  
 /**
 22  
  * <p>
 23  
  * Original code copied from <a href='http://commons.apache.org/io'>Commons IO</a>. Some modifications done. Don't
 24  
  * update directly to new versions from commons io.
 25  
  * </p>
 26  
  * General filename and filepath manipulation utilities.
 27  
  * <p>
 28  
  * When dealing with filenames you can hit problems when moving from a Windows based development machine to a Unix based
 29  
  * production machine. This class aims to help avoid those problems.
 30  
  * <p>
 31  
  * <b>NOTE</b>: You may be able to avoid using this class entirely simply by using JDK {@link java.io.File File} objects
 32  
  * and the two argument constructor {@link java.io.File#File(java.io.File, java.lang.String) File(File,String)}.
 33  
  * <p>
 34  
  * Most methods on this class are designed to work the same on both Unix and Windows. Those that don't include 'System',
 35  
  * 'Unix' or 'Windows' in their name.
 36  
  * <p>
 37  
  * Most methods recognise both separators (forward and back), and both sets of prefixes. See the javadoc of each method
 38  
  * for details.
 39  
  * <p>
 40  
  * This class defines six components within a filename (example C:\dev\project\file.txt):
 41  
  * <ul>
 42  
  * <li>the prefix - C:\</li>
 43  
  * <li>the path - dev\project\</li>
 44  
  * <li>the full path - C:\dev\project\</li>
 45  
  * <li>the name - file.txt</li>
 46  
  * <li>the base name - file</li>
 47  
  * <li>the extension - txt</li>
 48  
  * </ul>
 49  
  * Note that this class works best if directory filenames end with a separator. If you omit the last separator, it is
 50  
  * impossible to determine if the filename corresponds to a file or a directory. As a result, we have chosen to say it
 51  
  * corresponds to a file.
 52  
  * <p>
 53  
  * This class only supports Unix and Windows style names. Prefixes are matched as follows:
 54  
  * 
 55  
  * <pre>
 56  
  * Windows:
 57  
  * a\b\c.txt           --> ""          --> relative
 58  
  * \a\b\c.txt          --> "\"         --> current drive absolute
 59  
  * C:a\b\c.txt         --> "C:"        --> drive relative
 60  
  * C:\a\b\c.txt        --> "C:\"       --> absolute
 61  
  * \\server\a\b\c.txt  --> "\\server\" --> UNC
 62  
  * 
 63  
  * Unix:
 64  
  * a/b/c.txt           --> ""          --> relative
 65  
  * /a/b/c.txt          --> "/"         --> absolute
 66  
  * ~/a/b/c.txt         --> "~/"        --> current user
 67  
  * ~                   --> "~/"        --> current user (slash added)
 68  
  * ~user/a/b/c.txt     --> "~user/"    --> named user
 69  
  * ~user               --> "~user/"    --> named user (slash added)
 70  
  * </pre>
 71  
  * 
 72  
  * Both prefix styles are matched always, irrespective of the machine that you are currently running on.
 73  
  * <p>
 74  
  * Origin of code: Excalibur, Alexandria, Tomcat, Commons-Utils.
 75  
  * 
 76  
  * @author <a href="mailto:burton@relativity.yi.org">Kevin A. Burton</A>
 77  
  * @author <a href="mailto:sanders@apache.org">Scott Sanders</a>
 78  
  * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
 79  
  * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph.Reck</a>
 80  
  * @author <a href="mailto:peter@apache.org">Peter Donald</a>
 81  
  * @author <a href="mailto:jefft@apache.org">Jeff Turner</a>
 82  
  * @author Matthew Hawthorne
 83  
  * @author Martin Cooper
 84  
  * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
 85  
  * @author Stephen Colebourne
 86  
  * @version $Id: FilenameUtils.java 609870 2008-01-08 04:46:26Z niallp $
 87  
  * @since Commons IO 1.1
 88  
  */
 89  
 public class FilenameUtils {
 90  
 
 91  
     /**
 92  
      * The extension separator character.
 93  
      * 
 94  
      * @since Commons IO 1.4
 95  
      */
 96  
     public static final char EXTENSION_SEPARATOR = '.';
 97  
 
 98  
     /**
 99  
      * The extension separator String.
 100  
      * 
 101  
      * @since Commons IO 1.4
 102  
      */
 103  1
     public static final String EXTENSION_SEPARATOR_STR = (new Character(EXTENSION_SEPARATOR)).toString();
 104  
 
 105  
     /**
 106  
      * The Unix separator character.
 107  
      */
 108  
     private static final char UNIX_SEPARATOR = '/';
 109  
 
 110  
     /**
 111  
      * The Windows separator character. Changed from original commons io implementation: We have to use '/' because we
 112  
      * use to resolve classpath paths that only works with '/'
 113  
      */
 114  
     private static final char WINDOWS_SEPARATOR = '/';
 115  
 
 116  
     /**
 117  
      * The system separator character. Changed to '/' from the same reason as WINDOWS_SEPARATOR
 118  
      */
 119  
     private static final char SYSTEM_SEPARATOR = '/';
 120  
 
 121  
     /**
 122  
      * The separator character that is the opposite of the system separator.
 123  
      */
 124  
     private static final char OTHER_SEPARATOR;
 125  
     static {
 126  1
         if (isSystemWindows()) {
 127  1
             OTHER_SEPARATOR = UNIX_SEPARATOR;
 128  
         } else {
 129  0
             OTHER_SEPARATOR = WINDOWS_SEPARATOR;
 130  
         }
 131  1
     }
 132  
 
 133  
     /**
 134  
      * Instances should NOT be constructed in standard programming.
 135  
      */
 136  
     public FilenameUtils() {
 137  0
         super();
 138  0
     }
 139  
 
 140  
     // -----------------------------------------------------------------------
 141  
     /**
 142  
      * Determines if Windows file system is in use.
 143  
      * 
 144  
      * @return true if the system is Windows
 145  
      */
 146  
     static boolean isSystemWindows() {
 147  1
         return SYSTEM_SEPARATOR == WINDOWS_SEPARATOR;
 148  
     }
 149  
 
 150  
     // -----------------------------------------------------------------------
 151  
     /**
 152  
      * Checks if the character is a separator.
 153  
      * 
 154  
      * @param ch
 155  
      *            the character to check
 156  
      * @return true if it is a separator character
 157  
      */
 158  
     private static boolean isSeparator(char ch) {
 159  61
         return (ch == UNIX_SEPARATOR) || (ch == WINDOWS_SEPARATOR);
 160  
     }
 161  
 
 162  
     // -----------------------------------------------------------------------
 163  
     /**
 164  
      * Normalizes a path, removing double and single dot path steps.
 165  
      * <p>
 166  
      * This method normalizes a path to a standard format. The input may contain separators in either Unix or Windows
 167  
      * format. The output will contain separators in the format of the system.
 168  
      * <p>
 169  
      * A trailing slash will be retained. A double slash will be merged to a single slash (but UNC names are handled). A
 170  
      * single dot path segment will be removed. A double dot will cause that path segment and the one before to be
 171  
      * removed. If the double dot has no parent path segment to work with, <code>null</code> is returned.
 172  
      * <p>
 173  
      * The output will be the same on both Unix and Windows except for the separator character.
 174  
      * 
 175  
      * <pre>
 176  
      * /foo//               -->   /foo/
 177  
      * /foo/./              -->   /foo/
 178  
      * /foo/../bar          -->   /bar
 179  
      * /foo/../bar/         -->   /bar/
 180  
      * /foo/../bar/../baz   -->   /baz
 181  
      * //foo//./bar         -->   /foo/bar
 182  
      * /../                 -->   null
 183  
      * ../foo               -->   null
 184  
      * foo/bar/..           -->   foo/
 185  
      * foo/../../bar        -->   null
 186  
      * foo/../bar           -->   bar
 187  
      * //server/foo/../bar  -->   //server/bar
 188  
      * //server/../bar      -->   null
 189  
      * C:\foo\..\bar        -->   C:\bar
 190  
      * C:\..\bar            -->   null
 191  
      * ~/foo/../bar/        -->   ~/bar/
 192  
      * ~/../bar             -->   null
 193  
      * </pre>
 194  
      * 
 195  
      * (Note the file separator returned will be correct for Windows/Unix)
 196  
      * 
 197  
      * @param filename
 198  
      *            the filename to normalize, null returns null
 199  
      * @return the normalized filename, or null if invalid
 200  
      */
 201  
     public static String normalize(String filename) {
 202  3
         return doNormalize(filename, true);
 203  
     }
 204  
 
 205  
     // -----------------------------------------------------------------------
 206  
     /**
 207  
      * Normalizes a path, removing double and single dot path steps, and removing any final directory separator.
 208  
      * <p>
 209  
      * This method normalizes a path to a standard format. The input may contain separators in either Unix or Windows
 210  
      * format. The output will contain separators in the format of the system.
 211  
      * <p>
 212  
      * A trailing slash will be removed. A double slash will be merged to a single slash (but UNC names are handled). A
 213  
      * single dot path segment will be removed. A double dot will cause that path segment and the one before to be
 214  
      * removed. If the double dot has no parent path segment to work with, <code>null</code> is returned.
 215  
      * <p>
 216  
      * The output will be the same on both Unix and Windows except for the separator character.
 217  
      * 
 218  
      * <pre>
 219  
      * /foo//               -->   /foo
 220  
      * /foo/./              -->   /foo
 221  
      * /foo/../bar          -->   /bar
 222  
      * /foo/../bar/         -->   /bar
 223  
      * /foo/../bar/../baz   -->   /baz
 224  
      * //foo//./bar         -->   /foo/bar
 225  
      * /../                 -->   null
 226  
      * ../foo               -->   null
 227  
      * foo/bar/..           -->   foo
 228  
      * foo/../../bar        -->   null
 229  
      * foo/../bar           -->   bar
 230  
      * //server/foo/../bar  -->   //server/bar
 231  
      * //server/../bar      -->   null
 232  
      * C:\foo\..\bar        -->   C:\bar
 233  
      * C:\..\bar            -->   null
 234  
      * ~/foo/../bar/        -->   ~/bar
 235  
      * ~/../bar             -->   null
 236  
      * </pre>
 237  
      * 
 238  
      * (Note the file separator returned will be correct for Windows/Unix)
 239  
      * 
 240  
      * @param filename
 241  
      *            the filename to normalize, null returns null
 242  
      * @return the normalized filename, or null if invalid
 243  
      */
 244  
     public static String normalizeNoEndSeparator(String filename) {
 245  0
         return doNormalize(filename, false);
 246  
     }
 247  
 
 248  
     /**
 249  
      * Internal method to perform the normalization.
 250  
      * 
 251  
      * @param filename
 252  
      *            the filename
 253  
      * @param keepSeparator
 254  
      *            true to keep the final separator
 255  
      * @return the normalized filename
 256  
      */
 257  
     private static String doNormalize(String filename, boolean keepSeparator) {
 258  3
         if (filename == null) {
 259  0
             return null;
 260  
         }
 261  3
         int size = filename.length();
 262  3
         if (size == 0) {
 263  0
             return filename;
 264  
         }
 265  3
         int prefix = getPrefixLength(filename);
 266  3
         if (prefix < 0) {
 267  0
             return null;
 268  
         }
 269  
 
 270  3
         char[] array = new char[size + 2]; // +1 for possible extra slash, +2 for arraycopy
 271  3
         filename.getChars(0, filename.length(), array, 0);
 272  
 
 273  
         // fix separators throughout
 274  152
         for (int i = 0; i < array.length; i++) {
 275  149
             if (array[i] == OTHER_SEPARATOR) {
 276  12
                 array[i] = SYSTEM_SEPARATOR;
 277  
             }
 278  
         }
 279  
 
 280  
         // add extra separator on the end to simplify code below
 281  3
         boolean lastIsDirectory = true;
 282  3
         if (array[size - 1] != SYSTEM_SEPARATOR) {
 283  3
             array[size++] = SYSTEM_SEPARATOR;
 284  3
             lastIsDirectory = false;
 285  
         }
 286  
 
 287  
         // adjoining slashes
 288  146
         for (int i = prefix + 1; i < size; i++) {
 289  143
             if (array[i] == SYSTEM_SEPARATOR && array[i - 1] == SYSTEM_SEPARATOR) {
 290  0
                 System.arraycopy(array, i, array, i - 1, size - i);
 291  0
                 size--;
 292  0
                 i--;
 293  
             }
 294  
         }
 295  
 
 296  
         // dot slash
 297  146
         for (int i = prefix + 1; i < size; i++) {
 298  143
             if (array[i] == SYSTEM_SEPARATOR && array[i - 1] == '.'
 299  
                     && (i == prefix + 1 || array[i - 2] == SYSTEM_SEPARATOR)) {
 300  0
                 if (i == size - 1) {
 301  0
                     lastIsDirectory = true;
 302  
                 }
 303  0
                 System.arraycopy(array, i + 1, array, i - 1, size - i);
 304  0
                 size -= 2;
 305  0
                 i--;
 306  
             }
 307  
         }
 308  
 
 309  
         // double dot slash
 310  143
         outer: for (int i = prefix + 2; i < size; i++) {
 311  140
             if (array[i] == SYSTEM_SEPARATOR && array[i - 1] == '.' && array[i - 2] == '.'
 312  
                     && (i == prefix + 2 || array[i - 3] == SYSTEM_SEPARATOR)) {
 313  0
                 if (i == prefix + 2) {
 314  0
                     return null;
 315  
                 }
 316  0
                 if (i == size - 1) {
 317  0
                     lastIsDirectory = true;
 318  
                 }
 319  
                 int j;
 320  0
                 for (j = i - 4; j >= prefix; j--) {
 321  0
                     if (array[j] == SYSTEM_SEPARATOR) {
 322  
                         // remove b/../ from a/b/../c
 323  0
                         System.arraycopy(array, i + 1, array, j + 1, size - i);
 324  0
                         size -= (i - j);
 325  0
                         i = j + 1;
 326  0
                         continue outer;
 327  
                     }
 328  
                 }
 329  
                 // remove a/../ from a/../c
 330  0
                 System.arraycopy(array, i + 1, array, prefix, size - i);
 331  0
                 size -= (i + 1 - prefix);
 332  0
                 i = prefix + 1;
 333  
             }
 334  
         }
 335  
 
 336  3
         if (size <= 0) { // should never be less than 0
 337  0
             return "";
 338  
         }
 339  3
         if (size <= prefix) { // should never be less than prefix
 340  0
             return new String(array, 0, size);
 341  
         }
 342  3
         if (lastIsDirectory && keepSeparator) {
 343  0
             return new String(array, 0, size); // keep trailing separator
 344  
         }
 345  3
         return new String(array, 0, size - 1); // lose trailing separator
 346  
     }
 347  
 
 348  
     // -----------------------------------------------------------------------
 349  
     /**
 350  
      * Concatenates a filename to a base path using normal command line style rules.
 351  
      * <p>
 352  
      * The effect is equivalent to resultant directory after changing directory to the first argument, followed by
 353  
      * changing directory to the second argument.
 354  
      * <p>
 355  
      * The first argument is the base path, the second is the path to concatenate. The returned path is always
 356  
      * normalized via {@link #normalize(String)}, thus <code>..</code> is handled.
 357  
      * <p>
 358  
      * If <code>pathToAdd</code> is absolute (has an absolute prefix), then it will be normalized and returned.
 359  
      * Otherwise, the paths will be joined, normalized and returned.
 360  
      * <p>
 361  
      * The output will be the same on both Unix and Windows except for the separator character.
 362  
      * 
 363  
      * <pre>
 364  
      * /foo/ + bar          -->   /foo/bar
 365  
      * /foo + bar           -->   /foo/bar
 366  
      * /foo + /bar          -->   /bar
 367  
      * /foo + C:/bar        -->   C:/bar
 368  
      * /foo + C:bar         -->   C:bar (*)
 369  
      * /foo/a/ + ../bar     -->   foo/bar
 370  
      * /foo/ + ../../bar    -->   null
 371  
      * /foo/ + /bar         -->   /bar
 372  
      * /foo/.. + /bar       -->   /bar
 373  
      * /foo + bar/c.txt     -->   /foo/bar/c.txt
 374  
      * /foo/c.txt + bar     -->   /foo/c.txt/bar (!)
 375  
      * </pre>
 376  
      * 
 377  
      * (*) Note that the Windows relative drive prefix is unreliable when used with this method. (!) Note that the first
 378  
      * parameter must be a path. If it ends with a name, then the name will be built into the concatenated path. If this
 379  
      * might be a problem, use {@link #getFullPath(String)} on the base path argument.
 380  
      * 
 381  
      * @param basePath
 382  
      *            the base path to attach to, always treated as a path
 383  
      * @param fullFilenameToAdd
 384  
      *            the filename (or path) to attach to the base
 385  
      * @return the concatenated path, or null if invalid
 386  
      */
 387  
     public static String concat(String basePath, String fullFilenameToAdd) {
 388  3
         int prefix = getPrefixLength(fullFilenameToAdd);
 389  3
         if (prefix < 0) {
 390  0
             return null;
 391  
         }
 392  3
         if (prefix > 0) {
 393  0
             return normalize(fullFilenameToAdd);
 394  
         }
 395  3
         if (basePath == null) {
 396  0
             return null;
 397  
         }
 398  3
         int len = basePath.length();
 399  3
         if (len == 0) {
 400  0
             return normalize(fullFilenameToAdd);
 401  
         }
 402  3
         char ch = basePath.charAt(len - 1);
 403  3
         if (isSeparator(ch)) {
 404  3
             return normalize(basePath + fullFilenameToAdd);
 405  
         } else {
 406  0
             return normalize(basePath + '/' + fullFilenameToAdd);
 407  
         }
 408  
     }
 409  
 
 410  
     // -----------------------------------------------------------------------
 411  
     /**
 412  
      * Converts all separators to the Unix separator of forward slash.
 413  
      * 
 414  
      * @param path
 415  
      *            the path to be changed, null ignored
 416  
      * @return the updated path
 417  
      */
 418  
     public static String separatorsToUnix(String path) {
 419  0
         if (path == null || path.indexOf(WINDOWS_SEPARATOR) == -1) {
 420  0
             return path;
 421  
         }
 422  0
         return path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR);
 423  
     }
 424  
 
 425  
     /**
 426  
      * Converts all separators to the Windows separator of backslash.
 427  
      * 
 428  
      * @param path
 429  
      *            the path to be changed, null ignored
 430  
      * @return the updated path
 431  
      */
 432  
     public static String separatorsToWindows(String path) {
 433  0
         if (path == null || path.indexOf(UNIX_SEPARATOR) == -1) {
 434  0
             return path;
 435  
         }
 436  0
         return path.replace(UNIX_SEPARATOR, WINDOWS_SEPARATOR);
 437  
     }
 438  
 
 439  
     /**
 440  
      * Converts all separators to the system separator.
 441  
      * 
 442  
      * @param path
 443  
      *            the path to be changed, null ignored
 444  
      * @return the updated path
 445  
      */
 446  
     public static String separatorsToSystem(String path) {
 447  0
         if (path == null) {
 448  0
             return null;
 449  
         }
 450  0
         if (isSystemWindows()) {
 451  0
             return separatorsToWindows(path);
 452  
         } else {
 453  0
             return separatorsToUnix(path);
 454  
         }
 455  
     }
 456  
 
 457  
     // -----------------------------------------------------------------------
 458  
     /**
 459  
      * Returns the length of the filename prefix, such as <code>C:/</code> or <code>~/</code>.
 460  
      * <p>
 461  
      * This method will handle a file in either Unix or Windows format.
 462  
      * <p>
 463  
      * The prefix length includes the first slash in the full filename if applicable. Thus, it is possible that the
 464  
      * length returned is greater than the length of the input string.
 465  
      * 
 466  
      * <pre>
 467  
      * Windows:
 468  
      * a\b\c.txt           --> ""          --> relative
 469  
      * \a\b\c.txt          --> "\"         --> current drive absolute
 470  
      * C:a\b\c.txt         --> "C:"        --> drive relative
 471  
      * C:\a\b\c.txt        --> "C:\"       --> absolute
 472  
      * \\server\a\b\c.txt  --> "\\server\" --> UNC
 473  
      * 
 474  
      * Unix:
 475  
      * a/b/c.txt           --> ""          --> relative
 476  
      * /a/b/c.txt          --> "/"         --> absolute
 477  
      * ~/a/b/c.txt         --> "~/"        --> current user
 478  
      * ~                   --> "~/"        --> current user (slash added)
 479  
      * ~user/a/b/c.txt     --> "~user/"    --> named user
 480  
      * ~user               --> "~user/"    --> named user (slash added)
 481  
      * </pre>
 482  
      * <p>
 483  
      * The output will be the same irrespective of the machine that the code is running on. ie. both Unix and Windows
 484  
      * prefixes are matched regardless.
 485  
      * 
 486  
      * @param filename
 487  
      *            the filename to find the prefix in, null returns -1
 488  
      * @return the length of the prefix, -1 if invalid or null
 489  
      */
 490  
     public static int getPrefixLength(String filename) {
 491  29
         if (filename == null) {
 492  0
             return -1;
 493  
         }
 494  29
         int len = filename.length();
 495  29
         if (len == 0) {
 496  0
             return 0;
 497  
         }
 498  29
         char ch0 = filename.charAt(0);
 499  29
         if (ch0 == ':') {
 500  0
             return -1;
 501  
         }
 502  29
         if (len == 1) {
 503  0
             if (ch0 == '~') {
 504  0
                 return 2; // return a length greater than the input
 505  
             }
 506  0
             return (isSeparator(ch0) ? 1 : 0);
 507  
         } else {
 508  29
             if (ch0 == '~') {
 509  0
                 int posUnix = filename.indexOf(UNIX_SEPARATOR, 1);
 510  0
                 int posWin = filename.indexOf(WINDOWS_SEPARATOR, 1);
 511  0
                 if (posUnix == -1 && posWin == -1) {
 512  0
                     return len + 1; // return a length greater than the input
 513  
                 }
 514  0
                 posUnix = (posUnix == -1 ? posWin : posUnix);
 515  0
                 posWin = (posWin == -1 ? posUnix : posWin);
 516  0
                 return Math.min(posUnix, posWin) + 1;
 517  
             }
 518  29
             char ch1 = filename.charAt(1);
 519  29
             if (ch1 == ':') {
 520  0
                 ch0 = Character.toUpperCase(ch0);
 521  0
                 if (ch0 >= 'A' && ch0 <= 'Z') {
 522  0
                     if (len == 2 || isSeparator(filename.charAt(2)) == false) {
 523  0
                         return 2;
 524  
                     }
 525  0
                     return 3;
 526  
                 }
 527  0
                 return -1;
 528  
 
 529  29
             } else if (isSeparator(ch0) && isSeparator(ch1)) {
 530  0
                 int posUnix = filename.indexOf(UNIX_SEPARATOR, 2);
 531  0
                 int posWin = filename.indexOf(WINDOWS_SEPARATOR, 2);
 532  0
                 if ((posUnix == -1 && posWin == -1) || posUnix == 2 || posWin == 2) {
 533  0
                     return -1;
 534  
                 }
 535  0
                 posUnix = (posUnix == -1 ? posWin : posUnix);
 536  0
                 posWin = (posWin == -1 ? posUnix : posWin);
 537  0
                 return Math.min(posUnix, posWin) + 1;
 538  
             } else {
 539  29
                 return (isSeparator(ch0) ? 1 : 0);
 540  
             }
 541  
         }
 542  
     }
 543  
 
 544  
     /**
 545  
      * Returns the index of the last directory separator character.
 546  
      * <p>
 547  
      * This method will handle a file in either Unix or Windows format. The position of the last forward or backslash is
 548  
      * returned.
 549  
      * <p>
 550  
      * The output will be the same irrespective of the machine that the code is running on.
 551  
      * 
 552  
      * @param filename
 553  
      *            the filename to find the last path separator in, null returns -1
 554  
      * @return the index of the last separator character, or -1 if there is no such character
 555  
      */
 556  
     public static int indexOfLastSeparator(String filename) {
 557  23
         if (filename == null) {
 558  0
             return -1;
 559  
         }
 560  23
         int lastUnixPos = filename.lastIndexOf(UNIX_SEPARATOR);
 561  23
         int lastWindowsPos = filename.lastIndexOf(WINDOWS_SEPARATOR);
 562  23
         return Math.max(lastUnixPos, lastWindowsPos);
 563  
     }
 564  
 
 565  
     /**
 566  
      * Returns the index of the last extension separator character, which is a dot.
 567  
      * <p>
 568  
      * This method also checks that there is no directory separator after the last dot. To do this it uses
 569  
      * {@link #indexOfLastSeparator(String)} which will handle a file in either Unix or Windows format.
 570  
      * <p>
 571  
      * The output will be the same irrespective of the machine that the code is running on.
 572  
      * 
 573  
      * @param filename
 574  
      *            the filename to find the last path separator in, null returns -1
 575  
      * @return the index of the last separator character, or -1 if there is no such character
 576  
      */
 577  
     public static int indexOfExtension(String filename) {
 578  0
         if (filename == null) {
 579  0
             return -1;
 580  
         }
 581  0
         int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR);
 582  0
         int lastSeparator = indexOfLastSeparator(filename);
 583  0
         return (lastSeparator > extensionPos ? -1 : extensionPos);
 584  
     }
 585  
 
 586  
     // -----------------------------------------------------------------------
 587  
     /**
 588  
      * Gets the prefix from a full filename, such as <code>C:/</code> or <code>~/</code>.
 589  
      * <p>
 590  
      * This method will handle a file in either Unix or Windows format. The prefix includes the first slash in the full
 591  
      * filename where applicable.
 592  
      * 
 593  
      * <pre>
 594  
      * Windows:
 595  
      * a\b\c.txt           --> ""          --> relative
 596  
      * \a\b\c.txt          --> "\"         --> current drive absolute
 597  
      * C:a\b\c.txt         --> "C:"        --> drive relative
 598  
      * C:\a\b\c.txt        --> "C:\"       --> absolute
 599  
      * \\server\a\b\c.txt  --> "\\server\" --> UNC
 600  
      * 
 601  
      * Unix:
 602  
      * a/b/c.txt           --> ""          --> relative
 603  
      * /a/b/c.txt          --> "/"         --> absolute
 604  
      * ~/a/b/c.txt         --> "~/"        --> current user
 605  
      * ~                   --> "~/"        --> current user (slash added)
 606  
      * ~user/a/b/c.txt     --> "~user/"    --> named user
 607  
      * ~user               --> "~user/"    --> named user (slash added)
 608  
      * </pre>
 609  
      * <p>
 610  
      * The output will be the same irrespective of the machine that the code is running on. ie. both Unix and Windows
 611  
      * prefixes are matched regardless.
 612  
      * 
 613  
      * @param filename
 614  
      *            the filename to query, null returns null
 615  
      * @return the prefix of the file, null if invalid
 616  
      */
 617  
     public static String getPrefix(String filename) {
 618  0
         if (filename == null) {
 619  0
             return null;
 620  
         }
 621  0
         int len = getPrefixLength(filename);
 622  0
         if (len < 0) {
 623  0
             return null;
 624  
         }
 625  0
         if (len > filename.length()) {
 626  0
             return filename + UNIX_SEPARATOR; // we know this only happens for unix
 627  
         }
 628  0
         return filename.substring(0, len);
 629  
     }
 630  
 
 631  
     /**
 632  
      * Gets the path from a full filename, which excludes the prefix.
 633  
      * <p>
 634  
      * This method will handle a file in either Unix or Windows format. The method is entirely text based, and returns
 635  
      * the text before and including the last forward or backslash.
 636  
      * 
 637  
      * <pre>
 638  
      * C:\a\b\c.txt --> a\b\
 639  
      * ~/a/b/c.txt  --> a/b/
 640  
      * a.txt        --> ""
 641  
      * a/b/c        --> a/b/
 642  
      * a/b/c/       --> a/b/c/
 643  
      * </pre>
 644  
      * <p>
 645  
      * The output will be the same irrespective of the machine that the code is running on.
 646  
      * <p>
 647  
      * This method drops the prefix from the result. See {@link #getFullPath(String)} for the method that retains the
 648  
      * prefix.
 649  
      * 
 650  
      * @param filename
 651  
      *            the filename to query, null returns null
 652  
      * @return the path of the file, an empty string if none exists, null if invalid
 653  
      */
 654  
     public static String getPath(String filename) {
 655  0
         return doGetPath(filename, 1);
 656  
     }
 657  
 
 658  
     /**
 659  
      * Gets the path from a full filename, which excludes the prefix, and also excluding the final directory separator.
 660  
      * <p>
 661  
      * This method will handle a file in either Unix or Windows format. The method is entirely text based, and returns
 662  
      * the text before the last forward or backslash.
 663  
      * 
 664  
      * <pre>
 665  
      * C:\a\b\c.txt --> a\b
 666  
      * ~/a/b/c.txt  --> a/b
 667  
      * a.txt        --> ""
 668  
      * a/b/c        --> a/b
 669  
      * a/b/c/       --> a/b/c
 670  
      * </pre>
 671  
      * <p>
 672  
      * The output will be the same irrespective of the machine that the code is running on.
 673  
      * <p>
 674  
      * This method drops the prefix from the result. See {@link #getFullPathNoEndSeparator(String)} for the method that
 675  
      * retains the prefix.
 676  
      * 
 677  
      * @param filename
 678  
      *            the filename to query, null returns null
 679  
      * @return the path of the file, an empty string if none exists, null if invalid
 680  
      */
 681  
     public static String getPathNoEndSeparator(String filename) {
 682  0
         return doGetPath(filename, 0);
 683  
     }
 684  
 
 685  
     /**
 686  
      * Does the work of getting the path.
 687  
      * 
 688  
      * @param filename
 689  
      *            the filename
 690  
      * @param separatorAdd
 691  
      *            0 to omit the end separator, 1 to return it
 692  
      * @return the path
 693  
      */
 694  
     private static String doGetPath(String filename, int separatorAdd) {
 695  0
         if (filename == null) {
 696  0
             return null;
 697  
         }
 698  0
         int prefix = getPrefixLength(filename);
 699  0
         if (prefix < 0) {
 700  0
             return null;
 701  
         }
 702  0
         int index = indexOfLastSeparator(filename);
 703  0
         if (prefix >= filename.length() || index < 0) {
 704  0
             return "";
 705  
         }
 706  0
         return filename.substring(prefix, index + separatorAdd);
 707  
     }
 708  
 
 709  
     /**
 710  
      * Gets the full path from a full filename, which is the prefix + path.
 711  
      * <p>
 712  
      * This method will handle a file in either Unix or Windows format. The method is entirely text based, and returns
 713  
      * the text before and including the last forward or backslash.
 714  
      * 
 715  
      * <pre>
 716  
      * C:\a\b\c.txt --> C:\a\b\
 717  
      * ~/a/b/c.txt  --> ~/a/b/
 718  
      * a.txt        --> ""
 719  
      * a/b/c        --> a/b/
 720  
      * a/b/c/       --> a/b/c/
 721  
      * C:           --> C:
 722  
      * C:\          --> C:\
 723  
      * ~            --> ~/
 724  
      * ~/           --> ~/
 725  
      * ~user        --> ~user/
 726  
      * ~user/       --> ~user/
 727  
      * </pre>
 728  
      * <p>
 729  
      * The output will be the same irrespective of the machine that the code is running on.
 730  
      * 
 731  
      * @param filename
 732  
      *            the filename to query, null returns null
 733  
      * @return the path of the file, an empty string if none exists, null if invalid
 734  
      */
 735  
     public static String getFullPath(String filename) {
 736  23
         return doGetFullPath(filename, true);
 737  
     }
 738  
 
 739  
     /**
 740  
      * Gets the full path from a full filename, which is the prefix + path, and also excluding the final directory
 741  
      * separator.
 742  
      * <p>
 743  
      * This method will handle a file in either Unix or Windows format. The method is entirely text based, and returns
 744  
      * the text before the last forward or backslash.
 745  
      * 
 746  
      * <pre>
 747  
      * C:\a\b\c.txt --> C:\a\b
 748  
      * ~/a/b/c.txt  --> ~/a/b
 749  
      * a.txt        --> ""
 750  
      * a/b/c        --> a/b
 751  
      * a/b/c/       --> a/b/c
 752  
      * C:           --> C:
 753  
      * C:\          --> C:\
 754  
      * ~            --> ~
 755  
      * ~/           --> ~
 756  
      * ~user        --> ~user
 757  
      * ~user/       --> ~user
 758  
      * </pre>
 759  
      * <p>
 760  
      * The output will be the same irrespective of the machine that the code is running on.
 761  
      * 
 762  
      * @param filename
 763  
      *            the filename to query, null returns null
 764  
      * @return the path of the file, an empty string if none exists, null if invalid
 765  
      */
 766  
     public static String getFullPathNoEndSeparator(String filename) {
 767  0
         return doGetFullPath(filename, false);
 768  
     }
 769  
 
 770  
     /**
 771  
      * Does the work of getting the path.
 772  
      * 
 773  
      * @param filename
 774  
      *            the filename
 775  
      * @param includeSeparator
 776  
      *            true to include the end separator
 777  
      * @return the path
 778  
      */
 779  
     private static String doGetFullPath(String filename, boolean includeSeparator) {
 780  23
         if (filename == null) {
 781  0
             return null;
 782  
         }
 783  23
         int prefix = getPrefixLength(filename);
 784  23
         if (prefix < 0) {
 785  0
             return null;
 786  
         }
 787  23
         if (prefix >= filename.length()) {
 788  0
             if (includeSeparator) {
 789  0
                 return getPrefix(filename); // add end slash if necessary
 790  
             } else {
 791  0
                 return filename;
 792  
             }
 793  
         }
 794  23
         int index = indexOfLastSeparator(filename);
 795  23
         if (index < 0) {
 796  0
             return filename.substring(0, prefix);
 797  
         }
 798  23
         int end = index + (includeSeparator ? 1 : 0);
 799  23
         return filename.substring(0, end);
 800  
     }
 801  
 
 802  
     /**
 803  
      * Gets the name minus the path from a full filename.
 804  
      * <p>
 805  
      * This method will handle a file in either Unix or Windows format. The text after the last forward or backslash is
 806  
      * returned.
 807  
      * 
 808  
      * <pre>
 809  
      * a/b/c.txt --> c.txt
 810  
      * a.txt     --> a.txt
 811  
      * a/b/c     --> c
 812  
      * a/b/c/    --> ""
 813  
      * </pre>
 814  
      * <p>
 815  
      * The output will be the same irrespective of the machine that the code is running on.
 816  
      * 
 817  
      * @param filename
 818  
      *            the filename to query, null returns null
 819  
      * @return the name of the file without the path, or an empty string if none exists
 820  
      */
 821  
     public static String getName(String filename) {
 822  0
         if (filename == null) {
 823  0
             return null;
 824  
         }
 825  0
         int index = indexOfLastSeparator(filename);
 826  0
         return filename.substring(index + 1);
 827  
     }
 828  
 
 829  
     /**
 830  
      * Gets the base name, minus the full path and extension, from a full filename.
 831  
      * <p>
 832  
      * This method will handle a file in either Unix or Windows format. The text after the last forward or backslash and
 833  
      * before the last dot is returned.
 834  
      * 
 835  
      * <pre>
 836  
      * a/b/c.txt --> c
 837  
      * a.txt     --> a
 838  
      * a/b/c     --> c
 839  
      * a/b/c/    --> ""
 840  
      * </pre>
 841  
      * <p>
 842  
      * The output will be the same irrespective of the machine that the code is running on.
 843  
      * 
 844  
      * @param filename
 845  
      *            the filename to query, null returns null
 846  
      * @return the name of the file without the path, or an empty string if none exists
 847  
      */
 848  
     public static String getBaseName(String filename) {
 849  0
         return removeExtension(getName(filename));
 850  
     }
 851  
 
 852  
     /**
 853  
      * Gets the extension of a filename.
 854  
      * <p>
 855  
      * This method returns the textual part of the filename after the last dot. There must be no directory separator
 856  
      * after the dot.
 857  
      * 
 858  
      * <pre>
 859  
      * foo.txt      --> "txt"
 860  
      * a/b/c.jpg    --> "jpg"
 861  
      * a/b.txt/c    --> ""
 862  
      * a/b/c        --> ""
 863  
      * </pre>
 864  
      * <p>
 865  
      * The output will be the same irrespective of the machine that the code is running on.
 866  
      * 
 867  
      * @param filename
 868  
      *            the filename to retrieve the extension of.
 869  
      * @return the extension of the file or an empty string if none exists.
 870  
      */
 871  
     public static String getExtension(String filename) {
 872  0
         if (filename == null) {
 873  0
             return null;
 874  
         }
 875  0
         int index = indexOfExtension(filename);
 876  0
         if (index == -1) {
 877  0
             return "";
 878  
         } else {
 879  0
             return filename.substring(index + 1);
 880  
         }
 881  
     }
 882  
 
 883  
     // -----------------------------------------------------------------------
 884  
     /**
 885  
      * Removes the extension from a filename.
 886  
      * <p>
 887  
      * This method returns the textual part of the filename before the last dot. There must be no directory separator
 888  
      * after the dot.
 889  
      * 
 890  
      * <pre>
 891  
      * foo.txt    --> foo
 892  
      * a\b\c.jpg  --> a\b\c
 893  
      * a\b\c      --> a\b\c
 894  
      * a.b\c      --> a.b\c
 895  
      * </pre>
 896  
      * <p>
 897  
      * The output will be the same irrespective of the machine that the code is running on.
 898  
      * 
 899  
      * @param filename
 900  
      *            the filename to query, null returns null
 901  
      * @return the filename minus the extension
 902  
      */
 903  
     public static String removeExtension(String filename) {
 904  0
         if (filename == null) {
 905  0
             return null;
 906  
         }
 907  0
         int index = indexOfExtension(filename);
 908  0
         if (index == -1) {
 909  0
             return filename;
 910  
         } else {
 911  0
             return filename.substring(0, index);
 912  
         }
 913  
     }
 914  
 
 915  
     // -----------------------------------------------------------------------
 916  
     /**
 917  
      * Checks whether two filenames are equal exactly.
 918  
      * <p>
 919  
      * No processing is performed on the filenames other than comparison, thus this is merely a null-safe case-sensitive
 920  
      * equals.
 921  
      * 
 922  
      * @param filename1
 923  
      *            the first filename to query, may be null
 924  
      * @param filename2
 925  
      *            the second filename to query, may be null
 926  
      * @return true if the filenames are equal, null equals null
 927  
      * @see IOCase#SENSITIVE
 928  
      */
 929  
     public static boolean equals(String filename1, String filename2) {
 930  0
         return equals(filename1, filename2, false, IOCase.SENSITIVE);
 931  
     }
 932  
 
 933  
     /**
 934  
      * Checks whether two filenames are equal using the case rules of the system.
 935  
      * <p>
 936  
      * No processing is performed on the filenames other than comparison. The check is case-sensitive on Unix and
 937  
      * case-insensitive on Windows.
 938  
      * 
 939  
      * @param filename1
 940  
      *            the first filename to query, may be null
 941  
      * @param filename2
 942  
      *            the second filename to query, may be null
 943  
      * @return true if the filenames are equal, null equals null
 944  
      * @see IOCase#SYSTEM
 945  
      */
 946  
     public static boolean equalsOnSystem(String filename1, String filename2) {
 947  0
         return equals(filename1, filename2, false, IOCase.SYSTEM);
 948  
     }
 949  
 
 950  
     // -----------------------------------------------------------------------
 951  
     /**
 952  
      * Checks whether two filenames are equal after both have been normalized.
 953  
      * <p>
 954  
      * Both filenames are first passed to {@link #normalize(String)}. The check is then performed in a case-sensitive
 955  
      * manner.
 956  
      * 
 957  
      * @param filename1
 958  
      *            the first filename to query, may be null
 959  
      * @param filename2
 960  
      *            the second filename to query, may be null
 961  
      * @return true if the filenames are equal, null equals null
 962  
      * @see IOCase#SENSITIVE
 963  
      */
 964  
     public static boolean equalsNormalized(String filename1, String filename2) {
 965  0
         return equals(filename1, filename2, true, IOCase.SENSITIVE);
 966  
     }
 967  
 
 968  
     /**
 969  
      * Checks whether two filenames are equal after both have been normalized and using the case rules of the system.
 970  
      * <p>
 971  
      * Both filenames are first passed to {@link #normalize(String)}. The check is then performed case-sensitive on Unix
 972  
      * and case-insensitive on Windows.
 973  
      * 
 974  
      * @param filename1
 975  
      *            the first filename to query, may be null
 976  
      * @param filename2
 977  
      *            the second filename to query, may be null
 978  
      * @return true if the filenames are equal, null equals null
 979  
      * @see IOCase#SYSTEM
 980  
      */
 981  
     public static boolean equalsNormalizedOnSystem(String filename1, String filename2) {
 982  0
         return equals(filename1, filename2, true, IOCase.SYSTEM);
 983  
     }
 984  
 
 985  
     /**
 986  
      * Checks whether two filenames are equal, optionally normalizing and providing control over the case-sensitivity.
 987  
      * 
 988  
      * @param filename1
 989  
      *            the first filename to query, may be null
 990  
      * @param filename2
 991  
      *            the second filename to query, may be null
 992  
      * @param normalized
 993  
      *            whether to normalize the filenames
 994  
      * @param caseSensitivity
 995  
      *            what case sensitivity rule to use, null means case-sensitive
 996  
      * @return true if the filenames are equal, null equals null
 997  
      * @since Commons IO 1.3
 998  
      */
 999  
     public static boolean equals(String filename1, String filename2, boolean normalized, IOCase caseSensitivity) {
 1000  
 
 1001  0
         if (filename1 == null || filename2 == null) {
 1002  0
             return filename1 == filename2;
 1003  
         }
 1004  0
         if (normalized) {
 1005  0
             filename1 = normalize(filename1);
 1006  0
             filename2 = normalize(filename2);
 1007  0
             if (filename1 == null || filename2 == null) {
 1008  0
                 throw new NullPointerException("Error normalizing one or both of the file names");
 1009  
             }
 1010  
         }
 1011  0
         if (caseSensitivity == null) {
 1012  0
             caseSensitivity = IOCase.SENSITIVE;
 1013  
         }
 1014  0
         return caseSensitivity.checkEquals(filename1, filename2);
 1015  
     }
 1016  
 
 1017  
     // -----------------------------------------------------------------------
 1018  
     /**
 1019  
      * Checks whether the extension of the filename is that specified.
 1020  
      * <p>
 1021  
      * This method obtains the extension as the textual part of the filename after the last dot. There must be no
 1022  
      * directory separator after the dot. The extension check is case-sensitive on all platforms.
 1023  
      * 
 1024  
      * @param filename
 1025  
      *            the filename to query, null returns false
 1026  
      * @param extension
 1027  
      *            the extension to check for, null or empty checks for no extension
 1028  
      * @return true if the filename has the specified extension
 1029  
      */
 1030  
     public static boolean isExtension(String filename, String extension) {
 1031  0
         if (filename == null) {
 1032  0
             return false;
 1033  
         }
 1034  0
         if (extension == null || extension.length() == 0) {
 1035  0
             return (indexOfExtension(filename) == -1);
 1036  
         }
 1037  0
         String fileExt = getExtension(filename);
 1038  0
         return fileExt.equals(extension);
 1039  
     }
 1040  
 
 1041  
     /**
 1042  
      * Checks whether the extension of the filename is one of those specified.
 1043  
      * <p>
 1044  
      * This method obtains the extension as the textual part of the filename after the last dot. There must be no
 1045  
      * directory separator after the dot. The extension check is case-sensitive on all platforms.
 1046  
      * 
 1047  
      * @param filename
 1048  
      *            the filename to query, null returns false
 1049  
      * @param extensions
 1050  
      *            the extensions to check for, null checks for no extension
 1051  
      * @return true if the filename is one of the extensions
 1052  
      */
 1053  
     public static boolean isExtension(String filename, String[] extensions) {
 1054  0
         if (filename == null) {
 1055  0
             return false;
 1056  
         }
 1057  0
         if (extensions == null || extensions.length == 0) {
 1058  0
             return (indexOfExtension(filename) == -1);
 1059  
         }
 1060  0
         String fileExt = getExtension(filename);
 1061  0
         for (int i = 0; i < extensions.length; i++) {
 1062  0
             if (fileExt.equals(extensions[i])) {
 1063  0
                 return true;
 1064  
             }
 1065  
         }
 1066  0
         return false;
 1067  
     }
 1068  
 
 1069  
     /**
 1070  
      * Checks whether the extension of the filename is one of those specified.
 1071  
      * <p>
 1072  
      * This method obtains the extension as the textual part of the filename after the last dot. There must be no
 1073  
      * directory separator after the dot. The extension check is case-sensitive on all platforms.
 1074  
      * 
 1075  
      * @param filename
 1076  
      *            the filename to query, null returns false
 1077  
      * @param extensions
 1078  
      *            the extensions to check for, null checks for no extension
 1079  
      * @return true if the filename is one of the extensions
 1080  
      */
 1081  
     public static boolean isExtension(String filename, Collection extensions) {
 1082  0
         if (filename == null) {
 1083  0
             return false;
 1084  
         }
 1085  0
         if (extensions == null || extensions.isEmpty()) {
 1086  0
             return (indexOfExtension(filename) == -1);
 1087  
         }
 1088  0
         String fileExt = getExtension(filename);
 1089  0
         for (Iterator it = extensions.iterator(); it.hasNext();) {
 1090  0
             if (fileExt.equals(it.next())) {
 1091  0
                 return true;
 1092  
             }
 1093  
         }
 1094  0
         return false;
 1095  
     }
 1096  
 
 1097  
     // -----------------------------------------------------------------------
 1098  
     /**
 1099  
      * Checks a filename to see if it matches the specified wildcard matcher, always testing case-sensitive.
 1100  
      * <p>
 1101  
      * The wildcard matcher uses the characters '?' and '*' to represent a single or multiple wildcard characters. This
 1102  
      * is the same as often found on Dos/Unix command lines. The check is case-sensitive always.
 1103  
      * 
 1104  
      * <pre>
 1105  
      * wildcardMatch("c.txt", "*.txt")      --> true
 1106  
      * wildcardMatch("c.txt", "*.jpg")      --> false
 1107  
      * wildcardMatch("a/b/c.txt", "a/b/*")  --> true
 1108  
      * wildcardMatch("c.txt", "*.???")      --> true
 1109  
      * wildcardMatch("c.txt", "*.????")     --> false
 1110  
      * </pre>
 1111  
      * 
 1112  
      * @param filename
 1113  
      *            the filename to match on
 1114  
      * @param wildcardMatcher
 1115  
      *            the wildcard string to match against
 1116  
      * @return true if the filename matches the wilcard string
 1117  
      * @see IOCase#SENSITIVE
 1118  
      */
 1119  
     public static boolean wildcardMatch(String filename, String wildcardMatcher) {
 1120  0
         return wildcardMatch(filename, wildcardMatcher, IOCase.SENSITIVE);
 1121  
     }
 1122  
 
 1123  
     /**
 1124  
      * Checks a filename to see if it matches the specified wildcard matcher using the case rules of the system.
 1125  
      * <p>
 1126  
      * The wildcard matcher uses the characters '?' and '*' to represent a single or multiple wildcard characters. This
 1127  
      * is the same as often found on Dos/Unix command lines. The check is case-sensitive on Unix and case-insensitive on
 1128  
      * Windows.
 1129  
      * 
 1130  
      * <pre>
 1131  
      * wildcardMatch("c.txt", "*.txt")      --> true
 1132  
      * wildcardMatch("c.txt", "*.jpg")      --> false
 1133  
      * wildcardMatch("a/b/c.txt", "a/b/*")  --> true
 1134  
      * wildcardMatch("c.txt", "*.???")      --> true
 1135  
      * wildcardMatch("c.txt", "*.????")     --> false
 1136  
      * </pre>
 1137  
      * 
 1138  
      * @param filename
 1139  
      *            the filename to match on
 1140  
      * @param wildcardMatcher
 1141  
      *            the wildcard string to match against
 1142  
      * @return true if the filename matches the wilcard string
 1143  
      * @see IOCase#SYSTEM
 1144  
      */
 1145  
     public static boolean wildcardMatchOnSystem(String filename, String wildcardMatcher) {
 1146  0
         return wildcardMatch(filename, wildcardMatcher, IOCase.SYSTEM);
 1147  
     }
 1148  
 
 1149  
     /**
 1150  
      * Checks a filename to see if it matches the specified wildcard matcher allowing control over case-sensitivity.
 1151  
      * <p>
 1152  
      * The wildcard matcher uses the characters '?' and '*' to represent a single or multiple wildcard characters.
 1153  
      * 
 1154  
      * @param filename
 1155  
      *            the filename to match on
 1156  
      * @param wildcardMatcher
 1157  
      *            the wildcard string to match against
 1158  
      * @param caseSensitivity
 1159  
      *            what case sensitivity rule to use, null means case-sensitive
 1160  
      * @return true if the filename matches the wilcard string
 1161  
      * @since Commons IO 1.3
 1162  
      */
 1163  
     public static boolean wildcardMatch(String filename, String wildcardMatcher, IOCase caseSensitivity) {
 1164  0
         if (filename == null && wildcardMatcher == null) {
 1165  0
             return true;
 1166  
         }
 1167  0
         if (filename == null || wildcardMatcher == null) {
 1168  0
             return false;
 1169  
         }
 1170  0
         if (caseSensitivity == null) {
 1171  0
             caseSensitivity = IOCase.SENSITIVE;
 1172  
         }
 1173  0
         filename = caseSensitivity.convertCase(filename);
 1174  0
         wildcardMatcher = caseSensitivity.convertCase(wildcardMatcher);
 1175  0
         String[] wcs = splitOnTokens(wildcardMatcher);
 1176  0
         boolean anyChars = false;
 1177  0
         int textIdx = 0;
 1178  0
         int wcsIdx = 0;
 1179  0
         Stack backtrack = new Stack();
 1180  
 
 1181  
         // loop around a backtrack stack, to handle complex * matching
 1182  
         do {
 1183  0
             if (backtrack.size() > 0) {
 1184  0
                 int[] array = (int[]) backtrack.pop();
 1185  0
                 wcsIdx = array[0];
 1186  0
                 textIdx = array[1];
 1187  0
                 anyChars = true;
 1188  
             }
 1189  
 
 1190  
             // loop whilst tokens and text left to process
 1191  0
             while (wcsIdx < wcs.length) {
 1192  
 
 1193  0
                 if (wcs[wcsIdx].equals("?")) {
 1194  
                     // ? so move to next text char
 1195  0
                     textIdx++;
 1196  0
                     anyChars = false;
 1197  
 
 1198  0
                 } else if (wcs[wcsIdx].equals("*")) {
 1199  
                     // set any chars status
 1200  0
                     anyChars = true;
 1201  0
                     if (wcsIdx == wcs.length - 1) {
 1202  0
                         textIdx = filename.length();
 1203  
                     }
 1204  
 
 1205  
                 } else {
 1206  
                     // matching text token
 1207  0
                     if (anyChars) {
 1208  
                         // any chars then try to locate text token
 1209  0
                         textIdx = filename.indexOf(wcs[wcsIdx], textIdx);
 1210  0
                         if (textIdx == -1) {
 1211  
                             // token not found
 1212  0
                             break;
 1213  
                         }
 1214  0
                         int repeat = filename.indexOf(wcs[wcsIdx], textIdx + 1);
 1215  0
                         if (repeat >= 0) {
 1216  0
                             backtrack.push(new int[] { wcsIdx, repeat });
 1217  
                         }
 1218  0
                     } else {
 1219  
                         // matching from current position
 1220  0
                         if (!filename.startsWith(wcs[wcsIdx], textIdx)) {
 1221  
                             // couldnt match token
 1222  0
                             break;
 1223  
                         }
 1224  
                     }
 1225  
 
 1226  
                     // matched text token, move text index to end of matched token
 1227  0
                     textIdx += wcs[wcsIdx].length();
 1228  0
                     anyChars = false;
 1229  
                 }
 1230  
 
 1231  0
                 wcsIdx++;
 1232  
             }
 1233  
 
 1234  
             // full match
 1235  0
             if (wcsIdx == wcs.length && textIdx == filename.length()) {
 1236  0
                 return true;
 1237  
             }
 1238  
 
 1239  0
         } while (backtrack.size() > 0);
 1240  
 
 1241  0
         return false;
 1242  
     }
 1243  
 
 1244  
     /**
 1245  
      * Splits a string into a number of tokens.
 1246  
      * 
 1247  
      * @param text
 1248  
      *            the text to split
 1249  
      * @return the tokens, never null
 1250  
      */
 1251  
     static String[] splitOnTokens(String text) {
 1252  
         // used by wildcardMatch
 1253  
         // package level so a unit test may run on this
 1254  
 
 1255  0
         if (text.indexOf("?") == -1 && text.indexOf("*") == -1) {
 1256  0
             return new String[] { text };
 1257  
         }
 1258  
 
 1259  0
         char[] array = text.toCharArray();
 1260  0
         ArrayList list = new ArrayList();
 1261  0
         StringBuffer buffer = new StringBuffer();
 1262  0
         for (int i = 0; i < array.length; i++) {
 1263  0
             if (array[i] == '?' || array[i] == '*') {
 1264  0
                 if (buffer.length() != 0) {
 1265  0
                     list.add(buffer.toString());
 1266  0
                     buffer.setLength(0);
 1267  
                 }
 1268  0
                 if (array[i] == '?') {
 1269  0
                     list.add("?");
 1270  0
                 } else if (list.size() == 0 || (i > 0 && list.get(list.size() - 1).equals("*") == false)) {
 1271  0
                     list.add("*");
 1272  
                 }
 1273  
             } else {
 1274  0
                 buffer.append(array[i]);
 1275  
             }
 1276  
         }
 1277  0
         if (buffer.length() != 0) {
 1278  0
             list.add(buffer.toString());
 1279  
         }
 1280  
 
 1281  0
         return (String[]) list.toArray(new String[list.size()]);
 1282  
     }
 1283  
 
 1284  
 }