1 package org.codehaus.plexus.util;
2
3 /* ====================================================================
4 * The Apache Software License, Version 1.1
5 *
6 * Copyright (c) 2001 The Apache Software Foundation. All rights
7 * reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in
18 * the documentation and/or other materials provided with the
19 * distribution.
20 *
21 * 3. The end-user documentation included with the redistribution,
22 * if any, must include the following acknowledgment:
23 * "This product includes software developed by the
24 * Apache Software Foundation (http://www.codehaus.org/)."
25 * Alternately, this acknowledgment may appear in the software itself,
26 * if and wherever such third-party acknowledgments normally appear.
27 *
28 * 4. The names "Apache" and "Apache Software Foundation" and
29 * "Apache Turbine" must not be used to endorse or promote products
30 * derived from this software without prior written permission. For
31 * written permission, please contact codehaus@codehaus.org.
32 *
33 * 5. Products derived from this software may not be called "Apache",
34 * "Apache Turbine", nor may "Apache" appear in their name, without
35 * prior written permission of the Apache Software Foundation.
36 *
37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48 * SUCH DAMAGE.
49 * ====================================================================
50 *
51 * This software consists of voluntary contributions made by many
52 * individuals on behalf of the Apache Software Foundation. For more
53 * information on the Apache Software Foundation, please see
54 * <http://www.codehaus.org/>.
55 *
56 */
57
58 import java.io.BufferedReader;
59 import java.io.File;
60 import java.io.IOException;
61 import java.io.InputStream;
62 import java.io.OutputStream;
63 import java.io.OutputStreamWriter;
64 import java.io.Reader;
65 import java.io.Writer;
66 import java.net.URL;
67 import java.nio.charset.Charset;
68 import java.nio.file.Files;
69 import java.nio.file.Path;
70 import java.nio.file.Paths;
71 import java.nio.file.StandardOpenOption;
72 import java.security.SecureRandom;
73 import java.text.DecimalFormat;
74 import java.util.ArrayList;
75 import java.util.Arrays;
76 import java.util.List;
77 import java.util.Random;
78
79 import org.codehaus.plexus.util.io.InputStreamFacade;
80 import org.codehaus.plexus.util.io.URLInputStreamFacade;
81
82 /**
83 * <p>This class provides basic facilities for manipulating files and file paths.</p>
84 *
85 * <b>Path-related methods</b>
86 *
87 * <p>Methods exist to retrieve the components of a typical file path. For example
88 * <code>/www/hosted/mysite/index.html</code>, can be broken into:
89 * <ul>
90 * <li><code>/www/hosted/mysite/</code> -- retrievable through {@link #getPath}</li>
91 * <li><code>index.html</code> -- retrievable through {@link #removePath}</li>
92 * <li><code>/www/hosted/mysite/index</code> -- retrievable through {@link #removeExtension}</li>
93 * <li><code>html</code> -- retrievable through {@link #getExtension}</li>
94 * </ul>
95 * <p>There are also methods to {@link #catPath concatenate two paths}, {@link #resolveFile resolve a path relative to a
96 * File} and {@link #normalize} a path.</p>
97 *
98 * <b>File-related methods</b>
99 *
100 * <p>There are methods to create a {@link #toFile File from a URL}, copy a {@link #copyFileToDirectory File to a
101 * directory}, copy a {@link #copyFile File to another File}, copy a {@link #copyURLToFile URL's contents to a File}, as
102 * well as methods to {@link #deleteDirectory(File) delete} and {@link #cleanDirectory(File) clean} a directory.</p>
103 *
104 * <p>Common {@link java.io.File} manipulation routines.</p>
105 *
106 * <p>Taken from the commons-utils repo. Also code from Alexandria's FileUtils. And from Avalon Excalibur's IO. And from
107 * Ant.</p>
108 *
109 * @author <a href="mailto:burton@relativity.yi.org">Kevin A. Burton</A>
110 * @author <a href="mailto:sanders@codehaus.org">Scott Sanders</a>
111 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
112 * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph.Reck</a>
113 * @author <a href="mailto:peter@codehaus.org">Peter Donald</a>
114 * @author <a href="mailto:jefft@codehaus.org">Jeff Turner</a>
115 *
116 */
117 public class FileUtils extends BaseFileUtils {
118 /**
119 * The number of bytes in a kilobyte.
120 */
121 public static final int ONE_KB = 1024;
122
123 /**
124 * The number of bytes in a megabyte.
125 */
126 public static final int ONE_MB = ONE_KB * ONE_KB;
127
128 /**
129 * The number of bytes in a gigabyte.
130 */
131 public static final int ONE_GB = ONE_KB * ONE_MB;
132
133 /**
134 * The vm file separator
135 */
136 public static String FS = File.separator;
137
138 /**
139 * Non-valid Characters for naming files, folders under Windows: <code>":", "*", "?", "\"", "<", ">", "|"</code>
140 *
141 * @see <a href="http://support.microsoft.com/?scid=kb%3Ben-us%3B177506&x=12&y=13">
142 * http://support.microsoft.com/?scid=kb%3Ben-us%3B177506&x=12&y=13</a>
143 */
144 private static final String[] INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME = {":", "*", "?", "\"", "<", ">", "|"};
145
146 /**
147 * @return the default excludes pattern
148 * @see DirectoryScanner#DEFAULTEXCLUDES
149 */
150 public static String[] getDefaultExcludes() {
151 return DirectoryScanner.DEFAULTEXCLUDES;
152 }
153
154 /**
155 * @return the default excludes pattern as list.
156 * @see #getDefaultExcludes()
157 */
158 public static List<String> getDefaultExcludesAsList() {
159 return Arrays.asList(getDefaultExcludes());
160 }
161
162 /**
163 * @return the default excludes pattern as comma separated string.
164 * @see DirectoryScanner#DEFAULTEXCLUDES
165 * @see StringUtils#join(Object[], String)
166 */
167 public static String getDefaultExcludesAsString() {
168 return StringUtils.join(DirectoryScanner.DEFAULTEXCLUDES, ",");
169 }
170
171 /**
172 * Returns a human-readable version of the file size (original is in bytes).
173 *
174 * @param size The number of bytes.
175 * @return A human-readable display value (includes units).
176 */
177 public static String byteCountToDisplaySize(int size) {
178 String displaySize;
179
180 if (size / ONE_GB > 0) {
181 displaySize = String.valueOf(size / ONE_GB) + " GB";
182 } else if (size / ONE_MB > 0) {
183 displaySize = String.valueOf(size / ONE_MB) + " MB";
184 } else if (size / ONE_KB > 0) {
185 displaySize = String.valueOf(size / ONE_KB) + " KB";
186 } else {
187 displaySize = String.valueOf(size) + " bytes";
188 }
189
190 return displaySize;
191 }
192
193 /**
194 * Returns the directory path portion of a file specification string. Matches the equally named unix command.
195 *
196 * @param filename the file path
197 * @return The directory portion excluding the ending file separator.
198 */
199 public static String dirname(String filename) {
200 int i = filename.lastIndexOf(File.separator);
201 return (i >= 0 ? filename.substring(0, i) : "");
202 }
203
204 /**
205 * Returns the filename portion of a file specification string.
206 *
207 * @param filename the file path
208 * @return The filename string with extension.
209 */
210 public static String filename(String filename) {
211 int i = filename.lastIndexOf(File.separator);
212 return (i >= 0 ? filename.substring(i + 1) : filename);
213 }
214
215 /**
216 * Returns the filename portion of a file specification string. Matches the equally named unix command.
217 *
218 * @param filename the file path
219 * @return The filename string without extension.
220 */
221 public static String basename(String filename) {
222 return basename(filename, extension(filename));
223 }
224
225 /**
226 * Returns the filename portion of a file specification string. Matches the equally named unix command.
227 *
228 * @param filename the file path
229 * @param suffix the file suffix
230 * @return the basename of the file
231 */
232 public static String basename(String filename, String suffix) {
233 int i = filename.lastIndexOf(File.separator) + 1;
234 int lastDot = ((suffix != null) && (suffix.length() > 0)) ? filename.lastIndexOf(suffix) : -1;
235
236 if (lastDot >= 0) {
237 return filename.substring(i, lastDot);
238 } else if (i > 0) {
239 return filename.substring(i);
240 } else {
241 return filename; // else returns all (no path and no extension)
242 }
243 }
244
245 /**
246 * Returns the extension portion of a file specification string. This everything after the last dot '.' in the
247 * filename (NOT including the dot).
248 *
249 * @param filename the file path
250 * @return the extension of the file
251 */
252 public static String extension(String filename) {
253 // Ensure the last dot is after the last file separator
254 int lastSep = filename.lastIndexOf(File.separatorChar);
255 int lastDot;
256 if (lastSep < 0) {
257 lastDot = filename.lastIndexOf('.');
258 } else {
259 lastDot = filename.substring(lastSep + 1).lastIndexOf('.');
260 if (lastDot >= 0) {
261 lastDot += lastSep + 1;
262 }
263 }
264
265 if (lastDot >= 0 && lastDot > lastSep) {
266 return filename.substring(lastDot + 1);
267 }
268
269 return "";
270 }
271
272 /**
273 * Check if a file exits.
274 *
275 * @param fileName the file path.
276 * @return true if file exists.
277 */
278 public static boolean fileExists(String fileName) {
279 File file = new File(fileName);
280 return file.exists();
281 }
282
283 /**
284 * Note: the file content is read with platform encoding.
285 *
286 * @param file the file path
287 * @return the file content using the platform encoding.
288 * @throws IOException if any
289 */
290 public static String fileRead(String file) throws IOException {
291 return fileRead(file, null);
292 }
293
294 /**
295 * @param file the file path
296 * @param encoding the wanted encoding
297 * @return the file content using the specified encoding.
298 * @throws IOException if any
299 */
300 public static String fileRead(String file, String encoding) throws IOException {
301 return fileRead(Paths.get(file), encoding);
302 }
303
304 /**
305 * Note: the file content is read with platform encoding
306 *
307 * @param file the file path
308 * @return the file content using the platform encoding.
309 * @throws IOException if any
310 */
311 public static String fileRead(File file) throws IOException {
312 return fileRead(file, null);
313 }
314
315 /**
316 * @param file the file path
317 * @param encoding the wanted encoding
318 * @return the file content using the specified encoding.
319 * @throws IOException if any
320 */
321 public static String fileRead(File file, String encoding) throws IOException {
322 return fileRead(file.toPath(), encoding);
323 }
324
325 /**
326 * Appends data to a file. The file will be created if it does not exist. Note: the data is written with platform
327 * encoding
328 *
329 * @param fileName The path of the file to write.
330 * @param data The content to write to the file.
331 * @throws IOException if any
332 * @deprecated use {@code java.nio.files.Files.write(filename, data.getBytes(encoding),
333 * StandardOpenOption.APPEND, StandardOpenOption.CREATE)}
334 */
335 public static void fileAppend(String fileName, String data) throws IOException {
336 fileAppend(fileName, null, data);
337 }
338
339 /**
340 * Appends data to a file. The file will be created if it does not exist.
341 *
342 * @param fileName The path of the file to write.
343 * @param encoding The encoding of the file.
344 * @param data The content to write to the file.
345 * @throws IOException if any
346 * @deprecated use {@code java.nio.files.Files.write(filename, data.getBytes(encoding),
347 * StandardOpenOption.APPEND, StandardOpenOption.CREATE)}
348 */
349 public static void fileAppend(String fileName, String encoding, String data) throws IOException {
350 fileAppend(Paths.get(fileName), encoding, data);
351 }
352
353 private static void fileAppend(Path path, String encoding, String data) throws IOException {
354 fileWrite(path, encoding, data, StandardOpenOption.APPEND, StandardOpenOption.CREATE);
355 }
356
357 /**
358 * Writes data to a file. The file will be created if it does not exist. Note: the data is written with platform
359 * encoding
360 *
361 * @param fileName The path of the file to write.
362 * @param data The content to write to the file.
363 * @throws IOException if any
364 */
365 public static void fileWrite(String fileName, String data) throws IOException {
366 fileWrite(fileName, null, data);
367 }
368
369 /**
370 * Writes data to a file. The file will be created if it does not exist.
371 *
372 * @param fileName The path of the file to write.
373 * @param encoding The encoding of the file.
374 * @param data The content to write to the file.
375 * @throws IOException if any
376 */
377 public static void fileWrite(String fileName, String encoding, String data) throws IOException {
378 Path file = (fileName == null) ? null : Paths.get(fileName);
379 fileWrite(file, encoding, data);
380 }
381
382 /**
383 * Writes data to a file. The file will be created if it does not exist. Note: the data is written with platform
384 * encoding
385 *
386 * @param file The file to write.
387 * @param data The content to write to the file.
388 * @throws IOException if any
389 * @since 2.0.6
390 */
391 public static void fileWrite(File file, String data) throws IOException {
392 fileWrite(file, null, data);
393 }
394
395 /**
396 * Writes data to a file. The file will be created if it does not exist.
397 *
398 * @param file The file to write.
399 * @param encoding The encoding of the file.
400 * @param data The content to write to the file.
401 * @throws IOException if any
402 * @since 2.0.6
403 */
404 public static void fileWrite(File file, String encoding, String data) throws IOException {
405 fileWrite(file.toPath(), encoding, data);
406 }
407
408 /**
409 * Deletes a file.
410 *
411 * @param fileName The path of the file to delete.
412 */
413 public static void fileDelete(String fileName) {
414 File file = new File(fileName);
415 try {
416 NioFiles.deleteIfExists(file);
417 } catch (IOException e) {
418 throw new RuntimeException(e);
419 }
420 }
421
422 /**
423 * Waits for NFS to propagate a file creation, imposing a timeout.
424 *
425 * @param fileName The path of the file.
426 * @param seconds The maximum time in seconds to wait.
427 * @return True if file exists.
428 */
429 public static boolean waitFor(String fileName, int seconds) {
430 return waitFor(new File(fileName), seconds);
431 }
432
433 /**
434 * Waits for NFS to propagate a file creation, imposing a timeout.
435 *
436 * @param file The file.
437 * @param seconds The maximum time in seconds to wait.
438 * @return True if file exists.
439 */
440 public static boolean waitFor(File file, int seconds) {
441 int timeout = 0;
442 int tick = 0;
443 while (!file.exists()) {
444 if (tick++ >= 10) {
445 tick = 0;
446 if (timeout++ > seconds) {
447 return false;
448 }
449 }
450 try {
451 Thread.sleep(100);
452 } catch (InterruptedException ignore) {
453 // nop
454 }
455 }
456 return true;
457 }
458
459 /**
460 * Creates a file handle.
461 *
462 * @param fileName The path of the file.
463 * @return A <code>File</code> manager.
464 */
465 public static File getFile(String fileName) {
466 return new File(fileName);
467 }
468
469 /**
470 * <p>Given a directory and an array of extensions return an array of compliant files.</p>
471 *
472 * <p>TODO Should an ignore list be passed in? TODO Should a recurse flag be passed in?</p>
473 *
474 * <p>The given extensions should be like "java" and not like ".java"</p>
475 *
476 * @param directory The path of the directory.
477 * @param extensions an array of expected extensions.
478 * @return An array of files for the wanted extensions.
479 */
480 public static String[] getFilesFromExtension(String directory, String[] extensions) {
481 List<String> files = new ArrayList<String>();
482
483 File currentDir = new File(directory);
484
485 String[] unknownFiles = currentDir.list();
486
487 if (unknownFiles == null) {
488 return new String[0];
489 }
490
491 for (String unknownFile : unknownFiles) {
492 String currentFileName = directory + System.getProperty("file.separator") + unknownFile;
493 File currentFile = new File(currentFileName);
494
495 if (currentFile.isDirectory()) {
496 // ignore all CVS directories...
497 if (currentFile.getName().equals("CVS")) {
498 continue;
499 }
500
501 // ok... transverse into this directory and get all the files... then combine
502 // them with the current list.
503
504 String[] fetchFiles = getFilesFromExtension(currentFileName, extensions);
505 files = blendFilesToVector(files, fetchFiles);
506 } else {
507 // ok... add the file
508
509 String add = currentFile.getAbsolutePath();
510 if (isValidFile(add, extensions)) {
511 files.add(add);
512 }
513 }
514 }
515
516 // ok... move the Vector into the files list...
517 return files.toArray(new String[0]);
518 }
519
520 /**
521 * Private helper method for getFilesFromExtension()
522 */
523 private static List<String> blendFilesToVector(List<String> v, String[] files) {
524 for (String file : files) {
525 v.add(file);
526 }
527
528 return v;
529 }
530
531 /**
532 * Checks to see if a file is of a particular type(s). Note that if the file does not have an extension, an empty
533 * string ("") is matched for.
534 */
535 private static boolean isValidFile(String file, String[] extensions) {
536 String extension = extension(file);
537 if (extension == null) {
538 extension = "";
539 }
540
541 // ok.. now that we have the "extension" go through the current know
542 // excepted extensions and determine if this one is OK.
543
544 for (String extension1 : extensions) {
545 if (extension1.equals(extension)) {
546 return true;
547 }
548 }
549
550 return false;
551 }
552
553 /**
554 * Simple way to make a directory
555 *
556 * @param dir the directory to create
557 * @throws IllegalArgumentException if the dir contains illegal Windows characters under Windows OS.
558 * @see #INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME
559 */
560 public static void mkdir(String dir) {
561 File file = new File(dir);
562
563 if (Os.isFamily(Os.FAMILY_WINDOWS) && !isValidWindowsFileName(file)) {
564 throw new IllegalArgumentException("The file (" + dir
565 + ") cannot contain any of the following characters: \n"
566 + StringUtils.join(INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME, " "));
567 }
568
569 if (!file.exists()) {
570 file.mkdirs();
571 }
572 }
573
574 /**
575 * Compare the contents of two files to determine if they are equal or not.
576 *
577 * @param file1 the first file
578 * @param file2 the second file
579 * @return true if the content of the files are equal or they both don't exist, false otherwise
580 * @throws IOException if any
581 */
582 public static boolean contentEquals(final File file1, final File file2) throws IOException {
583 final boolean file1Exists = file1.exists();
584 if (file1Exists != file2.exists()) {
585 return false;
586 }
587
588 if (!file1Exists) {
589 // two not existing files are equal
590 return true;
591 }
592
593 if (file1.isDirectory() || file2.isDirectory()) {
594 // don't want to compare directory contents
595 return false;
596 }
597
598 try (InputStream input1 = Files.newInputStream(file1.toPath());
599 InputStream input2 = Files.newInputStream(file2.toPath())) {
600 return IOUtil.contentEquals(input1, input2);
601 }
602 }
603
604 /**
605 * Convert from a <code>URL</code> to a <code>File</code>.
606 *
607 * @param url File URL.
608 * @return The equivalent <code>File</code> object, or <code>null</code> if the URL's protocol is not
609 * <code>file</code>
610 */
611 public static File toFile(final URL url) {
612 if (url == null || !url.getProtocol().equalsIgnoreCase("file")) {
613 return null;
614 }
615
616 String filename = url.getFile().replace('/', File.separatorChar);
617 int pos = -1;
618 while ((pos = filename.indexOf('%', pos + 1)) >= 0) {
619 if (pos + 2 < filename.length()) {
620 String hexStr = filename.substring(pos + 1, pos + 3);
621 char ch = (char) Integer.parseInt(hexStr, 16);
622 filename = filename.substring(0, pos) + ch + filename.substring(pos + 3);
623 }
624 }
625 return new File(filename);
626 }
627
628 /**
629 * Convert the array of Files into a list of URLs.
630 *
631 * @param files the array of files
632 * @return the array of URLs
633 * @throws IOException if an error occurs
634 */
635 public static URL[] toURLs(final File[] files) throws IOException {
636 final URL[] urls = new URL[files.length];
637
638 for (int i = 0; i < urls.length; i++) {
639 urls[i] = files[i].toURI().toURL();
640 }
641
642 return urls;
643 }
644
645 /**
646 * Remove extension from filename. ie
647 *
648 * <pre>
649 * foo.txt --> foo
650 * a\b\c.jpg --> a\b\c
651 * a\b\c --> a\b\c
652 * </pre>
653 *
654 * @param filename the path of the file
655 * @return the filename minus extension
656 */
657 public static String removeExtension(final String filename) {
658 String ext = extension(filename);
659
660 if ("".equals(ext)) {
661 return filename;
662 }
663
664 final int index = filename.lastIndexOf(ext) - 1;
665 return filename.substring(0, index);
666 }
667
668 /**
669 * Get extension from filename. ie
670 *
671 * <pre>
672 * foo.txt --> "txt"
673 * a\b\c.jpg --> "jpg"
674 * a\b\c --> ""
675 * </pre>
676 *
677 * @param filename the path of the file
678 * @return the extension of filename or "" if none
679 */
680 public static String getExtension(final String filename) {
681 return extension(filename);
682 }
683
684 /**
685 * Remove path from filename. Equivalent to the unix command <code>basename</code> ie.
686 *
687 * <pre>
688 * a/b/c.txt --> c.txt
689 * a.txt --> a.txt
690 * </pre>
691 *
692 * @param filepath the path of the file
693 * @return the filename minus path
694 */
695 public static String removePath(final String filepath) {
696 return removePath(filepath, File.separatorChar);
697 }
698
699 /**
700 * Remove path from filename. ie.
701 *
702 * <pre>
703 * a/b/c.txt --> c.txt
704 * a.txt --> a.txt
705 * </pre>
706 *
707 * @param filepath the path of the file
708 * @param fileSeparatorChar the file separator character like <b>/</b> on Unix platforms.
709 * @return the filename minus path
710 */
711 public static String removePath(final String filepath, final char fileSeparatorChar) {
712 final int index = filepath.lastIndexOf(fileSeparatorChar);
713
714 if (-1 == index) {
715 return filepath;
716 }
717
718 return filepath.substring(index + 1);
719 }
720
721 /**
722 * Get path from filename. Roughly equivalent to the unix command <code>dirname</code>. ie.
723 *
724 * <pre>
725 * a/b/c.txt --> a/b
726 * a.txt --> ""
727 * </pre>
728 *
729 * @param filepath the filepath
730 * @return the filename minus path
731 */
732 public static String getPath(final String filepath) {
733 return getPath(filepath, File.separatorChar);
734 }
735
736 /**
737 * Get path from filename. ie.
738 *
739 * <pre>
740 * a/b/c.txt --> a/b
741 * a.txt --> ""
742 * </pre>
743 *
744 * @param filepath the filepath
745 * @param fileSeparatorChar the file separator character like <b>/</b> on Unix platforms.
746 * @return the filename minus path
747 */
748 public static String getPath(final String filepath, final char fileSeparatorChar) {
749 final int index = filepath.lastIndexOf(fileSeparatorChar);
750 if (-1 == index) {
751 return "";
752 }
753
754 return filepath.substring(0, index);
755 }
756
757 /**
758 * Copy file from source to destination. If <code>destinationDirectory</code> does not exist, it (and any parent
759 * directories) will be created. If a file <code>source</code> in <code>destinationDirectory</code> exists, it will
760 * be overwritten.
761 *
762 * @param source An existing <code>File</code> to copy.
763 * @param destinationDirectory A directory to copy <code>source</code> into.
764 * @throws java.io.FileNotFoundException if <code>source</code> isn't a normal file.
765 * @throws IllegalArgumentException if <code>destinationDirectory</code> isn't a directory.
766 * @throws IOException if <code>source</code> does not exist, the file in <code>destinationDirectory</code> cannot
767 * be written to, or an IO error occurs during copying.
768 */
769 public static void copyFileToDirectory(final String source, final String destinationDirectory) throws IOException {
770 copyFileToDirectory(new File(source), new File(destinationDirectory));
771 }
772
773 /**
774 * Copy file from source to destination only if source is newer than the target file. If
775 * <code>destinationDirectory</code> does not exist, it (and any parent directories) will be created. If a file
776 * <code>source</code> in <code>destinationDirectory</code> exists, it will be overwritten.
777 *
778 * @param source An existing <code>File</code> to copy.
779 * @param destinationDirectory A directory to copy <code>source</code> into.
780 * @throws java.io.FileNotFoundException if <code>source</code> isn't a normal file.
781 * @throws IllegalArgumentException if <code>destinationDirectory</code> isn't a directory.
782 * @throws IOException if <code>source</code> does not exist, the file in <code>destinationDirectory</code> cannot
783 * be written to, or an IO error occurs during copying.
784 */
785 public static void copyFileToDirectoryIfModified(final String source, final String destinationDirectory)
786 throws IOException {
787 copyFileToDirectoryIfModified(new File(source), new File(destinationDirectory));
788 }
789
790 /**
791 * Copy file from source to destination. If <code>destinationDirectory</code> does not exist, it (and any parent
792 * directories) will be created. If a file <code>source</code> in <code>destinationDirectory</code> exists, it will
793 * be overwritten.
794 *
795 * @param source An existing <code>File</code> to copy.
796 * @param destinationDirectory A directory to copy <code>source</code> into.
797 * @throws java.io.FileNotFoundException if <code>source</code> isn't a normal file.
798 * @throws IllegalArgumentException if <code>destinationDirectory</code> isn't a directory.
799 * @throws IOException if <code>source</code> does not exist, the file in <code>destinationDirectory</code> cannot
800 * be written to, or an IO error occurs during copying.
801 */
802 public static void copyFileToDirectory(final File source, final File destinationDirectory) throws IOException {
803 if (destinationDirectory.exists() && !destinationDirectory.isDirectory()) {
804 throw new IllegalArgumentException("Destination is not a directory");
805 }
806
807 copyFile(source, new File(destinationDirectory, source.getName()));
808 }
809
810 /**
811 * Copy file from source to destination only if source is newer than the target file. If
812 * <code>destinationDirectory</code> does not exist, it (and any parent directories) will be created. If a file
813 * <code>source</code> in <code>destinationDirectory</code> exists, it will be overwritten.
814 *
815 * @param source An existing <code>File</code> to copy.
816 * @param destinationDirectory A directory to copy <code>source</code> into.
817 * @throws java.io.FileNotFoundException if <code>source</code> isn't a normal file.
818 * @throws IllegalArgumentException if <code>destinationDirectory</code> isn't a directory.
819 * @throws IOException if <code>source</code> does not exist, the file in <code>destinationDirectory</code> cannot
820 * be written to, or an IO error occurs during copying.
821 */
822 public static void copyFileToDirectoryIfModified(final File source, final File destinationDirectory)
823 throws IOException {
824 if (destinationDirectory.exists() && !destinationDirectory.isDirectory()) {
825 throw new IllegalArgumentException("Destination is not a directory");
826 }
827
828 copyFileIfModified(source, new File(destinationDirectory, source.getName()));
829 }
830
831 /**
832 * Creates a number of directories, as delivered from DirectoryScanner
833 *
834 * @param sourceBase The basedir used for the directory scan
835 * @param dirs The getIncludedDirs from the dirscanner
836 * @param destination The base dir of the output structure
837 * @throws IOException io issue
838 */
839 public static void mkDirs(final File sourceBase, String[] dirs, final File destination) throws IOException {
840 for (String dir : dirs) {
841 File src = new File(sourceBase, dir);
842 File dst = new File(destination, dir);
843 if (NioFiles.isSymbolicLink(src)) {
844 File target = NioFiles.readSymbolicLink(src);
845 NioFiles.createSymbolicLink(dst, target);
846 } else {
847 dst.mkdirs();
848 }
849 }
850 }
851
852 /**
853 * Copy file from source to destination. The directories up to <code>destination</code> will be created if they
854 * don't already exist. <code>destination</code> will be overwritten if it already exists.
855 *
856 * @param source An existing non-directory <code>File</code> to copy bytes from.
857 * @param destination A non-directory <code>File</code> to write bytes to (possibly overwriting).
858 * @throws IOException if <code>source</code> does not exist, <code>destination</code> cannot be written to, or an
859 * IO error occurs during copying.
860 * @throws java.io.FileNotFoundException if <code>destination</code> is a directory (use
861 * {@link #copyFileToDirectory}).
862 */
863 public static void copyFile(final File source, final File destination) throws IOException {
864 // check source exists
865 if (!source.exists()) {
866 final String message = "File " + source + " does not exist";
867 throw new IOException(message);
868 }
869
870 // check source != destination, see PLXUTILS-10
871 if (source.getCanonicalPath().equals(destination.getCanonicalPath())) {
872 // if they are equal, we can exit the method without doing any work
873 return;
874 }
875 mkdirsFor(destination);
876
877 doCopyFile(source, destination);
878
879 if (source.length() != destination.length()) {
880 String message = "Failed to copy full contents from " + source + " to " + destination;
881 throw new IOException(message);
882 }
883 }
884
885 private static void doCopyFile(File source, File destination) throws IOException {
886 doCopyFileUsingNewIO(source, destination);
887 }
888
889 private static void doCopyFileUsingNewIO(File source, File destination) throws IOException {
890 NioFiles.copy(source, destination);
891 }
892
893 /**
894 * Link file from destination to source. The directories up to <code>destination</code> will be created if they
895 * don't already exist. <code>destination</code> will be overwritten if it already exists.
896 *
897 * @param source An existing non-directory <code>File</code> to link to.
898 * @param destination A non-directory <code>File</code> becoming the link (possibly overwriting).
899 * @throws IOException if <code>source</code> does not exist, <code>destination</code> cannot be created, or an
900 * IO error occurs during linking.
901 * @throws java.io.FileNotFoundException if <code>destination</code> is a directory (use
902 * {@link #copyFileToDirectory}).
903 */
904 public static void linkFile(final File source, final File destination) throws IOException {
905 // check source exists
906 if (!source.exists()) {
907 final String message = "File " + source + " does not exist";
908 throw new IOException(message);
909 }
910
911 // check source != destination, see PLXUTILS-10
912 if (source.getCanonicalPath().equals(destination.getCanonicalPath())) {
913 // if they are equal, we can exit the method without doing any work
914 return;
915 }
916 mkdirsFor(destination);
917
918 NioFiles.createSymbolicLink(destination, source);
919 }
920
921 /**
922 * Copy file from source to destination only if source timestamp is later than the destination timestamp. The
923 * directories up to <code>destination</code> will be created if they don't already exist. <code>destination</code>
924 * will be overwritten if it already exists.
925 *
926 * @param source An existing non-directory <code>File</code> to copy bytes from.
927 * @param destination A non-directory <code>File</code> to write bytes to (possibly overwriting).
928 * @return true if no problem occured
929 * @throws IOException if <code>source</code> does not exist, <code>destination</code> cannot be written to, or an
930 * IO error occurs during copying.
931 */
932 public static boolean copyFileIfModified(final File source, final File destination) throws IOException {
933 if (isSourceNewerThanDestination(source, destination)) {
934 copyFile(source, destination);
935
936 return true;
937 }
938
939 return false;
940 }
941
942 /**
943 * Copies bytes from the URL <code>source</code> to a file <code>destination</code>. The directories up to
944 * <code>destination</code> will be created if they don't already exist. <code>destination</code> will be
945 * overwritten if it already exists.
946 *
947 * @param source A <code>URL</code> to copy bytes from.
948 * @param destination A non-directory <code>File</code> to write bytes to (possibly overwriting).
949 * @throws IOException if
950 * <ul>
951 * <li><code>source</code> URL cannot be opened</li>
952 * <li><code>destination</code> cannot be written to</li>
953 * <li>an IO error occurs during copying</li>
954 * </ul>
955 */
956 public static void copyURLToFile(final URL source, final File destination) throws IOException {
957 copyStreamToFile(new URLInputStreamFacade(source), destination);
958 }
959
960 /**
961 * Copies bytes from the {@link InputStream} <code>source</code> to a file <code>destination</code>. The directories
962 * up to <code>destination</code> will be created if they don't already exist. <code>destination</code> will be
963 * overwritten if it already exists.
964 *
965 * @param source An {@link InputStream} to copy bytes from. This stream is guaranteed to be closed.
966 * @param destination A non-directory <code>File</code> to write bytes to (possibly overwriting).
967 * @throws IOException if
968 * <ul>
969 * <li><code>source</code> URL cannot be opened</li>
970 * <li><code>destination</code> cannot be written to</li>
971 * <li>an IO error occurs during copying</li>
972 * </ul>
973 */
974 public static void copyStreamToFile(final InputStreamFacade source, final File destination) throws IOException {
975 mkdirsFor(destination);
976 checkCanWrite(destination);
977
978 try (InputStream input = source.getInputStream();
979 OutputStream output = Files.newOutputStream(destination.toPath())) {
980 IOUtil.copy(input, output);
981 }
982 }
983
984 private static void checkCanWrite(File destination) throws IOException {
985 // make sure we can write to destination
986 if (destination.exists() && !destination.canWrite()) {
987 final String message = "Unable to open file " + destination + " for writing.";
988 throw new IOException(message);
989 }
990 }
991
992 private static void mkdirsFor(File destination) {
993 // does destination directory exist ?
994 File parentFile = destination.getParentFile();
995 if (parentFile != null && !parentFile.exists()) {
996 parentFile.mkdirs();
997 }
998 }
999
1000 /**
1001 * Normalize a path. Eliminates "/../" and "/./" in a string. Returns <code>null</code> if the ..'s went past the
1002 * root. Eg:
1003 *
1004 * <pre>
1005 * /foo// --> /foo/
1006 * /foo/./ --> /foo/
1007 * /foo/../bar --> /bar
1008 * /foo/../bar/ --> /bar/
1009 * /foo/../bar/../baz --> /baz
1010 * //foo//./bar --> /foo/bar
1011 * /../ --> null
1012 * </pre>
1013 *
1014 * @param path the path to normalize
1015 * @return the normalized String, or <code>null</code> if too many ..'s.
1016 */
1017 public static String normalize(final String path) {
1018 String normalized = path;
1019 // Resolve occurrences of "//" in the normalized path
1020 while (true) {
1021 int index = normalized.indexOf("//");
1022 if (index < 0) {
1023 break;
1024 }
1025 normalized = normalized.substring(0, index) + normalized.substring(index + 1);
1026 }
1027
1028 // Resolve occurrences of "/./" in the normalized path
1029 while (true) {
1030 int index = normalized.indexOf("/./");
1031 if (index < 0) {
1032 break;
1033 }
1034 normalized = normalized.substring(0, index) + normalized.substring(index + 2);
1035 }
1036
1037 // Resolve occurrences of "/../" in the normalized path
1038 while (true) {
1039 int index = normalized.indexOf("/../");
1040 if (index < 0) {
1041 break;
1042 }
1043 if (index == 0) {
1044 return null; // Trying to go outside our context
1045 }
1046 int index2 = normalized.lastIndexOf('/', index - 1);
1047 normalized = normalized.substring(0, index2) + normalized.substring(index + 3);
1048 }
1049
1050 // Return the normalized path that we have completed
1051 return normalized;
1052 }
1053
1054 /**
1055 * <p>Will concatenate 2 paths. Paths with <code>..</code> will be properly handled.</p>
1056 *
1057 * Eg.,
1058 * <pre>
1059 * /a/b/c + d = /a/b/d
1060 * /a/b/c + ../d = /a/d
1061 * </pre>
1062 *
1063 * <p>Thieved from Tomcat sources...</p>
1064 *
1065 * @param lookupPath a path
1066 * @param path the path to concatenate
1067 * @return The concatenated paths, or null if error occurs
1068 */
1069 public static String catPath(final String lookupPath, final String path) {
1070 // Cut off the last slash and everything beyond
1071 int index = lookupPath.lastIndexOf("/");
1072 String lookup = lookupPath.substring(0, index);
1073 String pth = path;
1074
1075 // Deal with .. by chopping dirs off the lookup path
1076 while (pth.startsWith("../")) {
1077 if (lookup.length() > 0) {
1078 index = lookup.lastIndexOf("/");
1079 lookup = lookup.substring(0, index);
1080 } else {
1081 // More ..'s than dirs, return null
1082 return null;
1083 }
1084
1085 index = pth.indexOf("../") + 3;
1086 pth = pth.substring(index);
1087 }
1088
1089 return new StringBuffer(lookup).append("/").append(pth).toString();
1090 }
1091
1092 /**
1093 * Resolve a file <code>filename</code> to it's canonical form. If <code>filename</code> is relative (doesn't start
1094 * with <code>/</code>), it will be resolved relative to <code>baseFile</code>, otherwise it is treated as a normal
1095 * root-relative path.
1096 *
1097 * @param baseFile Where to resolve <code>filename</code> from, if <code>filename</code> is relative.
1098 * @param filename Absolute or relative file path to resolve.
1099 * @return The canonical <code>File</code> of <code>filename</code>.
1100 */
1101 public static File resolveFile(final File baseFile, String filename) {
1102 String filenm = filename;
1103 if ('/' != File.separatorChar) {
1104 filenm = filename.replace('/', File.separatorChar);
1105 }
1106
1107 if ('\\' != File.separatorChar) {
1108 filenm = filename.replace('\\', File.separatorChar);
1109 }
1110
1111 // deal with absolute files
1112 if (filenm.startsWith(File.separator) || (Os.isFamily(Os.FAMILY_WINDOWS) && filenm.indexOf(":") > 0)) {
1113 File file = new File(filenm);
1114
1115 try {
1116 file = file.getCanonicalFile();
1117 } catch (final IOException ioe) {
1118 // nop
1119 }
1120
1121 return file;
1122 }
1123 // FIXME: I'm almost certain this // removal is unnecessary, as getAbsoluteFile() strips
1124 // them. However, I'm not sure about this UNC stuff. (JT)
1125 final char[] chars = filename.toCharArray();
1126 final StringBuilder sb = new StringBuilder();
1127
1128 // remove duplicate file separators in succession - except
1129 // on win32 at start of filename as UNC filenames can
1130 // be \\AComputer\AShare\myfile.txt
1131 int start = 0;
1132 if ('\\' == File.separatorChar) {
1133 sb.append(filenm.charAt(0));
1134 start++;
1135 }
1136
1137 for (int i = start; i < chars.length; i++) {
1138 final boolean doubleSeparator = File.separatorChar == chars[i] && File.separatorChar == chars[i - 1];
1139
1140 if (!doubleSeparator) {
1141 sb.append(chars[i]);
1142 }
1143 }
1144
1145 filenm = sb.toString();
1146
1147 // must be relative
1148 File file = (new File(baseFile, filenm)).getAbsoluteFile();
1149
1150 try {
1151 file = file.getCanonicalFile();
1152 } catch (final IOException ioe) {
1153 // nop
1154 }
1155
1156 return file;
1157 }
1158
1159 /**
1160 * Delete a file. If file is directory delete it and all sub-directories.
1161 *
1162 * @param file the file path
1163 * @throws IOException if any
1164 */
1165 public static void forceDelete(final String file) throws IOException {
1166 forceDelete(new File(file));
1167 }
1168
1169 /**
1170 * Delete a file. If file is directory delete it and all sub-directories.
1171 *
1172 * @param file a file
1173 * @throws IOException if any
1174 */
1175 public static void forceDelete(final File file) throws IOException {
1176 if (file.isDirectory()) {
1177 deleteDirectory(file);
1178 } else {
1179 /*
1180 * NOTE: Always try to delete the file even if it appears to be non-existent. This will ensure that a
1181 * symlink whose target does not exist is deleted, too.
1182 */
1183 boolean filePresent = file.getCanonicalFile().exists();
1184 if (!deleteFile(file) && filePresent) {
1185 final String message = "File " + file + " unable to be deleted.";
1186 throw new IOException(message);
1187 }
1188 }
1189 }
1190
1191 /**
1192 * Accommodate Windows bug encountered in both Sun and IBM JDKs. Others possible. If the delete does not work, call
1193 * System.gc(), wait a little and try again.
1194 *
1195 * @param file a file
1196 * @throws IOException if any
1197 */
1198 private static boolean deleteFile(File file) throws IOException {
1199 if (file.isDirectory()) {
1200 throw new IOException("File " + file + " isn't a file.");
1201 }
1202
1203 if (!file.delete()) {
1204 if (Os.isFamily(Os.FAMILY_WINDOWS)) {
1205 file = file.getCanonicalFile();
1206 System.gc();
1207 }
1208
1209 try {
1210 Thread.sleep(10);
1211 return file.delete();
1212 } catch (InterruptedException ignore) {
1213 return file.delete();
1214 }
1215 }
1216
1217 return true;
1218 }
1219
1220 /**
1221 * Schedule a file to be deleted when JVM exits. If file is directory delete it and all sub-directories.
1222 *
1223 * @param file a file
1224 * @throws IOException if any
1225 */
1226 public static void forceDeleteOnExit(final File file) throws IOException {
1227 if (!file.exists()) {
1228 return;
1229 }
1230
1231 if (file.isDirectory()) {
1232 deleteDirectoryOnExit(file);
1233 } else {
1234 file.deleteOnExit();
1235 }
1236 }
1237
1238 /**
1239 * Recursively schedule directory for deletion on JVM exit.
1240 *
1241 * @param directory a directory
1242 * @throws IOException if any
1243 */
1244 private static void deleteDirectoryOnExit(final File directory) throws IOException {
1245 if (!directory.exists()) {
1246 return;
1247 }
1248 directory.deleteOnExit(); // The hook reverses the list
1249
1250 cleanDirectoryOnExit(directory);
1251 }
1252
1253 /**
1254 * Clean a directory without deleting it.
1255 *
1256 * @param directory a directory
1257 * @throws IOException if any
1258 */
1259 private static void cleanDirectoryOnExit(final File directory) throws IOException {
1260 if (!directory.exists()) {
1261 final String message = directory + " does not exist";
1262 throw new IllegalArgumentException(message);
1263 }
1264
1265 if (!directory.isDirectory()) {
1266 final String message = directory + " is not a directory";
1267 throw new IllegalArgumentException(message);
1268 }
1269
1270 IOException exception = null;
1271
1272 final File[] files = directory.listFiles();
1273 for (final File file : files) {
1274 try {
1275 forceDeleteOnExit(file);
1276 } catch (final IOException ioe) {
1277 exception = ioe;
1278 }
1279 }
1280
1281 if (null != exception) {
1282 throw exception;
1283 }
1284 }
1285
1286 /**
1287 * Make a directory.
1288 *
1289 * @param file not null
1290 * @throws IOException If there already exists a file with specified name or the directory is unable to be created
1291 * @throws IllegalArgumentException if the file contains illegal Windows characters under Windows OS.
1292 * @see #INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME
1293 */
1294 public static void forceMkdir(final File file) throws IOException {
1295 if (Os.isFamily(Os.FAMILY_WINDOWS)) {
1296 if (!isValidWindowsFileName(file)) {
1297 throw new IllegalArgumentException("The file (" + file.getAbsolutePath()
1298 + ") cannot contain any of the following characters: \n"
1299 + StringUtils.join(INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME, " "));
1300 }
1301 }
1302
1303 if (file.exists()) {
1304 if (file.isFile()) {
1305 final String message =
1306 "File " + file + " exists and is " + "not a directory. Unable to create directory.";
1307 throw new IOException(message);
1308 }
1309 } else {
1310 if (false == file.mkdirs()) {
1311 final String message = "Unable to create directory " + file;
1312 throw new IOException(message);
1313 }
1314 }
1315 }
1316
1317 /**
1318 * Recursively delete a directory.
1319 *
1320 * @param directory a directory
1321 * @throws IOException if any
1322 */
1323 public static void deleteDirectory(final String directory) throws IOException {
1324 deleteDirectory(new File(directory));
1325 }
1326
1327 /**
1328 * Recursively delete a directory.
1329 *
1330 * @param directory a directory
1331 * @throws IOException if any
1332 */
1333 public static void deleteDirectory(final File directory) throws IOException {
1334 if (!directory.exists()) {
1335 return;
1336 }
1337
1338 /*
1339 * try delete the directory before its contents, which will take care of any directories that are really
1340 * symbolic links.
1341 */
1342 if (directory.delete()) {
1343 return;
1344 }
1345
1346 cleanDirectory(directory);
1347 if (!directory.delete()) {
1348 final String message = "Directory " + directory + " unable to be deleted.";
1349 throw new IOException(message);
1350 }
1351 }
1352
1353 /**
1354 * Clean a directory without deleting it.
1355 *
1356 * @param directory a directory
1357 * @throws IOException if any
1358 */
1359 public static void cleanDirectory(final String directory) throws IOException {
1360 cleanDirectory(new File(directory));
1361 }
1362
1363 /**
1364 * Clean a directory without deleting it.
1365 *
1366 * @param directory a directory
1367 * @throws IOException if any
1368 */
1369 public static void cleanDirectory(final File directory) throws IOException {
1370 if (!directory.exists()) {
1371 final String message = directory + " does not exist";
1372 throw new IllegalArgumentException(message);
1373 }
1374
1375 if (!directory.isDirectory()) {
1376 final String message = directory + " is not a directory";
1377 throw new IllegalArgumentException(message);
1378 }
1379
1380 IOException exception = null;
1381
1382 final File[] files = directory.listFiles();
1383
1384 if (files == null) {
1385 return;
1386 }
1387
1388 for (final File file : files) {
1389 try {
1390 forceDelete(file);
1391 } catch (final IOException ioe) {
1392 exception = ioe;
1393 }
1394 }
1395
1396 if (null != exception) {
1397 throw exception;
1398 }
1399 }
1400
1401 /**
1402 * Recursively count size of a directory.
1403 *
1404 * @param directory a directory
1405 * @return size of directory in bytes.
1406 */
1407 public static long sizeOfDirectory(final String directory) {
1408 return sizeOfDirectory(new File(directory));
1409 }
1410
1411 /**
1412 * Recursively count size of a directory.
1413 *
1414 * @param directory a directory
1415 * @return size of directory in bytes.
1416 */
1417 public static long sizeOfDirectory(final File directory) {
1418 if (!directory.exists()) {
1419 final String message = directory + " does not exist";
1420 throw new IllegalArgumentException(message);
1421 }
1422
1423 if (!directory.isDirectory()) {
1424 final String message = directory + " is not a directory";
1425 throw new IllegalArgumentException(message);
1426 }
1427
1428 long size = 0;
1429
1430 final File[] files = directory.listFiles();
1431 for (final File file : files) {
1432 if (file.isDirectory()) {
1433 size += sizeOfDirectory(file);
1434 } else {
1435 size += file.length();
1436 }
1437 }
1438
1439 return size;
1440 }
1441
1442 /**
1443 * Return the files contained in the directory, using inclusion and exclusion Ant patterns, including the directory
1444 * name in each of the files
1445 *
1446 * @param directory the directory to scan
1447 * @param includes the includes pattern, comma separated
1448 * @param excludes the excludes pattern, comma separated
1449 * @return a list of File objects
1450 * @throws IOException io issue
1451 * @see #getFileNames(File, String, String, boolean)
1452 */
1453 public static List<File> getFiles(File directory, String includes, String excludes) throws IOException {
1454 return getFiles(directory, includes, excludes, true);
1455 }
1456
1457 /**
1458 * Return the files contained in the directory, using inclusion and exclusion Ant patterns
1459 *
1460 * @param directory the directory to scan
1461 * @param includes the includes pattern, comma separated
1462 * @param excludes the excludes pattern, comma separated
1463 * @param includeBasedir true to include the base dir in each file
1464 * @return a list of File objects
1465 * @throws IOException io issue
1466 * @see #getFileNames(File, String, String, boolean)
1467 */
1468 public static List<File> getFiles(File directory, String includes, String excludes, boolean includeBasedir)
1469 throws IOException {
1470 List<String> fileNames = getFileNames(directory, includes, excludes, includeBasedir);
1471
1472 List<File> files = new ArrayList<File>();
1473
1474 for (String filename : fileNames) {
1475 files.add(new File(filename));
1476 }
1477
1478 return files;
1479 }
1480
1481 /**
1482 * Return a list of files as String depending options. This method use case sensitive file name.
1483 *
1484 * @param directory the directory to scan
1485 * @param includes the includes pattern, comma separated
1486 * @param excludes the excludes pattern, comma separated
1487 * @param includeBasedir true to include the base dir in each String of file
1488 * @return a list of files as String
1489 * @throws IOException io issue
1490 */
1491 public static List<String> getFileNames(File directory, String includes, String excludes, boolean includeBasedir)
1492 throws IOException {
1493 return getFileNames(directory, includes, excludes, includeBasedir, true);
1494 }
1495
1496 /**
1497 * Return a list of files as String depending options.
1498 *
1499 * @param directory the directory to scan
1500 * @param includes the includes pattern, comma separated
1501 * @param excludes the excludes pattern, comma separated
1502 * @param includeBasedir true to include the base dir in each String of file
1503 * @param isCaseSensitive true if case sensitive
1504 * @return a list of files as String
1505 * @throws IOException io issue
1506 */
1507 public static List<String> getFileNames(
1508 File directory, String includes, String excludes, boolean includeBasedir, boolean isCaseSensitive)
1509 throws IOException {
1510 return getFileAndDirectoryNames(directory, includes, excludes, includeBasedir, isCaseSensitive, true, false);
1511 }
1512
1513 /**
1514 * Return a list of directories as String depending options. This method use case sensitive file name.
1515 *
1516 * @param directory the directory to scan
1517 * @param includes the includes pattern, comma separated
1518 * @param excludes the excludes pattern, comma separated
1519 * @param includeBasedir true to include the base dir in each String of file
1520 * @return a list of directories as String
1521 * @throws IOException io issue
1522 */
1523 public static List<String> getDirectoryNames(
1524 File directory, String includes, String excludes, boolean includeBasedir) throws IOException {
1525 return getDirectoryNames(directory, includes, excludes, includeBasedir, true);
1526 }
1527
1528 /**
1529 * Return a list of directories as String depending options.
1530 *
1531 * @param directory the directory to scan
1532 * @param includes the includes pattern, comma separated
1533 * @param excludes the excludes pattern, comma separated
1534 * @param includeBasedir true to include the base dir in each String of file
1535 * @param isCaseSensitive true if case sensitive
1536 * @return a list of directories as String
1537 * @throws IOException io issue
1538 */
1539 public static List<String> getDirectoryNames(
1540 File directory, String includes, String excludes, boolean includeBasedir, boolean isCaseSensitive)
1541 throws IOException {
1542 return getFileAndDirectoryNames(directory, includes, excludes, includeBasedir, isCaseSensitive, false, true);
1543 }
1544
1545 /**
1546 * Return a list of files as String depending options.
1547 *
1548 * @param directory the directory to scan
1549 * @param includes the includes pattern, comma separated
1550 * @param excludes the excludes pattern, comma separated
1551 * @param includeBasedir true to include the base dir in each String of file
1552 * @param isCaseSensitive true if case sensitive
1553 * @param getFiles true if get files
1554 * @param getDirectories true if get directories
1555 * @return a list of files as String
1556 * @throws IOException io issue
1557 */
1558 public static List<String> getFileAndDirectoryNames(
1559 File directory,
1560 String includes,
1561 String excludes,
1562 boolean includeBasedir,
1563 boolean isCaseSensitive,
1564 boolean getFiles,
1565 boolean getDirectories)
1566 throws IOException {
1567 DirectoryScanner scanner = new DirectoryScanner();
1568
1569 scanner.setBasedir(directory);
1570
1571 if (includes != null) {
1572 scanner.setIncludes(StringUtils.split(includes, ","));
1573 }
1574
1575 if (excludes != null) {
1576 scanner.setExcludes(StringUtils.split(excludes, ","));
1577 }
1578
1579 scanner.setCaseSensitive(isCaseSensitive);
1580
1581 scanner.scan();
1582
1583 List<String> list = new ArrayList<String>();
1584
1585 if (getFiles) {
1586 String[] files = scanner.getIncludedFiles();
1587
1588 for (String file : files) {
1589 if (includeBasedir) {
1590 list.add(directory + FileUtils.FS + file);
1591 } else {
1592 list.add(file);
1593 }
1594 }
1595 }
1596
1597 if (getDirectories) {
1598 String[] directories = scanner.getIncludedDirectories();
1599
1600 for (String directory1 : directories) {
1601 if (includeBasedir) {
1602 list.add(directory + FileUtils.FS + directory1);
1603 } else {
1604 list.add(directory1);
1605 }
1606 }
1607 }
1608
1609 return list;
1610 }
1611
1612 /**
1613 * Copy a directory to an other one.
1614 *
1615 * @param sourceDirectory the source dir
1616 * @param destinationDirectory the target dir
1617 * @throws IOException if any
1618 */
1619 public static void copyDirectory(File sourceDirectory, File destinationDirectory) throws IOException {
1620 copyDirectory(sourceDirectory, destinationDirectory, "**", null);
1621 }
1622
1623 /**
1624 * Copy a directory to an other one.
1625 *
1626 * @param sourceDirectory the source dir
1627 * @param destinationDirectory the target dir
1628 * @param includes include pattern
1629 * @param excludes exclude pattern
1630 * @throws IOException if any
1631 * @see #getFiles(File, String, String)
1632 */
1633 public static void copyDirectory(File sourceDirectory, File destinationDirectory, String includes, String excludes)
1634 throws IOException {
1635 if (!sourceDirectory.exists()) {
1636 return;
1637 }
1638
1639 List<File> files = getFiles(sourceDirectory, includes, excludes);
1640
1641 for (File file : files) {
1642 copyFileToDirectory(file, destinationDirectory);
1643 }
1644 }
1645
1646 /**
1647 * <p>Copies a entire directory layout : no files will be copied only directories</p>
1648 *
1649 * Note:
1650 * <ul>
1651 * <li>It will include empty directories.
1652 * <li>The <code>sourceDirectory</code> must exists.
1653 * </ul>
1654 *
1655 * @param sourceDirectory the source dir
1656 * @param destinationDirectory the target dir
1657 * @param includes include pattern
1658 * @param excludes exclude pattern
1659 * @throws IOException if any
1660 * @since 1.5.7
1661 */
1662 public static void copyDirectoryLayout(
1663 File sourceDirectory, File destinationDirectory, String[] includes, String[] excludes) throws IOException {
1664 if (sourceDirectory == null) {
1665 throw new IOException("source directory can't be null.");
1666 }
1667
1668 if (destinationDirectory == null) {
1669 throw new IOException("destination directory can't be null.");
1670 }
1671
1672 if (sourceDirectory.equals(destinationDirectory)) {
1673 throw new IOException("source and destination are the same directory.");
1674 }
1675
1676 if (!sourceDirectory.exists()) {
1677 throw new IOException("Source directory doesn't exists (" + sourceDirectory.getAbsolutePath() + ").");
1678 }
1679
1680 DirectoryScanner scanner = new DirectoryScanner();
1681
1682 scanner.setBasedir(sourceDirectory);
1683
1684 if (includes != null && includes.length >= 1) {
1685 scanner.setIncludes(includes);
1686 } else {
1687 scanner.setIncludes(new String[] {"**"});
1688 }
1689
1690 if (excludes != null && excludes.length >= 1) {
1691 scanner.setExcludes(excludes);
1692 }
1693
1694 scanner.addDefaultExcludes();
1695 scanner.scan();
1696 List<String> includedDirectories = Arrays.asList(scanner.getIncludedDirectories());
1697
1698 for (String name : includedDirectories) {
1699 File source = new File(sourceDirectory, name);
1700
1701 if (source.equals(sourceDirectory)) {
1702 continue;
1703 }
1704
1705 File destination = new File(destinationDirectory, name);
1706 destination.mkdirs();
1707 }
1708 }
1709
1710 /**
1711 * <p>Copies a entire directory structure.</p>
1712 *
1713 * Note:
1714 * <ul>
1715 * <li>It will include empty directories.
1716 * <li>The <code>sourceDirectory</code> must exists.
1717 * </ul>
1718 *
1719 * @param sourceDirectory the source dir
1720 * @param destinationDirectory the target dir
1721 * @throws IOException if any
1722 */
1723 public static void copyDirectoryStructure(File sourceDirectory, File destinationDirectory) throws IOException {
1724 copyDirectoryStructure(sourceDirectory, destinationDirectory, destinationDirectory, false);
1725 }
1726
1727 /**
1728 * <p>Copies an entire directory structure but only source files with timestamp later than the destinations'.</p>
1729 *
1730 * Note:
1731 * <ul>
1732 * <li>It will include empty directories.
1733 * <li>The <code>sourceDirectory</code> must exists.
1734 * </ul>
1735 *
1736 * @param sourceDirectory the source dir
1737 * @param destinationDirectory the target dir
1738 * @throws IOException if any
1739 */
1740 public static void copyDirectoryStructureIfModified(File sourceDirectory, File destinationDirectory)
1741 throws IOException {
1742 copyDirectoryStructure(sourceDirectory, destinationDirectory, destinationDirectory, true);
1743 }
1744
1745 private static void copyDirectoryStructure(
1746 File sourceDirectory, File destinationDirectory, File rootDestinationDirectory, boolean onlyModifiedFiles)
1747 throws IOException {
1748 if (sourceDirectory == null) {
1749 throw new IOException("source directory can't be null.");
1750 }
1751
1752 if (destinationDirectory == null) {
1753 throw new IOException("destination directory can't be null.");
1754 }
1755
1756 if (sourceDirectory.equals(destinationDirectory)) {
1757 throw new IOException("source and destination are the same directory.");
1758 }
1759
1760 if (!sourceDirectory.exists()) {
1761 throw new IOException("Source directory doesn't exists (" + sourceDirectory.getAbsolutePath() + ").");
1762 }
1763
1764 File[] files = sourceDirectory.listFiles();
1765
1766 String sourcePath = sourceDirectory.getAbsolutePath();
1767
1768 for (File file : files) {
1769 if (file.equals(rootDestinationDirectory)) {
1770 // We don't copy the destination directory in itself
1771 continue;
1772 }
1773
1774 String dest = file.getAbsolutePath();
1775
1776 dest = dest.substring(sourcePath.length() + 1);
1777
1778 File destination = new File(destinationDirectory, dest);
1779
1780 if (file.isFile()) {
1781 destination = destination.getParentFile();
1782
1783 if (onlyModifiedFiles) {
1784 copyFileToDirectoryIfModified(file, destination);
1785 } else {
1786 copyFileToDirectory(file, destination);
1787 }
1788 } else if (file.isDirectory()) {
1789 if (!destination.exists() && !destination.mkdirs()) {
1790 throw new IOException(
1791 "Could not create destination directory '" + destination.getAbsolutePath() + "'.");
1792 }
1793
1794 copyDirectoryStructure(file, destination, rootDestinationDirectory, onlyModifiedFiles);
1795 } else {
1796 throw new IOException("Unknown file type: " + file.getAbsolutePath());
1797 }
1798 }
1799 }
1800
1801 /**
1802 * <p>Renames a file, even if that involves crossing file system boundaries.</p>
1803 *
1804 * <p>This will remove <code>to</code> (if it exists), ensure that <code>to</code>'s parent directory exists and move
1805 * <code>from</code>, which involves deleting <code>from</code> as well.</p>
1806 *
1807 * @param from the file to move
1808 * @param to the new file name
1809 * @throws IOException if anything bad happens during this process. Note that <code>to</code> may have been deleted
1810 * already when this happens.
1811 */
1812 public static void rename(File from, File to) throws IOException {
1813 if (to.exists() && !to.delete()) {
1814 throw new IOException("Failed to delete " + to + " while trying to rename " + from);
1815 }
1816
1817 File parent = to.getParentFile();
1818 if (parent != null && !parent.exists() && !parent.mkdirs()) {
1819 throw new IOException("Failed to create directory " + parent + " while trying to rename " + from);
1820 }
1821
1822 if (!from.renameTo(to)) {
1823 copyFile(from, to);
1824 if (!from.delete()) {
1825 throw new IOException("Failed to delete " + from + " while trying to rename it.");
1826 }
1827 }
1828 }
1829
1830 /**
1831 * <p>Create a temporary file in a given directory.</p>
1832 *
1833 * <p>The file denoted by the returned abstract pathname did not exist before this method was invoked, any subsequent
1834 * invocation of this method will yield a different file name.</p>
1835 *
1836 * <p>The filename is prefixNNNNNsuffix where NNNN is a random number</p>
1837 *
1838 * <p>This method is different to {@link File#createTempFile(String, String, File)} of JDK 1.2 as it doesn't create the
1839 * file itself. It uses the location pointed to by java.io.tmpdir when the parentDir attribute is null.</p>
1840 *
1841 * <p>To delete automatically the file created by this method, use the {@link File#deleteOnExit()} method.</p>
1842 *
1843 * @param prefix prefix before the random number
1844 * @param suffix file extension; include the '.'
1845 * @param parentDir Directory to create the temporary file in <code>-java.io.tmpdir</code> used if not specificed
1846 * @return a File reference to the new temporary file.
1847 */
1848 public static File createTempFile(String prefix, String suffix, File parentDir) {
1849 File result = null;
1850 String parent = System.getProperty("java.io.tmpdir");
1851 if (parentDir != null) {
1852 parent = parentDir.getPath();
1853 }
1854 DecimalFormat fmt = new DecimalFormat("#####");
1855 SecureRandom secureRandom = new SecureRandom();
1856 long secureInitializer = secureRandom.nextLong();
1857 Random rand = new Random(secureInitializer + Runtime.getRuntime().freeMemory());
1858 synchronized (rand) {
1859 do {
1860 result = new File(parent, prefix + fmt.format(Math.abs(rand.nextInt())) + suffix);
1861 } while (result.exists());
1862 }
1863
1864 return result;
1865 }
1866
1867 /**
1868 * <b>If wrappers is null or empty, the file will be copy only if {@code to.lastModified() < from.lastModified()}</b>
1869 *
1870 * @param from the file to copy
1871 * @param to the destination file
1872 * @param encoding the file output encoding (only if wrappers is not empty)
1873 * @param wrappers array of {@link FilterWrapper}
1874 * @throws IOException if an IO error occurs during copying or filtering
1875 */
1876 public static void copyFile(File from, File to, String encoding, FilterWrapper[] wrappers) throws IOException {
1877 copyFile(from, to, encoding, wrappers, false);
1878 }
1879
1880 public abstract static class FilterWrapper {
1881 public abstract Reader getReader(Reader fileReader);
1882 }
1883
1884 /**
1885 * <b>If wrappers is null or empty, the file will be copy only if {@code to.lastModified() < from.lastModified()}, if overwrite is true</b>
1886 *
1887 * @param from the file to copy
1888 * @param to the destination file
1889 * @param encoding the file output encoding (only if wrappers is not empty)
1890 * @param wrappers array of {@link FilterWrapper}
1891 * @param overwrite if true and wrappers is null or empty, the file will be copied even if {@code to.lastModified() < from.lastModified()}
1892 * @throws IOException if an IO error occurs during copying or filtering
1893 * @since 1.5.2
1894 */
1895 public static void copyFile(File from, File to, String encoding, FilterWrapper[] wrappers, boolean overwrite)
1896 throws IOException {
1897 if (wrappers != null && wrappers.length > 0) {
1898 // buffer so it isn't reading a byte at a time!
1899 Reader fileReader = null;
1900 Writer fileWriter = null;
1901 try {
1902 if (encoding == null || encoding.length() < 1) {
1903 fileReader = Files.newBufferedReader(from.toPath());
1904 fileWriter = Files.newBufferedWriter(to.toPath());
1905 } else {
1906 OutputStream outstream = Files.newOutputStream(to.toPath());
1907
1908 fileReader = Files.newBufferedReader(from.toPath(), Charset.forName(encoding));
1909
1910 fileWriter = new OutputStreamWriter(outstream, encoding);
1911 }
1912
1913 Reader reader = fileReader;
1914 for (FilterWrapper wrapper : wrappers) {
1915 reader = wrapper.getReader(reader);
1916 }
1917
1918 IOUtil.copy(reader, fileWriter);
1919 fileWriter.close();
1920 fileWriter = null;
1921 fileReader.close();
1922 fileReader = null;
1923 } finally {
1924 IOUtil.close(fileReader);
1925 IOUtil.close(fileWriter);
1926 }
1927 } else {
1928 if (isSourceNewerThanDestination(from, to) || overwrite) {
1929 copyFile(from, to);
1930 }
1931 }
1932 }
1933
1934 private static boolean isSourceNewerThanDestination(File source, File destination) {
1935 return (destination.lastModified() == 0L && source.lastModified() == 0L)
1936 || destination.lastModified() < source.lastModified();
1937 }
1938
1939 /**
1940 * Note: the file content is read with platform encoding
1941 *
1942 * @param file the file
1943 * @return a List containing every every line not starting with # and not empty
1944 * @throws IOException if any
1945 */
1946 public static List<String> loadFile(File file) throws IOException {
1947 final List<String> lines = new ArrayList<String>();
1948
1949 if (file.exists()) {
1950 try (BufferedReader reader = Files.newBufferedReader(file.toPath())) {
1951 for (String line = reader.readLine(); line != null; line = reader.readLine()) {
1952 line = line.trim();
1953
1954 if (!line.startsWith("#") && line.length() != 0) {
1955 lines.add(line);
1956 }
1957 }
1958 }
1959 }
1960
1961 return lines;
1962 }
1963
1964 /**
1965 * For Windows OS, check if the file name contains any of the following characters:
1966 * <code>":", "*", "?", "\"", "<", ">", "|"</code>
1967 *
1968 * @param f not null file
1969 * @return <code>false</code> if the file path contains any of forbidden Windows characters, <code>true</code> if
1970 * the Os is not Windows or if the file path respect the Windows constraints.
1971 * @see #INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME
1972 * @since 1.5.2
1973 */
1974 public static boolean isValidWindowsFileName(File f) {
1975 if (Os.isFamily(Os.FAMILY_WINDOWS)) {
1976 if (StringUtils.indexOfAny(f.getName(), INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME) != -1) {
1977 return false;
1978 }
1979
1980 File parentFile = f.getParentFile();
1981 if (parentFile != null) {
1982 return isValidWindowsFileName(parentFile);
1983 }
1984 }
1985
1986 return true;
1987 }
1988 }