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