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