001    /*
002     * Copyright (C) 2007 The Guava Authors
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package com.google.common.io;
018    
019    import static com.google.common.base.Preconditions.checkNotNull;
020    
021    import com.google.common.annotations.Beta;
022    import com.google.common.base.Charsets;
023    import com.google.common.base.Joiner;
024    import com.google.common.base.Preconditions;
025    import com.google.common.base.Splitter;
026    import com.google.common.hash.HashCode;
027    import com.google.common.hash.HashFunction;
028    
029    import java.io.BufferedReader;
030    import java.io.BufferedWriter;
031    import java.io.Closeable;
032    import java.io.File;
033    import java.io.FileInputStream;
034    import java.io.FileNotFoundException;
035    import java.io.FileOutputStream;
036    import java.io.IOException;
037    import java.io.InputStream;
038    import java.io.InputStreamReader;
039    import java.io.OutputStream;
040    import java.io.OutputStreamWriter;
041    import java.io.RandomAccessFile;
042    import java.nio.MappedByteBuffer;
043    import java.nio.channels.FileChannel;
044    import java.nio.channels.FileChannel.MapMode;
045    import java.nio.charset.Charset;
046    import java.security.MessageDigest;
047    import java.util.ArrayList;
048    import java.util.List;
049    import java.util.zip.Checksum;
050    
051    /**
052     * Provides utility methods for working with files.
053     *
054     * <p>All method parameters must be non-null unless documented otherwise.
055     *
056     * @author Chris Nokleberg
057     * @since 1.0
058     */
059    @Beta
060    public final class Files {
061    
062      /** Maximum loop count when creating temp directories. */
063      private static final int TEMP_DIR_ATTEMPTS = 10000;
064    
065      private Files() {}
066    
067      /**
068       * Returns a buffered reader that reads from a file using the given
069       * character set.
070       *
071       * @param file the file to read from
072       * @param charset the charset used to decode the input stream; see {@link
073       *     Charsets} for helpful predefined constants
074       * @return the buffered reader
075       */
076      public static BufferedReader newReader(File file, Charset charset)
077          throws FileNotFoundException {
078        return new BufferedReader(
079            new InputStreamReader(new FileInputStream(file), charset));
080      }
081    
082      /**
083       * Returns a buffered writer that writes to a file using the given
084       * character set.
085       *
086       * @param file the file to write to
087       * @param charset the charset used to encode the output stream; see {@link
088       *     Charsets} for helpful predefined constants
089       * @return the buffered writer
090       */
091      public static BufferedWriter newWriter(File file, Charset charset)
092          throws FileNotFoundException {
093        return new BufferedWriter(
094            new OutputStreamWriter(new FileOutputStream(file), charset));
095      }
096    
097      /**
098       * Returns a factory that will supply instances of {@link FileInputStream}
099       * that read from a file.
100       *
101       * @param file the file to read from
102       * @return the factory
103       */
104      public static InputSupplier<FileInputStream> newInputStreamSupplier(
105          final File file) {
106        Preconditions.checkNotNull(file);
107        return new InputSupplier<FileInputStream>() {
108          @Override
109          public FileInputStream getInput() throws IOException {
110            return new FileInputStream(file);
111          }
112        };
113      }
114    
115      /**
116       * Returns a factory that will supply instances of {@link FileOutputStream}
117       * that write to a file.
118       *
119       * @param file the file to write to
120       * @return the factory
121       */
122      public static OutputSupplier<FileOutputStream> newOutputStreamSupplier(
123          File file) {
124        return newOutputStreamSupplier(file, false);
125      }
126    
127      /**
128       * Returns a factory that will supply instances of {@link FileOutputStream}
129       * that write to or append to a file.
130       *
131       * @param file the file to write to
132       * @param append if true, the encoded characters will be appended to the file;
133       *     otherwise the file is overwritten
134       * @return the factory
135       */
136      public static OutputSupplier<FileOutputStream> newOutputStreamSupplier(
137          final File file, final boolean append) {
138        Preconditions.checkNotNull(file);
139        return new OutputSupplier<FileOutputStream>() {
140          @Override
141          public FileOutputStream getOutput() throws IOException {
142            return new FileOutputStream(file, append);
143          }
144        };
145      }
146    
147      /**
148       * Returns a factory that will supply instances of
149       * {@link InputStreamReader} that read a file using the given character set.
150       *
151       * @param file the file to read from
152       * @param charset the charset used to decode the input stream; see {@link
153       *     Charsets} for helpful predefined constants
154       * @return the factory
155       */
156      public static InputSupplier<InputStreamReader> newReaderSupplier(File file,
157          Charset charset) {
158        return CharStreams.newReaderSupplier(newInputStreamSupplier(file), charset);
159      }
160    
161      /**
162       * Returns a factory that will supply instances of {@link OutputStreamWriter}
163       * that write to a file using the given character set.
164       *
165       * @param file the file to write to
166       * @param charset the charset used to encode the output stream; see {@link
167       *     Charsets} for helpful predefined constants
168       * @return the factory
169       */
170      public static OutputSupplier<OutputStreamWriter> newWriterSupplier(File file,
171          Charset charset) {
172        return newWriterSupplier(file, charset, false);
173      }
174    
175      /**
176       * Returns a factory that will supply instances of {@link OutputStreamWriter}
177       * that write to or append to a file using the given character set.
178       *
179       * @param file the file to write to
180       * @param charset the charset used to encode the output stream; see {@link
181       *     Charsets} for helpful predefined constants
182       * @param append if true, the encoded characters will be appended to the file;
183       *     otherwise the file is overwritten
184       * @return the factory
185       */
186      public static OutputSupplier<OutputStreamWriter> newWriterSupplier(File file,
187          Charset charset, boolean append) {
188        return CharStreams.newWriterSupplier(newOutputStreamSupplier(file, append),
189            charset);
190      }
191    
192      /**
193       * Reads all bytes from a file into a byte array.
194       *
195       * @param file the file to read from
196       * @return a byte array containing all the bytes from file
197       * @throws IllegalArgumentException if the file is bigger than the largest
198       *     possible byte array (2^31 - 1)
199       * @throws IOException if an I/O error occurs
200       */
201      public static byte[] toByteArray(File file) throws IOException {
202        Preconditions.checkArgument(file.length() <= Integer.MAX_VALUE);
203        if (file.length() == 0) {
204          // Some special files are length 0 but have content nonetheless.
205          return ByteStreams.toByteArray(newInputStreamSupplier(file));
206        } else {
207          // Avoid an extra allocation and copy.
208          byte[] b = new byte[(int) file.length()];
209          boolean threw = true;
210          InputStream in = new FileInputStream(file);
211          try {
212            ByteStreams.readFully(in, b);
213            threw = false;
214          } finally {
215            Closeables.close(in, threw);
216          }
217          return b;
218        }
219      }
220    
221      /**
222       * Reads all characters from a file into a {@link String}, using the given
223       * character set.
224       *
225       * @param file the file to read from
226       * @param charset the charset used to decode the input stream; see {@link
227       *     Charsets} for helpful predefined constants
228       * @return a string containing all the characters from the file
229       * @throws IOException if an I/O error occurs
230       */
231      public static String toString(File file, Charset charset) throws IOException {
232        return new String(toByteArray(file), charset.name());
233      }
234    
235      /**
236       * Copies to a file all bytes from an {@link InputStream} supplied by a
237       * factory.
238       *
239       * @param from the input factory
240       * @param to the destination file
241       * @throws IOException if an I/O error occurs
242       */
243      public static void copy(InputSupplier<? extends InputStream> from, File to)
244          throws IOException {
245        ByteStreams.copy(from, newOutputStreamSupplier(to));
246      }
247    
248      /**
249       * Overwrites a file with the contents of a byte array.
250       *
251       * @param from the bytes to write
252       * @param to the destination file
253       * @throws IOException if an I/O error occurs
254       */
255      public static void write(byte[] from, File to) throws IOException {
256        ByteStreams.write(from, newOutputStreamSupplier(to));
257      }
258    
259      /**
260       * Copies all bytes from a file to an {@link OutputStream} supplied by
261       * a factory.
262       *
263       * @param from the source file
264       * @param to the output factory
265       * @throws IOException if an I/O error occurs
266       */
267      public static void copy(File from, OutputSupplier<? extends OutputStream> to)
268          throws IOException {
269        ByteStreams.copy(newInputStreamSupplier(from), to);
270      }
271    
272      /**
273       * Copies all bytes from a file to an output stream.
274       *
275       * @param from the source file
276       * @param to the output stream
277       * @throws IOException if an I/O error occurs
278       */
279      public static void copy(File from, OutputStream to) throws IOException {
280        ByteStreams.copy(newInputStreamSupplier(from), to);
281      }
282    
283      /**
284       * Copies all the bytes from one file to another.
285       *.
286       * @param from the source file
287       * @param to the destination file
288       * @throws IOException if an I/O error occurs
289       * @throws IllegalArgumentException if {@code from.equals(to)}
290       */
291      public static void copy(File from, File to) throws IOException {
292        Preconditions.checkArgument(!from.equals(to),
293            "Source %s and destination %s must be different", from, to);
294        copy(newInputStreamSupplier(from), to);
295      }
296    
297      /**
298       * Copies to a file all characters from a {@link Readable} and
299       * {@link Closeable} object supplied by a factory, using the given
300       * character set.
301       *
302       * @param from the readable supplier
303       * @param to the destination file
304       * @param charset the charset used to encode the output stream; see {@link
305       *     Charsets} for helpful predefined constants
306       * @throws IOException if an I/O error occurs
307       */
308      public static <R extends Readable & Closeable> void copy(
309          InputSupplier<R> from, File to, Charset charset) throws IOException {
310        CharStreams.copy(from, newWriterSupplier(to, charset));
311      }
312    
313      /**
314       * Writes a character sequence (such as a string) to a file using the given
315       * character set.
316       *
317       * @param from the character sequence to write
318       * @param to the destination file
319       * @param charset the charset used to encode the output stream; see {@link
320       *     Charsets} for helpful predefined constants
321       * @throws IOException if an I/O error occurs
322       */
323      public static void write(CharSequence from, File to, Charset charset)
324          throws IOException {
325        write(from, to, charset, false);
326      }
327    
328      /**
329       * Appends a character sequence (such as a string) to a file using the given
330       * character set.
331       *
332       * @param from the character sequence to append
333       * @param to the destination file
334       * @param charset the charset used to encode the output stream; see {@link
335       *     Charsets} for helpful predefined constants
336       * @throws IOException if an I/O error occurs
337       */
338      public static void append(CharSequence from, File to, Charset charset)
339          throws IOException {
340        write(from, to, charset, true);
341      }
342    
343      /**
344       * Private helper method. Writes a character sequence to a file,
345       * optionally appending.
346       *
347       * @param from the character sequence to append
348       * @param to the destination file
349       * @param charset the charset used to encode the output stream; see {@link
350       *     Charsets} for helpful predefined constants
351       * @param append true to append, false to overwrite
352       * @throws IOException if an I/O error occurs
353       */
354      private static void write(CharSequence from, File to, Charset charset,
355          boolean append) throws IOException {
356        CharStreams.write(from, newWriterSupplier(to, charset, append));
357      }
358    
359      /**
360       * Copies all characters from a file to a {@link Appendable} &
361       * {@link Closeable} object supplied by a factory, using the given
362       * character set.
363       *
364       * @param from the source file
365       * @param charset the charset used to decode the input stream; see {@link
366       *     Charsets} for helpful predefined constants
367       * @param to the appendable supplier
368       * @throws IOException if an I/O error occurs
369       */
370      public static <W extends Appendable & Closeable> void copy(File from,
371          Charset charset, OutputSupplier<W> to) throws IOException {
372        CharStreams.copy(newReaderSupplier(from, charset), to);
373      }
374    
375      /**
376       * Copies all characters from a file to an appendable object,
377       * using the given character set.
378       *
379       * @param from the source file
380       * @param charset the charset used to decode the input stream; see {@link
381       *     Charsets} for helpful predefined constants
382       * @param to the appendable object
383       * @throws IOException if an I/O error occurs
384       */
385      public static void copy(File from, Charset charset, Appendable to)
386          throws IOException {
387        CharStreams.copy(newReaderSupplier(from, charset), to);
388      }
389    
390      /**
391       * Returns true if the files contains the same bytes.
392       *
393       * @throws IOException if an I/O error occurs
394       */
395      public static boolean equal(File file1, File file2) throws IOException {
396        if (file1 == file2 || file1.equals(file2)) {
397          return true;
398        }
399    
400        /*
401         * Some operating systems may return zero as the length for files
402         * denoting system-dependent entities such as devices or pipes, in
403         * which case we must fall back on comparing the bytes directly.
404         */
405        long len1 = file1.length();
406        long len2 = file2.length();
407        if (len1 != 0 && len2 != 0 && len1 != len2) {
408          return false;
409        }
410        return ByteStreams.equal(newInputStreamSupplier(file1),
411            newInputStreamSupplier(file2));
412      }
413    
414      /**
415       * Atomically creates a new directory somewhere beneath the system's
416       * temporary directory (as defined by the {@code java.io.tmpdir} system
417       * property), and returns its name.
418       *
419       * <p>Use this method instead of {@link File#createTempFile(String, String)}
420       * when you wish to create a directory, not a regular file.  A common pitfall
421       * is to call {@code createTempFile}, delete the file and create a
422       * directory in its place, but this leads a race condition which can be
423       * exploited to create security vulnerabilities, especially when executable
424       * files are to be written into the directory.
425       *
426       * <p>This method assumes that the temporary volume is writable, has free
427       * inodes and free blocks, and that it will not be called thousands of times
428       * per second.
429       *
430       * @return the newly-created directory
431       * @throws IllegalStateException if the directory could not be created
432       */
433      public static File createTempDir() {
434        File baseDir = new File(System.getProperty("java.io.tmpdir"));
435        String baseName = System.currentTimeMillis() + "-";
436    
437        for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) {
438          File tempDir = new File(baseDir, baseName + counter);
439          if (tempDir.mkdir()) {
440            return tempDir;
441          }
442        }
443        throw new IllegalStateException("Failed to create directory within "
444            + TEMP_DIR_ATTEMPTS + " attempts (tried "
445            + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')');
446      }
447    
448      /**
449       * Creates an empty file or updates the last updated timestamp on the
450       * same as the unix command of the same name.
451       *
452       * @param file the file to create or update
453       * @throws IOException if an I/O error occurs
454       */
455      public static void touch(File file) throws IOException {
456        if (!file.createNewFile()
457            && !file.setLastModified(System.currentTimeMillis())) {
458          throw new IOException("Unable to update modification time of " + file);
459        }
460      }
461    
462      /**
463       * Creates any necessary but nonexistent parent directories of the specified
464       * file. Note that if this operation fails it may have succeeded in creating
465       * some (but not all) of the necessary parent directories.
466       *
467       * @throws IOException if an I/O error occurs, or if any necessary but
468       *     nonexistent parent directories of the specified file could not be
469       *     created.
470       * @since 4.0
471       */
472      public static void createParentDirs(File file) throws IOException {
473        File parent = file.getCanonicalFile().getParentFile();
474        if (parent == null) {
475          /*
476           * The given directory is a filesystem root. All zero of its ancestors
477           * exist. This doesn't mean that the root itself exists -- consider x:\ on
478           * a Windows machine without such a drive -- or even that the caller can
479           * create it, but this method makes no such guarantees even for non-root
480           * files.
481           */
482          return;
483        }
484        parent.mkdirs();
485        if (!parent.isDirectory()) {
486          throw new IOException("Unable to create parent directories of " + file);
487        }
488      }
489    
490      /**
491       * Moves the file from one path to another. This method can rename a file or
492       * move it to a different directory, like the Unix {@code mv} command.
493       *
494       * @param from the source file
495       * @param to the destination file
496       * @throws IOException if an I/O error occurs
497       * @throws IllegalArgumentException if {@code from.equals(to)}
498       */
499      public static void move(File from, File to) throws IOException {
500        Preconditions.checkNotNull(to);
501        Preconditions.checkArgument(!from.equals(to),
502            "Source %s and destination %s must be different", from, to);
503    
504        if (!from.renameTo(to)) {
505          copy(from, to);
506          if (!from.delete()) {
507            if (!to.delete()) {
508              throw new IOException("Unable to delete " + to);
509            }
510            throw new IOException("Unable to delete " + from);
511          }
512        }
513      }
514    
515      /**
516       * Reads the first line from a file. The line does not include
517       * line-termination characters, but does include other leading and
518       * trailing whitespace.
519       *
520       * @param file the file to read from
521       * @param charset the charset used to decode the input stream; see {@link
522       *     Charsets} for helpful predefined constants
523       * @return the first line, or null if the file is empty
524       * @throws IOException if an I/O error occurs
525       */
526      public static String readFirstLine(File file, Charset charset)
527          throws IOException {
528        return CharStreams.readFirstLine(Files.newReaderSupplier(file, charset));
529      }
530    
531      /**
532       * Reads all of the lines from a file. The lines do not include
533       * line-termination characters, but do include other leading and
534       * trailing whitespace.
535       *
536       * @param file the file to read from
537       * @param charset the charset used to decode the input stream; see {@link
538       *     Charsets} for helpful predefined constants
539       * @return a mutable {@link List} containing all the lines
540       * @throws IOException if an I/O error occurs
541       */
542      public static List<String> readLines(File file, Charset charset)
543          throws IOException {
544        return CharStreams.readLines(Files.newReaderSupplier(file, charset));
545      }
546    
547      /**
548       * Streams lines from a {@link File}, stopping when our callback returns
549       * false, or we have read all of the lines.
550       *
551       * @param file the file to read from
552       * @param charset the charset used to decode the input stream; see {@link
553       *     Charsets} for helpful predefined constants
554       * @param callback the {@link LineProcessor} to use to handle the lines
555       * @return the output of processing the lines
556       * @throws IOException if an I/O error occurs
557       */
558      public static <T> T readLines(File file, Charset charset,
559          LineProcessor<T> callback) throws IOException {
560        return CharStreams.readLines(Files.newReaderSupplier(file, charset),
561            callback);
562      }
563    
564      /**
565       * Process the bytes of a file.
566       *
567       * <p>(If this seems too complicated, maybe you're looking for
568       * {@link #toByteArray}.)
569       *
570       * @param file the file to read
571       * @param processor the object to which the bytes of the file are passed.
572       * @return the result of the byte processor
573       * @throws IOException if an I/O error occurs
574       */
575      public static <T> T readBytes(File file, ByteProcessor<T> processor)
576          throws IOException {
577        return ByteStreams.readBytes(newInputStreamSupplier(file), processor);
578      }
579    
580      /**
581       * Computes and returns the checksum value for a file.
582       * The checksum object is reset when this method returns successfully.
583       *
584       * @param file the file to read
585       * @param checksum the checksum object
586       * @return the result of {@link Checksum#getValue} after updating the
587       *     checksum object with all of the bytes in the file
588       * @throws IOException if an I/O error occurs
589       */
590      public static long getChecksum(File file, Checksum checksum)
591          throws IOException {
592        return ByteStreams.getChecksum(newInputStreamSupplier(file), checksum);
593      }
594    
595      /**
596       * Computes and returns the digest value for a file.
597       * The digest object is reset when this method returns successfully.
598       *
599       * @param file the file to read
600       * @param md the digest object
601       * @return the result of {@link MessageDigest#digest()} after updating the
602       *     digest object with all of the bytes in this file
603       * @throws IOException if an I/O error occurs
604       * @deprecated Use {@link #hash} instead. For example,
605       *     {@code Files.hash(file, Hashing.sha1())}. This method is scheduled to
606       *     be removed in Guava release 13.0.
607       */
608      @Deprecated
609      public static byte[] getDigest(File file, MessageDigest md)
610          throws IOException {
611        return ByteStreams.getDigest(newInputStreamSupplier(file), md);
612      }
613    
614      /**
615       * Computes the hash code of the {@code file} using {@code hashFunction}.
616       *
617       * @param file the file to read
618       * @param hashFunction the hash function to use to hash the data
619       * @return the {@link HashCode} of all of the bytes in the file
620       * @throws IOException if an I/O error occurs
621       * @since 12.0
622       */
623      public static HashCode hash(File file, HashFunction hashFunction)
624          throws IOException {
625        return ByteStreams.hash(newInputStreamSupplier(file), hashFunction);
626      }
627    
628      /**
629       * Fully maps a file read-only in to memory as per
630       * {@link FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)}.
631       *
632       * <p>Files are mapped from offset 0 to its length.
633       *
634       * <p>This only works for files <= {@link Integer#MAX_VALUE} bytes.
635       *
636       * @param file the file to map
637       * @return a read-only buffer reflecting {@code file}
638       * @throws FileNotFoundException if the {@code file} does not exist
639       * @throws IOException if an I/O error occurs
640       *
641       * @see FileChannel#map(MapMode, long, long)
642       * @since 2.0
643       */
644      public static MappedByteBuffer map(File file) throws IOException {
645        return map(file, MapMode.READ_ONLY);
646      }
647    
648      /**
649       * Fully maps a file in to memory as per
650       * {@link FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)}
651       * using the requested {@link MapMode}.
652       *
653       * <p>Files are mapped from offset 0 to its length.
654       *
655       * <p>This only works for files <= {@link Integer#MAX_VALUE} bytes.
656       *
657       * @param file the file to map
658       * @param mode the mode to use when mapping {@code file}
659       * @return a buffer reflecting {@code file}
660       * @throws FileNotFoundException if the {@code file} does not exist
661       * @throws IOException if an I/O error occurs
662       *
663       * @see FileChannel#map(MapMode, long, long)
664       * @since 2.0
665       */
666      public static MappedByteBuffer map(File file, MapMode mode)
667          throws IOException {
668        if (!file.exists()) {
669          throw new FileNotFoundException(file.toString());
670        }
671        return map(file, mode, file.length());
672      }
673    
674      /**
675       * Maps a file in to memory as per
676       * {@link FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)}
677       * using the requested {@link MapMode}.
678       *
679       * <p>Files are mapped from offset 0 to {@code size}.
680       *
681       * <p>If the mode is {@link MapMode#READ_WRITE} and the file does not exist,
682       * it will be created with the requested {@code size}. Thus this method is
683       * useful for creating memory mapped files which do not yet exist.
684       *
685       * <p>This only works for files <= {@link Integer#MAX_VALUE} bytes.
686       *
687       * @param file the file to map
688       * @param mode the mode to use when mapping {@code file}
689       * @return a buffer reflecting {@code file}
690       * @throws IOException if an I/O error occurs
691       *
692       * @see FileChannel#map(MapMode, long, long)
693       * @since 2.0
694       */
695      public static MappedByteBuffer map(File file, MapMode mode, long size)
696          throws FileNotFoundException, IOException {
697        RandomAccessFile raf =
698            new RandomAccessFile(file, mode == MapMode.READ_ONLY ? "r" : "rw");
699    
700        boolean threw = true;
701        try {
702          MappedByteBuffer mbb = map(raf, mode, size);
703          threw = false;
704          return mbb;
705        } finally {
706          Closeables.close(raf, threw);
707        }
708      }
709    
710      private static MappedByteBuffer map(RandomAccessFile raf, MapMode mode,
711          long size) throws IOException {
712        FileChannel channel = raf.getChannel();
713    
714        boolean threw = true;
715        try {
716          MappedByteBuffer mbb = channel.map(mode, 0, size);
717          threw = false;
718          return mbb;
719        } finally {
720          Closeables.close(channel, threw);
721        }
722      }
723    
724      /**
725       * Returns the lexically cleaned form of the path name, <i>usually</i> (but
726       * not always) equivalent to the original. The following heuristics are used:
727       *
728       * <ul>
729       * <li>empty string becomes .
730       * <li>. stays as .
731       * <li>fold out ./
732       * <li>fold out ../ when possible
733       * <li>collapse multiple slashes
734       * <li>delete trailing slashes (unless the path is just "/")
735       * </ul>
736       *
737       * These heuristics do not always match the behavior of the filesystem. In
738       * particular, consider the path {@code a/../b}, which {@code simplifyPath}
739       * will change to {@code b}. If {@code a} is a symlink to {@code x}, {@code
740       * a/../b} may refer to a sibling of {@code x}, rather than the sibling of
741       * {@code a} referred to by {@code b}.
742       *
743       * @since 11.0
744       */
745      public static String simplifyPath(String pathname) {
746        if (pathname.length() == 0) {
747          return ".";
748        }
749    
750        // split the path apart
751        Iterable<String> components =
752            Splitter.on('/').omitEmptyStrings().split(pathname);
753        List<String> path = new ArrayList<String>();
754    
755        // resolve ., .., and //
756        for (String component : components) {
757          if (component.equals(".")) {
758            continue;
759          } else if (component.equals("..")) {
760            if (path.size() > 0 && !path.get(path.size() - 1).equals("..")) {
761              path.remove(path.size() - 1);
762            } else {
763              path.add("..");
764            }
765          } else {
766            path.add(component);
767          }
768        }
769    
770        // put it back together
771        String result = Joiner.on('/').join(path);
772        if (pathname.charAt(0) == '/') {
773          result = "/" + result;
774        }
775    
776        while (result.startsWith("/../")) {
777          result = result.substring(3);
778        }
779        if (result.equals("/..")) {
780          result = "/";
781        } else if ("".equals(result)) {
782          result = ".";
783        }
784    
785        return result;
786      }
787    
788      /**
789       * Returns the <a href="http://en.wikipedia.org/wiki/Filename_extension">file
790       * extension</a> for the given file name, or the empty string if the file has
791       * no extension.  The result does not include the '{@code .}'.
792       *
793       * @since 11.0
794       */
795      public static String getFileExtension(String fileName) {
796        checkNotNull(fileName);
797        int dotIndex = fileName.lastIndexOf('.');
798        return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1);
799      }
800    }