1 /* 2 * Copyright (C) 2007 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.io; 18 19 import static com.google.common.base.Preconditions.checkArgument; 20 import static com.google.common.base.Preconditions.checkNotNull; 21 import static com.google.common.io.FileWriteMode.APPEND; 22 23 import com.google.common.annotations.Beta; 24 import com.google.common.base.Charsets; 25 import com.google.common.base.Joiner; 26 import com.google.common.base.Predicate; 27 import com.google.common.base.Splitter; 28 import com.google.common.collect.ImmutableSet; 29 import com.google.common.collect.Lists; 30 import com.google.common.collect.TreeTraverser; 31 import com.google.common.hash.HashCode; 32 import com.google.common.hash.HashFunction; 33 34 import java.io.BufferedReader; 35 import java.io.BufferedWriter; 36 import java.io.File; 37 import java.io.FileInputStream; 38 import java.io.FileNotFoundException; 39 import java.io.FileOutputStream; 40 import java.io.IOException; 41 import java.io.InputStream; 42 import java.io.InputStreamReader; 43 import java.io.OutputStream; 44 import java.io.OutputStreamWriter; 45 import java.io.RandomAccessFile; 46 import java.nio.MappedByteBuffer; 47 import java.nio.channels.FileChannel; 48 import java.nio.channels.FileChannel.MapMode; 49 import java.nio.charset.Charset; 50 import java.util.ArrayList; 51 import java.util.Arrays; 52 import java.util.Collections; 53 import java.util.List; 54 55 /** 56 * Provides utility methods for working with files. 57 * 58 * <p>All method parameters must be non-null unless documented otherwise. 59 * 60 * @author Chris Nokleberg 61 * @author Colin Decker 62 * @since 1.0 63 */ 64 @Beta 65 public final class Files { 66 67 /** Maximum loop count when creating temp directories. */ 68 private static final int TEMP_DIR_ATTEMPTS = 10000; 69 Files()70 private Files() {} 71 72 /** 73 * Returns a buffered reader that reads from a file using the given 74 * character set. 75 * 76 * @param file the file to read from 77 * @param charset the charset used to decode the input stream; see {@link 78 * Charsets} for helpful predefined constants 79 * @return the buffered reader 80 */ newReader(File file, Charset charset)81 public static BufferedReader newReader(File file, Charset charset) 82 throws FileNotFoundException { 83 checkNotNull(file); 84 checkNotNull(charset); 85 return new BufferedReader( 86 new InputStreamReader(new FileInputStream(file), charset)); 87 } 88 89 /** 90 * Returns a buffered writer that writes to a file using the given 91 * character set. 92 * 93 * @param file the file to write to 94 * @param charset the charset used to encode the output stream; see {@link 95 * Charsets} for helpful predefined constants 96 * @return the buffered writer 97 */ newWriter(File file, Charset charset)98 public static BufferedWriter newWriter(File file, Charset charset) 99 throws FileNotFoundException { 100 checkNotNull(file); 101 checkNotNull(charset); 102 return new BufferedWriter( 103 new OutputStreamWriter(new FileOutputStream(file), charset)); 104 } 105 106 /** 107 * Returns a new {@link ByteSource} for reading bytes from the given file. 108 * 109 * @since 14.0 110 */ asByteSource(File file)111 public static ByteSource asByteSource(File file) { 112 return new FileByteSource(file); 113 } 114 115 private static final class FileByteSource extends ByteSource { 116 117 private final File file; 118 FileByteSource(File file)119 private FileByteSource(File file) { 120 this.file = checkNotNull(file); 121 } 122 123 @Override openStream()124 public FileInputStream openStream() throws IOException { 125 return new FileInputStream(file); 126 } 127 128 @Override size()129 public long size() throws IOException { 130 if (!file.isFile()) { 131 throw new FileNotFoundException(file.toString()); 132 } 133 return file.length(); 134 } 135 136 @Override read()137 public byte[] read() throws IOException { 138 Closer closer = Closer.create(); 139 try { 140 FileInputStream in = closer.register(openStream()); 141 return readFile(in, in.getChannel().size()); 142 } catch (Throwable e) { 143 throw closer.rethrow(e); 144 } finally { 145 closer.close(); 146 } 147 } 148 149 @Override toString()150 public String toString() { 151 return "Files.asByteSource(" + file + ")"; 152 } 153 } 154 155 /** 156 * Reads a file of the given expected size from the given input stream, if 157 * it will fit into a byte array. This method handles the case where the file 158 * size changes between when the size is read and when the contents are read 159 * from the stream. 160 */ readFile( InputStream in, long expectedSize)161 static byte[] readFile( 162 InputStream in, long expectedSize) throws IOException { 163 if (expectedSize > Integer.MAX_VALUE) { 164 throw new OutOfMemoryError("file is too large to fit in a byte array: " 165 + expectedSize + " bytes"); 166 } 167 168 // some special files may return size 0 but have content, so read 169 // the file normally in that case 170 return expectedSize == 0 171 ? ByteStreams.toByteArray(in) 172 : ByteStreams.toByteArray(in, (int) expectedSize); 173 } 174 175 /** 176 * Returns a new {@link ByteSink} for writing bytes to the given file. The 177 * given {@code modes} control how the file is opened for writing. When no 178 * mode is provided, the file will be truncated before writing. When the 179 * {@link FileWriteMode#APPEND APPEND} mode is provided, writes will 180 * append to the end of the file without truncating it. 181 * 182 * @since 14.0 183 */ asByteSink(File file, FileWriteMode... modes)184 public static ByteSink asByteSink(File file, FileWriteMode... modes) { 185 return new FileByteSink(file, modes); 186 } 187 188 private static final class FileByteSink extends ByteSink { 189 190 private final File file; 191 private final ImmutableSet<FileWriteMode> modes; 192 FileByteSink(File file, FileWriteMode... modes)193 private FileByteSink(File file, FileWriteMode... modes) { 194 this.file = checkNotNull(file); 195 this.modes = ImmutableSet.copyOf(modes); 196 } 197 198 @Override openStream()199 public FileOutputStream openStream() throws IOException { 200 return new FileOutputStream(file, modes.contains(APPEND)); 201 } 202 203 @Override toString()204 public String toString() { 205 return "Files.asByteSink(" + file + ", " + modes + ")"; 206 } 207 } 208 209 /** 210 * Returns a new {@link CharSource} for reading character data from the given 211 * file using the given character set. 212 * 213 * @since 14.0 214 */ asCharSource(File file, Charset charset)215 public static CharSource asCharSource(File file, Charset charset) { 216 return asByteSource(file).asCharSource(charset); 217 } 218 219 /** 220 * Returns a new {@link CharSink} for writing character data to the given 221 * file using the given character set. The given {@code modes} control how 222 * the file is opened for writing. When no mode is provided, the file 223 * will be truncated before writing. When the 224 * {@link FileWriteMode#APPEND APPEND} mode is provided, writes will 225 * append to the end of the file without truncating it. 226 * 227 * @since 14.0 228 */ asCharSink(File file, Charset charset, FileWriteMode... modes)229 public static CharSink asCharSink(File file, Charset charset, 230 FileWriteMode... modes) { 231 return asByteSink(file, modes).asCharSink(charset); 232 } 233 modes(boolean append)234 private static FileWriteMode[] modes(boolean append) { 235 return append 236 ? new FileWriteMode[]{ FileWriteMode.APPEND } 237 : new FileWriteMode[0]; 238 } 239 240 /** 241 * Reads all bytes from a file into a byte array. 242 * 243 * @param file the file to read from 244 * @return a byte array containing all the bytes from file 245 * @throws IllegalArgumentException if the file is bigger than the largest 246 * possible byte array (2^31 - 1) 247 * @throws IOException if an I/O error occurs 248 */ toByteArray(File file)249 public static byte[] toByteArray(File file) throws IOException { 250 return asByteSource(file).read(); 251 } 252 253 /** 254 * Reads all characters from a file into a {@link String}, using the given 255 * character set. 256 * 257 * @param file the file to read from 258 * @param charset the charset used to decode the input stream; see {@link 259 * Charsets} for helpful predefined constants 260 * @return a string containing all the characters from the file 261 * @throws IOException if an I/O error occurs 262 */ toString(File file, Charset charset)263 public static String toString(File file, Charset charset) throws IOException { 264 return asCharSource(file, charset).read(); 265 } 266 267 /** 268 * Overwrites a file with the contents of a byte array. 269 * 270 * @param from the bytes to write 271 * @param to the destination file 272 * @throws IOException if an I/O error occurs 273 */ write(byte[] from, File to)274 public static void write(byte[] from, File to) throws IOException { 275 asByteSink(to).write(from); 276 } 277 278 /** 279 * Copies all bytes from a file to an output stream. 280 * 281 * @param from the source file 282 * @param to the output stream 283 * @throws IOException if an I/O error occurs 284 */ copy(File from, OutputStream to)285 public static void copy(File from, OutputStream to) throws IOException { 286 asByteSource(from).copyTo(to); 287 } 288 289 /** 290 * Copies all the bytes from one file to another. 291 * 292 * <p><b>Warning:</b> If {@code to} represents an existing file, that file 293 * will be overwritten with the contents of {@code from}. If {@code to} and 294 * {@code from} refer to the <i>same</i> file, the contents of that file 295 * will be deleted. 296 * 297 * @param from the source file 298 * @param to the destination file 299 * @throws IOException if an I/O error occurs 300 * @throws IllegalArgumentException if {@code from.equals(to)} 301 */ copy(File from, File to)302 public static void copy(File from, File to) throws IOException { 303 checkArgument(!from.equals(to), 304 "Source %s and destination %s must be different", from, to); 305 asByteSource(from).copyTo(asByteSink(to)); 306 } 307 308 /** 309 * Writes a character sequence (such as a string) to a file using the given 310 * character set. 311 * 312 * @param from the character sequence to write 313 * @param to the destination file 314 * @param charset the charset used to encode the output stream; see {@link 315 * Charsets} for helpful predefined constants 316 * @throws IOException if an I/O error occurs 317 */ write(CharSequence from, File to, Charset charset)318 public static void write(CharSequence from, File to, Charset charset) 319 throws IOException { 320 asCharSink(to, charset).write(from); 321 } 322 323 /** 324 * Appends a character sequence (such as a string) to a file using the given 325 * character set. 326 * 327 * @param from the character sequence to append 328 * @param to the destination file 329 * @param charset the charset used to encode the output stream; see {@link 330 * Charsets} for helpful predefined constants 331 * @throws IOException if an I/O error occurs 332 */ append(CharSequence from, File to, Charset charset)333 public static void append(CharSequence from, File to, Charset charset) 334 throws IOException { 335 write(from, to, charset, true); 336 } 337 338 /** 339 * Private helper method. Writes a character sequence to a file, 340 * optionally appending. 341 * 342 * @param from the character sequence to append 343 * @param to the destination file 344 * @param charset the charset used to encode the output stream; see {@link 345 * Charsets} for helpful predefined constants 346 * @param append true to append, false to overwrite 347 * @throws IOException if an I/O error occurs 348 */ write(CharSequence from, File to, Charset charset, boolean append)349 private static void write(CharSequence from, File to, Charset charset, 350 boolean append) throws IOException { 351 asCharSink(to, charset, modes(append)).write(from); 352 } 353 354 /** 355 * Copies all characters from a file to an appendable object, 356 * using the given character set. 357 * 358 * @param from the source file 359 * @param charset the charset used to decode the input stream; see {@link 360 * Charsets} for helpful predefined constants 361 * @param to the appendable object 362 * @throws IOException if an I/O error occurs 363 */ copy(File from, Charset charset, Appendable to)364 public static void copy(File from, Charset charset, Appendable to) 365 throws IOException { 366 asCharSource(from, charset).copyTo(to); 367 } 368 369 /** 370 * Returns true if the files contains the same bytes. 371 * 372 * @throws IOException if an I/O error occurs 373 */ equal(File file1, File file2)374 public static boolean equal(File file1, File file2) throws IOException { 375 checkNotNull(file1); 376 checkNotNull(file2); 377 if (file1 == file2 || file1.equals(file2)) { 378 return true; 379 } 380 381 /* 382 * Some operating systems may return zero as the length for files 383 * denoting system-dependent entities such as devices or pipes, in 384 * which case we must fall back on comparing the bytes directly. 385 */ 386 long len1 = file1.length(); 387 long len2 = file2.length(); 388 if (len1 != 0 && len2 != 0 && len1 != len2) { 389 return false; 390 } 391 return asByteSource(file1).contentEquals(asByteSource(file2)); 392 } 393 394 /** 395 * Atomically creates a new directory somewhere beneath the system's 396 * temporary directory (as defined by the {@code java.io.tmpdir} system 397 * property), and returns its name. 398 * 399 * <p>Use this method instead of {@link File#createTempFile(String, String)} 400 * when you wish to create a directory, not a regular file. A common pitfall 401 * is to call {@code createTempFile}, delete the file and create a 402 * directory in its place, but this leads a race condition which can be 403 * exploited to create security vulnerabilities, especially when executable 404 * files are to be written into the directory. 405 * 406 * <p>This method assumes that the temporary volume is writable, has free 407 * inodes and free blocks, and that it will not be called thousands of times 408 * per second. 409 * 410 * @return the newly-created directory 411 * @throws IllegalStateException if the directory could not be created 412 */ createTempDir()413 public static File createTempDir() { 414 File baseDir = new File(System.getProperty("java.io.tmpdir")); 415 String baseName = System.currentTimeMillis() + "-"; 416 417 for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { 418 File tempDir = new File(baseDir, baseName + counter); 419 if (tempDir.mkdir()) { 420 return tempDir; 421 } 422 } 423 throw new IllegalStateException("Failed to create directory within " 424 + TEMP_DIR_ATTEMPTS + " attempts (tried " 425 + baseName + "0 to " + baseName + (TEMP_DIR_ATTEMPTS - 1) + ')'); 426 } 427 428 /** 429 * Creates an empty file or updates the last updated timestamp on the 430 * same as the unix command of the same name. 431 * 432 * @param file the file to create or update 433 * @throws IOException if an I/O error occurs 434 */ touch(File file)435 public static void touch(File file) throws IOException { 436 checkNotNull(file); 437 if (!file.createNewFile() 438 && !file.setLastModified(System.currentTimeMillis())) { 439 throw new IOException("Unable to update modification time of " + file); 440 } 441 } 442 443 /** 444 * Creates any necessary but nonexistent parent directories of the specified 445 * file. Note that if this operation fails it may have succeeded in creating 446 * some (but not all) of the necessary parent directories. 447 * 448 * @throws IOException if an I/O error occurs, or if any necessary but 449 * nonexistent parent directories of the specified file could not be 450 * created. 451 * @since 4.0 452 */ createParentDirs(File file)453 public static void createParentDirs(File file) throws IOException { 454 checkNotNull(file); 455 File parent = file.getCanonicalFile().getParentFile(); 456 if (parent == null) { 457 /* 458 * The given directory is a filesystem root. All zero of its ancestors 459 * exist. This doesn't mean that the root itself exists -- consider x:\ on 460 * a Windows machine without such a drive -- or even that the caller can 461 * create it, but this method makes no such guarantees even for non-root 462 * files. 463 */ 464 return; 465 } 466 parent.mkdirs(); 467 if (!parent.isDirectory()) { 468 throw new IOException("Unable to create parent directories of " + file); 469 } 470 } 471 472 /** 473 * Moves a file from one path to another. This method can rename a file 474 * and/or move it to a different directory. In either case {@code to} must 475 * be the target path for the file itself; not just the new name for the 476 * file or the path to the new parent directory. 477 * 478 * @param from the source file 479 * @param to the destination file 480 * @throws IOException if an I/O error occurs 481 * @throws IllegalArgumentException if {@code from.equals(to)} 482 */ move(File from, File to)483 public static void move(File from, File to) throws IOException { 484 checkNotNull(from); 485 checkNotNull(to); 486 checkArgument(!from.equals(to), 487 "Source %s and destination %s must be different", from, to); 488 489 if (!from.renameTo(to)) { 490 copy(from, to); 491 if (!from.delete()) { 492 if (!to.delete()) { 493 throw new IOException("Unable to delete " + to); 494 } 495 throw new IOException("Unable to delete " + from); 496 } 497 } 498 } 499 500 /** 501 * Reads the first line from a file. The line does not include 502 * line-termination characters, but does include other leading and 503 * trailing whitespace. 504 * 505 * @param file the file to read from 506 * @param charset the charset used to decode the input stream; see {@link 507 * Charsets} for helpful predefined constants 508 * @return the first line, or null if the file is empty 509 * @throws IOException if an I/O error occurs 510 */ readFirstLine(File file, Charset charset)511 public static String readFirstLine(File file, Charset charset) 512 throws IOException { 513 return asCharSource(file, charset).readFirstLine(); 514 } 515 516 /** 517 * Reads all of the lines from a file. The lines do not include 518 * line-termination characters, but do include other leading and 519 * trailing whitespace. 520 * 521 * <p>This method returns a mutable {@code List}. For an 522 * {@code ImmutableList}, use 523 * {@code Files.asCharSource(file, charset).readLines()}. 524 * 525 * @param file the file to read from 526 * @param charset the charset used to decode the input stream; see {@link 527 * Charsets} for helpful predefined constants 528 * @return a mutable {@link List} containing all the lines 529 * @throws IOException if an I/O error occurs 530 */ readLines(File file, Charset charset)531 public static List<String> readLines(File file, Charset charset) 532 throws IOException { 533 // don't use asCharSource(file, charset).readLines() because that returns 534 // an immutable list, which would change the behavior of this method 535 return readLines(file, charset, new LineProcessor<List<String>>() { 536 final List<String> result = Lists.newArrayList(); 537 538 @Override 539 public boolean processLine(String line) { 540 result.add(line); 541 return true; 542 } 543 544 @Override 545 public List<String> getResult() { 546 return result; 547 } 548 }); 549 } 550 551 /** 552 * Streams lines from a {@link File}, stopping when our callback returns 553 * false, or we have read all of the lines. 554 * 555 * @param file the file to read from 556 * @param charset the charset used to decode the input stream; see {@link 557 * Charsets} for helpful predefined constants 558 * @param callback the {@link LineProcessor} to use to handle the lines 559 * @return the output of processing the lines 560 * @throws IOException if an I/O error occurs 561 */ readLines(File file, Charset charset, LineProcessor<T> callback)562 public static <T> T readLines(File file, Charset charset, 563 LineProcessor<T> callback) throws IOException { 564 return asCharSource(file, charset).readLines(callback); 565 } 566 567 /** 568 * Process the bytes of a file. 569 * 570 * <p>(If this seems too complicated, maybe you're looking for 571 * {@link #toByteArray}.) 572 * 573 * @param file the file to read 574 * @param processor the object to which the bytes of the file are passed. 575 * @return the result of the byte processor 576 * @throws IOException if an I/O error occurs 577 */ readBytes(File file, ByteProcessor<T> processor)578 public static <T> T readBytes(File file, ByteProcessor<T> processor) 579 throws IOException { 580 return asByteSource(file).read(processor); 581 } 582 583 /** 584 * Computes the hash code of the {@code file} using {@code hashFunction}. 585 * 586 * @param file the file to read 587 * @param hashFunction the hash function to use to hash the data 588 * @return the {@link HashCode} of all of the bytes in the file 589 * @throws IOException if an I/O error occurs 590 * @since 12.0 591 */ hash(File file, HashFunction hashFunction)592 public static HashCode hash(File file, HashFunction hashFunction) 593 throws IOException { 594 return asByteSource(file).hash(hashFunction); 595 } 596 597 /** 598 * Fully maps a file read-only in to memory as per 599 * {@link FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)}. 600 * 601 * <p>Files are mapped from offset 0 to its length. 602 * 603 * <p>This only works for files <= {@link Integer#MAX_VALUE} bytes. 604 * 605 * @param file the file to map 606 * @return a read-only buffer reflecting {@code file} 607 * @throws FileNotFoundException if the {@code file} does not exist 608 * @throws IOException if an I/O error occurs 609 * 610 * @see FileChannel#map(MapMode, long, long) 611 * @since 2.0 612 */ map(File file)613 public static MappedByteBuffer map(File file) throws IOException { 614 checkNotNull(file); 615 return map(file, MapMode.READ_ONLY); 616 } 617 618 /** 619 * Fully maps a file in to memory as per 620 * {@link FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)} 621 * using the requested {@link MapMode}. 622 * 623 * <p>Files are mapped from offset 0 to its length. 624 * 625 * <p>This only works for files <= {@link Integer#MAX_VALUE} bytes. 626 * 627 * @param file the file to map 628 * @param mode the mode to use when mapping {@code file} 629 * @return a buffer reflecting {@code file} 630 * @throws FileNotFoundException if the {@code file} does not exist 631 * @throws IOException if an I/O error occurs 632 * 633 * @see FileChannel#map(MapMode, long, long) 634 * @since 2.0 635 */ map(File file, MapMode mode)636 public static MappedByteBuffer map(File file, MapMode mode) 637 throws IOException { 638 checkNotNull(file); 639 checkNotNull(mode); 640 if (!file.exists()) { 641 throw new FileNotFoundException(file.toString()); 642 } 643 return map(file, mode, file.length()); 644 } 645 646 /** 647 * Maps a file in to memory as per 648 * {@link FileChannel#map(java.nio.channels.FileChannel.MapMode, long, long)} 649 * using the requested {@link MapMode}. 650 * 651 * <p>Files are mapped from offset 0 to {@code size}. 652 * 653 * <p>If the mode is {@link MapMode#READ_WRITE} and the file does not exist, 654 * it will be created with the requested {@code size}. Thus this method is 655 * useful for creating memory mapped files which do not yet exist. 656 * 657 * <p>This only works for files <= {@link Integer#MAX_VALUE} bytes. 658 * 659 * @param file the file to map 660 * @param mode the mode to use when mapping {@code file} 661 * @return a buffer reflecting {@code file} 662 * @throws IOException if an I/O error occurs 663 * 664 * @see FileChannel#map(MapMode, long, long) 665 * @since 2.0 666 */ map(File file, MapMode mode, long size)667 public static MappedByteBuffer map(File file, MapMode mode, long size) 668 throws FileNotFoundException, IOException { 669 checkNotNull(file); 670 checkNotNull(mode); 671 672 Closer closer = Closer.create(); 673 try { 674 RandomAccessFile raf = closer.register( 675 new RandomAccessFile(file, mode == MapMode.READ_ONLY ? "r" : "rw")); 676 return map(raf, mode, size); 677 } catch (Throwable e) { 678 throw closer.rethrow(e); 679 } finally { 680 closer.close(); 681 } 682 } 683 map(RandomAccessFile raf, MapMode mode, long size)684 private static MappedByteBuffer map(RandomAccessFile raf, MapMode mode, 685 long size) throws IOException { 686 Closer closer = Closer.create(); 687 try { 688 FileChannel channel = closer.register(raf.getChannel()); 689 return channel.map(mode, 0, size); 690 } catch (Throwable e) { 691 throw closer.rethrow(e); 692 } finally { 693 closer.close(); 694 } 695 } 696 697 /** 698 * Returns the lexically cleaned form of the path name, <i>usually</i> (but 699 * not always) equivalent to the original. The following heuristics are used: 700 * 701 * <ul> 702 * <li>empty string becomes . 703 * <li>. stays as . 704 * <li>fold out ./ 705 * <li>fold out ../ when possible 706 * <li>collapse multiple slashes 707 * <li>delete trailing slashes (unless the path is just "/") 708 * </ul> 709 * 710 * <p>These heuristics do not always match the behavior of the filesystem. In 711 * particular, consider the path {@code a/../b}, which {@code simplifyPath} 712 * will change to {@code b}. If {@code a} is a symlink to {@code x}, {@code 713 * a/../b} may refer to a sibling of {@code x}, rather than the sibling of 714 * {@code a} referred to by {@code b}. 715 * 716 * @since 11.0 717 */ simplifyPath(String pathname)718 public static String simplifyPath(String pathname) { 719 checkNotNull(pathname); 720 if (pathname.length() == 0) { 721 return "."; 722 } 723 724 // split the path apart 725 Iterable<String> components = 726 Splitter.on('/').omitEmptyStrings().split(pathname); 727 List<String> path = new ArrayList<String>(); 728 729 // resolve ., .., and // 730 for (String component : components) { 731 if (component.equals(".")) { 732 continue; 733 } else if (component.equals("..")) { 734 if (path.size() > 0 && !path.get(path.size() - 1).equals("..")) { 735 path.remove(path.size() - 1); 736 } else { 737 path.add(".."); 738 } 739 } else { 740 path.add(component); 741 } 742 } 743 744 // put it back together 745 String result = Joiner.on('/').join(path); 746 if (pathname.charAt(0) == '/') { 747 result = "/" + result; 748 } 749 750 while (result.startsWith("/../")) { 751 result = result.substring(3); 752 } 753 if (result.equals("/..")) { 754 result = "/"; 755 } else if ("".equals(result)) { 756 result = "."; 757 } 758 759 return result; 760 } 761 762 /** 763 * Returns the <a href="http://en.wikipedia.org/wiki/Filename_extension">file 764 * extension</a> for the given file name, or the empty string if the file has 765 * no extension. The result does not include the '{@code .}'. 766 * 767 * @since 11.0 768 */ getFileExtension(String fullName)769 public static String getFileExtension(String fullName) { 770 checkNotNull(fullName); 771 String fileName = new File(fullName).getName(); 772 int dotIndex = fileName.lastIndexOf('.'); 773 return (dotIndex == -1) ? "" : fileName.substring(dotIndex + 1); 774 } 775 776 /** 777 * Returns the file name without its 778 * <a href="http://en.wikipedia.org/wiki/Filename_extension">file extension</a> or path. This is 779 * similar to the {@code basename} unix command. The result does not include the '{@code .}'. 780 * 781 * @param file The name of the file to trim the extension from. This can be either a fully 782 * qualified file name (including a path) or just a file name. 783 * @return The file name without its path or extension. 784 * @since 14.0 785 */ getNameWithoutExtension(String file)786 public static String getNameWithoutExtension(String file) { 787 checkNotNull(file); 788 String fileName = new File(file).getName(); 789 int dotIndex = fileName.lastIndexOf('.'); 790 return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex); 791 } 792 793 /** 794 * Returns a {@link TreeTraverser} instance for {@link File} trees. 795 * 796 * <p><b>Warning:</b> {@code File} provides no support for symbolic links, and as such there is no 797 * way to ensure that a symbolic link to a directory is not followed when traversing the tree. 798 * In this case, iterables created by this traverser could contain files that are outside of the 799 * given directory or even be infinite if there is a symbolic link loop. 800 * 801 * @since 15.0 802 */ fileTreeTraverser()803 public static TreeTraverser<File> fileTreeTraverser() { 804 return FILE_TREE_TRAVERSER; 805 } 806 807 private static final TreeTraverser<File> FILE_TREE_TRAVERSER = new TreeTraverser<File>() { 808 @Override 809 public Iterable<File> children(File file) { 810 // check isDirectory() just because it may be faster than listFiles() on a non-directory 811 if (file.isDirectory()) { 812 File[] files = file.listFiles(); 813 if (files != null) { 814 return Collections.unmodifiableList(Arrays.asList(files)); 815 } 816 } 817 818 return Collections.emptyList(); 819 } 820 821 @Override 822 public String toString() { 823 return "Files.fileTreeTraverser()"; 824 } 825 }; 826 827 /** 828 * Returns a predicate that returns the result of {@link File#isDirectory} on input files. 829 * 830 * @since 15.0 831 */ isDirectory()832 public static Predicate<File> isDirectory() { 833 return FilePredicate.IS_DIRECTORY; 834 } 835 836 /** 837 * Returns a predicate that returns the result of {@link File#isFile} on input files. 838 * 839 * @since 15.0 840 */ isFile()841 public static Predicate<File> isFile() { 842 return FilePredicate.IS_FILE; 843 } 844 845 private enum FilePredicate implements Predicate<File> { 846 IS_DIRECTORY { 847 @Override apply(File file)848 public boolean apply(File file) { 849 return file.isDirectory(); 850 } 851 852 @Override toString()853 public String toString() { 854 return "Files.isDirectory()"; 855 } 856 }, 857 858 IS_FILE { 859 @Override apply(File file)860 public boolean apply(File file) { 861 return file.isFile(); 862 } 863 864 @Override toString()865 public String toString() { 866 return "Files.isFile()"; 867 } 868 }; 869 } 870 } 871