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