1/*
2 * Copyright (C) 2007 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.common.io;
18
19import static com.google.common.base.Preconditions.checkArgument;
20import static com.google.common.base.Preconditions.checkNotNull;
21import static com.google.common.io.FileWriteMode.APPEND;
22
23import com.google.common.annotations.Beta;
24import com.google.common.base.Charsets;
25import com.google.common.base.Joiner;
26import com.google.common.base.Predicate;
27import com.google.common.base.Splitter;
28import com.google.common.collect.ImmutableSet;
29import com.google.common.collect.Lists;
30import com.google.common.collect.TreeTraverser;
31import com.google.common.hash.HashCode;
32import com.google.common.hash.HashFunction;
33
34import java.io.BufferedReader;
35import java.io.BufferedWriter;
36import java.io.File;
37import java.io.FileInputStream;
38import java.io.FileNotFoundException;
39import java.io.FileOutputStream;
40import java.io.IOException;
41import java.io.InputStream;
42import java.io.InputStreamReader;
43import java.io.OutputStream;
44import java.io.OutputStreamWriter;
45import java.io.RandomAccessFile;
46import java.nio.MappedByteBuffer;
47import java.nio.channels.FileChannel;
48import java.nio.channels.FileChannel.MapMode;
49import java.nio.charset.Charset;
50import java.util.ArrayList;
51import java.util.Arrays;
52import java.util.Collections;
53import java.util.List;
54
55/**
56 * Provides utility methods for working with files.
57 *
58 * <p>All method parameters must be non-null unless documented otherwise.
59 *
60 * @author Chris Nokleberg
61 * @author Colin Decker
62 * @since 1.0
63 */
64@Beta
65public final class Files {
66
67  /** Maximum loop count when creating temp directories. */
68  private static final int TEMP_DIR_ATTEMPTS = 10000;
69
70  private Files() {}
71
72  /**
73   * Returns a buffered reader that reads from a file using the given
74   * character set.
75   *
76   * @param file the file to read from
77   * @param charset the charset used to decode the input stream; see {@link
78   *     Charsets} for helpful predefined constants
79   * @return the buffered reader
80   */
81  public static BufferedReader newReader(File file, Charset charset)
82      throws FileNotFoundException {
83    checkNotNull(file);
84    checkNotNull(charset);
85    return new BufferedReader(
86        new InputStreamReader(new FileInputStream(file), charset));
87  }
88
89  /**
90   * Returns a buffered writer that writes to a file using the given
91   * character set.
92   *
93   * @param file the file to write to
94   * @param charset the charset used to encode the output stream; see {@link
95   *     Charsets} for helpful predefined constants
96   * @return the buffered writer
97   */
98  public static BufferedWriter newWriter(File file, Charset charset)
99      throws FileNotFoundException {
100    checkNotNull(file);
101    checkNotNull(charset);
102    return new BufferedWriter(
103        new OutputStreamWriter(new FileOutputStream(file), charset));
104  }
105
106  /**
107   * Returns a new {@link ByteSource} for reading bytes from the given file.
108   *
109   * @since 14.0
110   */
111  public static ByteSource asByteSource(File file) {
112    return new FileByteSource(file);
113  }
114
115  private static final class FileByteSource extends ByteSource {
116
117    private final File file;
118
119    private FileByteSource(File file) {
120      this.file = checkNotNull(file);
121    }
122
123    @Override
124    public FileInputStream openStream() throws IOException {
125      return new FileInputStream(file);
126    }
127
128    @Override
129    public long size() throws IOException {
130      if (!file.isFile()) {
131        throw new FileNotFoundException(file.toString());
132      }
133      return file.length();
134    }
135
136    @Override
137    public byte[] read() throws IOException {
138      Closer closer = Closer.create();
139      try {
140        FileInputStream in = closer.register(openStream());
141        return readFile(in, in.getChannel().size());
142      } catch (Throwable e) {
143        throw closer.rethrow(e);
144      } finally {
145        closer.close();
146      }
147    }
148
149    @Override
150    public String toString() {
151      return "Files.asByteSource(" + file + ")";
152    }
153  }
154
155  /**
156   * Reads a file of the given expected size from the given input stream, if
157   * it will fit into a byte array. This method handles the case where the file
158   * size changes between when the size is read and when the contents are read
159   * from the stream.
160   */
161  static byte[] readFile(
162      InputStream in, long expectedSize) throws IOException {
163    if (expectedSize > Integer.MAX_VALUE) {
164      throw new OutOfMemoryError("file is too large to fit in a byte array: "
165          + expectedSize + " bytes");
166    }
167
168    // some special files may return size 0 but have content, so read
169    // the file normally in that case
170    return expectedSize == 0
171        ? ByteStreams.toByteArray(in)
172        : ByteStreams.toByteArray(in, (int) expectedSize);
173  }
174
175  /**
176   * Returns a new {@link ByteSink} for writing bytes to the given file. The
177   * given {@code modes} control how the file is opened for writing. When no
178   * mode is provided, the file will be truncated before writing. When the
179   * {@link FileWriteMode#APPEND APPEND} mode is provided, writes will
180   * append to the end of the file without truncating it.
181   *
182   * @since 14.0
183   */
184  public static ByteSink asByteSink(File file, FileWriteMode... modes) {
185    return new FileByteSink(file, modes);
186  }
187
188  private static final class FileByteSink extends ByteSink {
189
190    private final File file;
191    private final ImmutableSet<FileWriteMode> modes;
192
193    private FileByteSink(File file, FileWriteMode... modes) {
194      this.file = checkNotNull(file);
195      this.modes = ImmutableSet.copyOf(modes);
196    }
197
198    @Override
199    public FileOutputStream openStream() throws IOException {
200      return new FileOutputStream(file, modes.contains(APPEND));
201    }
202
203    @Override
204    public String toString() {
205      return "Files.asByteSink(" + file + ", " + modes + ")";
206    }
207  }
208
209  /**
210   * Returns a new {@link CharSource} for reading character data from the given
211   * file using the given character set.
212   *
213   * @since 14.0
214   */
215  public static CharSource asCharSource(File file, Charset charset) {
216    return asByteSource(file).asCharSource(charset);
217  }
218
219  /**
220   * Returns a new {@link CharSink} for writing character data to the given
221   * file using the given character set. The given {@code modes} control how
222   * the file is opened for writing. When no mode is provided, the file
223   * will be truncated before writing. When the
224   * {@link FileWriteMode#APPEND APPEND} mode is provided, writes will
225   * append to the end of the file without truncating it.
226   *
227   * @since 14.0
228   */
229  public static CharSink asCharSink(File file, Charset charset,
230      FileWriteMode... modes) {
231    return asByteSink(file, modes).asCharSink(charset);
232  }
233
234  private static FileWriteMode[] modes(boolean append) {
235    return append
236        ? new FileWriteMode[]{ FileWriteMode.APPEND }
237        : new FileWriteMode[0];
238  }
239
240  /**
241   * Reads all bytes from a file into a byte array.
242   *
243   * @param file the file to read from
244   * @return a byte array containing all the bytes from file
245   * @throws IllegalArgumentException if the file is bigger than the largest
246   *     possible byte array (2^31 - 1)
247   * @throws IOException if an I/O error occurs
248   */
249  public static byte[] toByteArray(File file) throws IOException {
250    return asByteSource(file).read();
251  }
252
253  /**
254   * Reads all characters from a file into a {@link String}, using the given
255   * character set.
256   *
257   * @param file the file to read from
258   * @param charset the charset used to decode the input stream; see {@link
259   *     Charsets} for helpful predefined constants
260   * @return a string containing all the characters from the file
261   * @throws IOException if an I/O error occurs
262   */
263  public static String toString(File file, Charset charset) throws IOException {
264    return asCharSource(file, charset).read();
265  }
266
267  /**
268   * Overwrites a file with the contents of a byte array.
269   *
270   * @param from the bytes to write
271   * @param to the destination file
272   * @throws IOException if an I/O error occurs
273   */
274  public static void write(byte[] from, File to) throws IOException {
275    asByteSink(to).write(from);
276  }
277
278  /**
279   * Copies all bytes from a file to an output stream.
280   *
281   * @param from the source file
282   * @param to the output stream
283   * @throws IOException if an I/O error occurs
284   */
285  public static void copy(File from, OutputStream to) throws IOException {
286    asByteSource(from).copyTo(to);
287  }
288
289  /**
290   * Copies all the bytes from one file to another.
291   *
292   * <p><b>Warning:</b> If {@code to} represents an existing file, that file
293   * will be overwritten with the contents of {@code from}. If {@code to} and
294   * {@code from} refer to the <i>same</i> file, the contents of that file
295   * will be deleted.
296   *
297   * @param from the source file
298   * @param to the destination file
299   * @throws IOException if an I/O error occurs
300   * @throws IllegalArgumentException if {@code from.equals(to)}
301   */
302  public static void copy(File from, File to) throws IOException {
303    checkArgument(!from.equals(to),
304        "Source %s and destination %s must be different", from, to);
305    asByteSource(from).copyTo(asByteSink(to));
306  }
307
308  /**
309   * Writes a character sequence (such as a string) to a file using the given
310   * character set.
311   *
312   * @param from the character sequence to write
313   * @param to the destination file
314   * @param charset the charset used to encode the output stream; see {@link
315   *     Charsets} for helpful predefined constants
316   * @throws IOException if an I/O error occurs
317   */
318  public static void write(CharSequence from, File to, Charset charset)
319      throws IOException {
320    asCharSink(to, charset).write(from);
321  }
322
323  /**
324   * Appends a character sequence (such as a string) to a file using the given
325   * character set.
326   *
327   * @param from the character sequence to append
328   * @param to the destination file
329   * @param charset the charset used to encode the output stream; see {@link
330   *     Charsets} for helpful predefined constants
331   * @throws IOException if an I/O error occurs
332   */
333  public static void append(CharSequence from, File to, Charset charset)
334      throws IOException {
335    write(from, to, charset, true);
336  }
337
338  /**
339   * Private helper method. Writes a character sequence to a file,
340   * optionally appending.
341   *
342   * @param from the character sequence to append
343   * @param to the destination file
344   * @param charset the charset used to encode the output stream; see {@link
345   *     Charsets} for helpful predefined constants
346   * @param append true to append, false to overwrite
347   * @throws IOException if an I/O error occurs
348   */
349  private static void write(CharSequence from, File to, Charset charset,
350      boolean append) throws IOException {
351    asCharSink(to, charset, modes(append)).write(from);
352  }
353
354  /**
355   * Copies all characters from a file to an appendable object,
356   * using the given character set.
357   *
358   * @param from the source file
359   * @param charset the charset used to decode the input stream; see {@link
360   *     Charsets} for helpful predefined constants
361   * @param to the appendable object
362   * @throws IOException if an I/O error occurs
363   */
364  public static void copy(File from, Charset charset, Appendable to)
365      throws IOException {
366    asCharSource(from, charset).copyTo(to);
367  }
368
369  /**
370   * Returns true if the files contains the same bytes.
371   *
372   * @throws IOException if an I/O error occurs
373   */
374  public static boolean equal(File file1, File file2) throws IOException {
375    checkNotNull(file1);
376    checkNotNull(file2);
377    if (file1 == file2 || file1.equals(file2)) {
378      return true;
379    }
380
381    /*
382     * Some operating systems may return zero as the length for files
383     * denoting system-dependent entities such as devices or pipes, in
384     * which case we must fall back on comparing the bytes directly.
385     */
386    long len1 = file1.length();
387    long len2 = file2.length();
388    if (len1 != 0 && len2 != 0 && len1 != len2) {
389      return false;
390    }
391    return asByteSource(file1).contentEquals(asByteSource(file2));
392  }
393
394  /**
395   * Atomically creates a new directory somewhere beneath the system's
396   * temporary directory (as defined by the {@code java.io.tmpdir} system
397   * property), and returns its name.
398   *
399   * <p>Use this method instead of {@link File#createTempFile(String, String)}
400   * when you wish to create a directory, not a regular file.  A common pitfall
401   * is to call {@code createTempFile}, delete the file and create a
402   * directory in its place, but this leads a race condition which can be
403   * exploited to create security vulnerabilities, especially when executable
404   * files are to be written into the directory.
405   *
406   * <p>This method assumes that the temporary volume is writable, has free
407   * inodes and free blocks, and that it will not be called thousands of times
408   * per second.
409   *
410   * @return the newly-created directory
411   * @throws IllegalStateException if the directory could not be created
412   */
413  public static File createTempDir() {
414    File baseDir = new File(System.getProperty("java.io.tmpdir"));
415    String baseName = System.currentTimeMillis() + "-";
416
417    for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
418      File tempDir = new File(baseDir, baseName + counter);
419      if (tempDir.mkdir()) {
420        return tempDir;
421      }
422    }
423    throw new IllegalStateException("Failed to create directory within "
424        + TEMP_DIR_ATTEMPTS + " attempts (tried "
425        + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')');
426  }
427
428  /**
429   * Creates an empty file or updates the last updated timestamp on the
430   * same as the unix command of the same name.
431   *
432   * @param file the file to create or update
433   * @throws IOException if an I/O error occurs
434   */
435  public static void touch(File file) throws IOException {
436    checkNotNull(file);
437    if (!file.createNewFile()
438        && !file.setLastModified(System.currentTimeMillis())) {
439      throw new IOException("Unable to update modification time of " + file);
440    }
441  }
442
443  /**
444   * Creates any necessary but nonexistent parent directories of the specified
445   * file. Note that if this operation fails it may have succeeded in creating
446   * some (but not all) of the necessary parent directories.
447   *
448   * @throws IOException if an I/O error occurs, or if any necessary but
449   *     nonexistent parent directories of the specified file could not be
450   *     created.
451   * @since 4.0
452   */
453  public static void createParentDirs(File file) throws IOException {
454    checkNotNull(file);
455    File parent = file.getCanonicalFile().getParentFile();
456    if (parent == null) {
457      /*
458       * The given directory is a filesystem root. All zero of its ancestors
459       * exist. This doesn't mean that the root itself exists -- consider x:\ on
460       * a Windows machine without such a drive -- or even that the caller can
461       * create it, but this method makes no such guarantees even for non-root
462       * files.
463       */
464      return;
465    }
466    parent.mkdirs();
467    if (!parent.isDirectory()) {
468      throw new IOException("Unable to create parent directories of " + file);
469    }
470  }
471
472  /**
473   * Moves a file from one path to another. This method can rename a file
474   * and/or move it to a different directory. In either case {@code to} must
475   * be the target path for the file itself; not just the new name for the
476   * file or the path to the new parent directory.
477   *
478   * @param from the source file
479   * @param to the destination file
480   * @throws IOException if an I/O error occurs
481   * @throws IllegalArgumentException if {@code from.equals(to)}
482   */
483  public static void move(File from, File to) throws IOException {
484    checkNotNull(from);
485    checkNotNull(to);
486    checkArgument(!from.equals(to),
487        "Source %s and destination %s must be different", from, to);
488
489    if (!from.renameTo(to)) {
490      copy(from, to);
491      if (!from.delete()) {
492        if (!to.delete()) {
493          throw new IOException("Unable to delete " + to);
494        }
495        throw new IOException("Unable to delete " + from);
496      }
497    }
498  }
499
500  /**
501   * Reads the first line from a file. The line does not include
502   * line-termination characters, but does include other leading and
503   * trailing whitespace.
504   *
505   * @param file the file to read from
506   * @param charset the charset used to decode the input stream; see {@link
507   *     Charsets} for helpful predefined constants
508   * @return the first line, or null if the file is empty
509   * @throws IOException if an I/O error occurs
510   */
511  public static String readFirstLine(File file, Charset charset)
512      throws IOException {
513    return asCharSource(file, charset).readFirstLine();
514  }
515
516  /**
517   * Reads all of the lines from a file. The lines do not include
518   * line-termination characters, but do include other leading and
519   * trailing whitespace.
520   *
521   * <p>This method returns a mutable {@code List}. For an
522   * {@code ImmutableList}, use
523   * {@code Files.asCharSource(file, charset).readLines()}.
524   *
525   * @param file the file to read from
526   * @param charset the charset used to decode the input stream; see {@link
527   *     Charsets} for helpful predefined constants
528   * @return a mutable {@link List} containing all the lines
529   * @throws IOException if an I/O error occurs
530   */
531  public static List<String> readLines(File file, Charset charset)
532      throws IOException {
533    // don't use asCharSource(file, charset).readLines() because that returns
534    // an immutable list, which would change the behavior of this method
535    return readLines(file, charset, new LineProcessor<List<String>>() {
536      final List<String> result = Lists.newArrayList();
537
538      @Override
539      public boolean processLine(String line) {
540        result.add(line);
541        return true;
542      }
543
544      @Override
545      public List<String> getResult() {
546        return result;
547      }
548    });
549  }
550
551  /**
552   * Streams lines from a {@link File}, stopping when our callback returns
553   * false, or we have read all of the lines.
554   *
555   * @param file the file to read from
556   * @param charset the charset used to decode the input stream; see {@link
557   *     Charsets} for helpful predefined constants
558   * @param callback the {@link LineProcessor} to use to handle the lines
559   * @return the output of processing the lines
560   * @throws IOException if an I/O error occurs
561   */
562  public static <T> T readLines(File file, Charset charset,
563      LineProcessor<T> callback) throws IOException {
564    return asCharSource(file, charset).readLines(callback);
565  }
566
567  /**
568   * Process the bytes of a file.
569   *
570   * <p>(If this seems too complicated, maybe you're looking for
571   * {@link #toByteArray}.)
572   *
573   * @param file the file to read
574   * @param processor the object to which the bytes of the file are passed.
575   * @return the result of the byte processor
576   * @throws IOException if an I/O error occurs
577   */
578  public static <T> T readBytes(File file, ByteProcessor<T> processor)
579      throws IOException {
580    return asByteSource(file).read(processor);
581  }
582
583  /**
584   * Computes the hash code of the {@code file} using {@code hashFunction}.
585   *
586   * @param file the file to read
587   * @param hashFunction the hash function to use to hash the data
588   * @return the {@link HashCode} of all of the bytes in the file
589   * @throws IOException if an I/O error occurs
590   * @since 12.0
591   */
592  public static HashCode hash(File file, HashFunction hashFunction)
593      throws IOException {
594    return asByteSource(file).hash(hashFunction);
595  }
596
597  /**
598   * Fully maps a file read-only in to memory as per
599   * {@link FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)}.
600   *
601   * <p>Files are mapped from offset 0 to its length.
602   *
603   * <p>This only works for files <= {@link Integer#MAX_VALUE} bytes.
604   *
605   * @param file the file to map
606   * @return a read-only buffer reflecting {@code file}
607   * @throws FileNotFoundException if the {@code file} does not exist
608   * @throws IOException if an I/O error occurs
609   *
610   * @see FileChannel#map(MapMode, long, long)
611   * @since 2.0
612   */
613  public static MappedByteBuffer map(File file) throws IOException {
614    checkNotNull(file);
615    return map(file, MapMode.READ_ONLY);
616  }
617
618  /**
619   * Fully maps a file in to memory as per
620   * {@link FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)}
621   * using the requested {@link MapMode}.
622   *
623   * <p>Files are mapped from offset 0 to its length.
624   *
625   * <p>This only works for files <= {@link Integer#MAX_VALUE} bytes.
626   *
627   * @param file the file to map
628   * @param mode the mode to use when mapping {@code file}
629   * @return a buffer reflecting {@code file}
630   * @throws FileNotFoundException if the {@code file} does not exist
631   * @throws IOException if an I/O error occurs
632   *
633   * @see FileChannel#map(MapMode, long, long)
634   * @since 2.0
635   */
636  public static MappedByteBuffer map(File file, MapMode mode)
637      throws IOException {
638    checkNotNull(file);
639    checkNotNull(mode);
640    if (!file.exists()) {
641      throw new FileNotFoundException(file.toString());
642    }
643    return map(file, mode, file.length());
644  }
645
646  /**
647   * Maps a file in to memory as per
648   * {@link FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)}
649   * using the requested {@link MapMode}.
650   *
651   * <p>Files are mapped from offset 0 to {@code size}.
652   *
653   * <p>If the mode is {@link MapMode#READ_WRITE} and the file does not exist,
654   * it will be created with the requested {@code size}. Thus this method is
655   * useful for creating memory mapped files which do not yet exist.
656   *
657   * <p>This only works for files <= {@link Integer#MAX_VALUE} bytes.
658   *
659   * @param file the file to map
660   * @param mode the mode to use when mapping {@code file}
661   * @return a buffer reflecting {@code file}
662   * @throws IOException if an I/O error occurs
663   *
664   * @see FileChannel#map(MapMode, long, long)
665   * @since 2.0
666   */
667  public static MappedByteBuffer map(File file, MapMode mode, long size)
668      throws FileNotFoundException, IOException {
669    checkNotNull(file);
670    checkNotNull(mode);
671
672    Closer closer = Closer.create();
673    try {
674      RandomAccessFile raf = closer.register(
675          new RandomAccessFile(file, mode == MapMode.READ_ONLY ? "r" : "rw"));
676      return map(raf, mode, size);
677    } catch (Throwable e) {
678      throw closer.rethrow(e);
679    } finally {
680      closer.close();
681    }
682  }
683
684  private static MappedByteBuffer map(RandomAccessFile raf, MapMode mode,
685      long size) throws IOException {
686    Closer closer = Closer.create();
687    try {
688      FileChannel channel = closer.register(raf.getChannel());
689      return channel.map(mode, 0, size);
690    } catch (Throwable e) {
691      throw closer.rethrow(e);
692    } finally {
693      closer.close();
694    }
695  }
696
697  /**
698   * Returns the lexically cleaned form of the path name, <i>usually</i> (but
699   * not always) equivalent to the original. The following heuristics are used:
700   *
701   * <ul>
702   * <li>empty string becomes .
703   * <li>. stays as .
704   * <li>fold out ./
705   * <li>fold out ../ when possible
706   * <li>collapse multiple slashes
707   * <li>delete trailing slashes (unless the path is just "/")
708   * </ul>
709   *
710   * <p>These heuristics do not always match the behavior of the filesystem. In
711   * particular, consider the path {@code a/../b}, which {@code simplifyPath}
712   * will change to {@code b}. If {@code a} is a symlink to {@code x}, {@code
713   * a/../b} may refer to a sibling of {@code x}, rather than the sibling of
714   * {@code a} referred to by {@code b}.
715   *
716   * @since 11.0
717   */
718  public static String simplifyPath(String pathname) {
719    checkNotNull(pathname);
720    if (pathname.length() == 0) {
721      return ".";
722    }
723
724    // split the path apart
725    Iterable<String> components =
726        Splitter.on('/').omitEmptyStrings().split(pathname);
727    List<String> path = new ArrayList<String>();
728
729    // resolve ., .., and //
730    for (String component : components) {
731      if (component.equals(".")) {
732        continue;
733      } else if (component.equals("..")) {
734        if (path.size() > 0 && !path.get(path.size() - 1).equals("..")) {
735          path.remove(path.size() - 1);
736        } else {
737          path.add("..");
738        }
739      } else {
740        path.add(component);
741      }
742    }
743
744    // put it back together
745    String result = Joiner.on('/').join(path);
746    if (pathname.charAt(0) == '/') {
747      result = "/" + result;
748    }
749
750    while (result.startsWith("/../")) {
751      result = result.substring(3);
752    }
753    if (result.equals("/..")) {
754      result = "/";
755    } else if ("".equals(result)) {
756      result = ".";
757    }
758
759    return result;
760  }
761
762  /**
763   * Returns the <a href="http://en.wikipedia.org/wiki/Filename_extension">file
764   * extension</a> for the given file name, or the empty string if the file has
765   * no extension.  The result does not include the '{@code .}'.
766   *
767   * @since 11.0
768   */
769  public static String getFileExtension(String fullName) {
770    checkNotNull(fullName);
771    String fileName = new File(fullName).getName();
772    int dotIndex = fileName.lastIndexOf('.');
773    return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1);
774  }
775
776  /**
777   * Returns the file name without its
778   * <a href="http://en.wikipedia.org/wiki/Filename_extension">file extension</a> or path. This is
779   * similar to the {@code basename} unix command. The result does not include the '{@code .}'.
780   *
781   * @param file The name of the file to trim the extension from. This can be either a fully
782   *     qualified file name (including a path) or just a file name.
783   * @return The file name without its path or extension.
784   * @since 14.0
785   */
786  public static String getNameWithoutExtension(String file) {
787    checkNotNull(file);
788    String fileName = new File(file).getName();
789    int dotIndex = fileName.lastIndexOf('.');
790    return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex);
791  }
792
793  /**
794   * Returns a {@link TreeTraverser} instance for {@link File} trees.
795   *
796   * <p><b>Warning:</b> {@code File} provides no support for symbolic links, and as such there is no
797   * way to ensure that a symbolic link to a directory is not followed when traversing the tree.
798   * In this case, iterables created by this traverser could contain files that are outside of the
799   * given directory or even be infinite if there is a symbolic link loop.
800   *
801   * @since 15.0
802   */
803  public static TreeTraverser<File> fileTreeTraverser() {
804    return FILE_TREE_TRAVERSER;
805  }
806
807  private static final TreeTraverser<File> FILE_TREE_TRAVERSER = new TreeTraverser<File>() {
808    @Override
809    public Iterable<File> children(File file) {
810      // check isDirectory() just because it may be faster than listFiles() on a non-directory
811      if (file.isDirectory()) {
812        File[] files = file.listFiles();
813        if (files != null) {
814          return Collections.unmodifiableList(Arrays.asList(files));
815        }
816      }
817
818      return Collections.emptyList();
819    }
820
821    @Override
822    public String toString() {
823      return "Files.fileTreeTraverser()";
824    }
825  };
826
827  /**
828   * Returns a predicate that returns the result of {@link File#isDirectory} on input files.
829   *
830   * @since 15.0
831   */
832  public static Predicate<File> isDirectory() {
833    return FilePredicate.IS_DIRECTORY;
834  }
835
836  /**
837   * Returns a predicate that returns the result of {@link File#isFile} on input files.
838   *
839   * @since 15.0
840   */
841  public static Predicate<File> isFile() {
842    return FilePredicate.IS_FILE;
843  }
844
845  private enum FilePredicate implements Predicate<File> {
846    IS_DIRECTORY {
847      @Override
848      public boolean apply(File file) {
849        return file.isDirectory();
850      }
851
852      @Override
853      public String toString() {
854        return "Files.isDirectory()";
855      }
856    },
857
858    IS_FILE {
859      @Override
860      public boolean apply(File file) {
861        return file.isFile();
862      }
863
864      @Override
865      public String toString() {
866        return "Files.isFile()";
867      }
868    };
869  }
870}
871