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