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