Skip to content

Commit 3323d0a

Browse files
author
Stephen Colebourne
committed
Refactor normalize method, and simplify getPrefixLength
git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/io/trunk@140644 13f79535-47bb-0310-9956-ffa450edef68
1 parent a68c2dc commit 3323d0a

2 files changed

Lines changed: 291 additions & 358 deletions

File tree

src/java/org/apache/commons/io/FilenameUtils.java

Lines changed: 87 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@
1616
package org.apache.commons.io;
1717

1818
import java.io.File;
19-
import java.io.IOException;
20-
import java.util.Collection;
2119
import java.util.ArrayList;
20+
import java.util.Collection;
2221

2322
/**
2423
* Utility class that provides methods to manipulate filenames and filepaths.
@@ -51,7 +50,7 @@
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
*/
5756
public 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

Comments
 (0)