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 }