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