001: // Copyright (c) 2003-2007, Jodd Team (jodd.sf.net). All Rights Reserved.
002:
003: package jodd.io;
004:
005: import java.io.File;
006:
007: /**
008: * General filename and filepath manipulation utilities.
009: * <p>
010: * When dealing with filenames you can hit problems when moving from a Windows
011: * based development machine to a Unix based production machine.
012: * This class aims to help avoid those problems.
013: * <p>
014: * <b>NOTE</b>: You may be able to avoid using this class entirely simply by
015: * using JDK {@link java.io.File File} objects and the two argument constructor
016: * {@link java.io.File#File(java.io.File, java.lang.String) File(File,String)}.
017: * <p>
018: * Most methods on this class are designed to work the same on both Unix and Windows.
019: * Those that don't include 'System', 'Unix' or 'Windows' in their name.
020: * <p>
021: * Most methods recognise both separators (forward and back), and both
022: * sets of prefixes. See the javadoc of each method for details.
023: * <p>
024: * This class defines six components within a filename
025: * (example C:\dev\project\file.txt):
026: * <ul>
027: * <li>the prefix - C:\</li>
028: * <li>the path - dev\project\</li>
029: * <li>the full path - C:\dev\project\</li>
030: * <li>the name - file.txt</li>
031: * <li>the base name - file</li>
032: * <li>the extension - txt</li>
033: * </ul>
034: * Note that this class works best if directory filenames end with a separator.
035: * If you omit the last separator, it is impossible to determine if the filename
036: * corresponds to a file or a directory. As a result, we have chosen to say
037: * it corresponds to a file.
038: * <p>
039: * This class only supports Unix and Windows style names.
040: * Prefixes are matched as follows:
041: * <pre>
042: * Windows:
043: * a\b\c.txt --> "" --> relative
044: * \a\b\c.txt --> "\" --> current drive absolute
045: * C:a\b\c.txt --> "C:" --> drive relative
046: * C:\a\b\c.txt --> "C:\" --> absolute
047: * \\server\a\b\c.txt --> "\\server\" --> UNC
048: *
049: * Unix:
050: * a/b/c.txt --> "" --> relative
051: * /a/b/c.txt --> "/" --> absolute
052: * ~/a/b/c.txt --> "~/" --> current user
053: * ~ --> "~/" --> current user (slash added)
054: * ~user/a/b/c.txt --> "~user/" --> named user
055: * ~user --> "~user/" --> named user (slash added)
056: * </pre>
057: * Both prefix styles are matched always, irrespective of the machine that you are
058: * currently running on.
059: */
060: public class FileNameUtil {
061:
062: /**
063: * The extension separator character.
064: */
065: private static final char EXTENSION_SEPARATOR = '.';
066:
067: /**
068: * The Unix separator character.
069: */
070: private static final char UNIX_SEPARATOR = '/';
071:
072: /**
073: * The Windows separator character.
074: */
075: private static final char WINDOWS_SEPARATOR = '\\';
076:
077: /**
078: * The system separator character.
079: */
080: private static final char SYSTEM_SEPARATOR = File.separatorChar;
081:
082: /**
083: * The separator character that is the opposite of the system separator.
084: */
085: private static final char OTHER_SEPARATOR;
086: static {
087: if (SYSTEM_SEPARATOR == WINDOWS_SEPARATOR) {
088: OTHER_SEPARATOR = UNIX_SEPARATOR;
089: } else {
090: OTHER_SEPARATOR = WINDOWS_SEPARATOR;
091: }
092: }
093:
094: /**
095: * Checks if the character is a separator.
096: */
097: private static boolean isSeparator(char ch) {
098: return (ch == UNIX_SEPARATOR) || (ch == WINDOWS_SEPARATOR);
099: }
100:
101: // ---------------------------------------------------------------- normalization
102: /**
103: * Normalizes a path, removing double and single dot path steps.
104: * <p>
105: * This method normalizes a path to a standard format.
106: * The input may contain separators in either Unix or Windows format.
107: * The output will contain separators in the format of the system.
108: * <p>
109: * A trailing slash will be retained.
110: * A double slash will be merged to a single slash (but UNC names are handled).
111: * A single dot path segment will be removed.
112: * A double dot will cause that path segment and the one before to be removed.
113: * If the double dot has no parent path segment to work with, <code>null</code>
114: * is returned.
115: * <p>
116: * The output will be the same on both Unix and Windows except
117: * for the separator character.
118: * <pre>
119: * /foo// --> /foo/
120: * /foo/./ --> /foo/
121: * /foo/../bar --> /bar
122: * /foo/../bar/ --> /bar/
123: * /foo/../bar/../baz --> /baz
124: * //foo//./bar --> /foo/bar
125: * /../ --> null
126: * ../foo --> null
127: * foo/bar/.. --> foo/
128: * foo/../../bar --> null
129: * foo/../bar --> bar
130: * //server/foo/../bar --> //server/bar
131: * //server/../bar --> null
132: * C:\foo\..\bar --> C:\bar
133: * C:\..\bar --> null
134: * ~/foo/../bar/ --> ~/bar/
135: * ~/../bar --> null
136: * </pre>
137: * (Note the file separator returned will be correct for Windows/Unix)
138: *
139: * @param filename the filename to normalize, null returns null
140: * @return the normalized filename, or null if invalid
141: */
142: public static String normalize(String filename) {
143: return doNormalize(filename, true);
144: }
145:
146: /**
147: * Normalizes a path, removing double and single dot path steps,
148: * and removing any final directory separator.
149: * <p>
150: * This method normalizes a path to a standard format.
151: * The input may contain separators in either Unix or Windows format.
152: * The output will contain separators in the format of the system.
153: * <p>
154: * A trailing slash will be removed.
155: * A double slash will be merged to a single slash (but UNC names are handled).
156: * A single dot path segment will be removed.
157: * A double dot will cause that path segment and the one before to be removed.
158: * If the double dot has no parent path segment to work with, <code>null</code>
159: * is returned.
160: * <p>
161: * The output will be the same on both Unix and Windows except
162: * for the separator character.
163: * <pre>
164: * /foo// --> /foo
165: * /foo/./ --> /foo
166: * /foo/../bar --> /bar
167: * /foo/../bar/ --> /bar
168: * /foo/../bar/../baz --> /baz
169: * //foo//./bar --> /foo/bar
170: * /../ --> null
171: * ../foo --> null
172: * foo/bar/.. --> foo
173: * foo/../../bar --> null
174: * foo/../bar --> bar
175: * //server/foo/../bar --> //server/bar
176: * //server/../bar --> null
177: * C:\foo\..\bar --> C:\bar
178: * C:\..\bar --> null
179: * ~/foo/../bar/ --> ~/bar
180: * ~/../bar --> null
181: * </pre>
182: * (Note the file separator returned will be correct for Windows/Unix)
183: *
184: * @param filename the filename to normalize, null returns null
185: * @return the normalized filename, or null if invalid
186: */
187: public static String normalizeNoEndSeparator(String filename) {
188: return doNormalize(filename, false);
189: }
190:
191: /**
192: * Internal method to perform the normalization.
193: *
194: * @param filename the filename
195: * @param keepSeparator true to keep the final separator
196: * @return the normalized filename
197: */
198: private static String doNormalize(String filename,
199: boolean keepSeparator) {
200: if (filename == null) {
201: return null;
202: }
203: int size = filename.length();
204: if (size == 0) {
205: return filename;
206: }
207: int prefix = getPrefixLength(filename);
208: if (prefix < 0) {
209: return null;
210: }
211:
212: char[] array = new char[size + 2]; // +1 for possible extra slash, +2 for arraycopy
213: filename.getChars(0, filename.length(), array, 0);
214:
215: // fix separators throughout
216: for (int i = 0; i < array.length; i++) {
217: if (array[i] == OTHER_SEPARATOR) {
218: array[i] = SYSTEM_SEPARATOR;
219: }
220: }
221:
222: // add extra separator on the end to simplify code below
223: boolean lastIsDirectory = true;
224: if (array[size - 1] != SYSTEM_SEPARATOR) {
225: array[size] = SYSTEM_SEPARATOR;
226: size++;
227: lastIsDirectory = false;
228: }
229:
230: // adjoining slashes
231: for (int i = prefix + 1; i < size; i++) {
232: if (array[i] == SYSTEM_SEPARATOR
233: && array[i - 1] == SYSTEM_SEPARATOR) {
234: System.arraycopy(array, i, array, i - 1, size - i);
235: size--;
236: i--;
237: }
238: }
239:
240: // dot slash
241: for (int i = prefix + 1; i < size; i++) {
242: if (array[i] == SYSTEM_SEPARATOR
243: && array[i - 1] == '.'
244: && (i == prefix + 1 || array[i - 2] == SYSTEM_SEPARATOR)) {
245: if (i == size - 1) {
246: lastIsDirectory = true;
247: }
248: System.arraycopy(array, i + 1, array, i - 1, size - i);
249: size -= 2;
250: i--;
251: }
252: }
253:
254: // double dot slash
255: outer: for (int i = prefix + 2; i < size; i++) {
256: if (array[i] == SYSTEM_SEPARATOR
257: && array[i - 1] == '.'
258: && array[i - 2] == '.'
259: && (i == prefix + 2 || array[i - 3] == SYSTEM_SEPARATOR)) {
260: if (i == prefix + 2) {
261: return null;
262: }
263: if (i == size - 1) {
264: lastIsDirectory = true;
265: }
266: int j;
267: for (j = i - 4; j >= prefix; j--) {
268: if (array[j] == SYSTEM_SEPARATOR) {
269: // remove b/../ from a/b/../c
270: System.arraycopy(array, i + 1, array, j + 1,
271: size - i);
272: size -= (i - j);
273: i = j + 1;
274: continue outer;
275: }
276: }
277: // remove a/../ from a/../c
278: System.arraycopy(array, i + 1, array, prefix, size - i);
279: size -= (i + 1 - prefix);
280: i = prefix + 1;
281: }
282: }
283:
284: if (size <= 0) { // should never be less than 0
285: return "";
286: }
287: if (size <= prefix) { // should never be less than prefix
288: return new String(array, 0, size);
289: }
290: if (lastIsDirectory && keepSeparator) {
291: return new String(array, 0, size); // keep trailing separator
292: }
293: return new String(array, 0, size - 1); // lose trailing separator
294: }
295:
296: //-----------------------------------------------------------------------
297: /**
298: * Concatenates a filename to a base path using normal command line style rules.
299: * <p>
300: * The effect is equivalent to resultant directory after changing
301: * directory to the first argument, followed by changing directory to
302: * the second argument.
303: * <p>
304: * The first argument is the base path, the second is the path to concatenate.
305: * The returned path is always normalized via {@link #normalize(String)},
306: * thus <code>..</code> is handled.
307: * <p>
308: * If <code>pathToAdd</code> is absolute (has an absolute prefix), then
309: * it will be normalized and returned.
310: * Otherwise, the paths will be joined, normalized and returned.
311: * <p>
312: * The output will be the same on both Unix and Windows except
313: * for the separator character.
314: * <pre>
315: * /foo/ + bar --> /foo/bar
316: * /foo + bar --> /foo/bar
317: * /foo + /bar --> /bar
318: * /foo + C:/bar --> C:/bar
319: * /foo + C:bar --> C:bar (*)
320: * /foo/a/ + ../bar --> foo/bar
321: * /foo/ + ../../bar --> null
322: * /foo/ + /bar --> /bar
323: * /foo/.. + /bar --> /bar
324: * /foo + bar/c.txt --> /foo/bar/c.txt
325: * /foo/c.txt + bar --> /foo/c.txt/bar (!)
326: * </pre>
327: * (*) Note that the Windows relative drive prefix is unreliable when
328: * used with this method.
329: * (!) Note that the first parameter must be a path. If it ends with a name, then
330: * the name will be built into the concatenated path. If this might be a problem,
331: * use {@link #getFullPath(String)} on the base path argument.
332: *
333: * @param basePath the base path to attach to, always treated as a path
334: * @param fullFilenameToAdd the filename (or path) to attach to the base
335: * @return the concatenated path, or null if invalid
336: */
337: public static String concat(String basePath,
338: String fullFilenameToAdd) {
339: int prefix = getPrefixLength(fullFilenameToAdd);
340: if (prefix < 0) {
341: return null;
342: }
343: if (prefix > 0) {
344: return normalize(fullFilenameToAdd);
345: }
346: if (basePath == null) {
347: return null;
348: }
349: int len = basePath.length();
350: if (len == 0) {
351: return normalize(fullFilenameToAdd);
352: }
353: char ch = basePath.charAt(len - 1);
354: if (isSeparator(ch)) {
355: return normalize(basePath + fullFilenameToAdd);
356: } else {
357: return normalize(basePath + '/' + fullFilenameToAdd);
358: }
359: }
360:
361: // ---------------------------------------------------------------- separator conversion
362:
363: /**
364: * Converts all separators to the Unix separator of forward slash.
365: *
366: * @param path the path to be changed, null ignored
367: * @return the updated path
368: */
369: public static String separatorsToUnix(String path) {
370: if (path == null || path.indexOf(WINDOWS_SEPARATOR) == -1) {
371: return path;
372: }
373: return path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR);
374: }
375:
376: /**
377: * Converts all separators to the Windows separator of backslash.
378: *
379: * @param path the path to be changed, null ignored
380: * @return the updated path
381: */
382: public static String separatorsToWindows(String path) {
383: if (path == null || path.indexOf(UNIX_SEPARATOR) == -1) {
384: return path;
385: }
386: return path.replace(UNIX_SEPARATOR, WINDOWS_SEPARATOR);
387: }
388:
389: /**
390: * Converts all separators to the system separator.
391: *
392: * @param path the path to be changed, null ignored
393: * @return the updated path
394: */
395: public static String separatorsToSystem(String path) {
396: if (path == null) {
397: return null;
398: }
399: if (SYSTEM_SEPARATOR == WINDOWS_SEPARATOR) {
400: return separatorsToWindows(path);
401: } else {
402: return separatorsToUnix(path);
403: }
404: }
405:
406: // ---------------------------------------------------------------- prefix
407: /**
408: * Returns the length of the filename prefix, such as <code>C:/</code> or <code>~/</code>.
409: * <p>
410: * This method will handle a file in either Unix or Windows format.
411: * <p>
412: * The prefix length includes the first slash in the full filename
413: * if applicable. Thus, it is possible that the length returned is greater
414: * than the length of the input string.
415: * <pre>
416: * Windows:
417: * a\b\c.txt --> "" --> relative
418: * \a\b\c.txt --> "\" --> current drive absolute
419: * C:a\b\c.txt --> "C:" --> drive relative
420: * C:\a\b\c.txt --> "C:\" --> absolute
421: * \\server\a\b\c.txt --> "\\server\" --> UNC
422: *
423: * Unix:
424: * a/b/c.txt --> "" --> relative
425: * /a/b/c.txt --> "/" --> absolute
426: * ~/a/b/c.txt --> "~/" --> current user
427: * ~ --> "~/" --> current user (slash added)
428: * ~user/a/b/c.txt --> "~user/" --> named user
429: * ~user --> "~user/" --> named user (slash added)
430: * </pre>
431: * <p>
432: * The output will be the same irrespective of the machine that the code is running on.
433: * ie. both Unix and Windows prefixes are matched regardless.
434: *
435: * @param filename the filename to find the prefix in, null returns -1
436: * @return the length of the prefix, -1 if invalid or null
437: */
438: public static int getPrefixLength(String filename) {
439: if (filename == null) {
440: return -1;
441: }
442: int len = filename.length();
443: if (len == 0) {
444: return 0;
445: }
446: char ch0 = filename.charAt(0);
447: if (ch0 == ':') {
448: return -1;
449: }
450: if (len == 1) {
451: if (ch0 == '~') {
452: return 2; // return a length greater than the input
453: }
454: return (isSeparator(ch0) ? 1 : 0);
455: } else {
456: if (ch0 == '~') {
457: int posUnix = filename.indexOf(UNIX_SEPARATOR, 1);
458: int posWin = filename.indexOf(WINDOWS_SEPARATOR, 1);
459: if (posUnix == -1 && posWin == -1) {
460: return len + 1; // return a length greater than the input
461: }
462: posUnix = (posUnix == -1 ? posWin : posUnix);
463: posWin = (posWin == -1 ? posUnix : posWin);
464: return Math.min(posUnix, posWin) + 1;
465: }
466: char ch1 = filename.charAt(1);
467: if (ch1 == ':') {
468: ch0 = Character.toUpperCase(ch0);
469: if (ch0 >= 'A' && ch0 <= 'Z') {
470: if (len == 2
471: || isSeparator(filename.charAt(2)) == false) {
472: return 2;
473: }
474: return 3;
475: }
476: return -1;
477:
478: } else if (isSeparator(ch0) && isSeparator(ch1)) {
479: int posUnix = filename.indexOf(UNIX_SEPARATOR, 2);
480: int posWin = filename.indexOf(WINDOWS_SEPARATOR, 2);
481: if ((posUnix == -1 && posWin == -1) || posUnix == 2
482: || posWin == 2) {
483: return -1;
484: }
485: posUnix = (posUnix == -1 ? posWin : posUnix);
486: posWin = (posWin == -1 ? posUnix : posWin);
487: return Math.min(posUnix, posWin) + 1;
488: } else {
489: return (isSeparator(ch0) ? 1 : 0);
490: }
491: }
492: }
493:
494: /**
495: * Returns the index of the last directory separator character.
496: * <p>
497: * This method will handle a file in either Unix or Windows format.
498: * The position of the last forward or backslash is returned.
499: * <p>
500: * The output will be the same irrespective of the machine that the code is running on.
501: *
502: * @param filename the filename to find the last path separator in, null returns -1
503: * @return the index of the last separator character, or -1 if there is no such character
504: */
505: public static int indexOfLastSeparator(String filename) {
506: if (filename == null) {
507: return -1;
508: }
509: int lastUnixPos = filename.lastIndexOf(UNIX_SEPARATOR);
510: int lastWindowsPos = filename.lastIndexOf(WINDOWS_SEPARATOR);
511: return Math.max(lastUnixPos, lastWindowsPos);
512: }
513:
514: /**
515: * Returns the index of the last extension separator character, which is a dot.
516: * <p>
517: * This method also checks that there is no directory separator after the last dot.
518: * To do this it uses {@link #indexOfLastSeparator(String)} which will
519: * handle a file in either Unix or Windows format.
520: * <p>
521: * The output will be the same irrespective of the machine that the code is running on.
522: *
523: * @param filename the filename to find the last path separator in, null returns -1
524: * @return the index of the last separator character, or -1 if there
525: * is no such character
526: */
527: public static int indexOfExtension(String filename) {
528: if (filename == null) {
529: return -1;
530: }
531: int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR);
532: int lastSeparator = indexOfLastSeparator(filename);
533: return (lastSeparator > extensionPos ? -1 : extensionPos);
534: }
535:
536: // ---------------------------------------------------------------- get
537:
538: /**
539: * Gets the prefix from a full filename, such as <code>C:/</code>
540: * or <code>~/</code>.
541: * <p>
542: * This method will handle a file in either Unix or Windows format.
543: * The prefix includes the first slash in the full filename where applicable.
544: * <pre>
545: * Windows:
546: * a\b\c.txt --> "" --> relative
547: * \a\b\c.txt --> "\" --> current drive absolute
548: * C:a\b\c.txt --> "C:" --> drive relative
549: * C:\a\b\c.txt --> "C:\" --> absolute
550: * \\server\a\b\c.txt --> "\\server\" --> UNC
551: *
552: * Unix:
553: * a/b/c.txt --> "" --> relative
554: * /a/b/c.txt --> "/" --> absolute
555: * ~/a/b/c.txt --> "~/" --> current user
556: * ~ --> "~/" --> current user (slash added)
557: * ~user/a/b/c.txt --> "~user/" --> named user
558: * ~user --> "~user/" --> named user (slash added)
559: * </pre>
560: * <p>
561: * The output will be the same irrespective of the machine that the code is running on.
562: * ie. both Unix and Windows prefixes are matched regardless.
563: *
564: * @param filename the filename to query, null returns null
565: * @return the prefix of the file, null if invalid
566: */
567: public static String getPrefix(String filename) {
568: if (filename == null) {
569: return null;
570: }
571: int len = getPrefixLength(filename);
572: if (len < 0) {
573: return null;
574: }
575: if (len > filename.length()) {
576: return filename + UNIX_SEPARATOR; // we know this only happens for unix
577: }
578: return filename.substring(0, len);
579: }
580:
581: /**
582: * Gets the path from a full filename, which excludes the prefix.
583: * <p>
584: * This method will handle a file in either Unix or Windows format.
585: * The method is entirely text based, and returns the text before and
586: * including the last forward or backslash.
587: * <pre>
588: * C:\a\b\c.txt --> a\b\
589: * ~/a/b/c.txt --> a/b/
590: * a.txt --> ""
591: * a/b/c --> a/b/
592: * a/b/c/ --> a/b/c/
593: * </pre>
594: * <p>
595: * The output will be the same irrespective of the machine that the code is running on.
596: * <p>
597: * This method drops the prefix from the result.
598: * See {@link #getFullPath(String)} for the method that retains the prefix.
599: *
600: * @param filename the filename to query, null returns null
601: * @return the path of the file, an empty string if none exists, null if invalid
602: */
603: public static String getPath(String filename) {
604: return doGetPath(filename, 1);
605: }
606:
607: /**
608: * Gets the path from a full filename, which excludes the prefix, and
609: * also excluding the final directory separator.
610: * <p>
611: * This method will handle a file in either Unix or Windows format.
612: * The method is entirely text based, and returns the text before the
613: * last forward or backslash.
614: * <pre>
615: * C:\a\b\c.txt --> a\b
616: * ~/a/b/c.txt --> a/b
617: * a.txt --> ""
618: * a/b/c --> a/b
619: * a/b/c/ --> a/b/c
620: * </pre>
621: * <p>
622: * The output will be the same irrespective of the machine that the code is running on.
623: * <p>
624: * This method drops the prefix from the result.
625: * See {@link #getFullPathNoEndSeparator(String)} for the method that retains the prefix.
626: *
627: * @param filename the filename to query, null returns null
628: * @return the path of the file, an empty string if none exists, null if invalid
629: */
630: public static String getPathNoEndSeparator(String filename) {
631: return doGetPath(filename, 0);
632: }
633:
634: /**
635: * Does the work of getting the path.
636: *
637: * @param filename the filename
638: * @param separatorAdd 0 to omit the end separator, 1 to return it
639: * @return the path
640: */
641: private static String doGetPath(String filename, int separatorAdd) {
642: if (filename == null) {
643: return null;
644: }
645: int prefix = getPrefixLength(filename);
646: if (prefix < 0) {
647: return null;
648: }
649: int index = indexOfLastSeparator(filename);
650: if (prefix >= filename.length() || index < 0) {
651: return "";
652: }
653: return filename.substring(prefix, index + separatorAdd);
654: }
655:
656: /**
657: * Gets the full path from a full filename, which is the prefix + path.
658: * <p>
659: * This method will handle a file in either Unix or Windows format.
660: * The method is entirely text based, and returns the text before and
661: * including the last forward or backslash.
662: * <pre>
663: * C:\a\b\c.txt --> C:\a\b\
664: * ~/a/b/c.txt --> ~/a/b/
665: * a.txt --> ""
666: * a/b/c --> a/b/
667: * a/b/c/ --> a/b/c/
668: * C: --> C:
669: * C:\ --> C:\
670: * ~ --> ~/
671: * ~/ --> ~/
672: * ~user --> ~user/
673: * ~user/ --> ~user/
674: * </pre>
675: * <p>
676: * The output will be the same irrespective of the machine that the code is running on.
677: *
678: * @param filename the filename to query, null returns null
679: * @return the path of the file, an empty string if none exists, null if invalid
680: */
681: public static String getFullPath(String filename) {
682: return doGetFullPath(filename, true);
683: }
684:
685: /**
686: * Gets the full path from a full filename, which is the prefix + path,
687: * and also excluding the final directory separator.
688: * <p>
689: * This method will handle a file in either Unix or Windows format.
690: * The method is entirely text based, and returns the text before the
691: * last forward or backslash.
692: * <pre>
693: * C:\a\b\c.txt --> C:\a\b
694: * ~/a/b/c.txt --> ~/a/b
695: * a.txt --> ""
696: * a/b/c --> a/b
697: * a/b/c/ --> a/b/c
698: * C: --> C:
699: * C:\ --> C:\
700: * ~ --> ~
701: * ~/ --> ~
702: * ~user --> ~user
703: * ~user/ --> ~user
704: * </pre>
705: * <p>
706: * The output will be the same irrespective of the machine that the code is running on.
707: *
708: * @param filename the filename to query, null returns null
709: * @return the path of the file, an empty string if none exists, null if invalid
710: */
711: public static String getFullPathNoEndSeparator(String filename) {
712: return doGetFullPath(filename, false);
713: }
714:
715: /**
716: * Does the work of getting the path.
717: *
718: * @param filename the filename
719: * @param includeSeparator true to include the end separator
720: * @return the path
721: */
722: private static String doGetFullPath(String filename,
723: boolean includeSeparator) {
724: if (filename == null) {
725: return null;
726: }
727: int prefix = getPrefixLength(filename);
728: if (prefix < 0) {
729: return null;
730: }
731: if (prefix >= filename.length()) {
732: if (includeSeparator) {
733: return getPrefix(filename); // add end slash if necessary
734: } else {
735: return filename;
736: }
737: }
738: int index = indexOfLastSeparator(filename);
739: if (index < 0) {
740: return filename.substring(0, prefix);
741: }
742: int end = index + (includeSeparator ? 1 : 0);
743: return filename.substring(0, end);
744: }
745:
746: /**
747: * Gets the name minus the path from a full filename.
748: * <p>
749: * This method will handle a file in either Unix or Windows format.
750: * The text after the last forward or backslash is returned.
751: * <pre>
752: * a/b/c.txt --> c.txt
753: * a.txt --> a.txt
754: * a/b/c --> c
755: * a/b/c/ --> ""
756: * </pre>
757: * <p>
758: * The output will be the same irrespective of the machine that the code is running on.
759: *
760: * @param filename the filename to query, null returns null
761: * @return the name of the file without the path, or an empty string if none exists
762: */
763: public static String getName(String filename) {
764: if (filename == null) {
765: return null;
766: }
767: int index = indexOfLastSeparator(filename);
768: return filename.substring(index + 1);
769: }
770:
771: /**
772: * Gets the base name, minus the full path and extension, from a full filename.
773: * <p>
774: * This method will handle a file in either Unix or Windows format.
775: * The text after the last forward or backslash and before the last dot is returned.
776: * <pre>
777: * a/b/c.txt --> c
778: * a.txt --> a
779: * a/b/c --> c
780: * a/b/c/ --> ""
781: * </pre>
782: * <p>
783: * The output will be the same irrespective of the machine that the code is running on.
784: *
785: * @param filename the filename to query, null returns null
786: * @return the name of the file without the path, or an empty string if none exists
787: */
788: public static String getBaseName(String filename) {
789: return removeExtension(getName(filename));
790: }
791:
792: /**
793: * Gets the extension of a filename.
794: * <p>
795: * This method returns the textual part of the filename after the last dot.
796: * There must be no directory separator after the dot.
797: * <pre>
798: * foo.txt --> "txt"
799: * a/b/c.jpg --> "jpg"
800: * a/b.txt/c --> ""
801: * a/b/c --> ""
802: * </pre>
803: * <p>
804: * The output will be the same irrespective of the machine that the code is running on.
805: *
806: * @param filename the filename to retrieve the extension of.
807: * @return the extension of the file or an empty string if none exists.
808: */
809: public static String getExtension(String filename) {
810: if (filename == null) {
811: return null;
812: }
813: int index = indexOfExtension(filename);
814: if (index == -1) {
815: return "";
816: } else {
817: return filename.substring(index + 1);
818: }
819: }
820:
821: //----------------------------------------------------------------------- remove
822:
823: /**
824: * Removes the extension from a filename.
825: * <p>
826: * This method returns the textual part of the filename before the last dot.
827: * There must be no directory separator after the dot.
828: * <pre>
829: * foo.txt --> foo
830: * a\b\c.jpg --> a\b\c
831: * a\b\c --> a\b\c
832: * a.b\c --> a.b\c
833: * </pre>
834: * <p>
835: * The output will be the same irrespective of the machine that the code is running on.
836: *
837: * @param filename the filename to query, null returns null
838: * @return the filename minus the extension
839: */
840: public static String removeExtension(String filename) {
841: if (filename == null) {
842: return null;
843: }
844: int index = indexOfExtension(filename);
845: if (index == -1) {
846: return filename;
847: } else {
848: return filename.substring(0, index);
849: }
850: }
851:
852: // ---------------------------------------------------------------- equals
853:
854: /**
855: * Checks whether two filenames are equal exactly.
856: */
857: public static boolean equals(String filename1, String filename2) {
858: return equals(filename1, filename2, false);
859: }
860:
861: /**
862: * Checks whether two filenames are equal using the case rules of the system.
863: */
864: public static boolean equalsOnSystem(String filename1,
865: String filename2) {
866: return equals(filename1, filename2, true);
867: }
868:
869: /**
870: * Checks whether two filenames are equal optionally using the case rules of the system.
871: * <p>
872: *
873: * @param filename1 the first filename to query, may be null
874: * @param filename2 the second filename to query, may be null
875: * @param system whether to use the system (windows or unix)
876: * @return true if the filenames are equal, null equals null
877: */
878: private static boolean equals(String filename1, String filename2,
879: boolean system) {
880: //noinspection StringEquality
881: if (filename1 == filename2) {
882: return true;
883: }
884: if (filename1 == null || filename2 == null) {
885: return false;
886: }
887: if (system && (SYSTEM_SEPARATOR == WINDOWS_SEPARATOR)) {
888: return filename1.equalsIgnoreCase(filename2);
889: } else {
890: return filename1.equals(filename2);
891: }
892: }
893:
894: // ---------------------------------------------------------------- split
895:
896: /**
897: * Splits filename into a array of four Strings containing prefix, path, basename and extension.
898: * Path will contain ending separator.
899: */
900: public static String[] split(String filename) {
901: String prefix = getPrefix(filename);
902: if (prefix == null) {
903: prefix = "";
904: }
905: int lastSeparatorIndex = indexOfLastSeparator(filename);
906: int lastExtensionIndex = indexOfExtension(filename);
907:
908: String path;
909: String baseName;
910: String extension;
911:
912: if (lastSeparatorIndex == -1) {
913: path = "";
914: if (lastExtensionIndex == -1) {
915: baseName = filename.substring(prefix.length());
916: extension = "";
917: } else {
918: baseName = filename.substring(prefix.length(),
919: lastExtensionIndex);
920: extension = filename.substring(lastExtensionIndex + 1);
921: }
922: } else {
923: path = filename.substring(prefix.length(),
924: lastSeparatorIndex + 1);
925: if (lastExtensionIndex == -1) {
926: baseName = filename.substring(prefix.length()
927: + path.length());
928: extension = "";
929: } else {
930: baseName = filename.substring(prefix.length()
931: + path.length(), lastExtensionIndex);
932: extension = filename.substring(lastExtensionIndex + 1);
933: }
934: }
935: return new String[] { prefix, path, baseName, extension };
936: }
937:
938: }
|