001 /*--------------------------------------------------------------------------+ 002 $Id: FileSystemUtils.java 29722 2010-08-16 13:40:26Z deissenb $ 003 | | 004 | Copyright 2005-2010 Technische Universitaet Muenchen | 005 | | 006 | Licensed under the Apache License, Version 2.0 (the "License"); | 007 | you may not use this file except in compliance with the License. | 008 | You may obtain a copy of the License at | 009 | | 010 | http://www.apache.org/licenses/LICENSE-2.0 | 011 | | 012 | Unless required by applicable law or agreed to in writing, software | 013 | distributed under the License is distributed on an "AS IS" BASIS, | 014 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 015 | See the License for the specific language governing permissions and | 016 | limitations under the License. | 017 +--------------------------------------------------------------------------*/ 018 package edu.tum.cs.commons.filesystem; 019 020 import java.io.BufferedInputStream; 021 import java.io.ByteArrayInputStream; 022 import java.io.Closeable; 023 import java.io.EOFException; 024 import java.io.File; 025 import java.io.FileFilter; 026 import java.io.FileInputStream; 027 import java.io.FileOutputStream; 028 import java.io.IOException; 029 import java.io.InputStream; 030 import java.io.OutputStream; 031 import java.io.OutputStreamWriter; 032 import java.io.UnsupportedEncodingException; 033 import java.net.URL; 034 import java.nio.channels.FileChannel; 035 import java.nio.charset.Charset; 036 import java.util.ArrayList; 037 import java.util.Collection; 038 import java.util.Collections; 039 import java.util.Enumeration; 040 import java.util.HashSet; 041 import java.util.IllegalFormatException; 042 import java.util.List; 043 import java.util.Properties; 044 import java.util.Set; 045 import java.util.jar.JarEntry; 046 import java.util.jar.JarFile; 047 import java.util.jar.JarOutputStream; 048 import java.util.zip.GZIPInputStream; 049 import java.util.zip.ZipEntry; 050 051 import edu.tum.cs.commons.assertion.CCSMAssert; 052 import edu.tum.cs.commons.assertion.CCSMPre; 053 import edu.tum.cs.commons.assertion.PreconditionException; 054 import edu.tum.cs.commons.collections.CollectionUtils; 055 import edu.tum.cs.commons.logging.ILogger; 056 import edu.tum.cs.commons.string.StringUtils; 057 058 /** 059 * File system utilities. 060 * 061 * @author Florian Deissenboeck 062 * @author Benjamin Hummel 063 * @author $Author: deissenb $ 064 * @version $Rev: 29722 $ 065 * @levd.rating GREEN Hash: C0A48F2C854364D0F04BB664D5A318A6 066 */ 067 public class FileSystemUtils { 068 069 /** Encoding for UTF-8. */ 070 public static final String UTF8_ENCODING = "UTF-8"; 071 072 /** Charset for UTF-8. */ 073 public static final Charset UTF8_CHARSET = Charset.forName(UTF8_ENCODING); 074 075 /** 076 * Copy an input stream to an output stream. This does <em>not</em> close 077 * the streams. 078 * 079 * @param input 080 * input stream 081 * @param output 082 * output stream 083 * @return number of bytes copied 084 * @throws IOException 085 * if an IO exception occurs. 086 */ 087 public static int copy(InputStream input, OutputStream output) 088 throws IOException { 089 byte[] buffer = new byte[1024]; 090 int size = 0; 091 int len; 092 while ((len = input.read(buffer)) > 0) { 093 output.write(buffer, 0, len); 094 size += len; 095 } 096 return size; 097 } 098 099 /** 100 * Copy a file. This creates all necessary directories. 101 */ 102 public static void copyFile(File sourceFile, File targetFile) 103 throws IOException { 104 105 ensureParentDirectoryExists(targetFile); 106 107 FileChannel sourceChannel = new FileInputStream(sourceFile) 108 .getChannel(); 109 FileChannel targetChannel = new FileOutputStream(targetFile) 110 .getChannel(); 111 sourceChannel.transferTo(0, sourceChannel.size(), targetChannel); 112 sourceChannel.close(); 113 targetChannel.close(); 114 } 115 116 /** 117 * Copy a file. This creates all necessary directories. 118 */ 119 public static void copyFile(String sourceFilename, String targetFilename) 120 throws IOException { 121 copyFile(new File(sourceFilename), new File(targetFilename)); 122 } 123 124 /** 125 * Copy all files specified by a file filter from one directory to another. 126 * This automatically creates all necessary directories. 127 * 128 * @param sourceDirectory 129 * source directory 130 * @param targetDirectory 131 * target directory 132 * @param fileFilter 133 * filter to specify file types. If all files should be copied, 134 * use {@link FileOnlyFilter}. 135 * @throws IOException 136 * if an exception occurs. 137 * @return number of files copied 138 */ 139 public static int copyFiles(File sourceDirectory, File targetDirectory, 140 FileFilter fileFilter) throws IOException { 141 List<File> files = FileSystemUtils.listFilesRecursively( 142 sourceDirectory, fileFilter); 143 144 int fileCount = 0; 145 for (File sourceFile : files) { 146 if (sourceFile.isFile()) { 147 String path = sourceFile.getAbsolutePath(); 148 int index = sourceDirectory.getAbsolutePath().length(); 149 String newPath = path.substring(index); 150 File targetFile = new File(targetDirectory, newPath); 151 copyFile(sourceFile, targetFile); 152 fileCount++; 153 } 154 } 155 return fileCount; 156 } 157 158 /** 159 * Create jar file from all files in a directory. 160 * 161 * @param jarFile 162 * jar file to create. 163 * @param directory 164 * source directory. 165 * @param filter 166 * filter to specify file types. If all files should be copied, 167 * use {@link FileOnlyFilter}. 168 * @return number of files added to the jar file 169 * @throws IOException 170 * if an exception occurs. 171 */ 172 public static int createJARFile(File jarFile, File directory, 173 FileFilter filter) throws IOException { 174 JarOutputStream out = new JarOutputStream(new FileOutputStream(jarFile)); 175 List<File> files = FileSystemUtils.listFilesRecursively(directory, 176 filter); 177 int fileCount = 0; 178 for (File file : files) { 179 if (file.isFile()) { 180 FileInputStream in = new FileInputStream(file); 181 182 String entryName = file.getAbsolutePath().substring( 183 directory.getAbsolutePath().length() + 1); 184 185 // works for forward slashes only 186 entryName = entryName.replace(File.separatorChar, '/'); 187 188 out.putNextEntry(new ZipEntry(entryName)); 189 190 copy(in, out); 191 192 out.closeEntry(); 193 in.close(); 194 fileCount++; 195 } 196 } 197 out.close(); 198 return fileCount; 199 } 200 201 /** 202 * Returns a string describing the relative path to the given directory. If 203 * there is no relative path, as the directories do not share a common 204 * parent, the absolute path is returned. 205 * 206 * @param path 207 * the path to convert to a relative path (must describe an 208 * existing directory) 209 * @param relativeTo 210 * the anchor (must describe an existing directory) 211 * @return a relative path 212 * @throws IOException 213 * if creation of canonical pathes fails. 214 */ 215 public static String createRelativePath(File path, File relativeTo) 216 throws IOException { 217 if (!path.isDirectory() || !relativeTo.isDirectory()) { 218 throw new IllegalArgumentException( 219 "Both arguments must be existing directories!"); 220 } 221 path = path.getCanonicalFile(); 222 relativeTo = relativeTo.getCanonicalFile(); 223 224 Set<File> parents = new HashSet<File>(); 225 File f = path; 226 while (f != null) { 227 parents.add(f); 228 f = f.getParentFile(); 229 } 230 231 File root = relativeTo; 232 while (root != null && !parents.contains(root)) { 233 root = root.getParentFile(); 234 } 235 236 if (root == null) { 237 // no common root, so use full path 238 return path.getAbsolutePath(); 239 } 240 241 String result = ""; 242 while (!path.equals(root)) { 243 result = path.getName() + "/" + result; 244 path = path.getParentFile(); 245 } 246 while (!relativeTo.equals(root)) { 247 result = "../" + result; 248 relativeTo = relativeTo.getParentFile(); 249 } 250 251 return result; 252 } 253 254 /** 255 * Recursively delete directories and files. This method ignores the return 256 * value of delete(), i.e. if anything fails, some files might still exist. 257 */ 258 public static void deleteRecursively(File directory) { 259 260 if (directory == null) { 261 throw new IllegalArgumentException("Directory may not be null."); 262 } else if (directory.listFiles() == null) { 263 throw new IllegalArgumentException( 264 "Argument is not a valid directory."); 265 } 266 267 for (File entry : directory.listFiles()) { 268 if (entry.isDirectory()) { 269 deleteRecursively(entry); 270 } 271 entry.delete(); 272 } 273 directory.delete(); 274 } 275 276 /** 277 * Deletes the given file and throws an exception if this fails. 278 * 279 * @see File#delete() 280 */ 281 public static void deleteFile(File file) throws IOException { 282 if (file.exists() && !file.delete()) { 283 throw new IOException("Could not delete " + file); 284 } 285 } 286 287 /** 288 * Renames the given file and throws an exception if this fails. 289 * 290 * @see File#renameTo(File) 291 */ 292 public static void renameFileTo(File file, File dest) throws IOException { 293 if (!file.renameTo(dest)) { 294 throw new IOException("Could not rename " + file + " to " + dest); 295 } 296 } 297 298 /** 299 * Creates a directory and throws an exception if this fails. 300 * 301 * @see File#mkdir() 302 */ 303 public static void mkdir(File dir) throws IOException { 304 if (!dir.mkdir()) { 305 throw new IOException("Could not create directory " + dir); 306 } 307 } 308 309 /** 310 * Creates a directory and all required parent directories. Throws an 311 * exception if this fails. 312 * 313 * @see File#mkdirs() 314 */ 315 public static void mkdirs(File dir) throws IOException { 316 if (!dir.mkdirs()) { 317 throw new IOException("Could not create directory " + dir); 318 } 319 } 320 321 /** 322 * Checks if a directory exists. If not it creates the directory and all 323 * necessary parent directories. 324 * 325 * @param file 326 * the directory 327 * @throws IOException 328 * if directories couldn't be created. 329 */ 330 public static void ensureDirectoryExists(File file) throws IOException { 331 if (!file.exists()) { 332 if (!file.mkdirs()) { 333 throw new IOException("Couldn't create directory: " + file); 334 } 335 } 336 } 337 338 /** 339 * Checks if the parent directory of a file exists. If not it creates the 340 * directory and all necessary parent directories. 341 * 342 * @param file 343 * the file 344 * @throws IOException 345 * if directories couldn't be created. 346 */ 347 public static void ensureParentDirectoryExists(File file) 348 throws IOException { 349 ensureDirectoryExists(file.getCanonicalFile().getParentFile()); 350 } 351 352 /** 353 * Returns a list of all files and directories contained in the given 354 * directory and all subdirectories. The given directory itself is not 355 * included in the result. 356 * <p> 357 * This method knows nothing about (symbolic and hard) links, so care should 358 * be taken when traversing directories containing recursive links. 359 * 360 * @param directory 361 * the directory to start the search from. 362 * @return the list of files found (the order is determined by the file 363 * system). 364 */ 365 public static List<File> listFilesRecursively(File directory) { 366 return listFilesRecursively(directory, null); 367 } 368 369 /** 370 * Returns a list of all files and directories contained in the given 371 * directory and all subdirectories matching the filter provided. The given 372 * directory itself is not included in the result. 373 * <p> 374 * The file filter may or may not exclude directories. 375 * <p> 376 * This method knows nothing about (symbolic and hard) links, so care should 377 * be taken when traversing directories containing recursive links. 378 * 379 * @param directory 380 * the directory to start the search from. If this is null or the 381 * directory does not exists, an empty list is returned. 382 * @param filter 383 * the filter used to determine whether the result should be 384 * included. If the filter is null, all files and directories are 385 * included. 386 * @return the list of files found (the order is determined by the file 387 * system). 388 */ 389 public static List<File> listFilesRecursively(File directory, 390 FileFilter filter) { 391 if (directory == null || !directory.isDirectory()) { 392 return CollectionUtils.emptyList(); 393 } 394 List<File> result = new ArrayList<File>(); 395 listFilesRecursively(directory, result, filter); 396 return result; 397 } 398 399 /** 400 * Returns the extension of the file. 401 * 402 * @return File extension, i.e. "java" for "FileSystemUtils.java", or 403 * <code>null</code>, if the file has no extension (i.e. if a 404 * filename contains no '.'), returns the empty string if the '.' is 405 * the filename's last character. 406 */ 407 public static String getFileExtension(File file) { 408 String name = file.getName(); 409 int posLastDot = name.lastIndexOf('.'); 410 if (posLastDot < 0) { 411 return null; 412 } 413 return name.substring(posLastDot + 1); 414 } 415 416 /** 417 * This method is similar to the constructor {@link File#File(File, String)} 418 * but allows to define multiple child levels. 419 * 420 * @param parent 421 * parent file 422 * @param elements 423 * list of elements. If this is empty, the parent is returned. 424 * @return the new file. 425 */ 426 public static File newFile(File parent, String... elements) { 427 if (elements.length == 0) { 428 return parent; 429 } 430 431 File child = new File(parent, elements[0]); 432 433 String[] remainingElements = new String[elements.length - 1]; 434 435 System 436 .arraycopy(elements, 1, remainingElements, 0, 437 elements.length - 1); 438 439 return newFile(child, remainingElements); 440 } 441 442 /** 443 * Read file content into a string using the default encoding for the 444 * platform. If the file starts with a UTF byte order mark (BOM), the 445 * encoding is ignored and the correct encoding based on this BOM is used 446 * for reading the file. 447 * 448 * @see EByteOrderMark 449 */ 450 public static String readFile(File file) throws IOException { 451 return readFile(file, Charset.defaultCharset().name()); 452 } 453 454 /** 455 * Read file content into a string using UTF-8 encoding. If the file starts 456 * with a UTF byte order mark (BOM), the encoding is ignored and the correct 457 * encoding based on this BOM is used for reading the file. 458 * 459 * @see EByteOrderMark 460 */ 461 public static String readFileUTF8(File file) throws IOException { 462 return readFile(file, UTF8_ENCODING); 463 } 464 465 /** 466 * Read file content into a string using the given encoding. If the file 467 * starts with a UTF byte order mark (BOM), the encoding is ignored and the 468 * correct encoding based on this BOM is used for reading the file. 469 * 470 * @see EByteOrderMark 471 */ 472 public static String readFile(File file, String encoding) 473 throws IOException, UnsupportedEncodingException { 474 FileInputStream in = new FileInputStream(file); 475 byte[] buffer = new byte[(int) file.length()]; 476 in.read(buffer); 477 in.close(); 478 479 EByteOrderMark bom = EByteOrderMark.determineBOM(buffer); 480 if (bom != null) { 481 return new String(buffer, bom.getBOMLength(), buffer.length 482 - bom.getBOMLength(), bom.getEncoding()); 483 } 484 485 return new String(buffer, encoding); 486 } 487 488 /** 489 * Extract a JAR file to a directory. 490 * 491 * @param jarFile 492 * jar file to extract 493 * @param targetDirectory 494 * target directory. 495 * @throws IOException 496 * if an exception occurs. 497 */ 498 public static void unjar(File jarFile, File targetDirectory) 499 throws IOException { 500 JarFile jar = new JarFile(jarFile); 501 Enumeration<JarEntry> entries = jar.entries(); 502 503 while (entries.hasMoreElements()) { 504 JarEntry entry = entries.nextElement(); 505 if (!entry.isDirectory()) { 506 InputStream entryStream = jar.getInputStream(entry); 507 File file = new File(targetDirectory, entry.getName()); 508 FileSystemUtils.ensureParentDirectoryExists(file); 509 FileOutputStream outputStream = new FileOutputStream(file); 510 copy(entryStream, outputStream); 511 entryStream.close(); 512 outputStream.close(); 513 } 514 } 515 516 jar.close(); 517 } 518 519 /** 520 * Write string to a file with the default encoding. This ensures all 521 * directories exist. 522 */ 523 public static void writeFile(File file, String content) throws IOException { 524 writeFile(file, content, Charset.defaultCharset().name()); 525 } 526 527 /** 528 * Write string to a file with UTF8 encoding. This ensures all directories 529 * exist. 530 */ 531 public static void writeFileUTF8(File file, String content) 532 throws IOException { 533 writeFile(file, content, UTF8_ENCODING); 534 } 535 536 /** Write string to a file. This ensures all directories exist. */ 537 public static void writeFile(File file, String content, String encoding) 538 throws IOException { 539 ensureParentDirectoryExists(file); 540 OutputStreamWriter writer = null; 541 try { 542 writer = new OutputStreamWriter(new FileOutputStream(file), 543 encoding); 544 writer.write(content); 545 } finally { 546 FileSystemUtils.close(writer); 547 } 548 } 549 550 /** 551 * Write string to a file using a UTF encoding. The file will be prefixed 552 * with a byte-order mark. This ensures all directories exist. 553 */ 554 public static void writeFileWithBOM(File file, String content, 555 EByteOrderMark bom) throws IOException { 556 ensureParentDirectoryExists(file); 557 FileOutputStream out = null; 558 try { 559 out = new FileOutputStream(file); 560 out.write(bom.getBOM()); 561 562 OutputStreamWriter writer = new OutputStreamWriter(out, bom 563 .getEncoding()); 564 writer.write(content); 565 writer.flush(); 566 } finally { 567 FileSystemUtils.close(out); 568 } 569 } 570 571 /** 572 * Finds all files and directories contained in the given directory and all 573 * subdirectories matching the filter provided and put them into the result 574 * collection. The given directory itself is not included in the result. 575 * <p> 576 * This method knows nothing about (symbolic and hard) links, so care should 577 * be taken when traversing directories containing recursive links. 578 * 579 * @param directory 580 * the directory to start the search from. 581 * @param result 582 * the collection to add to all files found. 583 * @param filter 584 * the filter used to determine whether the result should be 585 * included. If the filter is null, all files and directories are 586 * included. 587 */ 588 private static void listFilesRecursively(File directory, 589 Collection<File> result, FileFilter filter) { 590 for (File f : directory.listFiles()) { 591 if (f.isDirectory()) { 592 listFilesRecursively(f, result, filter); 593 } 594 if (filter == null || filter.accept(f)) { 595 result.add(f); 596 } 597 } 598 } 599 600 /** 601 * Loads template file with a <a href= 602 * "http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax" 603 * >Format string</a>, formats it and writes result to file. 604 * 605 * @param templateFile 606 * the template file with the format string 607 * @param outFile 608 * the target file, parent directories are created automatically. 609 * @param arguments 610 * the formatting arguments. 611 * @throws IOException 612 * if an IO exception occurs or the template file defines an 613 * illegal format. 614 */ 615 public static void mergeTemplate(File templateFile, File outFile, 616 Object... arguments) throws IOException { 617 String template = readFile(templateFile); 618 String output; 619 try { 620 output = String.format(template, arguments); 621 } catch (IllegalFormatException e) { 622 // We do not pass the cause to the constructor as the required 623 // constructor is only defined in 1.6 624 throw new IOException("Illegal format: " + e.getMessage()); 625 } 626 writeFile(outFile, output); 627 } 628 629 /** 630 * Loads template file with a <a href= 631 * "http://java.sun.com/javase/6/docs/api/java/util/Formatter.html#syntax" 632 * >Format string</a>, formats it and provides result as stream. No streams 633 * are closed by this method. 634 * 635 * @param inStream 636 * stream that provides the template format string 637 * @param arguments 638 * the formatting arguments. 639 * @throws IOException 640 * if an IOException occurs or the template file defines an 641 * illegal format. 642 */ 643 public static InputStream mergeTemplate(InputStream inStream, 644 Object... arguments) throws IOException { 645 String template = readStream(inStream); 646 String output; 647 try { 648 output = String.format(template, arguments); 649 } catch (IllegalFormatException e) { 650 // We do not pass the cause to the constructor as the required 651 // constructor is only defined in 1.6 652 throw new IOException("Illegal format: " + e.getMessage()); 653 } 654 return new ByteArrayInputStream(output.getBytes()); 655 } 656 657 /** Read input stream into string. */ 658 public static String readStream(InputStream input) throws IOException { 659 return readStream(input, Charset.defaultCharset().name()); 660 } 661 662 /** Read input stream into string. */ 663 public static String readStreamUTF8(InputStream input) throws IOException { 664 return readStream(input, UTF8_ENCODING); 665 } 666 667 /** Read input stream into string. */ 668 public static String readStream(InputStream input, String encoding) 669 throws IOException { 670 StringBuilder out = new StringBuilder(); 671 byte[] b = new byte[4096]; 672 for (int n; (n = input.read(b)) != -1;) { 673 out.append(new String(b, 0, n, encoding)); 674 } 675 return out.toString(); 676 } 677 678 /** Reads properties from a properties file. */ 679 public static Properties readPropertiesFile(File propertiesFile) 680 throws IOException { 681 Properties properties = new Properties(); 682 InputStream inputStream = new FileInputStream(propertiesFile); 683 try { 684 properties.load(inputStream); 685 } finally { 686 inputStream.close(); 687 } 688 return properties; 689 } 690 691 /** 692 * Determines the root directory from a collection of files. The root 693 * directory is the lowest common ancestor directory of the files in the 694 * directory tree. 695 * <p> 696 * This method does not require the input files to exist. 697 * 698 * @param files 699 * Collection of files for which root directory gets determined. 700 * This collection is required to contain at least 2 files. If it 701 * does not, an AssertionError is thrown. 702 * 703 * @throws PreconditionException 704 * If less than two different files are provided whereas fully 705 * qualified canonical names are used for comparison. 706 * 707 * @throws IOException 708 * Since canonical paths are used for determination of the 709 * common root, and {@link File#getCanonicalPath()} can throw 710 * {@link IOException}s. 711 * 712 * @return Root directory, or null, if the files do not have a common root 713 * directory. 714 */ 715 public static File commonRoot(Iterable<? extends File> files) 716 throws IOException { 717 // determine longest common prefix on canonical absolute paths 718 Set<String> absolutePaths = new HashSet<String>(); 719 for (File file : files) { 720 absolutePaths.add(file.getCanonicalPath()); 721 } 722 723 CCSMPre.isTrue(absolutePaths.size() >= 2, 724 "Expected are at least 2 files"); 725 726 String longestCommonPrefix = StringUtils 727 .longestCommonPrefix(absolutePaths); 728 729 // trim to name of root directory (remove possible equal filename 730 // prefixes.) 731 int lastSeparator = longestCommonPrefix.lastIndexOf(File.separator); 732 if (lastSeparator > -1) { 733 longestCommonPrefix = longestCommonPrefix.substring(0, 734 lastSeparator); 735 } 736 737 if (StringUtils.isEmpty(longestCommonPrefix)) { 738 return null; 739 } 740 741 return new File(longestCommonPrefix); 742 } 743 744 /** See {@link #canonize(File)} */ 745 @Deprecated 746 public static String canonize(String path) { 747 return canonize(new File(path)).getPath(); 748 } 749 750 /** 751 * Creates a canonical path from a file path. This method wraps 752 * {@link File#getCanonicalPath()} and wraps {@link IOException}s thrown by 753 * it into AssertionErrors, since we expect this method not to throw 754 * IOExceptions. 755 */ 756 @Deprecated 757 public static File canonize(File file) { 758 try { 759 return file.getCanonicalFile(); 760 } catch (IOException e) { 761 throw new AssertionError("Problems creating canonical path for " 762 + file); 763 } 764 } 765 766 /** 767 * Transparently creates a stream for decompression if the provided stream 768 * is compressed. Otherwise the stream is just handed through. Currently the 769 * following compression methods are supported: 770 * <ul> 771 * <li>GZIP via {@link GZIPInputStream}</li> 772 * </ul> 773 */ 774 public static InputStream autoDecompressStream(InputStream in) 775 throws IOException { 776 if (!in.markSupported()) { 777 in = new BufferedInputStream(in); 778 } 779 in.mark(2); 780 // check first two bytes for GZIP header 781 boolean isGZIP = (in.read() & 0xff | (in.read() & 0xff) << 8) == GZIPInputStream.GZIP_MAGIC; 782 in.reset(); 783 if (isGZIP) { 784 return new GZIPInputStream(in); 785 } 786 return in; 787 } 788 789 /** 790 * Convenience method for calling {@link #close(Closeable, ILogger)} with a 791 * <code>null</code>-logger. 792 */ 793 public static void close(Closeable closeable) { 794 close(closeable, null); 795 } 796 797 /** 798 * This method can be used to simplify the typical <code>finally</code> 799 * -block of code dealing with streams and readers/writers. It checks if the 800 * provided closeable is <code>null</code>. If not it closes it. An 801 * exception thrown during the close operation is logged with the provided 802 * logger with level <i>warn</i>. If the provided logger is 803 * <code>null</code>, no logging is performed. If no logging is required, 804 * method {@link #close(Closeable)} may also be used. 805 */ 806 public static void close(Closeable closeable, ILogger logger) { 807 if (closeable == null) { 808 return; 809 } 810 811 try { 812 closeable.close(); 813 } catch (IOException e) { 814 if (logger != null) { 815 logger.warn("Trouble closing: " + e.getMessage()); 816 } 817 } 818 } 819 820 /** 821 * Compares files based on the lexical order of their fully qualified names. 822 * Files must not null. 823 */ 824 public static void sort(List<File> files) { 825 Collections.sort(files, new FilenameComparator()); 826 } 827 828 /** 829 * Replace platform dependent separator char with forward slashes to create 830 * system-independent paths. 831 */ 832 public static String normalizeSeparators(String path) { 833 return path.replace(File.separatorChar, '/'); 834 } 835 836 /** 837 * Returns the JAR file for an URL with protocol 'jar'. If the protocol is 838 * not 'jar' an assertion error will be caused! 839 */ 840 public static File extractJarFileFromJarURL(URL url) { 841 CCSMPre.isTrue("jar".equals(url.getProtocol()), 842 "May only be used with 'jar' URLs!"); 843 844 String path = url.getPath(); 845 path = StringUtils.stripPrefix("file:", path); 846 847 // the exclamation mark is the separator between jar file and path 848 // within the file 849 int index = path.indexOf('!'); 850 CCSMAssert.isTrue(index >= 0, "Unknown format for jar URLs"); 851 path = path.substring(0, index); 852 return new File(path); 853 } 854 855 /** 856 * Returns whether a filename represents an absolute path. 857 * 858 * This method returns the same result, independent on which operating 859 * system it gets executed. In contrast, the behavior of 860 * {@link File#isAbsolute()} is operating system specific. 861 */ 862 public static boolean isAbsolutePath(String filename) { 863 // Unix and MacOS: absolute path starts with slash 864 if (filename.startsWith("/") || filename.startsWith("~/")) { 865 return true; 866 } 867 // Windows and OS/2: absolute path start with letter and colon 868 if (filename.length() > 2 && Character.isLetter(filename.charAt(0)) 869 && filename.charAt(1) == ':') { 870 return true; 871 } 872 // UNC paths (aka network shares): start with double backslash 873 if (filename.startsWith("\\\\")) { 874 return true; 875 } 876 877 return false; 878 } 879 880 /** 881 * Reads bytes of data from the input stream into an array of bytes until 882 * the array is full. This method blocks until input data is available, end 883 * of file is detected, or an exception is thrown. 884 * 885 * The reason for this method is that {@link InputStream#read(byte[])} may 886 * read less than the requested number of bytes, while this method ensures 887 * the data is complete. 888 * 889 * @param in 890 * the stream to read from. 891 * @param data 892 * the stream to read from. 893 * @throws IOException 894 * if reading the underlying stream causes an exception. 895 * @throws EOFException 896 * if the end of file was reached before the requested data was 897 * read. 898 */ 899 public static void safeRead(InputStream in, byte[] data) 900 throws IOException, EOFException { 901 safeRead(in, data, 0, data.length); 902 } 903 904 /** 905 * Reads <code>length</code> bytes of data from the input stream into an 906 * array of bytes and stores it at position <code>offset</code>. This method 907 * blocks until input data is available, end of file is detected, or an 908 * exception is thrown. 909 * 910 * The reason for this method is that 911 * {@link InputStream#read(byte[], int, int)} may read less than the 912 * requested number of bytes, while this method ensures the data is 913 * complete. 914 * 915 * @param in 916 * the stream to read from. 917 * @param data 918 * the stream to read from. 919 * @param offset 920 * the offset in the array where the first read byte is stored. 921 * @param length 922 * the length of data read. 923 * @throws IOException 924 * if reading the underlying stream causes an exception. 925 * @throws EOFException 926 * if the end of file was reached before the requested data was 927 * read. 928 */ 929 public static void safeRead(InputStream in, byte[] data, int offset, 930 int length) throws IOException, EOFException { 931 while (length > 0) { 932 int read = in.read(data, offset, length); 933 if (read < 0) { 934 throw new EOFException( 935 "Reached end of file before completing read."); 936 } 937 offset += read; 938 length -= read; 939 } 940 } 941 }