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.IOException; 021import java.io.InputStream; 022import java.net.URI; 023import java.net.URL; 024import java.nio.file.CopyOption; 025import java.nio.file.DirectoryStream; 026import java.nio.file.FileVisitOption; 027import java.nio.file.FileVisitor; 028import java.nio.file.Files; 029import java.nio.file.LinkOption; 030import java.nio.file.NoSuchFileException; 031import java.nio.file.OpenOption; 032import java.nio.file.Path; 033import java.nio.file.Paths; 034import java.nio.file.attribute.AclEntry; 035import java.nio.file.attribute.AclFileAttributeView; 036import java.nio.file.attribute.DosFileAttributeView; 037import java.nio.file.attribute.PosixFileAttributeView; 038import java.nio.file.attribute.PosixFileAttributes; 039import java.nio.file.attribute.PosixFilePermission; 040import java.util.Arrays; 041import java.util.Collection; 042import java.util.Collections; 043import java.util.Comparator; 044import java.util.EnumSet; 045import java.util.List; 046import java.util.Set; 047import java.util.stream.Collectors; 048import java.util.stream.Stream; 049 050import org.apache.commons.io.IOUtils; 051import org.apache.commons.io.file.Counters.PathCounters; 052 053/** 054 * NIO Path utilities. 055 * 056 * @since 2.7 057 */ 058public final class PathUtils { 059 060 /** 061 * Private worker/holder that computes and tracks relative path names and their equality. We reuse the sorted 062 * relative lists when comparing directories. 063 */ 064 private static class RelativeSortedPaths { 065 066 final boolean equals; 067 // final List<Path> relativeDirList1; // might need later? 068 // final List<Path> relativeDirList2; // might need later? 069 final List<Path> relativeFileList1; 070 final List<Path> relativeFileList2; 071 072 /** 073 * Constructs and initializes a new instance by accumulating directory and file info. 074 * 075 * @param dir1 First directory to compare. 076 * @param dir2 Seconds directory to compare. 077 * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 078 * @param linkOptions Options indicating how symbolic links are handled. 079 * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 080 * @throws IOException if an I/O error is thrown by a visitor method. 081 */ 082 private RelativeSortedPaths(final Path dir1, final Path dir2, final int maxDepth, 083 final LinkOption[] linkOptions, final FileVisitOption[] fileVisitOptions) throws IOException { 084 List<Path> tmpRelativeDirList1 = null; 085 List<Path> tmpRelativeDirList2 = null; 086 List<Path> tmpRelativeFileList1 = null; 087 List<Path> tmpRelativeFileList2 = null; 088 if (dir1 == null && dir2 == null) { 089 equals = true; 090 } else if (dir1 == null ^ dir2 == null) { 091 equals = false; 092 } else { 093 final boolean parentDirExists1 = Files.exists(dir1, linkOptions); 094 final boolean parentDirExists2 = Files.exists(dir2, linkOptions); 095 if (!parentDirExists1 || !parentDirExists2) { 096 equals = !parentDirExists1 && !parentDirExists2; 097 } else { 098 final AccumulatorPathVisitor visitor1 = accumulate(dir1, maxDepth, fileVisitOptions); 099 final AccumulatorPathVisitor visitor2 = accumulate(dir2, maxDepth, fileVisitOptions); 100 if (visitor1.getDirList().size() != visitor2.getDirList().size() 101 || visitor1.getFileList().size() != visitor2.getFileList().size()) { 102 equals = false; 103 } else { 104 tmpRelativeDirList1 = visitor1.relativizeDirectories(dir1, true, null); 105 tmpRelativeDirList2 = visitor2.relativizeDirectories(dir2, true, null); 106 if (!tmpRelativeDirList1.equals(tmpRelativeDirList2)) { 107 equals = false; 108 } else { 109 tmpRelativeFileList1 = visitor1.relativizeFiles(dir1, true, null); 110 tmpRelativeFileList2 = visitor2.relativizeFiles(dir2, true, null); 111 equals = tmpRelativeFileList1.equals(tmpRelativeFileList2); 112 } 113 } 114 } 115 } 116 // relativeDirList1 = tmpRelativeDirList1; 117 // relativeDirList2 = tmpRelativeDirList2; 118 relativeFileList1 = tmpRelativeFileList1; 119 relativeFileList2 = tmpRelativeFileList2; 120 } 121 } 122 123 /** 124 * Empty {@link LinkOption} array. 125 * 126 * @since 2.8.0 127 */ 128 public static final DeleteOption[] EMPTY_DELETE_OPTION_ARRAY = new DeleteOption[0]; 129 130 /** 131 * Empty {@link FileVisitOption} array. 132 */ 133 public static final FileVisitOption[] EMPTY_FILE_VISIT_OPTION_ARRAY = new FileVisitOption[0]; 134 135 /** 136 * Empty {@link LinkOption} array. 137 */ 138 public static final LinkOption[] EMPTY_LINK_OPTION_ARRAY = new LinkOption[0]; 139 140 /** 141 * Empty {@link OpenOption} array. 142 */ 143 public static final OpenOption[] EMPTY_OPEN_OPTION_ARRAY = new OpenOption[0]; 144 145 /** 146 * Accumulates file tree information in a {@link AccumulatorPathVisitor}. 147 * 148 * @param directory The directory to accumulate information. 149 * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 150 * @param fileVisitOptions See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 151 * @throws IOException if an I/O error is thrown by a visitor method. 152 * @return file tree information. 153 */ 154 private static AccumulatorPathVisitor accumulate(final Path directory, final int maxDepth, 155 final FileVisitOption[] fileVisitOptions) throws IOException { 156 return visitFileTree(AccumulatorPathVisitor.withLongCounters(), directory, 157 toFileVisitOptionSet(fileVisitOptions), maxDepth); 158 } 159 160 /** 161 * Cleans a directory including sub-directories without deleting directories. 162 * 163 * @param directory directory to clean. 164 * @return The visitation path counters. 165 * @throws IOException if an I/O error is thrown by a visitor method. 166 */ 167 public static PathCounters cleanDirectory(final Path directory) throws IOException { 168 return cleanDirectory(directory, EMPTY_DELETE_OPTION_ARRAY); 169 } 170 171 /** 172 * Cleans a directory including sub-directories without deleting directories. 173 * 174 * @param directory directory to clean. 175 * @param options options indicating how deletion is handled. 176 * @return The visitation path counters. 177 * @throws IOException if an I/O error is thrown by a visitor method. 178 * @since 2.8.0 179 */ 180 public static PathCounters cleanDirectory(final Path directory, final DeleteOption... options) throws IOException { 181 return visitFileTree(new CleaningPathVisitor(Counters.longPathCounters(), options), directory) 182 .getPathCounters(); 183 } 184 185 /** 186 * Copies a directory to another directory. 187 * 188 * @param sourceDirectory The source directory. 189 * @param targetDirectory The target directory. 190 * @param copyOptions Specifies how the copying should be done. 191 * @return The visitation path counters. 192 * @throws IOException if an I/O error is thrown by a visitor method. 193 */ 194 public static PathCounters copyDirectory(final Path sourceDirectory, final Path targetDirectory, 195 final CopyOption... copyOptions) throws IOException { 196 return visitFileTree( 197 new CopyDirectoryVisitor(Counters.longPathCounters(), sourceDirectory, targetDirectory, copyOptions), 198 sourceDirectory).getPathCounters(); 199 } 200 201 /** 202 * Copies a URL to a directory. 203 * 204 * @param sourceFile The source URL. 205 * @param targetFile The target file. 206 * @param copyOptions Specifies how the copying should be done. 207 * @return The target file 208 * @throws IOException if an I/O error occurs 209 * @see Files#copy(InputStream, Path, CopyOption...) 210 */ 211 public static Path copyFile(final URL sourceFile, final Path targetFile, final CopyOption... copyOptions) 212 throws IOException { 213 try (final InputStream inputStream = sourceFile.openStream()) { 214 Files.copy(inputStream, targetFile, copyOptions); 215 return targetFile; 216 } 217 } 218 219 /** 220 * Copies a file to a directory. 221 * 222 * @param sourceFile The source file. 223 * @param targetDirectory The target directory. 224 * @param copyOptions Specifies how the copying should be done. 225 * @return The target file 226 * @throws IOException if an I/O error occurs 227 * @see Files#copy(Path, Path, CopyOption...) 228 */ 229 public static Path copyFileToDirectory(final Path sourceFile, final Path targetDirectory, 230 final CopyOption... copyOptions) throws IOException { 231 return Files.copy(sourceFile, targetDirectory.resolve(sourceFile.getFileName()), copyOptions); 232 } 233 234 /** 235 * Copies a URL to a directory. 236 * 237 * @param sourceFile The source URL. 238 * @param targetDirectory The target directory. 239 * @param copyOptions Specifies how the copying should be done. 240 * @return The target file 241 * @throws IOException if an I/O error occurs 242 * @see Files#copy(InputStream, Path, CopyOption...) 243 */ 244 public static Path copyFileToDirectory(final URL sourceFile, final Path targetDirectory, 245 final CopyOption... copyOptions) throws IOException { 246 try (final InputStream inputStream = sourceFile.openStream()) { 247 Files.copy(inputStream, targetDirectory.resolve(sourceFile.getFile()), copyOptions); 248 return targetDirectory; 249 } 250 } 251 252 /** 253 * Counts aspects of a directory including sub-directories. 254 * 255 * @param directory directory to delete. 256 * @return The visitor used to count the given directory. 257 * @throws IOException if an I/O error is thrown by a visitor method. 258 */ 259 public static PathCounters countDirectory(final Path directory) throws IOException { 260 return visitFileTree(new CountingPathVisitor(Counters.longPathCounters()), directory).getPathCounters(); 261 } 262 263 /** 264 * Deletes a file or directory. If the path is a directory, delete it and all sub-directories. 265 * <p> 266 * The difference between File.delete() and this method are: 267 * </p> 268 * <ul> 269 * <li>A directory to delete does not have to be empty.</li> 270 * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a 271 * boolean. 272 * </ul> 273 * 274 * @param path file or directory to delete, must not be {@code null} 275 * @return The visitor used to delete the given directory. 276 * @throws NullPointerException if the directory is {@code null} 277 * @throws IOException if an I/O error is thrown by a visitor method or if an I/O error occurs. 278 */ 279 public static PathCounters delete(final Path path) throws IOException { 280 return delete(path, EMPTY_DELETE_OPTION_ARRAY); 281 } 282 283 /** 284 * Deletes a file or directory. If the path is a directory, delete it and all sub-directories. 285 * <p> 286 * The difference between File.delete() and this method are: 287 * </p> 288 * <ul> 289 * <li>A directory to delete does not have to be empty.</li> 290 * <li>You get exceptions when a file or directory cannot be deleted; {@link java.io.File#delete()} returns a 291 * boolean. 292 * </ul> 293 * 294 * @param path file or directory to delete, must not be {@code null} 295 * @param options options indicating how deletion is handled. 296 * @return The visitor used to delete the given directory. 297 * @throws NullPointerException if the directory is {@code null} 298 * @throws IOException if an I/O error is thrown by a visitor method or if an I/O error occurs. 299 * @since 2.8.0 300 */ 301 public static PathCounters delete(final Path path, final DeleteOption... options) throws IOException { 302 // File deletion through Files deletes links, not targets, so use LinkOption.NOFOLLOW_LINKS. 303 return Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS) ? deleteDirectory(path, options) 304 : deleteFile(path, options); 305 } 306 307 /** 308 * Deletes a directory including sub-directories. 309 * 310 * @param directory directory to delete. 311 * @return The visitor used to delete the given directory. 312 * @throws IOException if an I/O error is thrown by a visitor method. 313 */ 314 public static PathCounters deleteDirectory(final Path directory) throws IOException { 315 return deleteDirectory(directory, EMPTY_DELETE_OPTION_ARRAY); 316 } 317 318 /** 319 * Deletes a directory including sub-directories. 320 * 321 * @param directory directory to delete. 322 * @param options options indicating how deletion is handled. 323 * @return The visitor used to delete the given directory. 324 * @throws IOException if an I/O error is thrown by a visitor method. 325 * @since 2.8.0 326 */ 327 public static PathCounters deleteDirectory(final Path directory, final DeleteOption... options) throws IOException { 328 return visitFileTree(new DeletingPathVisitor(Counters.longPathCounters(), options), directory) 329 .getPathCounters(); 330 } 331 332 /** 333 * Deletes the given file. 334 * 335 * @param file The file to delete. 336 * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file. 337 * @throws IOException if an I/O error occurs. 338 * @throws NoSuchFileException if the file is a directory. 339 */ 340 public static PathCounters deleteFile(final Path file) throws IOException { 341 return deleteFile(file, EMPTY_DELETE_OPTION_ARRAY); 342 } 343 344 /** 345 * Deletes the given file. 346 * 347 * @param file The file to delete. 348 * @param options options indicating how deletion is handled. 349 * @return A visitor with path counts set to 1 file, 0 directories, and the size of the deleted file. 350 * @throws IOException if an I/O error occurs. 351 * @throws NoSuchFileException if the file is a directory. 352 * @since 2.8.0 353 */ 354 public static PathCounters deleteFile(final Path file, final DeleteOption... options) throws IOException { 355 // Files.deleteIfExists() never follows links, so use LinkOption.NOFOLLOW_LINKS in other calls to Files. 356 if (Files.isDirectory(file, LinkOption.NOFOLLOW_LINKS)) { 357 throw new NoSuchFileException(file.toString()); 358 } 359 final PathCounters pathCounts = Counters.longPathCounters(); 360 final boolean exists = Files.exists(file, LinkOption.NOFOLLOW_LINKS); 361 final long size = exists ? Files.size(file) : 0; 362 if (overrideReadOnly(options) && exists) { 363 setReadOnly(file, false, LinkOption.NOFOLLOW_LINKS); 364 } 365 if (Files.deleteIfExists(file)) { 366 pathCounts.getFileCounter().increment(); 367 pathCounts.getByteCounter().add(size); 368 } 369 return pathCounts; 370 } 371 372 /** 373 * Returns true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}. 374 * 375 * @param options the array to test 376 * @return true if the given options contain {@link StandardDeleteOption#OVERRIDE_READ_ONLY}. 377 */ 378 private static boolean overrideReadOnly(final DeleteOption[] options) { 379 if (options == null) { 380 return false; 381 } 382 for (final DeleteOption deleteOption : options) { 383 if (deleteOption == StandardDeleteOption.OVERRIDE_READ_ONLY) { 384 return true; 385 } 386 } 387 return false; 388 } 389 390 /** 391 * Compares the file sets of two Paths to determine if they are equal or not while considering file contents. The 392 * comparison includes all files in all sub-directories. 393 * 394 * @param path1 The first directory. 395 * @param path2 The second directory. 396 * @return Whether the two directories contain the same files while considering file contents. 397 * @throws IOException if an I/O error is thrown by a visitor method 398 */ 399 public static boolean directoryAndFileContentEquals(final Path path1, final Path path2) throws IOException { 400 return directoryAndFileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY, 401 EMPTY_FILE_VISIT_OPTION_ARRAY); 402 } 403 404 /** 405 * Compares the file sets of two Paths to determine if they are equal or not while considering file contents. The 406 * comparison includes all files in all sub-directories. 407 * 408 * @param path1 The first directory. 409 * @param path2 The second directory. 410 * @param linkOptions options to follow links. 411 * @param openOptions options to open files. 412 * @param fileVisitOption options to configure traversal. 413 * @return Whether the two directories contain the same files while considering file contents. 414 * @throws IOException if an I/O error is thrown by a visitor method 415 */ 416 public static boolean directoryAndFileContentEquals(final Path path1, final Path path2, 417 final LinkOption[] linkOptions, final OpenOption[] openOptions, final FileVisitOption[] fileVisitOption) 418 throws IOException { 419 // First walk both file trees and gather normalized paths. 420 if (path1 == null && path2 == null) { 421 return true; 422 } 423 if (path1 == null ^ path2 == null) { 424 return false; 425 } 426 if (!Files.exists(path1) && !Files.exists(path2)) { 427 return true; 428 } 429 final RelativeSortedPaths relativeSortedPaths = new RelativeSortedPaths(path1, path2, Integer.MAX_VALUE, 430 linkOptions, fileVisitOption); 431 // If the normalized path names and counts are not the same, no need to compare contents. 432 if (!relativeSortedPaths.equals) { 433 return false; 434 } 435 // Both visitors contain the same normalized paths, we can compare file contents. 436 final List<Path> fileList1 = relativeSortedPaths.relativeFileList1; 437 final List<Path> fileList2 = relativeSortedPaths.relativeFileList2; 438 for (final Path path : fileList1) { 439 final int binarySearch = Collections.binarySearch(fileList2, path); 440 if (binarySearch > -1) { 441 if (!fileContentEquals(path1.resolve(path), path2.resolve(path), linkOptions, openOptions)) { 442 return false; 443 } 444 } else { 445 throw new IllegalStateException("Unexpected mismatch."); 446 } 447 } 448 return true; 449 } 450 451 /** 452 * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The 453 * comparison includes all files in all sub-directories. 454 * 455 * @param path1 The first directory. 456 * @param path2 The second directory. 457 * @return Whether the two directories contain the same files without considering file contents. 458 * @throws IOException if an I/O error is thrown by a visitor method 459 */ 460 public static boolean directoryContentEquals(final Path path1, final Path path2) throws IOException { 461 return directoryContentEquals(path1, path2, Integer.MAX_VALUE, EMPTY_LINK_OPTION_ARRAY, 462 EMPTY_FILE_VISIT_OPTION_ARRAY); 463 } 464 465 /** 466 * Compares the file sets of two Paths to determine if they are equal or not without considering file contents. The 467 * comparison includes all files in all sub-directories. 468 * 469 * @param path1 The first directory. 470 * @param path2 The second directory. 471 * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 472 * @param linkOptions options to follow links. 473 * @param fileVisitOptions options to configure the traversal 474 * @return Whether the two directories contain the same files without considering file contents. 475 * @throws IOException if an I/O error is thrown by a visitor method 476 */ 477 public static boolean directoryContentEquals(final Path path1, final Path path2, final int maxDepth, 478 final LinkOption[] linkOptions, final FileVisitOption[] fileVisitOptions) throws IOException { 479 return new RelativeSortedPaths(path1, path2, maxDepth, linkOptions, fileVisitOptions).equals; 480 } 481 482 /** 483 * Compares the file contents of two Paths to determine if they are equal or not. 484 * <p> 485 * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}. 486 * </p> 487 * 488 * @param path1 the first stream. 489 * @param path2 the second stream. 490 * @return true if the content of the streams are equal or they both don't exist, false otherwise. 491 * @throws NullPointerException if either input is null. 492 * @throws IOException if an I/O error occurs. 493 * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File) 494 */ 495 public static boolean fileContentEquals(final Path path1, final Path path2) throws IOException { 496 return fileContentEquals(path1, path2, EMPTY_LINK_OPTION_ARRAY, EMPTY_OPEN_OPTION_ARRAY); 497 } 498 499 /** 500 * Compares the file contents of two Paths to determine if they are equal or not. 501 * <p> 502 * File content is accessed through {@link Files#newInputStream(Path,OpenOption...)}. 503 * </p> 504 * 505 * @param path1 the first stream. 506 * @param path2 the second stream. 507 * @param linkOptions options specifying how files are followed. 508 * @param openOptions options specifying how files are opened. 509 * @return true if the content of the streams are equal or they both don't exist, false otherwise. 510 * @throws NullPointerException if either input is null. 511 * @throws IOException if an I/O error occurs. 512 * @see org.apache.commons.io.FileUtils#contentEquals(java.io.File, java.io.File) 513 */ 514 public static boolean fileContentEquals(final Path path1, final Path path2, final LinkOption[] linkOptions, 515 final OpenOption[] openOptions) throws IOException { 516 if (path1 == null && path2 == null) { 517 return true; 518 } 519 if (path1 == null ^ path2 == null) { 520 return false; 521 } 522 final Path nPath1 = path1.normalize(); 523 final Path nPath2 = path2.normalize(); 524 final boolean path1Exists = Files.exists(nPath1, linkOptions); 525 if (path1Exists != Files.exists(nPath2, linkOptions)) { 526 return false; 527 } 528 if (!path1Exists) { 529 // Two not existing files are equal? 530 // Same as FileUtils 531 return true; 532 } 533 if (Files.isDirectory(nPath1, linkOptions)) { 534 // don't compare directory contents. 535 throw new IOException("Can't compare directories, only files: " + nPath1); 536 } 537 if (Files.isDirectory(nPath2, linkOptions)) { 538 // don't compare directory contents. 539 throw new IOException("Can't compare directories, only files: " + nPath2); 540 } 541 if (Files.size(nPath1) != Files.size(nPath2)) { 542 // lengths differ, cannot be equal 543 return false; 544 } 545 if (path1.equals(path2)) { 546 // same file 547 return true; 548 } 549 try (final InputStream inputStream1 = Files.newInputStream(nPath1, openOptions); 550 final InputStream inputStream2 = Files.newInputStream(nPath2, openOptions)) { 551 return IOUtils.contentEquals(inputStream1, inputStream2); 552 } 553 } 554 555 /** 556 * Reads the access control list from a file attribute view. 557 * 558 * @param sourcePath the path to the file. 559 * @return a file attribute view of the specified type, or null ifthe attribute view type is not available. 560 * @throws IOException if an I/O error occurs. 561 * @since 2.8.0 562 */ 563 public static List<AclEntry> getAclEntryList(final Path sourcePath) throws IOException { 564 final AclFileAttributeView fileAttributeView = Files.getFileAttributeView(sourcePath, 565 AclFileAttributeView.class); 566 return fileAttributeView == null ? null : fileAttributeView.getAcl(); 567 } 568 569 /** 570 * Returns whether the given file or directory is empty. 571 * 572 * @param path the the given file or directory to query. 573 * @return whether the given file or directory is empty. 574 * @throws IOException if an I/O error occurs 575 */ 576 public static boolean isEmpty(final Path path) throws IOException { 577 return Files.isDirectory(path) ? isEmptyDirectory(path) : isEmptyFile(path); 578 } 579 580 /** 581 * Returns whether the directory is empty. 582 * 583 * @param directory the the given directory to query. 584 * @return whether the given directory is empty. 585 * @throws IOException if an I/O error occurs 586 */ 587 public static boolean isEmptyDirectory(final Path directory) throws IOException { 588 try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(directory)) { 589 if (directoryStream.iterator().hasNext()) { 590 return false; 591 } 592 } 593 return true; 594 } 595 596 /** 597 * Returns whether the given file is empty. 598 * 599 * @param file the the given file to query. 600 * @return whether the given file is empty. 601 * @throws IOException if an I/O error occurs 602 */ 603 public static boolean isEmptyFile(final Path file) throws IOException { 604 return Files.size(file) <= 0; 605 } 606 607 /** 608 * Relativizes all files in the given {@code collection} against a {@code parent}. 609 * 610 * @param collection The collection of paths to relativize. 611 * @param parent relativizes against this parent path. 612 * @param sort Whether to sort the result. 613 * @param comparator How to sort. 614 * @return A collection of relativized paths, optionally sorted. 615 */ 616 static List<Path> relativize(final Collection<Path> collection, final Path parent, final boolean sort, 617 final Comparator<? super Path> comparator) { 618 Stream<Path> stream = collection.stream().map(parent::relativize); 619 if (sort) { 620 stream = comparator == null ? stream.sorted() : stream.sorted(comparator); 621 } 622 return stream.collect(Collectors.toList()); 623 } 624 625 /** 626 * Sets the given Path to the {@code readOnly} value. 627 * <p> 628 * This behavior is OS dependent. 629 * </p> 630 * 631 * @param path The path to set. 632 * @param readOnly true for read-only, false for not read-only. 633 * @param options options indicating how symbolic links are handled. 634 * @return The given path. 635 * @throws IOException if an I/O error occurs. 636 * @since 2.8.0 637 */ 638 public static Path setReadOnly(final Path path, final boolean readOnly, final LinkOption... options) 639 throws IOException { 640 final DosFileAttributeView fileAttributeView = Files.getFileAttributeView(path, DosFileAttributeView.class, 641 options); 642 if (fileAttributeView != null) { 643 fileAttributeView.setReadOnly(readOnly); 644 return path; 645 } 646 final PosixFileAttributeView posixFileAttributeView = Files.getFileAttributeView(path, 647 PosixFileAttributeView.class, options); 648 if (posixFileAttributeView != null) { 649 // Works on Windows but not on Ubuntu: 650 // Files.setAttribute(path, "unix:readonly", readOnly, options); 651 // java.lang.IllegalArgumentException: 'unix:readonly' not recognized 652 final PosixFileAttributes readAttributes = posixFileAttributeView.readAttributes(); 653 final Set<PosixFilePermission> permissions = readAttributes.permissions(); 654 permissions.remove(PosixFilePermission.OWNER_WRITE); 655 permissions.remove(PosixFilePermission.GROUP_WRITE); 656 permissions.remove(PosixFilePermission.OTHERS_WRITE); 657 return Files.setPosixFilePermissions(path, permissions); 658 } 659 throw new IOException("No DosFileAttributeView or PosixFileAttributeView for " + path); 660 } 661 662 /** 663 * Converts an array of {@link FileVisitOption} to a {@link Set}. 664 * 665 * @param fileVisitOptions input array. 666 * @return a new Set. 667 */ 668 static Set<FileVisitOption> toFileVisitOptionSet(final FileVisitOption... fileVisitOptions) { 669 return fileVisitOptions == null ? EnumSet.noneOf(FileVisitOption.class) 670 : Arrays.stream(fileVisitOptions).collect(Collectors.toSet()); 671 } 672 673 /** 674 * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor. 675 * 676 * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path. 677 * 678 * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}. 679 * @param directory See {@link Files#walkFileTree(Path,FileVisitor)}. 680 * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}. 681 * @return the given visitor. 682 * 683 * @throws IOException if an I/O error is thrown by a visitor method 684 */ 685 public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path directory) 686 throws IOException { 687 Files.walkFileTree(directory, visitor); 688 return visitor; 689 } 690 691 /** 692 * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor. 693 * 694 * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path. 695 * 696 * @param start See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 697 * @param options See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 698 * @param maxDepth See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 699 * @param visitor See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 700 * @param <T> See {@link Files#walkFileTree(Path,Set,int,FileVisitor)}. 701 * @return the given visitor. 702 * 703 * @throws IOException if an I/O error is thrown by a visitor method 704 */ 705 public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final Path start, 706 final Set<FileVisitOption> options, final int maxDepth) throws IOException { 707 Files.walkFileTree(start, options, maxDepth, visitor); 708 return visitor; 709 } 710 711 /** 712 * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor. 713 * 714 * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path. 715 * 716 * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}. 717 * @param first See {@link Paths#get(String,String[])}. 718 * @param more See {@link Paths#get(String,String[])}. 719 * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}. 720 * @return the given visitor. 721 * 722 * @throws IOException if an I/O error is thrown by a visitor method 723 */ 724 public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final String first, 725 final String... more) throws IOException { 726 return visitFileTree(visitor, Paths.get(first, more)); 727 } 728 729 /** 730 * Performs {@link Files#walkFileTree(Path,FileVisitor)} and returns the given visitor. 731 * 732 * Note that {@link Files#walkFileTree(Path,FileVisitor)} returns the given path. 733 * 734 * @param visitor See {@link Files#walkFileTree(Path,FileVisitor)}. 735 * @param uri See {@link Paths#get(URI)}. 736 * @param <T> See {@link Files#walkFileTree(Path,FileVisitor)}. 737 * @return the given visitor. 738 * 739 * @throws IOException if an I/O error is thrown by a visitor method 740 */ 741 public static <T extends FileVisitor<? super Path>> T visitFileTree(final T visitor, final URI uri) 742 throws IOException { 743 return visitFileTree(visitor, Paths.get(uri)); 744 } 745 746 /** 747 * Does allow to instantiate. 748 */ 749 private PathUtils() { 750 // do not instantiate. 751 } 752 753}