1 /* 2 * Copyright (C) 2010, Google Inc. 3 * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com> 4 * Copyright (C) 2010, Jens Baumgart <jens.baumgart@sap.com> 5 * and other copyright owners as documented in the project's IP log. 6 * 7 * This program and the accompanying materials are made available 8 * under the terms of the Eclipse Distribution License v1.0 which 9 * accompanies this distribution, is reproduced below, and is 10 * available at http://www.eclipse.org/org/documents/edl-v10.php 11 * 12 * All rights reserved. 13 * 14 * Redistribution and use in source and binary forms, with or 15 * without modification, are permitted provided that the following 16 * conditions are met: 17 * 18 * - Redistributions of source code must retain the above copyright 19 * notice, this list of conditions and the following disclaimer. 20 * 21 * - Redistributions in binary form must reproduce the above 22 * copyright notice, this list of conditions and the following 23 * disclaimer in the documentation and/or other materials provided 24 * with the distribution. 25 * 26 * - Neither the name of the Eclipse Foundation, Inc. nor the 27 * names of its contributors may be used to endorse or promote 28 * products derived from this software without specific prior 29 * written permission. 30 * 31 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 32 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 33 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 34 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 35 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 36 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 37 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 38 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 39 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 40 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 41 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 42 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 43 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 44 */ 45 46 package org.eclipse.jgit.util; 47 48 import java.io.File; 49 import java.io.IOException; 50 import java.nio.file.AtomicMoveNotSupportedException; 51 import java.nio.file.CopyOption; 52 import java.nio.file.Files; 53 import java.nio.file.InvalidPathException; 54 import java.nio.file.LinkOption; 55 import java.nio.file.Path; 56 import java.nio.file.StandardCopyOption; 57 import java.nio.file.attribute.BasicFileAttributeView; 58 import java.nio.file.attribute.BasicFileAttributes; 59 import java.nio.file.attribute.FileTime; 60 import java.nio.file.attribute.PosixFileAttributeView; 61 import java.nio.file.attribute.PosixFileAttributes; 62 import java.nio.file.attribute.PosixFilePermission; 63 import java.text.MessageFormat; 64 import java.text.Normalizer; 65 import java.text.Normalizer.Form; 66 import java.util.ArrayList; 67 import java.util.List; 68 import java.util.Locale; 69 import java.util.regex.Pattern; 70 71 import org.eclipse.jgit.internal.JGitText; 72 import org.eclipse.jgit.lib.Constants; 73 import org.eclipse.jgit.util.FS.Attributes; 74 75 /** 76 * File Utilities 77 */ 78 public class FileUtils { 79 80 /** 81 * Option to delete given {@code File} 82 */ 83 public static final int NONE = 0; 84 85 /** 86 * Option to recursively delete given {@code File} 87 */ 88 public static final int RECURSIVE = 1; 89 90 /** 91 * Option to retry deletion if not successful 92 */ 93 public static final int RETRY = 2; 94 95 /** 96 * Option to skip deletion if file doesn't exist 97 */ 98 public static final int SKIP_MISSING = 4; 99 100 /** 101 * Option not to throw exceptions when a deletion finally doesn't succeed. 102 * @since 2.0 103 */ 104 public static final int IGNORE_ERRORS = 8; 105 106 /** 107 * Option to only delete empty directories. This option can be combined with 108 * {@link #RECURSIVE} 109 * 110 * @since 3.0 111 */ 112 public static final int EMPTY_DIRECTORIES_ONLY = 16; 113 114 /** 115 * Safe conversion from {@link java.io.File} to {@link java.nio.file.Path}. 116 * 117 * @param f 118 * {@code File} to be converted to {@code Path} 119 * @return the path represented by the file 120 * @throws java.io.IOException 121 * in case the path represented by the file is not valid ( 122 * {@link java.nio.file.InvalidPathException}) 123 * @since 4.10 124 */ 125 public static Path toPath(File f) throws IOException { 126 try { 127 return f.toPath(); 128 } catch (InvalidPathException ex) { 129 throw new IOException(ex); 130 } 131 } 132 133 /** 134 * Delete file or empty folder 135 * 136 * @param f 137 * {@code File} to be deleted 138 * @throws java.io.IOException 139 * if deletion of {@code f} fails. This may occur if {@code f} 140 * didn't exist when the method was called. This can therefore 141 * cause java.io.IOExceptions during race conditions when 142 * multiple concurrent threads all try to delete the same file. 143 */ 144 public static void delete(File f) throws IOException { 145 delete(f, NONE); 146 } 147 148 /** 149 * Delete file or folder 150 * 151 * @param f 152 * {@code File} to be deleted 153 * @param options 154 * deletion options, {@code RECURSIVE} for recursive deletion of 155 * a subtree, {@code RETRY} to retry when deletion failed. 156 * Retrying may help if the underlying file system doesn't allow 157 * deletion of files being read by another thread. 158 * @throws java.io.IOException 159 * if deletion of {@code f} fails. This may occur if {@code f} 160 * didn't exist when the method was called. This can therefore 161 * cause java.io.IOExceptions during race conditions when 162 * multiple concurrent threads all try to delete the same file. 163 * This exception is not thrown when IGNORE_ERRORS is set. 164 */ 165 public static void delete(File f, int options) throws IOException { 166 FS fs = FS.DETECTED; 167 if ((options & SKIP_MISSING) != 0 && !fs.exists(f)) 168 return; 169 170 if ((options & RECURSIVE) != 0 && fs.isDirectory(f)) { 171 final File[] items = f.listFiles(); 172 if (items != null) { 173 List<File> files = new ArrayList<>(); 174 List<File> dirs = new ArrayList<>(); 175 for (File c : items) 176 if (c.isFile()) 177 files.add(c); 178 else 179 dirs.add(c); 180 // Try to delete files first, otherwise options 181 // EMPTY_DIRECTORIES_ONLY|RECURSIVE will delete empty 182 // directories before aborting, depending on order. 183 for (File file : files) 184 delete(file, options); 185 for (File d : dirs) 186 delete(d, options); 187 } 188 } 189 190 boolean delete = false; 191 if ((options & EMPTY_DIRECTORIES_ONLY) != 0) { 192 if (f.isDirectory()) { 193 delete = true; 194 } else { 195 if ((options & IGNORE_ERRORS) == 0) 196 throw new IOException(MessageFormat.format( 197 JGitText.get().deleteFileFailed, 198 f.getAbsolutePath())); 199 } 200 } else { 201 delete = true; 202 } 203 204 if (delete && !f.delete()) { 205 if ((options & RETRY) != 0 && fs.exists(f)) { 206 for (int i = 1; i < 10; i++) { 207 try { 208 Thread.sleep(100); 209 } catch (InterruptedException e) { 210 // ignore 211 } 212 if (f.delete()) 213 return; 214 } 215 } 216 if ((options & IGNORE_ERRORS) == 0) 217 throw new IOException(MessageFormat.format( 218 JGitText.get().deleteFileFailed, f.getAbsolutePath())); 219 } 220 } 221 222 /** 223 * Rename a file or folder. If the rename fails and if we are running on a 224 * filesystem where it makes sense to repeat a failing rename then repeat 225 * the rename operation up to 9 times with 100ms sleep time between two 226 * calls. Furthermore if the destination exists and is directory hierarchy 227 * with only directories in it, the whole directory hierarchy will be 228 * deleted. If the target represents a non-empty directory structure, empty 229 * subdirectories within that structure may or may not be deleted even if 230 * the method fails. Furthermore if the destination exists and is a file 231 * then the file will be deleted and then the rename is retried. 232 * <p> 233 * This operation is <em>not</em> atomic. 234 * 235 * @see FS#retryFailedLockFileCommit() 236 * @param src 237 * the old {@code File} 238 * @param dst 239 * the new {@code File} 240 * @throws java.io.IOException 241 * if the rename has failed 242 * @since 3.0 243 */ 244 public static void rename(File src, File dst) 245 throws IOException { 246 rename(src, dst, StandardCopyOption.REPLACE_EXISTING); 247 } 248 249 /** 250 * Rename a file or folder using the passed 251 * {@link java.nio.file.CopyOption}s. If the rename fails and if we are 252 * running on a filesystem where it makes sense to repeat a failing rename 253 * then repeat the rename operation up to 9 times with 100ms sleep time 254 * between two calls. Furthermore if the destination exists and is a 255 * directory hierarchy with only directories in it, the whole directory 256 * hierarchy will be deleted. If the target represents a non-empty directory 257 * structure, empty subdirectories within that structure may or may not be 258 * deleted even if the method fails. Furthermore if the destination exists 259 * and is a file then the file will be replaced if 260 * {@link java.nio.file.StandardCopyOption#REPLACE_EXISTING} has been set. 261 * If {@link java.nio.file.StandardCopyOption#ATOMIC_MOVE} has been set the 262 * rename will be done atomically or fail with an 263 * {@link java.nio.file.AtomicMoveNotSupportedException} 264 * 265 * @param src 266 * the old file 267 * @param dst 268 * the new file 269 * @param options 270 * options to pass to 271 * {@link java.nio.file.Files#move(java.nio.file.Path, java.nio.file.Path, CopyOption...)} 272 * @throws java.nio.file.AtomicMoveNotSupportedException 273 * if file cannot be moved as an atomic file system operation 274 * @throws java.io.IOException 275 * @since 4.1 276 */ 277 public static void rename(final File src, final File dst, 278 CopyOption... options) 279 throws AtomicMoveNotSupportedException, IOException { 280 int attempts = FS.DETECTED.retryFailedLockFileCommit() ? 10 : 1; 281 while (--attempts >= 0) { 282 try { 283 Files.move(toPath(src), toPath(dst), options); 284 return; 285 } catch (AtomicMoveNotSupportedException e) { 286 throw e; 287 } catch (IOException e) { 288 try { 289 if (!dst.delete()) { 290 delete(dst, EMPTY_DIRECTORIES_ONLY | RECURSIVE); 291 } 292 // On *nix there is no try, you do or do not 293 Files.move(toPath(src), toPath(dst), options); 294 return; 295 } catch (IOException e2) { 296 // ignore and continue retry 297 } 298 } 299 try { 300 Thread.sleep(100); 301 } catch (InterruptedException e) { 302 throw new IOException( 303 MessageFormat.format(JGitText.get().renameFileFailed, 304 src.getAbsolutePath(), dst.getAbsolutePath())); 305 } 306 } 307 throw new IOException( 308 MessageFormat.format(JGitText.get().renameFileFailed, 309 src.getAbsolutePath(), dst.getAbsolutePath())); 310 } 311 312 /** 313 * Creates the directory named by this abstract pathname. 314 * 315 * @param d 316 * directory to be created 317 * @throws java.io.IOException 318 * if creation of {@code d} fails. This may occur if {@code d} 319 * did exist when the method was called. This can therefore 320 * cause java.io.IOExceptions during race conditions when 321 * multiple concurrent threads all try to create the same 322 * directory. 323 */ 324 public static void mkdir(File d) 325 throws IOException { 326 mkdir(d, false); 327 } 328 329 /** 330 * Creates the directory named by this abstract pathname. 331 * 332 * @param d 333 * directory to be created 334 * @param skipExisting 335 * if {@code true} skip creation of the given directory if it 336 * already exists in the file system 337 * @throws java.io.IOException 338 * if creation of {@code d} fails. This may occur if {@code d} 339 * did exist when the method was called. This can therefore 340 * cause java.io.IOExceptions during race conditions when 341 * multiple concurrent threads all try to create the same 342 * directory. 343 */ 344 public static void mkdir(File d, boolean skipExisting) 345 throws IOException { 346 if (!d.mkdir()) { 347 if (skipExisting && d.isDirectory()) 348 return; 349 throw new IOException(MessageFormat.format( 350 JGitText.get().mkDirFailed, d.getAbsolutePath())); 351 } 352 } 353 354 /** 355 * Creates the directory named by this abstract pathname, including any 356 * necessary but nonexistent parent directories. Note that if this operation 357 * fails it may have succeeded in creating some of the necessary parent 358 * directories. 359 * 360 * @param d 361 * directory to be created 362 * @throws java.io.IOException 363 * if creation of {@code d} fails. This may occur if {@code d} 364 * did exist when the method was called. This can therefore 365 * cause java.io.IOExceptions during race conditions when 366 * multiple concurrent threads all try to create the same 367 * directory. 368 */ 369 public static void mkdirs(File d) throws IOException { 370 mkdirs(d, false); 371 } 372 373 /** 374 * Creates the directory named by this abstract pathname, including any 375 * necessary but nonexistent parent directories. Note that if this operation 376 * fails it may have succeeded in creating some of the necessary parent 377 * directories. 378 * 379 * @param d 380 * directory to be created 381 * @param skipExisting 382 * if {@code true} skip creation of the given directory if it 383 * already exists in the file system 384 * @throws java.io.IOException 385 * if creation of {@code d} fails. This may occur if {@code d} 386 * did exist when the method was called. This can therefore 387 * cause java.io.IOExceptions during race conditions when 388 * multiple concurrent threads all try to create the same 389 * directory. 390 */ 391 public static void mkdirs(File d, boolean skipExisting) 392 throws IOException { 393 if (!d.mkdirs()) { 394 if (skipExisting && d.isDirectory()) 395 return; 396 throw new IOException(MessageFormat.format( 397 JGitText.get().mkDirsFailed, d.getAbsolutePath())); 398 } 399 } 400 401 /** 402 * Atomically creates a new, empty file named by this abstract pathname if 403 * and only if a file with this name does not yet exist. The check for the 404 * existence of the file and the creation of the file if it does not exist 405 * are a single operation that is atomic with respect to all other 406 * filesystem activities that might affect the file. 407 * <p> 408 * Note: this method should not be used for file-locking, as the resulting 409 * protocol cannot be made to work reliably. The 410 * {@link java.nio.channels.FileLock} facility should be used instead. 411 * 412 * @param f 413 * the file to be created 414 * @throws java.io.IOException 415 * if the named file already exists or if an I/O error occurred 416 */ 417 public static void createNewFile(File f) throws IOException { 418 if (!f.createNewFile()) 419 throw new IOException(MessageFormat.format( 420 JGitText.get().createNewFileFailed, f)); 421 } 422 423 /** 424 * Create a symbolic link 425 * 426 * @param path 427 * the path of the symbolic link to create 428 * @param target 429 * the target of the symbolic link 430 * @return the path to the symbolic link 431 * @throws java.io.IOException 432 * @since 4.2 433 */ 434 public static Path createSymLink(File path, String target) 435 throws IOException { 436 Path nioPath = toPath(path); 437 if (Files.exists(nioPath, LinkOption.NOFOLLOW_LINKS)) { 438 BasicFileAttributes attrs = Files.readAttributes(nioPath, 439 BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); 440 if (attrs.isRegularFile() || attrs.isSymbolicLink()) { 441 delete(path); 442 } else { 443 delete(path, EMPTY_DIRECTORIES_ONLY | RECURSIVE); 444 } 445 } 446 if (SystemReader.getInstance().isWindows()) { 447 target = target.replace('/', '\\'); 448 } 449 Path nioTarget = toPath(new File(target)); 450 return Files.createSymbolicLink(nioPath, nioTarget); 451 } 452 453 /** 454 * Read target path of the symlink. 455 * 456 * @param path 457 * a {@link java.io.File} object. 458 * @return target path of the symlink, or null if it is not a symbolic link 459 * @throws java.io.IOException 460 * @since 3.0 461 */ 462 public static String readSymLink(File path) throws IOException { 463 Path nioPath = toPath(path); 464 Path target = Files.readSymbolicLink(nioPath); 465 String targetString = target.toString(); 466 if (SystemReader.getInstance().isWindows()) { 467 targetString = targetString.replace('\\', '/'); 468 } else if (SystemReader.getInstance().isMacOS()) { 469 targetString = Normalizer.normalize(targetString, Form.NFC); 470 } 471 return targetString; 472 } 473 474 /** 475 * Create a temporary directory. 476 * 477 * @param prefix 478 * prefix string 479 * @param suffix 480 * suffix string 481 * @param dir 482 * The parent dir, can be null to use system default temp dir. 483 * @return the temp dir created. 484 * @throws java.io.IOException 485 * @since 3.4 486 */ 487 public static File createTempDir(String prefix, String suffix, File dir) 488 throws IOException { 489 final int RETRIES = 1; // When something bad happens, retry once. 490 for (int i = 0; i < RETRIES; i++) { 491 File tmp = File.createTempFile(prefix, suffix, dir); 492 if (!tmp.delete()) 493 continue; 494 if (!tmp.mkdir()) 495 continue; 496 return tmp; 497 } 498 throw new IOException(JGitText.get().cannotCreateTempDir); 499 } 500 501 /** 502 * Expresses <code>other</code> as a relative file path from 503 * <code>base</code>. File-separator and case sensitivity are based on the 504 * current file system. 505 * 506 * See also 507 * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}. 508 * 509 * @param base 510 * Base path 511 * @param other 512 * Destination path 513 * @return Relative path from <code>base</code> to <code>other</code> 514 * @since 4.8 515 */ 516 public static String relativizeNativePath(String base, String other) { 517 return FS.DETECTED.relativize(base, other); 518 } 519 520 /** 521 * Expresses <code>other</code> as a relative file path from 522 * <code>base</code>. File-separator and case sensitivity are based on Git's 523 * internal representation of files (which matches Unix). 524 * 525 * See also 526 * {@link org.eclipse.jgit.util.FileUtils#relativizePath(String, String, String, boolean)}. 527 * 528 * @param base 529 * Base path 530 * @param other 531 * Destination path 532 * @return Relative path from <code>base</code> to <code>other</code> 533 * @since 4.8 534 */ 535 public static String relativizeGitPath(String base, String other) { 536 return relativizePath(base, other, "/", false); //$NON-NLS-1$ 537 } 538 539 540 /** 541 * Expresses <code>other</code> as a relative file path from <code>base</code> 542 * <p> 543 * For example, if called with the two following paths : 544 * 545 * <pre> 546 * <code>base = "c:\\Users\\jdoe\\eclipse\\git\\project"</code> 547 * <code>other = "c:\\Users\\jdoe\\eclipse\\git\\another_project\\pom.xml"</code> 548 * </pre> 549 * 550 * This will return "..\\another_project\\pom.xml". 551 * 552 * <p> 553 * <b>Note</b> that this will return the empty String if <code>base</code> 554 * and <code>other</code> are equal. 555 * </p> 556 * 557 * @param base 558 * The path against which <code>other</code> should be 559 * relativized. This will be assumed to denote the path to a 560 * folder and not a file. 561 * @param other 562 * The path that will be made relative to <code>base</code>. 563 * @param dirSeparator 564 * A string that separates components of the path. In practice, this is "/" or "\\". 565 * @param caseSensitive 566 * Whether to consider differently-cased directory names as distinct 567 * @return A relative path that, when resolved against <code>base</code>, 568 * will yield the original <code>other</code>. 569 * @since 4.8 570 */ 571 public static String relativizePath(String base, String other, String dirSeparator, boolean caseSensitive) { 572 if (base.equals(other)) 573 return ""; //$NON-NLS-1$ 574 575 final String[] baseSegments = base.split(Pattern.quote(dirSeparator)); 576 final String[] otherSegments = other.split(Pattern 577 .quote(dirSeparator)); 578 579 int commonPrefix = 0; 580 while (commonPrefix < baseSegments.length 581 && commonPrefix < otherSegments.length) { 582 if (caseSensitive 583 && baseSegments[commonPrefix] 584 .equals(otherSegments[commonPrefix])) 585 commonPrefix++; 586 else if (!caseSensitive 587 && baseSegments[commonPrefix] 588 .equalsIgnoreCase(otherSegments[commonPrefix])) 589 commonPrefix++; 590 else 591 break; 592 } 593 594 final StringBuilder builder = new StringBuilder(); 595 for (int i = commonPrefix; i < baseSegments.length; i++) 596 builder.append("..").append(dirSeparator); //$NON-NLS-1$ 597 for (int i = commonPrefix; i < otherSegments.length; i++) { 598 builder.append(otherSegments[i]); 599 if (i < otherSegments.length - 1) 600 builder.append(dirSeparator); 601 } 602 return builder.toString(); 603 } 604 605 /** 606 * Determine if an IOException is a Stale NFS File Handle 607 * 608 * @param ioe 609 * an {@link java.io.IOException} object. 610 * @return a boolean true if the IOException is a Stale NFS FIle Handle 611 * @since 4.1 612 */ 613 public static boolean isStaleFileHandle(IOException ioe) { 614 String msg = ioe.getMessage(); 615 return msg != null 616 && msg.toLowerCase(Locale.ROOT) 617 .matches("stale .*file .*handle"); //$NON-NLS-1$ 618 } 619 620 /** 621 * Determine if a throwable or a cause in its causal chain is a Stale NFS 622 * File Handle 623 * 624 * @param throwable 625 * a {@link java.lang.Throwable} object. 626 * @return a boolean true if the throwable or a cause in its causal chain is 627 * a Stale NFS File Handle 628 * @since 4.7 629 */ 630 public static boolean isStaleFileHandleInCausalChain(Throwable throwable) { 631 while (throwable != null) { 632 if (throwable instanceof IOException 633 && isStaleFileHandle((IOException) throwable)) { 634 return true; 635 } 636 throwable = throwable.getCause(); 637 } 638 return false; 639 } 640 641 /** 642 * @param file 643 * @return {@code true} if the passed file is a symbolic link 644 */ 645 static boolean isSymlink(File file) { 646 return Files.isSymbolicLink(file.toPath()); 647 } 648 649 /** 650 * @param file 651 * @return lastModified attribute for given file, not following symbolic 652 * links 653 * @throws IOException 654 */ 655 static long lastModified(File file) throws IOException { 656 return Files.getLastModifiedTime(toPath(file), LinkOption.NOFOLLOW_LINKS) 657 .toMillis(); 658 } 659 660 /** 661 * @param file 662 * @param time 663 * @throws IOException 664 */ 665 static void setLastModified(File file, long time) throws IOException { 666 Files.setLastModifiedTime(toPath(file), FileTime.fromMillis(time)); 667 } 668 669 /** 670 * @param file 671 * @return {@code true} if the given file exists, not following symbolic 672 * links 673 */ 674 static boolean exists(File file) { 675 return Files.exists(file.toPath(), LinkOption.NOFOLLOW_LINKS); 676 } 677 678 /** 679 * @param file 680 * @return {@code true} if the given file is hidden 681 * @throws IOException 682 */ 683 static boolean isHidden(File file) throws IOException { 684 return Files.isHidden(toPath(file)); 685 } 686 687 /** 688 * Set a file hidden (on Windows) 689 * 690 * @param file 691 * a {@link java.io.File} object. 692 * @param hidden 693 * a boolean. 694 * @throws java.io.IOException 695 * @since 4.1 696 */ 697 public static void setHidden(File file, boolean hidden) throws IOException { 698 Files.setAttribute(toPath(file), "dos:hidden", Boolean.valueOf(hidden), //$NON-NLS-1$ 699 LinkOption.NOFOLLOW_LINKS); 700 } 701 702 /** 703 * Get file length 704 * 705 * @param file 706 * a {@link java.io.File}. 707 * @return length of the given file 708 * @throws java.io.IOException 709 * @since 4.1 710 */ 711 public static long getLength(File file) throws IOException { 712 Path nioPath = toPath(file); 713 if (Files.isSymbolicLink(nioPath)) 714 return Files.readSymbolicLink(nioPath).toString() 715 .getBytes(Constants.CHARSET).length; 716 return Files.size(nioPath); 717 } 718 719 /** 720 * @param file 721 * @return {@code true} if the given file is a directory, not following 722 * symbolic links 723 */ 724 static boolean isDirectory(File file) { 725 return Files.isDirectory(file.toPath(), LinkOption.NOFOLLOW_LINKS); 726 } 727 728 /** 729 * @param file 730 * @return {@code true} if the given file is a file, not following symbolic 731 * links 732 */ 733 static boolean isFile(File file) { 734 return Files.isRegularFile(file.toPath(), LinkOption.NOFOLLOW_LINKS); 735 } 736 737 /** 738 * Whether the given file can be executed. 739 * 740 * @param file 741 * a {@link java.io.File} object. 742 * @return {@code true} if the given file can be executed. 743 * @since 4.1 744 */ 745 public static boolean canExecute(File file) { 746 if (!isFile(file)) { 747 return false; 748 } 749 return Files.isExecutable(file.toPath()); 750 } 751 752 /** 753 * @param fs 754 * @param file 755 * @return non null attributes object 756 */ 757 static Attributes getFileAttributesBasic(FS fs, File file) { 758 try { 759 Path nioPath = toPath(file); 760 BasicFileAttributes readAttributes = nioPath 761 .getFileSystem() 762 .provider() 763 .getFileAttributeView(nioPath, 764 BasicFileAttributeView.class, 765 LinkOption.NOFOLLOW_LINKS).readAttributes(); 766 Attributes attributes = new Attributes(fs, file, 767 true, 768 readAttributes.isDirectory(), 769 fs.supportsExecute() ? file.canExecute() : false, 770 readAttributes.isSymbolicLink(), 771 readAttributes.isRegularFile(), // 772 readAttributes.creationTime().toMillis(), // 773 readAttributes.lastModifiedTime().toMillis(), 774 readAttributes.isSymbolicLink() ? Constants 775 .encode(readSymLink(file)).length 776 : readAttributes.size()); 777 return attributes; 778 } catch (IOException e) { 779 return new Attributes(file, fs); 780 } 781 } 782 783 /** 784 * Get file system attributes for the given file. 785 * 786 * @param fs 787 * a {@link org.eclipse.jgit.util.FS} object. 788 * @param file 789 * a {@link java.io.File}. 790 * @return file system attributes for the given file. 791 * @since 4.1 792 */ 793 public static Attributes getFileAttributesPosix(FS fs, File file) { 794 try { 795 Path nioPath = toPath(file); 796 PosixFileAttributes readAttributes = nioPath 797 .getFileSystem() 798 .provider() 799 .getFileAttributeView(nioPath, 800 PosixFileAttributeView.class, 801 LinkOption.NOFOLLOW_LINKS).readAttributes(); 802 Attributes attributes = new Attributes( 803 fs, 804 file, 805 true, // 806 readAttributes.isDirectory(), // 807 readAttributes.permissions().contains( 808 PosixFilePermission.OWNER_EXECUTE), 809 readAttributes.isSymbolicLink(), 810 readAttributes.isRegularFile(), // 811 readAttributes.creationTime().toMillis(), // 812 readAttributes.lastModifiedTime().toMillis(), 813 readAttributes.size()); 814 return attributes; 815 } catch (IOException e) { 816 return new Attributes(file, fs); 817 } 818 } 819 820 /** 821 * NFC normalize a file (on Mac), otherwise do nothing 822 * 823 * @param file 824 * a {@link java.io.File}. 825 * @return on Mac: NFC normalized {@link java.io.File}, otherwise the passed 826 * file 827 * @since 4.1 828 */ 829 public static File normalize(File file) { 830 if (SystemReader.getInstance().isMacOS()) { 831 // TODO: Would it be faster to check with isNormalized first 832 // assuming normalized paths are much more common 833 String normalized = Normalizer.normalize(file.getPath(), 834 Normalizer.Form.NFC); 835 return new File(normalized); 836 } 837 return file; 838 } 839 840 /** 841 * On Mac: get NFC normalized form of given name, otherwise the given name. 842 * 843 * @param name 844 * a {@link java.lang.String} object. 845 * @return on Mac: NFC normalized form of given name 846 * @since 4.1 847 */ 848 public static String normalize(String name) { 849 if (SystemReader.getInstance().isMacOS()) { 850 if (name == null) 851 return null; 852 return Normalizer.normalize(name, Normalizer.Form.NFC); 853 } 854 return name; 855 } 856 857 /** 858 * Best-effort variation of {@link java.io.File#getCanonicalFile()} 859 * returning the input file if the file cannot be canonicalized instead of 860 * throwing {@link java.io.IOException}. 861 * 862 * @param file 863 * to be canonicalized; may be {@code null} 864 * @return canonicalized file, or the unchanged input file if 865 * canonicalization failed or if {@code file == null} 866 * @throws java.lang.SecurityException 867 * if {@link java.io.File#getCanonicalFile()} throws one 868 * @since 4.2 869 */ 870 public static File canonicalize(File file) { 871 if (file == null) { 872 return null; 873 } 874 try { 875 return file.getCanonicalFile(); 876 } catch (IOException e) { 877 return file; 878 } 879 } 880 881 /** 882 * Convert a path to String, replacing separators as necessary. 883 * 884 * @param file 885 * a {@link java.io.File}. 886 * @return file's path as a String 887 * @since 4.10 888 */ 889 public static String pathToString(File file) { 890 final String path = file.getPath(); 891 if (SystemReader.getInstance().isWindows()) { 892 return path.replace('\\', '/'); 893 } 894 return path; 895 } 896 }