View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.io.file;
19  
20  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertNotEquals;
24  import static org.junit.jupiter.api.Assertions.assertNotNull;
25  import static org.junit.jupiter.api.Assertions.assertNull;
26  import static org.junit.jupiter.api.Assertions.assertThrows;
27  import static org.junit.jupiter.api.Assertions.assertTrue;
28  import static org.junit.jupiter.api.Assumptions.assumeFalse;
29  
30  import java.io.File;
31  import java.io.IOException;
32  import java.io.OutputStream;
33  import java.net.URISyntaxException;
34  import java.nio.charset.StandardCharsets;
35  import java.nio.file.DirectoryStream;
36  import java.nio.file.FileSystems;
37  import java.nio.file.Files;
38  import java.nio.file.LinkOption;
39  import java.nio.file.Path;
40  import java.nio.file.Paths;
41  import java.nio.file.attribute.DosFileAttributeView;
42  import java.nio.file.attribute.FileTime;
43  import java.nio.file.attribute.PosixFileAttributes;
44  import java.util.GregorianCalendar;
45  import java.util.Iterator;
46  
47  import org.apache.commons.io.FileUtils;
48  import org.apache.commons.io.filefilter.NameFileFilter;
49  import org.apache.commons.io.test.TestUtils;
50  import org.apache.commons.lang3.ArrayUtils;
51  import org.apache.commons.lang3.StringUtils;
52  import org.apache.commons.lang3.SystemProperties;
53  import org.apache.commons.lang3.SystemUtils;
54  import org.junit.jupiter.api.Test;
55  
56  /**
57   * Tests {@link PathUtils}.
58   */
59  class PathUtilsTest extends AbstractTempDirTest {
60  
61      private static final String STRING_FIXTURE = "Hello World";
62  
63      static final byte[] BYTE_ARRAY_FIXTURE = STRING_FIXTURE.getBytes(StandardCharsets.UTF_8);
64  
65      private static final String PATH_FIXTURE = "NOTICE.txt";
66  
67      private Path current() {
68          return PathUtils.current();
69      }
70  
71      private Long getLastModifiedMillis(final Path file) throws IOException {
72          return Files.getLastModifiedTime(file).toMillis();
73      }
74  
75      private Path getNonExistentPath() {
76          return Paths.get("/does not exist/for/certain");
77      }
78  
79      private void setLastModifiedMillis(final Path file, final long millis) throws IOException {
80          Files.setLastModifiedTime(file, FileTime.fromMillis(millis));
81      }
82  
83      @Test
84      void testCreateDirectoriesAlreadyExists() throws IOException {
85          assertEquals(tempDirPath.getParent(), PathUtils.createParentDirectories(tempDirPath));
86      }
87  
88      @SuppressWarnings("resource") // FileSystems.getDefault() is a singleton
89      @Test
90      void testCreateDirectoriesForRoots() throws IOException {
91          for (final Path path : FileSystems.getDefault().getRootDirectories()) {
92              final Path parent = path.getParent();
93              assertNull(parent);
94              assertEquals(parent, PathUtils.createParentDirectories(path));
95          }
96      }
97  
98      @Test
99      void testCreateDirectoriesForRootsLinkOptionNull() throws IOException {
100         for (final File f : File.listRoots()) {
101             final Path path = f.toPath();
102             assertEquals(path.getParent(), PathUtils.createParentDirectories(path, (LinkOption) null));
103         }
104     }
105 
106     @Test
107     void testCreateDirectoriesNew() throws IOException {
108         assertEquals(tempDirPath, PathUtils.createParentDirectories(tempDirPath.resolve("child")));
109     }
110 
111     @Test
112     void testCreateDirectoriesSymlink() throws IOException {
113         final Path symlinkedDir = createTempSymbolicLinkedRelativeDir(tempDirPath);
114         final String leafDirName = "child";
115         final Path newDirFollowed = PathUtils.createParentDirectories(symlinkedDir.resolve(leafDirName), PathUtils.NULL_LINK_OPTION);
116         assertEquals(Files.readSymbolicLink(symlinkedDir), newDirFollowed);
117     }
118 
119     @Test
120     void testCreateDirectoriesSymlinkClashing() throws IOException {
121         final Path symlinkedDir = createTempSymbolicLinkedRelativeDir(tempDirPath);
122         assertEquals(symlinkedDir, PathUtils.createParentDirectories(symlinkedDir.resolve("child")));
123     }
124 
125     @Test
126     void testGetBaseNamePathBaseCases() {
127         assertEquals("bar", PathUtils.getBaseName(Paths.get("a/b/c/bar.foo")));
128         assertEquals("foo", PathUtils.getBaseName(Paths.get("foo")));
129         assertEquals("", PathUtils.getBaseName(Paths.get("")));
130         assertEquals("", PathUtils.getBaseName(Paths.get(".")));
131         for (final File f : File.listRoots()) {
132             assertNull(PathUtils.getBaseName(f.toPath()));
133         }
134         if (SystemUtils.IS_OS_WINDOWS) {
135             assertNull(PathUtils.getBaseName(Paths.get("C:\\")));
136         }
137     }
138 
139     @Test
140     void testGetBaseNamePathCornerCases() {
141         assertNull(PathUtils.getBaseName((Path) null));
142         assertEquals("foo", PathUtils.getBaseName(Paths.get("foo.")));
143         assertEquals("", PathUtils.getBaseName(Paths.get("bar/.foo")));
144     }
145 
146     @Test
147     void testGetDosFileAttributeView() {
148         // dir
149         final DosFileAttributeView dosFileAttributeView = PathUtils.getDosFileAttributeView(current());
150         final Path path = Paths.get("this-file-does-not-exist-at.all");
151         assertFalse(Files.exists(path));
152         if (SystemUtils.IS_OS_MAC) {
153             assertNull(dosFileAttributeView);
154             // missing file
155             assertNull(PathUtils.getDosFileAttributeView(path));
156         } else {
157             assertNotNull(dosFileAttributeView);
158             // missing file
159             assertNotNull(PathUtils.getDosFileAttributeView(path));
160         }
161         // null
162         assertThrows(NullPointerException.class, () -> PathUtils.getDosFileAttributeView(null));
163     }
164 
165     @Test
166     void testGetExtension() {
167         assertNull(PathUtils.getExtension(null));
168         assertEquals("ext", PathUtils.getExtension(Paths.get("file.ext")));
169         assertEquals("", PathUtils.getExtension(Paths.get("README")));
170         assertEquals("com", PathUtils.getExtension(Paths.get("domain.dot.com")));
171         assertEquals("jpeg", PathUtils.getExtension(Paths.get("image.jpeg")));
172         assertEquals("", PathUtils.getExtension(Paths.get("a.b/c")));
173         assertEquals("txt", PathUtils.getExtension(Paths.get("a.b/c.txt")));
174         assertEquals("", PathUtils.getExtension(Paths.get("a/b/c")));
175         assertEquals("", PathUtils.getExtension(Paths.get("a.b\\c")));
176         assertEquals("txt", PathUtils.getExtension(Paths.get("a.b\\c.txt")));
177         assertEquals("", PathUtils.getExtension(Paths.get("a\\b\\c")));
178         assertEquals("", PathUtils.getExtension(Paths.get("C:\\temp\\foo.bar\\README")));
179         assertEquals("ext", PathUtils.getExtension(Paths.get("../filename.ext")));
180 
181         if (File.separatorChar != '\\') {
182             // Upwards compatibility:
183             assertEquals("txt", PathUtils.getExtension(Paths.get("foo.exe:bar.txt")));
184         }
185     }
186 
187     @Test
188     void testGetFileName() {
189         assertNull(PathUtils.getFileName(null, null));
190         assertNull(PathUtils.getFileName(null, Path::toString));
191         assertNull(PathUtils.getFileName(Paths.get("/"), Path::toString));
192         assertNull(PathUtils.getFileName(Paths.get("/"), Path::toString));
193         assertEquals("", PathUtils.getFileName(Paths.get(""), Path::toString));
194         assertEquals("a", PathUtils.getFileName(Paths.get("a"), Path::toString));
195         assertEquals("a", PathUtils.getFileName(Paths.get("p", "a"), Path::toString));
196     }
197 
198     @Test
199     void testGetFileNameString() {
200         assertNull(PathUtils.getFileNameString(Paths.get("/")));
201         assertEquals("", PathUtils.getFileNameString(Paths.get("")));
202         assertEquals("a", PathUtils.getFileNameString(Paths.get("a")));
203         assertEquals("a", PathUtils.getFileNameString(Paths.get("p", "a")));
204     }
205 
206     @Test
207     void testGetLastModifiedFileTime_File_Present() throws IOException {
208         assertNotNull(PathUtils.getLastModifiedFileTime(current().toFile()));
209     }
210 
211     @Test
212     void testGetLastModifiedFileTime_Path_Absent() throws IOException {
213         assertNull(PathUtils.getLastModifiedFileTime(getNonExistentPath()));
214     }
215 
216     @Test
217     void testGetLastModifiedFileTime_Path_FileTime_Absent() throws IOException {
218         final FileTime fromMillis = FileTime.fromMillis(0);
219         assertEquals(fromMillis, PathUtils.getLastModifiedFileTime(getNonExistentPath(), fromMillis));
220     }
221 
222     @Test
223     void testGetLastModifiedFileTime_Path_Present() throws IOException {
224         assertNotNull(PathUtils.getLastModifiedFileTime(current()));
225     }
226 
227     @Test
228     void testGetLastModifiedFileTime_URI_Present() throws IOException {
229         assertNotNull(PathUtils.getLastModifiedFileTime(current().toUri()));
230     }
231 
232     @Test
233     void testGetLastModifiedFileTime_URL_Present() throws IOException, URISyntaxException {
234         assertNotNull(PathUtils.getLastModifiedFileTime(current().toUri().toURL()));
235     }
236 
237     @Test
238     void testGetPath() {
239         final String validKey = "user.dir";
240         final Path value = Paths.get(System.getProperty(validKey));
241         assertEquals(value, PathUtils.getPath(validKey, null));
242         assertEquals(value, PathUtils.getPath(validKey, validKey));
243         final String invalidKey = "this property key does not exist";
244         assertEquals(value, PathUtils.getPath(invalidKey, value.toString()));
245         assertNull(PathUtils.getPath(invalidKey, null));
246         assertEquals(value, PathUtils.getPath(null, value.toString()));
247         assertEquals(value, PathUtils.getPath("", value.toString()));
248     }
249 
250     @Test
251     void testGetTempDirectory() {
252         final Path tempDirectory = Paths.get(SystemProperties.getJavaIoTmpdir());
253         assertEquals(tempDirectory, PathUtils.getTempDirectory());
254     }
255 
256     @Test
257     void testIsDirectory() throws IOException {
258         assertFalse(PathUtils.isDirectory(null));
259 
260         assertTrue(PathUtils.isDirectory(tempDirPath));
261         try (TempFile testFile1 = TempFile.create(tempDirPath, "prefix", null)) {
262             assertFalse(PathUtils.isDirectory(testFile1.get()));
263 
264             Path ref = null;
265             try (TempDirectory tempDir = TempDirectory.create(getClass().getCanonicalName())) {
266                 ref = tempDir.get();
267                 assertTrue(PathUtils.isDirectory(tempDir.get()));
268             }
269             assertFalse(PathUtils.isDirectory(ref));
270         }
271     }
272 
273     @Test
274     void testIsPosix() throws IOException {
275         boolean isPosix;
276         try {
277             Files.getPosixFilePermissions(current());
278             isPosix = true;
279         } catch (final UnsupportedOperationException e) {
280             isPosix = false;
281         }
282         assertEquals(isPosix, PathUtils.isPosix(current()));
283     }
284 
285     @Test
286     void testIsPosixAbsentFile() {
287         assertFalse(PathUtils.isPosix(Paths.get("ImNotHereAtAllEver.never")));
288         assertFalse(PathUtils.isPosix(null));
289     }
290 
291     @Test
292     void testIsRegularFile() throws IOException {
293         assertFalse(PathUtils.isRegularFile(null));
294 
295         assertFalse(PathUtils.isRegularFile(tempDirPath));
296         try (TempFile testFile1 = TempFile.create(tempDirPath, "prefix", null)) {
297             assertTrue(PathUtils.isRegularFile(testFile1.get()));
298 
299             Files.delete(testFile1.get());
300             assertFalse(PathUtils.isRegularFile(testFile1.get()));
301         }
302     }
303 
304     @Test
305     void testNewDirectoryStream() throws Exception {
306         final PathFilter pathFilter = new NameFileFilter(PATH_FIXTURE);
307         try (DirectoryStream<Path> stream = PathUtils.newDirectoryStream(current(), pathFilter)) {
308             final Iterator<Path> iterator = stream.iterator();
309             final Path path = iterator.next();
310             assertEquals(PATH_FIXTURE, PathUtils.getFileNameString(path));
311             assertFalse(iterator.hasNext());
312         }
313     }
314 
315     @Test
316     void testNewOutputStreamExistingFileAppendFalse() throws IOException {
317         testNewOutputStreamNewFile(false);
318         testNewOutputStreamNewFile(false);
319     }
320 
321     @Test
322     void testNewOutputStreamExistingFileAppendTrue() throws IOException {
323         testNewOutputStreamNewFile(true);
324         final Path file = writeToNewOutputStream(true);
325         assertArrayEquals(ArrayUtils.addAll(BYTE_ARRAY_FIXTURE, BYTE_ARRAY_FIXTURE), Files.readAllBytes(file));
326     }
327 
328     void testNewOutputStreamNewFile(final boolean append) throws IOException {
329         final Path file = writeToNewOutputStream(append);
330         assertArrayEquals(BYTE_ARRAY_FIXTURE, Files.readAllBytes(file));
331     }
332 
333     @Test
334     void testNewOutputStreamNewFileAppendFalse() throws IOException {
335         testNewOutputStreamNewFile(false);
336     }
337 
338     @Test
339     void testNewOutputStreamNewFileAppendTrue() throws IOException {
340         testNewOutputStreamNewFile(true);
341     }
342 
343     @Test
344     void testNewOutputStreamNewFileInsideExistingSymlinkedDir() throws IOException {
345         final Path symlinkDir = createTempSymbolicLinkedRelativeDir(tempDirPath);
346         final Path file = symlinkDir.resolve("test.txt");
347         try (OutputStream outputStream = PathUtils.newOutputStream(file, new LinkOption[] {})) {
348             // empty
349         }
350         try (OutputStream outputStream = PathUtils.newOutputStream(file, null)) {
351             // empty
352         }
353         try (OutputStream outputStream = PathUtils.newOutputStream(file, true)) {
354             // empty
355         }
356         try (OutputStream outputStream = PathUtils.newOutputStream(file, false)) {
357             // empty
358         }
359     }
360 
361     @Test
362     void testReadAttributesPosix() throws IOException {
363         boolean isPosix;
364         try {
365             Files.getPosixFilePermissions(current());
366             isPosix = true;
367         } catch (final UnsupportedOperationException e) {
368             isPosix = false;
369         }
370         assertEquals(isPosix, PathUtils.readAttributes(current(), PosixFileAttributes.class) != null);
371     }
372 
373     @Test
374     void testReadStringEmptyFile() throws IOException {
375         final Path path = Paths.get("src/test/resources/org/apache/commons/io/test-file-empty.bin");
376         assertEquals(StringUtils.EMPTY, PathUtils.readString(path, StandardCharsets.UTF_8));
377         assertEquals(StringUtils.EMPTY, PathUtils.readString(path, null));
378     }
379 
380     @Test
381     void testReadStringSimpleUtf8() throws IOException {
382         final Path path = Paths.get("src/test/resources/org/apache/commons/io/test-file-simple-utf8.bin");
383         final String expected = "ABC\r\n";
384         assertEquals(expected, PathUtils.readString(path, StandardCharsets.UTF_8));
385         assertEquals(expected, PathUtils.readString(path, null));
386     }
387 
388     @Test
389     void testSetReadOnlyFile() throws IOException {
390         final Path resolved = tempDirPath.resolve("testSetReadOnlyFile.txt");
391         // Ask now, as we are allowed before editing parent permissions.
392         final boolean isPosix = PathUtils.isPosix(tempDirPath);
393 
394         // TEMP HACK
395         assumeFalse(SystemUtils.IS_OS_LINUX);
396 
397         PathUtils.writeString(resolved, "test", StandardCharsets.UTF_8);
398         final boolean readable = Files.isReadable(resolved);
399         final boolean writable = Files.isWritable(resolved);
400         final boolean regularFile = Files.isRegularFile(resolved);
401         final boolean executable = Files.isExecutable(resolved);
402         final boolean hidden = Files.isHidden(resolved);
403         final boolean directory = Files.isDirectory(resolved);
404         final boolean symbolicLink = Files.isSymbolicLink(resolved);
405         // Sanity checks
406         assertTrue(readable);
407         assertTrue(writable);
408         // Test A
409         PathUtils.setReadOnly(resolved, false);
410         assertTrue(Files.isReadable(resolved), "isReadable");
411         assertTrue(Files.isWritable(resolved), "isWritable");
412         // Again, shouldn't blow up.
413         PathUtils.setReadOnly(resolved, false);
414         assertTrue(Files.isReadable(resolved), "isReadable");
415         assertTrue(Files.isWritable(resolved), "isWritable");
416         //
417         assertEquals(regularFile, Files.isReadable(resolved));
418         assertEquals(executable, Files.isExecutable(resolved));
419         assertEquals(hidden, Files.isHidden(resolved));
420         assertEquals(directory, Files.isDirectory(resolved));
421         assertEquals(symbolicLink, Files.isSymbolicLink(resolved));
422         // Test B
423         PathUtils.setReadOnly(resolved, true);
424         if (isPosix) {
425             // On POSIX, now that the parent is not WX, the file is not readable.
426             assertFalse(Files.isReadable(resolved), "isReadable");
427         } else {
428             assertTrue(Files.isReadable(resolved), "isReadable");
429         }
430         assertFalse(Files.isWritable(resolved), "isWritable");
431         final DosFileAttributeView dosFileAttributeView = PathUtils.getDosFileAttributeView(resolved);
432         if (dosFileAttributeView != null) {
433             assertTrue(dosFileAttributeView.readAttributes().isReadOnly());
434         }
435         if (isPosix) {
436             assertFalse(Files.isReadable(resolved));
437         } else {
438             assertEquals(regularFile, Files.isReadable(resolved));
439         }
440         assertEquals(executable, Files.isExecutable(resolved));
441         assertEquals(hidden, Files.isHidden(resolved));
442         assertEquals(directory, Files.isDirectory(resolved));
443         assertEquals(symbolicLink, Files.isSymbolicLink(resolved));
444         //
445         PathUtils.setReadOnly(resolved, false);
446         PathUtils.deleteFile(resolved);
447     }
448 
449     @Test
450     void testSetReadOnlyFileAbsent() {
451         assertThrows(IOException.class, () -> PathUtils.setReadOnly(Paths.get("does-not-exist-at-all-ever-never"), true));
452     }
453 
454     @Test
455     void testTouch() throws IOException {
456         assertThrows(NullPointerException.class, () -> FileUtils.touch(null));
457 
458         final Path file = managedTempDirPath.resolve("touch.txt");
459         Files.deleteIfExists(file);
460         assertFalse(Files.exists(file), "Bad test: test file still exists");
461         PathUtils.touch(file);
462         assertTrue(Files.exists(file), "touch() created file");
463         try (OutputStream out = Files.newOutputStream(file)) {
464             assertEquals(0, Files.size(file), "Created empty file.");
465             out.write(0);
466         }
467         assertEquals(1, Files.size(file), "Wrote one byte to file");
468         final long y2k = new GregorianCalendar(2000, 0, 1).getTime().getTime();
469         setLastModifiedMillis(file, y2k); // 0L fails on Win98
470         assertEquals(y2k, getLastModifiedMillis(file), "Bad test: set lastModified set incorrect value");
471         final long nowMillis = System.currentTimeMillis();
472         PathUtils.touch(file);
473         assertEquals(1, Files.size(file), "FileUtils.touch() didn't empty the file.");
474         assertNotEquals(y2k, getLastModifiedMillis(file), "FileUtils.touch() changed lastModified");
475         final int delta = 3000;
476         assertTrue(getLastModifiedMillis(file) >= nowMillis - delta, "FileUtils.touch() changed lastModified to more than now-3s");
477         assertTrue(getLastModifiedMillis(file) <= nowMillis + delta, "FileUtils.touch() changed lastModified to less than now+3s");
478     }
479 
480     @Test
481     void testWriteStringToFile1() throws Exception {
482         final Path file = tempDirPath.resolve("write.txt");
483         PathUtils.writeString(file, "Hello \u1234", StandardCharsets.UTF_8);
484         final byte[] text = "Hello \u1234".getBytes(StandardCharsets.UTF_8);
485         TestUtils.assertEqualContent(text, file);
486     }
487 
488     /**
489      * Tests newOutputStream() here and don't use Files.write obviously.
490      */
491     private Path writeToNewOutputStream(final boolean append) throws IOException {
492         final Path file = tempDirPath.resolve("test1.txt");
493         try (OutputStream os = PathUtils.newOutputStream(file, append)) {
494             os.write(BYTE_ARRAY_FIXTURE);
495         }
496         return file;
497     }
498 
499 }