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