001/* 002 * Copyright 2020 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.core.util.path; 017 018import java.io.File; 019import java.io.FileNotFoundException; 020import java.io.IOException; 021import java.nio.file.Files; 022import java.nio.file.Path; 023import java.nio.file.StandardCopyOption; 024import java.util.ArrayList; 025import java.util.List; 026import java.util.function.Predicate; 027import java.util.stream.Collectors; 028import java.util.stream.Stream; 029 030import org.apache.commons.io.FileExistsException; 031 032/** 033 * General path manipulation utilities. 034 */ 035public final class PathUtils 036{ 037 private PathUtils() 038 { 039 // utility class 040 } 041 042 /** 043 * Copies a whole directory to a new location preserving the file dates. 044 * <p> 045 * This method copies the specified directory and all its child 046 * directories and files to the specified destination. 047 * The destination is the new location and name of the directory. 048 * <p> 049 * The destination directory is created if it does not exist. 050 * If the destination directory did exist, then this method merges 051 * the source with the destination, with the source taking precedence. 052 * <p> 053 * <strong>Note:</strong> This method tries to preserve the files' last 054 * modified date/times using {@link File#setLastModified(long)}, however 055 * it is not guaranteed that those operations will succeed. 056 * If the modification operation fails, no indication is provided. 057 * 058 * @param srcDir an existing directory to copy, must not be {@code null} 059 * @param destDir the new directory, must not be {@code null} 060 * 061 * @throws NullPointerException if source or destination is {@code null} 062 * @throws IOException if source or destination is invalid 063 * @throws IOException if an IO error occurs during copying 064 */ 065 public static void copyDirectory(final Path srcDir, final Path destDir) throws IOException 066 { 067 copyDirectory(srcDir, destDir, true); 068 } 069 070 /** 071 * Copies a whole directory to a new location. 072 * <p> 073 * This method copies the contents of the specified source directory 074 * to within the specified destination directory. 075 * <p> 076 * The destination directory is created if it does not exist. 077 * If the destination directory did exist, then this method merges 078 * the source with the destination, with the source taking precedence. 079 * <p> 080 * <strong>Note:</strong> Setting <code>preserveFileDate</code> to 081 * {@code true} tries to preserve the files' last modified 082 * date/times using {@link File#setLastModified(long)}, however it is 083 * not guaranteed that those operations will succeed. 084 * If the modification operation fails, no indication is provided. 085 * 086 * @param srcDir an existing directory to copy, must not be {@code null} 087 * @param destDir the new directory, must not be {@code null} 088 * @param preserveFileDate true if the file date of the copy 089 * should be the same as the original 090 * 091 * @throws NullPointerException if source or destination is {@code null} 092 * @throws IOException if source or destination is invalid 093 * @throws IOException if an IO error occurs during copying 094 */ 095 public static void copyDirectory(final Path srcDir, final Path destDir, final boolean preserveFileDate) throws IOException 096 { 097 copyDirectory(srcDir, destDir, null, preserveFileDate); 098 } 099 100 /** 101 * Copies a filtered directory to a new location preserving the file dates. 102 * <p> 103 * This method copies the contents of the specified source directory 104 * to within the specified destination directory. 105 * <p> 106 * The destination directory is created if it does not exist. 107 * If the destination directory did exist, then this method merges 108 * the source with the destination, with the source taking precedence. 109 * <p> 110 * <strong>Note:</strong> This method tries to preserve the files' last 111 * modified date/times using {@link File#setLastModified(long)}, however 112 * it is not guaranteed that those operations will succeed. 113 * If the modification operation fails, no indication is provided. 114 * </p> 115 * <h3>Example: Copy directories only</h3> 116 * <pre> 117 * // only copy the directory structure 118 * FileUtils.copyDirectory(srcDir, destDir, Files::isDirectory); 119 * </pre> 120 * 121 * @param srcDir an existing directory to copy, must not be {@code null} 122 * @param destDir the new directory, must not be {@code null} 123 * @param filter the filter to apply, null means copy all directories and files 124 * should be the same as the original 125 * 126 * @throws NullPointerException if source or destination is {@code null} 127 * @throws IOException if source or destination is invalid 128 * @throws IOException if an IO error occurs during copying 129 */ 130 public static void copyDirectory(final Path srcDir, final Path destDir, final Predicate<Path> filter) throws IOException 131 { 132 copyDirectory(srcDir, destDir, filter, true); 133 } 134 135 /** 136 * Copies a filtered directory to a new location. 137 * <p> 138 * This method copies the contents of the specified source directory 139 * to within the specified destination directory. 140 * <p> 141 * The destination directory is created if it does not exist. 142 * If the destination directory did exist, then this method merges 143 * the source with the destination, with the source taking precedence. 144 * <p> 145 * <strong>Note:</strong> Setting <code>preserveFileDate</code> to 146 * {@code true} tries to preserve the files' last modified 147 * date/times using {@link File#setLastModified(long)}, however it is 148 * not guaranteed that those operations will succeed. 149 * If the modification operation fails, no indication is provided. 150 * </p> 151 * <h3>Example: Copy directories only</h3> 152 * <pre> 153 * // only copy the directory structure 154 * FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false); 155 * </pre> 156 * 157 * <h3>Example: Copy directories and txt files</h3> 158 * <pre> 159 * // Create a filter for ".txt" files 160 * IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt"); 161 * IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter); 162 * 163 * // Create a filter for either directories or ".txt" files 164 * FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles); 165 * 166 * // Copy using the filter 167 * FileUtils.copyDirectory(srcDir, destDir, filter, false); 168 * </pre> 169 * 170 * @param srcDir an existing directory to copy, must not be {@code null} 171 * @param destDir the new directory, must not be {@code null} 172 * @param filter the filter to apply, null means copy all directories and files 173 * @param preserveFileDate true if the file date of the copy 174 * should be the same as the original 175 * 176 * @throws NullPointerException if source or destination is {@code null} 177 * @throws IOException if source or destination is invalid 178 * @throws IOException if an IO error occurs during copying 179 */ 180 public static void copyDirectory(final Path srcDir, final Path destDir, final Predicate<Path> filter, final boolean preserveFileDate) throws IOException 181 { 182 checkFileRequirements(srcDir, destDir); 183 if (!Files.isDirectory(srcDir)) 184 { 185 throw new IOException("Source '" + srcDir + "' exists but is not a directory"); 186 } 187 if (srcDir.toAbsolutePath().equals(destDir.toAbsolutePath())) 188 { 189 throw new IOException("Source '" + srcDir + "' and destination '" + destDir + "' are the same"); 190 } 191 192 // Cater for destination being directory within the source directory (see IO-141) 193 List<Path> exclusionList = null; 194 if (destDir.getFileSystem() == srcDir.getFileSystem() 195 && destDir.toAbsolutePath().startsWith(srcDir.toAbsolutePath())) 196 { 197 List<Path> srcFiles; 198 try (Stream<Path> walk = Files.list(srcDir)) 199 { 200 if (filter != null) 201 { 202 srcFiles = walk.filter(filter).collect(Collectors.toList()); 203 } 204 else 205 { 206 srcFiles = walk.collect(Collectors.toList()); 207 } 208 } 209 210 if (!srcFiles.isEmpty()) 211 { 212 exclusionList = new ArrayList<>(srcFiles.size()); 213 for (final Path srcFile : srcFiles) 214 { 215 final Path copiedFile = destDir.resolve(srcFile.getFileName()); 216 exclusionList.add(copiedFile.toAbsolutePath()); 217 } 218 } 219 } 220 doCopyDirectory(srcDir, destDir, filter, preserveFileDate, exclusionList); 221 } 222 223 /** 224 * checks requirements for file copy 225 * @param src the source file 226 * @param dest the destination 227 * @throws FileNotFoundException if the destination does not exist 228 */ 229 private static void checkFileRequirements(final Path src, final Path dest) throws FileNotFoundException 230 { 231 if (src == null) 232 { 233 throw new NullPointerException("Source must not be null"); 234 } 235 if (dest == null) 236 { 237 throw new NullPointerException("Destination must not be null"); 238 } 239 if (!Files.exists(src)) 240 { 241 throw new FileNotFoundException("Source '" + src + "' does not exist"); 242 } 243 } 244 245 /** 246 * Internal copy directory method. 247 * 248 * @param srcDir the validated source directory, must not be {@code null} 249 * @param destDir the validated destination directory, must not be {@code null} 250 * @param filter the filter to apply, null means copy all directories and files 251 * @param preserveFileDate whether to preserve the file date 252 * @param exclusionList List of files and directories to exclude from the copy, may be null 253 * @throws IOException if an error occurs 254 */ 255 private static void doCopyDirectory(final Path srcDir, final Path destDir, final Predicate<Path> filter, final boolean preserveFileDate, final List<Path> exclusionList) throws IOException 256 { 257 // recurse 258 List<Path> srcFiles; 259 try (Stream<Path> walk = Files.list(srcDir)) 260 { 261 if (filter != null) 262 { 263 srcFiles = walk.filter(filter).collect(Collectors.toList()); 264 } 265 else 266 { 267 srcFiles = walk.collect(Collectors.toList()); 268 } 269 } 270 271 if (Files.exists(destDir)) 272 { 273 if (!Files.isDirectory(destDir)) 274 { 275 throw new IOException("Destination '" + destDir + "' exists but is not a directory"); 276 } 277 } 278 else 279 { 280 Path createdDirectories = Files.createDirectories(destDir); 281 if (!Files.isDirectory(createdDirectories)) 282 { 283 throw new IOException("Destination '" + destDir + "' directory cannot be created"); 284 } 285 } 286 if (!Files.isWritable(destDir)) 287 { 288 throw new IOException("Destination '" + destDir + "' cannot be written to"); 289 } 290 for (final Path srcFile : srcFiles) 291 { 292 final Path dstFile = destDir.resolve(srcFile.getFileName().toString()); 293 if (exclusionList == null || !exclusionList.contains(srcFile.toAbsolutePath())) 294 { 295 if (Files.isDirectory(srcFile)) 296 { 297 doCopyDirectory(srcFile, dstFile, filter, preserveFileDate, exclusionList); 298 } 299 else 300 { 301 doCopyFile(srcFile, dstFile, preserveFileDate); 302 } 303 } 304 } 305 306 // Do this last, as the above has probably affected directory metadata 307 if (preserveFileDate) 308 { 309 Files.setLastModifiedTime(destDir, Files.getLastModifiedTime(srcDir)); 310 } 311 } 312 313 /** 314 * Internal copy file method. 315 * This caches the original file length, and throws an IOException 316 * if the output file length is different from the current input file length. 317 * So it may fail if the file changes size. 318 * It may also fail with "IllegalArgumentException: Negative size" if the input file is truncated part way 319 * through copying the data and the new file size is less than the current position. 320 * 321 * @param srcFile the validated source file, must not be {@code null} 322 * @param destFile the validated destination file, must not be {@code null} 323 * @param preserveFileDate whether to preserve the file date 324 * @throws IOException if an error occurs 325 * @throws IOException if the output file length is not the same as the input file length after the 326 * copy completes 327 * @throws IllegalArgumentException "Negative size" if the file is truncated so that the size is less than the 328 * position 329 */ 330 private static void doCopyFile(final Path srcFile, final Path destFile, final boolean preserveFileDate) throws IOException 331 { 332 if (Files.exists(destFile) && Files.isDirectory(destFile)) 333 { 334 throw new IOException("Destination '" + destFile + "' exists but is a directory"); 335 } 336 337 Files.copy(srcFile, destFile, StandardCopyOption.REPLACE_EXISTING); 338 339 final long srcLen = Files.size(srcFile); // TODO See IO-386 340 final long dstLen = Files.size(destFile); // TODO See IO-386 341 if (srcLen != dstLen) 342 { 343 throw new IOException("Failed to copy full contents from '" + srcFile + "' to '" + destFile + "' Expected length: " + srcLen + " Actual: " + dstLen); 344 } 345 if (preserveFileDate) 346 { 347 Files.setLastModifiedTime(destFile, Files.getLastModifiedTime(srcFile)); 348 } 349 } 350 351 /** 352 * Moves a file. 353 * <p> 354 * When the destination file is on another file system, do a "copy and delete". 355 * 356 * @param srcFile the file to be moved 357 * @param destFile the destination file 358 * @throws NullPointerException if source or destination is {@code null} 359 * @throws FileExistsException if the destination file exists 360 * @throws IOException if source or destination is invalid 361 * @throws IOException if an IO error occurs moving the file 362 * @since 1.4 363 */ 364 public static void moveFile(final Path srcFile, final Path destFile) throws IOException 365 { 366 if (srcFile == null) 367 { 368 throw new NullPointerException("Source must not be null"); 369 } 370 if (destFile == null) 371 { 372 throw new NullPointerException("Destination must not be null"); 373 } 374 if (!Files.exists(srcFile)) 375 { 376 throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); 377 } 378 if (Files.isDirectory(srcFile)) 379 { 380 throw new IOException("Source '" + srcFile + "' is a directory"); 381 } 382 if (Files.exists(destFile)) 383 { 384 throw new FileExistsException("Destination '" + destFile + "' already exists"); 385 } 386 if (Files.isDirectory(destFile)) 387 { 388 throw new IOException("Destination '" + destFile + "' is a directory"); 389 } 390 391 try 392 { 393 Files.move(srcFile, destFile); 394 } 395 catch (IOException e) 396 { 397 copyFile(srcFile, destFile); 398 if (!Files.deleteIfExists(srcFile)) 399 { 400 deleteQuietly(destFile); 401 throw new IOException("Failed to delete original file '" + srcFile + "' after copy to '" + destFile + "'"); 402 } 403 } 404 } 405 406 /** 407 * Moves a file or directory to the destination directory. 408 * <p> 409 * When the destination is on another file system, do a "copy and delete". 410 * 411 * @param src the file or directory to be moved 412 * @param destDir the destination directory 413 * @param createDestDir If {@code true} create the destination directory, 414 * otherwise if {@code false} throw an IOException 415 * @throws NullPointerException if source or destination is {@code null} 416 * @throws FileExistsException if the directory or file exists in the destination directory 417 * @throws IOException if source or destination is invalid 418 * @throws IOException if an IO error occurs moving the file 419 */ 420 public static void moveToDirectory(final Path src, final Path destDir, final boolean createDestDir) throws IOException 421 { 422 if (src == null) 423 { 424 throw new NullPointerException("Source must not be null"); 425 } 426 if (destDir == null) 427 { 428 throw new NullPointerException("Destination must not be null"); 429 } 430 if (!Files.exists(src)) 431 { 432 throw new FileNotFoundException("Source '" + src + "' does not exist"); 433 } 434 if (Files.isDirectory(src)) 435 { 436 moveDirectoryToDirectory(src, destDir, createDestDir); 437 } 438 else 439 { 440 moveFileToDirectory(src, destDir, createDestDir); 441 } 442 } 443 444 /** 445 * Moves a file to a directory. 446 * 447 * @param srcFile the file to be moved 448 * @param destDir the destination file 449 * @param createDestDir If {@code true} create the destination directory, 450 * otherwise if {@code false} throw an IOException 451 * @throws NullPointerException if source or destination is {@code null} 452 * @throws FileExistsException if the destination file exists 453 * @throws IOException if source or destination is invalid 454 * @throws IOException if an IO error occurs moving the file 455 * @since 1.4 456 */ 457 public static void moveFileToDirectory(final Path srcFile, final Path destDir, final boolean createDestDir) throws IOException 458 { 459 if (srcFile == null) 460 { 461 throw new NullPointerException("Source must not be null"); 462 } 463 if (destDir == null) 464 { 465 throw new NullPointerException("Destination directory must not be null"); 466 } 467 if (!Files.exists(destDir) && createDestDir) 468 { 469 Files.createDirectories(destDir); 470 } 471 if (!Files.exists(destDir)) 472 { 473 throw new FileNotFoundException("Destination directory '" + destDir + "' does not exist [createDestDir=" + createDestDir + "]"); 474 } 475 if (!Files.isDirectory(destDir)) 476 { 477 throw new IOException("Destination '" + destDir + "' is not a directory"); 478 } 479 moveFile(srcFile, destDir.resolve(srcFile.getFileName().toString())); 480 } 481 482 483 /** 484 * Moves a directory to another directory. 485 * 486 * @param src the file to be moved 487 * @param destDir the destination file 488 * @param createDestDir If {@code true} create the destination directory, 489 * otherwise if {@code false} throw an IOException 490 * @throws NullPointerException if source or destination is {@code null} 491 * @throws FileExistsException if the directory exists in the destination directory 492 * @throws IOException if source or destination is invalid 493 * @throws IOException if an IO error occurs moving the file 494 */ 495 public static void moveDirectoryToDirectory(final Path src, final Path destDir, final boolean createDestDir) throws IOException 496 { 497 if (src == null) 498 { 499 throw new NullPointerException("Source must not be null"); 500 } 501 if (destDir == null) 502 { 503 throw new NullPointerException("Destination directory must not be null"); 504 } 505 if (!Files.exists(destDir) && createDestDir) 506 { 507 Files.createDirectories(destDir); 508 } 509 if (!Files.exists(destDir)) 510 { 511 throw new FileNotFoundException("Destination directory '" + destDir + "' does not exist [createDestDir=" + createDestDir + "]"); 512 } 513 if (!Files.isDirectory(destDir)) 514 { 515 throw new IOException("Destination '" + destDir + "' is not a directory"); 516 } 517 moveDirectory(src, destDir.resolve(src.getFileName().toString())); 518 } 519 520 /** 521 * Moves a directory. 522 * <p> 523 * When the destination directory is on another file system, do a "copy and delete". 524 * 525 * @param srcDir the directory to be moved 526 * @param destDir the destination directory 527 * @throws NullPointerException if source or destination is {@code null} 528 * @throws FileExistsException if the destination directory exists 529 * @throws IOException if source or destination is invalid 530 * @throws IOException if an IO error occurs moving the file 531 */ 532 public static void moveDirectory(final Path srcDir, final Path destDir) throws IOException 533 { 534 if (srcDir == null) 535 { 536 throw new NullPointerException("Source must not be null"); 537 } 538 if (destDir == null) 539 { 540 throw new NullPointerException("Destination must not be null"); 541 } 542 if (!Files.exists(srcDir)) 543 { 544 throw new FileNotFoundException("Source '" + srcDir + "' does not exist"); 545 } 546 if (!Files.isDirectory(srcDir)) 547 { 548 throw new IOException("Source '" + srcDir + "' is not a directory"); 549 } 550 if (Files.exists(destDir)) 551 { 552 throw new FileExistsException("Destination '" + destDir + "' already exists"); 553 } 554 555 try 556 { 557 Files.move(srcDir, destDir); 558 } 559 catch (IOException e) 560 { 561 if (destDir.toAbsolutePath().toString().startsWith(srcDir.toAbsolutePath().toString() + File.separator)) 562 { 563 throw new IOException("Cannot move directory: " + srcDir + " to a subdirectory of itself: " + destDir); 564 } 565 copyDirectory(srcDir, destDir); 566 deleteDirectory(srcDir); 567 if (Files.exists(srcDir)) 568 { 569 throw new IOException("Failed to delete original directory '" + srcDir + "' after copy to '" + destDir + "'"); 570 } 571 } 572 } 573 574 575 /** 576 * Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories. 577 * <p> 578 * The difference between File.delete() and this method are: 579 * <ul> 580 * <li>A directory to be deleted does not have to be empty.</li> 581 * <li>No exceptions are thrown when a file or directory cannot be deleted.</li> 582 * </ul> 583 * 584 * @param file file or directory to delete, can be {@code null} 585 * @return {@code true} if the file or directory was deleted, otherwise 586 * {@code false} 587 */ 588 public static boolean deleteQuietly(final Path file) 589 { 590 if (file == null) 591 { 592 return false; 593 } 594 try 595 { 596 if (Files.isDirectory(file)) 597 { 598 cleanDirectory(file); 599 } 600 } 601 catch (final Exception ignored) 602 { 603 // ignored 604 } 605 606 try 607 { 608 return Files.deleteIfExists(file); 609 } 610 catch (final Exception ignored) 611 { 612 return false; 613 } 614 } 615 616 /** 617 * Cleans a directory without deleting it. 618 * 619 * @param directory directory to clean 620 * @throws IOException in case cleaning is unsuccessful 621 * @throws IllegalArgumentException if {@code directory} does not exist or is not a directory 622 */ 623 public static void cleanDirectory(final Path directory) throws IOException 624 { 625 final Path[] files = verifiedListFiles(directory); 626 627 IOException exception = null; 628 for (final Path file : files) 629 { 630 try 631 { 632 forceDelete(file); 633 } 634 catch (final IOException ioe) 635 { 636 exception = ioe; 637 } 638 } 639 640 if (null != exception) 641 { 642 throw exception; 643 } 644 } 645 646 /** 647 * Lists files in a directory, asserting that the supplied directory satisfies exists and is a directory 648 * @param directory The directory to list 649 * @return The files in the directory, never null. 650 * @throws IOException if an I/O error occurs 651 */ 652 private static Path[] verifiedListFiles(final Path directory) throws IOException 653 { 654 if (!Files.exists(directory)) 655 { 656 final String message = directory + " does not exist"; 657 throw new IllegalArgumentException(message); 658 } 659 660 if (!Files.isDirectory(directory)) 661 { 662 final String message = directory + " is not a directory"; 663 throw new IllegalArgumentException(message); 664 } 665 666 try (Stream<Path> s = Files.list(directory)) 667 { 668 List<Path> collect = s.collect(Collectors.toList()); 669 return collect.toArray(new Path[collect.size()]); 670 } 671 } 672 673 /** 674 * Deletes a file. If file is a directory, delete it and all sub-directories. 675 * <p> 676 * The difference between File.delete() and this method are: 677 * <ul> 678 * <li>A directory to be deleted does not have to be empty.</li> 679 * <li>You get exceptions when a file or directory cannot be deleted. 680 * (java.io.File methods returns a boolean)</li> 681 * </ul> 682 * 683 * @param file file or directory to delete, must not be {@code null} 684 * @throws NullPointerException if the directory is {@code null} 685 * @throws FileNotFoundException if the file was not found 686 * @throws IOException in case deletion is unsuccessful 687 */ 688 public static void forceDelete(final Path file) throws IOException 689 { 690 if (Files.isDirectory(file)) 691 { 692 deleteDirectory(file); 693 } 694 else 695 { 696 final boolean filePresent = Files.exists(file); 697 if (!Files.deleteIfExists(file)) 698 { 699 if (!filePresent) 700 { 701 throw new FileNotFoundException("File does not exist: " + file); 702 } 703 final String message = "Unable to delete file: " + file; 704 throw new IOException(message); 705 } 706 } 707 } 708 709 /** 710 * Deletes a directory recursively. 711 * 712 * @param directory directory to delete 713 * @throws IOException in case deletion is unsuccessful 714 * @throws IllegalArgumentException if {@code directory} does not exist or is not a directory 715 */ 716 public static void deleteDirectory(final Path directory) throws IOException 717 { 718 if (!Files.exists(directory)) 719 { 720 return; 721 } 722 723 if (!isSymlink(directory)) 724 { 725 cleanDirectory(directory); 726 } 727 728 if (!Files.deleteIfExists(directory)) 729 { 730 final String message = "Unable to delete directory " + directory + "."; 731 throw new IOException(message); 732 } 733 } 734 735 /** 736 * Determines whether the specified file is a Symbolic Link rather than an actual file. 737 * <p> 738 * Will not return true if there is a Symbolic Link anywhere in the path, 739 * only if the specific file is. 740 * <p> 741 * When using jdk1.7, this method delegates to {@code boolean java.nio.file.Files.isSymbolicLink(Path path)} 742 * 743 * <p> 744 * For code that runs on Java 1.7 or later, use the following method instead: 745 * <br> 746 * {@code boolean java.nio.file.Files.isSymbolicLink(Path path)} 747 * @param file the file to check 748 * @return true if the file is a Symbolic Link 749 * @throws IOException if an IO error occurs while checking the file 750 */ 751 public static boolean isSymlink(final Path file) throws IOException 752 { 753 if (file == null) 754 { 755 throw new NullPointerException("File must not be null"); 756 } 757 return Files.isSymbolicLink(file); 758 } 759 760 /** 761 * Copies a file to a new location preserving the file date. 762 * <p> 763 * This method copies the contents of the specified source file to the 764 * specified destination file. The directory holding the destination file is 765 * created if it does not exist. If the destination file exists, then this 766 * method will overwrite it. 767 * <p> 768 * <strong>Note:</strong> This method tries to preserve the file's last 769 * modified date/times using {@link File#setLastModified(long)}, however 770 * it is not guaranteed that the operation will succeed. 771 * If the modification operation fails, no indication is provided. 772 * 773 * @param srcFile an existing file to copy, must not be {@code null} 774 * @param destFile the new file, must not be {@code null} 775 * 776 * @throws NullPointerException if source or destination is {@code null} 777 * @throws IOException if source or destination is invalid 778 * @throws IOException if an IO error occurs during copying 779 * @throws IOException if the output file length is not the same as the input file length after the copy 780 * completes 781 * @see #copyFile(Path, Path, boolean) 782 */ 783 public static void copyFile(final Path srcFile, final Path destFile) throws IOException 784 { 785 copyFile(srcFile, destFile, true); 786 } 787 788 /** 789 * Copies a file to a new location. 790 * <p> 791 * This method copies the contents of the specified source file 792 * to the specified destination file. 793 * The directory holding the destination file is created if it does not exist. 794 * If the destination file exists, then this method will overwrite it. 795 * <p> 796 * <strong>Note:</strong> Setting <code>preserveFileDate</code> to 797 * {@code true} tries to preserve the file's last modified 798 * date/times using {@link File#setLastModified(long)}, however it is 799 * not guaranteed that the operation will succeed. 800 * If the modification operation fails, no indication is provided. 801 * 802 * @param srcFile an existing file to copy, must not be {@code null} 803 * @param destFile the new file, must not be {@code null} 804 * @param preserveFileDate true if the file date of the copy 805 * should be the same as the original 806 * 807 * @throws NullPointerException if source or destination is {@code null} 808 * @throws IOException if source or destination is invalid 809 * @throws IOException if an IO error occurs during copying 810 * @throws IOException if the output file length is not the same as the input file length after the copy 811 * completes 812 * @see #doCopyFile(Path, Path, boolean) 813 */ 814 public static void copyFile(final Path srcFile, final Path destFile, final boolean preserveFileDate) throws IOException 815 { 816 checkFileRequirements(srcFile, destFile); 817 if (Files.isDirectory(srcFile)) 818 { 819 throw new IOException("Source '" + srcFile + "' exists but is a directory"); 820 } 821 if (srcFile.toAbsolutePath().equals(destFile.toAbsolutePath())) 822 { 823 throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same"); 824 } 825 826 final Path parentFile = destFile.getParent(); 827 if (parentFile != null) 828 { 829 Files.createDirectories(parentFile); 830 if (!Files.isDirectory(parentFile)) 831 { 832 throw new IOException("Destination '" + parentFile + "' directory cannot be created"); 833 } 834 } 835 if (Files.exists(destFile) && !Files.isWritable(destFile)) 836 { 837 throw new IOException("Destination '" + destFile + "' exists but is read-only"); 838 } 839 doCopyFile(srcFile, destFile, preserveFileDate); 840 } 841}