You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FileUtils.java 27KB

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