Coverage Report - org.apache.commons.io.FilenameUtils
 
Classes in this File Line Coverage Branch Coverage Complexity
FilenameUtils
97%
316/324
97%
293/302
6,25
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *      http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.apache.commons.io;
 18  
 
 19  
 import java.io.File;
 20  
 import java.io.IOException;
 21  
 import java.util.ArrayList;
 22  
 import java.util.Collection;
 23  
 import java.util.Stack;
 24  
 
 25  
 /**
 26  
  * General filename and filepath manipulation utilities.
 27  
  * <p>
 28  
  * When dealing with filenames you can hit problems when moving from a Windows
 29  
  * based development machine to a Unix based production machine.
 30  
  * This class aims to help avoid those problems.
 31  
  * <p>
 32  
  * <b>NOTE</b>: You may be able to avoid using this class entirely simply by
 33  
  * using JDK {@link java.io.File File} objects and the two argument constructor
 34  
  * {@link java.io.File#File(java.io.File, java.lang.String) File(File,String)}.
 35  
  * <p>
 36  
  * Most methods on this class are designed to work the same on both Unix and Windows.
 37  
  * Those that don't include 'System', 'Unix' or 'Windows' in their name.
 38  
  * <p>
 39  
  * Most methods recognise both separators (forward and back), and both
 40  
  * sets of prefixes. See the javadoc of each method for details.
 41  
  * <p>
 42  
  * This class defines six components within a filename
 43  
  * (example C:\dev\project\file.txt):
 44  
  * <ul>
 45  
  * <li>the prefix - C:\</li>
 46  
  * <li>the path - dev\project\</li>
 47  
  * <li>the full path - C:\dev\project\</li>
 48  
  * <li>the name - file.txt</li>
 49  
  * <li>the base name - file</li>
 50  
  * <li>the extension - txt</li>
 51  
  * </ul>
 52  
  * Note that this class works best if directory filenames end with a separator.
 53  
  * If you omit the last separator, it is impossible to determine if the filename
 54  
  * corresponds to a file or a directory. As a result, we have chosen to say
 55  
  * it corresponds to a file.
 56  
  * <p>
 57  
  * This class only supports Unix and Windows style names.
 58  
  * Prefixes are matched as follows:
 59  
  * <pre>
 60  
  * Windows:
 61  
  * a\b\c.txt           --&gt; ""          --&gt; relative
 62  
  * \a\b\c.txt          --&gt; "\"         --&gt; current drive absolute
 63  
  * C:a\b\c.txt         --&gt; "C:"        --&gt; drive relative
 64  
  * C:\a\b\c.txt        --&gt; "C:\"       --&gt; absolute
 65  
  * \\server\a\b\c.txt  --&gt; "\\server\" --&gt; UNC
 66  
  *
 67  
  * Unix:
 68  
  * a/b/c.txt           --&gt; ""          --&gt; relative
 69  
  * /a/b/c.txt          --&gt; "/"         --&gt; absolute
 70  
  * ~/a/b/c.txt         --&gt; "~/"        --&gt; current user
 71  
  * ~                   --&gt; "~/"        --&gt; current user (slash added)
 72  
  * ~user/a/b/c.txt     --&gt; "~user/"    --&gt; named user
 73  
  * ~user               --&gt; "~user/"    --&gt; named user (slash added)
 74  
  * </pre>
 75  
  * Both prefix styles are matched always, irrespective of the machine that you are
 76  
  * currently running on.
 77  
  * <p>
 78  
  * Origin of code: Excalibur, Alexandria, Tomcat, Commons-Utils.
 79  
  *
 80  
  * @since 1.1
 81  
  */
 82  
 public class FilenameUtils {
 83  
 
 84  
     private static final int NOT_FOUND = -1;
 85  
 
 86  
     /**
 87  
      * The extension separator character.
 88  
      * @since 1.4
 89  
      */
 90  
     public static final char EXTENSION_SEPARATOR = '.';
 91  
 
 92  
     /**
 93  
      * The extension separator String.
 94  
      * @since 1.4
 95  
      */
 96  40
     public static final String EXTENSION_SEPARATOR_STR = Character.toString(EXTENSION_SEPARATOR);
 97  
 
 98  
     /**
 99  
      * 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  40
     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  40
         if (isSystemWindows()) {
 119  0
             OTHER_SEPARATOR = UNIX_SEPARATOR;
 120  
         } else {
 121  40
             OTHER_SEPARATOR = WINDOWS_SEPARATOR;
 122  
         }
 123  40
     }
 124  
 
 125  
     /**
 126  
      * Instances should NOT be constructed in standard programming.
 127  
      */
 128  
     public FilenameUtils() {
 129  0
         super();
 130  0
     }
 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  92
         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  1426
         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//               --&gt;   /foo/
 172  
      * /foo/./              --&gt;   /foo/
 173  
      * /foo/../bar          --&gt;   /bar
 174  
      * /foo/../bar/         --&gt;   /bar/
 175  
      * /foo/../bar/../baz   --&gt;   /baz
 176  
      * //foo//./bar         --&gt;   /foo/bar
 177  
      * /../                 --&gt;   null
 178  
      * ../foo               --&gt;   null
 179  
      * foo/bar/..           --&gt;   foo/
 180  
      * foo/../../bar        --&gt;   null
 181  
      * foo/../bar           --&gt;   bar
 182  
      * //server/foo/../bar  --&gt;   //server/bar
 183  
      * //server/../bar      --&gt;   null
 184  
      * C:\foo\..\bar        --&gt;   C:\bar
 185  
      * C:\..\bar            --&gt;   null
 186  
      * ~/foo/../bar/        --&gt;   ~/bar/
 187  
      * ~/../bar             --&gt;   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  396
         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//               --&gt;   /foo/
 215  
      * /foo/./              --&gt;   /foo/
 216  
      * /foo/../bar          --&gt;   /bar
 217  
      * /foo/../bar/         --&gt;   /bar/
 218  
      * /foo/../bar/../baz   --&gt;   /baz
 219  
      * //foo//./bar         --&gt;   /foo/bar
 220  
      * /../                 --&gt;   null
 221  
      * ../foo               --&gt;   null
 222  
      * foo/bar/..           --&gt;   foo/
 223  
      * foo/../../bar        --&gt;   null
 224  
      * foo/../bar           --&gt;   bar
 225  
      * //server/foo/../bar  --&gt;   //server/bar
 226  
      * //server/../bar      --&gt;   null
 227  
      * C:\foo\..\bar        --&gt;   C:\bar
 228  
      * C:\..\bar            --&gt;   null
 229  
      * ~/foo/../bar/        --&gt;   ~/bar/
 230  
      * ~/../bar             --&gt;   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  26
         final char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR;
 243  26
         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//               --&gt;   /foo
 266  
      * /foo/./              --&gt;   /foo
 267  
      * /foo/../bar          --&gt;   /bar
 268  
      * /foo/../bar/         --&gt;   /bar
 269  
      * /foo/../bar/../baz   --&gt;   /baz
 270  
      * //foo//./bar         --&gt;   /foo/bar
 271  
      * /../                 --&gt;   null
 272  
      * ../foo               --&gt;   null
 273  
      * foo/bar/..           --&gt;   foo
 274  
      * foo/../../bar        --&gt;   null
 275  
      * foo/../bar           --&gt;   bar
 276  
      * //server/foo/../bar  --&gt;   //server/bar
 277  
      * //server/../bar      --&gt;   null
 278  
      * C:\foo\..\bar        --&gt;   C:\bar
 279  
      * C:\..\bar            --&gt;   null
 280  
      * ~/foo/../bar/        --&gt;   ~/bar
 281  
      * ~/../bar             --&gt;   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  276
         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//               --&gt;   /foo
 311  
      * /foo/./              --&gt;   /foo
 312  
      * /foo/../bar          --&gt;   /bar
 313  
      * /foo/../bar/         --&gt;   /bar
 314  
      * /foo/../bar/../baz   --&gt;   /baz
 315  
      * //foo//./bar         --&gt;   /foo/bar
 316  
      * /../                 --&gt;   null
 317  
      * ../foo               --&gt;   null
 318  
      * foo/bar/..           --&gt;   foo
 319  
      * foo/../../bar        --&gt;   null
 320  
      * foo/../bar           --&gt;   bar
 321  
      * //server/foo/../bar  --&gt;   //server/bar
 322  
      * //server/../bar      --&gt;   null
 323  
      * C:\foo\..\bar        --&gt;   C:\bar
 324  
      * C:\..\bar            --&gt;   null
 325  
      * ~/foo/../bar/        --&gt;   ~/bar
 326  
      * ~/../bar             --&gt;   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  8
          final char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR;
 337  8
         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  706
         if (filename == null) {
 350  4
             return null;
 351  
         }
 352  
 
 353  702
         failIfNullBytePresent(filename);
 354  
 
 355  698
         int size = filename.length();
 356  698
         if (size == 0) {
 357  16
             return filename;
 358  
         }
 359  682
         final int prefix = getPrefixLength(filename);
 360  682
         if (prefix < 0) {
 361  34
             return null;
 362  
         }
 363  
 
 364  648
         final char[] array = new char[size + 2];  // +1 for possible extra slash, +2 for arraycopy
 365  648
         filename.getChars(0, filename.length(), array, 0);
 366  
 
 367  
         // fix separators throughout
 368  648
         final char otherSeparator = separator == SYSTEM_SEPARATOR ? OTHER_SEPARATOR : SYSTEM_SEPARATOR;
 369  7818
         for (int i = 0; i < array.length; i++) {
 370  7170
             if (array[i] == otherSeparator) {
 371  100
                 array[i] = separator;
 372  
             }
 373  
         }
 374  
 
 375  
         // add extra separator on the end to simplify code below
 376  648
         boolean lastIsDirectory = true;
 377  648
         if (array[size - 1] != separator) {
 378  518
             array[size++] = separator;
 379  518
             lastIsDirectory = false;
 380  
         }
 381  
 
 382  
         // adjoining slashes
 383  4648
         for (int i = prefix + 1; i < size; i++) {
 384  4000
             if (array[i] == separator && array[i - 1] == separator) {
 385  28
                 System.arraycopy(array, i, array, i - 1, size - i);
 386  28
                 size--;
 387  28
                 i--;
 388  
             }
 389  
         }
 390  
 
 391  
         // dot slash
 392  4524
         for (int i = prefix + 1; i < size; i++) {
 393  3876
             if (array[i] == separator && array[i - 1] == '.' &&
 394  
                     (i == prefix + 1 || array[i - 2] == separator)) {
 395  184
                 if (i == size - 1) {
 396  88
                     lastIsDirectory = true;
 397  
                 }
 398  184
                 System.arraycopy(array, i + 1, array, i - 1, size - i);
 399  184
                 size -=2;
 400  184
                 i--;
 401  
             }
 402  
         }
 403  
 
 404  
         // double dot slash
 405  
         outer:
 406  3122
         for (int i = prefix + 2; i < size; i++) {
 407  2586
             if (array[i] == separator && array[i - 1] == '.' && array[i - 2] == '.' &&
 408  
                     (i == prefix + 2 || array[i - 3] == separator)) {
 409  504
                 if (i == prefix + 2) {
 410  112
                     return null;
 411  
                 }
 412  392
                 if (i == size - 1) {
 413  64
                     lastIsDirectory = true;
 414  
                 }
 415  
                 int j;
 416  784
                 for (j = i - 4 ; j >= prefix; j--) {
 417  664
                     if (array[j] == separator) {
 418  
                         // remove b/../ from a/b/../c
 419  272
                         System.arraycopy(array, i + 1, array, j + 1, size - i);
 420  272
                         size -= i - j;
 421  272
                         i = j + 1;
 422  272
                         continue outer;
 423  
                     }
 424  
                 }
 425  
                 // remove a/../ from a/../c
 426  120
                 System.arraycopy(array, i + 1, array, prefix, size - i);
 427  120
                 size -= i + 1 - prefix;
 428  120
                 i = prefix + 1;
 429  
             }
 430  
         }
 431  
 
 432  536
         if (size <= 0) {  // should never be less than 0
 433  16
             return "";
 434  
         }
 435  520
         if (size <= prefix) {  // should never be less than prefix
 436  104
             return new String(array, 0, size);
 437  
         }
 438  416
         if (lastIsDirectory && keepSeparator) {
 439  76
             return new String(array, 0, size);  // keep trailing separator
 440  
         }
 441  340
         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          --&gt;   /foo/bar
 464  
      * /foo + bar           --&gt;   /foo/bar
 465  
      * /foo + /bar          --&gt;   /bar
 466  
      * /foo + C:/bar        --&gt;   C:/bar
 467  
      * /foo + C:bar         --&gt;   C:bar (*)
 468  
      * /foo/a/ + ../bar     --&gt;   foo/bar
 469  
      * /foo/ + ../../bar    --&gt;   null
 470  
      * /foo/ + /bar         --&gt;   /bar
 471  
      * /foo/.. + /bar       --&gt;   /bar
 472  
      * /foo + bar/c.txt     --&gt;   /foo/bar/c.txt
 473  
      * /foo/c.txt + bar     --&gt;   /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  58
         final int prefix = getPrefixLength(fullFilenameToAdd);
 487  58
         if (prefix < 0) {
 488  6
             return null;
 489  
         }
 490  52
         if (prefix > 0) {
 491  24
             return normalize(fullFilenameToAdd);
 492  
         }
 493  28
         if (basePath == null) {
 494  4
             return null;
 495  
         }
 496  24
         final int len = basePath.length();
 497  24
         if (len == 0) {
 498  4
             return normalize(fullFilenameToAdd);
 499  
         }
 500  20
         final char ch = basePath.charAt(len - 1);
 501  20
         if (isSeparator(ch)) {
 502  8
             return normalize(basePath + fullFilenameToAdd);
 503  
         } else {
 504  12
             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  36
         if (canonicalParent == null) {
 536  0
             throw new IllegalArgumentException("Directory must not be null");
 537  
         }
 538  
 
 539  36
         if (canonicalChild == null) {
 540  0
             return false;
 541  
         }
 542  
 
 543  36
         if (IOCase.SYSTEM.checkEquals(canonicalParent, canonicalChild)) {
 544  0
             return false;
 545  
         }
 546  
 
 547  36
         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  22
         if (path == null || path.indexOf(WINDOWS_SEPARATOR) == NOT_FOUND) {
 559  10
             return path;
 560  
         }
 561  12
         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  12
         if (path == null || path.indexOf(UNIX_SEPARATOR) == NOT_FOUND) {
 572  6
             return path;
 573  
         }
 574  6
         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  12
         if (path == null) {
 585  2
             return null;
 586  
         }
 587  10
         if (isSystemWindows()) {
 588  0
             return separatorsToWindows(path);
 589  
         } else {
 590  10
             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           --&gt; ""          --&gt; relative
 606  
      * \a\b\c.txt          --&gt; "\"         --&gt; current drive absolute
 607  
      * C:a\b\c.txt         --&gt; "C:"        --&gt; drive relative
 608  
      * C:\a\b\c.txt        --&gt; "C:\"       --&gt; absolute
 609  
      * \\server\a\b\c.txt  --&gt; "\\server\" --&gt; UNC
 610  
      * \\\a\b\c.txt        --&gt;  error, length = -1
 611  
      *
 612  
      * Unix:
 613  
      * a/b/c.txt           --&gt; ""          --&gt; relative
 614  
      * /a/b/c.txt          --&gt; "/"         --&gt; absolute
 615  
      * ~/a/b/c.txt         --&gt; "~/"        --&gt; current user
 616  
      * ~                   --&gt; "~/"        --&gt; current user (slash added)
 617  
      * ~user/a/b/c.txt     --&gt; "~user/"    --&gt; named user
 618  
      * ~user               --&gt; "~user/"    --&gt; named user (slash added)
 619  
      * //server/a/b/c.txt  --&gt; "//server/"
 620  
      * ///a/b/c.txt        --&gt; 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  1130
         if (filename == null) {
 635  6
             return NOT_FOUND;
 636  
         }
 637  1124
         final int len = filename.length();
 638  1124
         if (len == 0) {
 639  18
             return 0;
 640  
         }
 641  1106
         char ch0 = filename.charAt(0);
 642  1106
         if (ch0 == ':') {
 643  20
             return NOT_FOUND;
 644  
         }
 645  1086
         if (len == 1) {
 646  50
             if (ch0 == '~') {
 647  22
                 return 2;  // return a length greater than the input
 648  
             }
 649  28
             return isSeparator(ch0) ? 1 : 0;
 650  
         } else {
 651  1036
             if (ch0 == '~') {
 652  240
                 int posUnix = filename.indexOf(UNIX_SEPARATOR, 1);
 653  240
                 int posWin = filename.indexOf(WINDOWS_SEPARATOR, 1);
 654  240
                 if (posUnix == NOT_FOUND && posWin == NOT_FOUND) {
 655  22
                     return len + 1;  // return a length greater than the input
 656  
                 }
 657  218
                 posUnix = posUnix == NOT_FOUND ? posWin : posUnix;
 658  218
                 posWin = posWin == NOT_FOUND ? posUnix : posWin;
 659  218
                 return Math.min(posUnix, posWin) + 1;
 660  
             }
 661  796
             final char ch1 = filename.charAt(1);
 662  796
             if (ch1 == ':') {
 663  276
                 ch0 = Character.toUpperCase(ch0);
 664  276
                 if (ch0 >= 'A' && ch0 <= 'Z') {
 665  222
                     if (len == 2 || isSeparator(filename.charAt(2)) == false) {
 666  114
                         return 2;
 667  
                     }
 668  108
                     return 3;
 669  54
                 } else if (ch0 == UNIX_SEPARATOR) {
 670  6
                     return 1;
 671  
                 }
 672  48
                 return NOT_FOUND;
 673  
 
 674  520
             } else if (isSeparator(ch0) && isSeparator(ch1)) {
 675  148
                 int posUnix = filename.indexOf(UNIX_SEPARATOR, 2);
 676  148
                 int posWin = filename.indexOf(WINDOWS_SEPARATOR, 2);
 677  148
                 if (posUnix == NOT_FOUND && posWin == NOT_FOUND || posUnix == 2 || posWin == 2) {
 678  44
                     return NOT_FOUND;
 679  
                 }
 680  104
                 posUnix = posUnix == NOT_FOUND ? posWin : posUnix;
 681  104
                 posWin = posWin == NOT_FOUND ? posUnix : posWin;
 682  104
                 return Math.min(posUnix, posWin) + 1;
 683  
             } else {
 684  372
                 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  526
         if (filename == null) {
 703  2
             return NOT_FOUND;
 704  
         }
 705  524
         final int lastUnixPos = filename.lastIndexOf(UNIX_SEPARATOR);
 706  524
         final int lastWindowsPos = filename.lastIndexOf(WINDOWS_SEPARATOR);
 707  524
         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  342
         if (filename == null) {
 726  2
             return NOT_FOUND;
 727  
         }
 728  340
         final int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR);
 729  340
         final int lastSeparator = indexOfLastSeparator(filename);
 730  340
         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           --&gt; ""          --&gt; relative
 743  
      * \a\b\c.txt          --&gt; "\"         --&gt; current drive absolute
 744  
      * C:a\b\c.txt         --&gt; "C:"        --&gt; drive relative
 745  
      * C:\a\b\c.txt        --&gt; "C:\"       --&gt; absolute
 746  
      * \\server\a\b\c.txt  --&gt; "\\server\" --&gt; UNC
 747  
      *
 748  
      * Unix:
 749  
      * a/b/c.txt           --&gt; ""          --&gt; relative
 750  
      * /a/b/c.txt          --&gt; "/"         --&gt; absolute
 751  
      * ~/a/b/c.txt         --&gt; "~/"        --&gt; current user
 752  
      * ~                   --&gt; "~/"        --&gt; current user (slash added)
 753  
      * ~user/a/b/c.txt     --&gt; "~user/"    --&gt; named user
 754  
      * ~user               --&gt; "~user/"    --&gt; 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  78
         if (filename == null) {
 765  2
             return null;
 766  
         }
 767  76
         final int len = getPrefixLength(filename);
 768  76
         if (len < 0) {
 769  12
             return null;
 770  
         }
 771  64
         if (len > filename.length()) {
 772  8
             failIfNullBytePresent(filename + UNIX_SEPARATOR);
 773  8
             return filename + UNIX_SEPARATOR;
 774  
         }
 775  56
         String path = filename.substring(0, len);
 776  56
         failIfNullBytePresent(path);
 777  54
         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 --&gt; a\b\
 788  
      * ~/a/b/c.txt  --&gt; a/b/
 789  
      * a.txt        --&gt; ""
 790  
      * a/b/c        --&gt; a/b/
 791  
      * a/b/c/       --&gt; 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  66
         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 --&gt; a\b
 816  
      * ~/a/b/c.txt  --&gt; a/b
 817  
      * a.txt        --&gt; ""
 818  
      * a/b/c        --&gt; a/b
 819  
      * a/b/c/       --&gt; 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  58
         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  124
         if (filename == null) {
 844  4
             return null;
 845  
         }
 846  120
         final int prefix = getPrefixLength(filename);
 847  120
         if (prefix < 0) {
 848  24
             return null;
 849  
         }
 850  96
         final int index = indexOfLastSeparator(filename);
 851  96
         final int endIndex = index+separatorAdd;
 852  96
         if (prefix >= filename.length() || index < 0 || prefix >= endIndex) {
 853  48
             return "";
 854  
         }
 855  48
         String path = filename.substring(prefix, endIndex);
 856  48
         failIfNullBytePresent(path);
 857  44
         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 --&gt; C:\a\b\
 868  
      * ~/a/b/c.txt  --&gt; ~/a/b/
 869  
      * a.txt        --&gt; ""
 870  
      * a/b/c        --&gt; a/b/
 871  
      * a/b/c/       --&gt; a/b/c/
 872  
      * C:           --&gt; C:
 873  
      * C:\          --&gt; C:\
 874  
      * ~            --&gt; ~/
 875  
      * ~/           --&gt; ~/
 876  
      * ~user        --&gt; ~user/
 877  
      * ~user/       --&gt; ~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  56
         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 --&gt; C:\a\b
 898  
      * ~/a/b/c.txt  --&gt; ~/a/b
 899  
      * a.txt        --&gt; ""
 900  
      * a/b/c        --&gt; a/b
 901  
      * a/b/c/       --&gt; a/b/c
 902  
      * C:           --&gt; C:
 903  
      * C:\          --&gt; C:\
 904  
      * ~            --&gt; ~
 905  
      * ~/           --&gt; ~
 906  
      * ~user        --&gt; ~user
 907  
      * ~user/       --&gt; ~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  68
         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  124
         if (filename == null) {
 928  4
             return null;
 929  
         }
 930  120
         final int prefix = getPrefixLength(filename);
 931  120
         if (prefix < 0) {
 932  24
             return null;
 933  
         }
 934  96
         if (prefix >= filename.length()) {
 935  36
             if (includeSeparator) {
 936  16
                 return getPrefix(filename);  // add end slash if necessary
 937  
             } else {
 938  20
                 return filename;
 939  
             }
 940  
         }
 941  60
         final int index = indexOfLastSeparator(filename);
 942  60
         if (index < 0) {
 943  8
             return filename.substring(0, prefix);
 944  
         }
 945  52
         int end = index + (includeSeparator ?  1 : 0);
 946  52
         if (end == 0) {
 947  4
             end++;
 948  
         }
 949  52
         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 --&gt; c.txt
 959  
      * a.txt     --&gt; a.txt
 960  
      * a/b/c     --&gt; c
 961  
      * a/b/c/    --&gt; ""
 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  30
         if (filename == null) {
 972  4
             return null;
 973  
         }
 974  26
         failIfNullBytePresent(filename);
 975  22
         final int index = indexOfLastSeparator(filename);
 976  22
         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(String path) {
 986  1040
         int len = path.length();
 987  9802
         for (int i = 0; i < len; i++) {
 988  8778
             if (path.charAt(i) == 0) {
 989  16
                 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  1024
     }
 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 --&gt; c
 1002  
      * a.txt     --&gt; a
 1003  
      * a/b/c     --&gt; c
 1004  
      * a/b/c/    --&gt; ""
 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  16
         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      --&gt; "txt"
 1024  
      * a/b/c.jpg    --&gt; "jpg"
 1025  
      * a/b.txt/c    --&gt; ""
 1026  
      * a/b/c        --&gt; ""
 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  224
         if (filename == null) {
 1037  2
             return null;
 1038  
         }
 1039  222
         final int index = indexOfExtension(filename);
 1040  222
         if (index == NOT_FOUND) {
 1041  16
             return "";
 1042  
         } else {
 1043  206
             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    --&gt; foo
 1055  
      * a\b\c.jpg  --&gt; a\b\c
 1056  
      * a\b\c      --&gt; a\b\c
 1057  
      * a.b\c      --&gt; 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  40
         if (filename == null) {
 1067  4
             return null;
 1068  
         }
 1069  36
         failIfNullBytePresent(filename);
 1070  
 
 1071  36
         final int index = indexOfExtension(filename);
 1072  36
         if (index == NOT_FOUND) {
 1073  18
             return filename;
 1074  
         } else {
 1075  18
             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  14
         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  14
         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  16
         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  22
         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  74
         if (filename1 == null || filename2 == null) {
 1159  24
             return filename1 == null && filename2 == null;
 1160  
         }
 1161  50
         if (normalized) {
 1162  34
             filename1 = normalize(filename1);
 1163  34
             filename2 = normalize(filename2);
 1164  34
             if (filename1 == null || filename2 == null) {
 1165  6
                 throw new NullPointerException(
 1166  
                     "Error normalizing one or both of the file names");
 1167  
             }
 1168  
         }
 1169  44
         if (caseSensitivity == null) {
 1170  2
             caseSensitivity = IOCase.SENSITIVE;
 1171  
         }
 1172  44
         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  50
         if (filename == null) {
 1190  2
             return false;
 1191  
         }
 1192  48
         failIfNullBytePresent(filename);
 1193  
 
 1194  46
         if (extension == null || extension.isEmpty()) {
 1195  24
             return indexOfExtension(filename) == NOT_FOUND;
 1196  
         }
 1197  22
         final String fileExt = getExtension(filename);
 1198  22
         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  60
         if (filename == null) {
 1215  2
             return false;
 1216  
         }
 1217  58
         failIfNullBytePresent(filename);
 1218  
 
 1219  58
         if (extensions == null || extensions.length == 0) {
 1220  22
             return indexOfExtension(filename) == NOT_FOUND;
 1221  
         }
 1222  36
         final String fileExt = getExtension(filename);
 1223  64
         for (final String extension : extensions) {
 1224  50
             if (fileExt.equals(extension)) {
 1225  22
                 return true;
 1226  
             }
 1227  
         }
 1228  14
         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  60
         if (filename == null) {
 1245  2
             return false;
 1246  
         }
 1247  58
         failIfNullBytePresent(filename);
 1248  
 
 1249  58
         if (extensions == null || extensions.isEmpty()) {
 1250  22
             return indexOfExtension(filename) == NOT_FOUND;
 1251  
         }
 1252  36
         final String fileExt = getExtension(filename);
 1253  36
         for (final String extension : extensions) {
 1254  50
             if (fileExt.equals(extension)) {
 1255  22
                 return true;
 1256  
             }
 1257  28
         }
 1258  14
         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")      --&gt; true
 1272  
      * wildcardMatch("c.txt", "*.jpg")      --&gt; false
 1273  
      * wildcardMatch("a/b/c.txt", "a/b/*")  --&gt; true
 1274  
      * wildcardMatch("c.txt", "*.???")      --&gt; true
 1275  
      * wildcardMatch("c.txt", "*.????")     --&gt; 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  318
         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")      --&gt; true
 1298  
      * wildcardMatch("c.txt", "*.jpg")      --&gt; false
 1299  
      * wildcardMatch("a/b/c.txt", "a/b/*")  --&gt; true
 1300  
      * wildcardMatch("c.txt", "*.???")      --&gt; true
 1301  
      * wildcardMatch("c.txt", "*.????")     --&gt; 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  40
         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  2748
         if (filename == null && wildcardMatcher == null) {
 1330  6
             return true;
 1331  
         }
 1332  2742
         if (filename == null || wildcardMatcher == null) {
 1333  12
             return false;
 1334  
         }
 1335  2730
         if (caseSensitivity == null) {
 1336  0
             caseSensitivity = IOCase.SENSITIVE;
 1337  
         }
 1338  2730
         final String[] wcs = splitOnTokens(wildcardMatcher);
 1339  2730
         boolean anyChars = false;
 1340  2730
         int textIdx = 0;
 1341  2730
         int wcsIdx = 0;
 1342  2730
         final Stack<int[]> backtrack = new Stack<>();
 1343  
 
 1344  
         // loop around a backtrack stack, to handle complex * matching
 1345  
         do {
 1346  2750
             if (backtrack.size() > 0) {
 1347  20
                 final int[] array = backtrack.pop();
 1348  20
                 wcsIdx = array[0];
 1349  20
                 textIdx = array[1];
 1350  20
                 anyChars = true;
 1351  
             }
 1352  
 
 1353  
             // loop whilst tokens and text left to process
 1354  5888
             while (wcsIdx < wcs.length) {
 1355  
 
 1356  3398
                 if (wcs[wcsIdx].equals("?")) {
 1357  
                     // ? so move to next text char
 1358  140
                     textIdx++;
 1359  140
                     if (textIdx > filename.length()) {
 1360  18
                         break;
 1361  
                     }
 1362  122
                     anyChars = false;
 1363  
 
 1364  3258
                 } else if (wcs[wcsIdx].equals("*")) {
 1365  
                     // set any chars status
 1366  446
                     anyChars = true;
 1367  446
                     if (wcsIdx == wcs.length - 1) {
 1368  142
                         textIdx = filename.length();
 1369  
                     }
 1370  
 
 1371  
                 } else {
 1372  
                     // matching text token
 1373  2812
                     if (anyChars) {
 1374  
                         // any chars then try to locate text token
 1375  322
                         textIdx = caseSensitivity.checkIndexOf(filename, textIdx, wcs[wcsIdx]);
 1376  322
                         if (textIdx == NOT_FOUND) {
 1377  
                             // token not found
 1378  140
                             break;
 1379  
                         }
 1380  182
                         final int repeat = caseSensitivity.checkIndexOf(filename, textIdx + 1, wcs[wcsIdx]);
 1381  182
                         if (repeat >= 0) {
 1382  22
                             backtrack.push(new int[] {wcsIdx, repeat});
 1383  
                         }
 1384  182
                     } else {
 1385  
                         // matching from current position
 1386  2490
                         if (!caseSensitivity.checkRegionMatches(filename, textIdx, wcs[wcsIdx])) {
 1387  
                             // couldnt match token
 1388  102
                             break;
 1389  
                         }
 1390  
                     }
 1391  
 
 1392  
                     // matched text token, move text index to end of matched token
 1393  2570
                     textIdx += wcs[wcsIdx].length();
 1394  2570
                     anyChars = false;
 1395  
                 }
 1396  
 
 1397  3138
                 wcsIdx++;
 1398  
             }
 1399  
 
 1400  
             // full match
 1401  2750
             if (wcsIdx == wcs.length && textIdx == filename.length()) {
 1402  2472
                 return true;
 1403  
             }
 1404  
 
 1405  278
         } while (backtrack.size() > 0);
 1406  
 
 1407  258
         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  2750
         if (text.indexOf('?') == NOT_FOUND && text.indexOf('*') == NOT_FOUND) {
 1423  2270
             return new String[] { text };
 1424  
         }
 1425  
 
 1426  480
         final char[] array = text.toCharArray();
 1427  480
         final ArrayList<String> list = new ArrayList<>();
 1428  480
         final StringBuilder buffer = new StringBuilder();
 1429  480
         char prevChar = 0;
 1430  3098
         for (final char ch : array) {
 1431  2618
             if (ch == '?' || ch == '*') {
 1432  694
                 if (buffer.length() != 0) {
 1433  274
                     list.add(buffer.toString());
 1434  274
                     buffer.setLength(0);
 1435  
                 }
 1436  694
                 if (ch == '?') {
 1437  158
                     list.add("?");
 1438  536
                 } else if (prevChar != '*') {// ch == '*' here; check if previous char was '*'
 1439  514
                     list.add("*");
 1440  
                 }
 1441  
             } else {
 1442  1924
                 buffer.append(ch);
 1443  
             }
 1444  2618
             prevChar = ch;
 1445  
         }
 1446  480
         if (buffer.length() != 0) {
 1447  262
             list.add(buffer.toString());
 1448  
         }
 1449  
 
 1450  480
         return list.toArray( new String[ list.size() ] );
 1451  
     }
 1452  
 
 1453  
 }