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