View Javadoc
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      * (&quot;&quot;) 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    &rarr; foo
634      * a\b\c.jpg  &rarr; a\b\c
635      * a\b\c      &rarr; 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    &rarr; "txt"
660      * a\b\c.jpg  &rarr; "jpg"
661      * a\b\c      &rarr; ""
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//               &rarr;     /foo/
881      * /foo/./              &rarr;     /foo/
882      * /foo/../bar          &rarr;     /bar
883      * /foo/../bar/         &rarr;     /bar/
884      * /foo/../bar/../baz   &rarr;     /baz
885      * //foo//./bar         &rarr;     /foo/bar
886      * /../                 &rarr;     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() &lt; 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() &lt; 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() &lt; 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 }