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 java.io.File;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.io.RandomAccessFile;
25 import java.math.BigInteger;
26 import java.net.URI;
27 import java.net.URISyntaxException;
28 import java.net.URL;
29 import java.nio.charset.Charset;
30 import java.nio.file.AccessDeniedException;
31 import java.nio.file.CopyOption;
32 import java.nio.file.DirectoryStream;
33 import java.nio.file.FileSystem;
34 import java.nio.file.FileVisitOption;
35 import java.nio.file.FileVisitResult;
36 import java.nio.file.FileVisitor;
37 import java.nio.file.Files;
38 import java.nio.file.LinkOption;
39 import java.nio.file.NoSuchFileException;
40 import java.nio.file.NotDirectoryException;
41 import java.nio.file.OpenOption;
42 import java.nio.file.Path;
43 import java.nio.file.Paths;
44 import java.nio.file.StandardOpenOption;
45 import java.nio.file.attribute.AclEntry;
46 import java.nio.file.attribute.AclFileAttributeView;
47 import java.nio.file.attribute.BasicFileAttributes;
48 import java.nio.file.attribute.DosFileAttributeView;
49 import java.nio.file.attribute.DosFileAttributes;
50 import java.nio.file.attribute.FileAttribute;
51 import java.nio.file.attribute.FileTime;
52 import java.nio.file.attribute.PosixFileAttributeView;
53 import java.nio.file.attribute.PosixFileAttributes;
54 import java.nio.file.attribute.PosixFilePermission;
55 import java.time.Duration;
56 import java.time.Instant;
57 import java.time.chrono.ChronoZonedDateTime;
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.Collection;
61 import java.util.Collections;
62 import java.util.Comparator;
63 import java.util.EnumSet;
64 import java.util.HashSet;
65 import java.util.Iterator;
66 import java.util.List;
67 import java.util.Objects;
68 import java.util.Set;
69 import java.util.function.Function;
70 import java.util.stream.Collector;
71 import java.util.stream.Collectors;
72 import java.util.stream.Stream;
73 import java.util.stream.StreamSupport;
74
75 import org.apache.commons.io.Charsets;
76 import org.apache.commons.io.FileUtils;
77 import org.apache.commons.io.FilenameUtils;
78 import org.apache.commons.io.IOUtils;
79 import org.apache.commons.io.RandomAccessFileMode;
80 import org.apache.commons.io.RandomAccessFiles;
81 import org.apache.commons.io.ThreadUtils;
82 import org.apache.commons.io.file.Counters.PathCounters;
83 import org.apache.commons.io.file.attribute.FileTimes;
84 import org.apache.commons.io.filefilter.IOFileFilter;
85 import org.apache.commons.io.function.IOFunction;
86 import org.apache.commons.io.function.IOSupplier;
87
88 /**
89 * NIO Path utilities.
90 *
91 * @since 2.7
92 */
93 public final class PathUtils {
94
95 /**
96 * Private worker/holder that computes and tracks relative path names and their equality. We reuse the sorted relative lists when comparing directories.
97 */
98 private static final class RelativeSortedPaths {
99
100 /**
101 * Compares lists of paths regardless of their file systems.
102 *
103 * @param list1 the first list.
104 * @param list2 the second list.
105 * @return whether the lists are equal.
106 */
107 private static boolean equalsIgnoreFileSystem(final List<Path> list1, final List<Path> list2) {
108 if (list1.size() != list2.size()) {
109 return false;
110 }
111 // compare both lists using iterators
112 final Iterator<Path> iterator1 = list1.iterator();
113 final Iterator<Path> iterator2 = list2.iterator();
114 while (iterator1.hasNext() && iterator2.hasNext()) {
115 if (!equalsIgnoreFileSystem(iterator1.next(), iterator2.next())) {
116 return false;
117 }
118 }
119 return true;
120 }
121
122 private static boolean equalsIgnoreFileSystem(final Path path1, final Path path2) {
123 final FileSystem fileSystem1 = path1.getFileSystem();
124 final FileSystem fileSystem2 = path2.getFileSystem();
125 if (fileSystem1 == fileSystem2) {
126 return path1.equals(path2);
127 }
128 final String separator1 = fileSystem1.getSeparator();
129 final String separator2 = fileSystem2.getSeparator();
130 final String string1 = path1.toString();
131 final String string2 = path2.toString();
132 if (Objects.equals(separator1, separator2)) {
133 // Separators are the same, so we can use toString comparison
134 return Objects.equals(string1, string2);
135 }
136 // Compare paths from different file systems component by component.
137 return extractKey(separator1, string1).equals(extractKey(separator2, string2));
138 }
139
140 /**
141 * Replaces the file separator in a path string with a string that is not legal in a path on Windows, Linux, and macOS.
142 *
143 * @param separator the file separator.
144 * @param string a path.
145 * @return a key.
146 */
147 static String extractKey(final String separator, final String string) {
148 return string.replace(separator, ">");
149 }
150
151 final boolean equals;
152 // final List<Path> relativeDirList1; // might need later?
153 // final List<Path> relativeDirList2; // might need later?
154 final List<Path> relativeFileList1;
155 final List<Path> relativeFileList2;
156
157 /**
158 * Constructs and initializes a new instance by accumulating directory and file info.
159 *
160 * @param dir1 First directory to compare.
161 * @param dir2 Seconds directory to compare.
162 * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
163 * @param linkOptions Options indicating how symbolic links are handled.
164 * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
165 * @throws IOException if an I/O error is thrown by a visitor method.
166 */
167 private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth, final LinkOption[] linkOptions,
168 final FileVisitOption[] fileVisitOptions) throws IOException {
169 final List<Path> tmpRelativeDirList1;
170 final List<Path> tmpRelativeDirList2;
171 List<Path> tmpRelativeFileList1 = null;
172 List<Path> tmpRelativeFileList2 = null;
173 if (dir1 == null && dir2 == null) {
174 equals = true;
175 } else if (dir1 == null ^ dir2 == null) {
176 equals = false;
177 } else {
178 final boolean parentDirNotExists1 = Files.notExists(dir1, linkOptions);
179 final boolean parentDirNotExists2 = Files.notExists(dir2, linkOptions);
180 if (parentDirNotExists1 || parentDirNotExists2) {
181 equals = parentDirNotExists1 && parentDirNotExists2;
182 } else {
183 final AccumulatorPathVisitor visitor1 = accumulate(dir1, maxDepth, fileVisitOptions);
184 final AccumulatorPathVisitor visitor2 = accumulate(dir2, maxDepth, fileVisitOptions);
185 if (visitor1.getDirList().size() != visitor2.getDirList().size() || visitor1.getFileList().size() != visitor2.getFileList().size()) {
186 equals = false;
187 } else {
188 tmpRelativeDirList1 = visitor1.relativizeDirectories(dir1, true, null);
189 tmpRelativeDirList2 = visitor2.relativizeDirectories(dir2, true, null);
190 if (!equalsIgnoreFileSystem(tmpRelativeDirList1, tmpRelativeDirList2)) {
191 equals = false;
192 } else {
193 tmpRelativeFileList1 = visitor1.relativizeFiles(dir1, true, null);
194 tmpRelativeFileList2 = visitor2.relativizeFiles(dir2, true, null);
195 equals = equalsIgnoreFileSystem(tmpRelativeFileList1, tmpRelativeFileList2);
196 }
197 }
198 }
199 }
200 // relativeDirList1 = tmpRelativeDirList1;
201 // relativeDirList2 = tmpRelativeDirList2;
202 relativeFileList1 = tmpRelativeFileList1;
203 relativeFileList2 = tmpRelativeFileList2;
204 }
205 }
206
207 private static final OpenOption[] OPEN_OPTIONS_TRUNCATE = { StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING };
208 private static final OpenOption[] OPEN_OPTIONS_APPEND = { StandardOpenOption.CREATE, StandardOpenOption.APPEND };
209
210 /**
211 * Empty {@link CopyOption} array.
212 *
213 * @since 2.8.0
214 */
215 public static final CopyOption[] EMPTY_COPY_OPTIONS = {};
216
217 /**
218 * Empty {@link DeleteOption} array.
219 *
220 * @since 2.8.0
221 */
222 public static final DeleteOption[] EMPTY_DELETE_OPTION_ARRAY = {};
223
224 /**
225 * Empty {@link FileAttribute} array.
226 *
227 * @since 2.13.0
228 */
229 public static final FileAttribute<?>[] EMPTY_FILE_ATTRIBUTE_ARRAY = {};
230
231 /**
232 * Empty {@link FileVisitOption} array.
233 */
234 public static final FileVisitOption[] EMPTY_FILE_VISIT_OPTION_ARRAY = {};
235
236 /**
237 * Empty {@link LinkOption} array.
238 */
239 public static final LinkOption[] EMPTY_LINK_OPTION_ARRAY = {};
240
241 /**
242 * {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}.
243 *
244 * @since 2.9.0
245 * @deprecated Use {@link #noFollowLinkOptionArray()}.
246 */
247 @Deprecated
248 public static final LinkOption[] NOFOLLOW_LINK_OPTION_ARRAY = { LinkOption.NOFOLLOW_LINKS };
249
250 /**
251 * A LinkOption used to follow link in this class, the inverse of {@link LinkOption#NOFOLLOW_LINKS}.
252 *
253 * @since 2.12.0
254 */
255 static final LinkOption NULL_LINK_OPTION = null;
256
257 /**
258 * Empty {@link OpenOption} array.
259 */
260 public static final OpenOption[] EMPTY_OPEN_OPTION_ARRAY = {};
261
262 /**
263 * Empty {@link Path} array.
264 *
265 * @since 2.9.0
266 */
267 public static final Path[] EMPTY_PATH_ARRAY = {};
268
269 /**
270 * Accumulates file tree information in a {@link AccumulatorPathVisitor}.
271 *
272 * @param directory The directory to accumulate information.
273 * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
274 * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
275 * @throws IOException if an I/O error is thrown by a visitor method.
276 * @return file tree information.
277 */
278 private static AccumulatorPathVisitor accumulate(final Path directory, final int maxDepth, final FileVisitOption[] fileVisitOptions) throws IOException {
279 return visitFileTree(AccumulatorPathVisitor.builder().setDirectoryPostTransformer(PathUtils::stripTrailingSeparator).get(), directory,
280 toFileVisitOptionSet(fileVisitOptions), maxDepth);
281 }
282
283 /**
284 * Cleans a directory by only deleting files, including in subdirectories, without deleting directories.
285 * <p>
286 * This leaves a directory empty of files but the directory and any subdirectories remain.
287 * </p>
288 *
289 * @param directory directory to clean.
290 * @return The visitation path counters.
291 * @throws IOException if an I/O error is thrown by a visitor method.
292 */
293 public static PathCounters cleanDirectory(final Path directory) throws IOException {
294 return cleanDirectory(directory, EMPTY_DELETE_OPTION_ARRAY);
295 }
296
297 /**
298 * Cleans a directory by only deleting files, including in subdirectories, without deleting directories.
299 * <p>
300 * This leaves a directory empty of files but the directory and any subdirectories remain.
301 * </p>
302 *
303 * @param directory directory to clean.
304 * @param deleteOptions How to handle deletion.
305 * @return The visitation path counters.
306 * @throws IOException if an I/O error is thrown by a visitor method.
307 * @since 2.8.0
308 */
309 public static PathCounters cleanDirectory(final Path directory, final DeleteOption... deleteOptions) throws IOException {
310 return visitFileTree(new CleaningPathVisitor(Counters.longPathCounters(), deleteOptions), directory).getPathCounters();
311 }
312
313 /**
314 * Compares the given {@link Path}'s last modified time to the given file time.
315 *
316 * @param file the {@link Path} to test.
317 * @param fileTime the time reference.
318 * @param options options indicating how to handle symbolic links.
319 * @return See {@link FileTime#compareTo(FileTime)}
320 * @throws IOException if an I/O error occurs.
321 * @throws NullPointerException if the file is {@code null}.
322 */
323 private static int compareLastModifiedTimeTo(final Path file, final FileTime fileTime, final LinkOption... options) throws IOException {
324 return getLastModifiedTime(file, options).compareTo(fileTime);
325 }
326
327 /**
328 * Compares the files of two FileSystems to determine if they are equal or not while considering file contents. The comparison includes all files in all
329 * subdirectories.
330 * <p>
331 * For example, to compare two ZIP files:
332 * </p>
333 *
334 * <pre>
335 * final Path zipPath1 = Paths.get("file1.zip");
336 * final Path zipPath2 = Paths.get("file2.zip");
337 * try (FileSystem fileSystem1 = FileSystems.newFileSystem(zipPath1, null); FileSystem fileSystem2 = FileSystems.newFileSystem(zipPath2, null)) {
338 * assertTrue(PathUtils.directoryAndFileContentEquals(dir1, dir2));
339 * }
340 * </pre>
341 *
342 * @param fileSystem1 The first FileSystem.
343 * @param fileSystem2 The second FileSystem.
344 * @return Whether the two FileSystem contain the same files while considering file contents.
345 * @throws IOException if an I/O error is thrown by a visitor method.
346 * @since 2.19.0
347 */
348 public static boolean contentEquals(final FileSystem fileSystem1, final FileSystem fileSystem2) throws IOException {
349 if (Objects.equals(fileSystem1, fileSystem2)) {
350 return true;
351 }
352 final List<Path> sortedList1 = toSortedList(fileSystem1.getRootDirectories());
353 final List<Path> sortedList2 = toSortedList(fileSystem2.getRootDirectories());
354 if (sortedList1.size() != sortedList2.size()) {
355 return false;
356 }
357 for (int i = 0; i < sortedList1.size(); i++) {
358 if (!directoryAndFileContentEquals(sortedList1.get(i), sortedList2.get(i))) {
359 return false;
360 }
361 }
362 return true;
363 }
364
365 /**
366 * Copies the InputStream from the supplier with {@link Files#copy(InputStream, Path, CopyOption...)}.
367 *
368 * @param in Supplies the InputStream.
369 * @param target See {@link Files#copy(InputStream, Path, CopyOption...)}.
370 * @param copyOptions See {@link Files#copy(InputStream, Path, CopyOption...)}.
371 * @return See {@link Files#copy(InputStream, Path, CopyOption...)}
372 * @throws IOException See {@link Files#copy(InputStream, Path, CopyOption...)}
373 * @since 2.12.0
374 */
375 public static long copy(final IOSupplier<InputStream> in, final Path target, final CopyOption... copyOptions) throws IOException {
376 try (InputStream inputStream = in.get()) {
377 return Files.copy(inputStream, target, copyOptions);
378 }
379 }
380
381 /**
382 * Copies a directory to another directory.
383 *
384 * @param sourceDirectory The source directory.
385 * @param targetDirectory The target directory.
386 * @param copyOptions Specifies how the copying should be done.
387 * @return The visitation path counters.
388 * @throws IOException if an I/O error is thrown by a visitor method.
389 */
390 public static PathCounters copyDirectory(final Path sourceDirectory, final Path targetDirectory, final CopyOption... copyOptions) throws IOException {
391 final Path absoluteSource = sourceDirectory.toAbsolutePath();
392 return visitFileTree(new CopyDirectoryVisitor(Counters.longPathCounters(), absoluteSource, targetDirectory, copyOptions), absoluteSource)
393 .getPathCounters();
394 }
395
396 /**
397 * Copies a URL to a directory.
398 *
399 * @param sourceFile The source URL.
400 * @param targetFile The target file.
401 * @param copyOptions Specifies how the copying should be done.
402 * @return The target file.
403 * @throws IOException if an I/O error occurs.
404 * @see Files#copy(InputStream, Path, CopyOption...)
405 */
406 public static Path copyFile(final URL sourceFile, final Path targetFile, final CopyOption... copyOptions) throws IOException {
407 copy(sourceFile::openStream, targetFile, copyOptions);
408 return targetFile;
409 }
410
411 /**
412 * Copies a file to a directory.
413 *
414 * @param sourceFile The source file.
415 * @param targetDirectory The target directory.
416 * @param copyOptions Specifies how the copying should be done.
417 * @return The target file.
418 * @throws IOException if an I/O error occurs.
419 * @see Files#copy(Path, Path, CopyOption...)
420 */
421 public static Path copyFileToDirectory(final Path sourceFile, final Path targetDirectory, final CopyOption... copyOptions) throws IOException {
422 // Path.resolve() naturally won't work across FileSystem unless we convert to a String
423 final Path sourceFileName = Objects.requireNonNull(sourceFile.getFileName(), "source file name");
424 final Path targetFile = resolve(targetDirectory, sourceFileName);
425 return Files.copy(sourceFile, targetFile, copyOptions);
426 }
427
428 /**
429 * Copies a URL to a directory.
430 *
431 * @param sourceFile The source URL.
432 * @param targetDirectory The target directory.
433 * @param copyOptions Specifies how the copying should be done.
434 * @return The target file.
435 * @throws IOException if an I/O error occurs.
436 * @see Files#copy(InputStream, Path, CopyOption...)
437 */
438 public static Path copyFileToDirectory(final URL sourceFile, final Path targetDirectory, final CopyOption... copyOptions) throws IOException {
439 final Path resolve = targetDirectory.resolve(FilenameUtils.getName(sourceFile.getFile()));
440 copy(sourceFile::openStream, resolve, copyOptions);
441 return resolve;
442 }
443
444 /**
445 * Counts aspects of a directory including subdirectories.
446 *
447 * @param directory directory to delete.
448 * @return The visitor used to count the given directory.
449 * @throws IOException if an I/O error is thrown by a visitor method.
450 */
451 public static PathCounters countDirectory(final Path directory) throws IOException {
452 return visitFileTree(CountingPathVisitor.withLongCounters(), directory).getPathCounters();
453 }
454
455 /**
456 * Counts aspects of a directory including subdirectories.
457 *
458 * @param directory directory to count.
459 * @return The visitor used to count the given directory.
460 * @throws IOException if an I/O error occurs.
461 * @since 2.12.0
462 */
463 public static PathCounters countDirectoryAsBigInteger(final Path directory) throws IOException {
464 return visitFileTree(CountingPathVisitor.withBigIntegerCounters(), directory).getPathCounters();
465 }
466
467 /**
468 * Creates the parent directories for the given {@code path}.
469 * <p>
470 * If the parent directory already exists, then return it.
471 * </p>
472 *
473 * @param path The path to a file (or directory).
474 * @param attrs An optional list of file attributes to set atomically when creating the directories.
475 * @return The Path for the {@code path}'s parent directory or null if the given path has no parent.
476 * @throws IOException if an I/O error occurs.
477 * @since 2.9.0
478 */
479 public static Path createParentDirectories(final Path path, final FileAttribute<?>... attrs) throws IOException {
480 return createParentDirectories(path, LinkOption.NOFOLLOW_LINKS, attrs);
481 }
482
483 /**
484 * Creates the parent directories for the given {@code path}.
485 * <p>
486 * If the parent directory already exists, then return it.
487 * </p>
488 *
489 * @param path The path to a file (or directory).
490 * @param linkOption A {@link LinkOption} or null.
491 * @param attrs An optional list of file attributes to set atomically when creating the directories.
492 * @return The Path for the {@code path}'s parent directory or null if the given path has no parent.
493 * @throws IOException if an I/O error occurs.
494 * @since 2.12.0
495 */
496 public static Path createParentDirectories(final Path path, final LinkOption linkOption, final FileAttribute<?>... attrs) throws IOException {
497 Path parent = getParent(path);
498 parent = linkOption == LinkOption.NOFOLLOW_LINKS ? parent : readIfSymbolicLink(parent);
499 if (parent == null) {
500 return null;
501 }
502 final boolean exists = linkOption == null ? Files.exists(parent) : Files.exists(parent, linkOption);
503 return exists ? parent : Files.createDirectories(parent, attrs);
504 }
505
506 /**
507 * Gets the current directory.
508 *
509 * @return the current directory.
510 * @since 2.9.0
511 */
512 public static Path current() {
513 return Paths.get(".");
514 }
515
516 /**
517 * Deletes a file or directory. If the path is a directory, delete it and all subdirectories.
518 * <p>
519 * The difference between {@link File#delete()} and this method are:
520 * </p>
521 * <ul>
522 * <li>A directory to delete does not have to be empty.</li>
523 * <li>You get exceptions when a file or directory cannot be deleted; {@link File#delete()} returns a boolean.</li>
524 * </ul>
525 *
526 * @param path file or directory to delete, must not be {@code null}
527 * @return The visitor used to delete the given directory.
528 * @throws NullPointerException if the directory is {@code null}
529 * @throws IOException if an I/O error is thrown by a visitor method or if an I/O error occurs.
530 */
531 public static PathCounters delete(final Path path) throws IOException {
532 return delete(path, EMPTY_DELETE_OPTION_ARRAY);
533 }
534
535 /**
536 * Deletes a file or directory. If the path is a directory, delete it and all subdirectories.
537 * <p>
538 * The difference between File.delete() and this method are:
539 * </p>
540 * <ul>
541 * <li>A directory to delete does not have to be empty.</li>
542 * <li>You get exceptions when a file or directory cannot be deleted; {@link File#delete()} returns a boolean.</li>
543 * </ul>
544 *
545 * @param path file or directory to delete, must not be {@code null}
546 * @param deleteOptions How to handle deletion.
547 * @return The visitor used to delete the given directory.
548 * @throws NullPointerException if the directory is {@code null}
549 * @throws IOException if an I/O error is thrown by a visitor method or if an I/O error occurs.
550 * @since 2.8.0
551 */
552 public static PathCounters delete(final Path path, final DeleteOption... deleteOptions) throws IOException {
553 // File deletion through Files deletes links, not targets, so use LinkOption.NOFOLLOW_LINKS.
554 return Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS) ? deleteDirectory(path, deleteOptions) : deleteFile(path, deleteOptions);
555 }
556
557 /**
558 * Deletes a file or directory. If the path is a directory, delete it and all subdirectories.
559 * <p>
560 * The difference between File.delete() and this method are:
561 * </p>
562 * <ul>
563 * <li>A directory to delete does not have to be empty.</li>
564 * <li>You get exceptions when a file or directory cannot be deleted; {@link File#delete()} returns a boolean.</li>
565 * </ul>
566 *
567 * @param path file or directory to delete, must not be {@code null}
568 * @param linkOptions How to handle symbolic links.
569 * @param deleteOptions How to handle deletion.
570 * @return The visitor used to delete the given directory.
571 * @throws NullPointerException if the directory is {@code null}
572 * @throws IOException if an I/O error is thrown by a visitor method or if an I/O error occurs.
573 * @since 2.9.0
574 */
575 public static PathCounters delete(final Path path, final LinkOption[] linkOptions, final DeleteOption... deleteOptions) throws IOException {
576 // File deletion through Files deletes links, not targets, so use LinkOption.NOFOLLOW_LINKS.
577 return Files.isDirectory(path, linkOptions) ? deleteDirectory(path, linkOptions, deleteOptions) : deleteFile(path, linkOptions, deleteOptions);
578 }
579
580 /**
581 * Deletes a directory including subdirectories.
582 *
583 * @param directory directory to delete.
584 * @return The visitor used to delete the given directory.
585 * @throws IOException if an I/O error is thrown by a visitor method.
586 */
587 public static PathCounters deleteDirectory(final Path directory) throws IOException {
588 return deleteDirectory(directory, EMPTY_DELETE_OPTION_ARRAY);
589 }
590
591 /**
592 * Deletes a directory including subdirectories.
593 *
594 * @param directory directory to delete.
595 * @param deleteOptions How to handle deletion.
596 * @return The visitor used to delete the given directory.
597 * @throws IOException if an I/O error is thrown by a visitor method.
598 * @since 2.8.0
599 */
600 public static PathCounters deleteDirectory(final Path directory, final DeleteOption... deleteOptions) throws IOException {
601 final LinkOption[] linkOptions = noFollowLinkOptionArray();
602 // POSIX ops will noop on non-POSIX.
603 return withPosixFileAttributes(getParent(directory), linkOptions, overrideReadOnly(deleteOptions),
604 pfa -> visitFileTree(new DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions), directory).getPathCounters());
605 }
606
607 /**
608 * Deletes a directory including subdirectories.
609 *
610 * @param directory directory to delete.
611 * @param linkOptions How to handle symbolic links.
612 * @param deleteOptions How to handle deletion.
613 * @return The visitor used to delete the given directory.
614 * @throws IOException if an I/O error is thrown by a visitor method.
615 * @since 2.9.0
616 */
617 public static PathCounters deleteDirectory(final Path directory, final LinkOption[] linkOptions, final DeleteOption... deleteOptions) throws IOException {
618 return visitFileTree(new DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions), directory).getPathCounters();
619 }
620
621 /**
622 * Deletes the given file.
623 *
624 * @param file The file to delete.
625 * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
626 * @throws IOException if an I/O error occurs.
627 * @throws NoSuchFileException if the file is a directory.
628 */
629 public static PathCounters deleteFile(final Path file) throws IOException {
630 return deleteFile(file, EMPTY_DELETE_OPTION_ARRAY);
631 }
632
633 /**
634 * Deletes the given file.
635 *
636 * @param file The file to delete.
637 * @param deleteOptions How to handle deletion.
638 * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
639 * @throws IOException if an I/O error occurs.
640 * @throws NoSuchFileException if the file is a directory.
641 * @since 2.8.0
642 */
643 public static PathCounters deleteFile(final Path file, final DeleteOption... deleteOptions) throws IOException {
644 // Files.deleteIfExists() never follows links, so use LinkOption.NOFOLLOW_LINKS in other calls to Files.
645 return deleteFile(file, noFollowLinkOptionArray(), deleteOptions);
646 }
647
648 /**
649 * Deletes the given file.
650 *
651 * @param file The file to delete.
652 * @param linkOptions How to handle symbolic links.
653 * @param deleteOptions How to handle deletion.
654 * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file.
655 * @throws IOException if an I/O error occurs.
656 * @throws NoSuchFileException if the file is a directory.
657 * @since 2.9.0
658 */
659 public static PathCounters deleteFile(final Path file, final LinkOption[] linkOptions, final DeleteOption... deleteOptions)
660 throws NoSuchFileException, IOException {
661 //
662 // TODO Needs clean up?
663 //
664 if (Files.isDirectory(file, linkOptions)) {
665 throw new NoSuchFileException(file.toString());
666 }
667 final PathCounters pathCounts = Counters.longPathCounters();
668 boolean exists = exists(file, linkOptions);
669 long size = exists && !Files.isSymbolicLink(file) ? Files.size(file) : 0;
670 try {
671 if (Files.deleteIfExists(file)) {
672 pathCounts.getFileCounter().increment();
673 pathCounts.getByteCounter().add(size);
674 return pathCounts;
675 }
676 } catch (final AccessDeniedException ignored) {
677 // Ignore and try again below.
678 }
679 final Path parent = getParent(file);
680 PosixFileAttributes posixFileAttributes = null;
681 try {
682 if (overrideReadOnly(deleteOptions)) {
683 posixFileAttributes = readPosixFileAttributes(parent, linkOptions);
684 setReadOnly(file, false, linkOptions);
685 }
686 // Read size _after_ having read/execute access on POSIX.
687 exists = exists(file, linkOptions);
688 size = exists && !Files.isSymbolicLink(file) ? Files.size(file) : 0;
689 if (Files.deleteIfExists(file)) {
690 pathCounts.getFileCounter().increment();
691 pathCounts.getByteCounter().add(size);
692 }
693 } finally {
694 if (posixFileAttributes != null) {
695 Files.setPosixFilePermissions(parent, posixFileAttributes.permissions());
696 }
697 }
698 return pathCounts;
699 }
700
701 /**
702 * Delegates to {@link File#deleteOnExit()}.
703 *
704 * @param path the path to delete.
705 * @since 3.13.0
706 */
707 public static void deleteOnExit(final Path path) {
708 Objects.requireNonNull(path).toFile().deleteOnExit();
709 }
710
711 /**
712 * Compares the files of two Paths to determine if they are equal or not while considering file contents. The comparison includes all files in all
713 * subdirectories.
714 *
715 * @param path1 The first directory.
716 * @param path2 The second directory.
717 * @return Whether the two directories contain the same files while considering file contents.
718 * @throws IOException if an I/O error is thrown by a visitor method.
719 */
720 public static boolean directoryAndFileContentEquals(final Path path1, final Path path2) throws IOException {
721 return directoryAndFileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY, EMPTY_FILE_VISIT_OPTION_ARRAY);
722 }
723
724 /**
725 * Compares the file sets of two Paths to determine if they are equal or not while considering file contents. The comparison includes all files in all
726 * subdirectories.
727 *
728 * @param path1 The first directory.
729 * @param path2 The second directory.
730 * @param linkOptions options to follow links.
731 * @param openOptions options to open files.
732 * @param fileVisitOption options to configure traversal.
733 * @return Whether the two directories contain the same files while considering file contents.
734 * @throws IOException if an I/O error is thrown by a visitor method.
735 */
736 public static boolean directoryAndFileContentEquals(final Path path1, final Path path2, final LinkOption[] linkOptions, final OpenOption[] openOptions,
737 final FileVisitOption[] fileVisitOption) throws IOException {
738 // First walk both file trees and gather normalized paths.
739 if (path1 == null && path2 == null) {
740 return true;
741 }
742 if (path1 == null || path2 == null) {
743 return false;
744 }
745 if (notExists(path1) && notExists(path2)) {
746 return true;
747 }
748 final RelativeSortedPaths relativeSortedPaths = new RelativeSortedPaths(path1, path2, Integer.MAX_VALUE, linkOptions, fileVisitOption);
749 // If the normalized path names and counts are not the same, no need to compare contents.
750 if (!relativeSortedPaths.equals) {
751 return false;
752 }
753 // Both visitors contain the same normalized paths, we can compare file contents.
754 final List<Path> fileList1 = relativeSortedPaths.relativeFileList1;
755 final List<Path> fileList2 = relativeSortedPaths.relativeFileList2;
756 final boolean sameFileSystem = isSameFileSystem(path1, path2);
757 for (final Path path : fileList1) {
758 final int binarySearch = sameFileSystem ? Collections.binarySearch(fileList2, path)
759 : Collections.binarySearch(fileList2, path,
760 Comparator.comparing(p -> RelativeSortedPaths.extractKey(p.getFileSystem().getSeparator(), p.toString())));
761 if (binarySearch < 0) {
762 throw new IllegalStateException("Unexpected mismatch.");
763 }
764 if (sameFileSystem && !fileContentEquals(path1.resolve(path), path2.resolve(path), linkOptions, openOptions)) {
765 return false;
766 }
767 if (!fileContentEquals(path1.resolve(path.toString()), path2.resolve(path.toString()), linkOptions, openOptions)) {
768 return false;
769 }
770 }
771 return true;
772 }
773
774 /**
775 * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The comparison includes all files in all
776 * subdirectories.
777 *
778 * @param path1 The first directory.
779 * @param path2 The second directory.
780 * @return Whether the two directories contain the same files without considering file contents.
781 * @throws IOException if an I/O error is thrown by a visitor method.
782 */
783 public static boolean directoryContentEquals(final Path path1, final Path path2) throws IOException {
784 return directoryContentEquals(path1, path2, Integer.MAX_VALUE, EMPTY_LINK_OPTION_ARRAY, EMPTY_FILE_VISIT_OPTION_ARRAY);
785 }
786
787 /**
788 * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The comparison includes all files in all
789 * subdirectories.
790 *
791 * @param path1 The first directory.
792 * @param path2 The second directory.
793 * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
794 * @param linkOptions options to follow links.
795 * @param fileVisitOptions options to configure the traversal.
796 * @return Whether the two directories contain the same files without considering file contents.
797 * @throws IOException if an I/O error is thrown by a visitor method.
798 */
799 public static boolean directoryContentEquals(final Path path1, final Path path2, final int maxDepth, final LinkOption[] linkOptions,
800 final FileVisitOption[] fileVisitOptions) throws IOException {
801 return new RelativeSortedPaths(path1, path2, maxDepth, linkOptions, fileVisitOptions).equals;
802 }
803
804 private static boolean exists(final Path path, final LinkOption... options) {
805 return path != null && (options != null ? Files.exists(path, options) : Files.exists(path));
806 }
807
808 /**
809 * Compares the file contents of two Paths to determine if they are equal or not.
810 * <p>
811 * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}.
812 * </p>
813 *
814 * @param path1 the first file path.
815 * @param path2 the second file path.
816 * @return true if the content of the streams are equal or they both don't exist, false otherwise.
817 * @throws NullPointerException if either input is null.
818 * @throws IOException if an I/O error occurs.
819 * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File)
820 */
821 public static boolean fileContentEquals(final Path path1, final Path path2) throws IOException {
822 return fileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY);
823 }
824
825 /**
826 * Compares the file contents of two Paths to determine if they are equal or not.
827 * <p>
828 * File content is accessed through {@link RandomAccessFileMode#create(Path)}.
829 * </p>
830 *
831 * @param path1 the first file path.
832 * @param path2 the second file path.
833 * @param linkOptions options specifying how files are followed.
834 * @param openOptions ignored.
835 * @return true if the content of the streams are equal or they both don't exist, false otherwise.
836 * @throws NullPointerException if openOptions is null.
837 * @throws IOException if an I/O error occurs.
838 * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File)
839 */
840 public static boolean fileContentEquals(final Path path1, final Path path2, final LinkOption[] linkOptions, final OpenOption[] openOptions)
841 throws IOException {
842 if (path1 == null && path2 == null) {
843 return true;
844 }
845 if (path1 == null || path2 == null) {
846 return false;
847 }
848 final Path nPath1 = path1.normalize();
849 final Path nPath2 = path2.normalize();
850 final boolean path1Exists = exists(nPath1, linkOptions);
851 if (path1Exists != exists(nPath2, linkOptions)) {
852 return false;
853 }
854 if (!path1Exists) {
855 // Two not existing files are equal?
856 // Same as FileUtils
857 return true;
858 }
859 if (Files.isDirectory(nPath1, linkOptions)) {
860 // don't compare directory contents.
861 throw new IOException("Can't compare directories, only files: " + nPath1);
862 }
863 if (Files.isDirectory(nPath2, linkOptions)) {
864 // don't compare directory contents.
865 throw new IOException("Can't compare directories, only files: " + nPath2);
866 }
867 if (Files.size(nPath1) != Files.size(nPath2)) {
868 // lengths differ, cannot be equal
869 return false;
870 }
871 if (isSameFileSystem(path1, path2) && path1.equals(path2)) {
872 // same file
873 return true;
874 }
875 // Faster:
876 try (RandomAccessFile raf1 = RandomAccessFileMode.READ_ONLY.create(path1.toRealPath(linkOptions));
877 RandomAccessFile raf2 = RandomAccessFileMode.READ_ONLY.create(path2.toRealPath(linkOptions))) {
878 return RandomAccessFiles.contentEquals(raf1, raf2);
879 } catch (final UnsupportedOperationException e) {
880 // Slower:
881 // Handle
882 // java.lang.UnsupportedOperationException
883 // at com.sun.nio.zipfs.ZipPath.toFile(ZipPath.java:656)
884 try (InputStream inputStream1 = Files.newInputStream(nPath1, openOptions);
885 InputStream inputStream2 = Files.newInputStream(nPath2, openOptions)) {
886 return IOUtils.contentEquals(inputStream1, inputStream2);
887 }
888 }
889 }
890
891 /**
892 * <p>
893 * Applies an {@link IOFileFilter} to the provided {@link File} objects. The resulting array is a subset of the original file list that matches the provided
894 * filter.
895 * </p>
896 *
897 * <p>
898 * The {@link Set} returned by this method is not guaranteed to be thread safe.
899 * </p>
900 *
901 * <pre>
902 * Set<File> allFiles = ...
903 * Set<File> javaFiles = FileFilterUtils.filterSet(allFiles,
904 * FileFilterUtils.suffixFileFilter(".java"));
905 * </pre>
906 *
907 * @param filter the filter to apply to the set of files.
908 * @param paths the array of files to apply the filter to.
909 * @return a subset of {@code files} that is accepted by the file filter.
910 * @throws NullPointerException if the filter is {@code null}
911 * @throws IllegalArgumentException if {@code files} contains a {@code null} value.
912 * @since 2.9.0
913 */
914 public static Path[] filter(final PathFilter filter, final Path... paths) {
915 Objects.requireNonNull(filter, "filter");
916 if (paths == null) {
917 return EMPTY_PATH_ARRAY;
918 }
919 return filterPaths(filter, Stream.of(paths), Collectors.toList()).toArray(EMPTY_PATH_ARRAY);
920 }
921
922 private static <R, A> R filterPaths(final PathFilter filter, final Stream<Path> stream, final Collector<? super Path, A, R> collector) {
923 Objects.requireNonNull(filter, "filter");
924 Objects.requireNonNull(collector, "collector");
925 if (stream == null) {
926 return Stream.<Path>empty().collect(collector);
927 }
928 return stream.filter(p -> {
929 try {
930 return p != null && filter.accept(p, readBasicFileAttributes(p)) == FileVisitResult.CONTINUE;
931 } catch (final IOException e) {
932 return false;
933 }
934 }).collect(collector);
935 }
936
937 /**
938 * Reads the access control list from a file attribute view.
939 *
940 * @param sourcePath the path to the file.
941 * @return a file attribute view of the given type, or null if the attribute view type is not available.
942 * @throws IOException if an I/O error occurs.
943 * @since 2.8.0
944 */
945 public static List<AclEntry> getAclEntryList(final Path sourcePath) throws IOException {
946 final AclFileAttributeView fileAttributeView = getAclFileAttributeView(sourcePath);
947 return fileAttributeView == null ? null : fileAttributeView.getAcl();
948 }
949
950 /**
951 * Shorthand for {@code Files.getFileAttributeView(path, AclFileAttributeView.class)}.
952 *
953 * @param path the path to the file.
954 * @param options how to handle symbolic links.
955 * @return a AclFileAttributeView, or {@code null} if the attribute view type is not available.
956 * @since 2.12.0
957 */
958 public static AclFileAttributeView getAclFileAttributeView(final Path path, final LinkOption... options) {
959 return Files.getFileAttributeView(path, AclFileAttributeView.class, options);
960 }
961
962 /**
963 * Gets the base name (the part up to and not including the last ".") of the last path segment of a file name.
964 * <p>
965 * Will return the file name itself if it doesn't contain any periods. All leading directories of the {@code file name} parameter are skipped.
966 * </p>
967 *
968 * @return the base name of file name.
969 * @param path the path of the file to obtain the base name of.
970 * @since 2.16.0
971 */
972 public static String getBaseName(final Path path) {
973 if (path == null) {
974 return null;
975 }
976 final Path fileName = path.getFileName();
977 return fileName != null ? FilenameUtils.removeExtension(fileName.toString()) : null;
978 }
979
980 /**
981 * Shorthand for {@code Files.getFileAttributeView(path, DosFileAttributeView.class, options)}.
982 *
983 * @param path the path to the file.
984 * @param options how to handle symbolic links.
985 * @return a DosFileAttributeView, or {@code null} if the attribute view type is not available.
986 * @since 2.12.0
987 */
988 public static DosFileAttributeView getDosFileAttributeView(final Path path, final LinkOption... options) {
989 return Files.getFileAttributeView(path, DosFileAttributeView.class, options);
990 }
991
992 /**
993 * Gets the extension of a Path.
994 * <p>
995 * This method returns the textual part of the Path after the last period.
996 * </p>
997 *
998 * <pre>
999 * foo.txt --> "txt"
1000 * a/b/c.jpg --> "jpg"
1001 * a/b.txt/c --> ""
1002 * a/b/c --> ""
1003 * </pre>
1004 * <p>
1005 * The output will be the same irrespective of the machine that the code is running on.
1006 * </p>
1007 *
1008 * @param path the path to query.
1009 * @return the extension of the file or an empty string if none exists or {@code null} if the fileName is {@code null}.
1010 * @since 2.16.0
1011 */
1012 public static String getExtension(final Path path) {
1013 final String fileName = getFileNameString(path);
1014 return fileName != null ? FilenameUtils.getExtension(fileName) : null;
1015 }
1016
1017 /**
1018 * Gets the Path's file name and apply the given function if the file name is non-null.
1019 *
1020 * @param <R> The function's result type.
1021 * @param path the path to query.
1022 * @param function function to apply to the file name.
1023 * @return the Path's file name as a string or null.
1024 * @see Path#getFileName()
1025 * @since 2.16.0
1026 */
1027 public static <R> R getFileName(final Path path, final Function<Path, R> function) {
1028 final Path fileName = path != null ? path.getFileName() : null;
1029 return fileName != null ? function.apply(fileName) : null;
1030 }
1031
1032 /**
1033 * Gets the Path's file name as a string.
1034 *
1035 * @param path the path to query.
1036 * @return the Path's file name as a string or null.
1037 * @see Path#getFileName()
1038 * @since 2.16.0
1039 */
1040 public static String getFileNameString(final Path path) {
1041 return getFileName(path, Path::toString);
1042 }
1043
1044 /**
1045 * Gets the file's last modified time or null if the file does not exist.
1046 * <p>
1047 * The method provides a workaround for bug <a href="https://bugs.openjdk.java.net/browse/JDK-8177809">JDK-8177809</a> where {@link File#lastModified()}
1048 * looses milliseconds and always ends in 000. This bug is in OpenJDK 8 and 9, and fixed in 11.
1049 * </p>
1050 *
1051 * @param file the file to query.
1052 * @return the file's last modified time.
1053 * @throws IOException Thrown if an I/O error occurs.
1054 * @since 2.12.0
1055 */
1056 public static FileTime getLastModifiedFileTime(final File file) throws IOException {
1057 return getLastModifiedFileTime(file.toPath(), null, EMPTY_LINK_OPTION_ARRAY);
1058 }
1059
1060 /**
1061 * Gets the file's last modified time or null if the file does not exist.
1062 *
1063 * @param path the file to query.
1064 * @param defaultIfAbsent Returns this file time of the file does not exist, may be null.
1065 * @param options options indicating how symbolic links are handled.
1066 * @return the file's last modified time.
1067 * @throws IOException Thrown if an I/O error occurs.
1068 * @since 2.12.0
1069 */
1070 public static FileTime getLastModifiedFileTime(final Path path, final FileTime defaultIfAbsent, final LinkOption... options) throws IOException {
1071 return Files.exists(path) ? getLastModifiedTime(path, options) : defaultIfAbsent;
1072 }
1073
1074 /**
1075 * Gets the file's last modified time or null if the file does not exist.
1076 *
1077 * @param path the file to query.
1078 * @param options options indicating how symbolic links are handled.
1079 * @return the file's last modified time.
1080 * @throws IOException Thrown if an I/O error occurs.
1081 * @since 2.12.0
1082 */
1083 public static FileTime getLastModifiedFileTime(final Path path, final LinkOption... options) throws IOException {
1084 return getLastModifiedFileTime(path, null, options);
1085 }
1086
1087 /**
1088 * Gets the file's last modified time or null if the file does not exist.
1089 *
1090 * @param uri the file to query.
1091 * @return the file's last modified time.
1092 * @throws IOException Thrown if an I/O error occurs.
1093 * @since 2.12.0
1094 */
1095 public static FileTime getLastModifiedFileTime(final URI uri) throws IOException {
1096 return getLastModifiedFileTime(Paths.get(uri), null, EMPTY_LINK_OPTION_ARRAY);
1097 }
1098
1099 /**
1100 * Gets the file's last modified time or null if the file does not exist.
1101 *
1102 * @param url the file to query.
1103 * @return the file's last modified time.
1104 * @throws IOException Thrown if an I/O error occurs.
1105 * @throws URISyntaxException if the URL is not formatted strictly according to RFC2396 and cannot be converted to a URI.
1106 * @since 2.12.0
1107 */
1108 public static FileTime getLastModifiedFileTime(final URL url) throws IOException, URISyntaxException {
1109 return getLastModifiedFileTime(url.toURI());
1110 }
1111
1112 private static FileTime getLastModifiedTime(final Path path, final LinkOption... options) throws IOException {
1113 return Files.getLastModifiedTime(Objects.requireNonNull(path, "path"), options);
1114 }
1115
1116 private static Path getParent(final Path path) {
1117 return path == null ? null : path.getParent();
1118 }
1119
1120 /**
1121 * Gets the system property with the specified name as a Path, or the default value as a Path if there is no property with that key.
1122 *
1123 * @param key the name of the system property.
1124 * @param defaultPath a default path, may be null.
1125 * @return the resulting {@code Path}, or the default value as a Path if there is no property with that key.
1126 * @since 2.21.0
1127 */
1128 public static Path getPath(final String key, final String defaultPath) {
1129 final String property = key != null && !key.isEmpty() ? System.getProperty(key, defaultPath) : defaultPath;
1130 return property != null ? Paths.get(property) : null;
1131 }
1132
1133 /**
1134 * Shorthand for {@code Files.getFileAttributeView(path, PosixFileAttributeView.class)}.
1135 *
1136 * @param path the path to the file.
1137 * @param options how to handle symbolic links.
1138 * @return a PosixFileAttributeView, or {@code null} if the attribute view type is not available.
1139 * @since 2.12.0
1140 */
1141 public static PosixFileAttributeView getPosixFileAttributeView(final Path path, final LinkOption... options) {
1142 return Files.getFileAttributeView(path, PosixFileAttributeView.class, options);
1143 }
1144
1145 /**
1146 * Gets a {@link Path} representing the system temporary directory.
1147 *
1148 * @return the system temporary directory.
1149 * @since 2.12.0
1150 */
1151 public static Path getTempDirectory() {
1152 return Paths.get(FileUtils.getTempDirectoryPath());
1153 }
1154
1155 /**
1156 * Tests whether the given {@link Path} is a directory or not. Implemented as a null-safe delegate to
1157 * {@code Files.isDirectory(Path path, LinkOption... options)}.
1158 *
1159 * @param path the path to the file.
1160 * @param options options indicating how to handle symbolic links.
1161 * @return {@code true} if the file is a directory; {@code false} if the path is null, the file does not exist, is not a directory, or it cannot be
1162 * determined if the file is a directory or not.
1163 * @throws SecurityException In the case of the default provider, and a security manager is installed, the {@link SecurityManager#checkRead(String)
1164 * checkRead} method is invoked to check read access to the directory.
1165 * @since 2.9.0
1166 */
1167 public static boolean isDirectory(final Path path, final LinkOption... options) {
1168 return path != null && Files.isDirectory(path, options);
1169 }
1170
1171 /**
1172 * Tests whether the given file or directory is empty.
1173 *
1174 * @param path the file or directory to query.
1175 * @return whether the file or directory is empty.
1176 * @throws IOException if an I/O error occurs.
1177 */
1178 public static boolean isEmpty(final Path path) throws IOException {
1179 return Files.isDirectory(path) ? isEmptyDirectory(path) : isEmptyFile(path);
1180 }
1181
1182 /**
1183 * Tests whether the directory is empty.
1184 *
1185 * @param directory the directory to query.
1186 * @return whether the directory is empty.
1187 * @throws NotDirectoryException if the file could not otherwise be opened because it is not a directory <em>(optional specific exception)</em>.
1188 * @throws IOException if an I/O error occurs.
1189 * @throws SecurityException In the case of the default provider, and a security manager is installed, the {@link SecurityManager#checkRead(String)
1190 * checkRead} method is invoked to check read access to the directory.
1191 */
1192 public static boolean isEmptyDirectory(final Path directory) throws IOException {
1193 try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) {
1194 return !directoryStream.iterator().hasNext();
1195 }
1196 }
1197
1198 /**
1199 * Tests whether the given file is empty.
1200 *
1201 * @param file the file to query.
1202 * @return whether the file is empty.
1203 * @throws IOException if an I/O error occurs.
1204 * @throws SecurityException In the case of the default provider, and a security manager is installed, its {@link SecurityManager#checkRead(String)
1205 * checkRead} method denies read access to the file.
1206 */
1207 public static boolean isEmptyFile(final Path file) throws IOException {
1208 return Files.size(file) <= 0;
1209 }
1210
1211 /**
1212 * Tests if the given {@link Path} is newer than the given time reference.
1213 *
1214 * @param file the {@link Path} to test.
1215 * @param czdt the time reference.
1216 * @param options options indicating how to handle symbolic links.
1217 * @return true if the {@link Path} exists and has been modified after the given time reference.
1218 * @throws IOException if an I/O error occurs.
1219 * @throws NullPointerException if the file is {@code null}.
1220 * @since 2.12.0
1221 */
1222 public static boolean isNewer(final Path file, final ChronoZonedDateTime<?> czdt, final LinkOption... options) throws IOException {
1223 Objects.requireNonNull(czdt, "czdt");
1224 return isNewer(file, czdt.toInstant(), options);
1225 }
1226
1227 /**
1228 * Tests if the given {@link Path} is newer than the given time reference.
1229 *
1230 * @param file the {@link Path} to test.
1231 * @param fileTime the time reference.
1232 * @param options options indicating how to handle symbolic links.
1233 * @return true if the {@link Path} exists and has been modified after the given time reference.
1234 * @throws IOException if an I/O error occurs.
1235 * @throws NullPointerException if the file is {@code null}.
1236 * @since 2.12.0
1237 */
1238 public static boolean isNewer(final Path file, final FileTime fileTime, final LinkOption... options) throws IOException {
1239 if (notExists(file)) {
1240 return false;
1241 }
1242 return compareLastModifiedTimeTo(file, fileTime, options) > 0;
1243 }
1244
1245 /**
1246 * Tests if the given {@link Path} is newer than the given time reference.
1247 *
1248 * @param file the {@link Path} to test.
1249 * @param instant the time reference.
1250 * @param options options indicating how to handle symbolic links.
1251 * @return true if the {@link Path} exists and has been modified after the given time reference.
1252 * @throws IOException if an I/O error occurs.
1253 * @throws NullPointerException if the file is {@code null}.
1254 * @since 2.12.0
1255 */
1256 public static boolean isNewer(final Path file, final Instant instant, final LinkOption... options) throws IOException {
1257 return isNewer(file, FileTime.from(instant), options);
1258 }
1259
1260 /**
1261 * Tests if the given {@link Path} is newer than the given time reference.
1262 *
1263 * @param file the {@link Path} to test.
1264 * @param timeMillis the time reference measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970).
1265 * @param options options indicating how to handle symbolic links.
1266 * @return true if the {@link Path} exists and has been modified after the given time reference.
1267 * @throws IOException if an I/O error occurs.
1268 * @throws NullPointerException if the file is {@code null}.
1269 * @since 2.9.0
1270 */
1271 public static boolean isNewer(final Path file, final long timeMillis, final LinkOption... options) throws IOException {
1272 return isNewer(file, FileTime.fromMillis(timeMillis), options);
1273 }
1274
1275 /**
1276 * Tests if the given {@link Path} is newer than the reference {@link Path}.
1277 *
1278 * @param file the {@link File} to test.
1279 * @param reference the {@link File} of which the modification date is used.
1280 * @return true if the {@link File} exists and has been modified more recently than the reference {@link File}.
1281 * @throws IOException if an I/O error occurs.
1282 * @since 2.12.0
1283 */
1284 public static boolean isNewer(final Path file, final Path reference) throws IOException {
1285 return isNewer(file, getLastModifiedTime(reference));
1286 }
1287
1288 /**
1289 * Tests if the given {@link Path} is older than the given time reference.
1290 *
1291 * @param file the {@link Path} to test.
1292 * @param fileTime the time reference.
1293 * @param options options indicating how to handle symbolic links.
1294 * @return true if the {@link Path} exists and has been modified before the given time reference.
1295 * @throws IOException if an I/O error occurs.
1296 * @throws NullPointerException if the file is {@code null}.
1297 * @since 2.12.0
1298 */
1299 public static boolean isOlder(final Path file, final FileTime fileTime, final LinkOption... options) throws IOException {
1300 if (notExists(file)) {
1301 return false;
1302 }
1303 return compareLastModifiedTimeTo(file, fileTime, options) < 0;
1304 }
1305
1306 /**
1307 * Tests if the given {@link Path} is older than the given time reference.
1308 *
1309 * @param file the {@link Path} to test.
1310 * @param instant the time reference.
1311 * @param options options indicating how to handle symbolic links.
1312 * @return true if the {@link Path} exists and has been modified before the given time reference.
1313 * @throws IOException if an I/O error occurs.
1314 * @throws NullPointerException if the file is {@code null}.
1315 * @since 2.12.0
1316 */
1317 public static boolean isOlder(final Path file, final Instant instant, final LinkOption... options) throws IOException {
1318 return isOlder(file, FileTime.from(instant), options);
1319 }
1320
1321 /**
1322 * Tests if the given {@link Path} is older than the given time reference.
1323 *
1324 * @param file the {@link Path} to test.
1325 * @param timeMillis the time reference measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970).
1326 * @param options options indicating how to handle symbolic links.
1327 * @return true if the {@link Path} exists and has been modified before the given time reference.
1328 * @throws IOException if an I/O error occurs.
1329 * @throws NullPointerException if the file is {@code null}.
1330 * @since 2.12.0
1331 */
1332 public static boolean isOlder(final Path file, final long timeMillis, final LinkOption... options) throws IOException {
1333 return isOlder(file, FileTime.fromMillis(timeMillis), options);
1334 }
1335
1336 /**
1337 * Tests if the given {@link Path} is older than the reference {@link Path}.
1338 *
1339 * @param file the {@link File} to test.
1340 * @param reference the {@link File} of which the modification date is used.
1341 * @return true if the {@link File} exists and has been modified before than the reference {@link File}.
1342 * @throws IOException if an I/O error occurs.
1343 * @since 2.12.0
1344 */
1345 public static boolean isOlder(final Path file, final Path reference) throws IOException {
1346 return isOlder(file, getLastModifiedTime(reference));
1347 }
1348
1349 /**
1350 * Tests whether the given path is on a POSIX file system.
1351 *
1352 * @param test The Path to test.
1353 * @param options options indicating how to handle symbolic links.
1354 * @return true if test is on a POSIX file system.
1355 * @since 2.12.0
1356 */
1357 public static boolean isPosix(final Path test, final LinkOption... options) {
1358 return exists(test, options) && readPosixFileAttributes(test, options) != null;
1359 }
1360
1361 /**
1362 * Tests whether the given {@link Path} is a regular file or not. Implemented as a null-safe delegate to
1363 * {@code Files.isRegularFile(Path path, LinkOption... options)}.
1364 *
1365 * @param path the path to the file.
1366 * @param options options indicating how to handle symbolic links.
1367 * @return {@code true} if the file is a regular file; {@code false} if the path is null, the file does not exist, is not a directory, or it cannot be
1368 * determined if the file is a regular file or not.
1369 * @throws SecurityException In the case of the default provider, and a security manager is installed, the {@link SecurityManager#checkRead(String)
1370 * checkRead} method is invoked to check read access to the directory.
1371 * @since 2.9.0
1372 */
1373 public static boolean isRegularFile(final Path path, final LinkOption... options) {
1374 return path != null && Files.isRegularFile(path, options);
1375 }
1376
1377 static boolean isSameFileSystem(final Path path1, final Path path2) {
1378 return path1.getFileSystem() == path2.getFileSystem();
1379 }
1380
1381 /**
1382 * Creates a new DirectoryStream for Paths rooted at the given directory.
1383 * <p>
1384 * If you don't use the try-with-resources construct, then you must call the stream's {@link Stream#close()} method after iteration is complete to free any
1385 * resources held for the open directory.
1386 * </p>
1387 *
1388 * @param dir the path to the directory to stream.
1389 * @param pathFilter the directory stream filter.
1390 * @return a new instance.
1391 * @throws IOException if an I/O error occurs.
1392 */
1393 public static DirectoryStream<Path> newDirectoryStream(final Path dir, final PathFilter pathFilter) throws IOException {
1394 return Files.newDirectoryStream(dir, new DirectoryStreamFilter(pathFilter));
1395 }
1396
1397 /**
1398 * Creates a new OutputStream by opening or creating a file, returning an output stream that may be used to write bytes to the file.
1399 *
1400 * @param path the Path.
1401 * @param append Whether or not to append.
1402 * @return a new OutputStream.
1403 * @throws IOException if an I/O error occurs.
1404 * @see Files#newOutputStream(Path, OpenOption...)
1405 * @since 2.12.0
1406 */
1407 public static OutputStream newOutputStream(final Path path, final boolean append) throws IOException {
1408 return newOutputStream(path, EMPTY_LINK_OPTION_ARRAY, append ? OPEN_OPTIONS_APPEND : OPEN_OPTIONS_TRUNCATE);
1409 }
1410
1411 static OutputStream newOutputStream(final Path path, final LinkOption[] linkOptions, final OpenOption... openOptions) throws IOException {
1412 if (!exists(path, linkOptions)) {
1413 createParentDirectories(path, linkOptions != null && linkOptions.length > 0 ? linkOptions[0] : NULL_LINK_OPTION);
1414 }
1415 final List<OpenOption> list = new ArrayList<>(Arrays.asList(openOptions != null ? openOptions : EMPTY_OPEN_OPTION_ARRAY));
1416 list.addAll(Arrays.asList(linkOptions != null ? linkOptions : EMPTY_LINK_OPTION_ARRAY));
1417 return Files.newOutputStream(path, list.toArray(EMPTY_OPEN_OPTION_ARRAY));
1418 }
1419
1420 /**
1421 * Copy of the {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}.
1422 *
1423 * @return Copy of the {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}.
1424 */
1425 public static LinkOption[] noFollowLinkOptionArray() {
1426 return NOFOLLOW_LINK_OPTION_ARRAY.clone();
1427 }
1428
1429 private static boolean notExists(final Path path, final LinkOption... options) {
1430 return Files.notExists(Objects.requireNonNull(path, "path"), options);
1431 }
1432
1433 /**
1434 * Returns true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}.
1435 *
1436 * @param deleteOptions the array to test.
1437 * @return true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}.
1438 */
1439 private static boolean overrideReadOnly(final DeleteOption... deleteOptions) {
1440 if (deleteOptions == null) {
1441 return false;
1442 }
1443 return Stream.of(deleteOptions).anyMatch(e -> e == StandardDeleteOption.OVERRIDE_READ_ONLY);
1444 }
1445
1446 /**
1447 * Reads the BasicFileAttributes from the given path. Returns null if the attributes can't be read.
1448 *
1449 * @param <A> The {@link BasicFileAttributes} type.
1450 * @param path The Path to test.
1451 * @param type the {@link Class} of the file attributes required to read.
1452 * @param options options indicating how to handle symbolic links.
1453 * @return the file attributes or null if the attributes can't be read.
1454 * @see Files#readAttributes(Path, Class, LinkOption...)
1455 * @since 2.12.0
1456 */
1457 public static <A extends BasicFileAttributes> A readAttributes(final Path path, final Class<A> type, final LinkOption... options) {
1458 try {
1459 return path == null ? null : Files.readAttributes(path, type, options);
1460 } catch (final UnsupportedOperationException | IOException e) {
1461 // For example, on Windows.
1462 return null;
1463 }
1464 }
1465
1466 /**
1467 * Reads the BasicFileAttributes from the given path.
1468 *
1469 * @param path the path to read.
1470 * @return the path attributes.
1471 * @throws IOException if an I/O error occurs.
1472 * @since 2.9.0
1473 */
1474 public static BasicFileAttributes readBasicFileAttributes(final Path path) throws IOException {
1475 return Files.readAttributes(path, BasicFileAttributes.class);
1476 }
1477
1478 /**
1479 * Reads the BasicFileAttributes from the given path. Returns null if the attributes can't be read.
1480 *
1481 * @param path the path to read.
1482 * @param options options indicating how to handle symbolic links.
1483 * @return the path attributes.
1484 * @since 2.12.0
1485 */
1486 public static BasicFileAttributes readBasicFileAttributes(final Path path, final LinkOption... options) {
1487 return readAttributes(path, BasicFileAttributes.class, options);
1488 }
1489
1490 /**
1491 * Reads the BasicFileAttributes from the given path. Returns null if the attributes can't be read.
1492 *
1493 * @param path the path to read.
1494 * @return the path attributes.
1495 * @since 2.9.0
1496 * @deprecated Use {@link #readBasicFileAttributes(Path, LinkOption...)}.
1497 */
1498 @Deprecated
1499 public static BasicFileAttributes readBasicFileAttributesUnchecked(final Path path) {
1500 return readBasicFileAttributes(path, EMPTY_LINK_OPTION_ARRAY);
1501 }
1502
1503 /**
1504 * Reads the DosFileAttributes from the given path. Returns null if the attributes can't be read.
1505 *
1506 * @param path the path to read.
1507 * @param options options indicating how to handle symbolic links.
1508 * @return the path attributes.
1509 * @since 2.12.0
1510 */
1511 public static DosFileAttributes readDosFileAttributes(final Path path, final LinkOption... options) {
1512 return readAttributes(path, DosFileAttributes.class, options);
1513 }
1514
1515 private static Path readIfSymbolicLink(final Path path) throws IOException {
1516 return path != null ? Files.isSymbolicLink(path) ? Files.readSymbolicLink(path) : path : null;
1517 }
1518
1519 /**
1520 * Reads the PosixFileAttributes or DosFileAttributes from the given path. Returns null if the attributes can't be read.
1521 *
1522 * @param path The Path to read.
1523 * @param options options indicating how to handle symbolic links.
1524 * @return the file attributes.
1525 * @since 2.12.0
1526 */
1527 public static BasicFileAttributes readOsFileAttributes(final Path path, final LinkOption... options) {
1528 final PosixFileAttributes fileAttributes = readPosixFileAttributes(path, options);
1529 return fileAttributes != null ? fileAttributes : readDosFileAttributes(path, options);
1530 }
1531
1532 /**
1533 * Reads the PosixFileAttributes from the given path. Returns null instead of throwing {@link UnsupportedOperationException}.
1534 *
1535 * @param path The Path to read.
1536 * @param options options indicating how to handle symbolic links.
1537 * @return the file attributes.
1538 * @since 2.12.0
1539 */
1540 public static PosixFileAttributes readPosixFileAttributes(final Path path, final LinkOption... options) {
1541 return readAttributes(path, PosixFileAttributes.class, options);
1542 }
1543
1544 /**
1545 * Reads the file contents at the given path as a String using the Charset.
1546 *
1547 * @param path The source path.
1548 * @param charset How to convert bytes to a String, null uses the default Charset.
1549 * @return the file contents as a new String.
1550 * @throws IOException if an I/O error occurs reading from the stream.
1551 * @see Files#readAllBytes(Path)
1552 * @see Charsets#toCharset(Charset)
1553 * @since 2.12.0
1554 */
1555 public static String readString(final Path path, final Charset charset) throws IOException {
1556 return new String(Files.readAllBytes(path), Charsets.toCharset(charset));
1557 }
1558
1559 /**
1560 * Relativizes all files in the given {@code collection} against a {@code parent}.
1561 *
1562 * @param collection The collection of paths to relativize.
1563 * @param parent relativizes against this parent path.
1564 * @param sort Whether to sort the result.
1565 * @param comparator How to sort.
1566 * @return A collection of relativized paths, optionally sorted.
1567 */
1568 static List<Path> relativize(final Collection<Path> collection, final Path parent, final boolean sort, final Comparator<? super Path> comparator) {
1569 Stream<Path> stream = collection.stream().map(parent::relativize);
1570 if (sort) {
1571 stream = comparator == null ? stream.sorted() : stream.sorted(comparator);
1572 }
1573 return stream.collect(Collectors.toList());
1574 }
1575
1576 /**
1577 * Requires that the given {@link File} exists and throws an {@link IllegalArgumentException} if it doesn't.
1578 *
1579 * @param file The {@link File} to check.
1580 * @param fileParamName The parameter name to use in the exception message in case of {@code null} input.
1581 * @param options options indicating how to handle symbolic links.
1582 * @return the given file.
1583 * @throws NullPointerException if the given {@link File} is {@code null}.
1584 * @throws IllegalArgumentException if the given {@link File} does not exist.
1585 */
1586 private static Path requireExists(final Path file, final String fileParamName, final LinkOption... options) {
1587 Objects.requireNonNull(file, fileParamName);
1588 if (!exists(file, options)) {
1589 throw new IllegalArgumentException("File system element for parameter '" + fileParamName + "' does not exist: '" + file + "'");
1590 }
1591 return file;
1592 }
1593
1594 static Path resolve(final Path targetDirectory, final Path otherPath) {
1595 final FileSystem fileSystemTarget = targetDirectory.getFileSystem();
1596 final FileSystem fileSystemSource = otherPath.getFileSystem();
1597 if (fileSystemTarget == fileSystemSource) {
1598 return targetDirectory.resolve(otherPath);
1599 }
1600 final String separatorSource = fileSystemSource.getSeparator();
1601 final String separatorTarget = fileSystemTarget.getSeparator();
1602 final String otherString = otherPath.toString();
1603 return targetDirectory.resolve(Objects.equals(separatorSource, separatorTarget) ? otherString : otherString.replace(separatorSource, separatorTarget));
1604 }
1605
1606 private static boolean setDosReadOnly(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
1607 final DosFileAttributeView dosFileAttributeView = getDosFileAttributeView(path, linkOptions);
1608 if (dosFileAttributeView != null) {
1609 dosFileAttributeView.setReadOnly(readOnly);
1610 return true;
1611 }
1612 return false;
1613 }
1614
1615 /**
1616 * Sets the given {@code targetFile}'s last modified time to the value from {@code sourceFile}.
1617 *
1618 * @param sourceFile The source path to query.
1619 * @param targetFile The target path to set.
1620 * @throws NullPointerException if sourceFile is {@code null}.
1621 * @throws NullPointerException if targetFile is {@code null}.
1622 * @throws IOException if setting the last-modified time failed.
1623 * @since 2.12.0
1624 */
1625 public static void setLastModifiedTime(final Path sourceFile, final Path targetFile) throws IOException {
1626 Objects.requireNonNull(sourceFile, "sourceFile");
1627 Files.setLastModifiedTime(targetFile, getLastModifiedTime(sourceFile));
1628 }
1629
1630 /**
1631 * To delete a file in POSIX, you need Write and Execute permissions on its parent directory.
1632 *
1633 * @param parent The parent path for a file element to delete which needs RW permissions.
1634 * @param enableDeleteChildren true to set permissions to delete.
1635 * @param linkOptions options indicating how handle symbolic links.
1636 * @return true if the operation was attempted and succeeded, false if parent is null.
1637 * @throws IOException if an I/O error occurs.
1638 */
1639 private static boolean setPosixDeletePermissions(final Path parent, final boolean enableDeleteChildren, final LinkOption... linkOptions)
1640 throws IOException {
1641 // To delete a file in POSIX, you need write and execute permissions on its parent directory.
1642 // @formatter:off
1643 return setPosixPermissions(parent, enableDeleteChildren, Arrays.asList(
1644 PosixFilePermission.OWNER_WRITE,
1645 //PosixFilePermission.GROUP_WRITE,
1646 //PosixFilePermission.OTHERS_WRITE,
1647 PosixFilePermission.OWNER_EXECUTE
1648 //PosixFilePermission.GROUP_EXECUTE,
1649 //PosixFilePermission.OTHERS_EXECUTE
1650 ), linkOptions);
1651 // @formatter:on
1652 }
1653
1654 /**
1655 * Low-level POSIX permission operation to set permissions.
1656 * <p>
1657 * If the permissions to update are already set, then make no additional calls.
1658 * </p>
1659 *
1660 * @param path Set this path's permissions.
1661 * @param addPermissions true to add, false to remove.
1662 * @param updatePermissions the List of PosixFilePermission to add or remove.
1663 * @param linkOptions options indicating how handle symbolic links.
1664 * @return true if the operation was attempted and succeeded, false if parent is null.
1665 * @throws IOException if an I/O error occurs.
1666 */
1667 private static boolean setPosixPermissions(final Path path, final boolean addPermissions, final List<PosixFilePermission> updatePermissions,
1668 final LinkOption... linkOptions) throws IOException {
1669 if (path != null) {
1670 final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path, linkOptions);
1671 final Set<PosixFilePermission> newPermissions = new HashSet<>(permissions);
1672 if (addPermissions) {
1673 newPermissions.addAll(updatePermissions);
1674 } else {
1675 newPermissions.removeAll(updatePermissions);
1676 }
1677 if (!newPermissions.equals(permissions)) {
1678 Files.setPosixFilePermissions(path, newPermissions);
1679 }
1680 return true;
1681 }
1682 return false;
1683 }
1684
1685 private static void setPosixReadOnlyFile(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
1686 // Not Windows 10
1687 final Set<PosixFilePermission> permissions = Files.getPosixFilePermissions(path, linkOptions);
1688 // @formatter:off
1689 final List<PosixFilePermission> readPermissions = Arrays.asList(
1690 PosixFilePermission.OWNER_READ
1691 //PosixFilePermission.GROUP_READ,
1692 //PosixFilePermission.OTHERS_READ
1693 );
1694 final List<PosixFilePermission> writePermissions = Arrays.asList(
1695 PosixFilePermission.OWNER_WRITE
1696 //PosixFilePermission.GROUP_WRITE,
1697 //PosixFilePermission.OTHERS_WRITE
1698 );
1699 // @formatter:on
1700 if (readOnly) {
1701 // RO: We can read, we cannot write.
1702 permissions.addAll(readPermissions);
1703 permissions.removeAll(writePermissions);
1704 } else {
1705 // Not RO: We can read, we can write.
1706 permissions.addAll(readPermissions);
1707 permissions.addAll(writePermissions);
1708 }
1709 Files.setPosixFilePermissions(path, permissions);
1710 }
1711
1712 /**
1713 * Sets the given Path to the {@code readOnly} value.
1714 * <p>
1715 * This behavior is OS dependent.
1716 * </p>
1717 *
1718 * @param path The path to set.
1719 * @param readOnly true for read-only, false for not read-only.
1720 * @param linkOptions options indicating how to handle symbolic links.
1721 * @return The given path.
1722 * @throws IOException if an I/O error occurs.
1723 * @since 2.8.0
1724 */
1725 public static Path setReadOnly(final Path path, final boolean readOnly, final LinkOption... linkOptions) throws IOException {
1726 try {
1727 // Windows is simplest
1728 if (setDosReadOnly(path, readOnly, linkOptions)) {
1729 return path;
1730 }
1731 } catch (final IOException ignored) {
1732 // Retry with POSIX below.
1733 }
1734 final Path parent = getParent(path);
1735 if (!isPosix(parent, linkOptions)) { // Test parent because we may not the permissions to test the file.
1736 throw new IOException(String.format("DOS or POSIX file operations not available for '%s', linkOptions %s", path, Arrays.toString(linkOptions)));
1737 }
1738 // POSIX
1739 if (readOnly) {
1740 // RO
1741 // File, then parent dir (if any).
1742 setPosixReadOnlyFile(path, readOnly, linkOptions);
1743 setPosixDeletePermissions(parent, false, linkOptions);
1744 } else {
1745 // RE
1746 // Parent dir (if any), then file.
1747 setPosixDeletePermissions(parent, true, linkOptions);
1748 }
1749 return path;
1750 }
1751
1752 /**
1753 * Returns the size of the given file or directory. If the provided {@link Path} is a regular file, then the file's size is returned. If the argument is a
1754 * directory, then the size of the directory is calculated recursively.
1755 * <p>
1756 * Note that overflow is not detected, and the return value may be negative if overflow occurs. See {@link #sizeOfAsBigInteger(Path)} for an alternative
1757 * method that does not overflow.
1758 * </p>
1759 *
1760 * @param path the regular file or directory to return the size of, must not be {@code null}.
1761 * @return the length of the file, or recursive size of the directory, in bytes.
1762 * @throws NullPointerException if the file is {@code null}.
1763 * @throws IllegalArgumentException if the file does not exist.
1764 * @throws IOException if an I/O error occurs.
1765 * @since 2.12.0
1766 */
1767 public static long sizeOf(final Path path) throws IOException {
1768 requireExists(path, "path");
1769 return Files.isDirectory(path) ? sizeOfDirectory(path) : Files.size(path);
1770 }
1771
1772 /**
1773 * Returns the size of the given file or directory. If the provided {@link Path} is a regular file, then the file's size is returned. If the argument is a
1774 * directory, then the size of the directory is calculated recursively.
1775 *
1776 * @param path the regular file or directory to return the size of (must not be {@code null}).
1777 * @return the length of the file, or recursive size of the directory, provided (in bytes).
1778 * @throws NullPointerException if the file is {@code null}.
1779 * @throws IllegalArgumentException if the file does not exist.
1780 * @throws IOException if an I/O error occurs.
1781 * @since 2.12.0
1782 */
1783 public static BigInteger sizeOfAsBigInteger(final Path path) throws IOException {
1784 requireExists(path, "path");
1785 return Files.isDirectory(path) ? sizeOfDirectoryAsBigInteger(path) : BigInteger.valueOf(Files.size(path));
1786 }
1787
1788 /**
1789 * Counts the size of a directory recursively (sum of the size of all files).
1790 * <p>
1791 * Note that overflow is not detected, and the return value may be negative if overflow occurs. See {@link #sizeOfDirectoryAsBigInteger(Path)} for an
1792 * alternative method that does not overflow.
1793 * </p>
1794 *
1795 * @param directory directory to inspect, must not be {@code null}.
1796 * @return size of directory in bytes, 0 if directory is security restricted, a negative number when the real total is greater than {@link Long#MAX_VALUE}.
1797 * @throws NullPointerException if the directory is {@code null}.
1798 * @throws IOException if an I/O error occurs.
1799 * @since 2.12.0
1800 */
1801 public static long sizeOfDirectory(final Path directory) throws IOException {
1802 return countDirectory(directory).getByteCounter().getLong();
1803 }
1804
1805 /**
1806 * Counts the size of a directory recursively (sum of the size of all files).
1807 *
1808 * @param directory directory to inspect, must not be {@code null}.
1809 * @return size of directory in bytes, 0 if directory is security restricted.
1810 * @throws NullPointerException if the directory is {@code null}.
1811 * @throws IOException if an I/O error occurs.
1812 * @since 2.12.0
1813 */
1814 public static BigInteger sizeOfDirectoryAsBigInteger(final Path directory) throws IOException {
1815 return countDirectoryAsBigInteger(directory).getByteCounter().getBigInteger();
1816 }
1817
1818 private static Path stripTrailingSeparator(final Path dir) {
1819 final String separator = dir.getFileSystem().getSeparator();
1820 final String fileName = getFileNameString(dir);
1821 return fileName != null && fileName.endsWith(separator) ? dir.resolveSibling(fileName.substring(0, fileName.length() - 1)) : dir;
1822 }
1823
1824 /**
1825 * Converts an array of {@link FileVisitOption} to a {@link Set}.
1826 *
1827 * @param fileVisitOptions input array.
1828 * @return a new Set.
1829 */
1830 static Set<FileVisitOption> toFileVisitOptionSet(final FileVisitOption... fileVisitOptions) {
1831 return fileVisitOptions == null ? EnumSet.noneOf(FileVisitOption.class) : Stream.of(fileVisitOptions).collect(Collectors.toSet());
1832 }
1833
1834 private static <T> List<T> toList(final Iterable<T> iterable) {
1835 return StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList());
1836 }
1837
1838 private static List<Path> toSortedList(final Iterable<Path> rootDirectories) {
1839 final List<Path> list = toList(rootDirectories);
1840 Collections.sort(list);
1841 return list;
1842 }
1843
1844 /**
1845 * Implements behavior similar to the Unix "touch" utility. Creates a new file with size 0, or, if the file exists, just updates the file's modified time.
1846 * this method creates parent directories if they do not exist.
1847 *
1848 * @param file the file to touch.
1849 * @return The given file.
1850 * @throws NullPointerException if the parameter is {@code null}.
1851 * @throws IOException if setting the last-modified time failed or an I/O problem occurs.
1852 * @since 2.12.0
1853 */
1854 public static Path touch(final Path file) throws IOException {
1855 Objects.requireNonNull(file, "file");
1856 if (!Files.exists(file)) {
1857 createParentDirectories(file);
1858 Files.createFile(file);
1859 } else {
1860 FileTimes.setLastModifiedTime(file);
1861 }
1862 return file;
1863 }
1864
1865 /**
1866 * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
1867 *
1868 * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
1869 *
1870 * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
1871 * @param directory See {@link Files#walkFileTree(Path,FileVisitor)}.
1872 * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}.
1873 * @return the given visitor.
1874 * @throws NoSuchFileException if the directory does not exist.
1875 * @throws IOException if an I/O error is thrown by a visitor method.
1876 * @throws NullPointerException if the directory is {@code null}.
1877 */
1878 public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path directory) throws IOException {
1879 Files.walkFileTree(directory, visitor);
1880 return visitor;
1881 }
1882
1883 /**
1884 * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
1885 *
1886 * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
1887 *
1888 * @param start See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
1889 * @param options See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
1890 * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
1891 * @param visitor See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
1892 * @param <T> See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}.
1893 * @return the given visitor.
1894 * @throws IOException if an I/O error is thrown by a visitor method.
1895 */
1896 public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path start, final Set<FileVisitOption> options,
1897 final int maxDepth) throws IOException {
1898 Files.walkFileTree(start, options, maxDepth, visitor);
1899 return visitor;
1900 }
1901
1902 /**
1903 * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
1904 *
1905 * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
1906 *
1907 * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
1908 * @param first See {@link Paths#get(String,String[])}.
1909 * @param more See {@link Paths#get(String,String[])}.
1910 * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}.
1911 * @return the given visitor.
1912 * @throws IOException if an I/O error is thrown by a visitor method.
1913 */
1914 public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final String first, final String... more) throws IOException {
1915 return visitFileTree(visitor, Paths.get(first, more));
1916 }
1917
1918 /**
1919 * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor.
1920 *
1921 * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path.
1922 *
1923 * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}.
1924 * @param uri See {@link Paths#get(URI)}.
1925 * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}.
1926 * @return the given visitor.
1927 * @throws IOException if an I/O error is thrown by a visitor method.
1928 */
1929 public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final URI uri) throws IOException {
1930 return visitFileTree(visitor, Paths.get(uri));
1931 }
1932
1933 /**
1934 * Waits for the file system to detect a file's presence, with a timeout.
1935 * <p>
1936 * This method repeatedly tests {@link Files#exists(Path,LinkOption...)} until it returns true up to the maximum time given.
1937 * </p>
1938 *
1939 * @param file the file to check, must not be {@code null}.
1940 * @param timeout the maximum time to wait.
1941 * @param options options indicating how to handle symbolic links.
1942 * @return true if file exists.
1943 * @throws NullPointerException if the file is {@code null}.
1944 * @since 2.12.0
1945 */
1946 public static boolean waitFor(final Path file, final Duration timeout, final LinkOption... options) {
1947 Objects.requireNonNull(file, "file");
1948 final Instant finishInstant = Instant.now().plus(timeout);
1949 boolean interrupted = false;
1950 final long minSleepMillis = 100;
1951 try {
1952 while (!exists(file, options)) {
1953 final Instant now = Instant.now();
1954 if (now.isAfter(finishInstant)) {
1955 return false;
1956 }
1957 try {
1958 ThreadUtils.sleep(Duration.ofMillis(Math.min(minSleepMillis, finishInstant.minusMillis(now.toEpochMilli()).toEpochMilli())));
1959 } catch (final InterruptedException ignore) {
1960 interrupted = true;
1961 } catch (final Exception ex) {
1962 break;
1963 }
1964 }
1965 } finally {
1966 if (interrupted) {
1967 Thread.currentThread().interrupt();
1968 }
1969 }
1970 return exists(file, options);
1971 }
1972
1973 /**
1974 * Returns a stream of filtered paths.
1975 * <p>
1976 * The returned {@link Stream} may wrap one or more {@link DirectoryStream}s. When you require timely disposal of file system resources, use a
1977 * {@code try}-with-resources block to ensure invocation of the stream's {@link Stream#close()} method after the stream operations are completed. Calling a
1978 * closed stream causes a {@link IllegalStateException}.
1979 * </p>
1980 *
1981 * @param start the start path.
1982 * @param pathFilter the path filter.
1983 * @param maxDepth the maximum depth of directories to walk.
1984 * @param readAttributes whether to call the filters with file attributes (false passes null).
1985 * @param options the options to configure the walk.
1986 * @return a filtered stream of paths.
1987 * @throws IOException if an I/O error is thrown when accessing the starting file.
1988 * @since 2.9.0
1989 */
1990 @SuppressWarnings("resource") // Caller closes
1991 public static Stream<Path> walk(final Path start, final PathFilter pathFilter, final int maxDepth, final boolean readAttributes,
1992 final FileVisitOption... options) throws IOException {
1993 return Files.walk(start, maxDepth, options).filter(
1994 path -> pathFilter.accept(path, readAttributes ? readBasicFileAttributes(path, EMPTY_LINK_OPTION_ARRAY) : null) == FileVisitResult.CONTINUE);
1995 }
1996
1997 private static <R> R withPosixFileAttributes(final Path path, final LinkOption[] linkOptions, final boolean overrideReadOnly,
1998 final IOFunction<PosixFileAttributes, R> function) throws IOException {
1999 final PosixFileAttributes posixFileAttributes = overrideReadOnly ? readPosixFileAttributes(path, linkOptions) : null;
2000 try {
2001 return function.apply(posixFileAttributes);
2002 } finally {
2003 if (posixFileAttributes != null && path != null && Files.exists(path, linkOptions)) {
2004 Files.setPosixFilePermissions(path, posixFileAttributes.permissions());
2005 }
2006 }
2007 }
2008
2009 /**
2010 * Writes the given character sequence to a file at the given path.
2011 *
2012 * @param path The target file.
2013 * @param charSequence The character sequence text.
2014 * @param charset The Charset to encode the text.
2015 * @param openOptions options How to open the file.
2016 * @return The given path.
2017 * @throws IOException if an I/O error occurs writing to or creating the file.
2018 * @throws NullPointerException if either {@code path} or {@code charSequence} is {@code null}.
2019 * @since 2.12.0
2020 */
2021 public static Path writeString(final Path path, final CharSequence charSequence, final Charset charset, final OpenOption... openOptions)
2022 throws IOException {
2023 // Check the text is not null before opening file.
2024 Objects.requireNonNull(path, "path");
2025 Objects.requireNonNull(charSequence, "charSequence");
2026 Files.write(path, String.valueOf(charSequence).getBytes(Charsets.toCharset(charset)), openOptions);
2027 return path;
2028 }
2029
2030 /**
2031 * Prevents instantiation.
2032 */
2033 private PathUtils() {
2034 // do not instantiate.
2035 }
2036 }