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