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.UncheckedIOException; 024import java.net.URI; 025import java.net.URL; 026import java.nio.file.CopyOption; 027import java.nio.file.DirectoryStream; 028import java.nio.file.FileVisitOption; 029import java.nio.file.FileVisitResult; 030import java.nio.file.FileVisitor; 031import java.nio.file.Files; 032import java.nio.file.LinkOption; 033import java.nio.file.NoSuchFileException; 034import java.nio.file.NotDirectoryException; 035import java.nio.file.OpenOption; 036import java.nio.file.Path; 037import java.nio.file.Paths; 038import java.nio.file.attribute.AclEntry; 039import java.nio.file.attribute.AclFileAttributeView; 040import java.nio.file.attribute.BasicFileAttributes; 041import java.nio.file.attribute.DosFileAttributeView; 042import java.nio.file.attribute.FileAttribute; 043import java.nio.file.attribute.PosixFileAttributeView; 044import java.nio.file.attribute.PosixFileAttributes; 045import java.nio.file.attribute.PosixFilePermission; 046import java.util.ArrayList; 047import java.util.Arrays; 048import java.util.Collection; 049import java.util.Collections; 050import java.util.Comparator; 051import java.util.EnumSet; 052import java.util.List; 053import java.util.Objects; 054import java.util.Set; 055import java.util.stream.Collector; 056import java.util.stream.Collectors; 057import java.util.stream.Stream; 058 059import org.apache.commons.io.IOExceptionList; 060import org.apache.commons.io.IOUtils; 061import org.apache.commons.io.file.Counters.PathCounters; 062import org.apache.commons.io.filefilter.IOFileFilter; 063 064/** 065 * NIO Path utilities. 066 * 067 * @since 2.7 068 */ 069public final class PathUtils { 070 071 /** 072 * Private worker/holder that computes and tracks relative path names and their equality. We reuse the sorted 073 * relative lists when comparing directories. 074 */ 075 private static class RelativeSortedPaths { 076 077 final boolean equals; 078 // final List<Path> relativeDirList1; // might need later? 079 // final List<Path> relativeDirList2; // might need later? 080 final List<Path> relativeFileList1; 081 final List<Path> relativeFileList2; 082 083 /** 084 * Constructs and initializes a new instance by accumulating directory and file info. 085 * 086 * @param dir1 First directory to compare. 087 * @param dir2 Seconds directory to compare. 088 * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 089 * @param linkOptions Options indicating how symbolic links are handled. 090 * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 091 * @throws IOException if an I/O error is thrown by a visitor method. 092 */ 093 private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth, 094 final LinkOption[] linkOptions, final FileVisitOption[] fileVisitOptions) throws IOException { 095 final List<Path> tmpRelativeDirList1; 096 final List<Path> tmpRelativeDirList2; 097 List<Path> tmpRelativeFileList1 = null; 098 List<Path> tmpRelativeFileList2 = null; 099 if (dir1 == null && dir2 == null) { 100 equals = true; 101 } else if (dir1 == null ^ dir2 == null) { 102 equals = false; 103 } else { 104 final boolean parentDirNotExists1 = Files.notExists(dir1, linkOptions); 105 final boolean parentDirNotExists2 = Files.notExists(dir2, linkOptions); 106 if (parentDirNotExists1 || parentDirNotExists2) { 107 equals = parentDirNotExists1 && parentDirNotExists2; 108 } else { 109 final AccumulatorPathVisitor visitor1 = accumulate(dir1, maxDepth, fileVisitOptions); 110 final AccumulatorPathVisitor visitor2 = accumulate(dir2, maxDepth, fileVisitOptions); 111 if (visitor1.getDirList().size() != visitor2.getDirList().size() 112 || visitor1.getFileList().size() != visitor2.getFileList().size()) { 113 equals = false; 114 } else { 115 tmpRelativeDirList1 = visitor1.relativizeDirectories(dir1, true, null); 116 tmpRelativeDirList2 = visitor2.relativizeDirectories(dir2, true, null); 117 if (!tmpRelativeDirList1.equals(tmpRelativeDirList2)) { 118 equals = false; 119 } else { 120 tmpRelativeFileList1 = visitor1.relativizeFiles(dir1, true, null); 121 tmpRelativeFileList2 = visitor2.relativizeFiles(dir2, true, null); 122 equals = tmpRelativeFileList1.equals(tmpRelativeFileList2); 123 } 124 } 125 } 126 } 127 // relativeDirList1 = tmpRelativeDirList1; 128 // relativeDirList2 = tmpRelativeDirList2; 129 relativeFileList1 = tmpRelativeFileList1; 130 relativeFileList2 = tmpRelativeFileList2; 131 } 132 } 133 134 /** 135 * Empty {@link CopyOption} array. 136 * 137 * @since 2.8.0 138 */ 139 public static final CopyOption[] EMPTY_COPY_OPTIONS = {}; 140 141 /** 142 * Empty {@link LinkOption} array. 143 * 144 * @since 2.8.0 145 */ 146 public static final DeleteOption[] EMPTY_DELETE_OPTION_ARRAY = {}; 147 148 /** 149 * Empty {@link FileVisitOption} array. 150 */ 151 public static final FileVisitOption[] EMPTY_FILE_VISIT_OPTION_ARRAY = {}; 152 153 /** 154 * Empty {@link LinkOption} array. 155 */ 156 public static final LinkOption[] EMPTY_LINK_OPTION_ARRAY = {}; 157 158 /** 159 * {@link LinkOption} array for {@link LinkOption#NOFOLLOW_LINKS}. 160 * 161 * @since 2.9.0 162 */ 163 public static final LinkOption[] NOFOLLOW_LINK_OPTION_ARRAY = {LinkOption.NOFOLLOW_LINKS}; 164 165 /** 166 * Empty {@link OpenOption} array. 167 */ 168 public static final OpenOption[] EMPTY_OPEN_OPTION_ARRAY = {}; 169 170 /** 171 * Empty {@link Path} array. 172 * 173 * @since 2.9.0 174 */ 175 public static final Path[] EMPTY_PATH_ARRAY = {}; 176 177 /** 178 * Accumulates file tree information in a {@link AccumulatorPathVisitor}. 179 * 180 * @param directory The directory to accumulate information. 181 * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 182 * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 183 * @throws IOException if an I/O error is thrown by a visitor method. 184 * @return file tree information. 185 */ 186 private static AccumulatorPathVisitor accumulate(final Path directory, final int maxDepth, 187 final FileVisitOption[] fileVisitOptions) throws IOException { 188 return visitFileTree(AccumulatorPathVisitor.withLongCounters(), directory, 189 toFileVisitOptionSet(fileVisitOptions), maxDepth); 190 } 191 192 /** 193 * Cleans a directory including sub-directories without deleting directories. 194 * 195 * @param directory directory to clean. 196 * @return The visitation path counters. 197 * @throws IOException if an I/O error is thrown by a visitor method. 198 */ 199 public static PathCounters cleanDirectory(final Path directory) throws IOException { 200 return cleanDirectory(directory, EMPTY_DELETE_OPTION_ARRAY); 201 } 202 203 /** 204 * Cleans a directory including sub-directories without deleting directories. 205 * 206 * @param directory directory to clean. 207 * @param deleteOptions How to handle deletion. 208 * @return The visitation path counters. 209 * @throws IOException if an I/O error is thrown by a visitor method. 210 * @since 2.8.0 211 */ 212 public static PathCounters cleanDirectory(final Path directory, final DeleteOption... deleteOptions) 213 throws IOException { 214 return visitFileTree(new CleaningPathVisitor(Counters.longPathCounters(), deleteOptions), directory) 215 .getPathCounters(); 216 } 217 218 /** 219 * Copies a directory to another directory. 220 * 221 * @param sourceDirectory The source directory. 222 * @param targetDirectory The target directory. 223 * @param copyOptions Specifies how the copying should be done. 224 * @return The visitation path counters. 225 * @throws IOException if an I/O error is thrown by a visitor method. 226 */ 227 public static PathCounters copyDirectory(final Path sourceDirectory, final Path targetDirectory, 228 final CopyOption... copyOptions) throws IOException { 229 final Path absoluteSource = sourceDirectory.toAbsolutePath(); 230 return visitFileTree( 231 new CopyDirectoryVisitor(Counters.longPathCounters(), absoluteSource, targetDirectory, copyOptions), 232 absoluteSource).getPathCounters(); 233 } 234 235 /** 236 * Copies a URL to a directory. 237 * 238 * @param sourceFile The source URL. 239 * @param targetFile The target file. 240 * @param copyOptions Specifies how the copying should be done. 241 * @return The target file 242 * @throws IOException if an I/O error occurs. 243 * @see Files#copy(InputStream, Path, CopyOption...) 244 */ 245 public static Path copyFile(final URL sourceFile, final Path targetFile, final CopyOption... copyOptions) 246 throws IOException { 247 try (final InputStream inputStream = sourceFile.openStream()) { 248 Files.copy(inputStream, targetFile, copyOptions); 249 return targetFile; 250 } 251 } 252 253 /** 254 * Copies a file to a directory. 255 * 256 * @param sourceFile The source file. 257 * @param targetDirectory The target directory. 258 * @param copyOptions Specifies how the copying should be done. 259 * @return The target file 260 * @throws IOException if an I/O error occurs. 261 * @see Files#copy(Path, Path, CopyOption...) 262 */ 263 public static Path copyFileToDirectory(final Path sourceFile, final Path targetDirectory, 264 final CopyOption... copyOptions) throws IOException { 265 return Files.copy(sourceFile, targetDirectory.resolve(sourceFile.getFileName()), copyOptions); 266 } 267 268 /** 269 * Copies a URL to a directory. 270 * 271 * @param sourceFile The source URL. 272 * @param targetDirectory The target directory. 273 * @param copyOptions Specifies how the copying should be done. 274 * @return The target file 275 * @throws IOException if an I/O error occurs. 276 * @see Files#copy(InputStream, Path, CopyOption...) 277 */ 278 public static Path copyFileToDirectory(final URL sourceFile, final Path targetDirectory, 279 final CopyOption... copyOptions) throws IOException { 280 try (final InputStream inputStream = sourceFile.openStream()) { 281 Files.copy(inputStream, targetDirectory.resolve(sourceFile.getFile()), copyOptions); 282 return targetDirectory; 283 } 284 } 285 286 /** 287 * Counts aspects of a directory including sub-directories. 288 * 289 * @param directory directory to delete. 290 * @return The visitor used to count the given directory. 291 * @throws IOException if an I/O error is thrown by a visitor method. 292 */ 293 public static PathCounters countDirectory(final Path directory) throws IOException { 294 return visitFileTree(new CountingPathVisitor(Counters.longPathCounters()), directory).getPathCounters(); 295 } 296 297 /** 298 * Creates the parent directories for the given {@code path}. 299 * 300 * @param path The path to a file (or directory). 301 * @param attrs An optional list of file attributes to set atomically when creating the directories. 302 * @return The Path for the {@code path}'s parent directory or null if the given path has no parent. 303 * @throws IOException if an I/O error occurs. 304 * @since 2.9.0 305 */ 306 public static Path createParentDirectories(final Path path, final FileAttribute<?>... attrs) throws IOException { 307 final Path parent = path.getParent(); 308 if (parent == null) { 309 return null; 310 } 311 return Files.createDirectories(parent, attrs); 312 } 313 314 /** 315 * Gets the current directory. 316 * 317 * @return the current directory. 318 * 319 * @since 2.9.0 320 */ 321 public static Path current() { 322 return Paths.get(""); 323 } 324 325 /** 326 * Deletes a file or directory. If the path is a directory, delete it and all sub-directories. 327 * <p> 328 * The difference between File.delete() and this method are: 329 * </p> 330 * <ul> 331 * <li>A directory to delete does not have to be empty.</li> 332 * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a 333 * boolean. 334 * </ul> 335 * 336 * @param path file or directory to delete, must not be {@code null} 337 * @return The visitor used to delete the given directory. 338 * @throws NullPointerException if the directory is {@code null} 339 * @throws IOException if an I/O error is thrown by a visitor method or if an I/O error occurs. 340 */ 341 public static PathCounters delete(final Path path) throws IOException { 342 return delete(path, EMPTY_DELETE_OPTION_ARRAY); 343 } 344 345 /** 346 * Deletes a file or directory. If the path is a directory, delete it and all sub-directories. 347 * <p> 348 * The difference between File.delete() and this method are: 349 * </p> 350 * <ul> 351 * <li>A directory to delete does not have to be empty.</li> 352 * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a 353 * boolean. 354 * </ul> 355 * 356 * @param path file or directory to delete, must not be {@code null} 357 * @param deleteOptions How to handle deletion. 358 * @return The visitor used to delete the given directory. 359 * @throws NullPointerException if the directory is {@code null} 360 * @throws IOException if an I/O error is thrown by a visitor method or if an I/O error occurs. 361 * @since 2.8.0 362 */ 363 public static PathCounters delete(final Path path, final DeleteOption... deleteOptions) throws IOException { 364 // File deletion through Files deletes links, not targets, so use LinkOption.NOFOLLOW_LINKS. 365 return Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS) ? deleteDirectory(path, deleteOptions) 366 : deleteFile(path, deleteOptions); 367 } 368 369 /** 370 * Deletes a file or directory. If the path is a directory, delete it and all sub-directories. 371 * <p> 372 * The difference between File.delete() and this method are: 373 * </p> 374 * <ul> 375 * <li>A directory to delete does not have to be empty.</li> 376 * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a 377 * boolean. 378 * </ul> 379 * 380 * @param path file or directory to delete, must not be {@code null} 381 * @param linkOptions How to handle symbolic links. 382 * @param deleteOptions How to handle deletion. 383 * @return The visitor used to delete the given directory. 384 * @throws NullPointerException if the directory is {@code null} 385 * @throws IOException if an I/O error is thrown by a visitor method or if an I/O error occurs. 386 * @since 2.9.0 387 */ 388 public static PathCounters delete(final Path path, final LinkOption[] linkOptions, 389 final DeleteOption... deleteOptions) throws IOException { 390 // File deletion through Files deletes links, not targets, so use LinkOption.NOFOLLOW_LINKS. 391 return Files.isDirectory(path, linkOptions) ? deleteDirectory(path, linkOptions, deleteOptions) 392 : deleteFile(path, linkOptions, deleteOptions); 393 } 394 395 /** 396 * Deletes a directory including sub-directories. 397 * 398 * @param directory directory to delete. 399 * @return The visitor used to delete the given directory. 400 * @throws IOException if an I/O error is thrown by a visitor method. 401 */ 402 public static PathCounters deleteDirectory(final Path directory) throws IOException { 403 return deleteDirectory(directory, EMPTY_DELETE_OPTION_ARRAY); 404 } 405 406 /** 407 * Deletes a directory including sub-directories. 408 * 409 * @param directory directory to delete. 410 * @param deleteOptions How to handle deletion. 411 * @return The visitor used to delete the given directory. 412 * @throws IOException if an I/O error is thrown by a visitor method. 413 * @since 2.8.0 414 */ 415 public static PathCounters deleteDirectory(final Path directory, final DeleteOption... deleteOptions) 416 throws IOException { 417 return visitFileTree( 418 new DeletingPathVisitor(Counters.longPathCounters(), PathUtils.NOFOLLOW_LINK_OPTION_ARRAY, deleteOptions), 419 directory).getPathCounters(); 420 } 421 422 /** 423 * Deletes a directory including sub-directories. 424 * 425 * @param directory directory to delete. 426 * @param linkOptions How to handle symbolic links. 427 * @param deleteOptions How to handle deletion. 428 * @return The visitor used to delete the given directory. 429 * @throws IOException if an I/O error is thrown by a visitor method. 430 * @since 2.9.0 431 */ 432 public static PathCounters deleteDirectory(final Path directory, final LinkOption[] linkOptions, 433 final DeleteOption... deleteOptions) throws IOException { 434 return visitFileTree(new DeletingPathVisitor(Counters.longPathCounters(), linkOptions, deleteOptions), 435 directory).getPathCounters(); 436 } 437 438 /** 439 * Deletes the given file. 440 * 441 * @param file The file to delete. 442 * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file. 443 * @throws IOException if an I/O error occurs. 444 * @throws NoSuchFileException if the file is a directory. 445 */ 446 public static PathCounters deleteFile(final Path file) throws IOException { 447 return deleteFile(file, EMPTY_DELETE_OPTION_ARRAY); 448 } 449 450 /** 451 * Deletes the given file. 452 * 453 * @param file The file to delete. 454 * @param deleteOptions How to handle deletion. 455 * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file. 456 * @throws IOException if an I/O error occurs. 457 * @throws NoSuchFileException if the file is a directory. 458 * @since 2.8.0 459 */ 460 public static PathCounters deleteFile(final Path file, final DeleteOption... deleteOptions) throws IOException { 461 // Files.deleteIfExists() never follows links, so use LinkOption.NOFOLLOW_LINKS in other calls to Files. 462 return deleteFile(file, NOFOLLOW_LINK_OPTION_ARRAY, deleteOptions); 463 } 464 465 /** 466 * Deletes the given file. 467 * 468 * @param file The file to delete. 469 * @param linkOptions How to handle symbolic links. 470 * @param deleteOptions How to handle deletion. 471 * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file. 472 * @throws IOException if an I/O error occurs. 473 * @throws NoSuchFileException if the file is a directory. 474 * @since 2.9.0 475 */ 476 public static PathCounters deleteFile(final Path file, final LinkOption[] linkOptions, 477 final DeleteOption... deleteOptions) throws NoSuchFileException, IOException { 478 if (Files.isDirectory(file, linkOptions)) { 479 throw new NoSuchFileException(file.toString()); 480 } 481 final PathCounters pathCounts = Counters.longPathCounters(); 482 final boolean exists = Files.exists(file, linkOptions); 483 final long size = exists && !Files.isSymbolicLink(file) ? Files.size(file) : 0; 484 if (overrideReadOnly(deleteOptions) && exists) { 485 setReadOnly(file, false, linkOptions); 486 } 487 if (Files.deleteIfExists(file)) { 488 pathCounts.getFileCounter().increment(); 489 pathCounts.getByteCounter().add(size); 490 } 491 return pathCounts; 492 } 493 494 /** 495 * Compares the file sets of two Paths to determine if they are equal or not while considering file contents. The 496 * comparison includes all files in all sub-directories. 497 * 498 * @param path1 The first directory. 499 * @param path2 The second directory. 500 * @return Whether the two directories contain the same files while considering file contents. 501 * @throws IOException if an I/O error is thrown by a visitor method 502 */ 503 public static boolean directoryAndFileContentEquals(final Path path1, final Path path2) throws IOException { 504 return directoryAndFileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY, 505 EMPTY_FILE_VISIT_OPTION_ARRAY); 506 } 507 508 /** 509 * Compares the file sets of two Paths to determine if they are equal or not while considering file contents. The 510 * comparison includes all files in all sub-directories. 511 * 512 * @param path1 The first directory. 513 * @param path2 The second directory. 514 * @param linkOptions options to follow links. 515 * @param openOptions options to open files. 516 * @param fileVisitOption options to configure traversal. 517 * @return Whether the two directories contain the same files while considering file contents. 518 * @throws IOException if an I/O error is thrown by a visitor method 519 */ 520 public static boolean directoryAndFileContentEquals(final Path path1, final Path path2, 521 final LinkOption[] linkOptions, final OpenOption[] openOptions, final FileVisitOption[] fileVisitOption) 522 throws IOException { 523 // First walk both file trees and gather normalized paths. 524 if (path1 == null && path2 == null) { 525 return true; 526 } 527 if (path1 == null || path2 == null) { 528 return false; 529 } 530 if (Files.notExists(path1) && Files.notExists(path2)) { 531 return true; 532 } 533 final RelativeSortedPaths relativeSortedPaths = new RelativeSortedPaths(path1, path2, Integer.MAX_VALUE, 534 linkOptions, fileVisitOption); 535 // If the normalized path names and counts are not the same, no need to compare contents. 536 if (!relativeSortedPaths.equals) { 537 return false; 538 } 539 // Both visitors contain the same normalized paths, we can compare file contents. 540 final List<Path> fileList1 = relativeSortedPaths.relativeFileList1; 541 final List<Path> fileList2 = relativeSortedPaths.relativeFileList2; 542 for (final Path path : fileList1) { 543 final int binarySearch = Collections.binarySearch(fileList2, path); 544 if (binarySearch <= -1) { 545 throw new IllegalStateException("Unexpected mismatch."); 546 } 547 if (!fileContentEquals(path1.resolve(path), path2.resolve(path), linkOptions, openOptions)) { 548 return false; 549 } 550 } 551 return true; 552 } 553 554 /** 555 * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The 556 * comparison includes all files in all sub-directories. 557 * 558 * @param path1 The first directory. 559 * @param path2 The second directory. 560 * @return Whether the two directories contain the same files without considering file contents. 561 * @throws IOException if an I/O error is thrown by a visitor method 562 */ 563 public static boolean directoryContentEquals(final Path path1, final Path path2) throws IOException { 564 return directoryContentEquals(path1, path2, Integer.MAX_VALUE, EMPTY_LINK_OPTION_ARRAY, 565 EMPTY_FILE_VISIT_OPTION_ARRAY); 566 } 567 568 /** 569 * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The 570 * comparison includes all files in all sub-directories. 571 * 572 * @param path1 The first directory. 573 * @param path2 The second directory. 574 * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 575 * @param linkOptions options to follow links. 576 * @param fileVisitOptions options to configure the traversal 577 * @return Whether the two directories contain the same files without considering file contents. 578 * @throws IOException if an I/O error is thrown by a visitor method 579 */ 580 public static boolean directoryContentEquals(final Path path1, final Path path2, final int maxDepth, 581 final LinkOption[] linkOptions, final FileVisitOption[] fileVisitOptions) throws IOException { 582 return new RelativeSortedPaths(path1, path2, maxDepth, linkOptions, fileVisitOptions).equals; 583 } 584 585 /** 586 * Compares the file contents of two Paths to determine if they are equal or not. 587 * <p> 588 * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}. 589 * </p> 590 * 591 * @param path1 the first stream. 592 * @param path2 the second stream. 593 * @return true if the content of the streams are equal or they both don't exist, false otherwise. 594 * @throws NullPointerException if either input is null. 595 * @throws IOException if an I/O error occurs. 596 * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File) 597 */ 598 public static boolean fileContentEquals(final Path path1, final Path path2) throws IOException { 599 return fileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY); 600 } 601 602 /** 603 * Compares the file contents of two Paths to determine if they are equal or not. 604 * <p> 605 * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}. 606 * </p> 607 * 608 * @param path1 the first stream. 609 * @param path2 the second stream. 610 * @param linkOptions options specifying how files are followed. 611 * @param openOptions options specifying how files are opened. 612 * @return true if the content of the streams are equal or they both don't exist, false otherwise. 613 * @throws NullPointerException if either input is null. 614 * @throws IOException if an I/O error occurs. 615 * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File) 616 */ 617 public static boolean fileContentEquals(final Path path1, final Path path2, final LinkOption[] linkOptions, 618 final OpenOption[] openOptions) throws IOException { 619 if (path1 == null && path2 == null) { 620 return true; 621 } 622 if (path1 == null || path2 == null) { 623 return false; 624 } 625 final Path nPath1 = path1.normalize(); 626 final Path nPath2 = path2.normalize(); 627 final boolean path1Exists = Files.exists(nPath1, linkOptions); 628 if (path1Exists != Files.exists(nPath2, linkOptions)) { 629 return false; 630 } 631 if (!path1Exists) { 632 // Two not existing files are equal? 633 // Same as FileUtils 634 return true; 635 } 636 if (Files.isDirectory(nPath1, linkOptions)) { 637 // don't compare directory contents. 638 throw new IOException("Can't compare directories, only files: " + nPath1); 639 } 640 if (Files.isDirectory(nPath2, linkOptions)) { 641 // don't compare directory contents. 642 throw new IOException("Can't compare directories, only files: " + nPath2); 643 } 644 if (Files.size(nPath1) != Files.size(nPath2)) { 645 // lengths differ, cannot be equal 646 return false; 647 } 648 if (path1.equals(path2)) { 649 // same file 650 return true; 651 } 652 try (final InputStream inputStream1 = Files.newInputStream(nPath1, openOptions); 653 final InputStream inputStream2 = Files.newInputStream(nPath2, openOptions)) { 654 return IOUtils.contentEquals(inputStream1, inputStream2); 655 } 656 } 657 658 /** 659 * <p> 660 * Applies an {@link IOFileFilter} to the provided {@link File} objects. The resulting array is a subset of the 661 * original file list that matches the provided filter. 662 * </p> 663 * 664 * <p> 665 * The {@link Set} returned by this method is not guaranteed to be thread safe. 666 * </p> 667 * 668 * <pre> 669 * Set<File> allFiles = ... 670 * Set<File> javaFiles = FileFilterUtils.filterSet(allFiles, 671 * FileFilterUtils.suffixFileFilter(".java")); 672 * </pre> 673 * 674 * @param filter the filter to apply to the set of files. 675 * @param paths the array of files to apply the filter to. 676 * 677 * @return a subset of {@code files} that is accepted by the file filter. 678 * @throws IllegalArgumentException if the filter is {@code null} or {@code files} contains a {@code null} 679 * value. 680 * 681 * @since 2.9.0 682 */ 683 public static Path[] filter(final PathFilter filter, final Path... paths) { 684 Objects.requireNonNull(filter, "filter"); 685 if (paths == null) { 686 return EMPTY_PATH_ARRAY; 687 } 688 return filterPaths(filter, Stream.of(paths), Collectors.toList()).toArray(EMPTY_PATH_ARRAY); 689 } 690 691 private static <R, A> R filterPaths(final PathFilter filter, final Stream<Path> stream, 692 final Collector<? super Path, A, R> collector) { 693 Objects.requireNonNull(filter, "filter"); 694 Objects.requireNonNull(collector, "collector"); 695 if (stream == null) { 696 return Stream.<Path>empty().collect(collector); 697 } 698 return stream.filter(p -> { 699 try { 700 return p != null && filter.accept(p, readBasicFileAttributes(p)) == FileVisitResult.CONTINUE; 701 } catch (final IOException e) { 702 return false; 703 } 704 }).collect(collector); 705 } 706 707 /** 708 * Reads the access control list from a file attribute view. 709 * 710 * @param sourcePath the path to the file. 711 * @return a file attribute view of the specified type, or null if the attribute view type is not available. 712 * @throws IOException if an I/O error occurs. 713 * @since 2.8.0 714 */ 715 public static List<AclEntry> getAclEntryList(final Path sourcePath) throws IOException { 716 final AclFileAttributeView fileAttributeView = Files.getFileAttributeView(sourcePath, 717 AclFileAttributeView.class); 718 return fileAttributeView == null ? null : fileAttributeView.getAcl(); 719 } 720 721 /** 722 * Tests whether the specified {@code Path} is a directory or not. Implemented as a 723 * null-safe delegate to {@code Files.isDirectory(Path path, LinkOption... options)}. 724 * 725 * @param path the path to the file. 726 * @param options options indicating how symbolic links are handled 727 * @return {@code true} if the file is a directory; {@code false} if 728 * the path is null, the file does not exist, is not a directory, or it cannot 729 * be determined if the file is a directory or not. 730 * @throws SecurityException In the case of the default provider, and a security manager is installed, the 731 * {@link SecurityManager#checkRead(String) checkRead} method is invoked to check read 732 * access to the directory. 733 * @since 2.9.0 734 */ 735 public static boolean isDirectory(final Path path, final LinkOption... options) { 736 return path != null && Files.isDirectory(path, options); 737 } 738 739 /** 740 * Tests whether the given file or directory is empty. 741 * 742 * @param path the file or directory to query. 743 * @return whether the file or directory is empty. 744 * @throws IOException if an I/O error occurs. 745 */ 746 public static boolean isEmpty(final Path path) throws IOException { 747 return Files.isDirectory(path) ? isEmptyDirectory(path) : isEmptyFile(path); 748 } 749 750 /** 751 * Tests whether the directory is empty. 752 * 753 * @param directory the directory to query. 754 * @return whether the directory is empty. 755 * @throws NotDirectoryException if the file could not otherwise be opened because it is not a directory 756 * <i>(optional specific exception)</i>. 757 * @throws IOException if an I/O error occurs. 758 * @throws SecurityException In the case of the default provider, and a security manager is installed, the 759 * {@link SecurityManager#checkRead(String) checkRead} method is invoked to check read 760 * access to the directory. 761 */ 762 public static boolean isEmptyDirectory(final Path directory) throws IOException { 763 try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) { 764 return !directoryStream.iterator().hasNext(); 765 } 766 } 767 768 /** 769 * Tests whether the given file is empty. 770 * 771 * @param file the file to query. 772 * @return whether the file is empty. 773 * @throws IOException if an I/O error occurs. 774 * @throws SecurityException In the case of the default provider, and a security manager is installed, its 775 * {@link SecurityManager#checkRead(String) checkRead} method denies read access to the 776 * file. 777 */ 778 public static boolean isEmptyFile(final Path file) throws IOException { 779 return Files.size(file) <= 0; 780 } 781 782 /** 783 * Tests if the specified {@code Path} is newer than the specified time reference. 784 * 785 * @param file the {@code Path} of which the modification date must be compared 786 * @param timeMillis the time reference measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970) 787 * @param options options indicating how symbolic links are handled * @return true if the {@code Path} exists and 788 * has been modified after the given time reference. 789 * @return true if the {@code Path} exists and has been modified after the given time reference. 790 * @throws IOException if an I/O error occurs. 791 * @throws NullPointerException if the file is {@code null} 792 * @since 2.9.0 793 */ 794 public static boolean isNewer(final Path file, final long timeMillis, final LinkOption... options) 795 throws IOException { 796 Objects.requireNonNull(file, "file"); 797 if (Files.notExists(file)) { 798 return false; 799 } 800 return Files.getLastModifiedTime(file, options).toMillis() > timeMillis; 801 } 802 803 /** 804 * Tests whether the specified {@code Path} is a regular file or not. Implemented as a 805 * null-safe delegate to {@code Files.isRegularFile(Path path, LinkOption... options)}. 806 * 807 * @param path the path to the file. 808 * @param options options indicating how symbolic links are handled 809 * @return {@code true} if the file is a regular file; {@code false} if 810 * the path is null, the file does not exist, is not a directory, or it cannot 811 * be determined if the file is a regular file or not. 812 * @throws SecurityException In the case of the default provider, and a security manager is installed, the 813 * {@link SecurityManager#checkRead(String) checkRead} method is invoked to check read 814 * access to the directory. 815 * @since 2.9.0 816 */ 817 public static boolean isRegularFile(final Path path, final LinkOption... options) { 818 return path != null && Files.isRegularFile(path, options); 819 } 820 821 /** 822 * Creates a new DirectoryStream for Paths rooted at the given directory. 823 * 824 * @param dir the path to the directory to stream. 825 * @param pathFilter the directory stream filter. 826 * @return a new instance. 827 * @throws IOException if an I/O error occurs. 828 */ 829 public static DirectoryStream<Path> newDirectoryStream(final Path dir, final PathFilter pathFilter) 830 throws IOException { 831 return Files.newDirectoryStream(dir, new DirectoryStreamFilter(pathFilter)); 832 } 833 834 /** 835 * Returns true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}. 836 * 837 * @param deleteOptions the array to test 838 * @return true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}. 839 */ 840 private static boolean overrideReadOnly(final DeleteOption... deleteOptions) { 841 if (deleteOptions == null) { 842 return false; 843 } 844 return Stream.of(deleteOptions).anyMatch(e -> e == StandardDeleteOption.OVERRIDE_READ_ONLY); 845 } 846 847 /** 848 * Shorthand for {@code Files.readAttributes(path, BasicFileAttributes.class)} 849 * 850 * @param path the path to read. 851 * @return the path attributes. 852 * @throws IOException if an I/O error occurs. 853 * @since 2.9.0 854 */ 855 public static BasicFileAttributes readBasicFileAttributes(final Path path) throws IOException { 856 return Files.readAttributes(path, BasicFileAttributes.class); 857 } 858 859 /** 860 * Shorthand for {@code Files.readAttributes(path, BasicFileAttributes.class)} while wrapping {@link IOException} 861 * as {@link UncheckedIOException}. 862 * 863 * @param path the path to read. 864 * @return the path attributes. 865 * @throws UncheckedIOException if an I/O error occurs 866 * @since 2.9.0 867 */ 868 public static BasicFileAttributes readBasicFileAttributesUnchecked(final Path path) { 869 try { 870 return readBasicFileAttributes(path); 871 } catch (final IOException e) { 872 throw new UncheckedIOException(e); 873 } 874 } 875 876 /** 877 * Relativizes all files in the given {@code collection} against a {@code parent}. 878 * 879 * @param collection The collection of paths to relativize. 880 * @param parent relativizes against this parent path. 881 * @param sort Whether to sort the result. 882 * @param comparator How to sort. 883 * @return A collection of relativized paths, optionally sorted. 884 */ 885 static List<Path> relativize(final Collection<Path> collection, final Path parent, final boolean sort, 886 final Comparator<? super Path> comparator) { 887 Stream<Path> stream = collection.stream().map(parent::relativize); 888 if (sort) { 889 stream = comparator == null ? stream.sorted() : stream.sorted(comparator); 890 } 891 return stream.collect(Collectors.toList()); 892 } 893 894 /** 895 * Sets the given Path to the {@code readOnly} value. 896 * <p> 897 * This behavior is OS dependent. 898 * </p> 899 * 900 * @param path The path to set. 901 * @param readOnly true for read-only, false for not read-only. 902 * @param linkOptions options indicating how symbolic links are handled. 903 * @return The given path. 904 * @throws IOException if an I/O error occurs. 905 * @since 2.8.0 906 */ 907 public static Path setReadOnly(final Path path, final boolean readOnly, final LinkOption... linkOptions) 908 throws IOException { 909 final List<Exception> causeList = new ArrayList<>(2); 910 final DosFileAttributeView fileAttributeView = Files.getFileAttributeView(path, DosFileAttributeView.class, 911 linkOptions); 912 if (fileAttributeView != null) { 913 try { 914 fileAttributeView.setReadOnly(readOnly); 915 return path; 916 } catch (final IOException e) { 917 // ignore for now, retry with PosixFileAttributeView 918 causeList.add(e); 919 } 920 } 921 final PosixFileAttributeView posixFileAttributeView = Files.getFileAttributeView(path, 922 PosixFileAttributeView.class, linkOptions); 923 if (posixFileAttributeView != null) { 924 // Works on Windows but not on Ubuntu: 925 // Files.setAttribute(path, "unix:readonly", readOnly, options); 926 // java.lang.IllegalArgumentException: 'unix:readonly' not recognized 927 final PosixFileAttributes readAttributes = posixFileAttributeView.readAttributes(); 928 final Set<PosixFilePermission> permissions = readAttributes.permissions(); 929 permissions.remove(PosixFilePermission.OWNER_WRITE); 930 permissions.remove(PosixFilePermission.GROUP_WRITE); 931 permissions.remove(PosixFilePermission.OTHERS_WRITE); 932 try { 933 return Files.setPosixFilePermissions(path, permissions); 934 } catch (final IOException e) { 935 causeList.add(e); 936 } 937 } 938 if (!causeList.isEmpty()) { 939 throw new IOExceptionList(path.toString(), causeList); 940 } 941 throw new IOException( 942 String.format("No DosFileAttributeView or PosixFileAttributeView for '%s' (linkOptions=%s)", path, 943 Arrays.toString(linkOptions))); 944 } 945 946 /** 947 * Converts an array of {@link FileVisitOption} to a {@link Set}. 948 * 949 * @param fileVisitOptions input array. 950 * @return a new Set. 951 */ 952 static Set<FileVisitOption> toFileVisitOptionSet(final FileVisitOption... fileVisitOptions) { 953 return fileVisitOptions == null ? EnumSet.noneOf(FileVisitOption.class) 954 : Stream.of(fileVisitOptions).collect(Collectors.toSet()); 955 } 956 957 /** 958 * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor. 959 * 960 * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path. 961 * 962 * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}. 963 * @param directory See {@link Files#walkFileTree(Path,FileVisitor)}. 964 * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}. 965 * @return the given visitor. 966 * 967 * @throws IOException if an I/O error is thrown by a visitor method 968 */ 969 public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path directory) 970 throws IOException { 971 Files.walkFileTree(directory, visitor); 972 return visitor; 973 } 974 975 /** 976 * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor. 977 * 978 * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path. 979 * 980 * @param start See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 981 * @param options See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 982 * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 983 * @param visitor See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 984 * @param <T> See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 985 * @return the given visitor. 986 * 987 * @throws IOException if an I/O error is thrown by a visitor method 988 */ 989 public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path start, 990 final Set<FileVisitOption> options, final int maxDepth) throws IOException { 991 Files.walkFileTree(start, options, maxDepth, visitor); 992 return visitor; 993 } 994 995 /** 996 * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor. 997 * 998 * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path. 999 * 1000 * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}. 1001 * @param first See {@link Paths#get(String,String[])}. 1002 * @param more See {@link Paths#get(String,String[])}. 1003 * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}. 1004 * @return the given visitor. 1005 * 1006 * @throws IOException if an I/O error is thrown by a visitor method 1007 */ 1008 public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final String first, 1009 final String... more) throws IOException { 1010 return visitFileTree(visitor, Paths.get(first, more)); 1011 } 1012 1013 /** 1014 * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor. 1015 * 1016 * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path. 1017 * 1018 * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}. 1019 * @param uri See {@link Paths#get(URI)}. 1020 * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}. 1021 * @return the given visitor. 1022 * 1023 * @throws IOException if an I/O error is thrown by a visitor method 1024 */ 1025 public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final URI uri) 1026 throws IOException { 1027 return visitFileTree(visitor, Paths.get(uri)); 1028 } 1029 1030 /** 1031 * Returns a stream of filtered paths. 1032 * 1033 * @param start the start path 1034 * @param pathFilter the path filter 1035 * @param maxDepth the maximum depth of directories to walk. 1036 * @param readAttributes whether to call the filters with file attributes (false passes null). 1037 * @param options the options to configure the walk. 1038 * @return a filtered stream of paths. 1039 * @throws IOException if an I/O error is thrown when accessing the starting file. 1040 * @since 2.9.0 1041 */ 1042 public static Stream<Path> walk(final Path start, final PathFilter pathFilter, final int maxDepth, 1043 final boolean readAttributes, final FileVisitOption... options) throws IOException { 1044 return Files.walk(start, maxDepth, options).filter(path -> pathFilter.accept(path, 1045 readAttributes ? readBasicFileAttributesUnchecked(path) : null) == FileVisitResult.CONTINUE); 1046 } 1047 1048 /** 1049 * Does allow to instantiate. 1050 */ 1051 private PathUtils() { 1052 // do not instantiate. 1053 } 1054 1055}