Skip to content

Commit 4a3fd09

Browse files
author
Niall Pemberton
committed
IO-156 Add normalize() and normalizeNoEndSeparator() method flavours which allow the separator character to be specified
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/io/trunk@723186 13f79535-47bb-0310-9956-ffa450edef68
1 parent 328a7ef commit 4a3fd09

2 files changed

Lines changed: 131 additions & 13 deletions

File tree

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

Lines changed: 109 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,54 @@ private static boolean isSeparator(char ch) {
201201
* @return the normalized filename, or null if invalid
202202
*/
203203
public static String normalize(String filename) {
204-
return doNormalize(filename, true);
204+
return doNormalize(filename, SYSTEM_SEPARATOR, true);
205+
}
206+
/**
207+
* Normalizes a path, removing double and single dot path steps.
208+
* <p>
209+
* This method normalizes a path to a standard format.
210+
* The input may contain separators in either Unix or Windows format.
211+
* The output will contain separators in the format specified.
212+
* <p>
213+
* A trailing slash will be retained.
214+
* A double slash will be merged to a single slash (but UNC names are handled).
215+
* A single dot path segment will be removed.
216+
* A double dot will cause that path segment and the one before to be removed.
217+
* If the double dot has no parent path segment to work with, <code>null</code>
218+
* is returned.
219+
* <p>
220+
* The output will be the same on both Unix and Windows except
221+
* for the separator character.
222+
* <pre>
223+
* /foo// --> /foo/
224+
* /foo/./ --> /foo/
225+
* /foo/../bar --> /bar
226+
* /foo/../bar/ --> /bar/
227+
* /foo/../bar/../baz --> /baz
228+
* //foo//./bar --> /foo/bar
229+
* /../ --> null
230+
* ../foo --> null
231+
* foo/bar/.. --> foo/
232+
* foo/../../bar --> null
233+
* foo/../bar --> bar
234+
* //server/foo/../bar --> //server/bar
235+
* //server/../bar --> null
236+
* C:\foo\..\bar --> C:\bar
237+
* C:\..\bar --> null
238+
* ~/foo/../bar/ --> ~/bar/
239+
* ~/../bar --> null
240+
* </pre>
241+
* The output will be the same on both Unix and Windows including
242+
* the separator character.
243+
*
244+
* @param filename the filename to normalize, null returns null
245+
* @param unixSeparator <code>true</code> if a unix separator should
246+
* be used or <code>false</code> if a windows separtor should be used.
247+
* @return the normalized filename, or null if invalid
248+
*/
249+
public static String normalize(String filename, boolean unixSeparator) {
250+
char separator = (unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR);
251+
return doNormalize(filename, separator, true);
205252
}
206253

207254
//-----------------------------------------------------------------------
@@ -247,17 +294,65 @@ public static String normalize(String filename) {
247294
* @return the normalized filename, or null if invalid
248295
*/
249296
public static String normalizeNoEndSeparator(String filename) {
250-
return doNormalize(filename, false);
297+
return doNormalize(filename, SYSTEM_SEPARATOR, false);
298+
}
299+
300+
/**
301+
* Normalizes a path, removing double and single dot path steps,
302+
* and removing any final directory separator.
303+
* <p>
304+
* This method normalizes a path to a standard format.
305+
* The input may contain separators in either Unix or Windows format.
306+
* The output will contain separators in the format specified.
307+
* <p>
308+
* A trailing slash will be removed.
309+
* A double slash will be merged to a single slash (but UNC names are handled).
310+
* A single dot path segment will be removed.
311+
* A double dot will cause that path segment and the one before to be removed.
312+
* If the double dot has no parent path segment to work with, <code>null</code>
313+
* is returned.
314+
* <p>
315+
* The output will be the same on both Unix and Windows including
316+
* the separator character.
317+
* <pre>
318+
* /foo// --> /foo
319+
* /foo/./ --> /foo
320+
* /foo/../bar --> /bar
321+
* /foo/../bar/ --> /bar
322+
* /foo/../bar/../baz --> /baz
323+
* //foo//./bar --> /foo/bar
324+
* /../ --> null
325+
* ../foo --> null
326+
* foo/bar/.. --> foo
327+
* foo/../../bar --> null
328+
* foo/../bar --> bar
329+
* //server/foo/../bar --> //server/bar
330+
* //server/../bar --> null
331+
* C:\foo\..\bar --> C:\bar
332+
* C:\..\bar --> null
333+
* ~/foo/../bar/ --> ~/bar
334+
* ~/../bar --> null
335+
* </pre>
336+
*
337+
* @param filename the filename to normalize, null returns null
338+
* @param unixSeparator <code>true</code> if a unix separator should
339+
* be used or <code>false</code> if a windows separtor should be used.
340+
* @return the normalized filename, or null if invalid
341+
*/
342+
public static String normalizeNoEndSeparator(String filename, boolean unixSeparator) {
343+
char separator = (unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR);
344+
return doNormalize(filename, separator, false);
251345
}
252346

253347
/**
254348
* Internal method to perform the normalization.
255349
*
256350
* @param filename the filename
351+
* @param separator The separator character to use
257352
* @param keepSeparator true to keep the final separator
258353
* @return the normalized filename
259354
*/
260-
private static String doNormalize(String filename, boolean keepSeparator) {
355+
private static String doNormalize(String filename, char separator, boolean keepSeparator) {
261356
if (filename == null) {
262357
return null;
263358
}
@@ -274,22 +369,23 @@ private static String doNormalize(String filename, boolean keepSeparator) {
274369
filename.getChars(0, filename.length(), array, 0);
275370

276371
// fix separators throughout
372+
char otherSeparator = (separator == SYSTEM_SEPARATOR ? OTHER_SEPARATOR : SYSTEM_SEPARATOR);
277373
for (int i = 0; i < array.length; i++) {
278-
if (array[i] == OTHER_SEPARATOR) {
279-
array[i] = SYSTEM_SEPARATOR;
374+
if (array[i] == otherSeparator) {
375+
array[i] = separator;
280376
}
281377
}
282378

283379
// add extra separator on the end to simplify code below
284380
boolean lastIsDirectory = true;
285-
if (array[size - 1] != SYSTEM_SEPARATOR) {
286-
array[size++] = SYSTEM_SEPARATOR;
381+
if (array[size - 1] != separator) {
382+
array[size++] = separator;
287383
lastIsDirectory = false;
288384
}
289385

290386
// adjoining slashes
291387
for (int i = prefix + 1; i < size; i++) {
292-
if (array[i] == SYSTEM_SEPARATOR && array[i - 1] == SYSTEM_SEPARATOR) {
388+
if (array[i] == separator && array[i - 1] == separator) {
293389
System.arraycopy(array, i, array, i - 1, size - i);
294390
size--;
295391
i--;
@@ -298,8 +394,8 @@ private static String doNormalize(String filename, boolean keepSeparator) {
298394

299395
// dot slash
300396
for (int i = prefix + 1; i < size; i++) {
301-
if (array[i] == SYSTEM_SEPARATOR && array[i - 1] == '.' &&
302-
(i == prefix + 1 || array[i - 2] == SYSTEM_SEPARATOR)) {
397+
if (array[i] == separator && array[i - 1] == '.' &&
398+
(i == prefix + 1 || array[i - 2] == separator)) {
303399
if (i == size - 1) {
304400
lastIsDirectory = true;
305401
}
@@ -312,8 +408,8 @@ private static String doNormalize(String filename, boolean keepSeparator) {
312408
// double dot slash
313409
outer:
314410
for (int i = prefix + 2; i < size; i++) {
315-
if (array[i] == SYSTEM_SEPARATOR && array[i - 1] == '.' && array[i - 2] == '.' &&
316-
(i == prefix + 2 || array[i - 3] == SYSTEM_SEPARATOR)) {
411+
if (array[i] == separator && array[i - 1] == '.' && array[i - 2] == '.' &&
412+
(i == prefix + 2 || array[i - 3] == separator)) {
317413
if (i == prefix + 2) {
318414
return null;
319415
}
@@ -322,7 +418,7 @@ private static String doNormalize(String filename, boolean keepSeparator) {
322418
}
323419
int j;
324420
for (j = i - 4 ; j >= prefix; j--) {
325-
if (array[j] == SYSTEM_SEPARATOR) {
421+
if (array[j] == separator) {
326422
// remove b/../ from a/b/../c
327423
System.arraycopy(array, i + 1, array, j + 1, size - i);
328424
size -= (i - j);

src/test/org/apache/commons/io/FilenameUtilsTestCase.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,17 @@ public void testNormalize() throws Exception {
232232
assertEquals(SEP + SEP + "server" + SEP + "", FilenameUtils.normalize("//server/"));
233233
}
234234

235+
public void testNormalizeUnixWin() throws Exception {
236+
237+
// Normalize (Unix Separator)
238+
assertEquals("/a/c/", FilenameUtils.normalize("/a/b/../c/", true));
239+
assertEquals("/a/c/", FilenameUtils.normalize("\\a\\b\\..\\c\\", true));
240+
241+
// Normalize (Windows Separator)
242+
assertEquals("\\a\\c\\", FilenameUtils.normalize("/a/b/../c/", false));
243+
assertEquals("\\a\\c\\", FilenameUtils.normalize("\\a\\b\\..\\c\\", false));
244+
}
245+
235246
//-----------------------------------------------------------------------
236247
public void testNormalizeNoEndSeparator() throws Exception {
237248
assertEquals(null, FilenameUtils.normalizeNoEndSeparator(null));
@@ -382,6 +393,17 @@ public void testNormalizeNoEndSeparator() throws Exception {
382393
assertEquals(SEP + SEP + "server" + SEP + "", FilenameUtils.normalizeNoEndSeparator("//server/"));
383394
}
384395

396+
public void testNormalizeNoEndSeparatorUnixWin() throws Exception {
397+
398+
// Normalize (Unix Separator)
399+
assertEquals("/a/c", FilenameUtils.normalizeNoEndSeparator("/a/b/../c/", true));
400+
assertEquals("/a/c", FilenameUtils.normalizeNoEndSeparator("\\a\\b\\..\\c\\", true));
401+
402+
// Normalize (Windows Separator)
403+
assertEquals("\\a\\c", FilenameUtils.normalizeNoEndSeparator("/a/b/../c/", false));
404+
assertEquals("\\a\\c", FilenameUtils.normalizeNoEndSeparator("\\a\\b\\..\\c\\", false));
405+
}
406+
385407
//-----------------------------------------------------------------------
386408
public void testConcat() {
387409
assertEquals(null, FilenameUtils.concat("", null));

0 commit comments

Comments
 (0)