1616package org .apache .commons .io ;
1717
1818import java .io .File ;
19- import java .io .IOException ;
20- import java .util .Collection ;
2119import java .util .ArrayList ;
20+ import java .util .Collection ;
2221
2322/**
2423 * Utility class that provides methods to manipulate filenames and filepaths.
5150 * @author Martin Cooper
5251 * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
5352 * @author Stephen Colebourne
54- * @version $Id: FilenameUtils.java,v 1.28 2004/11/26 19:18:28 martinc Exp $
53+ * @version $Id: FilenameUtils.java,v 1.29 2004/11/27 01:22:05 scolebourne Exp $
5554 * @since Commons IO 1.1
5655 */
5756public class FilenameUtils {
@@ -112,22 +111,29 @@ private static boolean isSeparator(char ch) {
112111 * The input may contain separators in either Unix or Windows format.
113112 * The output will contain separators in the format of the system.
114113 * <p>
115- * A double slash will be merged to a single slash (thus UNC names are not handled).
116- * A single dot path segment will be removed with no other effect.
114+ * A trailing slash will be removed.
115+ * A double slash will be merged to a single slash (but UNC names are handled).
116+ * A single dot path segment will be removed.
117117 * A double dot will cause that path segment and the one before to be removed.
118118 * If the double dot has no parent path segment to work with, <code>null</code>
119119 * is returned.
120120 * <pre>
121- * /foo// --> /foo/
122- * /foo/./ --> /foo/
123- * /foo/../bar --> /bar
124- * /foo/../bar/ --> /bar/
125- * /foo/../bar/../baz --> /baz
126- * //foo//./bar --> /foo/bar
127- * /../ --> null
128- * ../foo --> null
129- * foo/../../bar --> null
130- * foo/../bar --> bar
121+ * /foo// --> /foo
122+ * /foo/./ --> /foo
123+ * /foo/../bar --> /bar
124+ * /foo/../bar/ --> /bar
125+ * /foo/../bar/../baz --> /baz
126+ * //foo//./bar --> /foo/bar
127+ * /../ --> null
128+ * ../foo --> null
129+ * foo/../../bar --> null
130+ * foo/../bar --> bar
131+ * //server/foo/../bar --> //server/bar
132+ * //server/../bar --> null
133+ * C:\foo\..\bar --> C:\bar
134+ * C:\..\bar --> null
135+ * ~/foo/../bar --> ~/bar
136+ * ~/../bar --> null
131137 * </pre>
132138 *
133139 * @param filename the filename to normalize, null returns null
@@ -137,62 +143,75 @@ public static String normalize(String filename) {
137143 if (filename == null ) {
138144 return null ;
139145 }
140- char [] array = filename .toCharArray ();
146+ int size = filename .length ();
147+ if (size == 0 ) {
148+ return filename ;
149+ }
141150 int prefix = getPrefixLength (filename );
142151 if (prefix < 0 ) {
143152 return null ;
144153 }
145154
146- // TODO: Use prefix
155+ char [] array = new char [size + 2 ]; // +1 for possible extra slash, +2 for arraycopy
156+ filename .getChars (0 , filename .length (), array , 0 );
147157
148- int size = array .length ;
149- // fix separators
158+ // fix separators throughout
150159 for (int i = 0 ; i < array .length ; i ++) {
151160 if (array [i ] == OTHER_SEPARATOR ) {
152161 array [i ] = SYSTEM_SEPARATOR ;
153162 }
154163 }
164+ if (isSeparator (array [size - 1 ]) == false ) {
165+ array [size ++] = SYSTEM_SEPARATOR ;
166+ }
167+
155168 // adjoining slashes
156- for (int i = 1 ; i < size ; i ++) {
169+ for (int i = prefix + 1 ; i < size ; i ++) {
157170 if (array [i ] == SYSTEM_SEPARATOR && array [i - 1 ] == SYSTEM_SEPARATOR ) {
158171 System .arraycopy (array , i , array , i - 1 , size - i );
159172 size --;
160173 i --;
161174 }
162175 }
163176 // dot slash
164- for (int i = 2 ; i < size ; i ++) {
177+ for (int i = prefix + 1 ; i < size ; i ++) {
165178 if (array [i ] == SYSTEM_SEPARATOR && array [i - 1 ] == '.' &&
166- array [i - 2 ] == SYSTEM_SEPARATOR ) {
167- System .arraycopy (array , i , array , i - 2 , size - i );
179+ ( i == prefix + 1 || array [i - 2 ] == SYSTEM_SEPARATOR ) ) {
180+ System .arraycopy (array , i + 1 , array , i - 1 , size - i );
168181 size -=2 ;
169182 i --;
170183 }
171184 }
172185 // double dot slash
173186 outer :
174- for (int i = 2 ; i < size ; i ++) {
175- if (array [i ] == SYSTEM_SEPARATOR && array [i - 1 ] == '.' &&
176- array [ i - 2 ] == '.' && ( i == 2 || array [i - 3 ] == SYSTEM_SEPARATOR )) {
177- if (i == 2 ) {
187+ for (int i = prefix + 2 ; i < size ; i ++) {
188+ if (array [i ] == SYSTEM_SEPARATOR && array [i - 1 ] == '.' && array [ i - 2 ] == '.' &&
189+ ( i == prefix + 2 || array [i - 3 ] == SYSTEM_SEPARATOR )) {
190+ if (i == prefix + 2 ) {
178191 return null ;
179192 }
180193 int j ;
181- for (j = i - 4 ; j >= 0 ; j --) {
194+ for (j = i - 4 ; j >= prefix ; j --) {
182195 if (array [j ] == SYSTEM_SEPARATOR ) {
183- System .arraycopy (array , i , array , j , size - i );
196+ System .arraycopy (array , i + 1 , array , j + 1 , size - i );
184197 size -= (i - j );
185198 i = j + 1 ;
186199 continue outer ;
187200 }
188201 }
189- System .arraycopy (array , i + 1 , array , 0 , size - i - 1 );
190- size -= (i + 1 );
191- i = 1 ;
202+ System .arraycopy (array , i + 1 , array , prefix , size - i );
203+ size -= (i + 1 - prefix );
204+ i = prefix + 1 ;
192205 }
193206 }
194207
195- return new String (array , 0 , size );
208+ if (size <= 0 ) { // should never be less than 0
209+ return "" ;
210+ }
211+ if (size <= prefix ) { // should never be less than prefix
212+ return new String (array , 0 , size );
213+ }
214+ return new String (array , 0 , size - 1 );
196215 }
197216
198217 /**
@@ -241,79 +260,6 @@ public static String catPath(String lookupPath, String path) {
241260 append (File .separator ).append (pth ).toString ();
242261 }
243262
244- /**
245- * Resolve a file <code>filename</code> to it's canonical form. If
246- * <code>filename</code> is relative (doesn't start with <code>/</code>),
247- * it will be resolved relative to <code>baseFile</code>, otherwise it is
248- * treated as a normal root-relative path.
249- *
250- * @param baseFile Where to resolve <code>filename</code> from, if
251- * <code>filename</code> is relative.
252- * @param filename Absolute or relative file path to resolve.
253- * @return The canonical <code>File</code> of <code>filename</code>.
254- */
255- // KILL? Decide whether this is worth keeping?
256- public static File resolveFile (File baseFile , String filename ) {
257- String filenm = filename ;
258- if ('/' != File .separatorChar ) {
259- filenm = filename .replace ('/' , File .separatorChar );
260- }
261-
262- if ('\\' != File .separatorChar ) {
263- filenm = filename .replace ('\\' , File .separatorChar );
264- }
265-
266- // deal with absolute files
267- if (filenm .startsWith (File .separator )) {
268- File file = new File (filenm );
269-
270- try {
271- file = file .getCanonicalFile ();
272- } catch (IOException ioe ) {
273- // ignore
274- }
275-
276- return file ;
277- }
278- // FIXME: I'm almost certain this // removal is unnecessary, as
279- // getAbsoluteFile() strips
280- // them. However, I'm not sure about this UNC stuff. (JT)
281- char [] chars = filename .toCharArray ();
282- StringBuffer sb = new StringBuffer ();
283-
284- //remove duplicate file separators in succession - except
285- //on win32 at start of filename as UNC filenames can
286- //be \\AComputer\AShare\myfile.txt
287- int start = 0 ;
288- if ('\\' == File .separatorChar ) {
289- sb .append (filenm .charAt (0 ));
290- start ++;
291- }
292-
293- for (int i = start ; i < chars .length ; i ++) {
294- boolean doubleSeparator =
295- File .separatorChar == chars [i ]
296- && File .separatorChar == chars [i - 1 ];
297-
298- if (!doubleSeparator ) {
299- sb .append (chars [i ]);
300- }
301- }
302-
303- filenm = sb .toString ();
304-
305- //must be relative
306- File file = (new File (baseFile , filenm )).getAbsoluteFile ();
307-
308- try {
309- file = file .getCanonicalFile ();
310- } catch ( IOException ioe ) {
311- ;
312- }
313-
314- return file ;
315- }
316-
317263 //-----------------------------------------------------------------------
318264 /**
319265 * Converts all separators to the Unix separator of forward slash.
@@ -377,6 +323,8 @@ public static String separatorsToSystem(String path) {
377323 * ~/a/b/c.txt --> "~/" --> current user relative
378324 * ~user/a/b/c.txt --> "~user/" --> named user relative
379325 * </pre>
326+ * Both sets of prefixes will be matched regardless of the system
327+ * on which the code runs.
380328 *
381329 * @param filename the filename to find the prefix in, null returns -1
382330 * @return the length of the prefix, -1 if invalid or null
@@ -389,37 +337,14 @@ public static int getPrefixLength(String filename) {
389337 if (len == 0 ) {
390338 return 0 ;
391339 }
392- if (SYSTEM_SEPARATOR == WINDOWS_SEPARATOR ) {
393- char ch0 = filename .charAt (0 );
394- if (len == 1 ) {
395- return (isSeparator (ch0 ) ? 1 : 0 );
396- } else {
397- char ch1 = filename .charAt (1 );
398- if (ch1 == ':' ) {
399- ch0 = Character .toUpperCase (ch0 );
400- if (ch0 < 'A' || ch0 > 'Z' || len == 2 || isSeparator (filename .charAt (2 )) == false ) {
401- return -1 ;
402- }
403- return 3 ;
404- } else if (isSeparator (ch0 ) && isSeparator (ch1 )) {
405- int posUnix = filename .indexOf (UNIX_SEPARATOR , 2 );
406- int posWin = filename .indexOf (WINDOWS_SEPARATOR , 2 );
407- if ((posUnix == -1 && posWin == -1 ) || posUnix == 2 || posWin == 2 ) {
408- return -1 ;
409- }
410- posUnix = (posUnix == -1 ? posWin : posUnix );
411- posWin = (posWin == -1 ? posUnix : posWin );
412- return Math .min (posUnix , posWin ) + 1 ;
413- } else {
414- return (isSeparator (ch0 ) ? 1 : 0 );
415- }
340+ char ch0 = filename .charAt (0 );
341+ if (len == 1 ) {
342+ if (ch0 == '~' || ch0 == ':' ) {
343+ return -1 ;
416344 }
345+ return (isSeparator (ch0 ) ? 1 : 0 );
417346 } else {
418- char ch0 = filename .charAt (0 );
419347 if (ch0 == '~' ) {
420- if (len == 1 ) {
421- return -1 ;
422- }
423348 int posUnix = filename .indexOf (UNIX_SEPARATOR , 1 );
424349 int posWin = filename .indexOf (WINDOWS_SEPARATOR , 1 );
425350 if (posUnix == -1 && posWin == -1 ) {
@@ -428,6 +353,24 @@ public static int getPrefixLength(String filename) {
428353 posUnix = (posUnix == -1 ? posWin : posUnix );
429354 posWin = (posWin == -1 ? posUnix : posWin );
430355 return Math .min (posUnix , posWin ) + 1 ;
356+ }
357+ char ch1 = filename .charAt (1 );
358+ if (ch1 == ':' ) {
359+ ch0 = Character .toUpperCase (ch0 );
360+ if (ch0 < 'A' || ch0 > 'Z' || len == 2 || isSeparator (filename .charAt (2 )) == false ) {
361+ return -1 ;
362+ }
363+ return 3 ;
364+
365+ } else if (isSeparator (ch0 ) && isSeparator (ch1 )) {
366+ int posUnix = filename .indexOf (UNIX_SEPARATOR , 2 );
367+ int posWin = filename .indexOf (WINDOWS_SEPARATOR , 2 );
368+ if ((posUnix == -1 && posWin == -1 ) || posUnix == 2 || posWin == 2 ) {
369+ return -1 ;
370+ }
371+ posUnix = (posUnix == -1 ? posWin : posUnix );
372+ posWin = (posWin == -1 ? posUnix : posWin );
373+ return Math .min (posUnix , posWin ) + 1 ;
431374 } else {
432375 return (isSeparator (ch0 ) ? 1 : 0 );
433376 }
@@ -513,10 +456,11 @@ public static String getPrefix(String filename) {
513456 * This method will handle a file in either Unix or Windows format.
514457 * The text before the last forward or backslash is returned.
515458 * <pre>
516- * ~/a/b/c.txt --> a/b
517- * a.txt --> ""
518- * a/b/c --> a/b
519- * a/b/c/ --> a/b/c
459+ * C:\a\b\c.txt --> a\b
460+ * ~/a/b/c.txt --> a/b
461+ * a.txt --> ""
462+ * a/b/c --> a/b
463+ * a/b/c/ --> a/b/c
520464 * </pre>
521465 *
522466 * @param filename the filename to query, null returns null
@@ -544,10 +488,11 @@ public static String getPath(String filename) {
544488 * This method will handle a file in either Unix or Windows format.
545489 * The text before the last forward or backslash is returned.
546490 * <pre>
547- * ~/a/b/c.txt --> ~/a/b
548- * a.txt --> ""
549- * a/b/c --> a/b
550- * a/b/c/ --> a/b/c
491+ * C:\a\b\c.txt --> C:\a\b
492+ * ~/a/b/c.txt --> ~/a/b
493+ * a.txt --> ""
494+ * a/b/c --> a/b
495+ * a/b/c/ --> a/b/c
551496 * </pre>
552497 *
553498 * @param filename the filename to query, null returns null
0 commit comments