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.

FS.java 21KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805
  1. /*
  2. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.util;
  44. import java.io.BufferedReader;
  45. import java.io.File;
  46. import java.io.IOException;
  47. import java.io.InputStream;
  48. import java.io.InputStreamReader;
  49. import java.security.AccessController;
  50. import java.security.PrivilegedAction;
  51. import java.text.MessageFormat;
  52. import java.util.Arrays;
  53. import java.util.concurrent.atomic.AtomicBoolean;
  54. import org.eclipse.jgit.errors.SymlinksNotSupportedException;
  55. import org.eclipse.jgit.internal.JGitText;
  56. /** Abstraction to support various file system operations not in Java. */
  57. public abstract class FS {
  58. /**
  59. * This class creates FS instances. It will be overridden by a Java7 variant
  60. * if such can be detected in {@link #detect(Boolean)}.
  61. *
  62. * @since 3.0
  63. */
  64. public static class FSFactory {
  65. /**
  66. * Constructor
  67. */
  68. protected FSFactory() {
  69. // empty
  70. }
  71. /**
  72. * Detect the file system
  73. *
  74. * @param cygwinUsed
  75. * @return FS instance
  76. */
  77. public FS detect(Boolean cygwinUsed) {
  78. if (SystemReader.getInstance().isWindows()) {
  79. if (cygwinUsed == null)
  80. cygwinUsed = Boolean.valueOf(FS_Win32_Cygwin.isCygwin());
  81. if (cygwinUsed.booleanValue())
  82. return new FS_Win32_Cygwin();
  83. else
  84. return new FS_Win32();
  85. } else if (FS_POSIX_Java6.hasExecute())
  86. return new FS_POSIX_Java6();
  87. else
  88. return new FS_POSIX_Java5();
  89. }
  90. }
  91. /** The auto-detected implementation selected for this operating system and JRE. */
  92. public static final FS DETECTED = detect();
  93. private static FSFactory factory;
  94. /**
  95. * Auto-detect the appropriate file system abstraction.
  96. *
  97. * @return detected file system abstraction
  98. */
  99. public static FS detect() {
  100. return detect(null);
  101. }
  102. /**
  103. * Auto-detect the appropriate file system abstraction, taking into account
  104. * the presence of a Cygwin installation on the system. Using jgit in
  105. * combination with Cygwin requires a more elaborate (and possibly slower)
  106. * resolution of file system paths.
  107. *
  108. * @param cygwinUsed
  109. * <ul>
  110. * <li><code>Boolean.TRUE</code> to assume that Cygwin is used in
  111. * combination with jgit</li>
  112. * <li><code>Boolean.FALSE</code> to assume that Cygwin is
  113. * <b>not</b> used with jgit</li>
  114. * <li><code>null</code> to auto-detect whether a Cygwin
  115. * installation is present on the system and in this case assume
  116. * that Cygwin is used</li>
  117. * </ul>
  118. *
  119. * Note: this parameter is only relevant on Windows.
  120. *
  121. * @return detected file system abstraction
  122. */
  123. public static FS detect(Boolean cygwinUsed) {
  124. if (factory == null) {
  125. try {
  126. Class<?> activatorClass = Class
  127. .forName("org.eclipse.jgit.util.Java7FSFactory"); //$NON-NLS-1$
  128. // found Java7
  129. factory = (FSFactory) activatorClass.newInstance();
  130. } catch (ClassNotFoundException e) {
  131. // Java7 module not found
  132. // Silently ignore failure to find Java7 FS factory
  133. factory = new FS.FSFactory();
  134. } catch (UnsupportedClassVersionError e) {
  135. factory = new FS.FSFactory();
  136. } catch (InstantiationException e) {
  137. factory = new FS.FSFactory();
  138. } catch (IllegalAccessException e) {
  139. factory = new FS.FSFactory();
  140. }
  141. }
  142. return factory.detect(cygwinUsed);
  143. }
  144. private volatile Holder<File> userHome;
  145. private volatile Holder<File> gitPrefix;
  146. /**
  147. * Constructs a file system abstraction.
  148. */
  149. protected FS() {
  150. // Do nothing by default.
  151. }
  152. /**
  153. * Initialize this FS using another's current settings.
  154. *
  155. * @param src
  156. * the source FS to copy from.
  157. */
  158. protected FS(FS src) {
  159. userHome = src.userHome;
  160. gitPrefix = src.gitPrefix;
  161. }
  162. /** @return a new instance of the same type of FS. */
  163. public abstract FS newInstance();
  164. /**
  165. * Does this operating system and JRE support the execute flag on files?
  166. *
  167. * @return true if this implementation can provide reasonably accurate
  168. * executable bit information; false otherwise.
  169. */
  170. public abstract boolean supportsExecute();
  171. /**
  172. * Does this operating system and JRE supports symbolic links. The
  173. * capability to handle symbolic links is detected at runtime.
  174. *
  175. * @return true if symbolic links may be used
  176. * @since 3.0
  177. */
  178. public boolean supportsSymlinks() {
  179. return false;
  180. }
  181. /**
  182. * Is this file system case sensitive
  183. *
  184. * @return true if this implementation is case sensitive
  185. */
  186. public abstract boolean isCaseSensitive();
  187. /**
  188. * Determine if the file is executable (or not).
  189. * <p>
  190. * Not all platforms and JREs support executable flags on files. If the
  191. * feature is unsupported this method will always return false.
  192. * <p>
  193. * <em>If the platform supports symbolic links and <code>f</code> is a symbolic link
  194. * this method returns false, rather than the state of the executable flags
  195. * on the target file.</em>
  196. *
  197. * @param f
  198. * abstract path to test.
  199. * @return true if the file is believed to be executable by the user.
  200. */
  201. public abstract boolean canExecute(File f);
  202. /**
  203. * Set a file to be executable by the user.
  204. * <p>
  205. * Not all platforms and JREs support executable flags on files. If the
  206. * feature is unsupported this method will always return false and no
  207. * changes will be made to the file specified.
  208. *
  209. * @param f
  210. * path to modify the executable status of.
  211. * @param canExec
  212. * true to enable execution; false to disable it.
  213. * @return true if the change succeeded; false otherwise.
  214. */
  215. public abstract boolean setExecute(File f, boolean canExec);
  216. /**
  217. * Get the last modified time of a file system object. If the OS/JRE support
  218. * symbolic links, the modification time of the link is returned, rather
  219. * than that of the link target.
  220. *
  221. * @param f
  222. * @return last modified time of f
  223. * @throws IOException
  224. * @since 3.0
  225. */
  226. public long lastModified(File f) throws IOException {
  227. return f.lastModified();
  228. }
  229. /**
  230. * Set the last modified time of a file system object. If the OS/JRE support
  231. * symbolic links, the link is modified, not the target,
  232. *
  233. * @param f
  234. * @param time
  235. * @throws IOException
  236. * @since 3.0
  237. */
  238. public void setLastModified(File f, long time) throws IOException {
  239. f.setLastModified(time);
  240. }
  241. /**
  242. * Get the length of a file or link, If the OS/JRE supports symbolic links
  243. * it's the length of the link, else the length of the target.
  244. *
  245. * @param path
  246. * @return length of a file
  247. * @throws IOException
  248. * @since 3.0
  249. */
  250. public long length(File path) throws IOException {
  251. return path.length();
  252. }
  253. /**
  254. * Delete a file. Throws an exception if delete fails.
  255. *
  256. * @param f
  257. * @throws IOException
  258. * this may be a Java7 subclass with detailed information
  259. * @since 3.3
  260. */
  261. public void delete(File f) throws IOException {
  262. if (!f.delete())
  263. throw new IOException(MessageFormat.format(
  264. JGitText.get().deleteFileFailed, f.getAbsolutePath()));
  265. }
  266. /**
  267. * Resolve this file to its actual path name that the JRE can use.
  268. * <p>
  269. * This method can be relatively expensive. Computing a translation may
  270. * require forking an external process per path name translated. Callers
  271. * should try to minimize the number of translations necessary by caching
  272. * the results.
  273. * <p>
  274. * Not all platforms and JREs require path name translation. Currently only
  275. * Cygwin on Win32 require translation for Cygwin based paths.
  276. *
  277. * @param dir
  278. * directory relative to which the path name is.
  279. * @param name
  280. * path name to translate.
  281. * @return the translated path. <code>new File(dir,name)</code> if this
  282. * platform does not require path name translation.
  283. */
  284. public File resolve(final File dir, final String name) {
  285. final File abspn = new File(name);
  286. if (abspn.isAbsolute())
  287. return abspn;
  288. return new File(dir, name);
  289. }
  290. /**
  291. * Determine the user's home directory (location where preferences are).
  292. * <p>
  293. * This method can be expensive on the first invocation if path name
  294. * translation is required. Subsequent invocations return a cached result.
  295. * <p>
  296. * Not all platforms and JREs require path name translation. Currently only
  297. * Cygwin on Win32 requires translation of the Cygwin HOME directory.
  298. *
  299. * @return the user's home directory; null if the user does not have one.
  300. */
  301. public File userHome() {
  302. Holder<File> p = userHome;
  303. if (p == null) {
  304. p = new Holder<File>(userHomeImpl());
  305. userHome = p;
  306. }
  307. return p.value;
  308. }
  309. /**
  310. * Set the user's home directory location.
  311. *
  312. * @param path
  313. * the location of the user's preferences; null if there is no
  314. * home directory for the current user.
  315. * @return {@code this}.
  316. */
  317. public FS setUserHome(File path) {
  318. userHome = new Holder<File>(path);
  319. return this;
  320. }
  321. /**
  322. * Does this file system have problems with atomic renames?
  323. *
  324. * @return true if the caller should retry a failed rename of a lock file.
  325. */
  326. public abstract boolean retryFailedLockFileCommit();
  327. /**
  328. * Determine the user's home directory (location where preferences are).
  329. *
  330. * @return the user's home directory; null if the user does not have one.
  331. */
  332. protected File userHomeImpl() {
  333. final String home = AccessController
  334. .doPrivileged(new PrivilegedAction<String>() {
  335. public String run() {
  336. return System.getProperty("user.home"); //$NON-NLS-1$
  337. }
  338. });
  339. if (home == null || home.length() == 0)
  340. return null;
  341. return new File(home).getAbsoluteFile();
  342. }
  343. /**
  344. * Searches the given path to see if it contains one of the given files.
  345. * Returns the first it finds. Returns null if not found or if path is null.
  346. *
  347. * @param path
  348. * List of paths to search separated by File.pathSeparator
  349. * @param lookFor
  350. * Files to search for in the given path
  351. * @return the first match found, or null
  352. * @since 3.0
  353. **/
  354. protected static File searchPath(final String path, final String... lookFor) {
  355. if (path == null)
  356. return null;
  357. for (final String p : path.split(File.pathSeparator)) {
  358. for (String command : lookFor) {
  359. final File e = new File(p, command);
  360. if (e.isFile())
  361. return e.getAbsoluteFile();
  362. }
  363. }
  364. return null;
  365. }
  366. /**
  367. * Execute a command and return a single line of output as a String
  368. *
  369. * @param dir
  370. * Working directory for the command
  371. * @param command
  372. * as component array
  373. * @param encoding
  374. * @return the one-line output of the command
  375. */
  376. protected static String readPipe(File dir, String[] command, String encoding) {
  377. final boolean debug = Boolean.parseBoolean(SystemReader.getInstance()
  378. .getProperty("jgit.fs.debug")); //$NON-NLS-1$
  379. try {
  380. if (debug)
  381. System.err.println("readpipe " + Arrays.asList(command) + "," //$NON-NLS-1$ //$NON-NLS-2$
  382. + dir);
  383. final Process p = Runtime.getRuntime().exec(command, null, dir);
  384. final BufferedReader lineRead = new BufferedReader(
  385. new InputStreamReader(p.getInputStream(), encoding));
  386. p.getOutputStream().close();
  387. final AtomicBoolean gooblerFail = new AtomicBoolean(false);
  388. Thread gobbler = new Thread() {
  389. public void run() {
  390. InputStream is = p.getErrorStream();
  391. try {
  392. int ch;
  393. if (debug)
  394. while ((ch = is.read()) != -1)
  395. System.err.print((char) ch);
  396. else
  397. while (is.read() != -1) {
  398. // ignore
  399. }
  400. } catch (IOException e) {
  401. // Just print on stderr for debugging
  402. if (debug)
  403. e.printStackTrace(System.err);
  404. gooblerFail.set(true);
  405. }
  406. try {
  407. is.close();
  408. } catch (IOException e) {
  409. // Just print on stderr for debugging
  410. if (debug)
  411. e.printStackTrace(System.err);
  412. gooblerFail.set(true);
  413. }
  414. }
  415. };
  416. gobbler.start();
  417. String r = null;
  418. try {
  419. r = lineRead.readLine();
  420. if (debug) {
  421. System.err.println("readpipe may return '" + r + "'"); //$NON-NLS-1$ //$NON-NLS-2$
  422. System.err.println("(ignoring remaing output:"); //$NON-NLS-1$
  423. }
  424. String l;
  425. while ((l = lineRead.readLine()) != null) {
  426. if (debug)
  427. System.err.println(l);
  428. }
  429. } finally {
  430. p.getErrorStream().close();
  431. lineRead.close();
  432. }
  433. for (;;) {
  434. try {
  435. int rc = p.waitFor();
  436. gobbler.join();
  437. if (rc == 0 && r != null && r.length() > 0
  438. && !gooblerFail.get())
  439. return r;
  440. if (debug)
  441. System.err.println("readpipe rc=" + rc); //$NON-NLS-1$
  442. break;
  443. } catch (InterruptedException ie) {
  444. // Stop bothering me, I have a zombie to reap.
  445. }
  446. }
  447. } catch (IOException e) {
  448. if (debug)
  449. System.err.println(e);
  450. // Ignore error (but report)
  451. }
  452. if (debug)
  453. System.err.println("readpipe returns null"); //$NON-NLS-1$
  454. return null;
  455. }
  456. /** @return the $prefix directory C Git would use. */
  457. public File gitPrefix() {
  458. Holder<File> p = gitPrefix;
  459. if (p == null) {
  460. String overrideGitPrefix = SystemReader.getInstance().getProperty(
  461. "jgit.gitprefix"); //$NON-NLS-1$
  462. if (overrideGitPrefix != null)
  463. p = new Holder<File>(new File(overrideGitPrefix));
  464. else
  465. p = new Holder<File>(discoverGitPrefix());
  466. gitPrefix = p;
  467. }
  468. return p.value;
  469. }
  470. /** @return the $prefix directory C Git would use. */
  471. protected abstract File discoverGitPrefix();
  472. /**
  473. * Set the $prefix directory C Git uses.
  474. *
  475. * @param path
  476. * the directory. Null if C Git is not installed.
  477. * @return {@code this}
  478. */
  479. public FS setGitPrefix(File path) {
  480. gitPrefix = new Holder<File>(path);
  481. return this;
  482. }
  483. /**
  484. * Check if a file is a symbolic link and read it
  485. *
  486. * @param path
  487. * @return target of link or null
  488. * @throws IOException
  489. * @since 3.0
  490. */
  491. public String readSymLink(File path) throws IOException {
  492. throw new SymlinksNotSupportedException(
  493. JGitText.get().errorSymlinksNotSupported);
  494. }
  495. /**
  496. * @param path
  497. * @return true if the path is a symbolic link (and we support these)
  498. * @throws IOException
  499. * @since 3.0
  500. */
  501. public boolean isSymLink(File path) throws IOException {
  502. return false;
  503. }
  504. /**
  505. * Tests if the path exists, in case of a symbolic link, true even if the
  506. * target does not exist
  507. *
  508. * @param path
  509. * @return true if path exists
  510. * @since 3.0
  511. */
  512. public boolean exists(File path) {
  513. return path.exists();
  514. }
  515. /**
  516. * Check if path is a directory. If the OS/JRE supports symbolic links and
  517. * path is a symbolic link to a directory, this method returns false.
  518. *
  519. * @param path
  520. * @return true if file is a directory,
  521. * @since 3.0
  522. */
  523. public boolean isDirectory(File path) {
  524. return path.isDirectory();
  525. }
  526. /**
  527. * Examine if path represents a regular file. If the OS/JRE supports
  528. * symbolic links the test returns false if path represents a symbolic link.
  529. *
  530. * @param path
  531. * @return true if path represents a regular file
  532. * @since 3.0
  533. */
  534. public boolean isFile(File path) {
  535. return path.isFile();
  536. }
  537. /**
  538. * @param path
  539. * @return true if path is hidden, either starts with . on unix or has the
  540. * hidden attribute in windows
  541. * @throws IOException
  542. * @since 3.0
  543. */
  544. public boolean isHidden(File path) throws IOException {
  545. return path.isHidden();
  546. }
  547. /**
  548. * Set the hidden attribute for file whose name starts with a period.
  549. *
  550. * @param path
  551. * @param hidden
  552. * @throws IOException
  553. * @since 3.0
  554. */
  555. public void setHidden(File path, boolean hidden) throws IOException {
  556. if (!path.getName().startsWith(".")) //$NON-NLS-1$
  557. throw new IllegalArgumentException(
  558. "Hiding only allowed for names that start with a period");
  559. }
  560. /**
  561. * Create a symbolic link
  562. *
  563. * @param path
  564. * @param target
  565. * @throws IOException
  566. * @since 3.0
  567. */
  568. public void createSymLink(File path, String target) throws IOException {
  569. throw new SymlinksNotSupportedException(
  570. JGitText.get().errorSymlinksNotSupported);
  571. }
  572. /**
  573. * Initialize a ProcesssBuilder to run a command using the system shell.
  574. *
  575. * @param cmd
  576. * command to execute. This string should originate from the
  577. * end-user, and thus is platform specific.
  578. * @param args
  579. * arguments to pass to command. These should be protected from
  580. * shell evaluation.
  581. * @return a partially completed process builder. Caller should finish
  582. * populating directory, environment, and then start the process.
  583. */
  584. public abstract ProcessBuilder runInShell(String cmd, String[] args);
  585. private static class Holder<V> {
  586. final V value;
  587. Holder(V value) {
  588. this.value = value;
  589. }
  590. }
  591. /**
  592. * File attributes we typically care for.
  593. *
  594. * @since 3.3
  595. */
  596. public static class Attributes {
  597. /**
  598. * @return true if this are the attributes of a directory
  599. */
  600. public boolean isDirectory() {
  601. return isDirectory;
  602. }
  603. /**
  604. * @return true if this are the attributes of an executable file
  605. */
  606. public boolean isExecutable() {
  607. return isExecutable;
  608. }
  609. /**
  610. * @return true if this are the attributes of a symbolic link
  611. */
  612. public boolean isSymbolicLink() {
  613. return isSymbolicLink;
  614. }
  615. /**
  616. * @return true if this are the attributes of a regular file
  617. */
  618. public boolean isRegularFile() {
  619. return isRegularFile;
  620. }
  621. /**
  622. * @return the time when the file was created
  623. */
  624. public long getCreationTime() {
  625. return creationTime;
  626. }
  627. /**
  628. * @return the time (milliseconds since 1970-01-01) when this object was
  629. * last modified
  630. */
  631. public long getLastModifiedTime() {
  632. return lastModifiedTime;
  633. }
  634. private boolean isDirectory;
  635. private boolean isSymbolicLink;
  636. private boolean isRegularFile;
  637. private long creationTime;
  638. private long lastModifiedTime;
  639. private boolean isExecutable;
  640. private File file;
  641. private boolean exists;
  642. /**
  643. * file length
  644. */
  645. protected long length = -1;
  646. FS fs;
  647. Attributes(FS fs, File file, boolean exists, boolean isDirectory,
  648. boolean isExecutable, boolean isSymbolicLink,
  649. boolean isRegularFile, long creationTime,
  650. long lastModifiedTime, long length) {
  651. this.fs = fs;
  652. this.file = file;
  653. this.exists = exists;
  654. this.isDirectory = isDirectory;
  655. this.isExecutable = isExecutable;
  656. this.isSymbolicLink = isSymbolicLink;
  657. this.isRegularFile = isRegularFile;
  658. this.creationTime = creationTime;
  659. this.lastModifiedTime = lastModifiedTime;
  660. this.length = length;
  661. }
  662. /**
  663. * Constructor when there are issues with reading
  664. *
  665. * @param fs
  666. * @param path
  667. */
  668. public Attributes(File path, FS fs) {
  669. this.file = path;
  670. this.fs = fs;
  671. }
  672. /**
  673. * @return length of this file object
  674. */
  675. public long getLength() {
  676. if (length == -1)
  677. return length = file.length();
  678. return length;
  679. }
  680. /**
  681. * @return the filename
  682. */
  683. public String getName() {
  684. return file.getName();
  685. }
  686. /**
  687. * @return the file the attributes apply to
  688. */
  689. public File getFile() {
  690. return file;
  691. }
  692. boolean exists() {
  693. return exists;
  694. }
  695. }
  696. /**
  697. * @param path
  698. * @return the file attributes we care for
  699. * @since 3.3
  700. */
  701. public Attributes getAttributes(File path) {
  702. boolean isDirectory = isDirectory(path);
  703. boolean isFile = !isDirectory && path.isFile();
  704. assert path.exists() == isDirectory || isFile;
  705. boolean exists = isDirectory || isFile;
  706. boolean canExecute = exists && !isDirectory && canExecute(path);
  707. boolean isSymlink = false;
  708. long lastModified = exists ? path.lastModified() : 0L;
  709. long createTime = 0L;
  710. return new Attributes(this, path, exists, isDirectory, canExecute,
  711. isSymlink, isFile, createTime, lastModified, -1);
  712. }
  713. /**
  714. * Normalize the unicode path to composed form.
  715. *
  716. * @param file
  717. * @return NFC-format File
  718. * @since 3.3
  719. */
  720. public File normalize(File file) {
  721. return file;
  722. }
  723. /**
  724. * Normalize the unicode path to composed form.
  725. *
  726. * @param name
  727. * @return NFC-format string
  728. * @since 3.3
  729. */
  730. public String normalize(String name) {
  731. return name;
  732. }
  733. }