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