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