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