• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.google.common.io;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 import static com.google.common.io.FileWriteMode.APPEND;
22 
23 import com.google.common.annotations.Beta;
24 import com.google.common.base.Charsets;
25 import com.google.common.base.Joiner;
26 import com.google.common.base.Predicate;
27 import com.google.common.base.Splitter;
28 import com.google.common.collect.ImmutableSet;
29 import com.google.common.collect.Lists;
30 import com.google.common.collect.TreeTraverser;
31 import com.google.common.hash.HashCode;
32 import com.google.common.hash.HashFunction;
33 
34 import java.io.BufferedReader;
35 import java.io.BufferedWriter;
36 import java.io.File;
37 import java.io.FileInputStream;
38 import java.io.FileNotFoundException;
39 import java.io.FileOutputStream;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.io.InputStreamReader;
43 import java.io.OutputStream;
44 import java.io.OutputStreamWriter;
45 import java.io.RandomAccessFile;
46 import java.nio.MappedByteBuffer;
47 import java.nio.channels.FileChannel;
48 import java.nio.channels.FileChannel.MapMode;
49 import java.nio.charset.Charset;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.Collections;
53 import 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
65 public final class Files {
66 
67   /** Maximum loop count when creating temp directories. */
68   private static final int TEMP_DIR_ATTEMPTS = 10000;
69 
Files()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    */
newReader(File file, Charset charset)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    */
newWriter(File file, Charset charset)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    */
asByteSource(File file)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 
FileByteSource(File file)119     private FileByteSource(File file) {
120       this.file = checkNotNull(file);
121     }
122 
123     @Override
openStream()124     public FileInputStream openStream() throws IOException {
125       return new FileInputStream(file);
126     }
127 
128     @Override
size()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
read()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
toString()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    */
readFile( InputStream in, long expectedSize)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    */
asByteSink(File file, FileWriteMode... modes)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 
FileByteSink(File file, FileWriteMode... modes)193     private FileByteSink(File file, FileWriteMode... modes) {
194       this.file = checkNotNull(file);
195       this.modes = ImmutableSet.copyOf(modes);
196     }
197 
198     @Override
openStream()199     public FileOutputStream openStream() throws IOException {
200       return new FileOutputStream(file, modes.contains(APPEND));
201     }
202 
203     @Override
toString()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    */
asCharSource(File file, Charset charset)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    */
asCharSink(File file, Charset charset, FileWriteMode... modes)229   public static CharSink asCharSink(File file, Charset charset,
230       FileWriteMode... modes) {
231     return asByteSink(file, modes).asCharSink(charset);
232   }
233 
modes(boolean append)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    */
toByteArray(File file)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    */
toString(File file, Charset charset)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    */
write(byte[] from, File to)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    */
copy(File from, OutputStream to)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    */
copy(File from, File to)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    */
write(CharSequence from, File to, Charset charset)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    */
append(CharSequence from, File to, Charset charset)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    */
write(CharSequence from, File to, Charset charset, boolean append)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    */
copy(File from, Charset charset, Appendable to)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    */
equal(File file1, File file2)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    */
createTempDir()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    */
touch(File file)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    */
createParentDirs(File file)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    */
move(File from, File to)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    */
readFirstLine(File file, Charset charset)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    */
readLines(File file, Charset charset)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    */
readLines(File file, Charset charset, LineProcessor<T> callback)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    */
readBytes(File file, ByteProcessor<T> processor)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    */
hash(File file, HashFunction hashFunction)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    */
map(File file)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    */
map(File file, MapMode mode)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    */
map(File file, MapMode mode, long size)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 
map(RandomAccessFile raf, MapMode mode, long size)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    */
simplifyPath(String pathname)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    */
getFileExtension(String fullName)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    */
getNameWithoutExtension(String file)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    */
fileTreeTraverser()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    */
isDirectory()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    */
isFile()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
apply(File file)848       public boolean apply(File file) {
849         return file.isDirectory();
850       }
851 
852       @Override
toString()853       public String toString() {
854         return "Files.isDirectory()";
855       }
856     },
857 
858     IS_FILE {
859       @Override
apply(File file)860       public boolean apply(File file) {
861         return file.isFile();
862       }
863 
864       @Override
toString()865       public String toString() {
866         return "Files.isFile()";
867       }
868     };
869   }
870 }
871