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