2 * Licensed to the Apache Software Foundation (ASF) under one or more
\r
3 * contributor license agreements. See the NOTICE file distributed with
\r
4 * this work for additional information regarding copyright ownership.
\r
5 * The ASF licenses this file to You under the Apache License, Version 2.0
\r
6 * (the "License"); you may not use this file except in compliance with
\r
7 * the License. You may obtain a copy of the License at
\r
9 * http://www.apache.org/licenses/LICENSE-2.0
\r
11 * Unless required by applicable law or agreed to in writing, software
\r
12 * distributed under the License is distributed on an "AS IS" BASIS,
\r
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
14 * See the License for the specific language governing permissions and
\r
15 * limitations under the License.
\r
17 package org.apache.commons.io;
\r
19 import java.io.File;
\r
20 import java.util.ArrayList;
\r
21 import java.util.Collection;
\r
22 import java.util.Iterator;
\r
23 import java.util.Stack;
\r
26 * General filename and filepath manipulation utilities.
\r
28 * When dealing with filenames you can hit problems when moving from a Windows
\r
29 * based development machine to a Unix based production machine.
\r
30 * This class aims to help avoid those problems.
\r
32 * <b>NOTE</b>: You may be able to avoid using this class entirely simply by
\r
33 * using JDK {@link java.io.File File} objects and the two argument constructor
\r
34 * {@link java.io.File#File(java.io.File, java.lang.String) File(File,String)}.
\r
36 * Most methods on this class are designed to work the same on both Unix and Windows.
\r
37 * Those that don't include 'System', 'Unix' or 'Windows' in their name.
\r
39 * Most methods recognise both separators (forward and back), and both
\r
40 * sets of prefixes. See the javadoc of each method for details.
\r
42 * This class defines six components within a filename
\r
43 * (example C:\dev\project\file.txt):
\r
45 * <li>the prefix - C:\</li>
\r
46 * <li>the path - dev\project\</li>
\r
47 * <li>the full path - C:\dev\project\</li>
\r
48 * <li>the name - file.txt</li>
\r
49 * <li>the base name - file</li>
\r
50 * <li>the extension - txt</li>
\r
52 * Note that this class works best if directory filenames end with a separator.
\r
53 * If you omit the last separator, it is impossible to determine if the filename
\r
54 * corresponds to a file or a directory. As a result, we have chosen to say
\r
55 * it corresponds to a file.
\r
57 * This class only supports Unix and Windows style names.
\r
58 * Prefixes are matched as follows:
\r
61 * a\b\c.txt --> "" --> relative
\r
62 * \a\b\c.txt --> "\" --> current drive absolute
\r
63 * C:a\b\c.txt --> "C:" --> drive relative
\r
64 * C:\a\b\c.txt --> "C:\" --> absolute
\r
65 * \\server\a\b\c.txt --> "\\server\" --> UNC
\r
68 * a/b/c.txt --> "" --> relative
\r
69 * /a/b/c.txt --> "/" --> absolute
\r
70 * ~/a/b/c.txt --> "~/" --> current user
\r
71 * ~ --> "~/" --> current user (slash added)
\r
72 * ~user/a/b/c.txt --> "~user/" --> named user
\r
73 * ~user --> "~user/" --> named user (slash added)
\r
75 * Both prefix styles are matched always, irrespective of the machine that you are
\r
76 * currently running on.
\r
78 * Origin of code: Excalibur, Alexandria, Tomcat, Commons-Utils.
\r
80 * @author <a href="mailto:burton@relativity.yi.org">Kevin A. Burton</A>
\r
81 * @author <a href="mailto:sanders@apache.org">Scott Sanders</a>
\r
82 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
\r
83 * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph.Reck</a>
\r
84 * @author <a href="mailto:peter@apache.org">Peter Donald</a>
\r
85 * @author <a href="mailto:jefft@apache.org">Jeff Turner</a>
\r
86 * @author Matthew Hawthorne
\r
87 * @author Martin Cooper
\r
88 * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
\r
89 * @author Stephen Colebourne
\r
90 * @version $Id: FilenameUtils.java 609870 2008-01-08 04:46:26Z niallp $
\r
91 * @since Commons IO 1.1
\r
93 public class FilenameUtils {
\r
96 * The extension separator character.
\r
97 * @since Commons IO 1.4
\r
99 public static final char EXTENSION_SEPARATOR = '.';
\r
102 * The extension separator String.
\r
103 * @since Commons IO 1.4
\r
105 public static final String EXTENSION_SEPARATOR_STR = (new Character(EXTENSION_SEPARATOR)).toString();
\r
108 * The Unix separator character.
\r
110 private static final char UNIX_SEPARATOR = '/';
\r
113 * The Windows separator character.
\r
115 private static final char WINDOWS_SEPARATOR = '\\';
\r
118 * The system separator character.
\r
120 private static final char SYSTEM_SEPARATOR = File.separatorChar;
\r
123 * The separator character that is the opposite of the system separator.
\r
125 private static final char OTHER_SEPARATOR;
\r
127 if (isSystemWindows()) {
\r
128 OTHER_SEPARATOR = UNIX_SEPARATOR;
\r
130 OTHER_SEPARATOR = WINDOWS_SEPARATOR;
\r
135 * Instances should NOT be constructed in standard programming.
\r
137 public FilenameUtils() {
\r
141 //-----------------------------------------------------------------------
\r
143 * Determines if Windows file system is in use.
\r
145 * @return true if the system is Windows
\r
147 static boolean isSystemWindows() {
\r
148 return SYSTEM_SEPARATOR == WINDOWS_SEPARATOR;
\r
151 //-----------------------------------------------------------------------
\r
153 * Checks if the character is a separator.
\r
155 * @param ch the character to check
\r
156 * @return true if it is a separator character
\r
158 private static boolean isSeparator(char ch) {
\r
159 return (ch == UNIX_SEPARATOR) || (ch == WINDOWS_SEPARATOR);
\r
162 //-----------------------------------------------------------------------
\r
164 * Normalizes a path, removing double and single dot path steps.
\r
166 * This method normalizes a path to a standard format.
\r
167 * The input may contain separators in either Unix or Windows format.
\r
168 * The output will contain separators in the format of the system.
\r
170 * A trailing slash will be retained.
\r
171 * A double slash will be merged to a single slash (but UNC names are handled).
\r
172 * A single dot path segment will be removed.
\r
173 * A double dot will cause that path segment and the one before to be removed.
\r
174 * If the double dot has no parent path segment to work with, <code>null</code>
\r
177 * The output will be the same on both Unix and Windows except
\r
178 * for the separator character.
\r
181 * /foo/./ --> /foo/
\r
182 * /foo/../bar --> /bar
\r
183 * /foo/../bar/ --> /bar/
\r
184 * /foo/../bar/../baz --> /baz
\r
185 * //foo//./bar --> /foo/bar
\r
188 * foo/bar/.. --> foo/
\r
189 * foo/../../bar --> null
\r
190 * foo/../bar --> bar
\r
191 * //server/foo/../bar --> //server/bar
\r
192 * //server/../bar --> null
\r
193 * C:\foo\..\bar --> C:\bar
\r
194 * C:\..\bar --> null
\r
195 * ~/foo/../bar/ --> ~/bar/
\r
196 * ~/../bar --> null
\r
198 * (Note the file separator returned will be correct for Windows/Unix)
\r
200 * @param filename the filename to normalize, null returns null
\r
201 * @return the normalized filename, or null if invalid
\r
203 public static String normalize(String filename) {
\r
204 return doNormalize(filename, true);
\r
207 //-----------------------------------------------------------------------
\r
209 * Normalizes a path, removing double and single dot path steps,
\r
210 * and removing any final directory separator.
\r
212 * This method normalizes a path to a standard format.
\r
213 * The input may contain separators in either Unix or Windows format.
\r
214 * The output will contain separators in the format of the system.
\r
216 * A trailing slash will be removed.
\r
217 * A double slash will be merged to a single slash (but UNC names are handled).
\r
218 * A single dot path segment will be removed.
\r
219 * A double dot will cause that path segment and the one before to be removed.
\r
220 * If the double dot has no parent path segment to work with, <code>null</code>
\r
223 * The output will be the same on both Unix and Windows except
\r
224 * for the separator character.
\r
228 * /foo/../bar --> /bar
\r
229 * /foo/../bar/ --> /bar
\r
230 * /foo/../bar/../baz --> /baz
\r
231 * //foo//./bar --> /foo/bar
\r
234 * foo/bar/.. --> foo
\r
235 * foo/../../bar --> null
\r
236 * foo/../bar --> bar
\r
237 * //server/foo/../bar --> //server/bar
\r
238 * //server/../bar --> null
\r
239 * C:\foo\..\bar --> C:\bar
\r
240 * C:\..\bar --> null
\r
241 * ~/foo/../bar/ --> ~/bar
\r
242 * ~/../bar --> null
\r
244 * (Note the file separator returned will be correct for Windows/Unix)
\r
246 * @param filename the filename to normalize, null returns null
\r
247 * @return the normalized filename, or null if invalid
\r
249 public static String normalizeNoEndSeparator(String filename) {
\r
250 return doNormalize(filename, false);
\r
254 * Internal method to perform the normalization.
\r
256 * @param filename the filename
\r
257 * @param keepSeparator true to keep the final separator
\r
258 * @return the normalized filename
\r
260 private static String doNormalize(String filename, boolean keepSeparator) {
\r
261 if (filename == null) {
\r
264 int size = filename.length();
\r
268 int prefix = getPrefixLength(filename);
\r
273 char[] array = new char[size + 2]; // +1 for possible extra slash, +2 for arraycopy
\r
274 filename.getChars(0, filename.length(), array, 0);
\r
276 // fix separators throughout
\r
277 for (int i = 0; i < array.length; i++) {
\r
278 if (array[i] == OTHER_SEPARATOR) {
\r
279 array[i] = SYSTEM_SEPARATOR;
\r
283 // add extra separator on the end to simplify code below
\r
284 boolean lastIsDirectory = true;
\r
285 if (array[size - 1] != SYSTEM_SEPARATOR) {
\r
286 array[size++] = SYSTEM_SEPARATOR;
\r
287 lastIsDirectory = false;
\r
290 // adjoining slashes
\r
291 for (int i = prefix + 1; i < size; i++) {
\r
292 if (array[i] == SYSTEM_SEPARATOR && array[i - 1] == SYSTEM_SEPARATOR) {
\r
293 System.arraycopy(array, i, array, i - 1, size - i);
\r
300 for (int i = prefix + 1; i < size; i++) {
\r
301 if (array[i] == SYSTEM_SEPARATOR && array[i - 1] == '.' &&
\r
302 (i == prefix + 1 || array[i - 2] == SYSTEM_SEPARATOR)) {
\r
303 if (i == size - 1) {
\r
304 lastIsDirectory = true;
\r
306 System.arraycopy(array, i + 1, array, i - 1, size - i);
\r
312 // double dot slash
\r
314 for (int i = prefix + 2; i < size; i++) {
\r
315 if (array[i] == SYSTEM_SEPARATOR && array[i - 1] == '.' && array[i - 2] == '.' &&
\r
316 (i == prefix + 2 || array[i - 3] == SYSTEM_SEPARATOR)) {
\r
317 if (i == prefix + 2) {
\r
320 if (i == size - 1) {
\r
321 lastIsDirectory = true;
\r
324 for (j = i - 4 ; j >= prefix; j--) {
\r
325 if (array[j] == SYSTEM_SEPARATOR) {
\r
326 // remove b/../ from a/b/../c
\r
327 System.arraycopy(array, i + 1, array, j + 1, size - i);
\r
333 // remove a/../ from a/../c
\r
334 System.arraycopy(array, i + 1, array, prefix, size - i);
\r
335 size -= (i + 1 - prefix);
\r
340 if (size <= 0) { // should never be less than 0
\r
343 if (size <= prefix) { // should never be less than prefix
\r
344 return new String(array, 0, size);
\r
346 if (lastIsDirectory && keepSeparator) {
\r
347 return new String(array, 0, size); // keep trailing separator
\r
349 return new String(array, 0, size - 1); // lose trailing separator
\r
352 //-----------------------------------------------------------------------
\r
354 * Concatenates a filename to a base path using normal command line style rules.
\r
356 * The effect is equivalent to resultant directory after changing
\r
357 * directory to the first argument, followed by changing directory to
\r
358 * the second argument.
\r
360 * The first argument is the base path, the second is the path to concatenate.
\r
361 * The returned path is always normalized via {@link #normalize(String)},
\r
362 * thus <code>..</code> is handled.
\r
364 * If <code>pathToAdd</code> is absolute (has an absolute prefix), then
\r
365 * it will be normalized and returned.
\r
366 * Otherwise, the paths will be joined, normalized and returned.
\r
368 * The output will be the same on both Unix and Windows except
\r
369 * for the separator character.
\r
371 * /foo/ + bar --> /foo/bar
\r
372 * /foo + bar --> /foo/bar
\r
373 * /foo + /bar --> /bar
\r
374 * /foo + C:/bar --> C:/bar
\r
375 * /foo + C:bar --> C:bar (*)
\r
376 * /foo/a/ + ../bar --> foo/bar
\r
377 * /foo/ + ../../bar --> null
\r
378 * /foo/ + /bar --> /bar
\r
379 * /foo/.. + /bar --> /bar
\r
380 * /foo + bar/c.txt --> /foo/bar/c.txt
\r
381 * /foo/c.txt + bar --> /foo/c.txt/bar (!)
\r
383 * (*) Note that the Windows relative drive prefix is unreliable when
\r
384 * used with this method.
\r
385 * (!) Note that the first parameter must be a path. If it ends with a name, then
\r
386 * the name will be built into the concatenated path. If this might be a problem,
\r
387 * use {@link #getFullPath(String)} on the base path argument.
\r
389 * @param basePath the base path to attach to, always treated as a path
\r
390 * @param fullFilenameToAdd the filename (or path) to attach to the base
\r
391 * @return the concatenated path, or null if invalid
\r
393 public static String concat(String basePath, String fullFilenameToAdd) {
\r
394 int prefix = getPrefixLength(fullFilenameToAdd);
\r
399 return normalize(fullFilenameToAdd);
\r
401 if (basePath == null) {
\r
404 int len = basePath.length();
\r
406 return normalize(fullFilenameToAdd);
\r
408 char ch = basePath.charAt(len - 1);
\r
409 if (isSeparator(ch)) {
\r
410 return normalize(basePath + fullFilenameToAdd);
\r
412 return normalize(basePath + '/' + fullFilenameToAdd);
\r
416 //-----------------------------------------------------------------------
\r
418 * Converts all separators to the Unix separator of forward slash.
\r
420 * @param path the path to be changed, null ignored
\r
421 * @return the updated path
\r
423 public static String separatorsToUnix(String path) {
\r
424 if (path == null || path.indexOf(WINDOWS_SEPARATOR) == -1) {
\r
427 return path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR);
\r
431 * Converts all separators to the Windows separator of backslash.
\r
433 * @param path the path to be changed, null ignored
\r
434 * @return the updated path
\r
436 public static String separatorsToWindows(String path) {
\r
437 if (path == null || path.indexOf(UNIX_SEPARATOR) == -1) {
\r
440 return path.replace(UNIX_SEPARATOR, WINDOWS_SEPARATOR);
\r
444 * Converts all separators to the system separator.
\r
446 * @param path the path to be changed, null ignored
\r
447 * @return the updated path
\r
449 public static String separatorsToSystem(String path) {
\r
450 if (path == null) {
\r
453 if (isSystemWindows()) {
\r
454 return separatorsToWindows(path);
\r
456 return separatorsToUnix(path);
\r
460 //-----------------------------------------------------------------------
\r
462 * Returns the length of the filename prefix, such as <code>C:/</code> or <code>~/</code>.
\r
464 * This method will handle a file in either Unix or Windows format.
\r
466 * The prefix length includes the first slash in the full filename
\r
467 * if applicable. Thus, it is possible that the length returned is greater
\r
468 * than the length of the input string.
\r
471 * a\b\c.txt --> "" --> relative
\r
472 * \a\b\c.txt --> "\" --> current drive absolute
\r
473 * C:a\b\c.txt --> "C:" --> drive relative
\r
474 * C:\a\b\c.txt --> "C:\" --> absolute
\r
475 * \\server\a\b\c.txt --> "\\server\" --> UNC
\r
478 * a/b/c.txt --> "" --> relative
\r
479 * /a/b/c.txt --> "/" --> absolute
\r
480 * ~/a/b/c.txt --> "~/" --> current user
\r
481 * ~ --> "~/" --> current user (slash added)
\r
482 * ~user/a/b/c.txt --> "~user/" --> named user
\r
483 * ~user --> "~user/" --> named user (slash added)
\r
486 * The output will be the same irrespective of the machine that the code is running on.
\r
487 * ie. both Unix and Windows prefixes are matched regardless.
\r
489 * @param filename the filename to find the prefix in, null returns -1
\r
490 * @return the length of the prefix, -1 if invalid or null
\r
492 public static int getPrefixLength(String filename) {
\r
493 if (filename == null) {
\r
496 int len = filename.length();
\r
500 char ch0 = filename.charAt(0);
\r
506 return 2; // return a length greater than the input
\r
508 return (isSeparator(ch0) ? 1 : 0);
\r
511 int posUnix = filename.indexOf(UNIX_SEPARATOR, 1);
\r
512 int posWin = filename.indexOf(WINDOWS_SEPARATOR, 1);
\r
513 if (posUnix == -1 && posWin == -1) {
\r
514 return len + 1; // return a length greater than the input
\r
516 posUnix = (posUnix == -1 ? posWin : posUnix);
\r
517 posWin = (posWin == -1 ? posUnix : posWin);
\r
518 return Math.min(posUnix, posWin) + 1;
\r
520 char ch1 = filename.charAt(1);
\r
522 ch0 = Character.toUpperCase(ch0);
\r
523 if (ch0 >= 'A' && ch0 <= 'Z') {
\r
524 if (len == 2 || isSeparator(filename.charAt(2)) == false) {
\r
531 } else if (isSeparator(ch0) && isSeparator(ch1)) {
\r
532 int posUnix = filename.indexOf(UNIX_SEPARATOR, 2);
\r
533 int posWin = filename.indexOf(WINDOWS_SEPARATOR, 2);
\r
534 if ((posUnix == -1 && posWin == -1) || posUnix == 2 || posWin == 2) {
\r
537 posUnix = (posUnix == -1 ? posWin : posUnix);
\r
538 posWin = (posWin == -1 ? posUnix : posWin);
\r
539 return Math.min(posUnix, posWin) + 1;
\r
541 return (isSeparator(ch0) ? 1 : 0);
\r
547 * Returns the index of the last directory separator character.
\r
549 * This method will handle a file in either Unix or Windows format.
\r
550 * The position of the last forward or backslash is returned.
\r
552 * The output will be the same irrespective of the machine that the code is running on.
\r
554 * @param filename the filename to find the last path separator in, null returns -1
\r
555 * @return the index of the last separator character, or -1 if there
\r
556 * is no such character
\r
558 public static int indexOfLastSeparator(String filename) {
\r
559 if (filename == null) {
\r
562 int lastUnixPos = filename.lastIndexOf(UNIX_SEPARATOR);
\r
563 int lastWindowsPos = filename.lastIndexOf(WINDOWS_SEPARATOR);
\r
564 return Math.max(lastUnixPos, lastWindowsPos);
\r
568 * Returns the index of the last extension separator character, which is a dot.
\r
570 * This method also checks that there is no directory separator after the last dot.
\r
571 * To do this it uses {@link #indexOfLastSeparator(String)} which will
\r
572 * handle a file in either Unix or Windows format.
\r
574 * The output will be the same irrespective of the machine that the code is running on.
\r
576 * @param filename the filename to find the last path separator in, null returns -1
\r
577 * @return the index of the last separator character, or -1 if there
\r
578 * is no such character
\r
580 public static int indexOfExtension(String filename) {
\r
581 if (filename == null) {
\r
584 int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR);
\r
585 int lastSeparator = indexOfLastSeparator(filename);
\r
586 return (lastSeparator > extensionPos ? -1 : extensionPos);
\r
589 //-----------------------------------------------------------------------
\r
591 * Gets the prefix from a full filename, such as <code>C:/</code>
\r
592 * or <code>~/</code>.
\r
594 * This method will handle a file in either Unix or Windows format.
\r
595 * The prefix includes the first slash in the full filename where applicable.
\r
598 * a\b\c.txt --> "" --> relative
\r
599 * \a\b\c.txt --> "\" --> current drive absolute
\r
600 * C:a\b\c.txt --> "C:" --> drive relative
\r
601 * C:\a\b\c.txt --> "C:\" --> absolute
\r
602 * \\server\a\b\c.txt --> "\\server\" --> UNC
\r
605 * a/b/c.txt --> "" --> relative
\r
606 * /a/b/c.txt --> "/" --> absolute
\r
607 * ~/a/b/c.txt --> "~/" --> current user
\r
608 * ~ --> "~/" --> current user (slash added)
\r
609 * ~user/a/b/c.txt --> "~user/" --> named user
\r
610 * ~user --> "~user/" --> named user (slash added)
\r
613 * The output will be the same irrespective of the machine that the code is running on.
\r
614 * ie. both Unix and Windows prefixes are matched regardless.
\r
616 * @param filename the filename to query, null returns null
\r
617 * @return the prefix of the file, null if invalid
\r
619 public static String getPrefix(String filename) {
\r
620 if (filename == null) {
\r
623 int len = getPrefixLength(filename);
\r
627 if (len > filename.length()) {
\r
628 return filename + UNIX_SEPARATOR; // we know this only happens for unix
\r
630 return filename.substring(0, len);
\r
634 * Gets the path from a full filename, which excludes the prefix.
\r
636 * This method will handle a file in either Unix or Windows format.
\r
637 * The method is entirely text based, and returns the text before and
\r
638 * including the last forward or backslash.
\r
640 * C:\a\b\c.txt --> a\b\
\r
641 * ~/a/b/c.txt --> a/b/
\r
644 * a/b/c/ --> a/b/c/
\r
647 * The output will be the same irrespective of the machine that the code is running on.
\r
649 * This method drops the prefix from the result.
\r
650 * See {@link #getFullPath(String)} for the method that retains the prefix.
\r
652 * @param filename the filename to query, null returns null
\r
653 * @return the path of the file, an empty string if none exists, null if invalid
\r
655 public static String getPath(String filename) {
\r
656 return doGetPath(filename, 1);
\r
660 * Gets the path from a full filename, which excludes the prefix, and
\r
661 * also excluding the final directory separator.
\r
663 * This method will handle a file in either Unix or Windows format.
\r
664 * The method is entirely text based, and returns the text before the
\r
665 * last forward or backslash.
\r
667 * C:\a\b\c.txt --> a\b
\r
668 * ~/a/b/c.txt --> a/b
\r
674 * The output will be the same irrespective of the machine that the code is running on.
\r
676 * This method drops the prefix from the result.
\r
677 * See {@link #getFullPathNoEndSeparator(String)} for the method that retains the prefix.
\r
679 * @param filename the filename to query, null returns null
\r
680 * @return the path of the file, an empty string if none exists, null if invalid
\r
682 public static String getPathNoEndSeparator(String filename) {
\r
683 return doGetPath(filename, 0);
\r
687 * Does the work of getting the path.
\r
689 * @param filename the filename
\r
690 * @param separatorAdd 0 to omit the end separator, 1 to return it
\r
693 private static String doGetPath(String filename, int separatorAdd) {
\r
694 if (filename == null) {
\r
697 int prefix = getPrefixLength(filename);
\r
701 int index = indexOfLastSeparator(filename);
\r
702 if (prefix >= filename.length() || index < 0) {
\r
705 return filename.substring(prefix, index + separatorAdd);
\r
709 * Gets the full path from a full filename, which is the prefix + path.
\r
711 * This method will handle a file in either Unix or Windows format.
\r
712 * The method is entirely text based, and returns the text before and
\r
713 * including the last forward or backslash.
\r
715 * C:\a\b\c.txt --> C:\a\b\
\r
716 * ~/a/b/c.txt --> ~/a/b/
\r
719 * a/b/c/ --> a/b/c/
\r
725 * ~user/ --> ~user/
\r
728 * The output will be the same irrespective of the machine that the code is running on.
\r
730 * @param filename the filename to query, null returns null
\r
731 * @return the path of the file, an empty string if none exists, null if invalid
\r
733 public static String getFullPath(String filename) {
\r
734 return doGetFullPath(filename, true);
\r
738 * Gets the full path from a full filename, which is the prefix + path,
\r
739 * and also excluding the final directory separator.
\r
741 * This method will handle a file in either Unix or Windows format.
\r
742 * The method is entirely text based, and returns the text before the
\r
743 * last forward or backslash.
\r
745 * C:\a\b\c.txt --> C:\a\b
\r
746 * ~/a/b/c.txt --> ~/a/b
\r
758 * The output will be the same irrespective of the machine that the code is running on.
\r
760 * @param filename the filename to query, null returns null
\r
761 * @return the path of the file, an empty string if none exists, null if invalid
\r
763 public static String getFullPathNoEndSeparator(String filename) {
\r
764 return doGetFullPath(filename, false);
\r
768 * Does the work of getting the path.
\r
770 * @param filename the filename
\r
771 * @param includeSeparator true to include the end separator
\r
774 private static String doGetFullPath(String filename, boolean includeSeparator) {
\r
775 if (filename == null) {
\r
778 int prefix = getPrefixLength(filename);
\r
782 if (prefix >= filename.length()) {
\r
783 if (includeSeparator) {
\r
784 return getPrefix(filename); // add end slash if necessary
\r
789 int index = indexOfLastSeparator(filename);
\r
791 return filename.substring(0, prefix);
\r
793 int end = index + (includeSeparator ? 1 : 0);
\r
794 return filename.substring(0, end);
\r
798 * Gets the name minus the path from a full filename.
\r
800 * This method will handle a file in either Unix or Windows format.
\r
801 * The text after the last forward or backslash is returned.
\r
803 * a/b/c.txt --> c.txt
\r
809 * The output will be the same irrespective of the machine that the code is running on.
\r
811 * @param filename the filename to query, null returns null
\r
812 * @return the name of the file without the path, or an empty string if none exists
\r
814 public static String getName(String filename) {
\r
815 if (filename == null) {
\r
818 int index = indexOfLastSeparator(filename);
\r
819 return filename.substring(index + 1);
\r
823 * Gets the base name, minus the full path and extension, from a full filename.
\r
825 * This method will handle a file in either Unix or Windows format.
\r
826 * The text after the last forward or backslash and before the last dot is returned.
\r
834 * The output will be the same irrespective of the machine that the code is running on.
\r
836 * @param filename the filename to query, null returns null
\r
837 * @return the name of the file without the path, or an empty string if none exists
\r
839 public static String getBaseName(String filename) {
\r
840 return removeExtension(getName(filename));
\r
844 * Gets the extension of a filename.
\r
846 * This method returns the textual part of the filename after the last dot.
\r
847 * There must be no directory separator after the dot.
\r
849 * foo.txt --> "txt"
\r
850 * a/b/c.jpg --> "jpg"
\r
855 * The output will be the same irrespective of the machine that the code is running on.
\r
857 * @param filename the filename to retrieve the extension of.
\r
858 * @return the extension of the file or an empty string if none exists.
\r
860 public static String getExtension(String filename) {
\r
861 if (filename == null) {
\r
864 int index = indexOfExtension(filename);
\r
868 return filename.substring(index + 1);
\r
872 //-----------------------------------------------------------------------
\r
874 * Removes the extension from a filename.
\r
876 * This method returns the textual part of the filename before the last dot.
\r
877 * There must be no directory separator after the dot.
\r
880 * a\b\c.jpg --> a\b\c
\r
885 * The output will be the same irrespective of the machine that the code is running on.
\r
887 * @param filename the filename to query, null returns null
\r
888 * @return the filename minus the extension
\r
890 public static String removeExtension(String filename) {
\r
891 if (filename == null) {
\r
894 int index = indexOfExtension(filename);
\r
898 return filename.substring(0, index);
\r
902 //-----------------------------------------------------------------------
\r
904 * Checks whether two filenames are equal exactly.
\r
906 * No processing is performed on the filenames other than comparison,
\r
907 * thus this is merely a null-safe case-sensitive equals.
\r
909 * @param filename1 the first filename to query, may be null
\r
910 * @param filename2 the second filename to query, may be null
\r
911 * @return true if the filenames are equal, null equals null
\r
912 * @see IOCase#SENSITIVE
\r
914 public static boolean equals(String filename1, String filename2) {
\r
915 return equals(filename1, filename2, false, IOCase.SENSITIVE);
\r
919 * Checks whether two filenames are equal using the case rules of the system.
\r
921 * No processing is performed on the filenames other than comparison.
\r
922 * The check is case-sensitive on Unix and case-insensitive on Windows.
\r
924 * @param filename1 the first filename to query, may be null
\r
925 * @param filename2 the second filename to query, may be null
\r
926 * @return true if the filenames are equal, null equals null
\r
927 * @see IOCase#SYSTEM
\r
929 public static boolean equalsOnSystem(String filename1, String filename2) {
\r
930 return equals(filename1, filename2, false, IOCase.SYSTEM);
\r
933 //-----------------------------------------------------------------------
\r
935 * Checks whether two filenames are equal after both have been normalized.
\r
937 * Both filenames are first passed to {@link #normalize(String)}.
\r
938 * The check is then performed in a case-sensitive manner.
\r
940 * @param filename1 the first filename to query, may be null
\r
941 * @param filename2 the second filename to query, may be null
\r
942 * @return true if the filenames are equal, null equals null
\r
943 * @see IOCase#SENSITIVE
\r
945 public static boolean equalsNormalized(String filename1, String filename2) {
\r
946 return equals(filename1, filename2, true, IOCase.SENSITIVE);
\r
950 * Checks whether two filenames are equal after both have been normalized
\r
951 * and using the case rules of the system.
\r
953 * Both filenames are first passed to {@link #normalize(String)}.
\r
954 * The check is then performed case-sensitive on Unix and
\r
955 * case-insensitive on Windows.
\r
957 * @param filename1 the first filename to query, may be null
\r
958 * @param filename2 the second filename to query, may be null
\r
959 * @return true if the filenames are equal, null equals null
\r
960 * @see IOCase#SYSTEM
\r
962 public static boolean equalsNormalizedOnSystem(String filename1, String filename2) {
\r
963 return equals(filename1, filename2, true, IOCase.SYSTEM);
\r
967 * Checks whether two filenames are equal, optionally normalizing and providing
\r
968 * control over the case-sensitivity.
\r
970 * @param filename1 the first filename to query, may be null
\r
971 * @param filename2 the second filename to query, may be null
\r
972 * @param normalized whether to normalize the filenames
\r
973 * @param caseSensitivity what case sensitivity rule to use, null means case-sensitive
\r
974 * @return true if the filenames are equal, null equals null
\r
975 * @since Commons IO 1.3
\r
977 public static boolean equals(
\r
978 String filename1, String filename2,
\r
979 boolean normalized, IOCase caseSensitivity) {
\r
981 if (filename1 == null || filename2 == null) {
\r
982 return filename1 == filename2;
\r
985 filename1 = normalize(filename1);
\r
986 filename2 = normalize(filename2);
\r
987 if (filename1 == null || filename2 == null) {
\r
988 throw new NullPointerException(
\r
989 "Error normalizing one or both of the file names");
\r
992 if (caseSensitivity == null) {
\r
993 caseSensitivity = IOCase.SENSITIVE;
\r
995 return caseSensitivity.checkEquals(filename1, filename2);
\r
998 //-----------------------------------------------------------------------
\r
1000 * Checks whether the extension of the filename is that specified.
\r
1002 * This method obtains the extension as the textual part of the filename
\r
1003 * after the last dot. There must be no directory separator after the dot.
\r
1004 * The extension check is case-sensitive on all platforms.
\r
1006 * @param filename the filename to query, null returns false
\r
1007 * @param extension the extension to check for, null or empty checks for no extension
\r
1008 * @return true if the filename has the specified extension
\r
1010 public static boolean isExtension(String filename, String extension) {
\r
1011 if (filename == null) {
\r
1014 if (extension == null || extension.length() == 0) {
\r
1015 return (indexOfExtension(filename) == -1);
\r
1017 String fileExt = getExtension(filename);
\r
1018 return fileExt.equals(extension);
\r
1022 * Checks whether the extension of the filename is one of those specified.
\r
1024 * This method obtains the extension as the textual part of the filename
\r
1025 * after the last dot. There must be no directory separator after the dot.
\r
1026 * The extension check is case-sensitive on all platforms.
\r
1028 * @param filename the filename to query, null returns false
\r
1029 * @param extensions the extensions to check for, null checks for no extension
\r
1030 * @return true if the filename is one of the extensions
\r
1032 public static boolean isExtension(String filename, String[] extensions) {
\r
1033 if (filename == null) {
\r
1036 if (extensions == null || extensions.length == 0) {
\r
1037 return (indexOfExtension(filename) == -1);
\r
1039 String fileExt = getExtension(filename);
\r
1040 for (int i = 0; i < extensions.length; i++) {
\r
1041 if (fileExt.equals(extensions[i])) {
\r
1049 * Checks whether the extension of the filename is one of those specified.
\r
1051 * This method obtains the extension as the textual part of the filename
\r
1052 * after the last dot. There must be no directory separator after the dot.
\r
1053 * The extension check is case-sensitive on all platforms.
\r
1055 * @param filename the filename to query, null returns false
\r
1056 * @param extensions the extensions to check for, null checks for no extension
\r
1057 * @return true if the filename is one of the extensions
\r
1059 public static boolean isExtension(String filename, Collection extensions) {
\r
1060 if (filename == null) {
\r
1063 if (extensions == null || extensions.isEmpty()) {
\r
1064 return (indexOfExtension(filename) == -1);
\r
1066 String fileExt = getExtension(filename);
\r
1067 for (Iterator it = extensions.iterator(); it.hasNext();) {
\r
1068 if (fileExt.equals(it.next())) {
\r
1075 //-----------------------------------------------------------------------
\r
1077 * Checks a filename to see if it matches the specified wildcard matcher,
\r
1078 * always testing case-sensitive.
\r
1080 * The wildcard matcher uses the characters '?' and '*' to represent a
\r
1081 * single or multiple wildcard characters.
\r
1082 * This is the same as often found on Dos/Unix command lines.
\r
1083 * The check is case-sensitive always.
\r
1085 * wildcardMatch("c.txt", "*.txt") --> true
\r
1086 * wildcardMatch("c.txt", "*.jpg") --> false
\r
1087 * wildcardMatch("a/b/c.txt", "a/b/*") --> true
\r
1088 * wildcardMatch("c.txt", "*.???") --> true
\r
1089 * wildcardMatch("c.txt", "*.????") --> false
\r
1092 * @param filename the filename to match on
\r
1093 * @param wildcardMatcher the wildcard string to match against
\r
1094 * @return true if the filename matches the wilcard string
\r
1095 * @see IOCase#SENSITIVE
\r
1097 public static boolean wildcardMatch(String filename, String wildcardMatcher) {
\r
1098 return wildcardMatch(filename, wildcardMatcher, IOCase.SENSITIVE);
\r
1102 * Checks a filename to see if it matches the specified wildcard matcher
\r
1103 * using the case rules of the system.
\r
1105 * The wildcard matcher uses the characters '?' and '*' to represent a
\r
1106 * single or multiple wildcard characters.
\r
1107 * This is the same as often found on Dos/Unix command lines.
\r
1108 * The check is case-sensitive on Unix and case-insensitive on Windows.
\r
1110 * wildcardMatch("c.txt", "*.txt") --> true
\r
1111 * wildcardMatch("c.txt", "*.jpg") --> false
\r
1112 * wildcardMatch("a/b/c.txt", "a/b/*") --> true
\r
1113 * wildcardMatch("c.txt", "*.???") --> true
\r
1114 * wildcardMatch("c.txt", "*.????") --> false
\r
1117 * @param filename the filename to match on
\r
1118 * @param wildcardMatcher the wildcard string to match against
\r
1119 * @return true if the filename matches the wilcard string
\r
1120 * @see IOCase#SYSTEM
\r
1122 public static boolean wildcardMatchOnSystem(String filename, String wildcardMatcher) {
\r
1123 return wildcardMatch(filename, wildcardMatcher, IOCase.SYSTEM);
\r
1127 * Checks a filename to see if it matches the specified wildcard matcher
\r
1128 * allowing control over case-sensitivity.
\r
1130 * The wildcard matcher uses the characters '?' and '*' to represent a
\r
1131 * single or multiple wildcard characters.
\r
1133 * @param filename the filename to match on
\r
1134 * @param wildcardMatcher the wildcard string to match against
\r
1135 * @param caseSensitivity what case sensitivity rule to use, null means case-sensitive
\r
1136 * @return true if the filename matches the wilcard string
\r
1137 * @since Commons IO 1.3
\r
1139 public static boolean wildcardMatch(String filename, String wildcardMatcher, IOCase caseSensitivity) {
\r
1140 if (filename == null && wildcardMatcher == null) {
\r
1143 if (filename == null || wildcardMatcher == null) {
\r
1146 if (caseSensitivity == null) {
\r
1147 caseSensitivity = IOCase.SENSITIVE;
\r
1149 filename = caseSensitivity.convertCase(filename);
\r
1150 wildcardMatcher = caseSensitivity.convertCase(wildcardMatcher);
\r
1151 String[] wcs = splitOnTokens(wildcardMatcher);
\r
1152 boolean anyChars = false;
\r
1155 Stack backtrack = new Stack();
\r
1157 // loop around a backtrack stack, to handle complex * matching
\r
1159 if (backtrack.size() > 0) {
\r
1160 int[] array = (int[]) backtrack.pop();
\r
1161 wcsIdx = array[0];
\r
1162 textIdx = array[1];
\r
1166 // loop whilst tokens and text left to process
\r
1167 while (wcsIdx < wcs.length) {
\r
1169 if (wcs[wcsIdx].equals("?")) {
\r
1170 // ? so move to next text char
\r
1174 } else if (wcs[wcsIdx].equals("*")) {
\r
1175 // set any chars status
\r
1177 if (wcsIdx == wcs.length - 1) {
\r
1178 textIdx = filename.length();
\r
1182 // matching text token
\r
1184 // any chars then try to locate text token
\r
1185 textIdx = filename.indexOf(wcs[wcsIdx], textIdx);
\r
1186 if (textIdx == -1) {
\r
1187 // token not found
\r
1190 int repeat = filename.indexOf(wcs[wcsIdx], textIdx + 1);
\r
1191 if (repeat >= 0) {
\r
1192 backtrack.push(new int[] {wcsIdx, repeat});
\r
1195 // matching from current position
\r
1196 if (!filename.startsWith(wcs[wcsIdx], textIdx)) {
\r
1197 // couldnt match token
\r
1202 // matched text token, move text index to end of matched token
\r
1203 textIdx += wcs[wcsIdx].length();
\r
1211 if (wcsIdx == wcs.length && textIdx == filename.length()) {
\r
1215 } while (backtrack.size() > 0);
\r
1221 * Splits a string into a number of tokens.
\r
1223 * @param text the text to split
\r
1224 * @return the tokens, never null
\r
1226 static String[] splitOnTokens(String text) {
\r
1227 // used by wildcardMatch
\r
1228 // package level so a unit test may run on this
\r
1230 if (text.indexOf("?") == -1 && text.indexOf("*") == -1) {
\r
1231 return new String[] { text };
\r
1234 char[] array = text.toCharArray();
\r
1235 ArrayList list = new ArrayList();
\r
1236 StringBuffer buffer = new StringBuffer();
\r
1237 for (int i = 0; i < array.length; i++) {
\r
1238 if (array[i] == '?' || array[i] == '*') {
\r
1239 if (buffer.length() != 0) {
\r
1240 list.add(buffer.toString());
\r
1241 buffer.setLength(0);
\r
1243 if (array[i] == '?') {
\r
1245 } else if (list.size() == 0 ||
\r
1246 (i > 0 && list.get(list.size() - 1).equals("*") == false)) {
\r
1250 buffer.append(array[i]);
\r
1253 if (buffer.length() != 0) {
\r
1254 list.add(buffer.toString());
\r
1257 return (String[]) list.toArray( new String[ list.size() ] );
\r