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